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 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)
{

View File

@ -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();

View File

@ -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
*

View File

@ -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 ( ';

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';
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();