diff --git a/htdocs/comm/propal/class/propal.class.php b/htdocs/comm/propal/class/propal.class.php index 039cc5c1a1c..eb81c4c084e 100644 --- a/htdocs/comm/propal/class/propal.class.php +++ b/htdocs/comm/propal/class/propal.class.php @@ -709,6 +709,20 @@ class Propal extends CommonObject $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $product = new Product($this->db); + $result = $product->fetch($fk_product); + if ($qty < $product->packaging) { + $qty = $product->packaging; + } else { + if (!empty($product->packaging) && (fmod((float) $qty, $product->packaging) > 0.000001)) { + $coeff = intval((float) $qty / $product->packaging) + 1; + $qty = (float) $product->packaging * $coeff; + setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs'); + } + } + } + // Clean vat code $reg = array(); $vat_src_code = ''; @@ -974,6 +988,18 @@ class Propal extends CommonObject $this->line->rang = $rangmax + 1; } + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + if ($qty < $this->line->packaging) { + $qty = $this->line->packaging; + } else { + if (!empty($this->line->packaging) && ($qty % $this->line->packaging) > 0) { + $coeff = intval($qty / $this->line->packaging) + 1; + $qty = $this->line->packaging * $coeff; + setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs'); + } + } + } + $this->line->id = $rowid; $this->line->label = $label; $this->line->desc = $desc; diff --git a/htdocs/comm/propal/class/propaleligne.class.php b/htdocs/comm/propal/class/propaleligne.class.php index ea403b5b363..67c941e9851 100644 --- a/htdocs/comm/propal/class/propaleligne.class.php +++ b/htdocs/comm/propal/class/propaleligne.class.php @@ -343,6 +343,10 @@ class PropaleLigne extends CommonObjectLine */ public $multicurrency_total_ttc; + /** + * @var float + */ + public $packaging; /** * Class line Constructor @@ -370,6 +374,9 @@ class PropaleLigne extends CommonObjectLine $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc,'; $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,'; $sql .= ' pd.date_start, pd.date_end, pd.product_type'; + if (getDolGlobalInt('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $sql .= ', p.packaging'; + } $sql .= ' FROM '.MAIN_DB_PREFIX.'propaldet as pd'; $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid'; $sql .= ' WHERE pd.rowid = '.((int) $rowid); @@ -418,6 +425,10 @@ class PropaleLigne extends CommonObjectLine $this->product_desc = $objp->product_desc; $this->fk_unit = $objp->fk_unit; + if (getDolGlobalInt('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $this->packaging = $objp->packaging; + } + $this->date_start = $this->db->jdate($objp->date_start); $this->date_end = $this->db->jdate($objp->date_end); diff --git a/htdocs/commande/class/commande.class.php b/htdocs/commande/class/commande.class.php index 412fe15ef97..71e03085f69 100644 --- a/htdocs/commande/class/commande.class.php +++ b/htdocs/commande/class/commande.class.php @@ -1634,6 +1634,20 @@ class Commande extends CommonOrder $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $product = new Product($this->db); + $result = $product->fetch($fk_product); + if ($qty < $product->packaging) { + $qty = $product->packaging; + } else { + if (!empty($product->packaging) && (fmod((float) $qty, $product->packaging) > 0.000001)) { + $coeff = intval((float) $qty / $product->packaging) + 1; + $qty = (float) $product->packaging * $coeff; + setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs'); + } + } + } + // Clean vat code $reg = array(); $vat_src_code = ''; @@ -2192,6 +2206,10 @@ class Commande extends CommonOrder $line->volume = $objp->volume; $line->volume_units = $objp->volume_units; + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $line->packaging = $objp->packaging; + } + $line->date_start = $this->db->jdate($objp->date_start); $line->date_end = $this->db->jdate($objp->date_end); @@ -3163,6 +3181,18 @@ class Commande extends CommonOrder $this->line->rang = $rangmax + 1; } + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + if ($qty < $this->line->packaging) { + $qty = $this->line->packaging; + } else { + if (!empty($this->line->packaging) && fmod($qty, $this->line->packaging) > 0) { + $coeff = intval($qty / $this->line->packaging) + 1; + $qty = $this->line->packaging * $coeff; + setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs'); + } + } + } + $this->line->id = $rowid; $this->line->label = $label; $this->line->desc = $desc; diff --git a/htdocs/commande/class/orderline.class.php b/htdocs/commande/class/orderline.class.php index e16bde3054e..10519dd0cf2 100644 --- a/htdocs/commande/class/orderline.class.php +++ b/htdocs/commande/class/orderline.class.php @@ -145,6 +145,10 @@ class OrderLine extends CommonOrderLine */ public $skip_update_total; + /** + * @var float + */ + public $packaging; /** * Constructor @@ -171,6 +175,9 @@ class OrderLine extends CommonOrderLine $sql .= ' cd.fk_multicurrency, cd.multicurrency_code, cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc,'; $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc, p.tobatch as product_tobatch,'; $sql .= ' cd.date_start, cd.date_end, cd.vat_src_code'; + if (getDolGlobalInt('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $sql .= ', p.packaging'; + } $sql .= ' FROM '.MAIN_DB_PREFIX.'commandedet as cd'; $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON cd.fk_product = p.rowid'; $sql .= ' WHERE cd.rowid = '.((int) $rowid); @@ -225,6 +232,10 @@ class OrderLine extends CommonOrderLine $this->product_tobatch = $objp->product_tobatch; $this->fk_unit = $objp->fk_unit; + if (getDolGlobalInt('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $this->packaging = $objp->packaging; + } + $this->date_start = $this->db->jdate($objp->date_start); $this->date_end = $this->db->jdate($objp->date_end); diff --git a/htdocs/compta/facture/class/facture.class.php b/htdocs/compta/facture/class/facture.class.php index 8d88920efa7..09dddafbbd5 100644 --- a/htdocs/compta/facture/class/facture.class.php +++ b/htdocs/compta/facture/class/facture.class.php @@ -3999,6 +3999,20 @@ class Facture extends CommonInvoice $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc); + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $product = new Product($this->db); + $result = $product->fetch($fk_product); + if ($qty < $product->packaging) { + $qty = $product->packaging; + } else { + if (!empty($product->packaging) && (fmod((float) $qty, $product->packaging) > 0.000001)) { + $coeff = intval((float) $qty / $product->packaging) + 1; + $qty = (float) $product->packaging * $coeff; + setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs'); + } + } + } + // Clean vat code $reg = array(); $vat_src_code = ''; @@ -4303,6 +4317,18 @@ class Facture extends CommonInvoice $this->line->rang = $rangmax + 1; } + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + if ($qty < $this->line->packaging) { + $qty = $this->line->packaging; + } else { + if (!empty($this->line->packaging) && fmod($qty, $this->line->packaging) > 0) { + $coeff = intval($qty / $this->line->packaging) + 1; + $qty = $this->line->packaging * $coeff; + setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs'); + } + } + } + $this->line->id = $rowid; $this->line->rowid = $rowid; $this->line->label = $label; diff --git a/htdocs/compta/facture/class/factureligne.class.php b/htdocs/compta/facture/class/factureligne.class.php index e4e2b1a3ae2..c397b5ddf00 100644 --- a/htdocs/compta/facture/class/factureligne.class.php +++ b/htdocs/compta/facture/class/factureligne.class.php @@ -180,6 +180,11 @@ class FactureLigne extends CommonInvoiceLine */ public $fk_prev_id; + /** + * @var float + */ + public $packaging; + /** * Constructor @@ -211,6 +216,9 @@ class FactureLigne extends CommonInvoiceLine $sql .= ' fd.multicurrency_total_tva,'; $sql .= ' fd.multicurrency_total_ttc,'; $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc'; + if (getDolGlobalInt('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $sql .= ', p.packaging'; + } $sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet as fd'; $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON fd.fk_product = p.rowid'; $sql .= ' WHERE fd.rowid = '.((int) $rowid); @@ -277,6 +285,10 @@ class FactureLigne extends CommonInvoiceLine $this->multicurrency_total_tva = $objp->multicurrency_total_tva; $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc; + if (getDolGlobalInt('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $this->packaging = $objp->packaging; + } + $this->fetch_optionals(); $this->db->free($result); diff --git a/htdocs/core/modules/modProduct.class.php b/htdocs/core/modules/modProduct.class.php index 7b5ae32f69a..384692a0bf9 100644 --- a/htdocs/core/modules/modProduct.class.php +++ b/htdocs/core/modules/modProduct.class.php @@ -638,6 +638,12 @@ class modProduct extends DolibarrModules )); } + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $this->import_fields_array[$r] = array_merge($this->import_fields_array[$r], array( + 'p.packaging' => 'PackagingForThisProductSell', + )); + } + if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice") || isModEnabled('margin')) { $this->import_fields_array[$r] = array_merge($this->import_fields_array[$r], array('p.cost_price' => 'CostPrice')); } @@ -755,6 +761,13 @@ class modProduct extends DolibarrModules ) )); } + + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $import_sample = array_merge($import_sample, array( + 'p.packaging' => "2", + )); + } + $this->import_examplevalues_array[$r] = array_merge($import_sample, $import_extrafield_sample); $this->import_updatekeys_array[$r] = array('p.ref' => 'Ref'); if (isModEnabled('barcode')) { diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang index f256752d97a..a948c81da99 100644 --- a/htdocs/langs/en_US/products.lang +++ b/htdocs/langs/en_US/products.lang @@ -345,6 +345,9 @@ ProductSupplierDescription=Vendor description for the product UseProductSupplierPackaging=Use the "packaging" feature to round the quantities to some given multiples (when adding/updating line in a vendor documents, recalculate quantities and purchase prices according to the higher multiple set on the purchase prices of a product) PackagingForThisProduct=Packaging of quantities PackagingForThisProductDesc=You will automatically purchase a multiple of this quantity. +UseProductCustomerPackaging=Use the "packaging" feature to round the quantities to some given multiples (when adding/updating line in a customer documents, recalculate quantities and sale prices according to the higher multiple set on the sale prices of a product) +PackagingForThisProductSell=Packaging of quantities (sale) +PackagingForThisProductSellDesc=You will automatically sell a multiple of this quantity. QtyRecalculatedWithPackaging=The quantity of the line were recalculated according to supplier packaging #Attributes diff --git a/htdocs/langs/fr_FR/products.lang b/htdocs/langs/fr_FR/products.lang index b1e95a6ae9c..b25074d878b 100644 --- a/htdocs/langs/fr_FR/products.lang +++ b/htdocs/langs/fr_FR/products.lang @@ -345,6 +345,9 @@ ProductSupplierDescription=Description du fournisseur du produit UseProductSupplierPackaging=Utilisez la fonction "conditionnement" pour arrondir les quantités à certains multiples donnés (lors de l'ajout/de la mise à jour d'une ligne, dans les documents d'un fournisseur, recalculez les quantités et les prix d'achat en fonction du multiple supérieur défini sur les prix d'achat d'un produit) PackagingForThisProduct=Conditionnement des quantités PackagingForThisProductDesc=Vous achèterez automatiquement un multiple de cette quantité. +UseProductCustomerPackaging=Utilisez la fonction "conditionnement" pour arrondir les quantités à certains multiples donnés (lors de l'ajout/de la mise à jour d'une ligne, dans les documents d'un client, recalculez les quantités et les prix de vente en fonction du multiple supérieur défini sur les prix de vente d'un produit) +PackagingForThisProductSell=Conditionnement des quantités (vente) +PackagingForThisProductSellDesc=Vous vendrez automatiquement un multiple de cette quantité. QtyRecalculatedWithPackaging=La quantité de la ligne a été recalculée en fonction de l'emballage du fournisseur #Attributes diff --git a/htdocs/product/admin/product.php b/htdocs/product/admin/product.php index b31fae0df5f..60757b73689 100644 --- a/htdocs/product/admin/product.php +++ b/htdocs/product/admin/product.php @@ -169,6 +169,11 @@ if ($action == 'other') { $value = GETPOST('PRODUCT_USE_SUPPLIER_PACKAGING', 'alpha'); $res = dolibarr_set_const($db, "PRODUCT_USE_SUPPLIER_PACKAGING", $value, 'chaine', 0, '', $conf->entity); } + + if (GETPOSTISSET('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $value = GETPOST('PRODUCT_USE_CUSTOMER_PACKAGING', 'alpha'); + $res = dolibarr_set_const($db, "PRODUCT_USE_CUSTOMER_PACKAGING", $value, 'chaine', 0, '', $conf->entity); + } } @@ -642,6 +647,17 @@ if (isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) { print ''; } +// Use packaging during your sales +if (isModEnabled("order") || isModEnabled("invoice")) { + print ''; + print ''.$form->textwithpicto($langs->trans("UseProductCustomerPackaging"), $langs->trans("PackagingForThisProductSellDesc")).''; + print ''; + print ajax_constantonoff("PRODUCT_USE_CUSTOMER_PACKAGING", array(), $conf->entity, 0, 0, 0, 0); + //print $form->selectyesno("activate_useProdSupplierPackaging", (!empty($conf->global->PRODUCT_USE_CUSTOMER_PACKAGING) ? $conf->global->PRODUCT_USE_CUSTOMER_PACKAGING : 0), 1); + print ''; + print ''; +} + print ''; print ''; diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index fa66019bf9e..05d88261d19 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -1447,7 +1447,6 @@ class Product extends CommonObject $this->accountancy_code_sell_intra = trim($this->accountancy_code_sell_intra); $this->accountancy_code_sell_export = trim($this->accountancy_code_sell_export); - $this->db->begin(); $result = 0; @@ -1597,6 +1596,9 @@ class Product extends CommonObject $sql .= ", fk_price_expression = ".($this->fk_price_expression != 0 ? (int) $this->fk_price_expression : 'NULL'); $sql .= ", fk_user_modif = ".($user->id > 0 ? $user->id : 'NULL'); $sql .= ", mandatory_period = ".($this->mandatory_period); + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING') && !empty($this->packaging)) { + $sql .= ", packaging = " . (float) $this->packaging; + } // stock field is not here because it is a denormalized value from product_stock. $sql .= " WHERE rowid = ".((int) $id); @@ -2897,6 +2899,9 @@ class Product extends CommonObject } else { $sql .= " ppe.accountancy_code_buy, ppe.accountancy_code_buy_intra, ppe.accountancy_code_buy_export, ppe.accountancy_code_sell, ppe.accountancy_code_sell_intra, ppe.accountancy_code_sell_export,"; } + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $sql .= " p.packaging,"; + } // For MultiCompany // PMP per entity & Stocks Sharings stock_reel includes only stocks shared with this entity @@ -3076,6 +3081,10 @@ class Product extends CommonObject $this->mandatory_period = $obj->mandatory_period; + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $this->packaging = $obj->packaging; + } + $this->db->free($resql); // fetch optionals attributes and labels diff --git a/htdocs/product/price.php b/htdocs/product/price.php index 2fabbc1085c..2a3dc750d9b 100644 --- a/htdocs/product/price.php +++ b/htdocs/product/price.php @@ -14,7 +14,7 @@ * Copyright (C) 2018-2025 Frédéric France * Copyright (C) 2018 Nicolas ZABOURI * Copyright (C) 2024 MDW - * Copyright (C) 2024 Mélina Joum + * Copyright (C) 2025 Mélina Joum * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -283,6 +283,9 @@ if (empty($reshook)) { $psq = empty($newpsq) ? 0 : $newpsq; $maxpricesupplier = $object->min_recommended_price(); + // Packaging + $packaging = getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING') ? GETPOST('packaging') : null; + if (isModEnabled('dynamicprices')) { $object->fk_price_expression = empty($eid) ? 0 : $eid; //0 discards expression @@ -481,6 +484,11 @@ if (empty($reshook)) { if (!$error) { $db->begin(); + // Packaging + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + $object->packaging = (float) $packaging; + } + foreach ($pricestoupdate as $key => $val) { $newprice = $val['price']; @@ -1421,6 +1429,13 @@ if (getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUS } print ''; + // Packaging + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + print ''.$form->textwithpicto($langs->trans("PackagingForThisProduct"), $langs->trans("PackagingForThisProductSellDesc")).''; + print $object->packaging; + print ''; + } + // Price Label print ''.$langs->trans("PriceLabel").''; print $object->price_label; @@ -1743,6 +1758,17 @@ if (($action == 'edit_price' || $action == 'edit_level_price') && $object->getRi print ''; print ''; + // Packaging + if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) { + print ''; + print $form->textwithpicto($langs->trans("PackagingForThisProduct"), $langs->trans("PackagingForThisProductSellDesc")); + print ''; + $packaging = $object->packaging; + print ''; + print ''; + print ''; + } + // Price Label print ''; print $langs->trans('PriceLabel');