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
diff --git a/ChangeLog.md b/ChangeLog.md
index 2970243..af22b78 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é. / 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) / 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) / Added cost-price handling to refresh selling prices. (17/09/2025)
+
+## 1.0
+- Version initiale. / Initial release.
diff --git a/README.md b/README.md
index 528356b..21db353 100644
--- a/README.md
+++ b/README.md
@@ -1,96 +1,153 @@
-# DYNAMICSPRICES FOR [DOLIBARR ERP & CRM](https://www.dolibarr.org)
+# DynamicsPrices
-## Features
+## Présentation (FR)
-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é.
+Module Dolibarr pour la mise à jour dynamique des prix de vente à partir des coûts d'achat et des coefficients configurables.
-
+### Aperçu
-Other external modules are available on [Dolistore.com](https://www.dolistore.com).
+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.
-## Translations
+### Fonctionnalités clés
-Translations can be completed manually by editing files in the module directories under `langs`.
+- 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.
-
+### Installation
+#### Depuis une archive ZIP
-## Installation
+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**.
-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
+#### Depuis un dépôt Git
+```bash
+cd htdocs/custom
+git clone git@github.com:gitlogin/dynamicsprices.git dynamicsprices
+```
+
+Puis activer le module dans Dolibarr comme décrit ci-dessus.
+
+### 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
+
+- **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
+
+- 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é
+
+- 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.
-### From the ZIP file and GUI interface
+### Traductions
-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.
+Les fichiers de langue sont disponibles dans `langs/`. Complétez ou ajustez les traductions en `en_US` et `fr_FR` pour tout nouveau libellé.
-
+### Summary
-
+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.
-### Final steps
+### Usage
-Using your browser:
+- 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.
- - Log into Dolibarr as a super-administrator
- - Go to "Setup"> "Modules"
- - You should now be able to find and enable the module
+### 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
-## Licenses
+Language files live under `langs/`. Complete or adjust translations in `en_US` and `fr_FR` for any new labels.
-### Main code
+### Support
-GPLv3 or (at your option) any later version. See file COPYING for more information.
+- Dolibarr documentation and support: [https://wiki.dolibarr.org](https://wiki.dolibarr.org)
+- Other external modules: [Dolistore.com](https://www.dolistore.com)
-### Documentation
+### License
-All texts and readme's are licensed under [GFDL](https://www.gnu.org/licenses/fdl-1.3.en.html).
+- Code: GPLv3 or later (see `COPYING`).
+- Documentation: GFDL (see the corresponding license).
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 '';
+ 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;