From 14ecdce650c75b12612f3af706792766046cca8d Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 31 Jan 2018 18:01:56 +0100 Subject: [PATCH] Fix merge of thirdparties (pb when merging codes and error management) --- htdocs/core/class/commonobject.class.php | 2 +- htdocs/societe/card.php | 101 +++++---- .../societe/class/api_thirdparties.class.php | 212 ++++++++++++++++++ htdocs/societe/class/societe.class.php | 27 ++- htdocs/societe/note.php | 7 +- 5 files changed, 298 insertions(+), 51 deletions(-) diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index d71277a77ee..7ebd32b30b3 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -5849,7 +5849,7 @@ abstract class CommonObject * @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 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) { diff --git a/htdocs/societe/card.php b/htdocs/societe/card.php index 2dc70b74c7d..9d0a58d3196 100644 --- a/htdocs/societe/card.php +++ b/htdocs/societe/card.php @@ -119,7 +119,7 @@ if (empty($reshook)) { $object->fetch($socid); - $errors = 0; + $error = 0; $soc_origin_id = GETPOST('soc_origin', 'int'); $soc_origin = new Societe($db); @@ -131,13 +131,13 @@ if (empty($reshook)) } 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'); - $errors++; + $error++; } - if (!$errors) + if (!$error) { // 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'); $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 - $object->update($object->id, $user, 0); + $object->update($object->id, $user, 0, 1, 1, 'merge'); + if ($result < 0) + { + $error++; + } // Move links - $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) + if (! $error) { - 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++; - setEventMessages($db->lasterror(), null, 'errors'); + require_once DOL_DOCUMENT_ROOT.$object_file; + + if (!$error && !$object_name::replaceThirdparty($db, $soc_origin->id, $object->id)) + { + $error++; + setEventMessages($db->lasterror(), null, 'errors'); + } } } - //External modules should update their ones too - if (!$errors) + // External modules should update their ones too + if (! $error) { $reshook = $hookmanager->executeHooks('replaceThirdparty', array( 'soc_origin' => $soc_origin->id, @@ -235,7 +254,7 @@ if (empty($reshook)) if ($reshook < 0) { setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); - $errors++; + $error++; } } @@ -254,16 +273,16 @@ if (empty($reshook)) // End call triggers } - if (!$errors) + if (!$error) { //We finally remove the old thirdparty if ($soc_origin->delete($soc_origin->id, $user) < 1) { - $errors++; + $error++; } } - if (!$errors) + if (!$error) { setEventMessages($langs->trans('ThirdpartiesMergeSuccess'), null, 'mesgs'); $db->commit(); diff --git a/htdocs/societe/class/api_thirdparties.class.php b/htdocs/societe/class/api_thirdparties.class.php index 658dc6a50e2..9bc95c5e836 100644 --- a/htdocs/societe/class/api_thirdparties.class.php +++ b/htdocs/societe/class/api_thirdparties.class.php @@ -243,6 +243,218 @@ class Thirdparties extends DolibarrApi 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 * diff --git a/htdocs/societe/class/societe.class.php b/htdocs/societe/class/societe.class.php index 270a1dc344c..cc2e1fe50af 100644 --- a/htdocs/societe/class/societe.class.php +++ b/htdocs/societe/class/societe.class.php @@ -738,7 +738,7 @@ class Societe extends CommonObject * @param int $call_trigger 0=no, 1=yes * @param int $allowmodcodeclient Inclut modif code client et code compta * @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 * @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. // If error, this->errors[] is filled $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) { @@ -3767,16 +3772,22 @@ class Societe extends CommonObject * Function used to replace a thirdparty id with another one. * It must be used within a transaction to avoid trouble * - * @param DoliDB $db Database handler - * @param int $origin_id Old thirdparty id - * @param int $dest_id New thirdparty id - * @return bool + * @param DoliDB $db Database handler + * @param int $origin_id Old thirdparty id (will be removed) + * @param int $dest_id New thirdparty id + * @return bool True if success, False if error */ 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 - * Because this function is meant to be executed within a transaction, we won't take care of it. + * 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 begin/commit. */ $sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.'societe_commerciaux '; $sql .= ' WHERE fk_soc = '.(int) $dest_id.' AND fk_user IN ( '; diff --git a/htdocs/societe/note.php b/htdocs/societe/note.php index 0dbb20ebd00..d0766bc24d8 100644 --- a/htdocs/societe/note.php +++ b/htdocs/societe/note.php @@ -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'; llxHeader('',$title,$help_url); -if ($id > 0) +if ($object->id > 0) { /* * Affichage onglets @@ -120,6 +120,11 @@ if ($id > 0) dol_fiche_end(); } +else +{ + $langs->load("errors"); + print $langs->trans("ErrorRecordNotFound"); +} llxFooter(); $db->close();