From 08a66c5406beb15952af24e187d6b579f61cec5f Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Thu, 27 Nov 2025 20:41:20 +0100 Subject: [PATCH 01/38] Restore setup_print_title helper --- admin/setup.php | 337 ++++---- core/modules/modDynamicsPrices.class.php | 78 +- ...icsPrices_DynamicsPricesTriggers.class.php | 584 +++++++------ langs/en_US/dynamicsprices.lang | 28 +- langs/fr_FR/dynamicsprices.lang | 20 +- lib/dynamicsprices.lib.php | 797 ++++++++++-------- sql/dolibarr_allversions.sql | 30 +- sql/llx_c_margin_on_cost.sql | 14 + 8 files changed, 1026 insertions(+), 862 deletions(-) create mode 100644 sql/llx_c_margin_on_cost.sql diff --git a/admin/setup.php b/admin/setup.php index e6d32bc..6c6df35 100644 --- a/admin/setup.php +++ b/admin/setup.php @@ -1,155 +1,182 @@ - - * Copyright (C) 2024 Frédéric France - * Copyright (C) 2025 Pierre ARDOIN - * - * 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -/** - * \file dynamicsprices/admin/setup.php - * \ingroup dynamicsprices - * \brief DynamicsPrices setup page. - */ - -// Load Dolibarr environment -$res = 0; -// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) -if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) { - $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php"; -} -// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME -$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; -$tmp2 = realpath(__FILE__); -$i = strlen($tmp) - 1; -$j = strlen($tmp2) - 1; -while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { - $i--; - $j--; -} -if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) { - $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php"; -} -if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) { - $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php"; -} -// Try main.inc.php using relative path -if (!$res && file_exists("../../main.inc.php")) { - $res = @include "../../main.inc.php"; -} -if (!$res && file_exists("../../../main.inc.php")) { - $res = @include "../../../main.inc.php"; -} -if (!$res) { - die("Include of main fails"); -} - -// Libraries -require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php"; -require_once '../lib/dynamicsprices.lib.php'; -//require_once "../class/myclass.class.php"; - -/** - * @var Conf $conf - * @var DoliDB $db - * @var HookManager $hookmanager - * @var Translate $langs - * @var User $user - */ - -// Translations -$langs->loadLangs(array("admin", "dynamicsprices@dynamicsprices")); - -// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context -/** @var HookManager $hookmanager */ -$hookmanager->initHooks(array('dynamicspricessetup', 'globalsetup')); - -// Parameters -$action = GETPOST('action', 'aZ09'); -$backtopage = GETPOST('backtopage', 'alpha'); -$modulepart = GETPOST('modulepart', 'aZ09'); // Used by actions_setmoduleoptions.inc.php - -$value = GETPOST('value', 'alpha'); -$label = GETPOST('label', 'alpha'); -$scandir = GETPOST('scan_dir', 'alpha'); -$type = 'myobject'; - -$error = 0; -$setupnotempty = 1; - -// Access control -if (!$user->admin) { - accessforbidden(); -} - - -// Set this to 1 to use the factory to manage constants. Warning, the generated module will be compatible with version v15+ only -$useFormSetup = 1; - -if (!class_exists('FormSetup')) { - require_once DOL_DOCUMENT_ROOT.'/core/class/html.formsetup.class.php'; -} -$formSetup = new FormSetup($db); - -// Access control -if (!$user->admin) { - accessforbidden(); -} - - -$action = 'edit'; - - -/* - * View - */ - -$form = new Form($db); - -$help_url = ''; -$title = "DynamicsPricesSetup"; - -llxHeader('', $langs->trans($title), $help_url, '', 0, 0, '', '', '', 'mod-dynamicsprices page-admin'); - -// Subheader -$linkback = ''.$langs->trans("BackToModuleList").''; - -print load_fiche_titre($langs->trans($title), $linkback, 'title_setup'); - -// Configuration header -$head = dynamicspricesAdminPrepareHead(); -print dol_get_fiche_head($head, 'settings', $langs->trans($title), -1, "dynamicsprices@dynamicsprices"); - -// Setup page goes here -echo ''.$langs->trans("DynamicsPricesSetupPage").'

'; - -print ''; - -// Réglage - setup_print_title($langs->trans("LMDB_UpdateOptions")); - setup_print_on_off('LMDB_COST_PRICE_ONLY'); - setup_print_on_off('LMDB_SUPPLIER_BUYPRICE_ALTERED'); - - - -print '
'; -if (empty($setupnotempty)) { - print '
'.$langs->trans("NothingToSetup"); -} - -// Page end -print dol_get_fiche_end(); - -llxFooter(); -$db->close(); + +* Copyright (C) 2024 Frédéric France +* Copyright (C) 2025 Pierre ARDOIN +* +* 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 +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +/** +* \file dynamicsprices/admin/setup.php +* \ingroup dynamicsprices +* \brief DynamicsPrices setup page. +*/ + +// Load Dolibarr environment +$res = 0; +// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined) +if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) { + $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php"; +} +// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME +$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; +$tmp2 = realpath(__FILE__); +$i = strlen($tmp) - 1; +$j = strlen($tmp2) - 1; +while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { + $i--; + $j--; +} +if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) { + $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php"; +} +if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) { + $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php"; +} +// Try main.inc.php using relative path +if (!$res && file_exists("../../main.inc.php")) { + $res = @include "../../main.inc.php"; +} +if (!$res && file_exists("../../../main.inc.php")) { + $res = @include "../../../main.inc.php"; +} +if (!$res) { + die("Include of main fails"); +} + +// Libraries +require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php"; +require_once '../lib/dynamicsprices.lib.php'; +//require_once "../class/myclass.class.php"; +require_once __DIR__.'/../core/modules/modDynamicsPrices.class.php'; + +/** +* @var Conf $conf +* @var DoliDB $db +* @var HookManager $hookmanager +* @var Translate $langs +* @var User $user +*/ + +// Translations +$langs->loadLangs(array("admin", "dynamicsprices@dynamicsprices")); + +// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context +/** @var HookManager $hookmanager */ +$hookmanager->initHooks(array('dynamicspricessetup', 'globalsetup')); + +// Parameters +$action = GETPOST('action', 'aZ09'); +$backtopage = GETPOST('backtopage', 'alpha'); +$modulepart = GETPOST('modulepart', 'aZ09'); // Used by actions_setmoduleoptions.inc.php + +$value = GETPOST('value', 'alpha'); +$label = GETPOST('label', 'alpha'); +$scandir = GETPOST('scan_dir', 'alpha'); +$type = 'myobject'; + +$error = 0; +$setupnotempty = 1; + +// Access control +if (!$user->admin) { + accessforbidden(); +} + +// Actions on module constants +include DOL_DOCUMENT_ROOT.'/core/actions_setmoduleoptions.inc.php'; + +// Load dictionary definitions +$module = new modDynamicsPrices($db); +$taborder = empty($module->dictionaries['taborder']) ? array() : $module->dictionaries['taborder']; +$tabname = empty($module->dictionaries['tabname']) ? array() : $module->dictionaries['tabname']; +$tablib = empty($module->dictionaries['tablib']) ? array() : $module->dictionaries['tablib']; +$tabsql = empty($module->dictionaries['tabsql']) ? array() : $module->dictionaries['tabsql']; +$tabsqlsort = empty($module->dictionaries['tabsqlsort']) ? array() : $module->dictionaries['tabsqlsort']; +$tabfield = empty($module->dictionaries['tabfield']) ? array() : $module->dictionaries['tabfield']; +$tabfieldvalue = empty($module->dictionaries['tabfieldvalue']) ? array() : $module->dictionaries['tabfieldvalue']; +$tabfieldinsert = empty($module->dictionaries['tabfieldinsert']) ? array() : $module->dictionaries['tabfieldinsert']; +$tabrowid = empty($module->dictionaries['tabrowid']) ? array() : $module->dictionaries['tabrowid']; +$tabcond = empty($module->dictionaries['tabcond']) ? array() : $module->dictionaries['tabcond']; +$tabhelp = empty($module->dictionaries['tabhelp']) ? array() : $module->dictionaries['tabhelp']; +$tabsave = empty($module->dictionaries['tabsave']) ? array() : $module->dictionaries['tabsave']; +$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']); + +include DOL_DOCUMENT_ROOT.'/core/actions_dictionnaire.inc.php'; + + +// Set this to 1 to use the factory to manage constants. Warning, the generated module will be compatible with version v15+ only +$useFormSetup = 1; + +if (!class_exists('FormSetup')) { + require_once DOL_DOCUMENT_ROOT.'/core/class/html.formsetup.class.php'; +} +$formSetup = new FormSetup($db); + +// Access control +if (!$user->admin) { + accessforbidden(); +} + + +$action = 'edit'; + + +/* +* View +*/ + +$form = new Form($db); + +$help_url = ''; +$title = "DynamicsPricesSetup"; + +llxHeader('', $langs->trans($title), $help_url, '', 0, 0, '', '', '', 'mod-dynamicsprices page-admin'); + +// Subheader +$linkback = ''.$langs->trans("BackToModuleList").''; + +print load_fiche_titre($langs->trans($title), $linkback, 'title_setup'); + +// Configuration header +$head = dynamicspricesAdminPrepareHead(); +print dol_get_fiche_head($head, 'settings', $langs->trans($title), -1, "dynamicsprices@dynamicsprices"); + +// Setup page goes here +echo ''.$langs->trans("DynamicsPricesSetupPage").'

