Fix merge of thirdparties (pb when merging codes and error management)

This commit is contained in:
Laurent Destailleur 2018-01-31 18:01:56 +01:00
parent a6f12eebe3
commit 14ecdce650
5 changed files with 298 additions and 51 deletions

View File

@ -5849,7 +5849,7 @@ abstract class CommonObject
* @param int $dest_id New thirdparty id (the thirdparty that will received element of the other) * @param int $dest_id New thirdparty id (the thirdparty that will received element of the other)
* @param string[] $tables Tables that need to be changed * @param string[] $tables Tables that need to be changed
* @param int $ignoreerrors Ignore errors. Return true even if errors. We need this when replacement can fails like for categories (categorie of old thirdparty may already exists on new one) * @param int $ignoreerrors Ignore errors. Return true even if errors. We need this when replacement can fails like for categories (categorie of old thirdparty may already exists on new one)
* @return bool * @return bool True if success, False if error
*/ */
public static function commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors=0) public static function commonReplaceThirdparty(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
{ {

View File

@ -119,7 +119,7 @@ if (empty($reshook))
{ {
$object->fetch($socid); $object->fetch($socid);
$errors = 0; $error = 0;
$soc_origin_id = GETPOST('soc_origin', 'int'); $soc_origin_id = GETPOST('soc_origin', 'int');
$soc_origin = new Societe($db); $soc_origin = new Societe($db);
@ -131,13 +131,13 @@ if (empty($reshook))
} }
else else
{ {
if (!$errors && $soc_origin->fetch($soc_origin_id) < 1) if (!$error && $soc_origin->fetch($soc_origin_id) < 1)
{ {
setEventMessages($langs->trans('ErrorRecordNotFound'), null, 'errors'); setEventMessages($langs->trans('ErrorRecordNotFound'), null, 'errors');
$errors++; $error++;
} }
if (!$errors) if (!$error)
{ {
// TODO Move the merge function into class of object. // TODO Move the merge function into class of object.
@ -184,48 +184,67 @@ if (empty($reshook))
$suppcats = $static_cat->containing($soc_origin->id, 'supplier', 'id'); $suppcats = $static_cat->containing($soc_origin->id, 'supplier', 'id');
$object->setCategories($suppcats, 'supplier'); $object->setCategories($suppcats, 'supplier');
// If thirdparty has a new code that is same than origin, we clean origin code to avoid duplicate key from database unique keys.
if ($soc_origin->code_client == $object->code_client
|| $soc_origin->code_fournisseur == $object->code_fournisseur
|| $soc_origin->barcode == $object->barcode)
{
dol_syslog("We clean customer and supplier code so we will be able to make the update of target");
$soc_origin->code_client = '';
$soc_origin->code_fournisseur = '';
$soc_origin->barcode = '';
$soc_origin->update($soc_origin->id, $user, 0, 1, 1, 'merge');
}
// Update // Update
$object->update($object->id, $user, 0); $object->update($object->id, $user, 0, 1, 1, 'merge');
if ($result < 0)
{
$error++;
}
// Move links // Move links
$objects = array( if (! $error)
'Adherent' => '/adherents/class/adherent.class.php',
'Societe' => '/societe/class/societe.class.php',
'Categorie' => '/categories/class/categorie.class.php',
'ActionComm' => '/comm/action/class/actioncomm.class.php',
'Propal' => '/comm/propal/class/propal.class.php',
'Commande' => '/commande/class/commande.class.php',
'Facture' => '/compta/facture/class/facture.class.php',
'FactureRec' => '/compta/facture/class/facture-rec.class.php',
'LignePrelevement' => '/compta/prelevement/class/ligneprelevement.class.php',
'Contact' => '/contact/class/contact.class.php',
'Contrat' => '/contrat/class/contrat.class.php',
'Expedition' => '/expedition/class/expedition.class.php',
'Fichinter' => '/fichinter/class/fichinter.class.php',
'CommandeFournisseur' => '/fourn/class/fournisseur.commande.class.php',
'FactureFournisseur' => '/fourn/class/fournisseur.facture.class.php',
'SupplierProposal' => '/supplier_proposal/class/supplier_proposal.class.php',
'ProductFournisseur' => '/fourn/class/fournisseur.product.class.php',
'Livraison' => '/livraison/class/livraison.class.php',
'Product' => '/product/class/product.class.php',
'Project' => '/projet/class/project.class.php',
'User' => '/user/class/user.class.php',
);
//First, all core objects must update their tables
foreach ($objects as $object_name => $object_file)
{ {
require_once DOL_DOCUMENT_ROOT.$object_file; $objects = array(
'Adherent' => '/adherents/class/adherent.class.php',
'Societe' => '/societe/class/societe.class.php',
'Categorie' => '/categories/class/categorie.class.php',
'ActionComm' => '/comm/action/class/actioncomm.class.php',
'Propal' => '/comm/propal/class/propal.class.php',
'Commande' => '/commande/class/commande.class.php',
'Facture' => '/compta/facture/class/facture.class.php',
'FactureRec' => '/compta/facture/class/facture-rec.class.php',
'LignePrelevement' => '/compta/prelevement/class/ligneprelevement.class.php',
'Contact' => '/contact/class/contact.class.php',
'Contrat' => '/contrat/class/contrat.class.php',
'Expedition' => '/expedition/class/expedition.class.php',
'Fichinter' => '/fichinter/class/fichinter.class.php',
'CommandeFournisseur' => '/fourn/class/fournisseur.commande.class.php',
'FactureFournisseur' => '/fourn/class/fournisseur.facture.class.php',
'SupplierProposal' => '/supplier_proposal/class/supplier_proposal.class.php',
'ProductFournisseur' => '/fourn/class/fournisseur.product.class.php',
'Livraison' => '/livraison/class/livraison.class.php',
'Product' => '/product/class/product.class.php',
'Project' => '/projet/class/project.class.php',
'User' => '/user/class/user.class.php',
);
if (!$errors && !$object_name::replaceThirdparty($db, $soc_origin->id, $object->id)) //First, all core objects must update their tables
foreach ($objects as $object_name => $object_file)
{ {
$errors++; require_once DOL_DOCUMENT_ROOT.$object_file;
setEventMessages($db->lasterror(), null, 'errors');
if (!$error && !$object_name::replaceThirdparty($db, $soc_origin->id, $object->id))
{
$error++;
setEventMessages($db->lasterror(), null, 'errors');
}
} }
} }
//External modules should update their ones too // External modules should update their ones too
if (!$errors) if (! $error)
{ {
$reshook = $hookmanager->executeHooks('replaceThirdparty', array( $reshook = $hookmanager->executeHooks('replaceThirdparty', array(
'soc_origin' => $soc_origin->id, 'soc_origin' => $soc_origin->id,
@ -235,7 +254,7 @@ if (empty($reshook))
if ($reshook < 0) if ($reshook < 0)
{ {
setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
$errors++; $error++;
} }
} }
@ -254,16 +273,16 @@ if (empty($reshook))
// End call triggers // End call triggers
} }
if (!$errors) if (!$error)
{ {
//We finally remove the old thirdparty //We finally remove the old thirdparty
if ($soc_origin->delete($soc_origin->id, $user) < 1) if ($soc_origin->delete($soc_origin->id, $user) < 1)
{ {
$errors++; $error++;
} }
} }
if (!$errors) if (!$error)
{ {
setEventMessages($langs->trans('ThirdpartiesMergeSuccess'), null, 'mesgs'); setEventMessages($langs->trans('ThirdpartiesMergeSuccess'), null, 'mesgs');
$db->commit(); $db->commit();

View File

@ -243,6 +243,218 @@ class Thirdparties extends DolibarrApi
return false; return false;
} }
/**
* Merge a thirdparty into another one.
*
* Merge content (properties, notes) and objects (like invoices, events, orders, proposals, ...) of a thirdparty into a target thirdparty,
* then delete the merged thirdparty.
* If a property has a defined value both in thirdparty to delete and thirdparty to keep, the value into the thirdparty to
* delete will be ignored, the value of target thirdparty will remain, except for notes (content is concatenated).
*
* @param int $id ID of thirdparty to keep (the target thirdparty)
* @param int $idtodelete ID of thirdparty to remove (the thirdparty to delete), once data has been merged into the target thirdparty.
* @return int
*
* @url PUT {id}/merge/{idtodelete}
*/
function merge($id, $idtodelete)
{
global $db, $hookmanager;
if ($id == $idtodelete)
{
throw new RestException(400, 'Try to merge a thirdparty into itself');
}
if(! DolibarrApiAccess::$user->rights->societe->creer) {
throw new RestException(401);
}
$result = $this->company->fetch($id); // include the fetch of extra fields
if( ! $result ) {
throw new RestException(404, 'Thirdparty not found');
}
if( ! DolibarrApi::_checkAccessToResource('societe',$this->company->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
$this->companytoremove = new Societe($db);
$result = $this->companytoremove->fetch($idtodelete); // include the fetch of extra fields
if( ! $result ) {
throw new RestException(404, 'Thirdparty not found');
}
if( ! DolibarrApi::_checkAccessToResource('societe',$this->companytoremove->id)) {
throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
}
$soc_origin = $this->companytoremove;
$object = $this->company;
$user = DolibarrApiAccess::$user;
// Call same code than into action 'confirm_merge'
$db->begin();
// Recopy some data
$object->client = $object->client | $soc_origin->client;
$object->fournisseur = $object->fournisseur | $soc_origin->fournisseur;
$listofproperties=array(
'address', 'zip', 'town', 'state_id', 'country_id', 'phone', 'phone_pro', 'fax', 'email', 'skype', 'url', 'barcode',
'idprof1', 'idprof2', 'idprof3', 'idprof4', 'idprof5', 'idprof6',
'tva_intra', 'effectif_id', 'forme_juridique', 'remise_percent', 'mode_reglement_supplier_id', 'cond_reglement_supplier_id', 'name_bis',
'stcomm_id', 'outstanding_limit', 'price_level', 'parent', 'default_lang', 'ref', 'ref_ext', 'import_key', 'fk_incoterms', 'fk_multicurrency',
'code_client', 'code_fournisseur', 'code_compta', 'code_compta_fournisseur',
'model_pdf', 'fk_projet'
);
foreach ($listofproperties as $property)
{
if (empty($object->$property)) $object->$property = $soc_origin->$property;
}
// Concat some data
$listofproperties=array(
'note_public', 'note_private'
);
foreach ($listofproperties as $property)
{
$object->$property = dol_concatdesc($object->$property, $soc_origin->$property);
}
// Merge extrafields
if (is_array($soc_origin->array_options))
{
foreach ($soc_origin->array_options as $key => $val)
{
if (empty($object->array_options[$key])) $object->array_options[$key] = $val;
}
}
// Merge categories
$static_cat = new Categorie($db);
$custcats = $static_cat->containing($soc_origin->id, 'customer', 'id');
$object->setCategories($custcats, 'customer');
$suppcats = $static_cat->containing($soc_origin->id, 'supplier', 'id');
$object->setCategories($suppcats, 'supplier');
// If thirdparty has a new code that is same than origin, we clean origin code to avoid duplicate key from database unique keys.
if ($soc_origin->code_client == $object->code_client
|| $soc_origin->code_fournisseur == $object->code_fournisseur
|| $soc_origin->barcode == $object->barcode)
{
dol_syslog("We clean customer and supplier code so we will be able to make the update of target");
$soc_origin->code_client = '';
$soc_origin->code_fournisseur = '';
$soc_origin->barcode = '';
$soc_origin->update($soc_origin->id, $user, 0, 1, 1, 'merge');
}
// Update
$result = $object->update($object->id, $user, 0, 1, 1, 'merge');
if ($result < 0)
{
$error++;
}
// Move links
if (! $error)
{
$objects = array(
'Adherent' => '/adherents/class/adherent.class.php',
'Societe' => '/societe/class/societe.class.php',
'Categorie' => '/categories/class/categorie.class.php',
'ActionComm' => '/comm/action/class/actioncomm.class.php',
'Propal' => '/comm/propal/class/propal.class.php',
'Commande' => '/commande/class/commande.class.php',
'Facture' => '/compta/facture/class/facture.class.php',
'FactureRec' => '/compta/facture/class/facture-rec.class.php',
'LignePrelevement' => '/compta/prelevement/class/ligneprelevement.class.php',
'Contact' => '/contact/class/contact.class.php',
'Contrat' => '/contrat/class/contrat.class.php',
'Expedition' => '/expedition/class/expedition.class.php',
'Fichinter' => '/fichinter/class/fichinter.class.php',
'CommandeFournisseur' => '/fourn/class/fournisseur.commande.class.php',
'FactureFournisseur' => '/fourn/class/fournisseur.facture.class.php',
'SupplierProposal' => '/supplier_proposal/class/supplier_proposal.class.php',
'ProductFournisseur' => '/fourn/class/fournisseur.product.class.php',
'Livraison' => '/livraison/class/livraison.class.php',
'Product' => '/product/class/product.class.php',
'Project' => '/projet/class/project.class.php',
'User' => '/user/class/user.class.php',
);
//First, all core objects must update their tables
foreach ($objects as $object_name => $object_file)
{
require_once DOL_DOCUMENT_ROOT.$object_file;
if (!$errors && !$object_name::replaceThirdparty($db, $soc_origin->id, $object->id))
{
$errors++;
//setEventMessages($db->lasterror(), null, 'errors');
}
}
}
// External modules should update their ones too
if (!$errors)
{
$reshook = $hookmanager->executeHooks('replaceThirdparty', array(
'soc_origin' => $soc_origin->id,
'soc_dest' => $object->id
), $soc_dest, $action);
if ($reshook < 0)
{
//setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
$errors++;
}
}
if (! $error)
{
$object->context=array('merge'=>1, 'mergefromid'=>$soc_origin->id);
// Call trigger
$result=$object->call_trigger('COMPANY_MODIFY',$user);
if ($result < 0)
{
//setEventMessages($object->error, $object->errors, 'errors');
$error++;
}
// End call triggers
}
if (! $error)
{
//We finally remove the old thirdparty
if ($soc_origin->delete($soc_origin->id, $user) < 1)
{
$errors++;
}
}
// End of merge
if ($error)
{
$db->rollback();
throw new RestException(500, 'Error failed to merged thirdparty '.$this->companytoremove->id.' into '.$id.'. Enable and read log file for more information.');
}
else
{
$db->commit();
}
return $this->get($id);
}
/** /**
* Delete thirdparty * Delete thirdparty
* *

View File

@ -738,7 +738,7 @@ class Societe extends CommonObject
* @param int $call_trigger 0=no, 1=yes * @param int $call_trigger 0=no, 1=yes
* @param int $allowmodcodeclient Inclut modif code client et code compta * @param int $allowmodcodeclient Inclut modif code client et code compta
* @param int $allowmodcodefournisseur Inclut modif code fournisseur et code compta fournisseur * @param int $allowmodcodefournisseur Inclut modif code fournisseur et code compta fournisseur
* @param string $action 'add' or 'update' * @param string $action 'add' or 'update' or 'merge'
* @param int $nosyncmember Do not synchronize info of linked member * @param int $nosyncmember Do not synchronize info of linked member
* @return int <0 if KO, >=0 if OK * @return int <0 if KO, >=0 if OK
*/ */
@ -867,7 +867,12 @@ class Societe extends CommonObject
// Check name is required and codes are ok or unique. // Check name is required and codes are ok or unique.
// If error, this->errors[] is filled // If error, this->errors[] is filled
$result = 0; $result = 0;
if ($action != 'add') $result = $this->verify(); // We don't check when update called during a create because verify was already done if ($action != 'add' && $action != 'merge')
{
// We don't check when update called during a create because verify was already done.
// For a merge, we suppose source data is clean and a customer code of a deleted thirdparty must be accepted into a target thirdparty with empty code without duplicate error
$result = $this->verify();
}
if ($result >= 0) if ($result >= 0)
{ {
@ -3767,16 +3772,22 @@ class Societe extends CommonObject
* Function used to replace a thirdparty id with another one. * Function used to replace a thirdparty id with another one.
* It must be used within a transaction to avoid trouble * It must be used within a transaction to avoid trouble
* *
* @param DoliDB $db Database handler * @param DoliDB $db Database handler
* @param int $origin_id Old thirdparty id * @param int $origin_id Old thirdparty id (will be removed)
* @param int $dest_id New thirdparty id * @param int $dest_id New thirdparty id
* @return bool * @return bool True if success, False if error
*/ */
public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id) public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
{ {
if ($origin_id == $id)
{
dol_syslog('Error: Try to merge a thirdparty into itself');
return false;
}
/** /**
* Thirdparty commercials cannot be the same in both thirdparties so we look for them and remove some * Thirdparty commercials cannot be the same in both thirdparties so we look for them and remove some to avoid duplicate.
* Because this function is meant to be executed within a transaction, we won't take care of it. * Because this function is meant to be executed within a transaction, we won't take care of begin/commit.
*/ */
$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.'societe_commerciaux '; $sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.'societe_commerciaux ';
$sql .= ' WHERE fk_soc = '.(int) $dest_id.' AND fk_user IN ( '; $sql .= ' WHERE fk_soc = '.(int) $dest_id.' AND fk_user IN ( ';

View File

@ -62,7 +62,7 @@ if (! empty($conf->global->MAIN_HTML_TITLE) && preg_match('/thirdpartynameonly/'
$help_url='EN:Module_Third_Parties|FR:Module_Tiers|ES:Empresas'; $help_url='EN:Module_Third_Parties|FR:Module_Tiers|ES:Empresas';
llxHeader('',$title,$help_url); llxHeader('',$title,$help_url);
if ($id > 0) if ($object->id > 0)
{ {
/* /*
* Affichage onglets * Affichage onglets
@ -120,6 +120,11 @@ if ($id > 0)
dol_fiche_end(); dol_fiche_end();
} }
else
{
$langs->load("errors");
print $langs->trans("ErrorRecordNotFound");
}
llxFooter(); llxFooter();
$db->close(); $db->close();