Merge pull request #19459 from frederic34/productmerge

can merge products
This commit is contained in:
Laurent Destailleur 2022-04-07 15:02:03 +02:00 committed by GitHub
commit 26c3e235cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 461 additions and 7 deletions

View File

@ -1103,6 +1103,23 @@ class BOM extends CommonObject
}
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'bom_bomline'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
/**
* Get Net needs by product
*

View File

@ -2236,6 +2236,26 @@ class ActionComm extends CommonObject
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $dbs Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
{
$sql = 'UPDATE ' . MAIN_DB_PREFIX . 'actioncomm SET fk_element = ' . ((int) $dest_id) . ' WHERE elementtype="product" AND fk_element = '.((int) $origin_id);
// using $dbs, not $this->db because function is static
if (!$dbs->query($sql)) {
//$this->errors = $dbs->lasterror();
return false;
}
return true;
}
/**
* Is the action delayed?
*

View File

@ -3821,8 +3821,24 @@ class Propal extends CommonObject
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'propaldet'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
}
/**
* Class to manage commercial proposal lines

View File

@ -4039,6 +4039,23 @@ class Commande extends CommonOrder
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'commandedet',
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
/**
* Is the customer order delayed?
*

View File

@ -1728,7 +1728,7 @@ class Account extends CommonObject
if ($dbs->query($sql)) {
return true;
} else {
//if ($ignoreerrors) return true; // TODO Not enough. If there is A-B on kept thirdarty and B-C on old one, we must get A-B-C after merge. Not A-B.
//if ($ignoreerrors) return true; // TODO Not enough. If there is A-B on kept thirdparty and B-C on old one, we must get A-B-C after merge. Not A-B.
//$this->errors = $dbs->lasterror();
return false;
}

View File

@ -1759,6 +1759,23 @@ class FactureRec extends CommonInvoice
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'facturedet_rec'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
/**
* Update frequency and unit
*

View File

@ -4818,6 +4818,23 @@ class Facture extends CommonInvoice
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'facturedet'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
/**
* Is the customer invoice delayed?
*

View File

@ -2474,6 +2474,23 @@ class Contrat extends CommonObject
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'contratdet'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
/**
* Load an object from its id and create a new one in database
*

View File

@ -8120,7 +8120,7 @@ abstract class CommonObject
/**
* Function used to replace a thirdparty id with another one.
* This function is meant to be called from replaceThirdparty with the appropiate tables
* This function is meant to be called from replaceThirdparty with the appropriate tables
* Column name fk_soc MUST be used to identify thirdparties
*
* @param DoliDB $db Database handler
@ -8137,7 +8137,36 @@ abstract class CommonObject
if (!$db->query($sql)) {
if ($ignoreerrors) {
return true; // TODO Not enough. If there is A-B on kept thirdarty and B-C on old one, we must get A-B-C after merge. Not A-B.
return true; // TODO Not enough. If there is A-B on kept thirdparty and B-C on old one, we must get A-B-C after merge. Not A-B.
}
//$this->errors = $db->lasterror();
return false;
}
}
return true;
}
/**
* Function used to replace a product id with another one.
* This function is meant to be called from replaceProduct with the appropriate tables
* Column name fk_product MUST be used to identify products
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id (the product to delete)
* @param int $dest_id New product id (the product 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 product may already exists on new one)
* @return bool True if success, False if error
*/
public static function commonReplaceProduct(DoliDB $db, $origin_id, $dest_id, array $tables, $ignoreerrors = 0)
{
foreach ($tables as $table) {
$sql = 'UPDATE '.MAIN_DB_PREFIX.$table.' SET fk_product = '.((int) $dest_id).' WHERE fk_product = '.((int) $origin_id);
if (!$db->query($sql)) {
if ($ignoreerrors) {
return true; // TODO Not enough. If there is A-B on kept product and B-C on old one, we must get A-B-C after merge. Not A-B.
}
//$this->errors = $db->lasterror();
return false;

View File

@ -1092,6 +1092,23 @@ class Delivery extends CommonObject
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'deliverydet'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
}

View File

@ -40,7 +40,7 @@ class FichinterRec extends Fichinter
{
public $element = 'fichinterrec';
public $table_element = 'fichinter_rec';
public $table_element_line = 'fichinter_rec';
public $table_element_line = 'fichinterdet_rec';
/**
* @var string Fieldname with ID of parent key if this field has a parent
@ -706,6 +706,22 @@ class FichinterRec extends Fichinter
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'fichinterdet_rec'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
/**
* Update frequency and unit

View File

@ -3249,6 +3249,23 @@ class CommandeFournisseur extends CommonOrder
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'commande_fournisseurdet'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
/**
* Is the supplier order delayed?
* We suppose a purchase ordered as late if a the purchase order has been sent and the delivery date is set and before the delay.

View File

@ -3153,6 +3153,23 @@ class FactureFournisseur extends CommonInvoice
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'facture_fourn_det'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
/**
* Is the payment of the supplier invoice having a delay?
*

View File

@ -972,6 +972,23 @@ class ProductFournisseur extends Product
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'product_fournisseur_price'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
/**
* List supplier prices log of a supplier price
*

View File

@ -409,6 +409,11 @@ mandatoryHelper=Check this if you want a message to the user when creating / val
DefaultBOM=Default BOM
DefaultBOMDesc=The default BOM recommended to use to manufacture this product. This field can be set only if nature of product is '%s'.
Rank=Rank
MergeOriginProduct=Duplicate product (product you want to delete)
MergeProducts=Merge products
ConfirmMergeProducts=Are you sure you want to merge the chosen product with the current one? All linked objects (invoices, orders, ...) will be moved to the current product, after which the chosen product will be deleted.
ProductsMergeSuccess=Products have been merged
ErrorsProductsMerge=Errors in products merge
SwitchOnSaleStatus=Switch on sale status
SwitchOnPurchaseStatus=Switch on purchase status
StockMouvementExtraFields= Extra Fields (stock mouvement)

View File

@ -226,6 +226,174 @@ if (empty($reshook)) {
}
$action = '';
}
// merge products
if ($action == 'confirm_merge' && $confirm == 'yes' && $user->rights->societe->creer) {
$error = 0;
$productOriginId = GETPOST('product_origin', 'int');
$productOrigin = new Product($db);
if ($productOriginId <= 0) {
$langs->load('errors');
setEventMessages($langs->trans('ErrorProductIdIsMandatory', $langs->transnoentitiesnoconv('MergeOriginProduct')), null, 'errors');
} else {
if (!$error && $productOrigin->fetch($productOriginId) < 1) {
setEventMessages($langs->trans('ErrorRecordNotFound'), null, 'errors');
$error++;
}
if (!$error) {
// TODO Move the merge function into class of object.
$db->begin();
// Recopy some data
$listofproperties = array(
'ref',
'ref_ext',
'label',
'description',
'url',
'barcode',
'fk_barcode_type',
'import_key',
'mandatory_period',
'accountancy_code_buy',
'accountancy_code_buy_intra',
'accountancy_code_buy_export',
'accountancy_code_sell',
'accountancy_code_sell_intra',
'accountancy_code_sell_export'
);
foreach ($listofproperties as $property) {
if (empty($object->$property)) {
$object->$property = $productOrigin->$property;
}
}
// Concat some data
$listofproperties = array(
'note_public', 'note_private'
);
foreach ($listofproperties as $property) {
$object->$property = dol_concatdesc($object->$property, $productOrigin->$property);
}
// Merge extrafields
if (is_array($productOrigin->array_options)) {
foreach ($productOrigin->array_options as $key => $val) {
if (empty($object->array_options[$key])) {
$object->array_options[$key] = $val;
}
}
}
// Merge categories
$static_cat = new Categorie($db);
$custcats_ori = $static_cat->containing($productOrigin->id, 'product', 'id');
$custcats = $static_cat->containing($object->id, 'product', 'id');
$custcats = array_merge($custcats, $custcats_ori);
$object->setCategories($custcats);
// If product has a new code that is same than origin, we clean origin code to avoid duplicate key from database unique keys.
if ($productOrigin->barcode == $object->barcode) {
dol_syslog("We clean customer and supplier code so we will be able to make the update of target");
$productOrigin->barcode = '';
//$productOrigin->update($productOrigin->id, $user, 0, 'merge');
}
// Update
$result = $object->update($object->id, $user, 0, 'merge');
if ($result <= 0) {
setEventMessages($object->error, $object->errors, 'errors');
$error++;
}
// Move links
if (!$error) {
// TODO add this functionality into the api_products.class.php
// TODO Mutualise the list into object product.class.php
$objects = array(
'ActionComm' => '/comm/action/class/actioncomm.class.php',
'Bom' => '/bom/class/bom.class.php',
// do not use Categorie, it cause foreign key error, merge is done before
//'Categorie' => '/categories/class/categorie.class.php',
'Commande' => '/commande/class/commande.class.php',
'CommandeFournisseur' => '/fourn/class/fournisseur.commande.class.php',
'Contrat' => '/contrat/class/contrat.class.php',
'Delivery' => '/delivery/class/delivery.class.php',
'Facture' => '/compta/facture/class/facture.class.php',
'FactureFournisseur' => '/fourn/class/fournisseur.facture.class.php',
'FactureRec' => '/compta/facture/class/facture-rec.class.php',
'FichinterRec' => '/fichinter/class/fichinterrec.class.php',
'ProductFournisseur' => '/fourn/class/fournisseur.product.class.php',
'Propal' => '/comm/propal/class/propal.class.php',
'Reception' => '/reception/class/reception.class.php',
'SupplierProposal' => '/supplier_proposal/class/supplier_proposal.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 (!$error && !$object_name::replaceProduct($db, $productOrigin->id, $object->id)) {
$error++;
setEventMessages($db->lasterror(), null, 'errors');
break;
}
}
}
// External modules should update their ones too
if (!$error) {
$reshook = $hookmanager->executeHooks(
'replaceProduct',
array(
'soc_origin' => $productOrigin->id,
'soc_dest' => $object->id,
),
$object,
$action
);
if ($reshook < 0) {
setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
$error++;
}
}
if (!$error) {
$object->context = array(
'merge' => 1,
'mergefromid' => $productOrigin->id,
);
// Call trigger
$result = $object->call_trigger('PRODUCT_MODIFY', $user);
if ($result < 0) {
setEventMessages($object->error, $object->errors, 'errors');
$error++;
}
// End call triggers
}
if (!$error) {
// We finally remove the old product
// TODO merge attached files from old product into new one before delete
if ($productOrigin->delete($user) < 1) {
$error++;
}
}
if (!$error) {
setEventMessages($langs->trans('ProductsMergeSuccess'), null, 'mesgs');
$db->commit();
} else {
$langs->load("errors");
setEventMessages($langs->trans('ErrorsProductsMerge'), null, 'errors');
$db->rollback();
}
}
}
}
// Type
if ($action == 'setfk_product_type' && $usercancreate) {
@ -2502,6 +2670,17 @@ if (($action == 'delete' && (empty($conf->use_javascript_ajax) || !empty($conf->
|| (!empty($conf->use_javascript_ajax) && empty($conf->dol_use_jmobile))) { // Always output when not jmobile nor js
$formconfirm = $form->formconfirm("card.php?id=".$object->id, $langs->trans("DeleteProduct"), $langs->trans("ConfirmDeleteProduct"), "confirm_delete", '', 0, "action-delete");
}
if ($action == 'merge') {
$formquestion = array(
array(
'name' => 'product_origin',
'label' => $langs->trans('MergeOriginProduct'),
'type' => 'other',
'value' => $form->select_produits('', 'product_origin', '', 0, 0, 1, 2, '', 1, array(), 0, 1, 0, 'minwidth200', 0, '', null, 1),
)
);
$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"]."?id=".$object->id, $langs->trans("MergeProducts"), $langs->trans("ConfirmMergeProducts"), "confirm_merge", $formquestion, 'no', 1, 250);
}
// Clone confirmation
if (($action == 'clone' && (empty($conf->use_javascript_ajax) || !empty($conf->dol_use_jmobile))) // Output when action = clone if jmobile or no js
@ -2572,6 +2751,9 @@ if ($action != 'create' && $action != 'edit') {
} else {
print dolGetButtonAction($langs->trans("ProductIsUsed"), $langs->trans('Delete'), 'delete', '#', '', false);
}
if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 1) {
print '<a class="butActionDelete" href="card.php?action=merge&id='.$object->id.'" title="'.dol_escape_htmltag($langs->trans("MergeProducts")).'">'.$langs->trans('Merge').'</a>'."\n";
}
} else {
print dolGetButtonAction($langs->trans("NotEnoughPermissions"), $langs->trans('Delete'), 'delete', '#', '', false);
}

View File

@ -491,8 +491,7 @@ class Product extends CommonObject
'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'index'=>0, 'position'=>1000),
//'tosell' =>array('type'=>'integer', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Active', -1=>'Cancel')),
//'tobuy' =>array('type'=>'integer', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Active', -1=>'Cancel')),
'mandatory_period' =>array('type'=>'integer', 'label'=>'mandatory_period', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000),
'mandatory_period' => array('type'=>'integer', 'label'=>'mandatory_period', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'default'=>0, 'index'=>1, 'position'=>1000),
);
/**

View File

@ -2078,4 +2078,21 @@ class Reception extends CommonObject
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'commande_fournisseur_dispatch'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
}

View File

@ -2697,6 +2697,23 @@ class SupplierProposal extends CommonObject
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'supplier_proposaldet'
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
}