'; + +print ''; + +// Settings +setup_print_title($langs->trans("LMDB_UpdateOptions")); +setup_print_on_off('LMDB_COST_PRICE_ONLY'); +setup_print_on_off('LMDB_SUPPLIER_BUYPRICE_ALTERED'); +setup_print_on_off('LMDB_KIT_PRICE_FROM_COMPONENTS'); + +print '
'; + +print '
'; + +// Dictionary management +include DOL_DOCUMENT_ROOT.'/core/tpl/admin/dict.tpl.php'; + +if (empty($setupnotempty)) { +print '
'.$langs->trans("NothingToSetup"); +} + +// Page end +print dol_get_fiche_end(); + +llxFooter(); +$db->close(); diff --git a/core/modules/modDynamicsPrices.class.php b/core/modules/modDynamicsPrices.class.php index f3aaf19..04ea023 100644 --- a/core/modules/modDynamicsPrices.class.php +++ b/core/modules/modDynamicsPrices.class.php @@ -76,7 +76,7 @@ public function __construct($db) $this->editor_squarred_logo = 'logo.png@dynamicsprices'; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@dynamicsprices' // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' - $this->version = '1.1.1'; +$this->version = '1.2.0'; // Url to the file with your last numberversion of this module //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; @@ -253,40 +253,48 @@ public function __construct($db) 'tabhelp' => array(array('code' => $langs->trans('CodeTooltipHelp'), 'field2' => 'field2tooltip'), array('code' => $langs->trans('CodeTooltipHelp'), 'field2' => 'field2tooltip'), ...), ); */ - /* BEGIN MODULEBUILDER DICTIONARIES */ - $this->dictionaries = array( - 'langs'=>'dynamicsprices@dynamicsprices', - 'tabname'=>array(MAIN_DB_PREFIX."c_coefprice"), - 'tablib'=>array("LMDB_coefprice"), - 'tabsql'=>array( - 'SELECT t.rowid as rowid, t.entity, t.code, t.fk_nature, t.pricelevel, t.minrate, t.targetrate, t.active FROM '.MAIN_DB_PREFIX.'c_coefprice AS t WHERE t.entity = '.((int) $conf->entity), - ), - 'tabsqlsort'=>array( - "code ASC", - ), - 'tabfield'=>array( - "code,fk_nature,pricelevel,targetrate,minrate", - ), - 'tabfieldvalue'=>array( - "code,entity,fk_nature,pricelevel,targetrate,minrate", - ), - 'tabfieldinsert'=>array( - "code,entity,fk_nature,pricelevel,targetrate,minrate", - ), - 'tabrowid'=>array('rowid'), - 'tabcond'=>array( - isModEnabled('dynamicsprices'), - ), - 'tabhelp' => array( - 'code' => $langs->trans('LMDB_CodeTooltipHelp'), - 'entity' => $langs->trans('LMDB_ENtityTooltipHelp'), - 'fk_nature' => $langs->trans('LMDB_FkNatureTooltipHelp'), - 'pricelevel' => $langs->trans('LMDB_PriceLevelTooltipHelp'), - 'targetrate' => $langs->trans('LMDB_TargetRateTooltipHelp'), - 'minrate' => $langs->trans('LMDB_MinRateTooltipHelp'), - ), - ); - /* END MODULEBUILDER DICTIONARIES */ +/* BEGIN MODULEBUILDER DICTIONARIES */ +$this->dictionaries = array( +'langs'=>'dynamicsprices@dynamicsprices', +'tabname'=>array(MAIN_DB_PREFIX."c_coefprice", MAIN_DB_PREFIX."c_margin_on_cost"), +'tablib'=>array("LMDB_coefprice", "LMDB_marginoncost"), +'tabsql'=>array( +'SELECT t.rowid as rowid, t.entity, t.code, t.fk_nature, t.pricelevel, t.minrate, t.targetrate, t.active FROM '.MAIN_DB_PREFIX.'c_coefprice AS t WHERE t.entity = '.((int) $conf->entity), +'SELECT t.rowid as rowid, t.entity, t.code, t.code_nature, t.margin_on_cost_percent, t.active FROM '.MAIN_DB_PREFIX.'c_margin_on_cost AS t WHERE t.entity = '.((int) $conf->entity), +), +'tabsqlsort'=>array( +"code ASC", +"code ASC", +), +'tabfield'=>array( +"code,fk_nature,pricelevel,targetrate,minrate", +"code,code_nature,margin_on_cost_percent", +), +'tabfieldvalue'=>array( +"code,entity,fk_nature,pricelevel,targetrate,minrate", +"code,entity,code_nature,margin_on_cost_percent", +), +'tabfieldinsert'=>array( +"code,entity,fk_nature,pricelevel,targetrate,minrate", +"code,entity,code_nature,margin_on_cost_percent", +), +'tabrowid'=>array('rowid', 'rowid'), +'tabcond'=>array( +isModEnabled('dynamicsprices'), +isModEnabled('dynamicsprices'), +), +'tabhelp' => array( +'code' => $langs->trans('LMDB_CodeTooltipHelp'), +'entity' => $langs->trans('LMDB_ENtityTooltipHelp'), +'fk_nature' => $langs->trans('LMDB_FkNatureTooltipHelp'), +'pricelevel' => $langs->trans('LMDB_PriceLevelTooltipHelp'), +'targetrate' => $langs->trans('LMDB_TargetRateTooltipHelp'), +'minrate' => $langs->trans('LMDB_MinRateTooltipHelp'), +'code_nature' => $langs->trans('LMDB_CodeNatureTooltipHelp'), +'margin_on_cost_percent' => $langs->trans('LMDB_MarginOnCostTooltipHelp'), +), +); +/* END MODULEBUILDER DICTIONARIES */ // Boxes/Widgets // Add here list of php file(s) stored in dynamicsprices/core/boxes that contains a class to show a widget. diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index 6ac9a4c..6d276a7 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -37,298 +37,294 @@ */ class InterfaceDynamicsPricesTriggers extends DolibarrTriggers { - /** - * Constructor - * - * @param DoliDB $db Database handler - */ - public function __construct($db) - { - parent::__construct($db); - $this->family = "product"; - $this->description = "Dynamicsprices triggers."; - $this->version = self::VERSIONS['dev']; - $this->picto = 'logo.png@dynamicsprices'; - } - - /** - * Function called when a Dolibarr business event is done. - * All functions "runTrigger" are triggered if the file is inside the directory core/triggers - * - * @param string $action Event action code - * @param CommonObject $object Object - * @param User $user Object user - * @param Translate $langs Object langs - * @param Conf $conf Object conf - * @return int Return integer <0 if KO, 0 if no triggered ran, >0 if OK - */ - public function runTrigger($action, $object, User $user, Translate $langs, Conf $conf) - { - if (!isModEnabled('dynamicsprices')) { - return 0; // If module is not enabled, we do nothing - } - - // Put here code you want to execute when a Dolibarr business events occurs. - // Data and type of action are stored into $object and $action - - global $db; - - if (!getDolGlobalString('LMDB_COST_PRICE_ONLY')) { - if ($action === 'SUPPLIER_PRODUCT_BUYPRICE_CREATE' || $action === 'SUPPLIER_PRODUCT_BUYPRICE_MODIFY' || $action == 'SUPPLIER_PRODUCT_BUYPRICE_DELETE') { - require_once __DIR__.'/../../lib/dynamicsprices.lib.php'; - //var_dump(getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED')); - if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED')) { - $results = update_customer_prices_from_suppliers($db, $user, $langs, $conf, $object->fk_product); - } - //var_dump($results); - } - } else { - if ($action === 'PRODUCT_MODIFY') { - require_once __DIR__.'/../../lib/dynamicsprices.lib.php'; - //var_dump(getDolGlobalString('LMDB_COST_PRICE_ONLY')); - if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED')) { - $results = update_customer_prices_from_cost_price($db, $user, $langs, $conf, $object->fk_product); - } - } - } - - // You can isolate code for each action in a separate method: this method should be named like the trigger in camelCase. - // For example : COMPANY_CREATE => public function companyCreate($action, $object, User $user, Translate $langs, Conf $conf) - $methodName = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($action))))); - $callback = array($this, $methodName); - if (is_callable($callback)) { - dol_syslog( - "Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id - ); - - return call_user_func($callback, $action, $object, $user, $langs, $conf); - - - } - - // Or you can execute some code here - switch ($action) { // @phan-suppress-current-line PhanNoopSwitchCases - // Users - //case 'USER_CREATE': - //case 'USER_MODIFY': - //case 'USER_NEW_PASSWORD': - //case 'USER_ENABLEDISABLE': - //case 'USER_DELETE': - - // Actions - //case 'ACTION_MODIFY': - //case 'ACTION_CREATE': - //case 'ACTION_DELETE': - - // Groups - //case 'USERGROUP_CREATE': - //case 'USERGROUP_MODIFY': - //case 'USERGROUP_DELETE': - - // Companies - //case 'COMPANY_CREATE': - //case 'COMPANY_MODIFY': - //case 'COMPANY_DELETE': - - // Contacts - //case 'CONTACT_CREATE': - //case 'CONTACT_MODIFY': - //case 'CONTACT_DELETE': - //case 'CONTACT_ENABLEDISABLE': - - // Products - //case 'PRODUCT_CREATE': - //case 'PRODUCT_MODIFY': - //case 'PRODUCT_DELETE': - //case 'PRODUCT_PRICE_MODIFY': - //case 'PRODUCT_SET_MULTILANGS': - //case 'PRODUCT_DEL_MULTILANGS': - - //Stock movement - //case 'STOCK_MOVEMENT': - - //MYECMDIR - //case 'MYECMDIR_CREATE': - //case 'MYECMDIR_MODIFY': - //case 'MYECMDIR_DELETE': - - // Sales orders - //case 'ORDER_CREATE': - //case 'ORDER_MODIFY': - //case 'ORDER_VALIDATE': - //case 'ORDER_DELETE': - //case 'ORDER_CANCEL': - //case 'ORDER_SENTBYMAIL': - //case 'ORDER_CLASSIFY_BILLED': // TODO Replace it with ORDER_BILLED - //case 'ORDER_CLASSIFY_UNBILLED': // TODO Replace it with ORDER_UNBILLED - //case 'ORDER_SETDRAFT': - //case 'LINEORDER_INSERT': - //case 'LINEORDER_MODIFY': - //case 'LINEORDER_DELETE': - - // Supplier orders - //case 'ORDER_SUPPLIER_CREATE': - //case 'ORDER_SUPPLIER_MODIFY': - //case 'ORDER_SUPPLIER_VALIDATE': - //case 'ORDER_SUPPLIER_DELETE': - //case 'ORDER_SUPPLIER_APPROVE': - //case 'ORDER_SUPPLIER_CLASSIFY_BILLED': // TODO Replace with ORDER_SUPPLIER_BILLED - //case 'ORDER_SUPPLIER_CLASSIFY_UNBILLED': // TODO Replace with ORDER_SUPPLIER_UNBILLED - //case 'ORDER_SUPPLIER_REFUSE': - //case 'ORDER_SUPPLIER_CANCEL': - //case 'ORDER_SUPPLIER_SENTBYMAIL': - //case 'ORDER_SUPPLIER_RECEIVE': - //case 'LINEORDER_SUPPLIER_DISPATCH': - //case 'LINEORDER_SUPPLIER_CREATE': - //case 'LINEORDER_SUPPLIER_MODIFY': - //case 'LINEORDER_SUPPLIER_DELETE': - - // Proposals - //case 'PROPAL_CREATE': - //case 'PROPAL_MODIFY': - //case 'PROPAL_VALIDATE': - //case 'PROPAL_SENTBYMAIL': - //case 'PROPAL_CLASSIFY_BILLED': // TODO Replace it with PROPAL_BILLED - //case 'PROPAL_CLASSIFY_UNBILLED': // TODO Replace it with PROPAL_UNBILLED - //case 'PROPAL_CLOSE_SIGNED': - //case 'PROPAL_CLOSE_REFUSED': - //case 'PROPAL_DELETE': - //case 'LINEPROPAL_INSERT': - //case 'LINEPROPAL_MODIFY': - //case 'LINEPROPAL_DELETE': - - // SupplierProposal - //case 'SUPPLIER_PROPOSAL_CREATE': - //case 'SUPPLIER_PROPOSAL_MODIFY': - //case 'SUPPLIER_PROPOSAL_VALIDATE': - //case 'SUPPLIER_PROPOSAL_SENTBYMAIL': - //case 'SUPPLIER_PROPOSAL_CLOSE_SIGNED': - //case 'SUPPLIER_PROPOSAL_CLOSE_REFUSED': - //case 'SUPPLIER_PROPOSAL_DELETE': - //case 'LINESUPPLIER_PROPOSAL_INSERT': - //case 'LINESUPPLIER_PROPOSAL_MODIFY': - //case 'LINESUPPLIER_PROPOSAL_DELETE': - - // Contracts - //case 'CONTRACT_CREATE': - //case 'CONTRACT_MODIFY': - //case 'CONTRACT_ACTIVATE': - //case 'CONTRACT_CANCEL': - //case 'CONTRACT_CLOSE': - //case 'CONTRACT_DELETE': - //case 'LINECONTRACT_INSERT': - //case 'LINECONTRACT_MODIFY': - //case 'LINECONTRACT_DELETE': - - // Bills - //case 'BILL_CREATE': - //case 'BILL_MODIFY': - //case 'BILL_VALIDATE': - //case 'BILL_UNVALIDATE': - //case 'BILL_SENTBYMAIL': - //case 'BILL_CANCEL': - //case 'BILL_DELETE': - //case 'BILL_PAYED': - //case 'LINEBILL_INSERT': - //case 'LINEBILL_MODIFY': - //case 'LINEBILL_DELETE': - - // Recurring Bills - //case 'BILLREC_MODIFY': - //case 'BILLREC_DELETE': - //case 'BILLREC_AUTOCREATEBILL': - //case 'LINEBILLREC_MODIFY': - //case 'LINEBILLREC_DELETE': - - //Supplier Bill - //case 'BILL_SUPPLIER_CREATE': - //case 'BILL_SUPPLIER_MODIFY': - //case 'BILL_SUPPLIER_DELETE': - //case 'BILL_SUPPLIER_PAYED': - //case 'BILL_SUPPLIER_UNPAYED': - //case 'BILL_SUPPLIER_VALIDATE': - //case 'BILL_SUPPLIER_UNVALIDATE': - //case 'LINEBILL_SUPPLIER_CREATE': - //case 'LINEBILL_SUPPLIER_MODIFY': - //case 'LINEBILL_SUPPLIER_DELETE': - - // Payments - //case 'PAYMENT_CUSTOMER_CREATE': - //case 'PAYMENT_SUPPLIER_CREATE': - //case 'PAYMENT_ADD_TO_BANK': - //case 'PAYMENT_DELETE': - - // Online - //case 'PAYMENT_PAYBOX_OK': - //case 'PAYMENT_PAYPAL_OK': - //case 'PAYMENT_STRIPE_OK': - - // Donation - //case 'DON_CREATE': - //case 'DON_MODIFY': - //case 'DON_DELETE': - - // Interventions - //case 'FICHINTER_CREATE': - //case 'FICHINTER_MODIFY': - //case 'FICHINTER_VALIDATE': - //case 'FICHINTER_CLASSIFY_BILLED': // TODO Replace it with FICHINTER_BILLED - //case 'FICHINTER_CLASSIFY_UNBILLED': // TODO Replace it with FICHINTER_UNBILLED - //case 'FICHINTER_DELETE': - //case 'LINEFICHINTER_CREATE': - //case 'LINEFICHINTER_MODIFY': - //case 'LINEFICHINTER_DELETE': - - // Members - //case 'MEMBER_CREATE': - //case 'MEMBER_VALIDATE': - //case 'MEMBER_SUBSCRIPTION': - //case 'MEMBER_MODIFY': - //case 'MEMBER_NEW_PASSWORD': - //case 'MEMBER_RESILIATE': - //case 'MEMBER_DELETE': - - // Categories - //case 'CATEGORY_CREATE': - //case 'CATEGORY_MODIFY': - //case 'CATEGORY_DELETE': - //case 'CATEGORY_SET_MULTILANGS': - - // Projects - //case 'PROJECT_CREATE': - //case 'PROJECT_MODIFY': - //case 'PROJECT_DELETE': - - // Project tasks - //case 'TASK_CREATE': - //case 'TASK_MODIFY': - //case 'TASK_DELETE': - - // Task time spent - //case 'TASK_TIMESPENT_CREATE': - //case 'TASK_TIMESPENT_MODIFY': - //case 'TASK_TIMESPENT_DELETE': - //case 'PROJECT_ADD_CONTACT': - //case 'PROJECT_DELETE_CONTACT': - //case 'PROJECT_DELETE_RESOURCE': - - // Shipping - //case 'SHIPPING_CREATE': - //case 'SHIPPING_MODIFY': - //case 'SHIPPING_VALIDATE': - //case 'SHIPPING_SENTBYMAIL': - //case 'SHIPPING_BILLED': - //case 'SHIPPING_CLOSED': - //case 'SHIPPING_REOPEN': - //case 'SHIPPING_DELETE': - - // and more... - - default: - dol_syslog("Trigger '".$this->name."' for action '".$action."' launched by ".__FILE__.". id=".$object->id); - break; - } - - return 0; - } + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct($db) + { + parent::__construct($db); + $this->family = "product"; + $this->description = "Dynamicsprices triggers."; + $this->version = self::VERSIONS['dev']; + $this->picto = 'logo.png@dynamicsprices'; + } + + /** + * Function called when a Dolibarr business event is done. + * All functions "runTrigger" are triggered if the file is inside the directory core/triggers + * + * @param string $action Event action code + * @param CommonObject $object Object + * @param User $user Object user + * @param Translate $langs Object langs + * @param Conf $conf Object conf + * @return int Return integer <0 if KO, 0 if no triggered ran, >0 if OK + */ + public function runTrigger($action, $object, User $user, Translate $langs, Conf $conf) + { + if (!isModEnabled('dynamicsprices')) { + return 0; // If module is not enabled, we do nothing + } + + // Put here code you want to execute when a Dolibarr business events occurs. + // Data and type of action are stored into $object and $action + + global $db; + + require_once __DIR__.'/../../lib/dynamicsprices.lib.php'; + $updateFunction = getDolGlobalString('LMDB_COST_PRICE_ONLY') ? 'update_customer_prices_from_cost_price' : 'update_customer_prices_from_suppliers'; + $affectedActions = array('SUPPLIER_PRODUCT_BUYPRICE_CREATE', 'SUPPLIER_PRODUCT_BUYPRICE_MODIFY', 'SUPPLIER_PRODUCT_BUYPRICE_DELETE', 'PRODUCT_MODIFY', 'PRODUCT_PRICE_MODIFY'); + + if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED') && in_array($action, $affectedActions, true)) { + $productId = !empty($object->fk_product) ? $object->fk_product : (isset($object->id) ? $object->id : 0); + if ($productId > 0) { + call_user_func($updateFunction, $db, $user, $langs, $conf, $productId); + $parentKits = dynamicsprices_get_parent_kits($db, $productId); + foreach ($parentKits as $kitId) { + call_user_func($updateFunction, $db, $user, $langs, $conf, $kitId); + } + } + } + + // You can isolate code for each action in a separate method: this method should be named like the trigger in camelCase. + // For example : COMPANY_CREATE => public function companyCreate($action, $object, User $user, Translate $langs, Conf $conf) + $methodName = lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($action))))); + $callback = array($this, $methodName); + if (is_callable($callback)) { + dol_syslog( + "Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id + ); + + return call_user_func($callback, $action, $object, $user, $langs, $conf); + + + } + + // Or you can execute some code here + switch ($action) { // @phan-suppress-current-line PhanNoopSwitchCases + // Users + //case 'USER_CREATE': + //case 'USER_MODIFY': + //case 'USER_NEW_PASSWORD': + //case 'USER_ENABLEDISABLE': + //case 'USER_DELETE': + + // Actions + //case 'ACTION_MODIFY': + //case 'ACTION_CREATE': + //case 'ACTION_DELETE': + + // Groups + //case 'USERGROUP_CREATE': + //case 'USERGROUP_MODIFY': + //case 'USERGROUP_DELETE': + + // Companies + //case 'COMPANY_CREATE': + //case 'COMPANY_MODIFY': + //case 'COMPANY_DELETE': + + // Contacts + //case 'CONTACT_CREATE': + //case 'CONTACT_MODIFY': + //case 'CONTACT_DELETE': + //case 'CONTACT_ENABLEDISABLE': + + // Products + //case 'PRODUCT_CREATE': + //case 'PRODUCT_MODIFY': + //case 'PRODUCT_DELETE': + //case 'PRODUCT_PRICE_MODIFY': + //case 'PRODUCT_SET_MULTILANGS': + //case 'PRODUCT_DEL_MULTILANGS': + + //Stock movement + //case 'STOCK_MOVEMENT': + + //MYECMDIR + //case 'MYECMDIR_CREATE': + //case 'MYECMDIR_MODIFY': + //case 'MYECMDIR_DELETE': + + // Sales orders + //case 'ORDER_CREATE': + //case 'ORDER_MODIFY': + //case 'ORDER_VALIDATE': + //case 'ORDER_DELETE': + //case 'ORDER_CANCEL': + //case 'ORDER_SENTBYMAIL': + //case 'ORDER_CLASSIFY_BILLED': // TODO Replace it with ORDER_BILLED + //case 'ORDER_CLASSIFY_UNBILLED': // TODO Replace it with ORDER_UNBILLED + //case 'ORDER_SETDRAFT': + //case 'LINEORDER_INSERT': + //case 'LINEORDER_MODIFY': + //case 'LINEORDER_DELETE': + + // Supplier orders + //case 'ORDER_SUPPLIER_CREATE': + //case 'ORDER_SUPPLIER_MODIFY': + //case 'ORDER_SUPPLIER_VALIDATE': + //case 'ORDER_SUPPLIER_DELETE': + //case 'ORDER_SUPPLIER_APPROVE': + //case 'ORDER_SUPPLIER_CLASSIFY_BILLED': // TODO Replace with ORDER_SUPPLIER_BILLED + //case 'ORDER_SUPPLIER_CLASSIFY_UNBILLED': // TODO Replace with ORDER_SUPPLIER_UNBILLED + //case 'ORDER_SUPPLIER_REFUSE': + //case 'ORDER_SUPPLIER_CANCEL': + //case 'ORDER_SUPPLIER_SENTBYMAIL': + //case 'ORDER_SUPPLIER_RECEIVE': + //case 'LINEORDER_SUPPLIER_DISPATCH': + //case 'LINEORDER_SUPPLIER_CREATE': + //case 'LINEORDER_SUPPLIER_MODIFY': + //case 'LINEORDER_SUPPLIER_DELETE': + + // Proposals + //case 'PROPAL_CREATE': + //case 'PROPAL_MODIFY': + //case 'PROPAL_VALIDATE': + //case 'PROPAL_SENTBYMAIL': + //case 'PROPAL_CLASSIFY_BILLED': // TODO Replace it with PROPAL_BILLED + //case 'PROPAL_CLASSIFY_UNBILLED': // TODO Replace it with PROPAL_UNBILLED + //case 'PROPAL_CLOSE_SIGNED': + //case 'PROPAL_CLOSE_REFUSED': + //case 'PROPAL_DELETE': + //case 'LINEPROPAL_INSERT': + //case 'LINEPROPAL_MODIFY': + //case 'LINEPROPAL_DELETE': + + // SupplierProposal + //case 'SUPPLIER_PROPOSAL_CREATE': + //case 'SUPPLIER_PROPOSAL_MODIFY': + //case 'SUPPLIER_PROPOSAL_VALIDATE': + //case 'SUPPLIER_PROPOSAL_SENTBYMAIL': + //case 'SUPPLIER_PROPOSAL_CLOSE_SIGNED': + //case 'SUPPLIER_PROPOSAL_CLOSE_REFUSED': + //case 'SUPPLIER_PROPOSAL_DELETE': + //case 'LINESUPPLIER_PROPOSAL_INSERT': + //case 'LINESUPPLIER_PROPOSAL_MODIFY': + //case 'LINESUPPLIER_PROPOSAL_DELETE': + + // Contracts + //case 'CONTRACT_CREATE': + //case 'CONTRACT_MODIFY': + //case 'CONTRACT_ACTIVATE': + //case 'CONTRACT_CANCEL': + //case 'CONTRACT_CLOSE': + //case 'CONTRACT_DELETE': + //case 'LINECONTRACT_INSERT': + //case 'LINECONTRACT_MODIFY': + //case 'LINECONTRACT_DELETE': + + // Bills + //case 'BILL_CREATE': + //case 'BILL_MODIFY': + //case 'BILL_VALIDATE': + //case 'BILL_UNVALIDATE': + //case 'BILL_SENTBYMAIL': + //case 'BILL_CANCEL': + //case 'BILL_DELETE': + //case 'BILL_PAYED': + //case 'LINEBILL_INSERT': + //case 'LINEBILL_MODIFY': + //case 'LINEBILL_DELETE': + + // Recurring Bills + //case 'BILLREC_MODIFY': + //case 'BILLREC_DELETE': + //case 'BILLREC_AUTOCREATEBILL': + //case 'LINEBILLREC_MODIFY': + //case 'LINEBILLREC_DELETE': + + //Supplier Bill + //case 'BILL_SUPPLIER_CREATE': + //case 'BILL_SUPPLIER_MODIFY': + //case 'BILL_SUPPLIER_DELETE': + //case 'BILL_SUPPLIER_PAYED': + //case 'BILL_SUPPLIER_UNPAYED': + //case 'BILL_SUPPLIER_VALIDATE': + //case 'BILL_SUPPLIER_UNVALIDATE': + //case 'LINEBILL_SUPPLIER_CREATE': + //case 'LINEBILL_SUPPLIER_MODIFY': + //case 'LINEBILL_SUPPLIER_DELETE': + + // Payments + //case 'PAYMENT_CUSTOMER_CREATE': + //case 'PAYMENT_SUPPLIER_CREATE': + //case 'PAYMENT_ADD_TO_BANK': + //case 'PAYMENT_DELETE': + + // Online + //case 'PAYMENT_PAYBOX_OK': + //case 'PAYMENT_PAYPAL_OK': + //case 'PAYMENT_STRIPE_OK': + + // Donation + //case 'DON_CREATE': + //case 'DON_MODIFY': + //case 'DON_DELETE': + + // Interventions + //case 'FICHINTER_CREATE': + //case 'FICHINTER_MODIFY': + //case 'FICHINTER_VALIDATE': + //case 'FICHINTER_CLASSIFY_BILLED': // TODO Replace it with FICHINTER_BILLED + //case 'FICHINTER_CLASSIFY_UNBILLED': // TODO Replace it with FICHINTER_UNBILLED + //case 'FICHINTER_DELETE': + //case 'LINEFICHINTER_CREATE': + //case 'LINEFICHINTER_MODIFY': + //case 'LINEFICHINTER_DELETE': + + // Members + //case 'MEMBER_CREATE': + //case 'MEMBER_VALIDATE': + //case 'MEMBER_SUBSCRIPTION': + //case 'MEMBER_MODIFY': + //case 'MEMBER_NEW_PASSWORD': + //case 'MEMBER_RESILIATE': + //case 'MEMBER_DELETE': + + // Categories + //case 'CATEGORY_CREATE': + //case 'CATEGORY_MODIFY': + //case 'CATEGORY_DELETE': + //case 'CATEGORY_SET_MULTILANGS': + + // Projects + //case 'PROJECT_CREATE': + //case 'PROJECT_MODIFY': + //case 'PROJECT_DELETE': + + // Project tasks + //case 'TASK_CREATE': + //case 'TASK_MODIFY': + //case 'TASK_DELETE': + + // Task time spent + //case 'TASK_TIMESPENT_CREATE': + //case 'TASK_TIMESPENT_MODIFY': + //case 'TASK_TIMESPENT_DELETE': + //case 'PROJECT_ADD_CONTACT': + //case 'PROJECT_DELETE_CONTACT': + //case 'PROJECT_DELETE_RESOURCE': + + // Shipping + //case 'SHIPPING_CREATE': + //case 'SHIPPING_MODIFY': + //case 'SHIPPING_VALIDATE': + //case 'SHIPPING_SENTBYMAIL': + //case 'SHIPPING_BILLED': + //case 'SHIPPING_CLOSED': + //case 'SHIPPING_REOPEN': + //case 'SHIPPING_DELETE': + + // and more... + + default: + dol_syslog("Trigger '".$this->name."' for action '".$action."' launched by ".__FILE__.". id=".$object->id); + break; + } + + return 0; + } } diff --git a/langs/en_US/dynamicsprices.lang b/langs/en_US/dynamicsprices.lang index 3087700..c802fde 100644 --- a/langs/en_US/dynamicsprices.lang +++ b/langs/en_US/dynamicsprices.lang @@ -12,13 +12,14 @@ ModuleDynamicsPricesDesc = This module allows you to update sales prices based o #
# Admin page
#
-DynamicsPricesSetup = Dynamic sales price settings
-Settings = Settings
-DynamicsPricesSetupPage = Dynamic sales price module settings page
-LMDB_UpdateOptions = Sales price update options
-LMDB_COST_PRICE_ONLY = Update sales prices based on cost prices only. -LMDB_SUPPLIER_BUYPRICE_ALTERED = Update sales prices when creating/updating/deleting a purchase price, or when updating the cost price if cost-based calculation is enabled. -
+DynamicsPricesSetup = Dynamic sales price settings
+Settings = Settings
+DynamicsPricesSetupPage = Dynamic sales price module settings page
+LMDB_UpdateOptions = Sales price update options
+LMDB_KIT_PRICE_FROM_COMPONENTS = Calculate Kit prices from component prices
+LMDB_COST_PRICE_ONLY = Update sales prices based on cost prices only. +LMDB_SUPPLIER_BUYPRICE_ALTERED = Update sales prices when creating/updating/deleting a purchase price, or when updating the cost price if cost-based calculation is enabled. +
#
# About page
#
@@ -49,8 +50,11 @@ Pricelevel = Price level ID
Targetrate = Target margin rate (in %)
Minrate = Minimum margin rate (in %)

