From 1b2f16b01a311249058145f3568a512cf75a6159 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 27 Jan 2026 10:42:37 +0100 Subject: [PATCH 01/12] Fix ci --- htdocs/core/modules/modProduct.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/modules/modProduct.class.php b/htdocs/core/modules/modProduct.class.php index 36dae6c27f7..092305cdbf1 100644 --- a/htdocs/core/modules/modProduct.class.php +++ b/htdocs/core/modules/modProduct.class.php @@ -879,8 +879,8 @@ class modProduct extends DolibarrModules $this->import_regex_array[$r] = array( 'pr.datec'=>'^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$', 'pr.recuperableonly'=>'^[0|1]$' - ); - $this->import_convertvalue_array[$r] = array( + ); + $this->import_convertvalue_array[$r] = array( 'pr.fk_product'=>array('rule'=>'fetchidfromref', 'classfile'=>'/product/class/product.class.php', 'class'=>'Product', 'method'=>'fetch', 'element'=>'Product') ); $this->import_examplevalues_array[$r] = array('pr.fk_product'=>"ref:PRODUCT_REF or id:123456", From 51e61c8ef3e8a470c1570708512321eeded5f3c6 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Wed, 28 Jan 2026 15:17:39 +0100 Subject: [PATCH 02/12] FIX: missing code to add Ressource to event on creation card (#37002) --- htdocs/comm/action/card.php | 90 ++++++++++++++++++++++++++++++++++--- 1 file changed, 85 insertions(+), 5 deletions(-) diff --git a/htdocs/comm/action/card.php b/htdocs/comm/action/card.php index 3076f3d4854..40b6f99bf04 100644 --- a/htdocs/comm/action/card.php +++ b/htdocs/comm/action/card.php @@ -410,6 +410,11 @@ if (empty($reshook) && $action == 'add') { if (!empty($_SESSION['assignedtouser'])) { $listofuserid = json_decode($_SESSION['assignedtouser'], true); } + + if (!empty($_SESSION['assignedtoresource'])) { + $listofresourceid = json_decode($_SESSION['assignedtoresource'], true); + } + $i = 0; foreach ($listofuserid as $key => $value) { if ($i == 0) { // First entry @@ -520,22 +525,95 @@ if (empty($reshook) && $action == 'add') { // Creation of action/event $idaction = $object->create($user); + $moreparam = ''; if ($idaction > 0) { if (!$object->error) { + if (is_array($listofresourceid) && count($listofresourceid)) { + foreach ($listofresourceid as $resource_id => $val) { + $resource_type = 'dolresource'; + $busy = 1;//GETPOSTINT('busy'); + + // Resources association + if (getDolGlobalString('RESOURCE_USED_IN_EVENT_CHECK')) { + $eventDateStart = $object->datep; + $eventDateEnd = $object->datef; + $isFullDayEvent = $object->fulldayevent; + if (empty($eventDateEnd)) { + if ($isFullDayEvent) { + $eventDateStartArr = dol_getdate($eventDateStart); + $eventDateStart = dol_mktime(0, 0, 0, $eventDateStartArr['mon'], $eventDateStartArr['mday'], $eventDateStartArr['year']); + $eventDateEnd = dol_mktime(23, 59, 59, $eventDateStartArr['mon'], $eventDateStartArr['mday'], $eventDateStartArr['year']); + } + } + + $sql = "SELECT er.rowid, r.ref as r_ref, ac.id as ac_id, ac.label as ac_label"; + $sql .= " FROM " . MAIN_DB_PREFIX . "element_resources as er"; + $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "resource as r ON r.rowid = er.resource_id AND er.resource_type = '" . $db->escape($resource_type) . "'"; + $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "actioncomm as ac ON ac.id = er.element_id AND er.element_type = '" . $db->escape($object->element) . "'"; + $sql .= " WHERE er.resource_id = " . ((int) $resource_id); + $sql .= " AND er.busy = 1"; + $sql .= " AND ("; + + // event date start between ac.datep and ac.datep2 (if datep2 is null we consider there is no end) + $sql .= " (ac.datep <= '" . $db->idate($eventDateStart) . "' AND (ac.datep2 IS NULL OR ac.datep2 >= '" . $db->idate($eventDateStart) . "'))"; + // event date end between ac.datep and ac.datep2 + if (!empty($eventDateEnd)) { + $sql .= " OR (ac.datep <= '" . $db->idate($eventDateEnd) . "' AND (ac.datep2 >= '" . $db->idate($eventDateEnd) . "'))"; + } + // event date start before ac.datep and event date end after ac.datep2 + $sql .= " OR ("; + $sql .= "ac.datep >= '" . $db->idate($eventDateStart) . "'"; + if (!empty($eventDateEnd)) { + $sql .= " AND (ac.datep2 IS NOT NULL AND ac.datep2 <= '" . $db->idate($eventDateEnd) . "')"; + } + $sql .= ")"; + + $sql .= ")"; + $resql = $db->query($sql); + if (!$resql) { + $error++; + $object->error = $db->lasterror(); + $object->errors[] = $object->error; + } else { + if ($db->num_rows($resql) > 0) { + // Resource already in use + $error++; + $object->error = $langs->trans('ErrorResourcesAlreadyInUse') . ' : '; + while ($obj = $db->fetch_object($resql)) { + $object->error .= '
- ' . $langs->trans('ErrorResourceUseInEvent', $obj->r_ref, $obj->ac_label . ' [' . $obj->ac_id . ']'); + } + $object->errors[] = $object->error; + + setEventMessages($object->error, null, 'errors'); + } + $db->free($resql); + } + } + + if (!$error) { + $res = $object->add_element_resource($resource_id, $resource_type, $busy, $val['mandatory']); + } + } + } + + unset($_SESSION['assignedtoresource']); + + // Category association - $categories = GETPOST('categories', 'array'); - $object->setCategories($categories); + if (!$error) { + $categories = GETPOST('categories', 'array'); + $object->setCategories($categories); + } unset($_SESSION['assignedtouser']); - $moreparam = ''; if ($user->id != $object->userownerid) { $moreparam = "filtert=-1"; // We force to remove filter so created record is visible when going back to per user view. } // Create reminders - if ($addreminder == 'on') { + if (!$error && $addreminder == 'on') { $actionCommReminder = new ActionCommReminder($db); $dateremind = dol_time_plus_duree($datep, -1 * $offsetvalue, $offsetunit); @@ -606,7 +684,9 @@ if (empty($reshook) && $action == 'add') { $donotclearsession = 1; } - if ($eventisrecurring) { + if (!$error && $eventisrecurring) { + $dayoffset = 0; + $monthoffset = 0; // We set first date of recurrence and offsets if ($selectedrecurrulefreq == 'WEEKLY' && !empty($selectedrecurrulebyday)) { $firstdatearray = dol_get_first_day_week(GETPOST("apday", 'int'), GETPOST("apmonth", 'int'), GETPOST("apyear", 'int')); From 07ff579416ad1a9d2287e00c6af45de495ffba7a Mon Sep 17 00:00:00 2001 From: Eric Seigne Date: Thu, 29 Jan 2026 10:01:29 +0100 Subject: [PATCH 03/12] auto assign PR to 18 thanks to copilot --- .github/workflows/pr-18.yaml | 178 +++++++++++++++++++++++++---------- 1 file changed, 127 insertions(+), 51 deletions(-) diff --git a/.github/workflows/pr-18.yaml b/.github/workflows/pr-18.yaml index ef651ffa8e6..379657c5293 100644 --- a/.github/workflows/pr-18.yaml +++ b/.github/workflows/pr-18.yaml @@ -1,11 +1,17 @@ -# Action to prepare the github action -# Go on Dolibarr - Settings - Developer settings - Enter a name + webhook to disable + Permissions (see app test) + Can install by any account -# Click on generate the private keys -# Click on Install application - choose user of the Organization -# Go on Organisation - Secret and variables and create a secret PR18_SECRET_KEY and copy the content of received private key. Choose the repository access to "Repository Dolibarr". -# Go on Organisation - Secret and variables and create a variable PR18_APP_ID and copy the ID of the previously create ID. Choose the repository access to "Repository Dolibarr". +# Action to prepare the GitHub Action +# Prerequisites (create in the organization / repo): +# - Secret: PR18_SECRET_KEY (private key generated for the GitHub App) +# - Variable: PR18_APP_ID (GitHub App ID) +# +# Behavior: +# - On pull_request_target (opened, synchronize, reopened) against branch 18.0: +# - Generate a GitHub App token +# - Add the label "Issue for v18 maintenance Team" to the PR (error-tolerant) +# - Assign the reviewers listed below, excluding the PR author +# -> attempt per reviewer (if a reviewer fails, log and continue) +# -> the step fails only if no reviewer could be added +# - On push to 18.0: workflow runs but PR-specific actions are skipped # - name: Set reviewer and label for v18 on: pull_request_target: @@ -24,54 +30,124 @@ jobs: assign-and-label-v18: runs-on: ubuntu-latest + # Mergeers / reviewers list: edit here as needed (comma-separated) + env: + REVIEWERS: "lvessiller-opendsi,rycks" + # Label name to apply + V18_LABEL: "Issue for v18 maintenance Team" + steps: - #- name: Install GitHub CLI - # run: sudo apt-get install gh + # 1) Generate a GitHub App token (via actions/create-github-app-token) + - name: Generate GitHub App token + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.PR18_APP_ID }} + private-key: ${{ secrets.PR18_SECRET_KEY }} - - name: Generate a token - id: generate-token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ vars.PR18_APP_ID }} - private-key: ${{ secrets.PR18_SECRET_KEY }} + # 2) Checkout repository (useful if repo content is needed later) + - name: Checkout repository + uses: actions/checkout@v4 - - name: Checkout repository - uses: actions/checkout@v4 + # 3) Install / configure GitHub CLI (gh) on the runner + - name: Setup GitHub CLI (gh) + uses: cli/gh-action@v3 - - name: Assign Tag - env: - GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - prid: ${{ github.event.pull_request.number }} - url: ${{ github.event.pull_request.html_url }} - url2: ${{ github.event.pull_request_target.html_url }} - run: | - echo "env.prid=${{env.prid}}" - echo "env.url=${{env.url}}" - echo "env.url2=${{env.url2}}" - gh pr edit "${{env.prid}}" --add-label "Issue for v18 maintenance Team" + # Debug information (useful for diagnostics) + - name: Debug info + run: | + echo "Event: $GITHUB_EVENT_NAME" + echo "Ref: $GITHUB_REF" + echo "Run id: $GITHUB_RUN_ID" + echo "Reviewers configured: $REVIEWERS" - - name: Set reviewers except PR author - id: set-reviewers - run: | - # Liste des reviewers à ajuster selon équipe - REVIEWERS=("lvessiller-opendsi" "rycks") - AUTHOR="${{ github.event.pull_request.user.login }}" - FINAL_REVIEWERS=() - for reviewer in "${REVIEWERS[@]}"; do - if [ "$reviewer" != "$AUTHOR" ]; then - FINAL_REVIEWERS+=("$reviewer") + # 4) Add the label to the PR (PR events only) + # -> tolerant to errors: log on failure but do not fail the job + - name: Add label to PR (pull_request events only) + if: ${{ github.event_name == 'pull_request' }} + env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + V18_LABEL: ${{ env.V18_LABEL }} + run: | + set -euo pipefail + echo "Adding label '$V18_LABEL' to PR #${PR_NUMBER}" + if gh pr edit "$PR_NUMBER" --add-label "$V18_LABEL"; then + echo "Label added successfully." + else + echo "Warning: failed to add label '$V18_LABEL' to PR #${PR_NUMBER}. Continuing." fi - done - echo "AUTHOR=$AUTHOR" - echo "reviewers=$(IFS=, ; echo "${FINAL_REVIEWERS[*]}")" >> $GITHUB_OUTPUT - - name: Assign reviewers - if: steps.set-reviewers.outputs.reviewers != '' - env: - GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} - prid: ${{ github.event.pull_request.number }} - url: ${{ github.event.pull_request.html_url }} - reviewers: ${{ steps.set-reviewers.outputs.reviewers }} - run: | - echo "Assigning reviewers: ${{env.reviewers}}" - gh pr edit "${{env.prid}}" --add-reviewer "${{env.reviewers}}" + # 5) Compute final reviewers list excluding the PR author + - name: Compute reviewers (exclude PR author) + if: ${{ github.event_name == 'pull_request' }} + id: set-reviewers + run: | + set -euo pipefail + IFS=',' read -ra ALL_REVIEWERS <<< "${REVIEWERS}" + AUTHOR="${{ github.event.pull_request.user.login }}" + FINAL=() + for r in "${ALL_REVIEWERS[@]}"; do + r_trimmed="$(echo "$r" | xargs)" + if [ -z "$r_trimmed" ]; then + continue + fi + if [ "$r_trimmed" != "$AUTHOR" ]; then + FINAL+=("$r_trimmed") + fi + done + if [ ${#FINAL[@]} -eq 0 ]; then + echo "reviewers=" >> $GITHUB_OUTPUT + else + reviewers_csv="$(IFS=, ; echo "${FINAL[*]}")" + echo "reviewers=${reviewers_csv}" >> $GITHUB_OUTPUT + fi + echo "author=$AUTHOR" >> $GITHUB_OUTPUT + echo "Computed reviewers: ${reviewers_csv:-}" + + # 6) Assign reviewers one-by-one with fine-grained error handling + # - try each reviewer, track successes and failures + # - fail the step only if none could be added + # - succeed if at least one was added (but log failures) + - name: Assign reviewers on PR (per-reviewer, tolerant errors) + if: ${{ github.event_name == 'pull_request' && steps.set-reviewers.outputs.reviewers != '' }} + env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REVIEWERS_CSV: ${{ steps.set-reviewers.outputs.reviewers }} + run: | + # Note: avoid 'set -e' to handle per-reviewer failures manually + set -uo pipefail + IFS=',' read -ra TO_ADD <<< "${REVIEWERS_CSV}" + SUCCESS=0 + FAILED=() + for r in "${TO_ADD[@]}"; do + r_trimmed="$(echo "$r" | xargs)" + if [ -z "$r_trimmed" ]; then + continue + fi + echo "Attempting to add reviewer: $r_trimmed" + if gh pr edit "$PR_NUMBER" --add-reviewer "$r_trimmed"; then + echo "Added reviewer: $r_trimmed" + SUCCESS=$((SUCCESS+1)) + else + echo "Warning: failed to add reviewer: $r_trimmed" + echo "Debug: PR view:" + gh pr view "$PR_NUMBER" --json number,title,author,reviewRequests || true + FAILED+=("$r_trimmed") + fi + done + if [ $SUCCESS -eq 0 ]; then + echo "Error: none of the configured reviewers could be added: ${FAILED[*]:-}" + # Fail the step because no reviewer was added + exit 1 + else + echo "Reviewers added: ${SUCCESS}. Failed to add: ${FAILED[*]:-none}" + # Step succeeds even if some reviewers failed + fi + + # 7) Push event notice (no PR-specific actions performed) + - name: Push event notice + if: ${{ github.event_name == 'push' }} + run: | + echo "Triggered by push on branch 18.0. No PR-specific actions performed." From 70b3be5e7f40f21052745854f8eeb7e698e4db2b Mon Sep 17 00:00:00 2001 From: Sylvain Legrand Date: Thu, 29 Jan 2026 13:02:25 +0100 Subject: [PATCH 04/12] Fix from V22 (#35559) Using a packaging with a float value less than 1 (0.1, 0.5, etc.) generates a 500 error (division by 0) Co-authored-by: Laurent Destailleur Co-authored-by: Eric - CAP-REL <1468823+rycks@users.noreply.github.com> --- .../class/fournisseur.commande.class.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/htdocs/fourn/class/fournisseur.commande.class.php b/htdocs/fourn/class/fournisseur.commande.class.php index ee155e7ad3d..4cfb71d908f 100644 --- a/htdocs/fourn/class/fournisseur.commande.class.php +++ b/htdocs/fourn/class/fournisseur.commande.class.php @@ -616,7 +616,7 @@ class CommandeFournisseur extends CommonOrder $objsearchpackage = $this->db->fetch_object($resqlsearchpackage); if ($objsearchpackage) { $line->fk_fournprice = $objsearchpackage->rowid; - $line->packaging = $objsearchpackage->packaging; + $line->packaging = (float) $objsearchpackage->packaging; } } else { $this->error = $this->db->lasterror(); @@ -1979,16 +1979,16 @@ class CommandeFournisseur extends CommonOrder } // Predefine quantity according to packaging - if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) { + if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) { $prod = new Product($this->db); - $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc)); + $prod->get_buyprice($fk_prod_fourn_price, (float) $qty, $fk_product, 'none', (empty($this->fk_soc) ? $this->socid : $this->fk_soc)); if ($qty < $prod->packaging) { - $qty = $prod->packaging; + $qty = (float) $prod->packaging; } else { - if (!empty($prod->packaging) && ($qty % $prod->packaging) > 0) { - $coeff = intval($qty / $prod->packaging) + 1; - $qty = $prod->packaging * $coeff; + if (!empty($prod->packaging) && (fmod((float) $qty, (float) $prod->packaging) > 0.000001)) { + $coeff = intval((float) $qty / $prod->packaging) + 1; + $qty = (float) $prod->packaging * $coeff; setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs'); } } @@ -2962,11 +2962,11 @@ class CommandeFournisseur extends CommonOrder $this->line->desc = $desc; // redefine quantity according to packaging - if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) { + if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) { if ($qty < $this->line->packaging) { $qty = $this->line->packaging; } else { - if (!empty($this->line->packaging) && ($qty % $this->line->packaging) > 0) { + if (!empty($this->line->packaging) && is_numeric($this->line->packaging) && (float) $this->line->packaging > 0 && (fmod((float) $qty, $this->line->packaging) > 0)) { $coeff = intval($qty / $this->line->packaging) + 1; $qty = $this->line->packaging * $coeff; setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs'); @@ -3869,7 +3869,7 @@ class CommandeFournisseurLigne extends CommonOrderLine $objsearchpackage = $this->db->fetch_object($resqlsearchpackage); if ($objsearchpackage) { $this->fk_fournprice = $objsearchpackage->rowid; - $this->packaging = $objsearchpackage->packaging; + $this->packaging = (float) $objsearchpackage->packaging; } } else { $this->error = $this->db->lasterror(); From 72ed29a5990b778e2093d72988ba1e08760c1099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Fali=C3=A8re?= <121813548+BenjaminFlr@users.noreply.github.com> Date: Wed, 4 Feb 2026 14:50:21 +0100 Subject: [PATCH 05/12] FIX(API, thirdparties): get fixed amount discounts (#37068) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benjamin Falière --- htdocs/societe/class/api_thirdparties.class.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/htdocs/societe/class/api_thirdparties.class.php b/htdocs/societe/class/api_thirdparties.class.php index 351a178d318..7a19427f728 100644 --- a/htdocs/societe/class/api_thirdparties.class.php +++ b/htdocs/societe/class/api_thirdparties.class.php @@ -1,8 +1,9 @@ - * Copyright (C) 2018 Pierre Chéné - * Copyright (C) 2019 Cedric Ancelin +/* Copyright (C) 2015 Jean-François Ferry + * Copyright (C) 2018 Pierre Chéné + * Copyright (C) 2019 Cedric Ancelin * Copyright (C) 2020-2021 Frédéric France + * Copyright (C) 2026 Benjamin Falière * * 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 @@ -1052,8 +1053,9 @@ class Thirdparties extends DolibarrApi $sql = "SELECT f.ref, f.type as factype, re.fk_facture_source, re.rowid, re.amount_ht, re.amount_tva, re.amount_ttc, re.description, re.fk_facture, re.fk_facture_line"; - $sql .= " FROM ".MAIN_DB_PREFIX."societe_remise_except as re, ".MAIN_DB_PREFIX."facture as f"; - $sql .= " WHERE f.rowid = re.fk_facture_source AND re.fk_soc = ".((int) $id); + $sql .= " FROM ".MAIN_DB_PREFIX."societe_remise_except as re"; + $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as f ON f.rowid = re.fk_facture_source"; + $sql .= " WHERE re.fk_soc = ".((int) $id); if ($filter == "available") { $sql .= " AND re.fk_facture IS NULL AND re.fk_facture_line IS NULL"; } From 07a3d0e4b7d5e1d1d4e95cc6ce8ce61ae7d18821 Mon Sep 17 00:00:00 2001 From: Eric Seigne Date: Thu, 5 Feb 2026 09:43:02 +0100 Subject: [PATCH 06/12] auto assign PR on 18.0 / Unable to resolve action cli/gh-action, repository not found --- .github/workflows/pr-18.yaml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-18.yaml b/.github/workflows/pr-18.yaml index 379657c5293..e606aa233e1 100644 --- a/.github/workflows/pr-18.yaml +++ b/.github/workflows/pr-18.yaml @@ -50,8 +50,21 @@ jobs: uses: actions/checkout@v4 # 3) Install / configure GitHub CLI (gh) on the runner - - name: Setup GitHub CLI (gh) - uses: cli/gh-action@v3 + #- name: Setup GitHub CLI (gh) + # uses: cli/gh-action@v3 + # -> Error: Unable to resolve action cli/gh-action, repository not found + - name: Setup GitHub CLI (gh) (install locally) + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y gh || { + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt-get update + sudo apt-get install -y gh + } + gh --version # Debug information (useful for diagnostics) - name: Debug info From 66c552ccb2bd84217322e88beefe246bb3008c68 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 6 Feb 2026 02:39:30 +0100 Subject: [PATCH 07/12] Removed not used file --- .scrutinizer.yml | 231 ----------------------------------------------- 1 file changed, 231 deletions(-) delete mode 100644 .scrutinizer.yml diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index c1cb2e853f1..00000000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,231 +0,0 @@ -# .scrutinizer.yml -#build: -# - php-scrutinizer-run -build: - nodes: - analysis: - tests: - override: - - command: php-scrutinizer-run - idle_timeout: 8000 - -imports: - - javascript - - php - -filter: - excluded_paths: - - build/* - - dev/* - - doc/* - - documents/* - - node_modules/* - - test/* - dependency_paths: - - htdocs/includes/* - paths: - - htdocs/* - - scripts/* - -tools: - # php_analyzer. Doc on https://scrutinizer-ci.com/docs/tools/php/php-analyzer/ - php_analyzer: - enabled: true - extensions: - - php - dependency_paths: - - htdocs/includes/ - filter: - excluded_paths: - - build/* - - dev/* - - doc/* - - documents/* - - htdocs/includes/* - - htdocs/core/class/lessc.class.php - - node_modules/* - - test/* - paths: - - htdocs/ - - scripts/ - config: - parameter_reference_check: - enabled: true - checkstyle: - enabled: false - no_trailing_whitespace: true - naming: - enabled: true - local_variable: ^[a-z][a-zA-Z0-9]*$ - abstract_class_name: ^Abstract|Factory$ - utility_class_name: Utils?$ - constant_name: ^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$ - property_name: ^[a-z][a-zA-Z0-9]*$ - method_name: ^(?:[a-z]|__)[a-zA-Z0-9]*$ - parameter_name: ^[a-z][a-zA-Z0-9]*$ - interface_name: ^[A-Z][a-zA-Z0-9]*Interface$ - type_name: ^[A-Z][a-zA-Z0-9]*$ - exception_name: ^[A-Z][a-zA-Z0-9]*Exception$ - isser_method_name: ^(?:is|has|should|may|supports) - unreachable_code: - enabled: true - check_access_control: - enabled: true - typo_checks: - enabled: true - check_variables: - enabled: true - check_calls: - enabled: true - too_many_arguments: true - missing_argument: true - argument_type_checks: lenient # Allowed Values: "disabled", "lenient", "strict" - suspicious_code: - enabled: true - overriding_parameter: false - overriding_closure_use: true - parameter_closure_use_conflict: true - parameter_multiple_times: true - non_existent_class_in_instanceof_check: true - non_existent_class_in_catch_clause: true - assignment_of_null_return: true - non_commented_switch_fallthrough: true - non_commented_empty_catch_block: true - overriding_private_members: true - use_statement_alias_conflict: true - precedence_in_condition_assignment: true - dead_assignments: - enabled: true - verify_php_doc_comments: - enabled: false - parameters: true - return: true - suggest_more_specific_types: true - ask_for_return_if_not_inferrable: true - ask_for_param_type_annotation: true - loops_must_use_braces: - enabled: true - check_usage_context: - enabled: true - simplify_boolean_return: - enabled: false - phpunit_checks: - enabled: false - reflection_checks: - enabled: true - - # Checks Common Precedence Mistakes - precedence_checks: - enabled: true - assignment_in_condition: true - comparison_of_bit_result: true - basic_semantic_checks: - enabled: true - # Disabled unused code. In most cases, we want to keep it. - unused_code: - enabled: false - deprecation_checks: - enabled: true - useless_function_calls: - enabled: true - metrics_lack_of_cohesion_methods: - enabled: true - metrics_coupling: - enabled: true - stable_code: - namespace_prefixes: [] - classes: [] - doctrine_parameter_binding: - enabled: false - doctrine_entity_manager_injection: - enabled: false - symfony_request_injection: - enabled: false - doc_comment_fixes: - enabled: true - reflection_fixes: - enabled: false - use_statement_fixes: - enabled: true - remove_unused: true - # Whether you would like multiple imports in one USE statement to be preserved, e.g. ``use A, B;``. - preserve_multiple: false - # Whether you would like to preserve blank lines between use statements. - preserve_blanklines: false - order_alphabetically: false - # To use specific config for a specific path, use path_configs: (see example on page https://scrutinizer-ci.com/docs/configuration/tool_config_structure) - - # php_depend - php_pdepend: - enabled: false - configuration_file: null - suffixes: - - php - excluded_dirs: { } - filter: - excluded_paths: - - 'build/*' - - 'dev/*' - - 'doc/*' - - 'test/*' - - 'htdocs/includes/*' - paths: { } - - # change tracking - php_changetracking: - enabled: false - bug_patterns: - - '\bfix(?:es|ed)?\b' - feature_patterns: - - '\badd(?:s|ed)?\b' - - '\bimplement(?:s|ed)?\b' - filter: - excluded_paths: - - 'build/*' - - 'dev/*' - - 'doc/*' - - 'documents/*' - - 'htdocs/includes/*' - - 'node_modules/*' - - 'test/*' - paths: { } - - # Similar code detection - php_sim: - enabled: false - min_mass: 30 - filter: - excluded_paths: - - 'build/*' - - 'dev/*' - - 'doc/*' - - 'documents/*' - - 'htdocs/includes/*' - - 'node_modules/*' - - 'test/*' - paths: { } - - # Coding-Style / Bug Detection - js_hint: - enabled: false - use_native_config: true - extensions: - - js - filter: - excluded_paths: - - 'build/*' - - 'dev/*' - - 'doc/*' - - 'documents/*' - - 'htdocs/includes/*' - - 'node_modules/*' - - 'test/*' - paths: { } - config: { } - path_configs: { } - - -before_commands: { } -after_commands: { } -artifacts: { } -build_failure_conditions: { } From 59ce599ba55c6b1ed5e600908fd1ac509d433358 Mon Sep 17 00:00:00 2001 From: Vincent Penel Date: Fri, 6 Feb 2026 12:18:36 +0100 Subject: [PATCH 08/12] set oldCopy and Status (#37117) * set oldCopy and Status * Update ticket.class.php --------- Co-authored-by: Laurent Destailleur --- htdocs/ticket/class/ticket.class.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/htdocs/ticket/class/ticket.class.php b/htdocs/ticket/class/ticket.class.php index 187867c54c3..e821b4e80d4 100644 --- a/htdocs/ticket/class/ticket.class.php +++ b/htdocs/ticket/class/ticket.class.php @@ -1510,11 +1510,12 @@ class Ticket extends CommonObject if ($this->statut != self::STATUS_CANCELED) { // no closed $this->oldcopy = dol_clone($this); + $this->status = Ticket::STATUS_READ; $this->db->begin(); $sql = "UPDATE ".MAIN_DB_PREFIX."ticket"; - $sql .= " SET fk_statut = ".Ticket::STATUS_READ.", date_read = '".$this->db->idate(dol_now())."'"; + $sql .= " SET fk_statut = ".$this->status .", date_read = '".$this->db->idate(dol_now())."'"; $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog(get_class($this)."::markAsRead"); @@ -1538,12 +1539,14 @@ class Ticket extends CommonObject } else { $this->db->rollback(); $this->error = join(',', $this->errors); + $this->status = $this->oldcopy->status; dol_syslog(get_class($this)."::markAsRead ".$this->error, LOG_ERR); return -1; } } else { $this->db->rollback(); $this->error = $this->db->lasterror(); + $this->status = $this->oldcopy->status; dol_syslog(get_class($this)."::markAsRead ".$this->error, LOG_ERR); return -1; } @@ -1743,9 +1746,11 @@ class Ticket extends CommonObject if ($this->fk_statut != Ticket::STATUS_CLOSED && $this->fk_statut != Ticket::STATUS_CANCELED) { // not closed $this->db->begin(); + $this->oldcopy = dol_clone($this); + $this->status = ($mode ? Ticket::STATUS_CANCELED : Ticket::STATUS_CLOSED); $sql = "UPDATE ".MAIN_DB_PREFIX."ticket"; - $sql .= " SET fk_statut=".($mode ? Ticket::STATUS_CANCELED : Ticket::STATUS_CLOSED).", progress=100, date_close='".$this->db->idate(dol_now())."'"; + $sql .= " SET fk_statut=".$this->status.", progress=100, date_close='".$this->db->idate(dol_now())."'"; $sql .= " WHERE rowid = ".((int) $this->id); dol_syslog(get_class($this)."::close mode=".$mode); From 173c6865cb73fafdd736f2938e41c589d8cb8b15 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 6 Feb 2026 13:05:21 +0100 Subject: [PATCH 09/12] Test CI --- .github/workflows/pr-18-testldr.yaml | 79 ++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/pr-18-testldr.yaml diff --git a/.github/workflows/pr-18-testldr.yaml b/.github/workflows/pr-18-testldr.yaml new file mode 100644 index 00000000000..a04673417c3 --- /dev/null +++ b/.github/workflows/pr-18-testldr.yaml @@ -0,0 +1,79 @@ +# Action to prepare the github action +# Go on Dolibarr - Settings - Developer settings - Enter a name + webhook to disable + Permissions (see app test) + Can install by any account +# Click on generate the private keys +# Click on Install application - choose user of the Organization +# Go on Organisation - Secret and variables and create a secret PR18_SECRET_KEY and copy the content of received private key. Choose the repository access to "Repository Dolibarr". +# Go on Organisation - Secret and variables and create a variable PR18_APP_ID and copy the ID of the previously create ID. Choose the repository access to "Repository Dolibarr". +# + +name: Set reviewer and label for v18 +on: + pull_request_target: + types: [opened, synchronize, reopened] + branches: + - "18.0" + push: + branches: + - "18.0" + +permissions: + pull-requests: write + issues: write + +jobs: + assign-and-label-v18: + runs-on: ubuntu-latest + + steps: + #- name: Install GitHub CLI + # run: sudo apt-get install gh + + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.PR18_APP_ID }} + private-key: ${{ secrets.PR18_SECRET_KEY }} + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Assign Tag + env: + GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + prid: ${{ github.event.pull_request.number }} + prid2: ${{ github.event.pull_request_target.number }} + url: ${{ github.event.pull_request.html_url }} + url2: ${{ github.event.pull_request_target.html_url }} + run: | + echo "env.prid=${{env.prid}}" + echo "env.prid=${{env.prid2}}" + echo "env.url=${{env.url}}" + echo "env.url2=${{env.url2}}" + gh pr edit "${{env.prid}}" --add-label "Issue for v18 maintenance Team" + + - name: Set reviewers except PR author + id: set-reviewers + run: | + # Liste des reviewers à ajuster selon équipe + REVIEWERS=("lvessiller-opendsi" "rycks") + AUTHOR="${{ github.event.pull_request.user.login }}" + FINAL_REVIEWERS=() + for reviewer in "${REVIEWERS[@]}"; do + if [ "$reviewer" != "$AUTHOR" ]; then + FINAL_REVIEWERS+=("$reviewer") + fi + done + echo "AUTHOR=$AUTHOR" + echo "reviewers=$(IFS=, ; echo "${FINAL_REVIEWERS[*]}")" >> $GITHUB_OUTPUT + + - name: Assign reviewers + if: steps.set-reviewers.outputs.reviewers != '' + env: + GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} + prid: ${{ github.event.pull_request.number }} + url: ${{ github.event.pull_request.html_url }} + reviewers: ${{ steps.set-reviewers.outputs.reviewers }} + run: | + echo "Assigning reviewers: ${{env.reviewers}}" + gh pr edit "${{env.prid}}" --add-reviewer "${{env.reviewers}}" From da0ece345fb5fd72119c0aeda83f2f2bc3b59740 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 6 Feb 2026 13:06:16 +0100 Subject: [PATCH 10/12] Fix label --- .github/workflows/pr-18-testldr.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-18-testldr.yaml b/.github/workflows/pr-18-testldr.yaml index a04673417c3..330a853a55f 100644 --- a/.github/workflows/pr-18-testldr.yaml +++ b/.github/workflows/pr-18-testldr.yaml @@ -6,7 +6,7 @@ # Go on Organisation - Secret and variables and create a variable PR18_APP_ID and copy the ID of the previously create ID. Choose the repository access to "Repository Dolibarr". # -name: Set reviewer and label for v18 +name: Set reviewer and label for v18 (test ldr) on: pull_request_target: types: [opened, synchronize, reopened] From 2b3ae6c2d7d4ff3ccbf9df06b71c8e8d3168dfd3 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Fri, 6 Feb 2026 13:16:16 +0100 Subject: [PATCH 11/12] Add logs --- .github/workflows/pr-18-testldr.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-18-testldr.yaml b/.github/workflows/pr-18-testldr.yaml index 330a853a55f..6babdf86b76 100644 --- a/.github/workflows/pr-18-testldr.yaml +++ b/.github/workflows/pr-18-testldr.yaml @@ -28,6 +28,9 @@ jobs: #- name: Install GitHub CLI # run: sudo apt-get install gh + - name: Debug GitHub Event + run: cat $GITHUB_EVENT_PATH + - name: Generate a token id: generate-token uses: actions/create-github-app-token@v2 From 94d4f2661dbca1c3961e6d927cb6c41e70526749 Mon Sep 17 00:00:00 2001 From: Eric - CAP-REL <1468823+rycks@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:38:36 +0100 Subject: [PATCH 12/12] MEMBER_ADDON does not exists, MEMBER_CODEMEMBER_ADDON is the right key (#37151) --- htdocs/core/modules/modAdherent.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/modules/modAdherent.class.php b/htdocs/core/modules/modAdherent.class.php index 6e2aeb58c13..928620bcb8a 100644 --- a/htdocs/core/modules/modAdherent.class.php +++ b/htdocs/core/modules/modAdherent.class.php @@ -363,8 +363,8 @@ class modAdherent extends DolibarrModules $this->import_convertvalue_array[$r] = array( 'a.ref'=>array( 'rule'=>'getrefifauto', - 'class'=>(empty($conf->global->MEMBER_ADDON) ? 'mod_member_simple' : $conf->global->MEMBER_ADDON), - 'path'=>"/core/modules/member/".(empty($conf->global->MEMBER_ADDON) ? 'mod_member_simple' : $conf->global->MEMBER_ADDON).'.php' + 'class'=>(empty($conf->global->MEMBER_CODEMEMBER_ADDON) ? 'mod_member_simple' : $conf->global->MEMBER_CODEMEMBER_ADDON), + 'path'=>"/core/modules/member/".(empty($conf->global->MEMBER_CODEMEMBER_ADDON) ? 'mod_member_simple' : $conf->global->MEMBER_CODEMEMBER_ADDON).'.php' ), 'a.state_id' => array( 'rule' => 'fetchidfromcodeid',