-LMDB_CommentAutoUpdateSellPrice = Sales prices are updated based on the price coefficients given in the dictionary and the average of the Supplier purchase unit prices.
-LMDB_LabelAutoUpdateSellPrice = Automatic update of sales prices
-LMDB_coefprice = Margin rates on sales prices
- -DynamicsPrices = Dynamic sales prices +LMDB_CommentAutoUpdateSellPrice = Sales prices are updated based on the price coefficients given in the dictionary and the average of the Supplier purchase unit prices.
+LMDB_LabelAutoUpdateSellPrice = Automatic update of sales prices
+LMDB_coefprice = Margin rates on sales prices
+LMDB_marginoncost = Margin rate on cost prices
+ +DynamicsPrices = Dynamic sales prices +LMDB_CodeNatureTooltipHelp = Product nature code +LMDB_MarginOnCostTooltipHelp = Margin rate applied on the average purchase cost price diff --git a/langs/fr_FR/dynamicsprices.lang b/langs/fr_FR/dynamicsprices.lang index e5c65e6..5320608 100644 --- a/langs/fr_FR/dynamicsprices.lang +++ b/langs/fr_FR/dynamicsprices.lang @@ -15,10 +15,11 @@ ModuleDynamicsPricesDesc = Ce module permet de mettre à jour les prix de vente DynamicsPricesSetup = Réglages des prix de vente dynamiques Settings = Réglages DynamicsPricesSetupPage = Page de réglage du module de Prix de vente dynamiques -LMDB_UpdateOptions=Options de mise à jour des prix de vente - -LMDB_COST_PRICE_ONLY = Ne mettre à jour les prix de vente que sur la base des prix de revient. -LMDB_SUPPLIER_BUYPRICE_ALTERED = Actualisation de prix de vente à la création/mise à jour/suppression d'un prix d'achat ou l'actualisation du prix de revient si le calcul sur le prix de revient est activé. +LMDB_UpdateOptions=Options de mise à jour des prix de vente +LMDB_KIT_PRICE_FROM_COMPONENTS = Calculer les prix des Kits à partir des composants + +LMDB_COST_PRICE_ONLY = Ne mettre à jour les prix de vente que sur la base des prix de revient. +LMDB_SUPPLIER_BUYPRICE_ALTERED = Actualisation de prix de vente à la création/mise à jour/suppression d'un prix d'achat ou l'actualisation du prix de revient si le calcul sur le prix de revient est activé. @@ -53,8 +54,11 @@ Targetrate = Taux de marge cible (en %) Minrate = Taux de marge minimum (en %) Entity = Entité -LMDB_CommentAutoUpdateSellPrice = Les prix de vente sont mis à jour en fonction des coefficients de prix donnés dans le dictionnaire, et de la moyenne des prix unitaires d'achats fournisseurs. -LMDB_LabelAutoUpdateSellPrice = Mise à jour automatique des prix de vente -LMDB_coefprice = Taux de marges sur les prix de vente -DynamicsPrices = Prix de vente dynamiques +LMDB_CommentAutoUpdateSellPrice = Les prix de vente sont mis à jour en fonction des coefficients de prix donnés dans le dictionnaire, et de la moyenne des prix unitaires d'achats fournisseurs. +LMDB_LabelAutoUpdateSellPrice = Mise à jour automatique des prix de vente +LMDB_coefprice = Taux de marges sur les prix de vente +LMDB_marginoncost = Taux de marge sur les prix de revient +DynamicsPrices = Prix de vente dynamiques +LMDB_CodeNatureTooltipHelp = Code de la nature de produit +LMDB_MarginOnCostTooltipHelp = Taux de marge appliqué sur le prix de revient moyen d'achat diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index dcfaae0..5c88adf 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -86,343 +86,442 @@ function dynamicspricesAdminPrepareHead() } -function update_customer_prices_from_suppliers($db, $user, $langs, $conf, $productid = 0) +// Check if a product is a Kit using product associations +function dynamicsprices_is_kit($db, $productId) +{ + $sql = "SELECT COUNT(rowid) as nb"; + $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; + $sql .= " WHERE fk_product_parent = ".((int) $productId); + $sql .= " AND entity IN (".getEntity('product').")"; + $sql .= " AND (type IS NULL OR type IN (0,1))"; + + $resql = $db->query($sql); + if ($resql === false) { + return false; + } + + $obj = $db->fetch_object($resql); + return (!empty($obj->nb) && (int) $obj->nb > 0); +} + +// Get Kit components with quantities +function dynamicsprices_get_kit_components($db, $productId) +{ + $components = array(); + + $sql = "SELECT fk_product_child as fk_product, qty"; + $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; + $sql .= " WHERE fk_product_parent = ".((int) $productId); + $sql .= " AND entity IN (".getEntity('product').")"; + $sql .= " AND (type IS NULL OR type IN (0,1))"; + + $resql = $db->query($sql); + if ($resql === false) { + return $components; + } + + while ($obj = $db->fetch_object($resql)) { + $components[] = array('id' => (int) $obj->fk_product, 'qty' => (float) $obj->qty); + } + + return $components; +} + +// Get parent Kits containing a component +function dynamicsprices_get_parent_kits($db, $productId) { - dol_include_once('/product/class/product.class.php'); - - global $conf; - - $products = []; - $nb_line = 0; - $entity = $conf->entity; - - if ($productid > 0) { - $products[] = $productid; - } else { - $sql = "SELECT rowid, finished"; - $sql.= " FROM ".MAIN_DB_PREFIX."product"; - $sql.= " WHERE tosell = 1 "; - $sql.= " AND entity IN (".getEntity('product').")"; - - //var_dump($sql.'
'); - - $resql = $db->query($sql); - if ($resql === false) { - dol_print_error($db); - return; + $parents = array(); + + $sql = "SELECT fk_product_parent as fk_parent"; + $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; + $sql .= " WHERE fk_product_child = ".((int) $productId); + $sql .= " AND entity IN (".getEntity('product').")"; + $sql .= " AND (type IS NULL OR type IN (0,1))"; + + $resql = $db->query($sql); + if ($resql === false) { + return $parents; + } + + while ($obj = $db->fetch_object($resql)) { + $parents[] = (int) $obj->fk_parent; + } + + return $parents; +} + +// Compute average supplier price +function dynamicsprices_get_average_supplier_price($db, $productId) +{ + $sql = "SELECT price"; + $sql .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price"; + $sql .= " WHERE fk_product = ".((int) $productId); + $sql .= " AND entity IN (".getEntity('product_fournisseur_price').")"; + + $resql = $db->query($sql); + if ($resql === false) { + return null; + } + + $prices = array(); + while ($obj = $db->fetch_object($resql)) { + $prices[] = (float) $obj->price; + } + + if (count($prices) === 0) { + return null; + } + + return array_sum($prices) / count($prices); +} + +// Get margin on cost percent for a nature +function dynamicsprices_get_margin_on_cost_percent($db, $natureId) +{ + $sql = "SELECT margin_on_cost_percent"; + $sql .= " FROM ".MAIN_DB_PREFIX."c_margin_on_cost"; + $sql .= " WHERE code_nature = '".$db->escape($natureId)."'"; + $sql .= " AND entity IN (".getEntity('entity').")"; + $sql .= " AND active = 1"; + $sql .= " ORDER BY rowid DESC"; + $sql .= " LIMIT 1"; + + $resql = $db->query($sql); + if ($resql === false) { + return 0; + } + + $obj = $db->fetch_object($resql); + return $obj ? (float) $obj->margin_on_cost_percent : 0; +} + +// Fetch selling price rules from dictionary +function dynamicsprices_get_price_rules($db, $natureId) +{ + $rules = array(); + + $sql = "SELECT pricelevel, minrate, targetrate"; + $sql .= " FROM ".MAIN_DB_PREFIX."c_coefprice"; + $sql .= " WHERE fk_nature = ".((int) $natureId); + $sql .= " AND entity IN (".getEntity('entity').")"; + $sql .= " AND active = 1"; + + $resql = $db->query($sql); + if ($resql === false) { + return $rules; + } + + while ($obj = $db->fetch_object($resql)) { + $rules[(int) $obj->pricelevel] = array('minrate' => (float) $obj->minrate, 'targetrate' => (float) $obj->targetrate); + } + + return $rules; +} + +// Save cost price on product table +function dynamicsprices_save_cost_price($db, $productId, $costPrice) +{ + $sql = "UPDATE ".MAIN_DB_PREFIX."product"; + $sql .= " SET cost_price = ".price2num($costPrice, 'MU'); + $sql .= " WHERE rowid = ".((int) $productId); + $sql .= " AND entity IN (".getEntity('product').")"; + + return $db->query($sql); +} + +// Calculate and persist Kit cost price based on components +function dynamicsprices_update_kit_cost_price($db, $productId) +{ + $components = dynamicsprices_get_kit_components($db, $productId); + $totalCost = 0; + + foreach ($components as $component) { + $avg = dynamicsprices_get_average_supplier_price($db, $component['id']); + $avg = ($avg === null) ? 0 : $avg; + $totalCost += $avg * (float) $component['qty']; + } + + dynamicsprices_save_cost_price($db, $productId, $totalCost); + + return $totalCost; +} + +// Update selling prices from a base cost and rules +function dynamicsprices_update_prices_from_base($db, $user, $product, $basePrice, $rules, $tvaTx, $entity) +{ + $nb_line = 0; + $now = $db->idate(dol_now()); + + foreach ($rules as $level => $rule) { + $price = $basePrice * (1 + ((float) $rule['targetrate'] / 100)); + $price_ttc = $price * (1 + ((float) $tvaTx / 100)); + $price_min = $basePrice * (1 + ((float) $rule['minrate'] / 100)); + $price_min_ttc = $price_min * (1 + ((float) $tvaTx / 100)); + + $sqlv = "SELECT price, price_ttc, price_min, price_min_ttc"; + $sqlv .= " FROM ".MAIN_DB_PREFIX."product_price"; + $sqlv .= " WHERE fk_product = ".((int) $product->id); + $sqlv .= " AND price_level = ".((int) $level); + $sqlv .= " AND entity IN (".getEntity('productprice').")"; + $sqlv .= " ORDER BY date_price DESC LIMIT 1"; + + $resqlv = $db->query($sqlv); + $current = $db->fetch_object($resqlv); + + if (!$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { + $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; + $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; + $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; + $db->query($sqlp); + $nb_line++; } + } - while ($obj = $db->fetch_object($resql)) { - $products[] = array('id'=>$obj->rowid, 'nature'=>$obj->finished); - } - } + return $nb_line; +} - foreach ($products as $prod) { - $prodid = is_array($prod) ? $prod['id'] : $prod; - //var_dump('ID Produit = '.$prodid.'
'); - $natureid = is_array($prod) ? $prod['nature'] : 0; - //var_dump('Nature = '.$natureid.'
'); - $product = new Product($db); - $product->fetch($prodid); - - $tva_tx = (float) $product->tva_tx; - - //var_dump('$tva_tx = '.price($tva_tx).'
'); - - // Prix fournisseurs - $sqlf = "SELECT price FROM ".MAIN_DB_PREFIX."product_fournisseur_price - WHERE fk_product = ".((int) $prodid); - $sqlf.= " AND entity IN (".getEntity('product_fournisseur_price').")"; - - //var_dump('$sqfl = '.$sqlf.'
'); - - $resqlf = $db->query($sqlf); - - $prices_fourn = []; - while ($objf = $db->fetch_object($resqlf)) { - $prices_fourn[] = (float) $objf->price; - } - - if (!count($prices_fourn)) continue; - - $moyenne = array_sum($prices_fourn) / count($prices_fourn); - - //var_dump('moyenne = '.$moyenne.'
'); - - // Coefficients par nature - $sqlc = "SELECT code, pricelevel, minrate, targetrate - FROM ".MAIN_DB_PREFIX."c_coefprice - WHERE fk_nature = ".((int) $natureid); - $sqlc.= " AND entity IN (".getEntity('entity').")"; - - //var_dump('$sqlc = '.$sqlc.'
'); - - $resqlc = $db->query($sqlc); - - while ($objc = $db->fetch_object($resqlc)) { - $level = (int) $objc->pricelevel; - //var_dump('$level = '.$level.'
'); - $minrate = (float) $objc->minrate; - $targetrate = (float) $objc->targetrate; - - $price = $moyenne * (1 + $targetrate/100); - - //var_dump('$price = '.price($price).'
'); - - $price_ttc = $price * (1 + $tva_tx/100); - - //var_dump('$price_ttc = '.price($price_ttc).'
'); - - $price_min = $moyenne * (1 + $minrate/100); - - //var_dump('$price_min = '.price($price_min).'
'); - - $price_min_ttc = $price_min * (1 + $tva_tx/100); - - //var_dump('$price_min_ttc = '.price($price_min_ttc).'
'); - - $now = $db->idate(dol_now()); - - $sqlv = "SELECT price_level, price, price_ttc, price_min, price_min_ttc, tva_tx "; - $sqlv.= " FROM ".MAIN_DB_PREFIX."product_price"; - $sqlv.= " WHERE fk_product = ".((int) $prodid) ; - $sqlv.= " AND price_level = ".$level; - $sqlv.= " AND entity IN (".getEntity('productprice').")"; - $sqlv.= " ORDER BY date_price DESC LIMIT 1"; - //var_dump('$sqlv = '.$sqlv.'
'); - - $resqlv = $db->query($sqlv); - - while ($objv = $db->fetch_object($resqlv)) { - $price_v = price2num($objv->price,2); - $price_ttc_v = price2num($objv->price_ttc,2); - $price_min_v = price2num($objv->price_min,2); - $price_min_ttc_v = price2num($objv->price_min_ttc,2); - //$tva_tx_v = $objv->tva_tx; - - if (price2num($price,2)!=$price_v || price2num($price_min,2)!=$price_min_v || price2num($price_ttc,2)!=$price_ttc_v || price2num($price_min_ttc,2)!=$price_min_ttc_v) { - $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price - (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx) - VALUES (".((int )$entity).", - ".((int) $prodid).", - ".$level.", - ".$user->id.", - ".price2num($price,2).", - ".price2num($price_ttc,2).", - ".price2num($price_min,2).", - ".price2num($price_min_ttc,2).", - '".$now."', - ".((float) $tva_tx).") - ON DUPLICATE KEY UPDATE - price = VALUES(price), - price_ttc = VALUES(price_ttc), - price_min = VALUES(price_min), - price_min_ttc = VALUES(price_min_ttc), - date_price = VALUES(date_price), - tva_tx = VALUES(tva_tx)"; - $db->query($sqlp); - - $nb_line++ ; - //var_dump('$nb_line = '.$nb_line.'
'); - } - //var_dump('$nb_line2 = '.$nb_line.'
'); - } - //var_dump('$nb_line3 = '.$nb_line.'
'); - } - } - //var_dump('$nb_line4 = '.$nb_line.'
'); - - return $nb_line; +// Fetch latest component prices per level +function dynamicsprices_get_component_prices_by_level($db, $productId) +{ + $prices = array(); + + $sql = "SELECT price_level, price, price_min"; + $sql .= " FROM ".MAIN_DB_PREFIX."product_price"; + $sql .= " WHERE fk_product = ".((int) $productId); + $sql .= " AND entity IN (".getEntity('productprice').")"; + $sql .= " ORDER BY date_price DESC"; + + $resql = $db->query($sql); + if ($resql === false) { + return $prices; + } + + while ($obj = $db->fetch_object($resql)) { + $level = (int) $obj->price_level; + if (!isset($prices[$level])) { + $prices[$level] = array('price' => (float) $obj->price, 'price_min' => (float) $obj->price_min); + } + } + + return $prices; } -function update_customer_prices_from_cost_price($db, $user, $langs, $conf, $productid = 0) +// Update Kit prices by summing component prices +function dynamicsprices_update_kit_prices_from_components($db, $user, $product, $components, $tvaTx, $entity) { - dol_include_once('/product/class/product.class.php'); - - global $conf; - - $products = []; - $nb_line = 0; - $entity = $conf->entity; - - if ($productid > 0) { - $products[] = $productid; - } else { - $sql = "SELECT rowid, finished, cost_price"; - $sql.= " FROM ".MAIN_DB_PREFIX."product"; - $sql.= " WHERE tosell = 1 "; - $sql.= " AND entity IN (".getEntity('product').")"; - - //var_dump($sql.'
'); - - $resql = $db->query($sql); - if ($resql === false) { - dol_print_error($db); - return; - } - - while ($obj = $db->fetch_object($resql)) { - $products[] = array('id'=>$obj->rowid, 'nature'=>$obj->finished, 'cost_price'=>$obj->cost_price); - } - } - - foreach ($products as $prod) { - $prodid = is_array($prod) ? $prod['id'] : $prod; - //var_dump('ID Produit = '.$prodid.'
'); - $natureid = is_array($prod) ? $prod['nature'] : 0; - //var_dump('Nature = '.$natureid.'
'); - $cost = is_array($prod) ? $prod['cost_price'] : 0; - //var_dump('Prix de Revient = '.$cost.'
'); - $product = new Product($db); - $product->fetch($prodid); - - $tva_tx = (float) $product->tva_tx; - - // Coefficients par nature - $sqlc = "SELECT code, pricelevel, minrate, targetrate - FROM ".MAIN_DB_PREFIX."c_coefprice - WHERE fk_nature = ".((int) $natureid); - $sqlc.= " AND entity IN (".getEntity('entity').")"; - - //var_dump('$sqlc = '.$sqlc.'
'); - - $resqlc = $db->query($sqlc); - - ////var_dump($resqlc.'
'); - - while ($objc = $db->fetch_object($resqlc)) { - $level = (int) $objc->pricelevel; - //var_dump('$level = '.$level.'
'); - $minrate = (float) $objc->minrate; - $targetrate = (float) $objc->targetrate; - - $price = $cost * (1 + $targetrate/100); - - //var_dump('$price = '.price($price).'
'); - - $price_ttc = $price * (1 + $tva_tx/100); - - //var_dump('$price_ttc = '.price($price_ttc).'
'); - - $price_min = $cost * (1 + $minrate/100); - - //var_dump('$price_min = '.price($price_min).'
'); - - $price_min_ttc = $price_min * (1 + $tva_tx/100); - - //var_dump('$price_min_ttc = '.price($price_min_ttc).'
'); - - $now = $db->idate(dol_now()); - - $sqlv = "SELECT price_level, price, price_ttc, price_min, price_min_ttc, tva_tx "; - $sqlv.= " FROM ".MAIN_DB_PREFIX."product_price"; - $sqlv.= " WHERE fk_product = ".((int) $prodid) ; - $sqlv.= " AND price_level = ".$level; - $sqlv.= " AND entity IN (".getEntity('productprice').")"; - $sqlv.= " ORDER BY date_price DESC LIMIT 1"; - //var_dump('$sqlv = '.$sqlv.'
'); - - $resqlv = $db->query($sqlv); - - while ($objv = $db->fetch_object($resqlv)) { - $price_v = price2num($objv->price,2); - $price_ttc_v = price2num($objv->price_ttc,2); - $price_min_v = price2num($objv->price_min,2); - $price_min_ttc_v = price2num($objv->price_min_ttc,2); - //$tva_tx_v = $objv->tva_tx; - - if (price2num($price,2)!=$price_v || price2num($price_min,2)!=$price_min_v || price2num($price_ttc,2)!=$price_ttc_v || price2num($price_min_ttc,2)!=$price_min_ttc_v) { - $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price - (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx) - VALUES (".$entity.", - ".((int) $prodid).", - ".$level.", - ".$user->id.", - ".price2num($price,2).", - ".price2num($price_ttc,2).", - ".price2num($price_min,2).", - ".price2num($price_min_ttc,2).", - '".$now."', - ".((float) $tva_tx).") - ON DUPLICATE KEY UPDATE - price = VALUES(price), - price_ttc = VALUES(price_ttc), - price_min = VALUES(price_min), - price_min_ttc = VALUES(price_min_ttc), - date_price = VALUES(date_price), - tva_tx = VALUES(tva_tx)"; - $db->query($sqlp); - - $nb_line++ ; - } - } - } - } - - return $nb_line; + $levelTotals = array(); + $nb_line = 0; + $now = $db->idate(dol_now()); + + foreach ($components as $component) { + $componentPrices = dynamicsprices_get_component_prices_by_level($db, $component['id']); + foreach ($componentPrices as $level => $values) { + if (!isset($levelTotals[$level])) { + $levelTotals[$level] = array('price' => 0, 'price_min' => 0); + } + $levelTotals[$level]['price'] += ((float) $values['price']) * (float) $component['qty']; + $levelTotals[$level]['price_min'] += ((float) $values['price_min']) * (float) $component['qty']; + } + } + + foreach ($levelTotals as $level => $values) { + $price = (float) $values['price']; + $price_min = (float) $values['price_min']; + $price_ttc = $price * (1 + ((float) $tvaTx / 100)); + $price_min_ttc = $price_min * (1 + ((float) $tvaTx / 100)); + + $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; + $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; + $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; + $db->query($sqlp); + $nb_line++; + } + + return $nb_line; } +function update_customer_prices_from_suppliers($db, $user, $langs, $conf, $productid = 0) +{ + dol_include_once('/product/class/product.class.php'); + + global $conf; + + $products = array(); + $nb_line = 0; + $entity = $conf->entity; + + if ($productid > 0) { + $products[] = $productid; + } else { + $sql = "SELECT rowid, finished"; + $sql .= " FROM ".MAIN_DB_PREFIX."product"; + $sql .= " WHERE tosell = 1"; + $sql .= " AND entity IN (".getEntity('product').")"; + + $resql = $db->query($sql); + if ($resql === false) { + dol_print_error($db); + return 0; + } + + while ($obj = $db->fetch_object($resql)) { + $products[] = array('id' => $obj->rowid, 'nature' => $obj->finished); + } + } -/** - * Display title - * @param string $title - */ -function setup_print_title($title="Parameter", $width = 300) + foreach ($products as $prod) { + $prodid = is_array($prod) ? $prod['id'] : $prod; + $natureid = is_array($prod) ? $prod['nature'] : 0; + $product = new Product($db); + $product->fetch($prodid); + $tva_tx = (float) $product->tva_tx; + + if (dynamicsprices_is_kit($db, $prodid)) { + $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); + $rules = dynamicsprices_get_price_rules($db, $natureid); + if (getDolGlobalInt('LMDB_KIT_PRICE_FROM_COMPONENTS')) { + $components = dynamicsprices_get_kit_components($db, $prodid); + $nb_line += dynamicsprices_update_kit_prices_from_components($db, $user, $product, $components, $tva_tx, $entity); + } else { + $nb_line += dynamicsprices_update_prices_from_base($db, $user, $product, $costPrice, $rules, $tva_tx, $entity); + } + continue; + } + + $avgPrice = dynamicsprices_get_average_supplier_price($db, $prodid); + if ($avgPrice === null) { + continue; + } + + $marginPercent = dynamicsprices_get_margin_on_cost_percent($db, $natureid); + $costPrice = $avgPrice * (1 + ($marginPercent / 100)); + dynamicsprices_save_cost_price($db, $prodid, $costPrice); + + $rules = dynamicsprices_get_price_rules($db, $natureid); + $nb_line += dynamicsprices_update_prices_from_base($db, $user, $product, $avgPrice, $rules, $tva_tx, $entity); + } + + return $nb_line; +} + +function update_customer_prices_from_cost_price($db, $user, $langs, $conf, $productid = 0) { - global $langs; - print ''; - print ''.$langs->trans($title) . ''; - print ' '; - print ''.$langs->trans('Value').''; - print ''; + dol_include_once('/product/class/product.class.php'); + + global $conf; + + $products = array(); + $nb_line = 0; + $entity = $conf->entity; + + if ($productid > 0) { + $products[] = $productid; + } else { + $sql = "SELECT rowid, finished, cost_price"; + $sql .= " FROM ".MAIN_DB_PREFIX."product"; + $sql .= " WHERE tosell = 1"; + $sql .= " AND entity IN (".getEntity('product').")"; + + $resql = $db->query($sql); + if ($resql === false) { + dol_print_error($db); + return 0; + } + + while ($obj = $db->fetch_object($resql)) { + $products[] = array('id' => $obj->rowid, 'nature' => $obj->finished, 'cost_price' => $obj->cost_price); + } + } + + foreach ($products as $prod) { + $prodid = is_array($prod) ? $prod['id'] : $prod; + $natureid = is_array($prod) ? $prod['nature'] : 0; + $currentCost = is_array($prod) ? $prod['cost_price'] : 0; + $product = new Product($db); + $product->fetch($prodid); + $tva_tx = (float) $product->tva_tx; + + if (dynamicsprices_is_kit($db, $prodid)) { + $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); + $rules = dynamicsprices_get_price_rules($db, $natureid); + if (getDolGlobalInt('LMDB_KIT_PRICE_FROM_COMPONENTS')) { + $components = dynamicsprices_get_kit_components($db, $prodid); + $nb_line += dynamicsprices_update_kit_prices_from_components($db, $user, $product, $components, $tva_tx, $entity); + } else { + $nb_line += dynamicsprices_update_prices_from_base($db, $user, $product, $costPrice, $rules, $tva_tx, $entity); + } + continue; + } + + $avgPrice = dynamicsprices_get_average_supplier_price($db, $prodid); + if ($avgPrice !== null) { + $marginPercent = dynamicsprices_get_margin_on_cost_percent($db, $natureid); + $currentCost = $avgPrice * (1 + ($marginPercent / 100)); + dynamicsprices_save_cost_price($db, $prodid, $currentCost); + } + + $rules = dynamicsprices_get_price_rules($db, $natureid); + $nb_line += dynamicsprices_update_prices_from_base($db, $user, $product, $currentCost, $rules, $tva_tx, $entity); + } + + return $nb_line; } /** - * yes / no select - * @param string $confkey - * @param string $title - * @param string $desc - * @param $ajaxConstantOnOffInput will be send to ajax_constantonoff() input param + * Print a table section title. * - * exemple _print_on_off('CONSTNAME', 'ParamLabel' , 'ParamDesc'); + * @param string $title Title key to translate + * @param int $width Width of the column + * @return void */ +function setup_print_title($title = "Parameter", $width = 300) +{ + global $langs; + + print ''; + print ''.$langs->trans($title).''; + print ' '; + print ''.$langs->trans('Value').''; + print ''; +} + function setup_print_on_off($confkey, $title = false, $desc ='', $help = false, $width = 300, $forcereload = false, $ajaxConstantOnOffInput = array()) { - global $var, $bc, $langs, $conf, $form; - $var=!$var; + global $var, $bc, $langs, $conf, $form; + $var=!$var; - print ''; - print ''; + print ''; + print ''; if(empty($help) && !empty($langs->tab_translate[$confkey . '_HELP'])){ $help = $confkey . '_HELP'; } - if(!empty($help)){ - print $form->textwithtooltip( ($title?$title:$langs->trans($confkey)) , $langs->trans($help),2,1,img_help(1,'')); - } - else { - print $title?$title:$langs->trans($confkey); - } - - if(!empty($desc)) - { - print '
'.$langs->trans($desc).''; - } - print ''; - print ' '; - print ''; - - if($forcereload){ - $link = $_SERVER['PHP_SELF'].'?action=set_'.$confkey.'&token='. newToken() .'&'.$confkey.'='.intval((empty($conf->global->{$confkey}))); - $toggleClass = empty($conf->global->{$confkey})?'fa-toggle-off':'fa-toggle-on font-status4'; - print ''; - } - else{ - print ajax_constantonoff($confkey, $ajaxConstantOnOffInput); - } - print ''; + if(!empty($help)){ + print $form->textwithtooltip( ($title?$title:$langs->trans($confkey)) , $langs->trans($help),2,1,img_help(1,'')); + } + else { + print $title?$title:$langs->trans($confkey); + } + + if(!empty($desc)) + { + print '
'.$langs->trans($desc).''; + } + print ''; + print ' '; + print ''; + + if($forcereload){ + $link = $_SERVER['PHP_SELF'].'?action=set_'.$confkey.'&token='. newToken() .'&'.$confkey.'='.intval((empty($conf->global->{$confkey}))); + $toggleClass = empty($conf->global->{$confkey})?'fa-toggle-off':'fa-toggle-on font-status4'; + print ''; + } + else{ + print ajax_constantonoff($confkey, $ajaxConstantOnOffInput); + } + print ''; } /** @@ -437,53 +536,53 @@ function setup_print_on_off($confkey, $title = false, $desc ='', $help = false, */ function setup_print_input_form_part($confkey, $title = false, $desc ='', $metas = array(), $type='input', $help = false, $width = 300) { - global $var, $bc, $langs, $conf, $db; - $var=!$var; + global $var, $bc, $langs, $conf, $db; + $var=!$var; if(empty($help) && !empty($langs->tab_translate[$confkey . '_HELP'])){ $help = $confkey . '_HELP'; } - $form=new Form($db); + $form=new Form($db); - $defaultMetas = array( - 'name' => $confkey - ); + $defaultMetas = array( + 'name' => $confkey + ); - if($type!='textarea'){ - $defaultMetas['type'] = 'text'; - $defaultMetas['value'] = isset($conf->global->{$confkey}) ? $conf->global->{$confkey} : ''; - } + if($type!='textarea'){ + $defaultMetas['type'] = 'text'; + $defaultMetas['value'] = isset($conf->global->{$confkey}) ? $conf->global->{$confkey} : ''; + } - $metas = array_merge ($defaultMetas, $metas); - $metascompil = ''; - foreach ($metas as $key => $values) - { - $metascompil .= ' '.$key.'="'.$values.'" '; - } + $metas = array_merge ($defaultMetas, $metas); + $metascompil = ''; + foreach ($metas as $key => $values) + { + $metascompil .= ' '.$key.'="'.$values.'" '; + } - print ''; - print ''; + print ''; + print ''; - if(!empty($help)){ - print $form->textwithtooltip( ($title?$title:$langs->trans($confkey)) , $langs->trans($help),2,1,img_help(1,'')); - } - else { - print $title?$title:$langs->trans($confkey); - } + if(!empty($help)){ + print $form->textwithtooltip( ($title?$title:$langs->trans($confkey)) , $langs->trans($help),2,1,img_help(1,'')); + } + else { + print $title?$title:$langs->trans($confkey); + } - if(!empty($desc)) - { - print '
'.$langs->trans($desc).''; - } + if(!empty($desc)) + { + print '
'.$langs->trans($desc).''; + } - print ''; - print ' '; - print ''; - print '
'; - print ''; - print ''; + print ''; + print ' '; + print ''; + print ''; + print ''; + print ''; if($type=='textarea'){ print ''; @@ -496,7 +595,7 @@ function setup_print_input_form_part($confkey, $title = false, $desc ='', $metas print $type; } - print ''; - print '
'; - print ''; + print ''; + print ''; + print ''; } diff --git a/sql/dolibarr_allversions.sql b/sql/dolibarr_allversions.sql index 98cf761..96dfd7b 100644 --- a/sql/dolibarr_allversions.sql +++ b/sql/dolibarr_allversions.sql @@ -1,13 +1,25 @@ -- -- Script run when an upgrade of Dolibarr is done. Whatever is the Dolibarr version. -- -CREATE TABLE IF NOT EXISTS `llx_c_coefprice`( - `rowid` int(11) AUTO_INCREMENT, - `code` VARCHAR(255) NOT NULL UNIQUE, - `label` VARCHAR(255) NOT NULL, - `targetrate` FLOAT(24.8) NOT NULL, - `minrate` FLOAT(24.8) NOT NULL, - `active` TINYINT(4) NOT NULL DEFAULT 1, +CREATE TABLE IF NOT EXISTS `llx_c_coefprice`( +`rowid`int(11) AUTO_INCREMENT, +`code`VARCHAR(255) NOT NULL UNIQUE, +`label`VARCHAR(255) NOT NULL, +`targetrate`FLOAT(24.8) NOT NULL, +`minrate`FLOAT(24.8) NOT NULL, +`active`TINYINT(4)NOT NULL DEFAULT 1, - PRIMARY KEY (`rowid`) -)ENGINE=innodb DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; \ No newline at end of file +PRIMARY KEY (`rowid`) +)ENGINE=innodb DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; + +CREATE TABLE IF NOT EXISTS `llx_c_margin_on_cost`( +`rowid`INTEGER AUTO_INCREMENT PRIMARY KEY NOT NULL, +`entity`INTEGER NOT NULL DEFAULT '1', +`code`VARCHAR(50) NOT NULL, +`code_nature`VARCHAR(10) DEFAULT NULL, +`datec`DATETIME NULL, +`tms`TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +`margin_on_cost_percent`FLOAT NOT NULL DEFAULT '0', +`import_key`VARCHAR(14) NULL, +`active`TINYINT NOT NULL DEFAULT '1' +)ENGINE=innodb DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; diff --git a/sql/llx_c_margin_on_cost.sql b/sql/llx_c_margin_on_cost.sql new file mode 100644 index 0000000..b544ad8 --- /dev/null +++ b/sql/llx_c_margin_on_cost.sql @@ -0,0 +1,14 @@ +-- +-- Script run when an upgrade of Dolibarr is done. Whatever is the Dolibarr version. +-- +CREATE TABLE IF NOT EXISTS `llx_c_margin_on_cost`( +`rowid` integer AUTO_INCREMENT PRIMARY KEY NOT NULL, +`entity` integer NOT NULL DEFAULT '1', +`code` varchar(50) NOT NULL, +`code_nature` varchar(10) DEFAULT NULL, +`datec` datetime NULL, +`tms` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +`margin_on_cost_percent` float NOT NULL DEFAULT '0', +`import_key` varchar(14) NULL, +`active` tinyint NOT NULL DEFAULT '1' +)ENGINE=innodb; From fba060ed3e6a20b4c054211dd876aac53f9f612a Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Thu, 27 Nov 2025 21:15:32 +0100 Subject: [PATCH 02/38] Add additional affected actions for price updates --- ...erface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index 6d276a7..49384e7 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -75,7 +75,7 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf require_once __DIR__.'/../../lib/dynamicsprices.lib.php'; $updateFunction = getDolGlobalString('LMDB_COST_PRICE_ONLY') ? 'update_customer_prices_from_cost_price' : 'update_customer_prices_from_suppliers'; - $affectedActions = array('SUPPLIER_PRODUCT_BUYPRICE_CREATE', 'SUPPLIER_PRODUCT_BUYPRICE_MODIFY', 'SUPPLIER_PRODUCT_BUYPRICE_DELETE', 'PRODUCT_MODIFY', 'PRODUCT_PRICE_MODIFY'); + $affectedActions = array('SUPPLIER_PRODUCT_BUYPRICE_CREATE', 'SUPPLIER_PRODUCT_BUYPRICE_MODIFY', 'SUPPLIER_PRODUCT_BUYPRICE_DELETE', 'PRODUCT_MODIFY', 'PRODUCT_PRICE_MODIFY', 'PRODUCT_BUYPRICE_MODIFY', 'PRODUCT_BUYPRICE_DELETE', 'PRODUCT_SUBPRODUCT_ADD', 'PRODUCT_SUBPRODUCT_UPDATE', 'PRODUCT_SUBPRODUCT_DELETE'); if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED') && in_array($action, $affectedActions, true)) { $productId = !empty($object->fk_product) ? $object->fk_product : (isset($object->id) ? $object->id : 0); From e2ed8a3e0dc177c5cbd42dfd4bf0e1c9ec0dde80 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Thu, 27 Nov 2025 21:17:46 +0100 Subject: [PATCH 03/38] Add var_dump for update function and action Added debugging output for update function and action. --- ...rface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index 49384e7..f0c622e 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -76,7 +76,8 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf require_once __DIR__.'/../../lib/dynamicsprices.lib.php'; $updateFunction = getDolGlobalString('LMDB_COST_PRICE_ONLY') ? 'update_customer_prices_from_cost_price' : 'update_customer_prices_from_suppliers'; $affectedActions = array('SUPPLIER_PRODUCT_BUYPRICE_CREATE', 'SUPPLIER_PRODUCT_BUYPRICE_MODIFY', 'SUPPLIER_PRODUCT_BUYPRICE_DELETE', 'PRODUCT_MODIFY', 'PRODUCT_PRICE_MODIFY', 'PRODUCT_BUYPRICE_MODIFY', 'PRODUCT_BUYPRICE_DELETE', 'PRODUCT_SUBPRODUCT_ADD', 'PRODUCT_SUBPRODUCT_UPDATE', 'PRODUCT_SUBPRODUCT_DELETE'); - + var_dump($updateFunction); + var_dump($action); if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED') && in_array($action, $affectedActions, true)) { $productId = !empty($object->fk_product) ? $object->fk_product : (isset($object->id) ? $object->id : 0); if ($productId > 0) { From fdd6327a0dc982a7c5b985deb8ef06c74e594f3e Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:34:12 +0100 Subject: [PATCH 04/38] Fix SQL column names for product associations --- lib/dynamicsprices.lib.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 5c88adf..9b57922 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -91,7 +91,7 @@ function dynamicsprices_is_kit($db, $productId) { $sql = "SELECT COUNT(rowid) as nb"; $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; - $sql .= " WHERE fk_product_parent = ".((int) $productId); + $sql .= " WHERE fk_product_pere = ".((int) $productId); $sql .= " AND entity IN (".getEntity('product').")"; $sql .= " AND (type IS NULL OR type IN (0,1))"; @@ -109,9 +109,9 @@ function dynamicsprices_get_kit_components($db, $productId) { $components = array(); - $sql = "SELECT fk_product_child as fk_product, qty"; + $sql = "SELECT fk_product_fils as fk_product, qty"; $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; - $sql .= " WHERE fk_product_parent = ".((int) $productId); + $sql .= " WHERE fk_product_pere = ".((int) $productId); $sql .= " AND entity IN (".getEntity('product').")"; $sql .= " AND (type IS NULL OR type IN (0,1))"; @@ -132,9 +132,9 @@ function dynamicsprices_get_parent_kits($db, $productId) { $parents = array(); - $sql = "SELECT fk_product_parent as fk_parent"; + $sql = "SELECT fk_product_pere as fk_parent"; $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; - $sql .= " WHERE fk_product_child = ".((int) $productId); + $sql .= " WHERE fk_product_fils = ".((int) $productId); $sql .= " AND entity IN (".getEntity('product').")"; $sql .= " AND (type IS NULL OR type IN (0,1))"; From 4a7bbfc1dadbaa70ab2ece27b0dd315a175db9be Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:38:26 +0100 Subject: [PATCH 05/38] Remove entity filtering from product association queries Comment out entity filtering in SQL queries. --- lib/dynamicsprices.lib.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 9b57922..b805975 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -92,7 +92,7 @@ function dynamicsprices_is_kit($db, $productId) $sql = "SELECT COUNT(rowid) as nb"; $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; $sql .= " WHERE fk_product_pere = ".((int) $productId); - $sql .= " AND entity IN (".getEntity('product').")"; + //$sql .= " AND entity IN (".getEntity('product').")"; $sql .= " AND (type IS NULL OR type IN (0,1))"; $resql = $db->query($sql); @@ -112,7 +112,7 @@ function dynamicsprices_get_kit_components($db, $productId) $sql = "SELECT fk_product_fils as fk_product, qty"; $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; $sql .= " WHERE fk_product_pere = ".((int) $productId); - $sql .= " AND entity IN (".getEntity('product').")"; + //$sql .= " AND entity IN (".getEntity('product').")"; $sql .= " AND (type IS NULL OR type IN (0,1))"; $resql = $db->query($sql); @@ -135,7 +135,7 @@ function dynamicsprices_get_parent_kits($db, $productId) $sql = "SELECT fk_product_pere as fk_parent"; $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; $sql .= " WHERE fk_product_fils = ".((int) $productId); - $sql .= " AND entity IN (".getEntity('product').")"; + //$sql .= " AND entity IN (".getEntity('product').")"; $sql .= " AND (type IS NULL OR type IN (0,1))"; $resql = $db->query($sql); From 69ac049f7c5b39c8cfde10d9f116fec3c728efb8 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Fri, 28 Nov 2025 07:41:15 +0100 Subject: [PATCH 06/38] Comment out type filter in SQL queries --- lib/dynamicsprices.lib.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index b805975..e3bd5e0 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -93,7 +93,7 @@ function dynamicsprices_is_kit($db, $productId) $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; $sql .= " WHERE fk_product_pere = ".((int) $productId); //$sql .= " AND entity IN (".getEntity('product').")"; - $sql .= " AND (type IS NULL OR type IN (0,1))"; + //$sql .= " AND (type IS NULL OR type IN (0,1))"; $resql = $db->query($sql); if ($resql === false) { @@ -113,7 +113,7 @@ function dynamicsprices_get_kit_components($db, $productId) $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; $sql .= " WHERE fk_product_pere = ".((int) $productId); //$sql .= " AND entity IN (".getEntity('product').")"; - $sql .= " AND (type IS NULL OR type IN (0,1))"; + //$sql .= " AND (type IS NULL OR type IN (0,1))"; $resql = $db->query($sql); if ($resql === false) { @@ -136,7 +136,7 @@ function dynamicsprices_get_parent_kits($db, $productId) $sql .= " FROM ".MAIN_DB_PREFIX."product_association"; $sql .= " WHERE fk_product_fils = ".((int) $productId); //$sql .= " AND entity IN (".getEntity('product').")"; - $sql .= " AND (type IS NULL OR type IN (0,1))"; + //$sql .= " AND (type IS NULL OR type IN (0,1))"; $resql = $db->query($sql); if ($resql === false) { From 11724e67cf47d437e9107d3c91693bed3b539dad Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Fri, 28 Nov 2025 09:43:28 +0100 Subject: [PATCH 07/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index e3bd5e0..8fbf77d 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -153,7 +153,7 @@ function dynamicsprices_get_parent_kits($db, $productId) // Compute average supplier price function dynamicsprices_get_average_supplier_price($db, $productId) { - $sql = "SELECT price"; + $sql = "SELECT unitprice"; $sql .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price"; $sql .= " WHERE fk_product = ".((int) $productId); $sql .= " AND entity IN (".getEntity('product_fournisseur_price').")"; @@ -165,7 +165,7 @@ function dynamicsprices_get_average_supplier_price($db, $productId) $prices = array(); while ($obj = $db->fetch_object($resql)) { - $prices[] = (float) $obj->price; + $prices[] = (float) $obj->unitprice; } if (count($prices) === 0) { From 53238c4640626d56e50557c44c7af4f866360d92 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:31:48 +0100 Subject: [PATCH 08/38] Create php.yml --- .github/workflows/php.yml | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/php.yml diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..7d257b5 --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,39 @@ +name: PHP Composer + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" + # Docs: https://getcomposer.org/doc/articles/scripts.md + + # - name: Run test suite + # run: composer run-script test From 2afabee3850ae01220b549423350028197d884ec Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:33:37 +0100 Subject: [PATCH 09/38] Update interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php --- ...face_99_modDynamicsPrices_DynamicsPricesTriggers.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index f0c622e..e85d745 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -76,8 +76,8 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf require_once __DIR__.'/../../lib/dynamicsprices.lib.php'; $updateFunction = getDolGlobalString('LMDB_COST_PRICE_ONLY') ? 'update_customer_prices_from_cost_price' : 'update_customer_prices_from_suppliers'; $affectedActions = array('SUPPLIER_PRODUCT_BUYPRICE_CREATE', 'SUPPLIER_PRODUCT_BUYPRICE_MODIFY', 'SUPPLIER_PRODUCT_BUYPRICE_DELETE', 'PRODUCT_MODIFY', 'PRODUCT_PRICE_MODIFY', 'PRODUCT_BUYPRICE_MODIFY', 'PRODUCT_BUYPRICE_DELETE', 'PRODUCT_SUBPRODUCT_ADD', 'PRODUCT_SUBPRODUCT_UPDATE', 'PRODUCT_SUBPRODUCT_DELETE'); - var_dump($updateFunction); - var_dump($action); + //var_dump($updateFunction); + //var_dump($action); if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED') && in_array($action, $affectedActions, true)) { $productId = !empty($object->fk_product) ? $object->fk_product : (isset($object->id) ? $object->id : 0); if ($productId > 0) { From 35a379beba2d18a9a701846d3a684a565f5b5623 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:52:26 +0100 Subject: [PATCH 10/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 8fbf77d..77bb256 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -261,6 +261,7 @@ function dynamicsprices_update_prices_from_base($db, $user, $product, $basePrice $sqlv = "SELECT price, price_ttc, price_min, price_min_ttc"; $sqlv .= " FROM ".MAIN_DB_PREFIX."product_price"; $sqlv .= " WHERE fk_product = ".((int) $product->id); + $sqlv .= " AND fk_product_type = 1" ; $sqlv .= " AND price_level = ".((int) $level); $sqlv .= " AND entity IN (".getEntity('productprice').")"; $sqlv .= " ORDER BY date_price DESC LIMIT 1"; @@ -420,6 +421,7 @@ function update_customer_prices_from_cost_price($db, $user, $langs, $conf, $prod $sql = "SELECT rowid, finished, cost_price"; $sql .= " FROM ".MAIN_DB_PREFIX."product"; $sql .= " WHERE tosell = 1"; + $sql .= " AND fk_product_type = 1"; $sql .= " AND entity IN (".getEntity('product').")"; $resql = $db->query($sql); From d54157078434f44fe0b3dce356a100370df9849d Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:55:01 +0100 Subject: [PATCH 11/38] Update modDynamicsPrices.class.php --- core/modules/modDynamicsPrices.class.php | 91 ++++++++++++------------ 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/core/modules/modDynamicsPrices.class.php b/core/modules/modDynamicsPrices.class.php index 04ea023..ef29337 100644 --- a/core/modules/modDynamicsPrices.class.php +++ b/core/modules/modDynamicsPrices.class.php @@ -54,7 +54,7 @@ public function __construct($db) // Family can be 'base' (core modules),'crm','financial','hr','projects','products','ecm','technic' (transverse modules),'interface' (link with external tools),'other','...' // It is used to group modules by family in module setup page - $this->family = 'products'; + $this->family = 'Les Métiers du Bâtiment'; // Module position in the family on 2 digits ('01', '10', '20', ...) $this->module_position = '90'; @@ -76,7 +76,7 @@ public function __construct($db) $this->editor_squarred_logo = 'logo.png@dynamicsprices'; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@dynamicsprices' // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' -$this->version = '1.2.0'; + $this->version = '1.2.0'; // Url to the file with your last numberversion of this module //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; @@ -87,7 +87,7 @@ public function __construct($db) // If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue' // If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module' // To use a supported fa-xxx css style of font awesome, use this->picto='xxx' - $this->picto = 'margin'; + $this->picto = 'margin'; // Define some features supported by module (triggers, login, substitutions, menus, css, etc...) $this->module_parts = array( @@ -253,48 +253,48 @@ public function __construct($db) 'tabhelp' => array(array('code' => $langs->trans('CodeTooltipHelp'), 'field2' => 'field2tooltip'), array('code' => $langs->trans('CodeTooltipHelp'), 'field2' => 'field2tooltip'), ...), ); */ -/* BEGIN MODULEBUILDER DICTIONARIES */ -$this->dictionaries = array( -'langs'=>'dynamicsprices@dynamicsprices', -'tabname'=>array(MAIN_DB_PREFIX."c_coefprice", MAIN_DB_PREFIX."c_margin_on_cost"), -'tablib'=>array("LMDB_coefprice", "LMDB_marginoncost"), -'tabsql'=>array( -'SELECT t.rowid as rowid, t.entity, t.code, t.fk_nature, t.pricelevel, t.minrate, t.targetrate, t.active FROM '.MAIN_DB_PREFIX.'c_coefprice AS t WHERE t.entity = '.((int) $conf->entity), -'SELECT t.rowid as rowid, t.entity, t.code, t.code_nature, t.margin_on_cost_percent, t.active FROM '.MAIN_DB_PREFIX.'c_margin_on_cost AS t WHERE t.entity = '.((int) $conf->entity), -), -'tabsqlsort'=>array( -"code ASC", -"code ASC", -), -'tabfield'=>array( -"code,fk_nature,pricelevel,targetrate,minrate", -"code,code_nature,margin_on_cost_percent", -), -'tabfieldvalue'=>array( -"code,entity,fk_nature,pricelevel,targetrate,minrate", -"code,entity,code_nature,margin_on_cost_percent", -), -'tabfieldinsert'=>array( -"code,entity,fk_nature,pricelevel,targetrate,minrate", -"code,entity,code_nature,margin_on_cost_percent", -), -'tabrowid'=>array('rowid', 'rowid'), -'tabcond'=>array( -isModEnabled('dynamicsprices'), -isModEnabled('dynamicsprices'), -), -'tabhelp' => array( -'code' => $langs->trans('LMDB_CodeTooltipHelp'), -'entity' => $langs->trans('LMDB_ENtityTooltipHelp'), -'fk_nature' => $langs->trans('LMDB_FkNatureTooltipHelp'), -'pricelevel' => $langs->trans('LMDB_PriceLevelTooltipHelp'), -'targetrate' => $langs->trans('LMDB_TargetRateTooltipHelp'), -'minrate' => $langs->trans('LMDB_MinRateTooltipHelp'), -'code_nature' => $langs->trans('LMDB_CodeNatureTooltipHelp'), -'margin_on_cost_percent' => $langs->trans('LMDB_MarginOnCostTooltipHelp'), -), -); -/* END MODULEBUILDER DICTIONARIES */ +/* BEGIN MODULEBUILDER DICTIONARIES */ +$this->dictionaries = array( +'langs'=>'dynamicsprices@dynamicsprices', +'tabname'=>array(MAIN_DB_PREFIX."c_coefprice", MAIN_DB_PREFIX."c_margin_on_cost"), +'tablib'=>array("LMDB_coefprice", "LMDB_marginoncost"), +'tabsql'=>array( +'SELECT t.rowid as rowid, t.entity, t.code, t.fk_nature, t.pricelevel, t.minrate, t.targetrate, t.active FROM '.MAIN_DB_PREFIX.'c_coefprice AS t WHERE t.entity = '.((int) $conf->entity), +'SELECT t.rowid as rowid, t.entity, t.code, t.code_nature, t.margin_on_cost_percent, t.active FROM '.MAIN_DB_PREFIX.'c_margin_on_cost AS t WHERE t.entity = '.((int) $conf->entity), +), +'tabsqlsort'=>array( +"code ASC", +"code ASC", +), +'tabfield'=>array( +"code,fk_nature,pricelevel,targetrate,minrate", +"code,code_nature,margin_on_cost_percent", +), +'tabfieldvalue'=>array( +"code,entity,fk_nature,pricelevel,targetrate,minrate", +"code,entity,code_nature,margin_on_cost_percent", +), +'tabfieldinsert'=>array( +"code,entity,fk_nature,pricelevel,targetrate,minrate", +"code,entity,code_nature,margin_on_cost_percent", +), +'tabrowid'=>array('rowid', 'rowid'), +'tabcond'=>array( +isModEnabled('dynamicsprices'), +isModEnabled('dynamicsprices'), +), +'tabhelp' => array( +'code' => $langs->trans('LMDB_CodeTooltipHelp'), +'entity' => $langs->trans('LMDB_ENtityTooltipHelp'), +'fk_nature' => $langs->trans('LMDB_FkNatureTooltipHelp'), +'pricelevel' => $langs->trans('LMDB_PriceLevelTooltipHelp'), +'targetrate' => $langs->trans('LMDB_TargetRateTooltipHelp'), +'minrate' => $langs->trans('LMDB_MinRateTooltipHelp'), +'code_nature' => $langs->trans('LMDB_CodeNatureTooltipHelp'), +'margin_on_cost_percent' => $langs->trans('LMDB_MarginOnCostTooltipHelp'), +), +); +/* END MODULEBUILDER DICTIONARIES */ // Boxes/Widgets // Add here list of php file(s) stored in dynamicsprices/core/boxes that contains a class to show a widget. @@ -601,3 +601,4 @@ public function remove($options = '') return $this->_remove($sql, $options); } } + From 7250a824677bb4b66a1181fcab76b4fa8ae3724a Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:07:46 +0100 Subject: [PATCH 12/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 50 ++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 77bb256..df5314f 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -261,15 +261,14 @@ function dynamicsprices_update_prices_from_base($db, $user, $product, $basePrice $sqlv = "SELECT price, price_ttc, price_min, price_min_ttc"; $sqlv .= " FROM ".MAIN_DB_PREFIX."product_price"; $sqlv .= " WHERE fk_product = ".((int) $product->id); - $sqlv .= " AND fk_product_type = 1" ; $sqlv .= " AND price_level = ".((int) $level); $sqlv .= " AND entity IN (".getEntity('productprice').")"; $sqlv .= " ORDER BY date_price DESC LIMIT 1"; $resqlv = $db->query($sqlv); $current = $db->fetch_object($resqlv); - - if (!$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { + + if ($current->fk_product_type == || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; @@ -298,10 +297,10 @@ function dynamicsprices_get_component_prices_by_level($db, $productId) } while ($obj = $db->fetch_object($resql)) { - $level = (int) $obj->price_level; - if (!isset($prices[$level])) { - $prices[$level] = array('price' => (float) $obj->price, 'price_min' => (float) $obj->price_min); - } + $level = (int) $obj->price_level; + if (!isset($prices[$level])) { + $prices[$level] = array('price' => (float) $obj->price, 'price_min' => (float) $obj->price_min); + } } return $prices; @@ -315,27 +314,27 @@ function dynamicsprices_update_kit_prices_from_components($db, $user, $product, $now = $db->idate(dol_now()); foreach ($components as $component) { - $componentPrices = dynamicsprices_get_component_prices_by_level($db, $component['id']); - foreach ($componentPrices as $level => $values) { - if (!isset($levelTotals[$level])) { - $levelTotals[$level] = array('price' => 0, 'price_min' => 0); - } - $levelTotals[$level]['price'] += ((float) $values['price']) * (float) $component['qty']; - $levelTotals[$level]['price_min'] += ((float) $values['price_min']) * (float) $component['qty']; - } + $componentPrices = dynamicsprices_get_component_prices_by_level($db, $component['id']); + foreach ($componentPrices as $level => $values) { + if (!isset($levelTotals[$level])) { + $levelTotals[$level] = array('price' => 0, 'price_min' => 0); + } + $levelTotals[$level]['price'] += ((float) $values['price']) * (float) $component['qty']; + $levelTotals[$level]['price_min'] += ((float) $values['price_min']) * (float) $component['qty']; + } } foreach ($levelTotals as $level => $values) { - $price = (float) $values['price']; - $price_min = (float) $values['price_min']; - $price_ttc = $price * (1 + ((float) $tvaTx / 100)); - $price_min_ttc = $price_min * (1 + ((float) $tvaTx / 100)); - - $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; - $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; - $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; - $db->query($sqlp); - $nb_line++; + $price = (float) $values['price']; + $price_min = (float) $values['price_min']; + $price_ttc = $price * (1 + ((float) $tvaTx / 100)); + $price_min_ttc = $price_min * (1 + ((float) $tvaTx / 100)); + + $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; + $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; + $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; + $db->query($sqlp); + $nb_line++; } return $nb_line; @@ -421,7 +420,6 @@ function update_customer_prices_from_cost_price($db, $user, $langs, $conf, $prod $sql = "SELECT rowid, finished, cost_price"; $sql .= " FROM ".MAIN_DB_PREFIX."product"; $sql .= " WHERE tosell = 1"; - $sql .= " AND fk_product_type = 1"; $sql .= " AND entity IN (".getEntity('product').")"; $resql = $db->query($sql); From 19b9cbfa2c8221ee78dd113857d9abf3d373d0b6 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:08:55 +0100 Subject: [PATCH 13/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index df5314f..1cedbcf 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -268,7 +268,7 @@ function dynamicsprices_update_prices_from_base($db, $user, $product, $basePrice $resqlv = $db->query($sqlv); $current = $db->fetch_object($resqlv); - if ($current->fk_product_type == || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { + if ($current->fk_product_type == 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; From e6a13925be966198fd2981e5c69e08d2531e9b3a Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:14:53 +0100 Subject: [PATCH 14/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 1cedbcf..30dbba4 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -329,12 +329,24 @@ function dynamicsprices_update_kit_prices_from_components($db, $user, $product, $price_min = (float) $values['price_min']; $price_ttc = $price * (1 + ((float) $tvaTx / 100)); $price_min_ttc = $price_min * (1 + ((float) $tvaTx / 100)); - - $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; - $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; - $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; - $db->query($sqlp); - $nb_line++; + + $sqlw = "SELECT price, price_ttc, price_min, price_min_ttc"; + $sqlw .= " FROM ".MAIN_DB_PREFIX."product_price"; + $sqlw .= " WHERE fk_product = ".((int) $product->id); + $sqlw .= " AND price_level = ".((int) $level); + $sqlw .= " AND entity IN (".getEntity('productprice').")"; + $sqlw .= " ORDER BY date_price DESC LIMIT 1"; + + $resqlv = $db->query($sqlw); + $current = $db->fetch_object($resqlw); + + if ($current->fk_product_type == 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { + $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; + $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; + $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; + $db->query($sqlp); + $nb_line++; + } } return $nb_line; From d27169691315909fc3070db4dc17229b7fb8129f Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:17:48 +0100 Subject: [PATCH 15/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 30dbba4..d381871 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -268,7 +268,7 @@ function dynamicsprices_update_prices_from_base($db, $user, $product, $basePrice $resqlv = $db->query($sqlv); $current = $db->fetch_object($resqlv); - if ($current->fk_product_type == 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { + if ($current->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; @@ -340,7 +340,7 @@ function dynamicsprices_update_kit_prices_from_components($db, $user, $product, $resqlv = $db->query($sqlw); $current = $db->fetch_object($resqlw); - if ($current->fk_product_type == 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { + if ($current->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; From 87075f8a7d94f72f6cfd228e53ae656789e3205e Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:42:31 +0100 Subject: [PATCH 16/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index d381871..d8a92b0 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -267,6 +267,8 @@ function dynamicsprices_update_prices_from_base($db, $user, $product, $basePrice $resqlv = $db->query($sqlv); $current = $db->fetch_object($resqlv); + + var_dump($current->fk_product_type); if ($current->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; @@ -340,6 +342,8 @@ function dynamicsprices_update_kit_prices_from_components($db, $user, $product, $resqlv = $db->query($sqlw); $current = $db->fetch_object($resqlw); + var_dump($current->fk_product_type); + if ($current->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; From ac4003c296165367e8b8578fa8adfb32485fd054 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:45:12 +0100 Subject: [PATCH 17/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index d8a92b0..a76d09a 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -268,9 +268,9 @@ function dynamicsprices_update_prices_from_base($db, $user, $product, $basePrice $resqlv = $db->query($sqlv); $current = $db->fetch_object($resqlv); - var_dump($current->fk_product_type); + var_dump(product->fk_product_type); - if ($current->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { + if (product->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; From 47f6781aea63ed39e9f467d99604595757de3165 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:46:42 +0100 Subject: [PATCH 18/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index a76d09a..6070156 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -268,9 +268,9 @@ function dynamicsprices_update_prices_from_base($db, $user, $product, $basePrice $resqlv = $db->query($sqlv); $current = $db->fetch_object($resqlv); - var_dump(product->fk_product_type); + var_dump($product->fk_product_type); - if (product->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { + if ($product->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; From a70a2f39925aedc51f0f26908eac3db1bdb4e860 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:49:58 +0100 Subject: [PATCH 19/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 6070156..6b069bc 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -314,6 +314,7 @@ function dynamicsprices_update_kit_prices_from_components($db, $user, $product, $levelTotals = array(); $nb_line = 0; $now = $db->idate(dol_now()); + var_dump($product); foreach ($components as $component) { $componentPrices = dynamicsprices_get_component_prices_by_level($db, $component['id']); @@ -342,9 +343,9 @@ function dynamicsprices_update_kit_prices_from_components($db, $user, $product, $resqlv = $db->query($sqlw); $current = $db->fetch_object($resqlw); - var_dump($current->fk_product_type); + var_dump($product->fk_product_type); - if ($current->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { + if ($product->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; From 69ae7a4ee6ed4512dd8a02dc1a4c05d487031e0c Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:53:10 +0100 Subject: [PATCH 20/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 6b069bc..89c6bf9 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -340,7 +340,7 @@ function dynamicsprices_update_kit_prices_from_components($db, $user, $product, $sqlw .= " AND entity IN (".getEntity('productprice').")"; $sqlw .= " ORDER BY date_price DESC LIMIT 1"; - $resqlv = $db->query($sqlw); + $resqlw = $db->query($sqlw); $current = $db->fetch_object($resqlw); var_dump($product->fk_product_type); From c59df9b76256ac5c9415d926307d1eba4e9444e0 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:56:02 +0100 Subject: [PATCH 21/38] Update interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php --- ...erface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index e85d745..4a749dc 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -80,7 +80,7 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf //var_dump($action); if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED') && in_array($action, $affectedActions, true)) { $productId = !empty($object->fk_product) ? $object->fk_product : (isset($object->id) ? $object->id : 0); - if ($productId > 0) { + if ($productId >> 0 && $object->fk_product_type == 0) { call_user_func($updateFunction, $db, $user, $langs, $conf, $productId); $parentKits = dynamicsprices_get_parent_kits($db, $productId); foreach ($parentKits as $kitId) { From 4d151ae6019022e74abcc80eddb4ed86eee7f27b Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:57:27 +0100 Subject: [PATCH 22/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 89c6bf9..f4ba6a1 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -257,20 +257,8 @@ function dynamicsprices_update_prices_from_base($db, $user, $product, $basePrice $price_ttc = $price * (1 + ((float) $tvaTx / 100)); $price_min = $basePrice * (1 + ((float) $rule['minrate'] / 100)); $price_min_ttc = $price_min * (1 + ((float) $tvaTx / 100)); - - $sqlv = "SELECT price, price_ttc, price_min, price_min_ttc"; - $sqlv .= " FROM ".MAIN_DB_PREFIX."product_price"; - $sqlv .= " WHERE fk_product = ".((int) $product->id); - $sqlv .= " AND price_level = ".((int) $level); - $sqlv .= " AND entity IN (".getEntity('productprice').")"; - $sqlv .= " ORDER BY date_price DESC LIMIT 1"; - - $resqlv = $db->query($sqlv); - $current = $db->fetch_object($resqlv); - - var_dump($product->fk_product_type); - if ($product->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { + if (!$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; @@ -333,19 +321,7 @@ function dynamicsprices_update_kit_prices_from_components($db, $user, $product, $price_ttc = $price * (1 + ((float) $tvaTx / 100)); $price_min_ttc = $price_min * (1 + ((float) $tvaTx / 100)); - $sqlw = "SELECT price, price_ttc, price_min, price_min_ttc"; - $sqlw .= " FROM ".MAIN_DB_PREFIX."product_price"; - $sqlw .= " WHERE fk_product = ".((int) $product->id); - $sqlw .= " AND price_level = ".((int) $level); - $sqlw .= " AND entity IN (".getEntity('productprice').")"; - $sqlw .= " ORDER BY date_price DESC LIMIT 1"; - - $resqlw = $db->query($sqlw); - $current = $db->fetch_object($resqlw); - - var_dump($product->fk_product_type); - - if ($product->fk_product_type != 0 || !$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { + if (!$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; From 26ac5bdbce016021435906573ac69902a0d3d2c3 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Mon, 8 Dec 2025 23:59:32 +0100 Subject: [PATCH 23/38] Update interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php --- ...face_99_modDynamicsPrices_DynamicsPricesTriggers.class.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index 4a749dc..4810bf1 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -68,6 +68,8 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf return 0; // If module is not enabled, we do nothing } + var_dump($object->fk_product_type); + // Put here code you want to execute when a Dolibarr business events occurs. // Data and type of action are stored into $object and $action @@ -80,7 +82,7 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf //var_dump($action); if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED') && in_array($action, $affectedActions, true)) { $productId = !empty($object->fk_product) ? $object->fk_product : (isset($object->id) ? $object->id : 0); - if ($productId >> 0 && $object->fk_product_type == 0) { + if ($productId >> 0) { call_user_func($updateFunction, $db, $user, $langs, $conf, $productId); $parentKits = dynamicsprices_get_parent_kits($db, $productId); foreach ($parentKits as $kitId) { From 0c71acc05c28c8e4833b6dd341efdf4716f61907 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:12:50 +0100 Subject: [PATCH 24/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index f4ba6a1..41c333a 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -369,6 +369,10 @@ function update_customer_prices_from_suppliers($db, $user, $langs, $conf, $produ $product->fetch($prodid); $tva_tx = (float) $product->tva_tx; + if ($product->fk_product_type != 0) { + continue; + } + if (dynamicsprices_is_kit($db, $prodid)) { $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); $rules = dynamicsprices_get_price_rules($db, $natureid); @@ -434,6 +438,10 @@ function update_customer_prices_from_cost_price($db, $user, $langs, $conf, $prod $product->fetch($prodid); $tva_tx = (float) $product->tva_tx; + if ($product->fk_product_type != 0) { + continue; + } + if (dynamicsprices_is_kit($db, $prodid)) { $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); $rules = dynamicsprices_get_price_rules($db, $natureid); From 83b51899a710ebfa11b1f93980e203fa168c9c4c Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:14:17 +0100 Subject: [PATCH 25/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 41c333a..3c08958 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -369,6 +369,7 @@ function update_customer_prices_from_suppliers($db, $user, $langs, $conf, $produ $product->fetch($prodid); $tva_tx = (float) $product->tva_tx; + var_dump($product->fk_product_type); if ($product->fk_product_type != 0) { continue; } From 74c18619362002ed73e5c97736994ab94a65d33b Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:17:04 +0100 Subject: [PATCH 26/38] Update dynamicsprices.lib.php --- lib/dynamicsprices.lib.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 3c08958..f4ba6a1 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -369,11 +369,6 @@ function update_customer_prices_from_suppliers($db, $user, $langs, $conf, $produ $product->fetch($prodid); $tva_tx = (float) $product->tva_tx; - var_dump($product->fk_product_type); - if ($product->fk_product_type != 0) { - continue; - } - if (dynamicsprices_is_kit($db, $prodid)) { $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); $rules = dynamicsprices_get_price_rules($db, $natureid); @@ -439,10 +434,6 @@ function update_customer_prices_from_cost_price($db, $user, $langs, $conf, $prod $product->fetch($prodid); $tva_tx = (float) $product->tva_tx; - if ($product->fk_product_type != 0) { - continue; - } - if (dynamicsprices_is_kit($db, $prodid)) { $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); $rules = dynamicsprices_get_price_rules($db, $natureid); From 1df4d2096b366f6fa41ea911a4985d3ea68f879f Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:18:14 +0100 Subject: [PATCH 27/38] Update interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php --- ...99_modDynamicsPrices_DynamicsPricesTriggers.class.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index 4810bf1..4975bb5 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -68,13 +68,18 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf return 0; // If module is not enabled, we do nothing } - var_dump($object->fk_product_type); - // Put here code you want to execute when a Dolibarr business events occurs. // Data and type of action are stored into $object and $action global $db; + $sql = "SELECT fk_product_type FROM ".MAIN_DB_PREFIX."product"; + $sql.= " WHERE rowid = ".$object->fk_product ; + + $resql = $db->query($sql); + $product = $db->fetch($resql); + var_dump($product->fk_product_type); + require_once __DIR__.'/../../lib/dynamicsprices.lib.php'; $updateFunction = getDolGlobalString('LMDB_COST_PRICE_ONLY') ? 'update_customer_prices_from_cost_price' : 'update_customer_prices_from_suppliers'; $affectedActions = array('SUPPLIER_PRODUCT_BUYPRICE_CREATE', 'SUPPLIER_PRODUCT_BUYPRICE_MODIFY', 'SUPPLIER_PRODUCT_BUYPRICE_DELETE', 'PRODUCT_MODIFY', 'PRODUCT_PRICE_MODIFY', 'PRODUCT_BUYPRICE_MODIFY', 'PRODUCT_BUYPRICE_DELETE', 'PRODUCT_SUBPRODUCT_ADD', 'PRODUCT_SUBPRODUCT_UPDATE', 'PRODUCT_SUBPRODUCT_DELETE'); From 072f11e759e2d62a100a9ed30bc87d64ed09d69c Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:19:58 +0100 Subject: [PATCH 28/38] Update interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php --- ...erface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index 4975bb5..c4fcc62 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -77,7 +77,7 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf $sql.= " WHERE rowid = ".$object->fk_product ; $resql = $db->query($sql); - $product = $db->fetch($resql); + $product = $db->fetch_object($resql); var_dump($product->fk_product_type); require_once __DIR__.'/../../lib/dynamicsprices.lib.php'; From 1a036c145cc6719de7551a709a9d9b795b25e290 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:21:37 +0100 Subject: [PATCH 29/38] Update interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php --- ...e_99_modDynamicsPrices_DynamicsPricesTriggers.class.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index c4fcc62..f2898c8 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -78,7 +78,12 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf $resql = $db->query($sql); $product = $db->fetch_object($resql); - var_dump($product->fk_product_type); + //var_dump($product->fk_product_type); + + if (!$product->fk_product_type != 0) { + return 0; // If module is not enabled, we do nothing + } + require_once __DIR__.'/../../lib/dynamicsprices.lib.php'; $updateFunction = getDolGlobalString('LMDB_COST_PRICE_ONLY') ? 'update_customer_prices_from_cost_price' : 'update_customer_prices_from_suppliers'; From 0452ff01d68221bbd6e756d29af5314e02282e09 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:23:20 +0100 Subject: [PATCH 30/38] Update interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php --- ...erface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index f2898c8..9fef193 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -80,7 +80,7 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf $product = $db->fetch_object($resql); //var_dump($product->fk_product_type); - if (!$product->fk_product_type != 0) { + if ($product->fk_product_type != 0) { return 0; // If module is not enabled, we do nothing } From bb9230563ba2db67890eb699aea9b61cd83f4735 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:29:36 +0100 Subject: [PATCH 31/38] Update interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php --- ...icsPrices_DynamicsPricesTriggers.class.php | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index 9fef193..e37a521 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -73,18 +73,6 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf global $db; - $sql = "SELECT fk_product_type FROM ".MAIN_DB_PREFIX."product"; - $sql.= " WHERE rowid = ".$object->fk_product ; - - $resql = $db->query($sql); - $product = $db->fetch_object($resql); - //var_dump($product->fk_product_type); - - if ($product->fk_product_type != 0) { - return 0; // If module is not enabled, we do nothing - } - - require_once __DIR__.'/../../lib/dynamicsprices.lib.php'; $updateFunction = getDolGlobalString('LMDB_COST_PRICE_ONLY') ? 'update_customer_prices_from_cost_price' : 'update_customer_prices_from_suppliers'; $affectedActions = array('SUPPLIER_PRODUCT_BUYPRICE_CREATE', 'SUPPLIER_PRODUCT_BUYPRICE_MODIFY', 'SUPPLIER_PRODUCT_BUYPRICE_DELETE', 'PRODUCT_MODIFY', 'PRODUCT_PRICE_MODIFY', 'PRODUCT_BUYPRICE_MODIFY', 'PRODUCT_BUYPRICE_DELETE', 'PRODUCT_SUBPRODUCT_ADD', 'PRODUCT_SUBPRODUCT_UPDATE', 'PRODUCT_SUBPRODUCT_DELETE'); @@ -92,6 +80,16 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf //var_dump($action); if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED') && in_array($action, $affectedActions, true)) { $productId = !empty($object->fk_product) ? $object->fk_product : (isset($object->id) ? $object->id : 0); + $sql = "SELECT fk_product_type FROM ".MAIN_DB_PREFIX."product"; + $sql.= " WHERE rowid = ".$productId ; + + $resql = $db->query($sql); + $product = $db->fetch_object($resql); + //var_dump($product->fk_product_type); + + if ($product->fk_product_type != 0) { + return 0; // If module is not enabled, we do nothing + } if ($productId >> 0) { call_user_func($updateFunction, $db, $user, $langs, $conf, $productId); $parentKits = dynamicsprices_get_parent_kits($db, $productId); From 0b1dc45d0949287d64e9461de716d814919010b2 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 00:55:00 +0100 Subject: [PATCH 32/38] Support kit pricing updates and bump version --- core/modules/modDynamicsPrices.class.php | 4 +- ...icsPrices_DynamicsPricesTriggers.class.php | 19 +++--- lib/dynamicsprices.lib.php | 58 ++++++++++++++----- 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/core/modules/modDynamicsPrices.class.php b/core/modules/modDynamicsPrices.class.php index ef29337..4b2304b 100644 --- a/core/modules/modDynamicsPrices.class.php +++ b/core/modules/modDynamicsPrices.class.php @@ -75,8 +75,8 @@ public function __construct($db) $this->editor_url = 'lesmetiersdubatiment.fr'; // Must be an external online web site $this->editor_squarred_logo = 'logo.png@dynamicsprices'; // Must be image filename into the module/img directory followed with @modulename. Example: 'myimage.png@dynamicsprices' - // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' - $this->version = '1.2.0'; +// Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated', 'experimental_deprecated' or a version string like 'x.y.z' +$this->version = '2.0.0'; // Url to the file with your last numberversion of this module //$this->url_last_version = 'http://www.example.com/versionmodule.txt'; diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index e37a521..b4d0fc3 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -75,22 +75,19 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf require_once __DIR__.'/../../lib/dynamicsprices.lib.php'; $updateFunction = getDolGlobalString('LMDB_COST_PRICE_ONLY') ? 'update_customer_prices_from_cost_price' : 'update_customer_prices_from_suppliers'; - $affectedActions = array('SUPPLIER_PRODUCT_BUYPRICE_CREATE', 'SUPPLIER_PRODUCT_BUYPRICE_MODIFY', 'SUPPLIER_PRODUCT_BUYPRICE_DELETE', 'PRODUCT_MODIFY', 'PRODUCT_PRICE_MODIFY', 'PRODUCT_BUYPRICE_MODIFY', 'PRODUCT_BUYPRICE_DELETE', 'PRODUCT_SUBPRODUCT_ADD', 'PRODUCT_SUBPRODUCT_UPDATE', 'PRODUCT_SUBPRODUCT_DELETE'); + $affectedActions = array('SUPPLIER_PRODUCT_BUYPRICE_CREATE', 'SUPPLIER_PRODUCT_BUYPRICE_MODIFY', 'SUPPLIER_PRODUCT_BUYPRICE_DELETE', 'PRODUCT_MODIFY', 'PRODUCT_CREATE', 'PRODUCT_CLONE', 'PRODUCT_PRICE_CREATE', 'PRODUCT_PRICE_MODIFY', 'PRODUCT_PRICE_DELETE', 'PRODUCT_BUYPRICE_CREATE', 'PRODUCT_BUYPRICE_MODIFY', 'PRODUCT_BUYPRICE_DELETE', 'PRODUCT_SUBPRODUCT_ADD', 'PRODUCT_SUBPRODUCT_UPDATE', 'PRODUCT_SUBPRODUCT_DELETE'); //var_dump($updateFunction); //var_dump($action); if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED') && in_array($action, $affectedActions, true)) { + dol_include_once('/product/class/product.class.php'); $productId = !empty($object->fk_product) ? $object->fk_product : (isset($object->id) ? $object->id : 0); - $sql = "SELECT fk_product_type FROM ".MAIN_DB_PREFIX."product"; - $sql.= " WHERE rowid = ".$productId ; - - $resql = $db->query($sql); - $product = $db->fetch_object($resql); - //var_dump($product->fk_product_type); - - if ($product->fk_product_type != 0) { - return 0; // If module is not enabled, we do nothing + $product = new Product($db); + if ($productId > 0 && $product->fetch($productId) > 0) { + if ((int) $product->type !== Product::TYPE_PRODUCT) { + return 0; + } } - if ($productId >> 0) { + if ($productId > 0) { call_user_func($updateFunction, $db, $user, $langs, $conf, $productId); $parentKits = dynamicsprices_get_parent_kits($db, $productId); foreach ($parentKits as $kitId) { diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index f4ba6a1..295a270 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -257,11 +257,12 @@ function dynamicsprices_update_prices_from_base($db, $user, $product, $basePrice $price_ttc = $price * (1 + ((float) $tvaTx / 100)); $price_min = $basePrice * (1 + ((float) $rule['minrate'] / 100)); $price_min_ttc = $price_min * (1 + ((float) $tvaTx / 100)); - - if (!$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { - $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; - $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; - $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; + $current = dynamicsprices_get_latest_price_for_level($db, $product->id, $level); + +if (!$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { +$sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; +$sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; +$sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; $db->query($sqlp); $nb_line++; } @@ -276,8 +277,8 @@ function dynamicsprices_get_component_prices_by_level($db, $productId) $prices = array(); $sql = "SELECT price_level, price, price_min"; - $sql .= " FROM ".MAIN_DB_PREFIX."product_price"; - $sql .= " WHERE fk_product = ".((int) $productId); + $sql .= " FROM \".MAIN_DB_PREFIX.\"product_price"; + $sql .= " WHERE fk_product = \".((int) $productId);"; $sql .= " AND entity IN (".getEntity('productprice').")"; $sql .= " ORDER BY date_price DESC"; @@ -296,20 +297,38 @@ function dynamicsprices_get_component_prices_by_level($db, $productId) return $prices; } +// Get latest price line for a product and level +function dynamicsprices_get_latest_price_for_level($db, $productId, $level) +{ + $sql = "SELECT price, price_ttc, price_min, price_min_ttc"; + $sql .= " FROM \".MAIN_DB_PREFIX.\"product_price"; + $sql .= " WHERE fk_product = \".((int) $productId);"; + $sql .= " AND price_level = \".((int) $level);"; + $sql .= " AND entity IN (".getEntity('productprice').")"; + $sql .= " ORDER BY date_price DESC, rowid DESC"; + $sql .= " LIMIT 1"; + + $resql = $db->query($sql); + if ($resql === false) { + return null; + } + + return $db->fetch_object($resql); +} + // Update Kit prices by summing component prices function dynamicsprices_update_kit_prices_from_components($db, $user, $product, $components, $tvaTx, $entity) { $levelTotals = array(); $nb_line = 0; $now = $db->idate(dol_now()); - var_dump($product); foreach ($components as $component) { $componentPrices = dynamicsprices_get_component_prices_by_level($db, $component['id']); foreach ($componentPrices as $level => $values) { - if (!isset($levelTotals[$level])) { - $levelTotals[$level] = array('price' => 0, 'price_min' => 0); - } + if (!isset($levelTotals[$level])) { + $levelTotals[$level] = array('price' => 0, 'price_min' => 0); + } $levelTotals[$level]['price'] += ((float) $values['price']) * (float) $component['qty']; $levelTotals[$level]['price_min'] += ((float) $values['price_min']) * (float) $component['qty']; } @@ -320,11 +339,12 @@ function dynamicsprices_update_kit_prices_from_components($db, $user, $product, $price_min = (float) $values['price_min']; $price_ttc = $price * (1 + ((float) $tvaTx / 100)); $price_min_ttc = $price_min * (1 + ((float) $tvaTx / 100)); + $current = dynamicsprices_get_latest_price_for_level($db, $product->id, $level); - if (!$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { - $sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; - $sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; - $sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; +if (!$current || price2num($current->price, 2) != price2num($price, 2) || price2num($current->price_min, 2) != price2num($price_min, 2) || price2num($current->price_ttc, 2) != price2num($price_ttc, 2) || price2num($current->price_min_ttc, 2) != price2num($price_min_ttc, 2)) { +$sqlp = "INSERT INTO ".MAIN_DB_PREFIX."product_price (entity, fk_product, price_level, fk_user_author, price, price_ttc, price_min, price_min_ttc, date_price, tva_tx)"; +$sqlp .= " VALUES (".((int) $entity).", ".((int) $product->id).", ".((int) $level).", ".((int) $user->id).", ".price2num($price, 2).", ".price2num($price_ttc, 2).", ".price2num($price_min, 2).", ".price2num($price_min_ttc, 2).", '".$now."', ".((float) $tvaTx).")"; +$sqlp .= " ON DUPLICATE KEY UPDATE price = VALUES(price), price_ttc = VALUES(price_ttc), price_min = VALUES(price_min), price_min_ttc = VALUES(price_min_ttc), date_price = VALUES(date_price), tva_tx = VALUES(tva_tx)"; $db->query($sqlp); $nb_line++; } @@ -349,6 +369,7 @@ function update_customer_prices_from_suppliers($db, $user, $langs, $conf, $produ $sql = "SELECT rowid, finished"; $sql .= " FROM ".MAIN_DB_PREFIX."product"; $sql .= " WHERE tosell = 1"; + $sql .= " AND fk_product_type = 0"; $sql .= " AND entity IN (".getEntity('product').")"; $resql = $db->query($sql); @@ -368,6 +389,9 @@ function update_customer_prices_from_suppliers($db, $user, $langs, $conf, $produ $product = new Product($db); $product->fetch($prodid); $tva_tx = (float) $product->tva_tx; + if ((int) $product->type !== Product::TYPE_PRODUCT) { + continue; + } if (dynamicsprices_is_kit($db, $prodid)) { $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); @@ -413,6 +437,7 @@ function update_customer_prices_from_cost_price($db, $user, $langs, $conf, $prod $sql = "SELECT rowid, finished, cost_price"; $sql .= " FROM ".MAIN_DB_PREFIX."product"; $sql .= " WHERE tosell = 1"; + $sql .= " AND fk_product_type = 0"; $sql .= " AND entity IN (".getEntity('product').")"; $resql = $db->query($sql); @@ -433,6 +458,9 @@ function update_customer_prices_from_cost_price($db, $user, $langs, $conf, $prod $product = new Product($db); $product->fetch($prodid); $tva_tx = (float) $product->tva_tx; + if ((int) $product->type !== Product::TYPE_PRODUCT) { + continue; + } if (dynamicsprices_is_kit($db, $prodid)) { $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); From f63478db8b2245c1656e1f0445a65ad3e11ca42f Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 01:03:45 +0100 Subject: [PATCH 33/38] Handle kit updates after components and skip services --- ...icsPrices_DynamicsPricesTriggers.class.php | 24 +- lib/dynamicsprices.lib.php | 238 +++++++++++------- 2 files changed, 154 insertions(+), 108 deletions(-) diff --git a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php index b4d0fc3..93962d2 100644 --- a/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php +++ b/core/triggers/interface_99_modDynamicsPrices_DynamicsPricesTriggers.class.php @@ -1,6 +1,6 @@ - * Copyright (C) ---Replace with your own copyright and developer email--- + * Copyright (C) 2025 Pierre Ardoin * * 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 @@ -78,17 +78,17 @@ public function runTrigger($action, $object, User $user, Translate $langs, Conf $affectedActions = array('SUPPLIER_PRODUCT_BUYPRICE_CREATE', 'SUPPLIER_PRODUCT_BUYPRICE_MODIFY', 'SUPPLIER_PRODUCT_BUYPRICE_DELETE', 'PRODUCT_MODIFY', 'PRODUCT_CREATE', 'PRODUCT_CLONE', 'PRODUCT_PRICE_CREATE', 'PRODUCT_PRICE_MODIFY', 'PRODUCT_PRICE_DELETE', 'PRODUCT_BUYPRICE_CREATE', 'PRODUCT_BUYPRICE_MODIFY', 'PRODUCT_BUYPRICE_DELETE', 'PRODUCT_SUBPRODUCT_ADD', 'PRODUCT_SUBPRODUCT_UPDATE', 'PRODUCT_SUBPRODUCT_DELETE'); //var_dump($updateFunction); //var_dump($action); - if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED') && in_array($action, $affectedActions, true)) { - dol_include_once('/product/class/product.class.php'); - $productId = !empty($object->fk_product) ? $object->fk_product : (isset($object->id) ? $object->id : 0); - $product = new Product($db); - if ($productId > 0 && $product->fetch($productId) > 0) { - if ((int) $product->type !== Product::TYPE_PRODUCT) { - return 0; - } - } - if ($productId > 0) { - call_user_func($updateFunction, $db, $user, $langs, $conf, $productId); + if (getDolGlobalString('LMDB_SUPPLIER_BUYPRICE_ALTERED') && in_array($action, $affectedActions, true)) { + dol_include_once('/product/class/product.class.php'); + $productId = !empty($object->fk_product) ? $object->fk_product : (isset($object->id) ? $object->id : 0); + $product = new Product($db); + if ($productId > 0 && $product->fetch($productId) > 0) { + if ((int) $product->type !== Product::TYPE_PRODUCT) { + return 0; + } + } + if ($productId > 0) { + call_user_func($updateFunction, $db, $user, $langs, $conf, $productId); $parentKits = dynamicsprices_get_parent_kits($db, $productId); foreach ($parentKits as $kitId) { call_user_func($updateFunction, $db, $user, $langs, $conf, $kitId); diff --git a/lib/dynamicsprices.lib.php b/lib/dynamicsprices.lib.php index 295a270..2daa7af 100644 --- a/lib/dynamicsprices.lib.php +++ b/lib/dynamicsprices.lib.php @@ -1,5 +1,5 @@ * * 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 @@ -277,21 +277,21 @@ function dynamicsprices_get_component_prices_by_level($db, $productId) $prices = array(); $sql = "SELECT price_level, price, price_min"; - $sql .= " FROM \".MAIN_DB_PREFIX.\"product_price"; - $sql .= " WHERE fk_product = \".((int) $productId);"; + $sql .= " FROM ".MAIN_DB_PREFIX."product_price"; + $sql .= " WHERE fk_product = ".((int) $productId); $sql .= " AND entity IN (".getEntity('productprice').")"; $sql .= " ORDER BY date_price DESC"; $resql = $db->query($sql); if ($resql === false) { - return $prices; + return $prices; } while ($obj = $db->fetch_object($resql)) { - $level = (int) $obj->price_level; - if (!isset($prices[$level])) { - $prices[$level] = array('price' => (float) $obj->price, 'price_min' => (float) $obj->price_min); - } + $level = (int) $obj->price_level; + if (!isset($prices[$level])) { + $prices[$level] = array('price' => (float) $obj->price, 'price_min' => (float) $obj->price_min); + } } return $prices; @@ -301,16 +301,16 @@ function dynamicsprices_get_component_prices_by_level($db, $productId) function dynamicsprices_get_latest_price_for_level($db, $productId, $level) { $sql = "SELECT price, price_ttc, price_min, price_min_ttc"; - $sql .= " FROM \".MAIN_DB_PREFIX.\"product_price"; - $sql .= " WHERE fk_product = \".((int) $productId);"; - $sql .= " AND price_level = \".((int) $level);"; + $sql .= " FROM ".MAIN_DB_PREFIX."product_price"; + $sql .= " WHERE fk_product = ".((int) $productId); + $sql .= " AND price_level = ".((int) $level); $sql .= " AND entity IN (".getEntity('productprice').")"; $sql .= " ORDER BY date_price DESC, rowid DESC"; $sql .= " LIMIT 1"; $resql = $db->query($sql); if ($resql === false) { - return null; + return null; } return $db->fetch_object($resql); @@ -355,153 +355,199 @@ function dynamicsprices_update_kit_prices_from_components($db, $user, $product, function update_customer_prices_from_suppliers($db, $user, $langs, $conf, $productid = 0) { - dol_include_once('/product/class/product.class.php'); - global $conf; - $products = array(); - $nb_line = 0; - $entity = $conf->entity; - if ($productid > 0) { + + dol_include_once('/product/class/product.class.php'); + + global $conf; + + $products = array(); + $kits = array(); + $nb_line = 0; + $entity = $conf->entity; + + if ($productid > 0) { + $product = new Product($db); + if ($product->fetch($productid) > 0 && (int) $product->type !== Product::TYPE_PRODUCT) { + return 0; + } $products[] = $productid; - } else { + } else { $sql = "SELECT rowid, finished"; $sql .= " FROM ".MAIN_DB_PREFIX."product"; $sql .= " WHERE tosell = 1"; $sql .= " AND fk_product_type = 0"; $sql .= " AND entity IN (".getEntity('product').")"; - + $resql = $db->query($sql); if ($resql === false) { - dol_print_error($db); - return 0; + dol_print_error($db); + return 0; } - + while ($obj = $db->fetch_object($resql)) { - $products[] = array('id' => $obj->rowid, 'nature' => $obj->finished); + $products[] = array('id' => $obj->rowid, 'nature' => $obj->finished); } - } - - foreach ($products as $prod) { + } + + foreach ($products as $prod) { $prodid = is_array($prod) ? $prod['id'] : $prod; $natureid = is_array($prod) ? $prod['nature'] : 0; $product = new Product($db); $product->fetch($prodid); - $tva_tx = (float) $product->tva_tx; if ((int) $product->type !== Product::TYPE_PRODUCT) { - continue; + continue; } - + $tva_tx = (float) $product->tva_tx; + if (dynamicsprices_is_kit($db, $prodid)) { - $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); - $rules = dynamicsprices_get_price_rules($db, $natureid); - if (getDolGlobalInt('LMDB_KIT_PRICE_FROM_COMPONENTS')) { - $components = dynamicsprices_get_kit_components($db, $prodid); - $nb_line += dynamicsprices_update_kit_prices_from_components($db, $user, $product, $components, $tva_tx, $entity); - } else { - $nb_line += dynamicsprices_update_prices_from_base($db, $user, $product, $costPrice, $rules, $tva_tx, $entity); - } - continue; + $kits[] = array('id' => $prodid, 'nature' => $natureid, 'tva' => $tva_tx); + continue; } - + $avgPrice = dynamicsprices_get_average_supplier_price($db, $prodid); if ($avgPrice === null) { - continue; + continue; } - + $marginPercent = dynamicsprices_get_margin_on_cost_percent($db, $natureid); $costPrice = $avgPrice * (1 + ($marginPercent / 100)); dynamicsprices_save_cost_price($db, $prodid, $costPrice); - + $rules = dynamicsprices_get_price_rules($db, $natureid); $nb_line += dynamicsprices_update_prices_from_base($db, $user, $product, $avgPrice, $rules, $tva_tx, $entity); - } + } + + foreach ($kits as $kit) { + $prodid = $kit['id']; + $natureid = $kit['nature']; + $tva_tx = $kit['tva']; + $product = new Product($db); + $product->fetch($prodid); + + $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); + $rules = dynamicsprices_get_price_rules($db, $natureid); + if (getDolGlobalInt('LMDB_KIT_PRICE_FROM_COMPONENTS')) { + $components = dynamicsprices_get_kit_components($db, $prodid); + $nb_line += dynamicsprices_update_kit_prices_from_components($db, $user, $product, $components, $tva_tx, $entity); + } else { + $nb_line += dynamicsprices_update_prices_from_base($db, $user, $product, $costPrice, $rules, $tva_tx, $entity); + } + } + + return $nb_line; + + + - return $nb_line; } function update_customer_prices_from_cost_price($db, $user, $langs, $conf, $productid = 0) { - dol_include_once('/product/class/product.class.php'); - global $conf; - $products = array(); - $nb_line = 0; - $entity = $conf->entity; - if ($productid > 0) { + + dol_include_once('/product/class/product.class.php'); + + global $conf; + + $products = array(); + $kits = array(); + $nb_line = 0; + $entity = $conf->entity; + + if ($productid > 0) { + $product = new Product($db); + if ($product->fetch($productid) > 0 && (int) $product->type !== Product::TYPE_PRODUCT) { + return 0; + } $products[] = $productid; - } else { + } else { $sql = "SELECT rowid, finished, cost_price"; $sql .= " FROM ".MAIN_DB_PREFIX."product"; $sql .= " WHERE tosell = 1"; $sql .= " AND fk_product_type = 0"; $sql .= " AND entity IN (".getEntity('product').")"; - + $resql = $db->query($sql); if ($resql === false) { - dol_print_error($db); - return 0; + dol_print_error($db); + return 0; } - + while ($obj = $db->fetch_object($resql)) { - $products[] = array('id' => $obj->rowid, 'nature' => $obj->finished, 'cost_price' => $obj->cost_price); + $products[] = array('id' => $obj->rowid, 'nature' => $obj->finished, 'cost_price' => $obj->cost_price); } - } - - foreach ($products as $prod) { + } + + foreach ($products as $prod) { $prodid = is_array($prod) ? $prod['id'] : $prod; $natureid = is_array($prod) ? $prod['nature'] : 0; $currentCost = is_array($prod) ? $prod['cost_price'] : 0; $product = new Product($db); $product->fetch($prodid); - $tva_tx = (float) $product->tva_tx; if ((int) $product->type !== Product::TYPE_PRODUCT) { - continue; + continue; } - + $tva_tx = (float) $product->tva_tx; + if (dynamicsprices_is_kit($db, $prodid)) { - $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); - $rules = dynamicsprices_get_price_rules($db, $natureid); - if (getDolGlobalInt('LMDB_KIT_PRICE_FROM_COMPONENTS')) { - $components = dynamicsprices_get_kit_components($db, $prodid); - $nb_line += dynamicsprices_update_kit_prices_from_components($db, $user, $product, $components, $tva_tx, $entity); - } else { - $nb_line += dynamicsprices_update_prices_from_base($db, $user, $product, $costPrice, $rules, $tva_tx, $entity); - } - continue; + $kits[] = array('id' => $prodid, 'nature' => $natureid, 'tva' => $tva_tx); + continue; } - + $avgPrice = dynamicsprices_get_average_supplier_price($db, $prodid); if ($avgPrice !== null) { - $marginPercent = dynamicsprices_get_margin_on_cost_percent($db, $natureid); - $currentCost = $avgPrice * (1 + ($marginPercent / 100)); - dynamicsprices_save_cost_price($db, $prodid, $currentCost); + $marginPercent = dynamicsprices_get_margin_on_cost_percent($db, $natureid); + $currentCost = $avgPrice * (1 + ($marginPercent / 100)); + dynamicsprices_save_cost_price($db, $prodid, $currentCost); } - + $rules = dynamicsprices_get_price_rules($db, $natureid); $nb_line += dynamicsprices_update_prices_from_base($db, $user, $product, $currentCost, $rules, $tva_tx, $entity); - } - - return $nb_line; -} - -/** - * Print a table section title. - * - * @param string $title Title key to translate - * @param int $width Width of the column - * @return void - */ -function setup_print_title($title = "Parameter", $width = 300) -{ - global $langs; - - print ''; - print ''.$langs->trans($title).''; - print ' '; + } + + foreach ($kits as $kit) { + $prodid = $kit['id']; + $natureid = $kit['nature']; + $tva_tx = $kit['tva']; + $product = new Product($db); + $product->fetch($prodid); + + $costPrice = dynamicsprices_update_kit_cost_price($db, $prodid); + $rules = dynamicsprices_get_price_rules($db, $natureid); + if (getDolGlobalInt('LMDB_KIT_PRICE_FROM_COMPONENTS')) { + $components = dynamicsprices_get_kit_components($db, $prodid); + $nb_line += dynamicsprices_update_kit_prices_from_components($db, $user, $product, $components, $tva_tx, $entity); + } else { + $nb_line += dynamicsprices_update_prices_from_base($db, $user, $product, $costPrice, $rules, $tva_tx, $entity); + } + } + + return $nb_line; + + + + + } + + /** + * Print a table section title. + * + * @param string $title Title key to translate + * @param int $width Width of the column + * @return void + */ + function setup_print_title($title = "Parameter", $width = 300) + { + global $langs; + + print ''; + print ''.$langs->trans($title).''; + print ' '; print ''.$langs->trans('Value').''; print ''; } From 0acf68e3bd756231725a4d660f6d4a1a12109269 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 01:10:34 +0100 Subject: [PATCH 34/38] Update documentation for 2.0 release --- ChangeLog.md | 28 +++++++------ README.md | 114 +++++++++++++++++++++------------------------------ 2 files changed, 62 insertions(+), 80 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 2970243..b15233a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,12 +1,16 @@ -# CHANGELOG MODULE DYNAMICSPRICES FOR [DOLIBARR ERP CRM](https://www.dolibarr.org)
-
-##1.1.1
-- Correction du filtre pour ignorer les services, seul les produits sont pris en compte. (29/09/2025)
-
- -##1.1
-- Prise en charge des prix de revient pour l'actualisation des prix de vente. (17/09/2025)
-
-## 1.0
-
-Initial version
+# Changelog DynamicsPrices + +## 2.0.0 +- Ajout du support des kits : le prix d'un kit est recalculé après ses composants pour éviter les doublons et refléter le coût cumulé. +- Correction des mises à jour intempestives des services : seuls les produits physiques sont recalculés (`fk_product_type = 0`). +- Extension de la couverture des triggers pour lancer les recalculs sur davantage d'événements Dolibarr. +- Calcul automatique des prix de revient via un dictionnaire dédié et la moyenne des prix d'achat. + +## 1.1.1 +- Correction du filtre pour ignorer les services ; seuls les produits sont pris en compte. (29/09/2025) + +## 1.1 +- Prise en charge des prix de revient pour l'actualisation des prix de vente. (17/09/2025) + +## 1.0 +- Version initiale. diff --git a/README.md b/README.md index 528356b..47cf5f7 100644 --- a/README.md +++ b/README.md @@ -1,96 +1,74 @@ -# DYNAMICSPRICES FOR [DOLIBARR ERP & CRM](https://www.dolibarr.org) +# DynamicsPrices -## Features +Module Dolibarr pour la mise à jour dynamique des prix de vente à partir des coûts d'achat et des coefficients configurables. -Ce module permet de mettre à jour les prix de vente en fonction du prix d'achat moyen unitaire chez les fournisseurs et des coefficients de prix définis dans un dictionnaire dédié. +## Aperçu - +DynamicsPrices automatise le recalcul des prix des produits en s'appuyant sur les prix d'achat moyens, les coefficients de marge et les relations entre produits (composants, kits). Les déclencheurs du module s'occupent d'appliquer les nouveaux prix de vente au bon moment, tout en respectant les spécificités des produits et services Dolibarr. -Other external modules are available on [Dolistore.com](https://www.dolistore.com). +## Fonctionnalités clés -## Translations +- Mise à jour automatique des prix de vente en fonction du prix d'achat moyen et d'un dictionnaire de coefficients dédié. +- Recalcul des kits après leurs composants pour éviter les doublons de prix de vente et refléter le coût cumulé des sous-produits et services. +- Filtrage des services : seuls les produits physiques (`fk_product_type = 0`) sont recalculés pour éviter les mises à jour intempestives. +- Plus grand nombre de triggers pour couvrir les actions courantes (création, modification, réception d'achat, etc.). +- Calcul automatique des prix de revient à partir des nouveaux dictionnaires et de la moyenne des prix d'achat. -Translations can be completed manually by editing files in the module directories under `langs`. - - +## Compatibilité +- Dolibarr ≥ 19.0 (minimum recommandé). +- Module externe installable dans `htdocs/custom/dynamicsprices`. ## Installation -Prerequisites: You must have Dolibarr ERP & CRM software installed. You can download it from [Dolistore.org](https://www.dolibarr.org). -You can also get a ready-to-use instance in the cloud from https://saas.dolibarr.org - - -### From the ZIP file and GUI interface - -If the module is a ready-to-deploy zip file, so with a name `module_xxx-version.zip` (e.g., when downloading it from a marketplace like [Dolistore](https://www.dolistore.com)), -go to menu `Home> Setup> Modules> Deploy external module` and upload the zip file. - - +```bash +cd htdocs/custom +git clone git@github.com:gitlogin/dynamicsprices.git dynamicsprices +``` - +- **Dictionnaire des coefficients** : définir les coefficients de marge dans **Dictionnaires > Coefficients DynamicsPrices**. +- **Triggers** : les déclencheurs DynamicsPrices mettent à jour les prix lors des actions standards (création de produit, réception fournisseur, modification de prix, etc.). +- **Kits** : le prix de vente d'un kit est recalculé uniquement après mise à jour des prix de ses composants pour éviter toute duplication. -### Final steps +## Utilisation -Using your browser: +- Créer ou mettre à jour un produit avec un prix d'achat renseigné. +- Les triggers calculent automatiquement le prix de revient et le prix de vente suivant le coefficient applicable. +- Les services et produits non physiques (`fk_product_type != 0`) sont ignorés par les mises à jour automatiques. - - Log into Dolibarr as a super-administrator - - Go to "Setup"> "Modules" - - You should now be able to find and enable the module +## Permissions et sécurité +- Les actions de mise à jour sont soumises aux permissions Dolibarr standard sur les produits et dictionnaires. +- Les écrans du module masquent automatiquement les actions non autorisées. +## Traductions -## Licenses +Les fichiers de langue sont disponibles dans `langs/`. Complétez ou ajustez les traductions en `en_US` et `fr_FR` pour tout nouveau libellé. -### Main code +## Support -GPLv3 or (at your option) any later version. See file COPYING for more information. +- Documentation et support Dolibarr : [https://wiki.dolibarr.org](https://wiki.dolibarr.org) +- Autres modules externes : [Dolistore.com](https://www.dolistore.com) -### Documentation +## Licence -All texts and readme's are licensed under [GFDL](https://www.gnu.org/licenses/fdl-1.3.en.html). +- Code : GPLv3 ou ultérieure (voir `COPYING`). +- Documentation : GFDL (voir la licence correspondante). From 01c49edfafca407b3ad2745f9b372a75ca1af850 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 01:17:14 +0100 Subject: [PATCH 35/38] Add English sections to docs --- ChangeLog.md | 15 +++++++ README.md | 115 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 112 insertions(+), 18 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index b15233a..8dac392 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,11 +6,26 @@ - Extension de la couverture des triggers pour lancer les recalculs sur davantage d'événements Dolibarr. - Calcul automatique des prix de revient via un dictionnaire dédié et la moyenne des prix d'achat. +## 2.0.0 (EN) +- Added kit support: a kit price is recalculated after its components to avoid duplicates and reflect the cumulative cost. +- Fixed unintended service updates: only physical products are recalculated (`fk_product_type = 0`). +- Expanded trigger coverage to launch recalculations on more Dolibarr events. +- Automatic cost-price computation via a dedicated dictionary and average purchase prices. + ## 1.1.1 - Correction du filtre pour ignorer les services ; seuls les produits sont pris en compte. (29/09/2025) +## 1.1.1 (EN) +- Fixed the filter to ignore services; only products are taken into account. (29/09/2025) + ## 1.1 - Prise en charge des prix de revient pour l'actualisation des prix de vente. (17/09/2025) +## 1.1 (EN) +- Added cost-price handling to refresh selling prices. (17/09/2025) + ## 1.0 - Version initiale. + +## 1.0 (EN) +- Initial release. diff --git a/README.md b/README.md index 47cf5f7..21db353 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # DynamicsPrices +## Présentation (FR) + Module Dolibarr pour la mise à jour dynamique des prix de vente à partir des coûts d'achat et des coefficients configurables. -## Aperçu +### Aperçu DynamicsPrices automatise le recalcul des prix des produits en s'appuyant sur les prix d'achat moyens, les coefficients de marge et les relations entre produits (composants, kits). Les déclencheurs du module s'occupent d'appliquer les nouveaux prix de vente au bon moment, tout en respectant les spécificités des produits et services Dolibarr. -## Fonctionnalités clés +### Fonctionnalités clés - Mise à jour automatique des prix de vente en fonction du prix d'achat moyen et d'un dictionnaire de coefficients dédié. - Recalcul des kits après leurs composants pour éviter les doublons de prix de vente et refléter le coût cumulé des sous-produits et services. @@ -14,20 +16,20 @@ DynamicsPrices automatise le recalcul des prix des produits en s'appuyant sur le - Plus grand nombre de triggers pour couvrir les actions courantes (création, modification, réception d'achat, etc.). - Calcul automatique des prix de revient à partir des nouveaux dictionnaires et de la moyenne des prix d'achat. -## Compatibilité +### Compatibilité -- Dolibarr ≥ 19.0 (minimum recommandé). +- Dolibarr ≥ 19.0 (minimum recommandé). - Module externe installable dans `htdocs/custom/dynamicsprices`. -## Installation +### Installation -### Depuis une archive ZIP +#### Depuis une archive ZIP 1. Télécharger l'archive `module_dynamicsprices-x.y.z.zip`. -2. Déployer l'archive via le menu **Accueil > Configuration > Modules > Déployer un module externe**. -3. Activer le module **DynamicsPrices** dans **Configuration > Modules/Applications**. +2. Déployer l'archive via le menu **Accueil > Configuration > Modules > Déployer un module externe**. +3. Activer le module **DynamicsPrices** dans **Configuration > Modules/Applications**. -### Depuis un dépôt Git +#### Depuis un dépôt Git ```bash cd htdocs/custom @@ -36,39 +38,116 @@ git clone git@github.com:gitlogin/dynamicsprices.git dynamicsprices Puis activer le module dans Dolibarr comme décrit ci-dessus. -## Mise à jour +### Mise à jour 1. Sauvegarder la base de données et le répertoire du module. 2. Installer la nouvelle version (ZIP ou Git) dans `htdocs/custom/dynamicsprices`. 3. Lancer les scripts de migration proposés par Dolibarr si nécessaire. -## Configuration +### Configuration -- **Dictionnaire des coefficients** : définir les coefficients de marge dans **Dictionnaires > Coefficients DynamicsPrices**. +- **Dictionnaire des coefficients** : définir les coefficients de marge dans **Dictionnaires > Coefficients DynamicsPrices**. - **Triggers** : les déclencheurs DynamicsPrices mettent à jour les prix lors des actions standards (création de produit, réception fournisseur, modification de prix, etc.). - **Kits** : le prix de vente d'un kit est recalculé uniquement après mise à jour des prix de ses composants pour éviter toute duplication. -## Utilisation +### Utilisation - Créer ou mettre à jour un produit avec un prix d'achat renseigné. - Les triggers calculent automatiquement le prix de revient et le prix de vente suivant le coefficient applicable. - Les services et produits non physiques (`fk_product_type != 0`) sont ignorés par les mises à jour automatiques. -## Permissions et sécurité +### Permissions et sécurité - Les actions de mise à jour sont soumises aux permissions Dolibarr standard sur les produits et dictionnaires. - Les écrans du module masquent automatiquement les actions non autorisées. -## Traductions +### Traductions Les fichiers de langue sont disponibles dans `langs/`. Complétez ou ajustez les traductions en `en_US` et `fr_FR` pour tout nouveau libellé. -## Support +### Support -- Documentation et support Dolibarr : [https://wiki.dolibarr.org](https://wiki.dolibarr.org) +- Documentation et support Dolibarr : [https://wiki.dolibarr.org](https://wiki.dolibarr.org) - Autres modules externes : [Dolistore.com](https://www.dolistore.com) -## Licence +### Licence - Code : GPLv3 ou ultérieure (voir `COPYING`). - Documentation : GFDL (voir la licence correspondante). + +--- + +## Overview (EN) + +Dolibarr module for dynamically updating selling prices based on purchase costs and configurable margins. + +### Summary + +DynamicsPrices automates price recalculations using average purchase prices, margin coefficients, and relationships between products (components, kits). Module triggers apply new selling prices at the right time while respecting Dolibarr product and service specifics. + +### Key features + +- Automatic selling-price updates driven by average purchase price and a dedicated coefficient dictionary. +- Kits recalculated after their components to avoid duplicate selling prices and to reflect cumulative component and service costs. +- Service filtering: only physical products (`fk_product_type = 0`) are recalculated to avoid unintended updates. +- Expanded trigger coverage for common actions (creation, modification, purchase receipt, etc.). +- Automatic cost-price computation from dedicated dictionaries and purchase-price averages. + +### Compatibility + +- Dolibarr ≥ 19.0 (recommended minimum). +- External module installable in `htdocs/custom/dynamicsprices`. + +### Installation + +#### From a ZIP archive + +1. Download the `module_dynamicsprices-x.y.z.zip` archive. +2. Deploy it via **Home > Setup > Modules > Deploy an external module**. +3. Enable the **DynamicsPrices** module in **Setup > Modules/Applications**. + +#### From a Git repository + +```bash +cd htdocs/custom +git clone git@github.com:gitlogin/dynamicsprices.git dynamicsprices +``` + +Then enable the module in Dolibarr as described above. + +### Upgrade + +1. Back up the database and the module directory. +2. Install the new version (ZIP or Git) in `htdocs/custom/dynamicsprices`. +3. Run any migration scripts proposed by Dolibarr if needed. + +### Configuration + +- **Coefficient dictionary**: define margin coefficients in **Dictionaries > DynamicsPrices Coefficients**. +- **Triggers**: DynamicsPrices triggers update prices during standard actions (product creation, supplier receipt, price edits, etc.). +- **Kits**: a kit's selling price is recalculated only after updating its component prices to prevent duplication. + +### Usage + +- Create or update a product with a purchase price filled in. +- Triggers automatically compute cost price and selling price using the applicable coefficient. +- Services and non-physical products (`fk_product_type != 0`) are ignored by automated updates. + +### Permissions and security + +- Update actions follow Dolibarr standard permissions for products and dictionaries. +- Module screens automatically hide actions that the user is not allowed to perform. + +### Translations + +Language files live under `langs/`. Complete or adjust translations in `en_US` and `fr_FR` for any new labels. + +### Support + +- Dolibarr documentation and support: [https://wiki.dolibarr.org](https://wiki.dolibarr.org) +- Other external modules: [Dolistore.com](https://www.dolistore.com) + +### License + +- Code: GPLv3 or later (see `COPYING`). +- Documentation: GFDL (see the corresponding license). From e95321ca0d8c0b211c78a83a1f777201a7677885 Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 01:22:40 +0100 Subject: [PATCH 36/38] Update ChangeLog.md --- ChangeLog.md | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 8dac392..af22b78 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,31 +1,16 @@ # Changelog DynamicsPrices ## 2.0.0 -- Ajout du support des kits : le prix d'un kit est recalculé après ses composants pour éviter les doublons et refléter le coût cumulé. -- Correction des mises à jour intempestives des services : seuls les produits physiques sont recalculés (`fk_product_type = 0`). -- Extension de la couverture des triggers pour lancer les recalculs sur davantage d'événements Dolibarr. -- Calcul automatique des prix de revient via un dictionnaire dédié et la moyenne des prix d'achat. - -## 2.0.0 (EN) -- Added kit support: a kit price is recalculated after its components to avoid duplicates and reflect the cumulative cost. -- Fixed unintended service updates: only physical products are recalculated (`fk_product_type = 0`). -- Expanded trigger coverage to launch recalculations on more Dolibarr events. -- Automatic cost-price computation via a dedicated dictionary and average purchase prices. +- Ajout du support des kits : le prix d'un kit est recalculé après ses composants pour éviter les doublons et refléter le coût cumulé. / Added kit support: a kit price is recalculated after its components to avoid duplicates and reflect the cumulative cost. +- Correction des mises à jour intempestives des services : seuls les produits physiques sont recalculés (`fk_product_type = 0`). / Fixed unintended service updates: only physical products are recalculated (`fk_product_type = 0`). +- Extension de la couverture des triggers pour lancer les recalculs sur davantage d'événements Dolibarr. / Expanded trigger coverage to launch recalculations on more Dolibarr events. +- Calcul automatique des prix de revient via un dictionnaire dédié et la moyenne des prix d'achat. / Automatic cost-price computation via a dedicated dictionary and average purchase prices. ## 1.1.1 -- Correction du filtre pour ignorer les services ; seuls les produits sont pris en compte. (29/09/2025) - -## 1.1.1 (EN) -- Fixed the filter to ignore services; only products are taken into account. (29/09/2025) +- Correction du filtre pour ignorer les services ; seuls les produits sont pris en compte. (29/09/2025) / Fixed the filter to ignore services; only products are taken into account. (29/09/2025) ## 1.1 -- Prise en charge des prix de revient pour l'actualisation des prix de vente. (17/09/2025) - -## 1.1 (EN) -- Added cost-price handling to refresh selling prices. (17/09/2025) +- Prise en charge des prix de revient pour l'actualisation des prix de vente. (17/09/2025) / Added cost-price handling to refresh selling prices. (17/09/2025) ## 1.0 -- Version initiale. - -## 1.0 (EN) -- Initial release. +- Version initiale. / Initial release. From c98daac4bb3e8f2742306fe0e1422bc10f09b51d Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 01:23:35 +0100 Subject: [PATCH 37/38] Create composer.json --- composer.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/composer.json @@ -0,0 +1 @@ + From 9c1fa2ee3e8833fc822556c4b244321186e8af7e Mon Sep 17 00:00:00 2001 From: Pierre Ardoin <32256817+mapiolca@users.noreply.github.com> Date: Tue, 9 Dec 2025 01:26:04 +0100 Subject: [PATCH 38/38] Update composer.json --- composer.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8b13789..48fffde 100644 --- a/composer.json +++ b/composer.json @@ -1 +1,23 @@ - +{ + "name": "dolibarr/dynamicprices", + "description": "Dynamicprices module for Dolibarr ERP & CRM.", + "type": "dolibarr-module", + "license": "GPL-3.0-or-later", + "keywords": [ + "dolibarr", + "module", + "dynamicprices" + ], + "homepage": "https://www.dolibarr.org", + "support": { + "docs": "https://wiki.dolibarr.org/" + }, + "require": { + "php": ">=7.0" + }, + "extra": { + "dolibarr": { + "module-name": "dynamicprices" + } + } +}