From 1b2f16b01a311249058145f3568a512cf75a6159 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Tue, 27 Jan 2026 10:42:37 +0100 Subject: [PATCH 01/39] 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/39] 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/39] 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/39] 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/39] 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/39] 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/39] 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/39] 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/39] 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/39] 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/39] 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/39] 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', From 7b5ddac96354e3b992741419ed1e94a07a2cda01 Mon Sep 17 00:00:00 2001 From: kkhelifa-opendsi Date: Sun, 8 Feb 2026 22:25:58 +0100 Subject: [PATCH 13/39] FIX: Backport fix on v20 for result page of compta (/compta/resultat/index.php) (#37127) * FIX: Backport fix on v20 for result page of compta (/compta/resultat/index.php). The expense report were not included when the module was activated * Update index.php --------- Co-authored-by: Laurent Destailleur --- htdocs/compta/resultat/index.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/compta/resultat/index.php b/htdocs/compta/resultat/index.php index 6a1fd134562..b7de1fe19eb 100644 --- a/htdocs/compta/resultat/index.php +++ b/htdocs/compta/resultat/index.php @@ -45,7 +45,7 @@ $date_endyear = GETPOST('date_endyear', 'int'); $nbofyear = 4; -// Change this to test different cases of setup +// Change this to test different cases of setup. //$conf->global->SOCIETE_FISCAL_MONTH_START = 7; @@ -678,7 +678,7 @@ if (isModEnabled('salaries') && ($modecompta == 'CREANCES-DETTES' || $modecompta * Expense reports */ -if (!isModEnabled('expensereport') && ($modecompta == 'CREANCES-DETTES' || $modecompta == "RECETTES-DEPENSES")) { +if (isModEnabled('expensereport') && ($modecompta == 'CREANCES-DETTES' || $modecompta == "RECETTES-DEPENSES")) { $langs->load('trips'); if ($modecompta == 'CREANCES-DETTES') { From 0cc80e65ed41be9546ec85b1e7afd01bdbbb9947 Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Mon, 9 Feb 2026 18:33:54 +0100 Subject: [PATCH 14/39] FIX SQL Error on VAT not defined in product card (#37159) --- htdocs/core/class/html.form.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 8a285587b79..e8b04307c2f 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -15,7 +15,7 @@ * Copyright (C) 2012-2016 Marcos García * Copyright (C) 2012 Cedric Salvador * Copyright (C) 2012-2015 Raphaël Doursenaud - * Copyright (C) 2014-2023 Alexandre Spangaro + * Copyright (C) 2014-2026 Alexandre Spangaro * Copyright (C) 2018-2022 Ferran Marcet * Copyright (C) 2018-2021 Frédéric France * Copyright (C) 2018 Nicolas ZABOURI @@ -6446,7 +6446,7 @@ class Form if (!empty($user) && $user->admin && preg_match('/\'(..)\'/', $country_code, $reg)) { $langs->load("errors"); $new_country_code = $reg[1]; - $country_id = dol_getIdFromCode($this->db, $new_country_code, 'c_pays', 'code', 'rowid'); + $country_id = dol_getIdFromCode($this->db, $new_country_code, 'c_country', 'code', 'rowid'); $this->error .= '
'.$langs->trans("ErrorFixThisHere", DOL_URL_ROOT.'/admin/dict.php?id=10'.($country_id > 0 ? '&countryidforinsert='.$country_id : '')); } $this->error .= ''; From 0b8e0c4765d79a37314d2a6875fdd546ad2c15b1 Mon Sep 17 00:00:00 2001 From: ThomasNgr-OpenDSI Date: Tue, 10 Feb 2026 15:30:40 +0100 Subject: [PATCH 15/39] FIX display of DLC/DLUO in tooltip (#37164) --- htdocs/product/stock/class/productlot.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/product/stock/class/productlot.class.php b/htdocs/product/stock/class/productlot.class.php index 7e54fd90622..8070fc45a6b 100644 --- a/htdocs/product/stock/class/productlot.class.php +++ b/htdocs/product/stock/class/productlot.class.php @@ -956,10 +956,10 @@ class Productlot extends CommonObject //$datas['divopen'] = '
'; $datas['batch'] = '
'.$langs->trans('Batch').': '.$this->batch; if ($this->eatby && empty($conf->global->PRODUCT_DISABLE_EATBY)) { - $datas['eatby'] = '
'.$langs->trans('EatByDate').': '.dol_print_date($this->db->jdate($this->eatby), 'day'); + $datas['eatby'] = '
'.$langs->trans('EatByDate').': '.dol_print_date($this->eatby, 'day'); } if ($this->sellby && empty($conf->global->PRODUCT_DISABLE_SELLBY)) { - $datas['sellby'] = '
'.$langs->trans('SellByDate').': '.dol_print_date($this->db->jdate($this->sellby), 'day'); + $datas['sellby'] = '
'.$langs->trans('SellByDate').': '.dol_print_date($this->sellby, 'day'); } //$datas['divclose'] = '
'; From e43438cc7b82239e1438282437bb6c4c397950fc Mon Sep 17 00:00:00 2001 From: Vincent Penel Date: Tue, 10 Feb 2026 15:40:05 +0100 Subject: [PATCH 16/39] Fix/ticket status (#37129) * set oldCopy and Status * Update ticket.class.php * Question Eldy * Fix event status change --------- Co-authored-by: Laurent Destailleur --- htdocs/core/class/commonobject.class.php | 4 ++++ htdocs/ticket/card.php | 4 ++-- htdocs/ticket/class/ticket.class.php | 2 +- htdocs/ticket/list.php | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index 1561867c858..385b493b2d7 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -4478,7 +4478,11 @@ abstract class CommonObject } } + $this->context = array_merge($this->context, array('newstatus' => $status)); + if ($trigkey) { + $this->oldcopy = dol_clone($this); + // Call trigger $result = $this->call_trigger($trigkey, $user); if ($result < 0) { diff --git a/htdocs/ticket/card.php b/htdocs/ticket/card.php index e207479cacc..9832970bdfa 100755 --- a/htdocs/ticket/card.php +++ b/htdocs/ticket/card.php @@ -568,7 +568,7 @@ if (empty($reshook)) { if ($object->fetch(GETPOST('id', 'int'), '', GETPOST('track_id', 'alpha')) >= 0) { // prevent browser refresh from reopening ticket several times if ($object->status == Ticket::STATUS_CLOSED || $object->status == Ticket::STATUS_CANCELED) { - $res = $object->setStatut(Ticket::STATUS_ASSIGNED); + $res = $object->setStatut(Ticket::STATUS_ASSIGNED, null, '', 'TICKET_MODIFY'); if ($res) { // Log action in ticket logs table //$log_action = $langs->trans('TicketLogReopen'); @@ -626,7 +626,7 @@ if (empty($reshook)) { if ($object->fetch(GETPOST('id', 'int'), GETPOST('track_id', 'alpha')) >= 0) { $new_status = GETPOST('new_status', 'int'); $old_status = $object->status; - $res = $object->setStatut($new_status); + $res = $object->setStatut($new_status, null, '', 'TICKET_MODIFY'); if ($res) { // Log action in ticket logs table $log_action = $langs->trans('TicketLogStatusChanged', $langs->transnoentities($object->statuts_short[$old_status]), $langs->transnoentities($object->statuts_short[$new_status])); diff --git a/htdocs/ticket/class/ticket.class.php b/htdocs/ticket/class/ticket.class.php index e821b4e80d4..fadcb371358 100644 --- a/htdocs/ticket/class/ticket.class.php +++ b/htdocs/ticket/class/ticket.class.php @@ -2714,7 +2714,7 @@ class Ticket extends CommonObject if (($object->status < self::STATUS_IN_PROGRESS && !$user->socid && !$private) || ($object->status > self::STATUS_IN_PROGRESS && $public_area) ) { - $object->setStatut(3); + $object->setStatut(3, null, '', 'TICKET_MODIFY'); } return 1; } else { diff --git a/htdocs/ticket/list.php b/htdocs/ticket/list.php index 4c4dce5a428..1fddd77010c 100644 --- a/htdocs/ticket/list.php +++ b/htdocs/ticket/list.php @@ -284,7 +284,7 @@ if (empty($reshook)) { $result = $objecttmp->fetch($toselectid); if ($result > 0) { if ($objecttmp->status == Ticket::STATUS_CLOSED || $objecttmp->status == Ticket::STATUS_CANCELED) { - $result = $objecttmp->setStatut(Ticket::STATUS_ASSIGNED); + $result = $objecttmp->setStatut(Ticket::STATUS_ASSIGNED, null, '', 'TICKET_MODIFY'); if ($result < 0) { setEventMessages($objecttmp->error, $objecttmp->errors, 'errors'); $error++; From 673659de986ecd65929671fbe5dfe263f50bb0aa Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Thu, 12 Feb 2026 01:23:06 +0100 Subject: [PATCH 17/39] FIX Accountancy - Update height of the export popup (#37206) --- htdocs/accountancy/bookkeeping/export.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/accountancy/bookkeeping/export.php b/htdocs/accountancy/bookkeeping/export.php index 246b8e99ebe..45934b646f6 100644 --- a/htdocs/accountancy/bookkeeping/export.php +++ b/htdocs/accountancy/bookkeeping/export.php @@ -1,7 +1,7 @@ * Copyright (C) 2013-2016 Florian Henry - * Copyright (C) 2013-2024 Alexandre Spangaro + * Copyright (C) 2013-2026 Alexandre Spangaro * Copyright (C) 2022 Lionel Vessiller * Copyright (C) 2016-2017 Laurent Destailleur * Copyright (C) 2018-2021 Frédéric France @@ -778,7 +778,7 @@ if ($action == 'export_file') { ); } - $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").'...', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 420, 600); + $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?'.$param, $langs->trans("ExportFilteredList").'...', $langs->trans('ConfirmExportFile'), 'export_fileconfirm', $form_question, '', 1, 500, 600); } // Print form confirm From e850355b3034bb46e3fa5257bf8fbaf19441bf2b Mon Sep 17 00:00:00 2001 From: Eric - CAP-REL <1468823+rycks@users.noreply.github.com> Date: Thu, 12 Feb 2026 09:37:04 +0100 Subject: [PATCH 18/39] Refactor GitHub Actions workflow steps Remove depends on gh and only use curl and requests on github api ... --- .github/workflows/pr-18.yaml | 52 +++++++++++++++--------------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/.github/workflows/pr-18.yaml b/.github/workflows/pr-18.yaml index e606aa233e1..39e34ae88f3 100644 --- a/.github/workflows/pr-18.yaml +++ b/.github/workflows/pr-18.yaml @@ -49,23 +49,6 @@ jobs: - 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 - # -> 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 run: | @@ -74,7 +57,7 @@ jobs: echo "Run id: $GITHUB_RUN_ID" echo "Reviewers configured: $REVIEWERS" - # 4) Add the label to the PR (PR events only) + # 3) 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' }} @@ -82,16 +65,23 @@ jobs: GH_TOKEN: ${{ steps.generate-token.outputs.token }} PR_NUMBER: ${{ github.event.pull_request.number }} V18_LABEL: ${{ env.V18_LABEL }} + REPO: ${{ github.repository }} run: | set -euo pipefail echo "Adding label '$V18_LABEL' to PR #${PR_NUMBER}" - if gh pr edit "$PR_NUMBER" --add-label "$V18_LABEL"; then + response=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/labels" \ + -d "{\"labels\": [\"${V18_LABEL}\"]}") + + if [ "$response" -eq 200 ]; then echo "Label added successfully." else - echo "Warning: failed to add label '$V18_LABEL' to PR #${PR_NUMBER}. Continuing." + echo "Warning: failed to add label '$V18_LABEL' to PR #${PR_NUMBER} (HTTP $response). Continuing." fi - # 5) Compute final reviewers list excluding the PR author + # 4) Compute final reviewers list excluding the PR author - name: Compute reviewers (exclude PR author) if: ${{ github.event_name == 'pull_request' }} id: set-reviewers @@ -118,7 +108,7 @@ jobs: echo "author=$AUTHOR" >> $GITHUB_OUTPUT echo "Computed reviewers: ${reviewers_csv:-}" - # 6) Assign reviewers one-by-one with fine-grained error handling + # 5) 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) @@ -128,8 +118,8 @@ jobs: GH_TOKEN: ${{ steps.generate-token.outputs.token }} PR_NUMBER: ${{ github.event.pull_request.number }} REVIEWERS_CSV: ${{ steps.set-reviewers.outputs.reviewers }} + REPO: ${{ github.repository }} run: | - # Note: avoid 'set -e' to handle per-reviewer failures manually set -uo pipefail IFS=',' read -ra TO_ADD <<< "${REVIEWERS_CSV}" SUCCESS=0 @@ -140,26 +130,28 @@ jobs: continue fi echo "Attempting to add reviewer: $r_trimmed" - if gh pr edit "$PR_NUMBER" --add-reviewer "$r_trimmed"; then + response=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${REPO}/pulls/${PR_NUMBER}/requested_reviewers" \ + -d "{\"reviewers\": [\"${r_trimmed}\"]}") + + if [ "$response" -eq 201 ] || [ "$response" -eq 200 ]; 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 + echo "Warning: failed to add reviewer: $r_trimmed (HTTP $response)" 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) + # 6) Push event notice (no PR-specific actions performed) - name: Push event notice if: ${{ github.event_name == 'push' }} run: | From a8e050834312b1c9a5736f550ab012e941e72434 Mon Sep 17 00:00:00 2001 From: ThomasNgr-OpenDSI Date: Thu, 12 Feb 2026 16:30:45 +0100 Subject: [PATCH 19/39] Sec: Can init a page with php content without permission for php content (#37211) edition (reported by phdwg1410) Co-authored-by: Laurent Destailleur --- htdocs/website/class/websitepage.class.php | 12 ++++++++++++ htdocs/website/index.php | 3 +++ 2 files changed, 15 insertions(+) diff --git a/htdocs/website/class/websitepage.class.php b/htdocs/website/class/websitepage.class.php index 40ca4231d7d..0e3c84cbb8e 100644 --- a/htdocs/website/class/websitepage.class.php +++ b/htdocs/website/class/websitepage.class.php @@ -226,6 +226,18 @@ class WebsitePage extends CommonObject // Remove spaces and be sure we have main language only $this->lang = preg_replace('/[_-].*$/', '', trim($this->lang)); // en_US or en-US -> en + // Test if page contains dynamic PHP content + if (!$user->hasRight('website', 'writephp')) { + // Check there is no PHP content into the imported file (must be only HTML + JS) + $phpcontent = dolKeepOnlyPhpCode($this->content); + + if ($phpcontent) { + $this->error = 'Error: you try to create a page with PHP content without having permissions for that.'; + $this->errors = $this->error; + return -1; + } + } + return $this->createCommon($user, $notrigger); } diff --git a/htdocs/website/index.php b/htdocs/website/index.php index c67e80dee29..e8725ad8b56 100644 --- a/htdocs/website/index.php +++ b/htdocs/website/index.php @@ -806,6 +806,7 @@ if ($action == 'addcontainer' && $usercanedit) { // Remove comments $tmp['content'] = removeHtmlComment($tmp['content']); + /* disable this, moved into the create() method // Check there is no PHP content into the imported file (must be only HTML + JS) $phpcontent = dolKeepOnlyPhpCode($tmp['content']); if ($phpcontent) { @@ -813,6 +814,7 @@ if ($action == 'addcontainer' && $usercanedit) { setEventMessages('Error getting '.$urltograb.': file that include PHP content is not allowed', null, 'errors'); $action = 'createcontainer'; } + */ } if (!$error) { @@ -1169,6 +1171,7 @@ if ($action == 'addcontainer' && $usercanedit) { } if (!$error) { + // Create page. This also check there is no PHP content if user has no pemrissions for that. $pageid = $objectpage->create($user); if ($pageid <= 0) { $error++; From 808a3f820c7074ae1b7edbfabc4a1cfb46e1d9e1 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 Feb 2026 00:50:53 +0100 Subject: [PATCH 20/39] Debug v23 --- htdocs/compta/paiement/card.php | 2 +- htdocs/compta/paiement/document.php | 42 +++-------------------------- 2 files changed, 5 insertions(+), 39 deletions(-) diff --git a/htdocs/compta/paiement/card.php b/htdocs/compta/paiement/card.php index b3716d1213a..b3c1ea610db 100644 --- a/htdocs/compta/paiement/card.php +++ b/htdocs/compta/paiement/card.php @@ -312,7 +312,7 @@ dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', ''); print '
'; print '
'; -print ''."\n"; +print '
'."\n"; // Date payment print '
'.$form->editfieldkey("Date", 'datep', $object->date, $object, $user->hasRight('facture', 'paiement')).''; diff --git a/htdocs/compta/paiement/document.php b/htdocs/compta/paiement/document.php index 9a972b83afc..479bb790e4a 100644 --- a/htdocs/compta/paiement/document.php +++ b/htdocs/compta/paiement/document.php @@ -113,48 +113,13 @@ llxHeader('', $title); if ($object->id > 0 && $upload_dir !== null) { $head = payment_prepare_head($object); + print dol_get_fiche_head($head, 'documents', $langs->trans("Payment"), -1, 'payment'); // Supplier order card $linkback = ''.$langs->trans("BackToList").''; - $morehtmlref = '
'; - - // Date of payment - $morehtmlref .= $form->editfieldkey("Date", 'datep', $object->date, $object, (int) ($object->statut == 0 && ($user->hasRight('facture', 'creer'))), 'datehourpicker', '', 0, 3).': '; - $morehtmlref .= $form->editfieldval("Date", 'datep', $object->date, $object, (int) ($object->statut == 0 && ($user->hasRight('facture', 'creer'))), 'datehourpicker', '', null, $langs->trans('PaymentDateUpdateSucceeded')); - - // Payment mode - $morehtmlref .= '
'.$langs->trans('PaymentMode').' : '; - $morehtmlref .= $langs->trans("PaymentType".$object->type_code) != ("PaymentType".$object->type_code) ? $langs->trans("PaymentType".$object->type_code) : $object->type_label; - $morehtmlref .= $object->num_payment ? ' - '.$object->num_payment : ''; - - // Thirdparty - $morehtmlref .= '
'.$langs->trans('ThirdParty').' : '.$object->thirdparty->getNomUrl(1); - - // Amount - $morehtmlref .= '
'.$langs->trans('Amount').' : '. price($object->amount, 0, $langs, 0, 0, -1, $conf->currency); - - $allow_delete = 1; - // Bank account - if (isModEnabled('bank')) { - if ($object->fk_account) { - $bankline = new AccountLine($db); - $bankline->fetch($object->bank_line); - if ($bankline->rappro) { - $allow_delete = 0; - $title_button = dol_escape_htmltag($langs->transnoentitiesnoconv("CantRemoveConciliatedPayment")); - } - - $morehtmlref .= '
'.$langs->trans('BankAccount').' : '; - $accountstatic = new Account($db); - $accountstatic->fetch($bankline->fk_account); - $morehtmlref .= $accountstatic->getNomUrl(1); - - $morehtmlref .= '
'.$langs->trans('BankTransactionLine').' : '; - $morehtmlref .= $bankline->getNomUrl(1, 0, 'showconciliated'); - } - } + $morehtmlref = ''; dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref); @@ -168,7 +133,8 @@ if ($object->id > 0 && $upload_dir !== null) { print '
'; print '
'; - print ''; + print '
'."\n"; + print ''; print ''; print "
'.$langs->trans("NbOfAttachedFiles").''.count($filearray).'
'.$langs->trans("TotalSizeOfAttachedFiles").''.dol_print_size($totalsize, 1, 1).'
\n"; From 1d3cfdb65442c4eaac20aedce9e10262c0edee8c Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 Feb 2026 00:57:14 +0100 Subject: [PATCH 21/39] Debug v23 --- htdocs/takepos/invoice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/takepos/invoice.php b/htdocs/takepos/invoice.php index d52eb4ff32d..c8a7571dcc7 100644 --- a/htdocs/takepos/invoice.php +++ b/htdocs/takepos/invoice.php @@ -341,7 +341,7 @@ if (empty($reshook)) { $payment->paiementid = $paiementid; $payment->paiementcode = $paycode; - $payment->num_payment = $invoice->ref; + $payment->num_payment = ''; if ($pay != "delayed") { $res = $payment->create($user); // This record payment and regenerate the PDF From 96cd5b9afb8b96efd6c912999cd68a6ea9a28924 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 Feb 2026 02:03:38 +0100 Subject: [PATCH 22/39] Debug v23 --- htdocs/core/actions_linkedfiles.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/actions_linkedfiles.inc.php b/htdocs/core/actions_linkedfiles.inc.php index e59fc651416..b02b42e66ba 100644 --- a/htdocs/core/actions_linkedfiles.inc.php +++ b/htdocs/core/actions_linkedfiles.inc.php @@ -304,7 +304,7 @@ if ($action == 'confirm_deletefile' && $confirm == 'yes' && !empty($permissionto $langs->load("errors"); // lang must be loaded because we can't rely on loading during output, we need var substitution to be done now. setEventMessages($langs->trans("ErrorFilenameCantStartWithDot", $filenameto), null, 'errors'); } elseif (!file_exists($destpath)) { - $result = dol_move($srcpath, $destpath, '0', 1, 0, 1, [], $object->entity ?? 0); + $result = dol_move($srcpath, $destpath, '0', 1, 0, 1, [], $object->entity ?? $conf->entity); if ($result) { // Define if we have to generate thumbs or not $generatethumbs = 1; From 1a2813e13ca6a415a970ba863cab57e2ee70d240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20FRANCE?= Date: Mon, 16 Feb 2026 19:53:07 +0100 Subject: [PATCH 23/39] Update supplier_proposal.lang (#37238) --- htdocs/langs/en_US/supplier_proposal.lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/langs/en_US/supplier_proposal.lang b/htdocs/langs/en_US/supplier_proposal.lang index c651d8b0c86..4867c0a5146 100644 --- a/htdocs/langs/en_US/supplier_proposal.lang +++ b/htdocs/langs/en_US/supplier_proposal.lang @@ -41,7 +41,7 @@ SendAskRef=Sending the price request %s SupplierProposalCard=Request card ConfirmDeleteAsk=Are you sure you want to delete this price request %s? ActionsOnSupplierProposal=Events on price request -DocModelAuroreDescription=A complete template for a vendor quotation request (old implementation of Sponge template) +DocModelAuroreDescription=A complete template for a vendor quotation request (old implementation of Zenith template) DocModelZenithDescription=A complete template for a vendor quotation request CommercialAsk=Price request DefaultModelSupplierProposalCreate=Default model creation From 30f7f34708bd0cd7752901994a4a2887ddeaf361 Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Mon, 16 Feb 2026 19:54:09 +0100 Subject: [PATCH 24/39] FIX Societe - Wrong control on update of VAT Intra (#37224) --- htdocs/societe/class/societe.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/societe/class/societe.class.php b/htdocs/societe/class/societe.class.php index 9ffd25427be..a78c66f95c6 100644 --- a/htdocs/societe/class/societe.class.php +++ b/htdocs/societe/class/societe.class.php @@ -8,7 +8,7 @@ * Copyright (C) 2008 Patrick Raguin * Copyright (C) 2010-2018 Juanjo Menent * Copyright (C) 2013 Florian Henry - * Copyright (C) 2013-2023 Alexandre Spangaro + * Copyright (C) 2013-2026 Alexandre Spangaro * Copyright (C) 2013 Peter Fontaine * Copyright (C) 2014-2015 Marcos García * Copyright (C) 2015 Raphaël Doursenaud @@ -1368,7 +1368,7 @@ class Societe extends CommonObject $this->supplier_order_min_amount = price2num($this->supplier_order_min_amount); $this->tva_assuj = trim($this->tva_assuj); - $this->tva_intra = dol_sanitizeFileName($this->tva_intra, ''); + $this->tva_intra = trim($this->tva_intra); $this->vat_reverse_charge = empty($this->vat_reverse_charge) ? '0' : '1'; if (empty($this->status)) { $this->status = 0; From 9f4674806e40950c8c43d92307596d7746b4b6b9 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 Feb 2026 20:00:45 +0100 Subject: [PATCH 25/39] Fix CI --- htdocs/website/class/websitepage.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/website/class/websitepage.class.php b/htdocs/website/class/websitepage.class.php index 4f0e7e855df..9c8deaa2822 100644 --- a/htdocs/website/class/websitepage.class.php +++ b/htdocs/website/class/websitepage.class.php @@ -276,7 +276,7 @@ class WebsitePage extends CommonObject if ($phpcontent) { $this->error = 'Error: you try to create a page with PHP content without having permissions for that.'; - $this->errors = $this->error; + $this->errors[] = $this->error; return -1; } } From 83b137d8540029507476043b56e6f5e17f7ea018 Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Mon, 16 Feb 2026 20:01:27 +0100 Subject: [PATCH 26/39] FIX Luxembourg VAT rate by default (17% since 2024-01-01) (#37255) --- htdocs/install/mysql/data/llx_c_tva.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/install/mysql/data/llx_c_tva.sql b/htdocs/install/mysql/data/llx_c_tva.sql index 88b3afc8d4d..98a109b8fee 100644 --- a/htdocs/install/mysql/data/llx_c_tva.sql +++ b/htdocs/install/mysql/data/llx_c_tva.sql @@ -205,7 +205,7 @@ insert into llx_c_tva(fk_pays,taux,recuperableonly,note,active,entity) values (1 insert into llx_c_tva(fk_pays,taux,recuperableonly,note,active,entity) values (140, '14','0','VAT rate - intermediary',1,__ENTITY__); insert into llx_c_tva(fk_pays,taux,recuperableonly,note,active,entity) values (140, '8','0','VAT rate - reduced', 1,__ENTITY__); insert into llx_c_tva(fk_pays,taux,recuperableonly,note,active,entity) values (140, '3','0','VAT rate - super-reduced', 1,__ENTITY__); -insert into llx_c_tva(fk_pays,taux,recuperableonly,note,active,entity) values (140, '16','0','VAT rate - standard',1,__ENTITY__); +insert into llx_c_tva(fk_pays,taux,recuperableonly,note,active,entity) values (140, '17','0','VAT rate - standard',1,__ENTITY__); -- MALI (id country=147) insert into llx_c_tva(fk_pays,taux,recuperableonly,note,active,entity) values (147, '0','0','VAT rate 0', 1,__ENTITY__); From 7a37ebbeb3afc8ba375c1aa2549bc42bc419ef06 Mon Sep 17 00:00:00 2001 From: Joachim Kueter Date: Mon, 16 Feb 2026 20:03:22 +0100 Subject: [PATCH 27/39] FIX: Clean filenames with double spaces (#37256) * Clean filenames with double spaces If a filename contains double spaces, it can get uploaded in Dolibarr exactly as it is. When listing the file or trying to download it, the dolibarr functions clear double spaces in the filename. In particular the alpha parameter for GETPOST. This results in files being uploaded (e.g. attachments for invoices) than can get listed but remain inaccessible and even cannot get deleted via UI. We either need to fix all these places to accept double spaces or we change a single location, when we sanitize filenames. * removed white space --- htdocs/core/lib/functions.lib.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index d4d4ec2332d..5ac76b1c506 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -1783,6 +1783,7 @@ function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1, $includequotes $tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp); $tmp = preg_replace('/\s+\-$/', '', $tmp); $tmp = str_replace('..', '', $tmp); + $tmp = preg_replace('/\s{2,}/', ' ', $tmp); return $tmp; } @@ -1815,6 +1816,8 @@ function dol_sanitizePathName($str, $newstr = '_', $unaccent = 1) $tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp); $tmp = preg_replace('/\s+\-$/', '', $tmp); $tmp = str_replace('..', '', $tmp); + $tmp = preg_replace('/\s{2,}/', ' ', $tmp); + return $tmp; } From 87fc31fa7cccba7c6ff1999f7b50d6a9fea5c899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charl=C3=A8ne=20Benke?= <1179011+defrance@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:03:27 +0100 Subject: [PATCH 28/39] add company name on dropdown contract list (#37245) --- htdocs/core/class/html.formcontract.class.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/htdocs/core/class/html.formcontract.class.php b/htdocs/core/class/html.formcontract.class.php index 11eca55e4b6..2eb6591b79b 100644 --- a/htdocs/core/class/html.formcontract.class.php +++ b/htdocs/core/class/html.formcontract.class.php @@ -1,5 +1,5 @@ +/* Copyright (C) 2012-2026 Charlene BENKE * Copyright (C) 2025 MDW * * This program is free software; you can redistribute it and/or modify @@ -77,9 +77,10 @@ class FormContract $ret = ''; // Search all contacts - $sql = "SELECT c.rowid, c.ref, c.fk_soc, c.statut,"; + $sql = "SELECT c.rowid, c.ref, c.fk_soc, c.statut, s.nom,"; $sql .= " c.ref_customer, c.ref_supplier"; $sql .= " FROM ".$this->db->prefix()."contrat as c"; + $sql .= " INNER JOIN".$this->db->prefix()."societe as s ON s.rowid = c.fk_soc"; $sql .= " WHERE c.entity = ".$conf->entity; //if ($contratListId) $sql.= " AND c.rowid IN (".$this->db->sanitize($contratListId).")"; if ($socid > 0) { @@ -115,6 +116,7 @@ class FormContract $labeltoshow = dol_trunc($obj->ref, 18); if ($showRef) { + $labeltoshow = $labeltoshow." - ".$obj->nom; if ($obj->ref_customer) { $labeltoshow = $labeltoshow." - ".$obj->ref_customer; } From cc9f0e5bcf9344d21cc645658fc7b3b61334b2d7 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 Feb 2026 20:08:06 +0100 Subject: [PATCH 29/39] Clean dump file --- dev/initdemo/mysqldump_dolibarr_24.0.0.sql | 127 --------------------- 1 file changed, 127 deletions(-) diff --git a/dev/initdemo/mysqldump_dolibarr_24.0.0.sql b/dev/initdemo/mysqldump_dolibarr_24.0.0.sql index d060ffe5c65..51d09ade521 100644 --- a/dev/initdemo/mysqldump_dolibarr_24.0.0.sql +++ b/dev/initdemo/mysqldump_dolibarr_24.0.0.sql @@ -82148,133 +82148,6 @@ INSERT INTO `llx_usergroup_user` VALUES /*!40000 ALTER TABLE `llx_usergroup_user` ENABLE KEYS */; UNLOCK TABLES; --- --- Table structure for table `llx_verifactudolibarr_add_tpls` --- - -DROP TABLE IF EXISTS `llx_verifactudolibarr_add_tpls`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8mb4 */; -CREATE TABLE `llx_verifactudolibarr_add_tpls` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `title` varchar(100) NOT NULL, - `template` varchar(100) NOT NULL, - `status` int(11) NOT NULL DEFAULT 0, - `hash_tmp` varchar(100) NOT NULL, - `tms` datetime NOT NULL, - `content_tmp` longtext NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `llx_verifactudolibarr_add_tpls` --- - -LOCK TABLES `llx_verifactudolibarr_add_tpls` WRITE; -/*!40000 ALTER TABLE `llx_verifactudolibarr_add_tpls` DISABLE KEYS */; -INSERT INTO `llx_verifactudolibarr_add_tpls` VALUES -(1,'Verifactu casa interior','crabe_verifactu_casa_int',1,'1a70a6e54dd7a88e4fa45932ca280b90f349c9b435ce01e53995c7111152ecb7','2025-12-12 11:18:43','\n * Copyright (C) 2005-2012 Regis Houssin \n * Copyright (C) 2008 Raphael Bertrand \n * Copyright (C) 2010-2014 Juanjo Menent \n * Copyright (C) 2012 Christophe Battarel \n * Copyright (C) 2012 Cédric Salvador \n * Copyright (C) 2012-2014 Raphaël Doursenaud \n * Copyright (C) 2015 Marcos García \n * Copyright (C) 2017-2018 Ferran Marcet \n * Copyright (C) 2018-2020 Frédéric France \n * Copyright (C) 2022 Anthony Berton \n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see .\n * or see https://www.gnu.org/\n */\n\n/**\n * \\file htdocs/core/modules/facture/doc/pdf_crabe.modules.php\n * \\ingroup facture\n * \\brief File of class to generate customers invoices from crabe model\n */\n\nrequire_once DOL_DOCUMENT_ROOT.\'/core/modules/facture/modules_facture.php\';\nrequire_once DOL_DOCUMENT_ROOT.\'/product/class/product.class.php\';\nrequire_once DOL_DOCUMENT_ROOT.\'/core/lib/company.lib.php\';\nrequire_once DOL_DOCUMENT_ROOT.\'/core/lib/functions2.lib.php\';\nrequire_once DOL_DOCUMENT_ROOT.\'/core/lib/pdf.lib.php\';\n\n\n/**\n * Class to generate the customer invoice PDF with template Crabe\n */\nclass pdf_crabe_verifactu_casa_int extends ModelePDFFactures\n{\n /**\n * @var DoliDb Database handler\n */\n public $db;\n\n /**\n * @var string model name\n */\n public $name;\n\n /**\n * @var string model description (short text)\n */\n public $description;\n\n /**\n * @var int Save the name of generated file as the main doc when generating a doc with this template\n */\n public $update_main_doc_field;\n\n /**\n * @var string document type\n */\n public $type;\n\n /**\n * Dolibarr version of the loaded document\n * @var string\n */\n public $version = \'dolibarr\';\n\n /**\n * @var int page_largeur\n */\n public $page_largeur;\n\n /**\n * @var int page_hauteur\n */\n public $page_hauteur;\n\n /**\n * @var array format\n */\n public $format;\n\n /**\n * @var int marge_gauche\n */\n public $marge_gauche;\n\n /**\n * @var int marge_droite\n */\n public $marge_droite;\n\n /**\n * @var int marge_haute\n */\n public $marge_haute;\n\n /**\n * @var int marge_basse\n */\n public $marge_basse;\n\n /**\n * Issuer\n * @var Societe Object that emits\n */\n public $emetteur;\n\n /**\n * @var bool Situation invoice type\n */\n public $situationinvoice;\n\n /**\n * @var float X position for the situation progress column\n */\n public $posxprogress;\n\n /**\n * @var int Category of operation\n */\n public $categoryOfOperation = -1; // unknown by default\n\n\n /**\n * Constructor\n *\n * @param DoliDB $db Database handler\n */\n public function __construct($db)\n {\n global $conf, $langs, $mysoc;\n\n // Translations\n $langs->loadLangs(array(\"main\", \"bills\"));\n\n $this->db = $db;\n $this->name = \"Verifactu casa interior\";\n $this->name_verifactu = \"crabe_verifactu_casa_int\";\n $this->description = $langs->trans(\'PDFCrabeDescription\');\n $this->update_main_doc_field = 1; // Save the name of generated file as the main doc when generating a doc with this template\n\n // Dimension page\n $this->type = \'pdf\';\n $formatarray = pdf_getFormat();\n $this->page_largeur = $formatarray[\'width\'];\n $this->page_hauteur = $formatarray[\'height\'];\n $this->format = array($this->page_largeur, $this->page_hauteur);\n $this->marge_gauche = getDolGlobalInt(\'MAIN_PDF_MARGIN_LEFT\', 10);\n $this->marge_droite = getDolGlobalInt(\'MAIN_PDF_MARGIN_RIGHT\', 10);\n $this->marge_haute = getDolGlobalInt(\'MAIN_PDF_MARGIN_TOP\', 10);\n $this->marge_basse = getDolGlobalInt(\'MAIN_PDF_MARGIN_BOTTOM\', 10);\n\n $this->option_logo = 1; // Display logo\n $this->option_tva = 1; // Manage the vat option FACTURE_TVAOPTION\n $this->option_modereg = 1; // Display payment mode\n $this->option_condreg = 1; // Display payment terms\n $this->option_multilang = 1; // Available in several languages\n $this->option_escompte = 1; // Displays if there has been a discount\n $this->option_credit_note = 1; // Support credit notes\n $this->option_freetext = 1; // Support add of a personalised text\n $this->option_draft_watermark = 1; // Support add of a watermark on drafts\n $this->watermark = \'\';\n\n // Get source company\n $this->emetteur = $mysoc;\n if (empty($this->emetteur->country_code)) {\n $this->emetteur->country_code = substr($langs->defaultlang, -2); // By default, if was not defined\n }\n\n // Define position of columns\n $this->posxdesc = $this->marge_gauche + 1;\n if (getDolGlobalInt(\'PRODUCT_USE_UNITS\')) {\n $this->posxtva = 101;\n $this->posxup = 118;\n $this->posxqty = 135;\n $this->posxunit = 151;\n } else {\n $this->posxtva = 106;\n $this->posxup = 122;\n $this->posxqty = 145;\n $this->posxunit = 162;\n }\n $this->posxprogress = 151; // Only displayed for situation invoices\n $this->posxdiscount = 162;\n $this->posxprogress = 174;\n $this->postotalht = 174;\n if (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) || !empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_COLUMN)) {\n $this->posxtva = $this->posxup;\n }\n $this->posxpicture = $this->posxtva - (empty($conf->global->MAIN_DOCUMENTS_WITH_PICTURE_WIDTH) ? 20 : $conf->global->MAIN_DOCUMENTS_WITH_PICTURE_WIDTH); // width of images\n if ($this->page_largeur < 210) { // To work with US executive format\n $this->posxpicture -= 20;\n $this->posxtva -= 20;\n $this->posxup -= 20;\n $this->posxqty -= 20;\n $this->posxunit -= 20;\n $this->posxdiscount -= 20;\n $this->posxprogress -= 20;\n $this->postotalht -= 20;\n }\n\n $this->tva = array();\n $this->tva_array = array();\n $this->localtax1 = array();\n $this->localtax2 = array();\n $this->atleastoneratenotnull = 0;\n $this->atleastonediscount = 0;\n $this->situationinvoice = false;\n }\n\n\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps\n /**\n * Function to build pdf onto disk\n *\n * @param Facture $object Object to generate\n * @param Translate $outputlangs Lang output object\n * @param string $srctemplatepath Full path of source filename for generator using a template file\n * @param int $hidedetails Do not show line details\n * @param int $hidedesc Do not show desc\n * @param int $hideref Do not show ref\n * @return int 1=OK, 0=KO\n */\n public function write_file($object, $outputlangs, $srctemplatepath = \'\', $hidedetails = 0, $hidedesc = 0, $hideref = 0)\n {\n // phpcs:enable\n global $user, $langs, $conf, $mysoc, $hookmanager, $nblines;\n \n $hideref = 1; //Casa interior\n\n dol_syslog(\"write_file outputlangs->defaultlang=\".(is_object($outputlangs) ? $outputlangs->defaultlang : \'null\'));\n\n if (!is_object($outputlangs)) {\n $outputlangs = $langs;\n }\n // For backward compatibility with FPDF, force output charset to ISO, because FPDF expect text to be encoded in ISO\n if (!empty($conf->global->MAIN_USE_FPDF)) {\n $outputlangs->charset_output = \'ISO-8859-1\';\n }\n\n // Load translation files required by the page\n $outputlangs->loadLangs(array(\"main\", \"bills\", \"products\", \"dict\", \"companies\"));\n\n // Show Draft Watermark\n if ($object->statut == $object::STATUS_DRAFT && (!empty($conf->global->FACTURE_DRAFT_WATERMARK))) {\n $this->watermark = $conf->global->FACTURE_DRAFT_WATERMARK;\n }\n\n global $outputlangsbis;\n $outputlangsbis = null;\n if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE) && $outputlangs->defaultlang != $conf->global->PDF_USE_ALSO_LANGUAGE_CODE) {\n $outputlangsbis = new Translate(\'\', $conf);\n $outputlangsbis->setDefaultLang($conf->global->PDF_USE_ALSO_LANGUAGE_CODE);\n $outputlangsbis->loadLangs(array(\"main\", \"bills\", \"products\", \"dict\", \"companies\"));\n }\n\n $nblines = count($object->lines);\n\n // Loop on each lines to detect if there is at least one image to show\n $realpatharray = array();\n if (!empty($conf->global->MAIN_GENERATE_INVOICES_WITH_PICTURE)) {\n for ($i = 0; $i < $nblines; $i++) {\n if (empty($object->lines[$i]->fk_product)) {\n continue;\n }\n\n $objphoto = new Product($this->db);\n $objphoto->fetch($object->lines[$i]->fk_product);\n\n $pdir = get_exdir($object->lines[$i]->fk_product, 2, 0, 0, $objphoto, \'product\').$object->lines[$i]->fk_product.\"/photos/\";\n $dir = $conf->product->dir_output.\'/\'.$pdir;\n\n $realpath = \'\';\n foreach ($objphoto->liste_photos($dir, 1) as $key => $obj) {\n $filename = $obj[\'photo\'];\n //if ($obj[\'photo_vignette\']) $filename=\'thumbs/\'.$obj[\'photo_vignette\'];\n $realpath = $dir.$filename;\n break;\n }\n\n if ($realpath) {\n $realpatharray[$i] = $realpath;\n }\n }\n }\n if (count($realpatharray) == 0) {\n $this->posxpicture = $this->posxtva;\n }\n\n if ($conf->facture->dir_output) {\n $object->fetch_thirdparty();\n\n $deja_regle = $object->getSommePaiement((isModEnabled(\"multicurrency\") && $object->multicurrency_tx != 1) ? 1 : 0);\n $amount_credit_notes_included = $object->getSumCreditNotesUsed((isModEnabled(\"multicurrency\") && $object->multicurrency_tx != 1) ? 1 : 0);\n $amount_deposits_included = $object->getSumDepositsUsed((isModEnabled(\"multicurrency\") && $object->multicurrency_tx != 1) ? 1 : 0);\n\n // Definition of $dir and $file\n if ($object->specimen) {\n $dir = empty($conf->facture->multidir_output[$object->entity]) ? $conf->facture->dir_output : $conf->facture->multidir_output[$object->entity];\n $file = $dir.\"/SPECIMEN.pdf\";\n } else {\n $objectref = dol_sanitizeFileName($object->ref);\n $dir = (empty($conf->facture->multidir_output[$object->entity]) ? $conf->facture->dir_output : $conf->facture->multidir_output[$object->entity]).\"/\".$objectref;\n $file = $dir.\"/\".$objectref.\".pdf\";\n }\n if (!file_exists($dir)) {\n if (dol_mkdir($dir) < 0) {\n $this->error = $langs->transnoentities(\"ErrorCanNotCreateDir\", $dir);\n return 0;\n }\n }\n\n if (file_exists($dir)) {\n // Add pdfgeneration hook\n if (!is_object($hookmanager)) {\n include_once DOL_DOCUMENT_ROOT.\'/core/class/hookmanager.class.php\';\n $hookmanager = new HookManager($this->db);\n }\n $hookmanager->initHooks(array(\'pdfgeneration\'));\n $parameters = array(\'file\'=>$file, \'object\'=>$object, \'outputlangs\'=>$outputlangs);\n global $action;\n $reshook = $hookmanager->executeHooks(\'beforePDFCreation\', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks\n\n // Set nblines with the new facture lines content after hook\n $nblines = count($object->lines);\n $nbpayments = count($object->getListOfPayments());\n\n // Create pdf instance\n $pdf = pdf_getInstance($this->format);\n $default_font_size = pdf_getPDFFontSize($outputlangs); // Must be after pdf_getInstance\n $pdf->SetAutoPageBreak(1, 0);\n\n $heightforinfotot = 50 + (4 * $nbpayments); // Height reserved to output the info and total part and payment part\n if ($heightforinfotot > 220) {\n $heightforinfotot = 220;\n }\n $heightforfreetext = (isset($conf->global->MAIN_PDF_FREETEXT_HEIGHT) ? $conf->global->MAIN_PDF_FREETEXT_HEIGHT : 5); // Height reserved to output the free text on last page\n $heightforfooter = $this->marge_basse + 8; // Height reserved to output the footer (value include bottom margin)\n if (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS)) {\n $heightforfooter += 6;\n }\n\n $heightforqrinvoice_firstpage = 0;\n if (getDolGlobalString(\'INVOICE_ADD_SWISS_QR_CODE\') == \'bottom\') {\n $heightforqrinvoice_firstpage = $this->getHeightForQRInvoice(1, $object, $langs);\n if ($heightforqrinvoice_firstpage > 0) {\n // Shrink infotot to a base 30\n $heightforinfotot = 30 + (4 * $nbpayments); // Height reserved to output the info and total part and payment part\n }\n }\n\n if (class_exists(\'TCPDF\')) {\n $pdf->setPrintHeader(false);\n $pdf->setPrintFooter(false);\n }\n $pdf->SetFont(pdf_getPDFFont($outputlangs));\n\n // Set path to the background PDF File\n if (!empty($conf->global->MAIN_ADD_PDF_BACKGROUND)) {\n $logodir = $conf->mycompany->dir_output;\n if (!empty($conf->mycompany->multidir_output[$object->entity])) {\n $logodir = $conf->mycompany->multidir_output[$object->entity];\n }\n $pagecount = $pdf->setSourceFile($logodir.\'/\'.$conf->global->MAIN_ADD_PDF_BACKGROUND);\n $tplidx = $pdf->importPage(1);\n }\n\n $pdf->Open();\n $pagenb = 0;\n $pdf->SetDrawColor(128, 128, 128);\n\n $pdf->SetTitle($outputlangs->convToOutputCharset($object->ref));\n $pdf->SetSubject($outputlangs->transnoentities(\"PdfInvoiceTitle\"));\n $pdf->SetCreator(\"Dolibarr \".DOL_VERSION);\n $pdf->SetAuthor($mysoc->name.($user->id > 0 ? \' - \'.$outputlangs->convToOutputCharset($user->getFullName($outputlangs)) : \'\'));\n $pdf->SetKeyWords($outputlangs->convToOutputCharset($object->ref).\" \".$outputlangs->transnoentities(\"PdfInvoiceTitle\").\" \".$outputlangs->convToOutputCharset($object->thirdparty->name));\n if (getDolGlobalString(\'MAIN_DISABLE_PDF_COMPRESSION\')) {\n $pdf->SetCompression(false);\n }\n\n // Set certificate\n $cert = empty($user->conf->CERTIFICATE_CRT) ? \'\' : $user->conf->CERTIFICATE_CRT;\n $certprivate = empty($user->conf->CERTIFICATE_CRT_PRIVATE) ? \'\' : $user->conf->CERTIFICATE_CRT_PRIVATE;\n // If user has no certificate, we try to take the company one\n if (!$cert) {\n $cert = empty($conf->global->CERTIFICATE_CRT) ? \'\' : $conf->global->CERTIFICATE_CRT;\n }\n if (!$certprivate) {\n $certprivate = empty($conf->global->CERTIFICATE_CRT_PRIVATE) ? \'\' : $conf->global->CERTIFICATE_CRT_PRIVATE;\n }\n // If a certificate is found\n if ($cert) {\n $info = array(\n \'Name\' => $this->emetteur->name,\n \'Location\' => getCountry($this->emetteur->country_code, 0),\n \'Reason\' => \'INVOICE\',\n \'ContactInfo\' => $this->emetteur->email\n );\n $pdf->setSignature($cert, $certprivate, $this->emetteur->name, \'\', 2, $info);\n }\n\n $pdf->SetMargins($this->marge_gauche, $this->marge_haute, $this->marge_droite); // Left, Top, Right\n\n // Set $this->atleastonediscount if you have at least one discount\n // and determine category of operation\n $categoryOfOperation = 0;\n $nbProduct = 0;\n $nbService = 0;\n for ($i = 0; $i < $nblines; $i++) {\n if ($object->lines[$i]->remise_percent) {\n $this->atleastonediscount++;\n }\n\n // determine category of operation\n if ($categoryOfOperation < 2) {\n $lineProductType = $object->lines[$i]->product_type;\n if ($lineProductType == Product::TYPE_PRODUCT) {\n $nbProduct++;\n } elseif ($lineProductType == Product::TYPE_SERVICE) {\n $nbService++;\n }\n if ($nbProduct > 0 && $nbService > 0) {\n // mixed products and services\n $categoryOfOperation = 2;\n }\n }\n }\n // determine category of operation\n if ($categoryOfOperation <= 0) {\n // only services\n if ($nbProduct == 0 && $nbService > 0) {\n $categoryOfOperation = 1;\n }\n }\n $this->categoryOfOperation = $categoryOfOperation;\n if (empty($this->atleastonediscount)) { // retrieve space not used by discount\n $delta = ($this->posxprogress - $this->posxdiscount);\n $this->posxpicture += $delta;\n $this->posxtva += $delta;\n $this->posxup += $delta;\n $this->posxqty += $delta;\n $this->posxunit += $delta;\n $this->posxdiscount += $delta;\n // post of fields after are not modified, stay at same position\n }\n\n $progress_width = 0;\n // Situation invoice handling\n if ($object->situation_cycle_ref && empty($conf->global->MAIN_PDF_HIDE_SITUATION)) {\n $this->situationinvoice = true;\n $progress_width = 10;\n $this->posxpicture -= $progress_width;\n $this->posxtva -= $progress_width;\n $this->posxup -= $progress_width;\n $this->posxqty -= $progress_width;\n $this->posxunit -= $progress_width;\n $this->posxdiscount -= $progress_width;\n $this->posxprogress -= $progress_width;\n }\n\n // New page\n $pdf->AddPage();\n if (!empty($tplidx)) {\n $pdf->useTemplate($tplidx);\n }\n $pagenb++;\n\n // Output header (logo, ref and address blocks). This is first call for first page.\n $top_shift = $this->_pagehead($pdf, $object, 1, $outputlangs);\n $pdf->SetFont(\'\', \'\', $default_font_size - 1);\n $pdf->MultiCell(0, 3, \'\'); // Set interline to 3\n $pdf->SetTextColor(0, 0, 0);\n\n // $pdf->GetY() here can\'t be used. It is bottom of the second addresse box but first one may be higher\n\n // $tab_top is y where we must continue content (90 = 42 + 48: 42 is height of logo and ref, 48 is address blocks)\n $tab_top = 90 + $top_shift; // top_shift is an addition for linked objects or addons (0 in most cases)\n $tab_top_newpage = (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\') ? 42 + $top_shift : 10);\n\n // You can add more thing under header here, if you increase $extra_under_address_shift too.\n $extra_under_address_shift = 0;\n $qrcodestring = \'\';\n if (!empty($conf->global->INVOICE_ADD_ZATCA_QR_CODE)) {\n $qrcodestring = $object->buildZATCAQRString();\n } elseif (getDolGlobalString(\'INVOICE_ADD_SWISS_QR_CODE\') == \'1\') {\n $qrcodestring = $object->buildSwitzerlandQRString();\n }\n\n if ($qrcodestring) {\n $qrcodecolor = array(\'25\', \'25\', \'25\');\n // set style for QR-code\n $styleQr = array(\n \'border\' => false,\n \'padding\' => 0,\n \'fgcolor\' => $qrcodecolor,\n \'bgcolor\' => false, //array(255,255,255)\n \'module_width\' => 1, // width of a single module in points\n \'module_height\' => 1 // height of a single module in points\n );\n $pdf->write2DBarcode($qrcodestring, \'QRCODE,M\', $this->marge_gauche, $tab_top - 5, 25, 25, $styleQr, \'N\');\n $extra_under_address_shift += 25;\n }\n\n // Call hook printUnderHeaderPDFline\n $parameters = array(\n \'object\' => $object,\n \'i\' => $i,\n \'pdf\' =>& $pdf,\n \'outputlangs\' => $outputlangs,\n \'hidedetails\' => $hidedetails\n );\n $reshook = $hookmanager->executeHooks(\'printUnderHeaderPDFline\', $parameters, $this); // Note that $object may have been modified by hook\n if (!empty($hookmanager->resArray[\'extra_under_address_shift\'])) {\n $extra_under_address_shift += $hookmanager->resArray[\'extra_under_header_shift\'];\n }\n\n $tab_top += $extra_under_address_shift;\n $tab_top_newpage += 0;\n\n // Incoterm\n $height_incoterms = 0;\n if (isModEnabled(\'incoterm\')) {\n $desc_incoterms = $object->getIncotermsForPDF();\n if ($desc_incoterms) {\n $tab_top -= 2;\n\n $pdf->SetFont(\'\', \'\', $default_font_size - 1);\n $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top - 1, dol_htmlentitiesbr($desc_incoterms), 0, 1);\n $nexY = $pdf->GetY();\n $height_incoterms = $nexY - $tab_top;\n\n // Rect takes a length in 3rd parameter\n $pdf->SetDrawColor(192, 192, 192);\n $pdf->Rect($this->marge_gauche, $tab_top - 1, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $height_incoterms + 1);\n\n $tab_top = $nexY + 6;\n }\n }\n\n // Display notes\n $notetoshow = empty($object->note_public) ? \'\' : $object->note_public;\n if (!empty($conf->global->MAIN_ADD_SALE_REP_SIGNATURE_IN_NOTE)) {\n // Get first sale rep\n if (is_object($object->thirdparty)) {\n $salereparray = $object->thirdparty->getSalesRepresentatives($user);\n $salerepobj = new User($this->db);\n $salerepobj->fetch($salereparray[0][\'id\']);\n if (!empty($salerepobj->signature)) {\n $notetoshow = dol_concatdesc($notetoshow, $salerepobj->signature);\n }\n }\n }\n // Extrafields in note\n $extranote = $this->getExtrafieldsInHtml($object, $outputlangs);\n if (!empty($extranote)) {\n $notetoshow = dol_concatdesc($notetoshow, $extranote);\n }\n if ($notetoshow) {\n $tab_top -= 2;\n\n $substitutionarray = pdf_getSubstitutionArray($outputlangs, null, $object);\n complete_substitutions_array($substitutionarray, $outputlangs, $object);\n\n $notetoshow = make_substitutions($notetoshow, $substitutionarray, $outputlangs);\n $notetoshow = convertBackOfficeMediasLinksToPublicLinks($notetoshow);\n\n $pdf->SetFont(\'\', \'\', $default_font_size - 1);\n $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top - 1, dol_htmlentitiesbr($notetoshow), 0, 1);\n $nexY = $pdf->GetY();\n $height_note = $nexY - $tab_top;\n\n // Rect takes a length in 3rd parameter\n $pdf->SetDrawColor(192, 192, 192);\n $pdf->Rect($this->marge_gauche, $tab_top - 1, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $height_note + 1);\n\n $tab_top = $nexY + 6;\n }\n\n $iniY = $tab_top + 7;\n $curY = $tab_top + 7;\n $nexY = $tab_top + 7;\n\n // Loop on each lines\n for ($i = 0; $i < $nblines; $i++) {\n $curY = $nexY;\n $pdf->SetFont(\'\', \'\', $default_font_size - 1); // Into loop to work with multipage\n $pdf->SetTextColor(0, 0, 0);\n\n // Define size of image if we need it\n $imglinesize = array();\n if (!empty($realpatharray[$i])) {\n $imglinesize = pdf_getSizeForImage($realpatharray[$i]);\n }\n\n $pdf->setTopMargin($tab_top_newpage);\n $page_bottom_margin = $heightforfooter + $heightforfreetext + $heightforinfotot + $this->getHeightForQRInvoice($pdf->getPage(), $object, $langs);\n $pdf->setPageOrientation(\'\', 1, $page_bottom_margin);\n $pageposbefore = $pdf->getPage();\n\n $showpricebeforepagebreak = 1;\n $posYAfterImage = 0;\n $posYAfterDescription = 0;\n\n // We start with Photo of product line\n if (isset($imglinesize[\'width\']) && isset($imglinesize[\'height\']) && ($curY + $imglinesize[\'height\']) > ($this->page_hauteur - $page_bottom_margin)) { // If photo too high, we moved completely on new page\n $pdf->AddPage(\'\', \'\', true);\n if (!empty($tplidx)) {\n $pdf->useTemplate($tplidx);\n }\n if (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\')) {\n $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs);\n $tab_top_newpage = (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\') ? 42 + $top_shift : 10);\n }\n $pdf->setPage($pageposbefore + 1);\n\n $curY = $tab_top_newpage;\n\n // Allows data in the first page if description is long enough to break in multiples pages\n if (!empty($conf->global->MAIN_PDF_DATA_ON_FIRST_PAGE)) {\n $showpricebeforepagebreak = 1;\n } else {\n $showpricebeforepagebreak = 0;\n }\n }\n\n if (isset($imglinesize[\'width\']) && isset($imglinesize[\'height\'])) {\n $curX = $this->posxpicture - 1;\n $pdf->Image($realpatharray[$i], $curX + (($this->posxtva - $this->posxpicture - $imglinesize[\'width\']) / 2), $curY, $imglinesize[\'width\'], $imglinesize[\'height\'], \'\', \'\', \'\', 2, 300); // Use 300 dpi\n // $pdf->Image does not increase value return by getY, so we save it manually\n $posYAfterImage = $curY + $imglinesize[\'height\'];\n }\n\n // Description of product line\n $curX = $this->posxdesc - 1;\n\n $pdf->startTransaction();\n $this->pdf_writelinedesc($pdf, $object, $i, $outputlangs, $this->posxpicture - $curX - $progress_width, 3, $curX, $curY, $hideref, $hidedesc);\n $pageposafter = $pdf->getPage();\n if ($pageposafter > $pageposbefore) { // There is a pagebreak\n $pdf->rollbackTransaction(true);\n $pageposafter = $pageposbefore;\n //print $pageposafter.\'-\'.$pageposbefore;exit;\n $pdf->setPageOrientation(\'\', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it.\n $this->pdf_writelinedesc($pdf, $object, $i, $outputlangs, $this->posxpicture - $curX - $progress_width, 3, $curX, $curY, $hideref, $hidedesc);\n $pageposafter = $pdf->getPage();\n $posyafter = $pdf->GetY();\n //var_dump($posyafter); var_dump(($this->page_hauteur - ($heightforfooter+$heightforfreetext+$heightforinfotot))); exit;\n if ($posyafter > ($this->page_hauteur - $page_bottom_margin)) { // There is no space left for total+free text\n if ($i == ($nblines - 1)) { // No more lines, and no space left to show total, so we create a new page\n $pdf->AddPage(\'\', \'\', true);\n if (!empty($tplidx)) {\n $pdf->useTemplate($tplidx);\n }\n if (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\')) {\n $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs);\n $tab_top_newpage = (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\') ? 42 + $top_shift : 10);\n }\n $pdf->setPage($pageposafter + 1);\n }\n } else {\n // We found a page break\n\n // Allows data in the first page if description is long enough to break in multiples pages\n if (!empty($conf->global->MAIN_PDF_DATA_ON_FIRST_PAGE)) {\n $showpricebeforepagebreak = 1;\n } else {\n $showpricebeforepagebreak = 0;\n }\n }\n } else // No pagebreak\n {\n $pdf->commitTransaction();\n }\n $posYAfterDescription = $pdf->GetY();\n\n $nexY = $pdf->GetY();\n $pageposafter = $pdf->getPage();\n $pdf->setPage($pageposbefore);\n $pdf->setTopMargin($this->marge_haute);\n $pdf->setPageOrientation(\'\', 1, 0); // The only function to edit the bottom margin of current page to set it.\n\n // We suppose that a too long description or photo were moved completely on next page\n if ($pageposafter > $pageposbefore && empty($showpricebeforepagebreak)) {\n $pdf->setPage($pageposafter);\n $curY = $tab_top_newpage;\n }\n\n $pdf->SetFont(\'\', \'\', $default_font_size - 1); // We reposition the default font\n\n // VAT Rate\n if (empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) && empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_COLUMN)) {\n $vat_rate = pdf_getlinevatrate($object, $i, $outputlangs, $hidedetails);\n $pdf->SetXY($this->posxtva - 5, $curY);\n $pdf->MultiCell($this->posxup - $this->posxtva + 4, 3, $vat_rate, 0, \'R\');\n }\n\n // Unit price before discount\n $up_excl_tax = pdf_getlineupexcltax($object, $i, $outputlangs, $hidedetails);\n $pdf->SetXY($this->posxup, $curY);\n $pdf->MultiCell($this->posxqty - $this->posxup - 0.8, 3, $up_excl_tax, 0, \'R\', 0);\n\n // Quantity\n $qty = pdf_getlineqty($object, $i, $outputlangs, $hidedetails);\n $pdf->SetXY($this->posxqty, $curY);\n $pdf->MultiCell($this->posxunit - $this->posxqty - 0.8, 4, $qty, 0, \'R\'); // Enough for 6 chars\n\n // Unit\n if (getDolGlobalInt(\'PRODUCT_USE_UNITS\')) {\n $unit = pdf_getlineunit($object, $i, $outputlangs, $hidedetails, $hookmanager);\n $pdf->SetXY($this->posxunit, $curY);\n $pdf->MultiCell($this->posxdiscount - $this->posxunit - 0.8, 4, $unit, 0, \'L\');\n }\n\n // Discount on line\n if ($object->lines[$i]->remise_percent) {\n $pdf->SetXY($this->posxdiscount - 2, $curY);\n $remise_percent = pdf_getlineremisepercent($object, $i, $outputlangs, $hidedetails);\n $pdf->MultiCell($this->posxprogress - $this->posxdiscount + 2, 3, $remise_percent, 0, \'R\');\n }\n\n // Situation progress\n if ($this->situationinvoice) {\n $progress = pdf_getlineprogress($object, $i, $outputlangs, $hidedetails);\n $pdf->SetXY($this->posxprogress, $curY);\n $pdf->MultiCell($this->postotalht - $this->posxprogress + 1, 3, $progress, 0, \'R\');\n }\n\n // Total HT line\n $total_excl_tax = pdf_getlinetotalexcltax($object, $i, $outputlangs, $hidedetails);\n $pdf->SetXY($this->postotalht, $curY);\n $pdf->MultiCell($this->page_largeur - $this->marge_droite - $this->postotalht, 3, $total_excl_tax, 0, \'R\', 0);\n\n\n $sign = 1;\n if (isset($object->type) && $object->type == 2 && !empty($conf->global->INVOICE_POSITIVE_CREDIT_NOTE)) {\n $sign = -1;\n }\n // Collection of totals by value of VAT in $this->tva[\"taux\"]=total_tva\n $prev_progress = $object->lines[$i]->get_prev_progress($object->id);\n if ($prev_progress > 0 && !empty($object->lines[$i]->situation_percent)) { // Compute progress from previous situation\n if (isModEnabled(\"multicurrency\") && $object->multicurrency_tx != 1) {\n $tvaligne = $sign * $object->lines[$i]->multicurrency_total_tva * ($object->lines[$i]->situation_percent - $prev_progress) / $object->lines[$i]->situation_percent;\n } else {\n $tvaligne = $sign * $object->lines[$i]->total_tva * ($object->lines[$i]->situation_percent - $prev_progress) / $object->lines[$i]->situation_percent;\n }\n } else {\n if (isModEnabled(\"multicurrency\") && $object->multicurrency_tx != 1) {\n $tvaligne = $sign * $object->lines[$i]->multicurrency_total_tva;\n } else {\n $tvaligne = $sign * $object->lines[$i]->total_tva;\n }\n }\n\n $localtax1ligne = $object->lines[$i]->total_localtax1;\n $localtax2ligne = $object->lines[$i]->total_localtax2;\n $localtax1_rate = $object->lines[$i]->localtax1_tx;\n $localtax2_rate = $object->lines[$i]->localtax2_tx;\n $localtax1_type = $object->lines[$i]->localtax1_type;\n $localtax2_type = $object->lines[$i]->localtax2_type;\n\n // TODO remise_percent is an obsolete field for object parent\n /*if ($object->remise_percent) {\n $tvaligne -= ($tvaligne * $object->remise_percent) / 100;\n }\n if ($object->remise_percent) {\n $localtax1ligne -= ($localtax1ligne * $object->remise_percent) / 100;\n }\n if ($object->remise_percent) {\n $localtax2ligne -= ($localtax2ligne * $object->remise_percent) / 100;\n }*/\n\n $vatrate = (string) $object->lines[$i]->tva_tx;\n\n // Retrieve type from database for backward compatibility with old records\n if ((!isset($localtax1_type) || $localtax1_type == \'\' || !isset($localtax2_type) || $localtax2_type == \'\') // if tax type not defined\n && (!empty($localtax1_rate) || !empty($localtax2_rate))) { // and there is local tax\n $localtaxtmp_array = getLocalTaxesFromRate($vatrate, 0, $object->thirdparty, $mysoc);\n $localtax1_type = isset($localtaxtmp_array[0]) ? $localtaxtmp_array[0] : \'\';\n $localtax2_type = isset($localtaxtmp_array[2]) ? $localtaxtmp_array[2] : \'\';\n }\n\n // retrieve global local tax\n if ($localtax1_type && $localtax1ligne != 0) {\n if (empty($this->localtax1[$localtax1_type][$localtax1_rate])) {\n $this->localtax1[$localtax1_type][$localtax1_rate] = $localtax1ligne;\n } else {\n $this->localtax1[$localtax1_type][$localtax1_rate] += $localtax1ligne;\n }\n }\n if ($localtax2_type && $localtax2ligne != 0) {\n if (empty($this->localtax2[$localtax2_type][$localtax2_rate])) {\n $this->localtax2[$localtax2_type][$localtax2_rate] = $localtax2ligne;\n } else {\n $this->localtax2[$localtax2_type][$localtax2_rate] += $localtax2ligne;\n }\n }\n\n if (($object->lines[$i]->info_bits & 0x01) == 0x01) {\n $vatrate .= \'*\';\n }\n\n // Fill $this->tva and $this->tva_array\n if (!isset($this->tva[$vatrate])) {\n $this->tva[$vatrate] = 0;\n }\n $this->tva[$vatrate] += $tvaligne; // ->tva is abandonned, we use now ->tva_array that is more complete\n $vatcode = $object->lines[$i]->vat_src_code;\n if (empty($this->tva_array[$vatrate.($vatcode ? \' (\'.$vatcode.\')\' : \'\')][\'amount\'])) {\n $this->tva_array[$vatrate.($vatcode ? \' (\'.$vatcode.\')\' : \'\')][\'amount\'] = 0;\n }\n $this->tva_array[$vatrate.($vatcode ? \' (\'.$vatcode.\')\' : \'\')] = array(\'vatrate\'=>$vatrate, \'vatcode\'=>$vatcode, \'amount\'=> $this->tva_array[$vatrate.($vatcode ? \' (\'.$vatcode.\')\' : \'\')][\'amount\'] + $tvaligne);\n\n if ($posYAfterImage > $posYAfterDescription) {\n $nexY = $posYAfterImage;\n }\n\n // Add line\n if (!empty($conf->global->MAIN_PDF_DASH_BETWEEN_LINES) && $i < ($nblines - 1)) {\n $pdf->setPage($pageposafter);\n $pdf->SetLineStyle(array(\'dash\'=>\'1,1\', \'color\'=>array(80, 80, 80)));\n //$pdf->SetDrawColor(190,190,200);\n $pdf->line($this->marge_gauche, $nexY + 1, $this->page_largeur - $this->marge_droite, $nexY + 1);\n $pdf->SetLineStyle(array(\'dash\'=>0));\n }\n\n $nexY += 2; // Add space between lines\n\n // Detect if some page were added automatically and output _tableau for past pages\n while ($pagenb < $pageposafter) {\n $pdf->setPage($pagenb);\n if ($pagenb == 1) {\n $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter - $heightforqrinvoice_firstpage, 0, $outputlangs, 0, 1, $object->multicurrency_code);\n } else {\n $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforfooter, 0, $outputlangs, 1, 1, $object->multicurrency_code);\n }\n $this->_pagefoot($pdf, $object, $outputlangs, 1, $this->getHeightForQRInvoice($pagenb, $object, $langs));\n $pagenb++;\n $pdf->setPage($pagenb);\n $pdf->setPageOrientation(\'\', 1, 0); // The only function to edit the bottom margin of current page to set it.\n if (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\')) {\n $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs);\n $tab_top_newpage = (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\') ? 42 + $top_shift : 10);\n }\n if (!empty($tplidx)) {\n $pdf->useTemplate($tplidx);\n }\n }\n if (isset($object->lines[$i + 1]->pagebreak) && $object->lines[$i + 1]->pagebreak) {\n if ($pagenb == 1) {\n $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter - $heightforqrinvoice_firstpage, 0, $outputlangs, 0, 1, $object->multicurrency_code);\n } else {\n $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforfooter, 0, $outputlangs, 1, 1, $object->multicurrency_code);\n }\n $this->_pagefoot($pdf, $object, $outputlangs, 1, $this->getHeightForQRInvoice($pagenb, $object, $langs));\n // New page\n $pdf->AddPage();\n if (!empty($tplidx)) {\n $pdf->useTemplate($tplidx);\n }\n $pagenb++;\n if (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\')) {\n $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs);\n $tab_top_newpage = (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\') ? 42 + $top_shift : 10);\n }\n }\n }\n\n // Show square\n if ($pagenb == 1) {\n $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforinfotot - $heightforfreetext - $heightforfooter - $heightforqrinvoice_firstpage, 0, $outputlangs, 0, 0, $object->multicurrency_code);\n $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforfooter - $heightforqrinvoice_firstpage + 1;\n } else {\n $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforinfotot - $heightforfreetext - $heightforfooter, 0, $outputlangs, 1, 0, $object->multicurrency_code);\n $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforfooter + 1;\n }\n dol_syslog(\"bottomlasttab=\".$bottomlasttab.\" this->page_hauteur=\".$this->page_hauteur.\" heightforinfotot=\".$heightforinfotot.\" heightforfreetext=\".$heightforfreetext.\" heightforfooter=\".$heightforfooter);\n\n // Display info area\n $posy = $this->_tableau_info($pdf, $object, $bottomlasttab, $outputlangs, $outputlangsbis);\n\n // Display total area\n $posy = $this->_tableau_tot($pdf, $object, $deja_regle, $bottomlasttab, $outputlangs, $outputlangsbis);\n\n // Display Payments area\n if (($deja_regle || $amount_credit_notes_included || $amount_deposits_included) && empty($conf->global->INVOICE_NO_PAYMENT_DETAILS)) {\n $posy = $this->_tableau_versements($pdf, $object, $posy, $outputlangs, $heightforfooter);\n }\n\n // Pagefoot\n $this->_pagefoot($pdf, $object, $outputlangs, 0, $this->getHeightForQRInvoice($pdf->getPage(), $object, $langs));\n if (method_exists($pdf, \'AliasNbPages\')) {\n $pdf->AliasNbPages();\n }\n\n if (getDolGlobalString(\'INVOICE_ADD_SWISS_QR_CODE\') == \'bottom\') {\n $result = $this->addBottomQRInvoice($pdf, $object, $outputlangs);\n if (!$result) {\n $pdf->Close();\n return 0;\n }\n }\n $pdf->Close();\n\n $pdf->Output($file, \'F\');\n\n // Add pdfgeneration hook\n $hookmanager->initHooks(array(\'pdfgeneration\'));\n $parameters = array(\'file\'=>$file, \'object\'=>$object, \'outputlangs\'=>$outputlangs);\n global $action;\n $reshook = $hookmanager->executeHooks(\'afterPDFCreation\', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks\n if ($reshook < 0) {\n $this->error = $hookmanager->error;\n $this->errors = $hookmanager->errors;\n }\n\n dolChmod($file);\n\n $this->result = array(\'fullpath\'=>$file);\n\n return 1; // No error\n } else {\n $this->error = $langs->transnoentities(\"ErrorCanNotCreateDir\", $dir);\n return 0;\n }\n } else {\n $this->error = $langs->transnoentities(\"ErrorConstantNotDefined\", \"FAC_OUTPUTDIR\");\n return 0;\n }\n }\n\n\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore\n /**\n * Show payments table\n *\n * @param TCPDF $pdf Object PDF\n * @param Facture $object Object invoice\n * @param int $posy Position y in PDF\n * @param Translate $outputlangs Object langs for output\n * @param int $heightforfooter Height for footer\n * @return int <0 if KO, >0 if OK\n */\n protected function _tableau_versements(&$pdf, $object, $posy, $outputlangs, $heightforfooter = 0)\n {\n // phpcs:enable\n global $conf;\n\n $sign = 1;\n if ($object->type == 2 && !empty($conf->global->INVOICE_POSITIVE_CREDIT_NOTE)) {\n $sign = -1;\n }\n\n $current_page = $pdf->getPage();\n $tab3_posx = 120;\n $tab3_top = $posy + 8;\n $tab3_width = 80;\n $tab3_height = 4;\n if ($this->page_largeur < 210) { // To work with US executive format\n $tab3_posx -= 15;\n }\n\n $default_font_size = pdf_getPDFFontSize($outputlangs);\n\n $this->_tableau_versements_header($pdf, $object, $outputlangs, $default_font_size, $tab3_posx, $tab3_top, $tab3_width, $tab3_height);\n\n $y = 0;\n\n $pdf->SetFont(\'\', \'\', $default_font_size - 4);\n\n\n // Loop on each discount available (deposits and credit notes and excess of payment included)\n $sql = \"SELECT re.rowid, re.amount_ht, re.multicurrency_amount_ht, re.amount_tva, re.multicurrency_amount_tva, re.amount_ttc, re.multicurrency_amount_ttc,\";\n $sql .= \" re.description, re.fk_facture_source,\";\n $sql .= \" f.type, f.datef\";\n $sql .= \" FROM \".MAIN_DB_PREFIX.\"societe_remise_except as re, \".MAIN_DB_PREFIX.\"facture as f\";\n $sql .= \" WHERE re.fk_facture_source = f.rowid AND re.fk_facture = \".((int) $object->id);\n $resql = $this->db->query($sql);\n if ($resql) {\n $num = $this->db->num_rows($resql);\n $i = 0;\n $invoice = new Facture($this->db);\n while ($i < $num) {\n $y += 3;\n if ($tab3_top + $y >= ($this->page_hauteur - $heightforfooter)) {\n $y = 0;\n $current_page++;\n $pdf->AddPage(\'\', \'\', true);\n if (!empty($tplidx)) {\n $pdf->useTemplate($tplidx);\n }\n if (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\')) {\n $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs);\n $tab_top_newpage = (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\') ? 42 + $top_shift : 10);\n }\n $pdf->setPage($current_page);\n $this->_tableau_versements_header($pdf, $object, $outputlangs, $default_font_size, $tab3_posx, $tab3_top + $y - 3, $tab3_width, $tab3_height);\n }\n\n $obj = $this->db->fetch_object($resql);\n\n if ($obj->type == 2) {\n $text = $outputlangs->transnoentities(\"CreditNote\");\n } elseif ($obj->type == 3) {\n $text = $outputlangs->transnoentities(\"Deposit\");\n } elseif ($obj->type == 0) {\n $text = $outputlangs->transnoentities(\"ExcessReceived\");\n } else {\n $text = $outputlangs->transnoentities(\"UnknownType\");\n }\n\n $invoice->fetch($obj->fk_facture_source);\n\n $pdf->SetXY($tab3_posx, $tab3_top + $y);\n $pdf->MultiCell(20, 3, dol_print_date($this->db->jdate($obj->datef), \'day\', false, $outputlangs, true), 0, \'L\', 0);\n $pdf->SetXY($tab3_posx + 21, $tab3_top + $y);\n $pdf->MultiCell(20, 3, price((isModEnabled(\"multicurrency\") && $object->multicurrency_tx != 1) ? $obj->multicurrency_amount_ttc : $obj->amount_ttc, 0, $outputlangs), 0, \'L\', 0);\n $pdf->SetXY($tab3_posx + 40, $tab3_top + $y);\n $pdf->MultiCell(20, 3, $text, 0, \'L\', 0);\n $pdf->SetXY($tab3_posx + 58, $tab3_top + $y);\n $pdf->MultiCell(20, 3, $invoice->ref, 0, \'L\', 0);\n\n $pdf->line($tab3_posx, $tab3_top + $y + 3, $tab3_posx + $tab3_width, $tab3_top + $y + 3);\n\n $i++;\n }\n } else {\n $this->error = $this->db->lasterror();\n return -1;\n }\n\n // Loop on each payment\n // TODO Call getListOfPaymentsgetListOfPayments instead of hard coded sql\n $sql = \"SELECT p.datep as date, p.fk_paiement, p.num_paiement as num, pf.amount as amount, pf.multicurrency_amount,\";\n $sql .= \" cp.code\";\n $sql .= \" FROM \".MAIN_DB_PREFIX.\"paiement_facture as pf, \".MAIN_DB_PREFIX.\"paiement as p\";\n $sql .= \" LEFT JOIN \".MAIN_DB_PREFIX.\"c_paiement as cp ON p.fk_paiement = cp.id\";\n $sql .= \" WHERE pf.fk_paiement = p.rowid AND pf.fk_facture = \".((int) $object->id);\n //$sql.= \" WHERE pf.fk_paiement = p.rowid AND pf.fk_facture = 1\";\n $sql .= \" ORDER BY p.datep\";\n\n $resql = $this->db->query($sql);\n if ($resql) {\n $num = $this->db->num_rows($resql);\n $i = 0;\n while ($i < $num) {\n $y += 3;\n if ($tab3_top + $y >= ($this->page_hauteur - $heightforfooter)) {\n $y = 0;\n $current_page++;\n $pdf->AddPage(\'\', \'\', true);\n if (!empty($tplidx)) {\n $pdf->useTemplate($tplidx);\n }\n if (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\')) {\n $top_shift = $this->_pagehead($pdf, $object, 0, $outputlangs);\n $tab_top_newpage = (!getDolGlobalInt(\'MAIN_PDF_DONOTREPEAT_HEAD\') ? 42 + $top_shift : 10);\n }\n $pdf->setPage($current_page);\n $this->_tableau_versements_header($pdf, $object, $outputlangs, $default_font_size, $tab3_posx, $tab3_top + $y - 3, $tab3_width, $tab3_height);\n }\n\n $row = $this->db->fetch_object($resql);\n\n $pdf->SetXY($tab3_posx, $tab3_top + $y);\n $pdf->MultiCell(20, 3, dol_print_date($this->db->jdate($row->date), \'day\', false, $outputlangs, true), 0, \'L\', 0);\n $pdf->SetXY($tab3_posx + 21, $tab3_top + $y);\n $pdf->MultiCell(20, 3, price($sign * ((isModEnabled(\"multicurrency\") && $object->multicurrency_tx != 1) ? $row->multicurrency_amount : $row->amount), 0, $outputlangs), 0, \'L\', 0);\n $pdf->SetXY($tab3_posx + 40, $tab3_top + $y);\n $oper = $outputlangs->transnoentitiesnoconv(\"PaymentTypeShort\".$row->code);\n\n $pdf->MultiCell(20, 3, $oper, 0, \'L\', 0);\n $pdf->SetXY($tab3_posx + 58, $tab3_top + $y);\n $pdf->MultiCell(30, 3, $row->num, 0, \'L\', 0);\n\n $pdf->line($tab3_posx, $tab3_top + $y + 3, $tab3_posx + $tab3_width, $tab3_top + $y + 3);\n\n $i++;\n }\n\n return $tab3_top + $y + 3;\n } else {\n $this->error = $this->db->lasterror();\n return -1;\n }\n }\n\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore\n /**\n * Function _tableau_versements_header\n *\n * @param TCPDF $pdf Object PDF\n * @param Facture $object Object invoice\n * @param Translate $outputlangs Object langs for output\n * @param int $default_font_size Font size\n * @param int $tab3_posx pos x\n * @param int $tab3_top pos y\n * @param int $tab3_width width\n * @param int $tab3_height height\n * @return void\n */\n protected function _tableau_versements_header($pdf, $object, $outputlangs, $default_font_size, $tab3_posx, $tab3_top, $tab3_width, $tab3_height)\n {\n // phpcs:enable\n $title = $outputlangs->transnoentities(\"PaymentsAlreadyDone\");\n if ($object->type == 2) {\n $title = $outputlangs->transnoentities(\"PaymentsBackAlreadyDone\");\n }\n\n $pdf->SetFont(\'\', \'\', $default_font_size - 3);\n $pdf->SetXY($tab3_posx, $tab3_top - 4);\n $pdf->MultiCell(60, 3, $title, 0, \'L\', 0);\n\n $pdf->line($tab3_posx, $tab3_top, $tab3_posx + $tab3_width, $tab3_top);\n\n $pdf->SetFont(\'\', \'\', $default_font_size - 4);\n $pdf->SetXY($tab3_posx, $tab3_top);\n $pdf->MultiCell(20, 3, $outputlangs->transnoentities(\"Payment\"), 0, \'L\', 0);\n $pdf->SetXY($tab3_posx + 21, $tab3_top);\n $pdf->MultiCell(20, 3, $outputlangs->transnoentities(\"Amount\"), 0, \'L\', 0);\n $pdf->SetXY($tab3_posx + 40, $tab3_top);\n $pdf->MultiCell(20, 3, $outputlangs->transnoentities(\"Type\"), 0, \'L\', 0);\n $pdf->SetXY($tab3_posx + 58, $tab3_top);\n $pdf->MultiCell(20, 3, $outputlangs->transnoentities(\"Num\"), 0, \'L\', 0);\n\n $pdf->line($tab3_posx, $tab3_top - 1 + $tab3_height, $tab3_posx + $tab3_width, $tab3_top - 1 + $tab3_height);\n }\n\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore\n /**\n * Show miscellaneous information (payment mode, payment term, ...)\n *\n * @param TCPDF $pdf Object PDF\n * @param Facture $object Object to show\n * @param int $posy Y\n * @param Translate $outputlangs Langs object\n * @param Translate $outputlangsbis Object lang for output bis\n * @return int Pos y\n */\n protected function _tableau_info(&$pdf, $object, $posy, $outputlangs, $outputlangsbis)\n {\n // phpcs:enable\n global $conf, $mysoc;\n\n $default_font_size = pdf_getPDFFontSize($outputlangs);\n\n $pdf->SetFont(\'\', \'\', $default_font_size - 1);\n\n // If France, show VAT mention if not applicable\n if ($this->emetteur->country_code == \'FR\' && empty($mysoc->tva_assuj)) {\n $pdf->SetFont(\'\', \'B\', $default_font_size - 2);\n $pdf->SetXY($this->marge_gauche, $posy);\n if ($mysoc->forme_juridique_code == 92) {\n $pdf->MultiCell(100, 3, $outputlangs->transnoentities(\"VATIsNotUsedForInvoiceAsso\"), 0, \'L\', 0);\n } else {\n $pdf->MultiCell(100, 3, $outputlangs->transnoentities(\"VATIsNotUsedForInvoice\"), 0, \'L\', 0);\n }\n\n $posy = $pdf->GetY() + 4;\n }\n\n $posxval = 52;\n $posxend = 110; // End of x for text on left side\n if ($this->page_largeur < 210) { // To work with US executive format\n $posxend -= 10;\n }\n\n // Show payments conditions\n if ($object->type != 2 && ($object->cond_reglement_code || $object->cond_reglement)) {\n $pdf->SetFont(\'\', \'B\', $default_font_size - 2);\n $pdf->SetXY($this->marge_gauche, $posy);\n $titre = $outputlangs->transnoentities(\"PaymentConditions\").\':\';\n $pdf->MultiCell(43, 4, $titre, 0, \'L\');\n\n $pdf->SetFont(\'\', \'\', $default_font_size - 2);\n $pdf->SetXY($posxval, $posy);\n $lib_condition_paiement = $outputlangs->transnoentities(\"PaymentCondition\".$object->cond_reglement_code) != (\'PaymentCondition\'.$object->cond_reglement_code) ? $outputlangs->transnoentities(\"PaymentCondition\".$object->cond_reglement_code) : $outputlangs->convToOutputCharset($object->cond_reglement_doc ? $object->cond_reglement_doc : $object->cond_reglement_label);\n $lib_condition_paiement = str_replace(\'\\n\', \"\\n\", $lib_condition_paiement);\n $pdf->MultiCell(67, 4, $lib_condition_paiement, 0, \'L\');\n\n $posy = $pdf->GetY() + 3; // We need spaces for 2 lines payment conditions\n }\n\n // Show category of operations\n if (getDolGlobalInt(\'INVOICE_CATEGORY_OF_OPERATION\') == 2 && $this->categoryOfOperation >= 0) {\n $pdf->SetFont(\'\', \'B\', $default_font_size - 2);\n $pdf->SetXY($this->marge_gauche, $posy);\n $categoryOfOperationTitle = $outputlangs->transnoentities(\"MentionCategoryOfOperations\").\' : \';\n $pdf->MultiCell($posxval - $this->marge_gauche, 4, $categoryOfOperationTitle, 0, \'L\');\n\n $pdf->SetFont(\'\', \'\', $default_font_size - 2);\n $pdf->SetXY($posxval, $posy);\n $categoryOfOperationLabel = $outputlangs->transnoentities(\"MentionCategoryOfOperations\" . $this->categoryOfOperation);\n $pdf->MultiCell($posxend - $posxval, 4, $categoryOfOperationLabel, 0, \'L\');\n\n $posy = $pdf->GetY() + 3; // for 2 lines\n }\n\n if ($object->type != 2) {\n // Check a payment mode is defined\n if (empty($object->mode_reglement_code)\n && !getDolGlobalInt(\'FACTURE_CHQ_NUMBER\')\n && !getDolGlobalInt(\'FACTURE_RIB_NUMBER\')) {\n $this->error = $outputlangs->transnoentities(\"ErrorNoPaiementModeConfigured\");\n } elseif (($object->mode_reglement_code == \'CHQ\' && !getDolGlobalInt(\'FACTURE_CHQ_NUMBER\') && empty($object->fk_account) && empty($object->fk_bank))\n || ($object->mode_reglement_code == \'VIR\' && !getDolGlobalInt(\'FACTURE_RIB_NUMBER\') && empty($object->fk_account) && empty($object->fk_bank))) {\n // Avoid having any valid PDF with setup that is not complete\n $outputlangs->load(\"errors\");\n\n $pdf->SetXY($this->marge_gauche, $posy);\n $pdf->SetTextColor(200, 0, 0);\n $pdf->SetFont(\'\', \'B\', $default_font_size - 2);\n $this->error = $outputlangs->transnoentities(\"ErrorPaymentModeDefinedToWithoutSetup\", $object->mode_reglement_code);\n $pdf->MultiCell(80, 3, $this->error, 0, \'L\', 0);\n $pdf->SetTextColor(0, 0, 0);\n\n $posy = $pdf->GetY() + 1;\n }\n\n // Show payment mode\n if (!empty($object->mode_reglement_code)\n && $object->mode_reglement_code != \'CHQ\'\n && $object->mode_reglement_code != \'VIR\') {\n $pdf->SetFont(\'\', \'B\', $default_font_size - 2);\n $pdf->SetXY($this->marge_gauche, $posy);\n $titre = $outputlangs->transnoentities(\"PaymentMode\").\':\';\n $pdf->MultiCell(80, 5, $titre, 0, \'L\');\n\n $pdf->SetFont(\'\', \'\', $default_font_size - 2);\n $pdf->SetXY($posxval, $posy);\n $lib_mode_reg = $outputlangs->transnoentities(\"PaymentType\".$object->mode_reglement_code) != (\'PaymentType\'.$object->mode_reglement_code) ? $outputlangs->transnoentities(\"PaymentType\".$object->mode_reglement_code) : $outputlangs->convToOutputCharset($object->mode_reglement);\n //#21654: add account number used for the debit\n if ($object->mode_reglement_code == \"PRE\") {\n require_once DOL_DOCUMENT_ROOT.\'/societe/class/companybankaccount.class.php\';\n $bac = new CompanyBankAccount($this->db);\n $bac->fetch(0, $object->thirdparty->id);\n $iban= $bac->iban.(($bac->iban && $bac->bic) ? \' / \' : \'\').$bac->bic;\n $lib_mode_reg .= \' \'.$outputlangs->trans(\"PaymentTypePREdetails\", dol_trunc($iban, 6, \'right\', \'UTF-8\', 1));\n }\n $pdf->MultiCell(80, 5, $lib_mode_reg, 0, \'L\');\n\n $posy = $pdf->GetY();\n }\n\n // Show if Option VAT debit option is on also if transmitter is french\n // Decret n°2099-1299 2022-10-07\n // French mention : \"Option pour le paiement de la taxe d\'après les débits\"\n if ($this->emetteur->country_code == \'FR\') {\n if (isset($conf->global->TAX_MODE) && $conf->global->TAX_MODE == 1) {\n $pdf->SetXY($this->marge_gauche, $posy);\n $pdf->writeHTMLCell(80, 5, \'\', \'\', $outputlangs->transnoentities(\"MentionVATDebitOptionIsOn\"), 0, 1);\n\n $posy = $pdf->GetY() + 1;\n }\n }\n\n // Show online payment link\n if (empty($object->mode_reglement_code) || $object->mode_reglement_code == \'CB\' || $object->mode_reglement_code == \'VAD\') {\n $useonlinepayment = 0;\n if (!empty($conf->global->PDF_SHOW_LINK_TO_ONLINE_PAYMENT)) {\n if (isModEnabled(\'paypal\')) {\n $useonlinepayment++;\n }\n if (isModEnabled(\'stripe\')) {\n $useonlinepayment++;\n }\n if (isModEnabled(\'paybox\')) {\n $useonlinepayment++;\n }\n }\n\n if ($object->statut != Facture::STATUS_DRAFT && $useonlinepayment) {\n require_once DOL_DOCUMENT_ROOT.\'/core/lib/payments.lib.php\';\n global $langs;\n\n $langs->loadLangs(array(\'payment\', \'paybox\', \'stripe\'));\n $servicename = $langs->transnoentities(\'Online\');\n $paiement_url = getOnlinePaymentUrl(\'\', \'invoice\', $object->ref, \'\', \'\', \'\');\n $linktopay = $langs->trans(\"ToOfferALinkForOnlinePayment\", $servicename).\' \'.$outputlangs->transnoentities(\"ClickHere\").\'\';\n\n $pdf->SetXY($this->marge_gauche, $posy);\n $pdf->writeHTMLCell(80, 5, \'\', \'\', dol_htmlentitiesbr($linktopay), 0, 1);\n\n $posy = $pdf->GetY() + 1;\n }\n }\n\n // Show payment mode CHQ\n if (empty($object->mode_reglement_code) || $object->mode_reglement_code == \'CHQ\') {\n // If payment mode unregulated or payment mode forced to CHQ\n if (getDolGlobalInt(\'FACTURE_CHQ_NUMBER\')) {\n $diffsizetitle = (empty($conf->global->PDF_DIFFSIZE_TITLE) ? 3 : $conf->global->PDF_DIFFSIZE_TITLE);\n\n if ($conf->global->FACTURE_CHQ_NUMBER > 0) {\n $account = new Account($this->db);\n $account->fetch(getDolGlobalInt(\'FACTURE_CHQ_NUMBER\'));\n\n $pdf->SetXY($this->marge_gauche, $posy);\n $pdf->SetFont(\'\', \'B\', $default_font_size - $diffsizetitle);\n $pdf->MultiCell(100, 3, $outputlangs->transnoentities(\'PaymentByChequeOrderedTo\', $account->proprio), 0, \'L\', 0);\n $posy = $pdf->GetY() + 1;\n\n if (empty($conf->global->MAIN_PDF_HIDE_CHQ_ADDRESS)) {\n $pdf->SetXY($this->marge_gauche, $posy);\n $pdf->SetFont(\'\', \'\', $default_font_size - $diffsizetitle);\n $pdf->MultiCell(100, 3, $outputlangs->convToOutputCharset($account->owner_address), 0, \'L\', 0);\n $posy = $pdf->GetY() + 2;\n }\n }\n if ($conf->global->FACTURE_CHQ_NUMBER == -1) {\n $pdf->SetXY($this->marge_gauche, $posy);\n $pdf->SetFont(\'\', \'B\', $default_font_size - $diffsizetitle);\n $pdf->MultiCell(100, 3, $outputlangs->transnoentities(\'PaymentByChequeOrderedTo\', $this->emetteur->name), 0, \'L\', 0);\n $posy = $pdf->GetY() + 1;\n\n if (empty($conf->global->MAIN_PDF_HIDE_CHQ_ADDRESS)) {\n $pdf->SetXY($this->marge_gauche, $posy);\n $pdf->SetFont(\'\', \'\', $default_font_size - $diffsizetitle);\n $pdf->MultiCell(100, 3, $outputlangs->convToOutputCharset($this->emetteur->getFullAddress()), 0, \'L\', 0);\n $posy = $pdf->GetY() + 2;\n }\n }\n }\n }\n\n // If payment mode not forced or forced to VIR, show payment with BAN\n if (empty($object->mode_reglement_code) || $object->mode_reglement_code == \'VIR\') {\n if ($object->fk_account > 0 || $object->fk_bank > 0 || getDolGlobalInt(\'FACTURE_RIB_NUMBER\')) {\n $bankid = ($object->fk_account <= 0 ? $conf->global->FACTURE_RIB_NUMBER : $object->fk_account);\n if ($object->fk_bank > 0) {\n $bankid = $object->fk_bank; // For backward compatibility when object->fk_account is forced with object->fk_bank\n }\n $account = new Account($this->db);\n $account->fetch($bankid);\n\n $curx = $this->marge_gauche;\n $cury = $posy;\n\n $posy = pdf_bank($pdf, $outputlangs, $curx, $cury, $account, 0, $default_font_size);\n\n $posy += 2;\n }\n }\n }\n\n return $posy;\n }\n\n\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore\n /**\n * Show total to pay\n *\n * @param TCPDF $pdf Object PDF\n * @param Facture $object Object invoice\n * @param int $deja_regle Amount already paid (in the currency of invoice)\n * @param int $posy Position depart\n * @param Translate $outputlangs Objet langs\n * @param Translate $outputlangsbis Object lang for output bis\n * @return int Position pour suite\n */\n protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs, $outputlangsbis)\n {\n // phpcs:enable\n global $conf, $mysoc, $hookmanager;\n\n $sign = 1;\n if ($object->type == 2 && !empty($conf->global->INVOICE_POSITIVE_CREDIT_NOTE)) {\n $sign = -1;\n }\n\n $default_font_size = pdf_getPDFFontSize($outputlangs);\n\n $outputlangsbis = null;\n if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE) && $outputlangs->defaultlang != $conf->global->PDF_USE_ALSO_LANGUAGE_CODE) {\n $outputlangsbis = new Translate(\'\', $conf);\n $outputlangsbis->setDefaultLang($conf->global->PDF_USE_ALSO_LANGUAGE_CODE);\n $outputlangsbis->loadLangs(array(\"main\", \"dict\", \"companies\", \"bills\", \"products\", \"propal\"));\n $default_font_size--;\n }\n\n $tab2_top = $posy;\n $tab2_hl = 4;\n $pdf->SetFont(\'\', \'\', $default_font_size - 1);\n\n // Total table\n $col1x = 120;\n $col2x = 170;\n if ($this->page_largeur < 210) { // To work with US executive format\n $col1x -= 15;\n $col2x -= 10;\n }\n $largcol2 = ($this->page_largeur - $this->marge_droite - $col2x);\n\n $useborder = 0;\n $index = 0;\n\n // Total HT\n $pdf->SetFillColor(255, 255, 255);\n $pdf->SetXY($col1x, $tab2_top);\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) ? \"TotalHT\" : \"Total\").(is_object($outputlangsbis) ? \' / \'.$outputlangsbis->transnoentities(empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) ? \"TotalHT\" : \"Total\") : \'\'), 0, \'L\', 1);\n\n $total_ht = ((isModEnabled(\"multicurrency\") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? $object->multicurrency_total_ht : $object->total_ht);\n $pdf->SetXY($col2x, $tab2_top);\n $pdf->MultiCell($largcol2, $tab2_hl, price($sign * ($total_ht + (!empty($object->remise) ? $object->remise : 0)), 0, $outputlangs), 0, \'R\', 1);\n\n // Show VAT by rates and total\n $pdf->SetFillColor(248, 248, 248);\n\n $total_ttc = (isModEnabled(\"multicurrency\") && $object->multicurrency_tx != 1) ? $object->multicurrency_total_ttc : $object->total_ttc;\n\n $this->atleastoneratenotnull = 0;\n if (empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT)) {\n $tvaisnull = ((!empty($this->tva) && count($this->tva) == 1 && isset($this->tva[\'0.000\']) && is_float($this->tva[\'0.000\'])) ? true : false);\n if (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_IFNULL) && $tvaisnull) {\n // Nothing to do\n } else {\n //Local tax 1 before VAT\n //if (!empty($conf->global->FACTURE_LOCAL_TAX1_OPTION) && $conf->global->FACTURE_LOCAL_TAX1_OPTION==\'localtax1on\')\n //{\n foreach ($this->localtax1 as $localtax_type => $localtax_rate) {\n if (in_array((string) $localtax_type, array(\'1\', \'3\', \'5\'))) {\n continue;\n }\n\n foreach ($localtax_rate as $tvakey => $tvaval) {\n if ($tvakey != 0) { // On affiche pas taux 0\n //$this->atleastoneratenotnull++;\n\n $index++;\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n\n $tvacompl = \'\';\n if (preg_match(\'/\\*/\', $tvakey)) {\n $tvakey = str_replace(\'*\', \'\', $tvakey);\n $tvacompl = \" (\".$outputlangs->transnoentities(\"NonPercuRecuperable\").\")\";\n }\n\n $totalvat = $outputlangs->transcountrynoentities(\"TotalLT1\", $mysoc->country_code).(is_object($outputlangsbis) ? \' / \'.$outputlangsbis->transcountrynoentities(\"TotalLT1\", $mysoc->country_code) : \'\');\n $totalvat .= \' \';\n $totalvat .= vatrate(abs($tvakey), 1).$tvacompl;\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, \'L\', 1);\n\n $total_localtax = ((isModEnabled(\"multicurrency\") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, \'MT\') : $tvaval);\n\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, \'R\', 1);\n }\n }\n }\n //}\n //Local tax 2 before VAT\n //if (!empty($conf->global->FACTURE_LOCAL_TAX2_OPTION) && $conf->global->FACTURE_LOCAL_TAX2_OPTION==\'localtax2on\')\n //{\n foreach ($this->localtax2 as $localtax_type => $localtax_rate) {\n if (in_array((string) $localtax_type, array(\'1\', \'3\', \'5\'))) {\n continue;\n }\n\n foreach ($localtax_rate as $tvakey => $tvaval) {\n if ($tvakey != 0) { // On affiche pas taux 0\n //$this->atleastoneratenotnull++;\n\n $index++;\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n\n $tvacompl = \'\';\n if (preg_match(\'/\\*/\', $tvakey)) {\n $tvakey = str_replace(\'*\', \'\', $tvakey);\n $tvacompl = \" (\".$outputlangs->transnoentities(\"NonPercuRecuperable\").\")\";\n }\n $totalvat = $outputlangs->transcountrynoentities(\"TotalLT2\", $mysoc->country_code).(is_object($outputlangsbis) ? \' / \'.$outputlangsbis->transcountrynoentities(\"TotalLT2\", $mysoc->country_code) : \'\');\n $totalvat .= \' \';\n $totalvat .= vatrate(abs($tvakey), 1).$tvacompl;\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, \'L\', 1);\n\n $total_localtax = ((isModEnabled(\"multicurrency\") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, \'MT\') : $tvaval);\n\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, \'R\', 1);\n }\n }\n }\n\n //}\n\n // VAT\n foreach ($this->tva_array as $tvakey => $tvaval) {\n if ($tvakey != 0) { // On affiche pas taux 0\n $this->atleastoneratenotnull++;\n\n $index++;\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n\n $tvacompl = \'\';\n if (preg_match(\'/\\*/\', $tvakey)) {\n $tvakey = str_replace(\'*\', \'\', $tvakey);\n $tvacompl = \" (\".$outputlangs->transnoentities(\"NonPercuRecuperable\").\")\";\n }\n $totalvat = $outputlangs->transcountrynoentities(\"TotalVAT\", $mysoc->country_code).(is_object($outputlangsbis) ? \' / \'.$outputlangsbis->transcountrynoentities(\"TotalVAT\", $mysoc->country_code) : \'\');\n $totalvat .= \' \';\n if (getDolGlobalString(\'PDF_VAT_LABEL_IS_CODE_OR_RATE\') == \'rateonly\') {\n $totalvat .= vatrate($tvaval[\'vatrate\'], 1).$tvacompl;\n } elseif (getDolGlobalString(\'PDF_VAT_LABEL_IS_CODE_OR_RATE\') == \'codeonly\') {\n $totalvat .= $tvaval[\'vatcode\'].$tvacompl;\n } else {\n $totalvat .= vatrate($tvaval[\'vatrate\'], 1).($tvaval[\'vatcode\'] ? \' (\'.$tvaval[\'vatcode\'].\')\' : \'\').$tvacompl;\n }\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, \'L\', 1);\n\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price(price2num($tvaval[\'amount\'], \'MT\'), 0, $outputlangs), 0, \'R\', 1);\n }\n }\n\n //Local tax 1 after VAT\n //if (!empty($conf->global->FACTURE_LOCAL_TAX1_OPTION) && $conf->global->FACTURE_LOCAL_TAX1_OPTION==\'localtax1on\')\n //{\n foreach ($this->localtax1 as $localtax_type => $localtax_rate) {\n if (in_array((string) $localtax_type, array(\'2\', \'4\', \'6\'))) {\n continue;\n }\n\n foreach ($localtax_rate as $tvakey => $tvaval) {\n if ($tvakey != 0) { // On affiche pas taux 0\n //$this->atleastoneratenotnull++;\n\n $index++;\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n\n $tvacompl = \'\';\n if (preg_match(\'/\\*/\', $tvakey)) {\n $tvakey = str_replace(\'*\', \'\', $tvakey);\n $tvacompl = \" (\".$outputlangs->transnoentities(\"NonPercuRecuperable\").\")\";\n }\n $totalvat = $outputlangs->transcountrynoentities(\"TotalLT1\", $mysoc->country_code).\' \';\n $totalvat .= vatrate(abs($tvakey), 1).$tvacompl;\n\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, \'L\', 1);\n\n $total_localtax = ((isModEnabled(\"multicurrency\") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, \'MT\') : $tvaval);\n\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, \'R\', 1);\n }\n }\n }\n //}\n //Local tax 2 after VAT\n //if (!empty($conf->global->FACTURE_LOCAL_TAX2_OPTION) && $conf->global->FACTURE_LOCAL_TAX2_OPTION==\'localtax2on\')\n //{\n foreach ($this->localtax2 as $localtax_type => $localtax_rate) {\n if (in_array((string) $localtax_type, array(\'2\', \'4\', \'6\'))) {\n continue;\n }\n\n foreach ($localtax_rate as $tvakey => $tvaval) {\n //$this->atleastoneratenotnull++;\n\n $index++;\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n\n $tvacompl = \'\';\n if (preg_match(\'/\\*/\', $tvakey)) {\n $tvakey = str_replace(\'*\', \'\', $tvakey);\n $tvacompl = \" (\".$outputlangs->transnoentities(\"NonPercuRecuperable\").\")\";\n }\n $totalvat = $outputlangs->transcountrynoentities(\"TotalLT2\", $mysoc->country_code).\' \';\n\n $totalvat .= vatrate(abs($tvakey), 1).$tvacompl;\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $totalvat, 0, \'L\', 1);\n\n $total_localtax = ((isModEnabled(\"multicurrency\") && isset($object->multicurrency_tx) && $object->multicurrency_tx != 1) ? price2num($tvaval * $object->multicurrency_tx, \'MT\') : $tvaval);\n\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($total_localtax, 0, $outputlangs), 0, \'R\', 1);\n }\n }\n //}\n\n // Revenue stamp\n if (price2num($object->revenuestamp) != 0) {\n $index++;\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(\"RevenueStamp\"), $useborder, \'L\', 1);\n\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($sign * $object->revenuestamp), $useborder, \'R\', 1);\n }\n\n // Total TTC\n $index++;\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n $pdf->SetTextColor(0, 0, 60);\n $pdf->SetFillColor(224, 224, 224);\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(\"TotalTTC\"), $useborder, \'L\', 1);\n\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($sign * $total_ttc, 0, $outputlangs), $useborder, \'R\', 1);\n\n // Retained warranty\n if ($object->displayRetainedWarranty()) {\n $pdf->SetTextColor(40, 40, 40);\n $pdf->SetFillColor(255, 255, 255);\n\n $retainedWarranty = $object->getRetainedWarrantyAmount();\n $billedWithRetainedWarranty = $object->total_ttc - $retainedWarranty;\n\n // Billed - retained warranty\n $index++;\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(\"ToPayOn\", dol_print_date($object->date_lim_reglement, \'day\')), $useborder, \'L\', 1);\n\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($billedWithRetainedWarranty), $useborder, \'R\', 1);\n\n // retained warranty\n $index++;\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n\n $retainedWarrantyToPayOn = $outputlangs->transnoentities(\"RetainedWarranty\").\' (\'.$object->retained_warranty.\'%)\';\n $retainedWarrantyToPayOn .= !empty($object->retained_warranty_date_limit) ? \' \'.$outputlangs->transnoentities(\"toPayOn\", dol_print_date($object->retained_warranty_date_limit, \'day\')) : \'\';\n\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $retainedWarrantyToPayOn, $useborder, \'L\', 1);\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($retainedWarranty), $useborder, \'R\', 1);\n }\n }\n }\n\n $pdf->SetTextColor(0, 0, 0);\n $creditnoteamount = $object->getSumCreditNotesUsed((isModEnabled(\"multicurrency\") && $object->multicurrency_tx != 1) ? 1 : 0); // Warning, this also include excess received\n $depositsamount = $object->getSumDepositsUsed((isModEnabled(\"multicurrency\") && $object->multicurrency_tx != 1) ? 1 : 0);\n //print \"x\".$creditnoteamount.\"-\".$depositsamount;exit;\n $resteapayer = price2num($total_ttc - $deja_regle - $creditnoteamount - $depositsamount, \'MT\');\n if (!empty($object->paye)) {\n $resteapayer = 0;\n }\n\n if (($deja_regle > 0 || $creditnoteamount > 0 || $depositsamount > 0) && empty($conf->global->INVOICE_NO_PAYMENT_DETAILS)) {\n // Already paid + Deposits\n $index++;\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(\"Paid\"), 0, \'L\', 0);\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($deja_regle + $depositsamount, 0, $outputlangs), 0, \'R\', 0);\n\n // Credit note\n if ($creditnoteamount) {\n $labeltouse = ($outputlangs->transnoentities(\"CreditNotesOrExcessReceived\") != \"CreditNotesOrExcessReceived\") ? $outputlangs->transnoentities(\"CreditNotesOrExcessReceived\") : $outputlangs->transnoentities(\"CreditNotes\");\n $index++;\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $labeltouse, 0, \'L\', 0);\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($creditnoteamount, 0, $outputlangs), 0, \'R\', 0);\n }\n\n // Escompte\n if ($object->close_code == Facture::CLOSECODE_DISCOUNTVAT) {\n $index++;\n $pdf->SetFillColor(255, 255, 255);\n\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(\"EscompteOfferedShort\"), $useborder, \'L\', 1);\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($object->total_ttc - $deja_regle - $creditnoteamount - $depositsamount, 0, $outputlangs), $useborder, \'R\', 1);\n\n $resteapayer = 0;\n }\n\n $index++;\n $pdf->SetTextColor(0, 0, 60);\n $pdf->SetFillColor(224, 224, 224);\n $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities(\"RemainderToPay\"), $useborder, \'L\', 1);\n $pdf->SetXY($col2x, $tab2_top + $tab2_hl * $index);\n $pdf->MultiCell($largcol2, $tab2_hl, price($resteapayer, 0, $outputlangs), $useborder, \'R\', 1);\n\n $pdf->SetFont(\'\', \'\', $default_font_size - 1);\n $pdf->SetTextColor(0, 0, 0);\n }\n\n $index++;\n return ($tab2_top + ($tab2_hl * $index));\n }\n\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore\n /**\n * Show table for lines\n *\n * @param TCPDF $pdf Object PDF\n * @param string $tab_top Top position of table\n * @param string $tab_height Height of table (rectangle)\n * @param int $nexY Y (not used)\n * @param Translate $outputlangs Langs object\n * @param int $hidetop 1=Hide top bar of array and title, 0=Hide nothing, -1=Hide only title\n * @param int $hidebottom Hide bottom bar of array\n * @param string $currency Currency code\n * @return void\n */\n protected function _tableau(&$pdf, $tab_top, $tab_height, $nexY, $outputlangs, $hidetop = 0, $hidebottom = 0, $currency = \'\')\n {\n global $conf;\n\n // Force to disable hidetop and hidebottom\n $hidebottom = 0;\n if ($hidetop) {\n $hidetop = -1;\n }\n\n $currency = !empty($currency) ? $currency : $conf->currency;\n $default_font_size = pdf_getPDFFontSize($outputlangs);\n\n // Amount in (at tab_top - 1)\n $pdf->SetTextColor(0, 0, 0);\n $pdf->SetFont(\'\', \'\', $default_font_size - 2);\n\n if (empty($hidetop)) {\n // Show category of operations\n if (getDolGlobalInt(\'INVOICE_CATEGORY_OF_OPERATION\') == 1 && $this->categoryOfOperation >= 0) {\n $categoryOfOperations = $outputlangs->transnoentities(\"MentionCategoryOfOperations\") . \' : \' . $outputlangs->transnoentities(\"MentionCategoryOfOperations\" . $this->categoryOfOperation);\n $pdf->SetXY($this->marge_gauche, $tab_top - 4);\n $pdf->MultiCell(($pdf->GetStringWidth($categoryOfOperations)) + 4, 2, $categoryOfOperations);\n }\n\n $titre = $outputlangs->transnoentities(\"AmountInCurrency\", $outputlangs->transnoentitiesnoconv(\"Currency\".$currency));\n $pdf->SetXY($this->page_largeur - $this->marge_droite - ($pdf->GetStringWidth($titre) + 3), $tab_top - 4);\n $pdf->MultiCell(($pdf->GetStringWidth($titre) + 3), 2, $titre);\n\n //$conf->global->MAIN_PDF_TITLE_BACKGROUND_COLOR=\'230,230,230\';\n if (!empty($conf->global->MAIN_PDF_TITLE_BACKGROUND_COLOR)) {\n $pdf->Rect($this->marge_gauche, $tab_top, $this->page_largeur - $this->marge_droite - $this->marge_gauche, 5, \'F\', null, explode(\',\', $conf->global->MAIN_PDF_TITLE_BACKGROUND_COLOR));\n }\n }\n\n $pdf->SetDrawColor(128, 128, 128);\n $pdf->SetFont(\'\', \'\', $default_font_size - 1);\n\n // Output Rect\n $this->printRect($pdf, $this->marge_gauche, $tab_top, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $tab_height, $hidetop, $hidebottom); // Rect takes a length in 3rd parameter and 4th parameter\n\n if (empty($hidetop)) {\n $pdf->line($this->marge_gauche, $tab_top + 5, $this->page_largeur - $this->marge_droite, $tab_top + 5); // line takes a position y in 2nd parameter and 4th parameter\n\n $pdf->SetXY($this->posxdesc - 1, $tab_top + 1);\n $pdf->MultiCell(108, 2, $outputlangs->transnoentities(\"Designation\"), \'\', \'L\');\n }\n\n if (!empty($conf->global->MAIN_GENERATE_INVOICES_WITH_PICTURE)) {\n $pdf->line($this->posxpicture - 1, $tab_top, $this->posxpicture - 1, $tab_top + $tab_height);\n if (empty($hidetop)) {\n //$pdf->SetXY($this->posxpicture-1, $tab_top+1);\n //$pdf->MultiCell($this->posxtva-$this->posxpicture-1,2, $outputlangs->transnoentities(\"Photo\"),\'\',\'C\');\n }\n }\n\n if (empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT) && empty($conf->global->MAIN_GENERATE_DOCUMENTS_WITHOUT_VAT_COLUMN)) {\n $pdf->line($this->posxtva - 1, $tab_top, $this->posxtva - 1, $tab_top + $tab_height);\n if (empty($hidetop)) {\n $pdf->SetXY($this->posxtva - 3, $tab_top + 1);\n $pdf->MultiCell($this->posxup - $this->posxtva + 3, 2, $outputlangs->transnoentities(\"VAT\"), \'\', \'C\');\n }\n }\n\n $pdf->line($this->posxup - 1, $tab_top, $this->posxup - 1, $tab_top + $tab_height);\n if (empty($hidetop)) {\n $pdf->SetXY($this->posxup - 1, $tab_top + 1);\n $pdf->MultiCell($this->posxqty - $this->posxup - 1, 2, $outputlangs->transnoentities(\"PriceUHT\"), \'\', \'C\');\n }\n\n $pdf->line($this->posxqty - 1, $tab_top, $this->posxqty - 1, $tab_top + $tab_height);\n if (empty($hidetop)) {\n $pdf->SetXY($this->posxqty - 1, $tab_top + 1);\n $pdf->MultiCell($this->posxunit - $this->posxqty - 1, 2, $outputlangs->transnoentities(\"Qty\"), \'\', \'C\');\n }\n\n if (getDolGlobalInt(\'PRODUCT_USE_UNITS\')) {\n $pdf->line($this->posxunit - 1, $tab_top, $this->posxunit - 1, $tab_top + $tab_height);\n if (empty($hidetop)) {\n $pdf->SetXY($this->posxunit - 1, $tab_top + 1);\n $pdf->MultiCell($this->posxdiscount - $this->posxunit - 1, 2, $outputlangs->transnoentities(\"Unit\"), \'\', \'C\');\n }\n }\n\n if ($this->atleastonediscount) {\n $pdf->line($this->posxdiscount - 1, $tab_top, $this->posxdiscount - 1, $tab_top + $tab_height);\n if (empty($hidetop)) {\n $pdf->SetXY($this->posxdiscount - 1, $tab_top + 1);\n $pdf->MultiCell($this->posxprogress - $this->posxdiscount + 1, 2, $outputlangs->transnoentities(\"ReductionShort\"), \'\', \'C\');\n }\n }\n\n if ($this->situationinvoice) {\n $pdf->line($this->posxprogress - 1, $tab_top, $this->posxprogress - 1, $tab_top + $tab_height);\n if (empty($hidetop)) {\n $pdf->SetXY($this->posxprogress, $tab_top + 1);\n $pdf->MultiCell($this->postotalht - $this->posxprogress, 2, $outputlangs->transnoentities(\"ProgressShort\"), \'\', \'C\');\n }\n }\n\n $pdf->line($this->postotalht, $tab_top, $this->postotalht, $tab_top + $tab_height);\n if (empty($hidetop)) {\n $pdf->SetXY($this->postotalht - 1, $tab_top + 1);\n $pdf->MultiCell(30, 2, $outputlangs->transnoentities(\"TotalHTShort\"), \'\', \'C\');\n }\n }\n\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore\n /**\n * Show top header of page.\n *\n * @param TCPDF $pdf Object PDF\n * @param Facture $object Object to show\n * @param int $showaddress 0=no, 1=yes\n * @param Translate $outputlangs Object lang for output\n * @param Translate $outputlangsbis Object lang for output bis\n * @return int top shift of linked object lines\n */\n protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs, $outputlangsbis = null)\n {\n global $conf, $langs;\n\n $ltrdirection = \'L\';\n if ($outputlangs->trans(\"DIRECTION\") == \'rtl\') $ltrdirection = \'R\';\n\n // Load traductions files required by page\n $outputlangs->loadLangs(array(\"main\", \"bills\", \"propal\", \"companies\"));\n\n $default_font_size = pdf_getPDFFontSize($outputlangs);\n\n pdf_pagehead($pdf, $outputlangs, $this->page_hauteur);\n\n $pdf->SetTextColor(0, 0, 60);\n $pdf->SetFont(\'\', \'B\', $default_font_size + 3);\n\n $w = 110;\n\n $posy = $this->marge_haute;\n $posx = $this->page_largeur - $this->marge_droite - $w;\n\n $pdf->SetXY($this->marge_gauche, $posy);\n\n // Logo\n if (!getDolGlobalInt(\'PDF_DISABLE_MYCOMPANY_LOGO\')) {\n if ($this->emetteur->logo) {\n $logodir = $conf->mycompany->dir_output;\n if (!empty($conf->mycompany->multidir_output[$object->entity])) {\n $logodir = $conf->mycompany->multidir_output[$object->entity];\n }\n if (!getDolGlobalInt(\'MAIN_PDF_USE_LARGE_LOGO\')) {\n $logo = $logodir.\'/logos/thumbs/\'.$this->emetteur->logo_small;\n } else {\n $logo = $logodir.\'/logos/\'.$this->emetteur->logo;\n }\n if (is_readable($logo)) {\n $height = pdf_getHeightForLogo($logo);\n $pdf->Image($logo, $this->marge_gauche, $posy, 0, $height); // width=0 (auto)\n } else {\n $pdf->SetTextColor(200, 0, 0);\n $pdf->SetFont(\'\', \'B\', $default_font_size - 2);\n $pdf->MultiCell($w, 3, $outputlangs->transnoentities(\"ErrorLogoFileNotFound\", $logo), 0, \'L\');\n $pdf->MultiCell($w, 3, $outputlangs->transnoentities(\"ErrorGoToGlobalSetup\"), 0, \'L\');\n }\n } else {\n $text = $this->emetteur->name;\n $pdf->MultiCell($w, 4, $outputlangs->convToOutputCharset($text), 0, $ltrdirection);\n }\n }\n\n $pdf->SetFont(\'\', \'B\', $default_font_size + 3);\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $title = $outputlangs->transnoentities(\"PdfInvoiceTitle\");\n if ($object->type == 1) {\n $title = $outputlangs->transnoentities(\"InvoiceReplacement\");\n }\n if ($object->type == 2) {\n $title = $outputlangs->transnoentities(\"InvoiceAvoir\");\n }\n if ($object->type == 3) {\n $title = $outputlangs->transnoentities(\"InvoiceDeposit\");\n }\n if ($object->type == 4) {\n $title = $outputlangs->transnoentities(\"InvoiceProForma\");\n }\n if ($this->situationinvoice) {\n $langs->loadLangs(array(\"other\"));\n $title = $outputlangs->transnoentities(\"PDFInvoiceSituation\") . \" \" . $outputlangs->transnoentities(\"NumberingShort\") . $object->situation_counter . \" -\";\n }\n if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE) && is_object($outputlangsbis)) {\n $title .= \' - \';\n if ($object->type == 0) {\n if ($this->situationinvoice) {\n $title .= $outputlangsbis->transnoentities(\"PDFInvoiceSituation\");\n }\n $title .= $outputlangsbis->transnoentities(\"PdfInvoiceTitle\");\n } elseif ($object->type == 1) {\n $title .= $outputlangsbis->transnoentities(\"InvoiceReplacement\");\n } elseif ($object->type == 2) {\n $title .= $outputlangsbis->transnoentities(\"InvoiceAvoir\");\n } elseif ($object->type == 3) {\n $title .= $outputlangsbis->transnoentities(\"InvoiceDeposit\");\n } elseif ($object->type == 4) {\n $title .= $outputlangsbis->transnoentities(\"InvoiceProForma\");\n }\n }\n $title .= \' \'.$outputlangs->convToOutputCharset($object->ref);\n if ($object->statut == $object::STATUS_DRAFT) {\n $pdf->SetTextColor(128, 0, 0);\n $title .= \' - \'.$outputlangs->transnoentities(\"NotValidated\");\n }\n\n $pdf->MultiCell($w, 3, $title, \'\', \'R\');\n\n $pdf->SetFont(\'\', \'B\', $default_font_size);\n\n /*\n $posy += 5;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $textref = $outputlangs->transnoentities(\"Ref\").\" : \".$outputlangs->convToOutputCharset($object->ref);\n if ($object->statut == $object::STATUS_DRAFT) {\n $pdf->SetTextColor(128, 0, 0);\n $textref .= \' - \'.$outputlangs->transnoentities(\"NotValidated\");\n }\n $pdf->MultiCell($w, 4, $textref, \'\', \'R\');*/\n\n $posy += 3;\n $pdf->SetFont(\'\', \'\', $default_font_size - 2);\n\n if ($object->ref_customer) {\n $posy += 4;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $pdf->MultiCell($w, 3, $outputlangs->transnoentities(\"RefCustomer\").\" : \".$outputlangs->convToOutputCharset($object->ref_customer), \'\', \'R\');\n }\n\n if (!empty($conf->global->PDF_SHOW_PROJECT_TITLE)) {\n $object->fetch_projet();\n if (!empty($object->project->ref)) {\n $posy += 3;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $pdf->MultiCell($w, 3, $outputlangs->transnoentities(\"Project\").\" : \".(empty($object->project->title) ? \'\' : $object->project->title), \'\', \'R\');\n }\n }\n\n if (!empty($conf->global->PDF_SHOW_PROJECT)) {\n $object->fetch_projet();\n if (!empty($object->project->ref)) {\n $outputlangs->load(\"projects\");\n $posy += 3;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $pdf->MultiCell($w, 3, $outputlangs->transnoentities(\"RefProject\").\" : \".(empty($object->project->ref) ? \'\' : $object->project->ref), \'\', \'R\');\n }\n }\n\n $objectidnext = $object->getIdReplacingInvoice(\'validated\');\n if ($object->type == 0 && $objectidnext) {\n $objectreplacing = new Facture($this->db);\n $objectreplacing->fetch($objectidnext);\n\n $posy += 3;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $pdf->MultiCell($w, 3, $outputlangs->transnoentities(\"ReplacementByInvoice\").\' : \'.$outputlangs->convToOutputCharset($objectreplacing->ref), \'\', \'R\');\n }\n if ($object->type == 1) {\n $objectreplaced = new Facture($this->db);\n $objectreplaced->fetch($object->fk_facture_source);\n\n $posy += 4;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $pdf->MultiCell($w, 3, $outputlangs->transnoentities(\"ReplacementInvoice\").\' : \'.$outputlangs->convToOutputCharset($objectreplaced->ref), \'\', \'R\');\n }\n if ($object->type == 2 && !empty($object->fk_facture_source)) {\n $objectreplaced = new Facture($this->db);\n $objectreplaced->fetch($object->fk_facture_source);\n\n $posy += 3;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $pdf->MultiCell($w, 3, $outputlangs->transnoentities(\"CorrectionInvoice\").\' : \'.$outputlangs->convToOutputCharset($objectreplaced->ref), \'\', \'R\');\n }\n\n $posy += 4;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $title = $outputlangs->transnoentities(\"DateInvoice\");\n if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE) && is_object($outputlangsbis)) {\n $title .= \' - \'.$outputlangsbis->transnoentities(\"DateInvoice\");\n }\n $pdf->MultiCell($w, 3, $title.\" : \".dol_print_date($object->date, \"day\", false, $outputlangs, true), \'\', \'R\');\n\n if (!empty($conf->global->INVOICE_POINTOFTAX_DATE)) {\n $posy += 4;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $pdf->MultiCell($w, 3, $outputlangs->transnoentities(\"DatePointOfTax\").\" : \".dol_print_date($object->date_pointoftax, \"day\", false, $outputlangs), \'\', \'R\');\n }\n\n if ($object->type != 2) {\n $posy += 3;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $title = $outputlangs->transnoentities(\"DateDue\");\n if (!empty($conf->global->PDF_USE_ALSO_LANGUAGE_CODE) && is_object($outputlangsbis)) {\n $title .= \' - \'.$outputlangsbis->transnoentities(\"DateDue\");\n }\n $pdf->MultiCell($w, 3, $title.\" : \".dol_print_date($object->date_lim_reglement, \"day\", false, $outputlangs, true), \'\', \'R\');\n }\n\n if (empty($conf->global->MAIN_PDF_HIDE_CUSTOMER_CODE) && $object->thirdparty->code_client) {\n $posy += 3;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $pdf->MultiCell($w, 3, $outputlangs->transnoentities(\"CustomerCode\").\" : \".$outputlangs->transnoentities($object->thirdparty->code_client), \'\', \'R\');\n }\n\n // Get contact\n if (!empty($conf->global->DOC_SHOW_FIRST_SALES_REP)) {\n $arrayidcontact = $object->getIdContact(\'internal\', \'SALESREPFOLL\');\n if (count($arrayidcontact) > 0) {\n $usertmp = new User($this->db);\n $usertmp->fetch($arrayidcontact[0]);\n $posy += 4;\n $pdf->SetXY($posx, $posy);\n $pdf->SetTextColor(0, 0, 60);\n $pdf->MultiCell($w, 3, $outputlangs->transnoentities(\"SalesRepresentative\").\" : \".$usertmp->getFullName($langs), \'\', \'R\');\n }\n }\n\n $posy += 1;\n\n $top_shift = 0;\n // Show list of linked objects\n $current_y = $pdf->getY();\n $posy = pdf_writeLinkedObjects($pdf, $object, $outputlangs, $posx, $posy, $w, 3, \'R\', $default_font_size);\n if ($current_y < $pdf->getY()) {\n $top_shift = $pdf->getY() - $current_y;\n }\n \n //=======QR verifactu========\n \n if ($object->array_options[\'options_verifactu_url\'] !=\"\") {\n \n $outputlangs->loadLangs(array(\"verifactudolibarr@verifactudolibarr\"));\n \n $url = $object->array_options[\'options_verifactu_url\'];\n \n $style = [\'border\'=>false, \'padding\'=>0, \'fgcolor\'=>[0,0,0], \'bgcolor\'=>false];\n // posicion: x=150mm, y=20mm, ancho/alto en mm\n \n $pdf->SetXY(100,11);\n $pdf->SetFont(\'\', \'\', $default_font_size - 2);\n $pdf->MultiCell(25, \'\', $outputlangs->transnoentities(\'verifactu_qr_tributario\'), 0, \'L\', 0);\n \n \n $pdf->write2DBarcode($url, \'QRCODE,H\', 100, 17, 20, 20, $style, \'N\');\n \n $pdf->SetXY(74, 38);\n $pdf->SetFont(\'\', \'\', $default_font_size - 2);\n $pdf->MultiCell(75, \'\', $outputlangs->transnoentities(\'verifactu_doc_verificable_tributario\'), 0, \'C\', 0);\n \n $top_shift+=8;\n }\n \n //=======QR verifactu========\n\n if ($showaddress) {\n // Sender properties\n $carac_emetteur = \'\';\n // Add internal contact of object if defined\n $arrayidcontact = $object->getIdContact(\'internal\', \'BILLING\');\n if (count($arrayidcontact) > 0) {\n $object->fetch_user($arrayidcontact[0]);\n $labelbeforecontactname = ($outputlangs->transnoentities(\"FromContactName\") != \'FromContactName\' ? $outputlangs->transnoentities(\"FromContactName\") : $outputlangs->transnoentities(\"Name\"));\n $carac_emetteur .= ($carac_emetteur ? \"\\n\" : \'\').$labelbeforecontactname.\" \".$outputlangs->convToOutputCharset($object->user->getFullName($outputlangs));\n $carac_emetteur .= \"\\n\";\n }\n\n $carac_emetteur .= pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, \'\', 0, \'source\', $object);\n\n // Show sender\n $posy = !empty($conf->global->MAIN_PDF_USE_ISO_LOCATION) ? 40 : 42;\n $posy += $top_shift;\n $posx = $this->marge_gauche;\n if (!empty($conf->global->MAIN_INVERT_SENDER_RECIPIENT)) {\n $posx = $this->page_largeur - $this->marge_droite - 80;\n }\n\n $hautcadre = !empty($conf->global->MAIN_PDF_USE_ISO_LOCATION) ? 38 : 40;\n $widthrecbox = !empty($conf->global->MAIN_PDF_USE_ISO_LOCATION) ? 92 : 82;\n\n\n // Show sender frame\n if (empty($conf->global->MAIN_PDF_NO_SENDER_FRAME)) {\n $pdf->SetTextColor(0, 0, 0);\n $pdf->SetFont(\'\', \'\', $default_font_size - 2);\n $pdf->SetXY($posx, $posy - 5);\n $pdf->MultiCell($widthrecbox, 5, $outputlangs->transnoentities(\"BillFrom\"), 0, $ltrdirection);\n $pdf->SetXY($posx, $posy);\n $pdf->SetFillColor(230, 230, 230);\n $pdf->MultiCell($widthrecbox, $hautcadre, \"\", 0, \'R\', 1);\n $pdf->SetTextColor(0, 0, 60);\n }\n\n // Show sender name\n if (empty($conf->global->MAIN_PDF_HIDE_SENDER_NAME)) {\n $pdf->SetXY($posx + 2, $posy + 3);\n $pdf->SetFont(\'\', \'B\', $default_font_size);\n $pdf->MultiCell($widthrecbox - 2, 4, $outputlangs->convToOutputCharset($this->emetteur->name), 0, $ltrdirection);\n $posy = $pdf->getY();\n }\n\n // Show sender information\n $pdf->SetXY($posx + 2, $posy);\n $pdf->SetFont(\'\', \'\', $default_font_size - 1);\n $pdf->MultiCell($widthrecbox - 2, 4, $carac_emetteur, 0, $ltrdirection);\n\n\n // If BILLING contact defined on invoice, we use it\n $usecontact = false;\n $arrayidcontact = $object->getIdContact(\'external\', \'BILLING\');\n if (count($arrayidcontact) > 0) {\n $usecontact = true;\n $result = $object->fetch_contact($arrayidcontact[0]);\n }\n\n // Recipient name\n if ($usecontact && ($object->contact->socid != $object->thirdparty->id && (!isset($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT) || !empty($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT)))) {\n $thirdparty = $object->contact;\n } else {\n $thirdparty = $object->thirdparty;\n }\n\n $carac_client_name = pdfBuildThirdpartyName($thirdparty, $outputlangs);\n\n $mode = \'target\';\n $carac_client = pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, ($usecontact ? $object->contact : \'\'), $usecontact, $mode, $object);\n\n // Show recipient\n $widthrecbox = !empty($conf->global->MAIN_PDF_USE_ISO_LOCATION) ? 92 : 100;\n if ($this->page_largeur < 210) {\n $widthrecbox = 84; // To work with US executive format\n }\n $posy = !empty($conf->global->MAIN_PDF_USE_ISO_LOCATION) ? 40 : 42;\n $posy += $top_shift;\n $posx = $this->page_largeur - $this->marge_droite - $widthrecbox;\n if (!empty($conf->global->MAIN_INVERT_SENDER_RECIPIENT)) {\n $posx = $this->marge_gauche;\n }\n\n // Show recipient frame\n if (empty($conf->global->MAIN_PDF_NO_RECIPENT_FRAME)) {\n $pdf->SetTextColor(0, 0, 0);\n $pdf->SetFont(\'\', \'\', $default_font_size - 2);\n $pdf->SetXY($posx + 2, $posy - 5);\n $pdf->MultiCell($widthrecbox - 2, 5, $outputlangs->transnoentities(\"BillTo\"), 0, $ltrdirection);\n $pdf->Rect($posx, $posy, $widthrecbox, $hautcadre);\n }\n\n // Show recipient name\n $pdf->SetXY($posx + 2, $posy + 3);\n $pdf->SetFont(\'\', \'B\', $default_font_size);\n $pdf->MultiCell($widthrecbox - 2, 2, $carac_client_name, 0, $ltrdirection);\n\n $posy = $pdf->getY();\n\n // Show recipient information\n $pdf->SetFont(\'\', \'\', $default_font_size - 1);\n $pdf->SetXY($posx + 2, $posy);\n $pdf->MultiCell($widthrecbox - 2, 4, $carac_client, 0, $ltrdirection);\n\n // Show shipping address\n if (getDolGlobalInt(\'INVOICE_SHOW_SHIPPING_ADDRESS\')) {\n $idaddressshipping = $object->getIdContact(\'external\', \'SHIPPING\');\n\n if (!empty($idaddressshipping)) {\n $contactshipping = $object->fetch_Contact($idaddressshipping[0]);\n $companystatic = new Societe($this->db);\n $companystatic->fetch($object->contact->fk_soc);\n $carac_client_name_shipping=pdfBuildThirdpartyName($object->contact, $outputlangs);\n $carac_client_shipping = pdf_build_address($outputlangs, $this->emetteur, $companystatic, $object->contact, $usecontact, \'target\', $object);\n } else {\n $carac_client_name_shipping=pdfBuildThirdpartyName($object->thirdparty, $outputlangs);\n $carac_client_shipping=pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, \'\', 0, \'target\', $object);\n }\n if (!empty($carac_client_shipping)) {\n $posy += $hautcadre;\n\n // Show shipping frame\n $pdf->SetXY($posx + 2, $posy - 5);\n $pdf->SetFont(\'\', \'\', $default_font_size - 2);\n $pdf->MultiCell($widthrecbox, \'\', $outputlangs->transnoentities(\'ShippingTo\'), 0, \'L\', 0);\n $pdf->Rect($posx, $posy, $widthrecbox, $hautcadre);\n\n // Show shipping name\n $pdf->SetXY($posx + 2, $posy + 3);\n $pdf->SetFont(\'\', \'B\', $default_font_size);\n $pdf->MultiCell($widthrecbox - 2, 2, $carac_client_name_shipping, \'\', \'L\');\n\n $posy = $pdf->getY();\n\n // Show shipping information\n $pdf->SetXY($posx+2, $posy);\n $pdf->SetFont(\'\', \'\', $default_font_size - 1);\n $pdf->MultiCell($widthrecbox - 2, 2, $carac_client_shipping, \'\', \'L\');\n $top_shift += $hautcadre;\n }\n }\n }\n\n $pdf->SetTextColor(0, 0, 0);\n return $top_shift;\n }\n\n // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore\n /**\n * Show footer of page. Need this->emetteur object\n *\n * @param TCPDF $pdf PDF\n * @param Facture $object Object to show\n * @param Translate $outputlangs Object lang for output\n * @param int $hidefreetext 1=Hide free text\n * @param int $heightforqrinvoice Height for QR invoices\n * @return int Return height of bottom margin including footer text\n */#VPDFC:1a70a6e54dd7a88e4fa45932ca280b90f349c9b435ce01e53995c7111152ecb7\n protected function _pagefoot(&$pdf, $object, $outputlangs, $hidefreetext = 0, $heightforqrinvoice = 0)\n {\n $showdetails = getDolGlobalInt(\'MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS\', 0);\n return pdf_pagefoot($pdf, $outputlangs, \'INVOICE_FREE_TEXT\', $this->emetteur, $heightforqrinvoice + $this->marge_basse, $this->marge_gauche, $this->page_hauteur, $object, $showdetails, $hidefreetext, $this->page_largeur, $this->watermark);\n }\n \n public function pdf_writelinedesc(&$pdf, $object, $i, $outputlangs, $w, $h, $posx, $posy, $hideref = 0, $hidedesc = 0, $issupplierline = 0)\n {\n global $db, $conf, $langs, $hookmanager;\n\n $reshook = 0;\n $result = \'\';\n //if (is_object($hookmanager) && ( (isset($object->lines[$i]->product_type) && $object->lines[$i]->product_type == 9 && !empty($object->lines[$i]->special_code)) || !empty($object->lines[$i]->fk_parent_line) ) )\n if (is_object($hookmanager)) { // Old code is commented on preceding line. Reproduct this test in the pdf_xxx function if you don\'t want your hook to run\n $special_code = empty($object->lines[$i]->special_code) ? \'\' : $object->lines[$i]->special_code;\n if (!empty($object->lines[$i]->fk_parent_line) && $object->lines[$i]->fk_parent_line > 0) {\n $special_code = $object->getSpecialCode($object->lines[$i]->fk_parent_line);\n }\n $parameters = array(\'pdf\'=>$pdf, \'i\'=>$i, \'outputlangs\'=>$outputlangs, \'w\'=>$w, \'h\'=>$h, \'posx\'=>$posx, \'posy\'=>$posy, \'hideref\'=>$hideref, \'hidedesc\'=>$hidedesc, \'issupplierline\'=>$issupplierline, \'special_code\'=>$special_code);\n $action = \'\';\n $reshook = $hookmanager->executeHooks(\'pdf_writelinedesc\', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks\n\n if (!empty($hookmanager->resPrint)) {\n $result .= $hookmanager->resPrint;\n }\n }\n if (empty($reshook)) {\n $labelproductservice = $this->pdf_getlinedesc($object, $i, $outputlangs, $hideref, $hidedesc, $issupplierline);\n\n //var_dump($labelproductservice);exit;\n\n // Fix bug of some HTML editors that replace links ]*src=\")([^\"]*)(&)([^\"]*\")/\', \'\\1\\2&\\4\', $labelproductservice, -1, $nbrep);\n\n //var_dump($labelproductservice);exit;\n\n // Description\n $pdf->writeHTMLCell($w, $h, $posx, $posy, $outputlangs->convToOutputCharset($labelproductservice), 0, 1, false, true, \'J\', true);\n $result .= $labelproductservice;\n }\n return $result;\n }\n \n function pdf_getlinedesc($object, $i, $outputlangs, $hideref = 0, $hidedesc = 0, $issupplierline = 0)\n {\n global $db, $conf, $langs;\n\n $idprod = (!empty($object->lines[$i]->fk_product) ? $object->lines[$i]->fk_product : false);\n $label = (!empty($object->lines[$i]->label) ? $object->lines[$i]->label : (!empty($object->lines[$i]->product_label) ? $object->lines[$i]->product_label : \'\'));\n $product_barcode = (!empty($object->lines[$i]->product_barcode) ? $object->lines[$i]->product_barcode : \"\");\n $desc = (!empty($object->lines[$i]->desc) ? $object->lines[$i]->desc : (!empty($object->lines[$i]->description) ? $object->lines[$i]->description : \'\'));\n $ref_supplier = (!empty($object->lines[$i]->ref_supplier) ? $object->lines[$i]->ref_supplier : (!empty($object->lines[$i]->ref_fourn) ? $object->lines[$i]->ref_fourn : \'\')); // TODO Not yet saved for supplier invoices, only supplier orders\n $note = (!empty($object->lines[$i]->note) ? $object->lines[$i]->note : \'\');\n $dbatch = (!empty($object->lines[$i]->detail_batch) ? $object->lines[$i]->detail_batch : false);\n\n $multilangsactive = getDolGlobalInt(\'MAIN_MULTILANGS\');\n\n if ($issupplierline) {\n include_once DOL_DOCUMENT_ROOT.\'/fourn/class/fournisseur.product.class.php\';\n $prodser = new ProductFournisseur($db);\n } else {\n include_once DOL_DOCUMENT_ROOT.\'/product/class/product.class.php\';\n $prodser = new Product($db);\n\n if (getDolGlobalString(\'PRODUIT_CUSTOMER_PRICES\') || getDolGlobalString(\'PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES\')) {\n include_once DOL_DOCUMENT_ROOT . \'/product/class/productcustomerprice.class.php\';\n }\n }\n\n //id\n $idprod = (!empty($object->lines[$i]->fk_product) ? $object->lines[$i]->fk_product : false);\n if ($idprod) {\n $prodser->fetch($idprod);\n //load multilangs\n if ($multilangsactive) {\n $prodser->getMultiLangs();\n $object->lines[$i]->multilangs = $prodser->multilangs;\n }\n }\n //label\n if (!empty($object->lines[$i]->label)) {\n $label = \'\'.$object->lines[$i]->label.\'\';\n } else {\n if (!empty($object->lines[$i]->multilangs[$outputlangs->defaultlang][\'label\']) && $multilangsactive) {\n $label = \'\'.$object->lines[$i]->multilangs[$outputlangs->defaultlang][\'label\'].\'\';\n } else {\n if (!empty($object->lines[$i]->product_label)) {\n $label = \'\'.$object->lines[$i]->product_label.\'\';\n } else {\n $label = \'\';\n }\n }\n }\n //description\n if (!empty($object->lines[$i]->desc)) {\n $desc = $object->lines[$i]->desc;\n } else {\n if (!empty($object->lines[$i]->multilangs[$outputlangs->defaultlang][\'description\']) && $multilangsactive) {\n $desc = $object->lines[$i]->multilangs[$outputlangs->defaultlang][\'description\'];\n } else {\n if (!empty($object->lines[$i]->description)) {\n $desc = $object->lines[$i]->description;\n } else {\n $desc = \'\';\n }\n }\n }\n //ref supplier\n $ref_supplier = (!empty($object->lines[$i]->ref_supplier) ? $object->lines[$i]->ref_supplier : (!empty($object->lines[$i]->ref_fourn) ? $object->lines[$i]->ref_fourn : \'\')); // TODO Not yet saved for supplier invoices, only supplier orders\n //note\n $note = (!empty($object->lines[$i]->note) ? $object->lines[$i]->note : \'\');\n //dbatch\n $dbatch = (!empty($object->lines[$i]->detail_batch) ? $object->lines[$i]->detail_batch : false);\n\n if ($idprod) {\n // If a predefined product and multilang and on other lang, we renamed label with label translated\n if ($multilangsactive && ($outputlangs->defaultlang != $langs->defaultlang)) {\n $translatealsoifmodified = getDolGlobalString(\'MAIN_MULTILANG_TRANSLATE_EVEN_IF_MODIFIED\'); // By default if value was modified manually, we keep it (no translation because we don\'t have it)\n\n // TODO Instead of making a compare to see if param was modified, check that content contains reference translation. If yes, add the added part to the new translation\n // ($textwasnotmodified is replaced with $textwasmodifiedorcompleted and we add completion).\n\n // Set label\n // If we want another language, and if label is same than default language (we did not force it to a specific value), we can use translation.\n //var_dump($outputlangs->defaultlang.\' - \'.$langs->defaultlang.\' - \'.$label.\' - \'.$prodser->label);exit;\n $textwasnotmodified = ($label == $prodser->label);\n if (!empty($prodser->multilangs[$outputlangs->defaultlang][\"label\"]) && ($textwasnotmodified || $translatealsoifmodified)) {\n $label = $prodser->multilangs[$outputlangs->defaultlang][\"label\"];\n }\n\n // Set desc\n // Manage HTML entities description test because $prodser->description is store with htmlentities but $desc no\n $textwasnotmodified = false;\n if (!empty($desc) && dol_textishtml($desc) && !empty($prodser->description) && dol_textishtml($prodser->description)) {\n $textwasnotmodified = (strpos(dol_html_entity_decode($desc, ENT_QUOTES | ENT_HTML5), dol_html_entity_decode($prodser->description, ENT_QUOTES | ENT_HTML5)) !== false);\n } else {\n $textwasnotmodified = ($desc == $prodser->description);\n }\n if (!empty($prodser->multilangs[$outputlangs->defaultlang][\"description\"])) {\n if ($textwasnotmodified) {\n $desc = str_replace($prodser->description, $prodser->multilangs[$outputlangs->defaultlang][\"description\"], $desc);\n } elseif ($translatealsoifmodified) {\n $desc = $prodser->multilangs[$outputlangs->defaultlang][\"description\"];\n }\n }\n\n // Set note\n $textwasnotmodified = ($note == $prodser->note_public);\n if (!empty($prodser->multilangs[$outputlangs->defaultlang][\"other\"]) && ($textwasnotmodified || $translatealsoifmodified)) {\n $note = $prodser->multilangs[$outputlangs->defaultlang][\"other\"];\n }\n }\n } elseif (($object->element == \'facture\' || $object->element == \'facturefourn\') && preg_match(\'/^\\(DEPOSIT\\).+/\', $desc)) { // We must not replace \'(DEPOSIT)\' when it is alone, it will be translated and detailed later\n $desc = str_replace(\'(DEPOSIT)\', $outputlangs->trans(\'Deposit\'), $desc);\n }\n\n $libelleproduitservice = \'\'; // Default value\n if (!getDolGlobalString(\'PDF_HIDE_PRODUCT_LABEL_IN_SUPPLIER_LINES\')) {\n // Description short of product line\n $libelleproduitservice = $label;\n if (!empty($libelleproduitservice) && getDolGlobalString(\'PDF_BOLD_PRODUCT_LABEL\')) {\n // Adding may convert the original string into a HTML string. So we have to first\n // convert \\n into
we text is not already HTML.\n if (!dol_textishtml($libelleproduitservice)) {\n $libelleproduitservice = str_replace(\"\\n\", \'
\', $libelleproduitservice);\n }\n $libelleproduitservice = \'\'.$libelleproduitservice.\'\';\n }\n }\n\n\n // Add ref of subproducts\n if (getDolGlobalString(\'SHOW_SUBPRODUCT_REF_IN_PDF\')) {\n $prodser->get_sousproduits_arbo();\n if (!empty($prodser->sousprods) && is_array($prodser->sousprods) && count($prodser->sousprods)) {\n $outputlangs->load(\'mrp\');\n $tmparrayofsubproducts = reset($prodser->sousprods);\n\n $qtyText = null;\n if (isset($object->lines[$i]->qty) && !empty($object->lines[$i]->qty)) {\n $qtyText = $object->lines[$i]->qty;\n } elseif (isset($object->lines[$i]->qty_shipped) && !empty($object->lines[$i]->qty_shipped)) {\n $qtyText = $object->lines[$i]->qty;\n }\n\n if (getDolGlobalString(\'MAIN_GENERATE_DOCUMENTS_HIDE_REF\')) {\n foreach ($tmparrayofsubproducts as $subprodval) {\n $libelleproduitservice = dol_concatdesc(\n dol_concatdesc($libelleproduitservice, \" * \".$subprodval[3]),\n (!empty($qtyText) ?\n $outputlangs->trans(\'Qty\').\':\'.$qtyText.\' x \'.$outputlangs->trans(\'AssociatedProducts\').\':\'.$subprodval[1].\'= \'.$outputlangs->trans(\'QtyTot\').\':\'.$subprodval[1] * $qtyText :\n $outputlangs->trans(\'Qty\').\' \'.$outputlangs->trans(\'AssociatedProducts\').\':\'.$subprodval[1])\n );\n }\n } else {\n foreach ($tmparrayofsubproducts as $subprodval) {\n $libelleproduitservice = dol_concatdesc(\n dol_concatdesc($libelleproduitservice, \" * \".$subprodval[5].(($subprodval[5] && $subprodval[3]) ? \' - \' : \'\').$subprodval[3]),\n (!empty($qtyText) ?\n $outputlangs->trans(\'Qty\').\':\'.$qtyText.\' x \'.$outputlangs->trans(\'AssociatedProducts\').\':\'.$subprodval[1].\'= \'.$outputlangs->trans(\'QtyTot\').\':\'.$subprodval[1] * $qtyText :\n $outputlangs->trans(\'Qty\').\' \'.$outputlangs->trans(\'AssociatedProducts\').\':\'.$subprodval[1])\n );\n }\n }\n }\n }\n\n if (isModEnabled(\'barcode\') && getDolGlobalString(\'MAIN_GENERATE_DOCUMENTS_SHOW_PRODUCT_BARCODE\') && !empty($product_barcode)) {\n $libelleproduitservice = dol_concatdesc($libelleproduitservice, $outputlangs->trans(\"BarCode\").\" \".$product_barcode);\n }\n\n // Description long of product line\n if (!empty($desc) && ($desc != $label)) {\n if ($desc == \'(CREDIT_NOTE)\' && $object->lines[$i]->fk_remise_except) {\n $discount = new DiscountAbsolute($db);\n $discount->fetch($object->lines[$i]->fk_remise_except);\n $sourceref = !empty($discount->discount_type) ? $discount->ref_invoice_supplier_source : $discount->ref_facture_source;\n $libelleproduitservice = $outputlangs->transnoentitiesnoconv(\"DiscountFromCreditNote\", $sourceref);\n } elseif ($desc == \'(DEPOSIT)\' && $object->lines[$i]->fk_remise_except) {\n $discount = new DiscountAbsolute($db);\n $discount->fetch($object->lines[$i]->fk_remise_except);\n $sourceref = !empty($discount->discount_type) ? $discount->ref_invoice_supplier_source : $discount->ref_facture_source;\n $libelleproduitservice = $outputlangs->transnoentitiesnoconv(\"DiscountFromDeposit\", $sourceref);\n // Add date of deposit\n if (getDolGlobalString(\'INVOICE_ADD_DEPOSIT_DATE\')) {\n $libelleproduitservice .= \' (\'.dol_print_date($discount->datec, \'day\', \'\', $outputlangs).\')\';\n }\n } elseif ($desc == \'(EXCESS RECEIVED)\' && $object->lines[$i]->fk_remise_except) {\n $discount = new DiscountAbsolute($db);\n $discount->fetch($object->lines[$i]->fk_remise_except);\n $libelleproduitservice = $outputlangs->transnoentitiesnoconv(\"DiscountFromExcessReceived\", $discount->ref_facture_source);\n } elseif ($desc == \'(EXCESS PAID)\' && $object->lines[$i]->fk_remise_except) {\n $discount = new DiscountAbsolute($db);\n $discount->fetch($object->lines[$i]->fk_remise_except);\n $libelleproduitservice = $outputlangs->transnoentitiesnoconv(\"DiscountFromExcessPaid\", $discount->ref_invoice_supplier_source);\n } else {\n if ($idprod) {\n // Check if description must be output\n if (!empty($object->element)) {\n $tmpkey = \'MAIN_DOCUMENTS_HIDE_DESCRIPTION_FOR_\'.strtoupper($object->element);\n if (getDolGlobalString($tmpkey)) {\n $hidedesc = 1;\n }\n }\n if (empty($hidedesc)) {\n if (getDolGlobalString(\'MAIN_DOCUMENTS_DESCRIPTION_FIRST\')) {\n $libelleproduitservice = dol_concatdesc($desc, $libelleproduitservice);\n } else {\n if (getDolGlobalString(\'HIDE_LABEL_VARIANT_PDF\') && $prodser->isVariant()) {\n $libelleproduitservice = $desc;\n } else {\n $libelleproduitservice = dol_concatdesc($libelleproduitservice, $desc);\n }\n }\n }\n } else {\n $libelleproduitservice = dol_concatdesc($libelleproduitservice, $desc);\n }\n }\n }\n\n // We add ref of product (and supplier ref if defined)\n $prefix_prodserv = \"\";\n $ref_prodserv = \"\";\n if (getDolGlobalString(\'PRODUCT_ADD_TYPE_IN_DOCUMENTS\')) { // In standard mode, we do not show this\n if ($prodser->isService()) {\n $prefix_prodserv = $outputlangs->transnoentitiesnoconv(\"Service\").\" \";\n } else {\n $prefix_prodserv = $outputlangs->transnoentitiesnoconv(\"Product\").\" \";\n }\n }\n\n if (empty($hideref)) {\n if ($issupplierline) {\n if (!getDolGlobalString(\'PDF_HIDE_PRODUCT_REF_IN_SUPPLIER_LINES\')) { // Common case\n $ref_prodserv = $prodser->ref; // Show local ref\n if ($ref_supplier) {\n $ref_prodserv .= ($prodser->ref ? \' (\' : \'\').$outputlangs->transnoentitiesnoconv(\"SupplierRef\").\' \'.$ref_supplier.($prodser->ref ? \')\' : \'\');\n }\n } elseif (getDolGlobalInt(\'PDF_HIDE_PRODUCT_REF_IN_SUPPLIER_LINES\') == 1) {\n $ref_prodserv = $ref_supplier;\n } elseif (getDolGlobalInt(\'PDF_HIDE_PRODUCT_REF_IN_SUPPLIER_LINES\') == 2) {\n $ref_prodserv = $ref_supplier.\' (\'.$outputlangs->transnoentitiesnoconv(\"InternalRef\").\' \'.$prodser->ref.\')\';\n }\n } else {\n $ref_prodserv = $prodser->ref; // Show local ref only\n\n if (getDolGlobalString(\'PRODUIT_CUSTOMER_PRICES\') || getDolGlobalString(\'PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES\')) {\n $productCustomerPriceStatic = new ProductCustomerPrice($db);\n $filter = array(\'fk_product\' => (string) $idprod, \'fk_soc\' => (string) $object->socid);\n\n $nbCustomerPrices = $productCustomerPriceStatic->fetchAll(\'\', \'\', 1, 0, $filter);\n\n if ($nbCustomerPrices > 0) {\n $productCustomerPrice = null;\n if (count($productCustomerPriceStatic->lines) > 0) {\n $date_now = (int) floor(dol_now() / 86400) * 86400; // date without hours\n foreach ($productCustomerPriceStatic->lines as $k => $custprice_line) {\n if ($custprice_line->date_begin <= $date_now && (empty($custprice_line->date_end) || $date_now <= $custprice_line->date_end)) {\n $productCustomerPrice = $custprice_line;\n break;\n }\n }\n }\n\n if (isset($productCustomerPrice) && !empty($productCustomerPrice->ref_customer)) {\n switch ($conf->global->PRODUIT_CUSTOMER_PRICES_PDF_REF_MODE) {\n case 1:\n $ref_prodserv = $productCustomerPrice->ref_customer;\n break;\n\n case 2:\n $ref_prodserv = $productCustomerPrice->ref_customer . \' (\' . $outputlangs->transnoentitiesnoconv(\'InternalRef\') . \' \' . $ref_prodserv . \')\';\n break;\n\n default:\n $ref_prodserv = $ref_prodserv . \' (\' . $outputlangs->transnoentitiesnoconv(\'RefCustomer\') . \' \' . $productCustomerPrice->ref_customer . \')\';\n }\n }\n }\n }\n }\n\n if (!empty($libelleproduitservice) && !empty($ref_prodserv)) {\n $ref_prodserv .= \" - \";\n }\n }\n\n if (!empty($ref_prodserv) && getDolGlobalString(\'PDF_BOLD_PRODUCT_REF_AND_PERIOD\')) {\n if (!dol_textishtml($libelleproduitservice)) {\n $libelleproduitservice = str_replace(\"\\n\", \'
\', $libelleproduitservice);\n }\n $ref_prodserv = \'\'.$ref_prodserv.\'\';\n // $prefix_prodserv and $ref_prodser are not HTML var\n }\n $libelleproduitservice = $prefix_prodserv.$ref_prodserv.$libelleproduitservice;\n\n // Add an additional description for the category products\n if (getDolGlobalString(\'CATEGORY_ADD_DESC_INTO_DOC\') && $idprod && isModEnabled(\'category\')) {\n include_once DOL_DOCUMENT_ROOT.\'/categories/class/categorie.class.php\';\n $categstatic = new Categorie($db);\n // recovering the list of all the categories linked to product\n $tblcateg = $categstatic->containing($idprod, Categorie::TYPE_PRODUCT);\n foreach ($tblcateg as $cate) {\n // Adding the descriptions if they are filled\n $desccateg = $cate->description;\n if ($desccateg) {\n $libelleproduitservice = dol_concatdesc($libelleproduitservice, $desccateg);\n }\n }\n }\n\n if (!empty($object->lines[$i]->date_start) || !empty($object->lines[$i]->date_end)) {\n $format = \'day\';\n $period = \'\';\n // Show duration if exists\n if ($object->lines[$i]->date_start && $object->lines[$i]->date_end) {\n $period = \'(\'.$outputlangs->transnoentitiesnoconv(\'DateFromTo\', dol_print_date($object->lines[$i]->date_start, $format, false, $outputlangs), dol_print_date($object->lines[$i]->date_end, $format, false, $outputlangs)).\')\';\n }\n if ($object->lines[$i]->date_start && !$object->lines[$i]->date_end) {\n $period = \'(\'.$outputlangs->transnoentitiesnoconv(\'DateFrom\', dol_print_date($object->lines[$i]->date_start, $format, false, $outputlangs)).\')\';\n }\n if (!$object->lines[$i]->date_start && $object->lines[$i]->date_end) {\n $period = \'(\'.$outputlangs->transnoentitiesnoconv(\'DateUntil\', dol_print_date($object->lines[$i]->date_end, $format, false, $outputlangs)).\')\';\n }\n //print \'>\'.$outputlangs->charset_output.\',\'.$period;\n if (getDolGlobalString(\'PDF_BOLD_PRODUCT_REF_AND_PERIOD\')) {\n if (!dol_textishtml($libelleproduitservice)) {\n $libelleproduitservice = str_replace(\"\\n\", \'
\', $libelleproduitservice);\n }\n $libelleproduitservice .= \'
\'.$period.\'\';\n } else {\n $libelleproduitservice = dol_concatdesc($libelleproduitservice, $period);\n }\n //print $libelleproduitservice;\n }\n\n // Show information for lot\n if (!empty($dbatch)) {\n // $object is a shipment.\n //var_dump($object->lines[$i]->details_entrepot); // array from llx_expeditiondet (we can have several lines for one fk_origin_line)\n //var_dump($object->lines[$i]->detail_batch); // array from llx_expeditiondet_batch (each line with a lot is linked to llx_expeditiondet)\n\n include_once DOL_DOCUMENT_ROOT.\'/product/stock/class/entrepot.class.php\';\n include_once DOL_DOCUMENT_ROOT.\'/product/class/productbatch.class.php\';\n $tmpwarehouse = new Entrepot($db);\n $tmpproductbatch = new Productbatch($db);\n\n $format = \'day\';\n foreach ($dbatch as $detail) {\n $dte = array();\n if ($detail->eatby) {\n $dte[] = $outputlangs->transnoentitiesnoconv(\'printEatby\', dol_print_date($detail->eatby, $format, false, $outputlangs));\n }\n if ($detail->sellby) {\n $dte[] = $outputlangs->transnoentitiesnoconv(\'printSellby\', dol_print_date($detail->sellby, $format, false, $outputlangs));\n }\n if ($detail->batch) {\n $dte[] = $outputlangs->transnoentitiesnoconv(\'printBatch\', $detail->batch);\n }\n if ($detail->qty) {\n $dte[] = $outputlangs->transnoentitiesnoconv(\'printQty\', (string) $detail->qty);\n }\n\n // Add also info of planned warehouse for lot\n if ($object->element == \'shipping\' && $detail->fk_origin_stock > 0 && getDolGlobalInt(\'PRODUCTBATCH_SHOW_WAREHOUSE_ON_SHIPMENT\')) {\n $resproductbatch = $tmpproductbatch->fetch($detail->fk_origin_stock);\n if ($resproductbatch > 0) {\n $reswarehouse = $tmpwarehouse->fetch($tmpproductbatch->warehouseid);\n if ($reswarehouse > 0) {\n $dte[] = $tmpwarehouse->ref;\n }\n }\n }\n\n $libelleproduitservice .= \"__N__ \".implode(\" - \", $dte);\n }\n } else {\n if (getDolGlobalInt(\'PRODUCTBATCH_SHOW_WAREHOUSE_ON_SHIPMENT\')) {\n // TODO Show warehouse for shipment line without batch\n }\n }\n\n // Now we convert \\n into br\n if (dol_textishtml($libelleproduitservice)) {\n $libelleproduitservice = preg_replace(\'/__N__/\', \'
\', $libelleproduitservice);\n } else {\n $libelleproduitservice = preg_replace(\'/__N__/\', \"\\n\", $libelleproduitservice);\n }\n $libelleproduitservice = dol_htmlentitiesbr($libelleproduitservice, 1);\n\n return $libelleproduitservice;\n }\n}\n'); -/*!40000 ALTER TABLE `llx_verifactudolibarr_add_tpls` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `llx_verifactudolibarr_facture_log` --- - -DROP TABLE IF EXISTS `llx_verifactudolibarr_facture_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8mb4 */; -CREATE TABLE `llx_verifactudolibarr_facture_log` ( - `rowid` int(10) unsigned NOT NULL AUTO_INCREMENT, - `facture` varchar(22) DEFAULT NULL, - `datec` datetime DEFAULT NULL, - `fk_user_author` varchar(22) DEFAULT NULL, - `processed` enum('SI','NO') DEFAULT 'NO', - `processed_date` datetime DEFAULT NULL, - `error_log` enum('SI','NO') DEFAULT NULL, - `send_data` longtext DEFAULT NULL, - `response_data` mediumtext DEFAULT NULL, - PRIMARY KEY (`rowid`) USING BTREE -) ENGINE=MyISAM AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='Almacena el log de envío a la integración'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `llx_verifactudolibarr_facture_log` --- - -LOCK TABLES `llx_verifactudolibarr_facture_log` WRITE; -/*!40000 ALTER TABLE `llx_verifactudolibarr_facture_log` DISABLE KEYS */; -INSERT INTO `llx_verifactudolibarr_facture_log` VALUES -(1,'464','2025-11-11 13:18:13','','SI','2025-11-11 13:18:14','NO','\n44890487FMarta Molina PérezES2025-F0242025-11-11Documento verificable2025-11-11F1S19530.0010.00953.0001S14200.0010.00420.0001S1375.0010.0037.5001S1325.0010.0032.5001S1700.0010.0070.0001S11200.0010.00120.0001S1190.0010.0019.0001S1250.0010.0025.0001S1360.0010.0036.0001S11650.0010.00165.0001S1600.0010.0060.0001S1890.0010.0089.0001S1850.0010.0085.0001S1400.0010.0040.0001S1400.0010.0040.0001S1315.0010.0031.5001S14900.0010.00490.0001S15976.0010.00597.6001S11404.0010.00140.4001S12500.0010.00250.0001S1-27727.2710.00-2772.7301S1-9090.9110.00-909.0901S1-196.8210.00-19.68010.000.00\n','{\"success\":true,\"data\":{\"verifactuUrl\":\"https:\\/\\/prewww2.aeat.es\\/wlpl\\/TIKE-CONT\\/ValidarQR?nif=B40529687&numserie=2025-F024&fecha=11-11-2025&importe=0.00\",\"queueId\":1}}'), -(2,'465','2025-11-25 07:23:46','','SI','2025-11-25 07:23:47','NO','\n24390713HVictor Guillermo Cortes TronchES2025-F0252025-11-25Documento verificable2025-11-25F1S13550.0021.00745.5001S11125.0021.00236.2501S1700.0021.00147.0001S1850.0021.00178.5001S16900.0021.001449.0001S12660.0021.00558.6001S1560.0021.00117.6001S160.0021.0012.6001S13680.0021.00772.8001S13200.0021.00672.0001S15800.0021.001218.0001S11361.350.000.0001S1-3.4721.00-0.7301S1-5785.1221.00-1214.8801S1-16157.0321.00-3392.9801S1-7139.3821.00-1499.2601S1-1361.350.000.00010.000.00\n','{\"success\":true,\"data\":{\"verifactuUrl\":\"https:\\/\\/prewww2.aeat.es\\/wlpl\\/TIKE-CONT\\/ValidarQR?nif=B40529687&numserie=2025-F025&fecha=25-11-2025&importe=0.00\",\"queueId\":2}}'), -(3,'466','2025-11-26 12:19:21','','SI','2025-11-26 12:19:22','NO','\n48388162AIgnacio Navarrete DualdeES2025-F0262025-11-26Documento verificable2025-11-26F1S11710.0021.00359.1001S16000.0021.001260.0001S12370.0021.00497.7001S1660.0021.00138.6001S1660.0021.00138.6001S12085.0021.00437.8501S1799.2021.00167.8301S18228.2421.001727.9301S1750.0021.00157.5001S11265.0021.00265.6501S11420.0021.00298.2001S1667.0021.00140.0701S1120.0021.0025.2001S12300.0021.00483.0001S1650.0021.00136.5001S1295.0021.0061.9501S195.0021.0019.9501S11400.0021.00294.0001S1840.0021.00176.4001S145.0021.009.4501S1711.7221.00149.4601S1280.0021.0058.8001S1194.0021.0040.7401S1198.0021.0041.5801S176.0021.0015.9601S176.0021.0015.9601S1634.610.000.0001S1-535.3321.00-112.4201S1-634.610.000.0001S1-33359.8321.00-7005.56010.000.00\n','{\"success\":true,\"data\":{\"verifactuUrl\":\"https:\\/\\/prewww2.aeat.es\\/wlpl\\/TIKE-CONT\\/ValidarQR?nif=B40529687&numserie=2025-F026&fecha=26-11-2025&importe=0.00\",\"queueId\":3}}'), -(4,'469','2025-11-26 16:26:20','','SI','2025-11-26 16:26:22','NO','\n48388162AIgnacio Navarrete DualdeES2025-F0272025-11-26Documento verificable2025-11-26F1S1-1710.0021.00-359.1001S1-6000.0021.00-1260.0001S1-2370.0021.00-497.7001S1-660.0021.00-138.6001S1-660.0021.00-138.6001S1-2085.0021.00-437.8501S1-799.2021.00-167.8301S1-8228.2421.00-1727.9301S1-750.0021.00-157.5001S1-1265.0021.00-265.6501S1-1420.0021.00-298.2001S1-667.0021.00-140.0701S1-120.0021.00-25.2001S1-2300.0021.00-483.0001S1-650.0021.00-136.5001S1-295.0021.00-61.9501S1-95.0021.00-19.9501S1-1400.0021.00-294.0001S1-840.0021.00-176.4001S1-45.0021.00-9.4501S1-711.7221.00-149.4601S1-280.0021.00-58.8001S1-194.0021.00-40.7401S1-198.0021.00-41.5801S1-76.0021.00-15.9601S1-76.0021.00-15.9601S1-634.610.000.0001S1535.3321.00112.4201S1634.610.000.0001S133359.8321.007005.56010.000.00\n','{\"success\":true,\"data\":{\"verifactuUrl\":\"https:\\/\\/prewww2.aeat.es\\/wlpl\\/TIKE-CONT\\/ValidarQR?nif=B40529687&numserie=2025-F027&fecha=26-11-2025&importe=0.00\",\"queueId\":4}}'), -(5,'470','2025-11-26 16:28:57','','SI','2025-11-26 16:28:58','NO','\n24373623VJuana Mª Alegría LlovetES2025-F0282025-11-26Documento verificable2025-11-26F1S11710.0021.00359.1001S16000.0021.001260.0001S12370.0021.00497.7001S1660.0021.00138.6001S1660.0021.00138.6001S12085.0021.00437.8501S1799.2021.00167.8301S18228.2421.001727.9301S1750.0021.00157.5001S11265.0021.00265.6501S11420.0021.00298.2001S1667.0021.00140.0701S1120.0021.0025.2001S12300.0021.00483.0001S1650.0021.00136.5001S1295.0021.0061.9501S195.0021.0019.9501S11400.0021.00294.0001S1840.0021.00176.4001S145.0021.009.4501S1711.7221.00149.4601S1280.0021.0058.8001S1194.0021.0040.7401S1198.0021.0041.5801S176.0021.0015.9601S176.0021.0015.9601S1634.610.000.0001S1-535.3321.00-112.4201S1-634.610.000.0001S1-33359.8321.00-7005.56010.000.00\n','{\"success\":true,\"data\":{\"verifactuUrl\":\"https:\\/\\/prewww2.aeat.es\\/wlpl\\/TIKE-CONT\\/ValidarQR?nif=B40529687&numserie=2025-F028&fecha=26-11-2025&importe=0.00\",\"queueId\":5}}'), -(6,'472','2025-11-26 18:39:59','','SI','2025-11-26 18:40:00','NO','\n44896705SRosa García DosdáES2025-F0292025-11-26Documento verificable2025-11-26F1S18256.4021.001733.8401S1900.0021.00189.0001S11235.0021.00259.3501S1990.0021.00207.9001S1925.0021.00194.2501S1350.0021.0073.5001S1285.0021.0059.8501S1450.0021.0094.5001S1200.0021.0042.0001S1120.0021.0025.2001S12950.0021.00619.5001S11300.0021.00273.0001S1735.0021.00154.3501S1360.0021.0075.6001S1495.0021.00103.9501S1570.0021.00119.7001S11141.750.000.0001S1610.0021.00128.1001S11400.0021.00294.0001S13386.4021.00711.1401S1-15.1221.00-3.1801S1-9917.3621.00-2082.6401S1-15585.3321.00-3272.9101S1-1141.740.000.00010.000.00\n','{\"success\":true,\"data\":{\"verifactuUrl\":\"https:\\/\\/prewww2.aeat.es\\/wlpl\\/TIKE-CONT\\/ValidarQR?nif=B40529687&numserie=2025-F029&fecha=26-11-2025&importe=0.00\",\"queueId\":6}}'), -(7,'473','2025-11-26 19:32:44','','SI','2025-11-26 19:32:46','NO','\n44896705SRosa García DosdáES2025-F0302025-11-26Documento verificable2025-11-26F1S114469.0010.001446.9001S13835.0010.00383.5001S14900.0010.00490.0001S11170.0010.00117.0001S15490.0010.00549.0001S11215.0010.00121.5001S11456.0010.00145.6001S1350.0010.0035.0001S159.5010.005.9501S11377.0010.00137.7001S1588.0010.0058.8001S1582.0010.0058.2001S1186.0010.0018.6001S1200.0010.0020.0001S11000.0010.00100.0001S1380.0010.0038.0001S1620.0010.0062.0001S12500.0010.00250.0001S11900.0010.00190.0001S1833.6010.0083.3601S1663.0010.0066.3001S1148.6010.0014.8601S1204.0010.0020.4001S1108.0010.0010.8001S1564.0010.0056.4001S1780.0010.0078.0001S1359.0010.0035.9001S16070.0010.00607.0001S1-147.4921.00-30.9801S12700.0010.00270.0001S1-9090.9110.00-909.0901S1-31818.1810.00-3181.820115000.001348.88\n','{\"success\":true,\"data\":{\"verifactuUrl\":\"https:\\/\\/prewww2.aeat.es\\/wlpl\\/TIKE-CONT\\/ValidarQR?nif=B40529687&numserie=2025-F030&fecha=26-11-2025&importe=15000.00\",\"queueId\":7}}'), -(8,'474','2025-12-23 09:14:59','','SI','2025-12-23 09:15:00','SI','\n24362205FAngel Brazales MarcoES2025-F0312025-12-23Documento verificable2025-12-23F1S1371.9021.0078.1001450.0078.10\n','null'), -(9,'474','2025-12-23 09:15:29','','SI','2025-12-23 09:15:29','SI','\n24362205FAngel Brazales MarcoES2025-F0312025-12-23Documento verificable2025-12-23F1S1371.9021.0078.1001450.0078.10\n','null'), -(10,'474','2025-12-23 09:15:53','','SI','2025-12-23 09:15:53','SI','\n24362205FAngel Brazales MarcoES2025-F0312025-12-23Documento verificable2025-12-23F1S1371.9021.0078.1001450.0078.10\n','null'), -(11,'474','2025-12-23 09:17:09','','SI','2025-12-23 09:17:09','SI','\n24362205FAngel Brazales MarcoES2025-F0312025-12-23Documento verificable2025-12-23F1S1371.9021.0078.1001450.0078.10\n','null'), -(12,'474','2025-12-23 09:19:52','','SI','2025-12-23 09:19:53','SI','\n24362205FAngel Brazales MarcoES2025-F0312025-12-23Documento verificable2025-12-23F1S1371.9021.0078.1001450.0078.10\n','null'), -(13,'475','2025-12-23 09:25:05','','SI','2025-12-23 09:25:06','SI','\n19984467CAdelina Boltá EscolanoES2025-F0312025-12-23Documento verificable2025-12-23F1S1578.5121.00121.4901700.00121.49\n','null'); -/*!40000 ALTER TABLE `llx_verifactudolibarr_facture_log` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `llx_verifactudolibarr_messages` --- - -DROP TABLE IF EXISTS `llx_verifactudolibarr_messages`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8mb4 */; -CREATE TABLE `llx_verifactudolibarr_messages` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `Message` text DEFAULT NULL, - `date` datetime DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `llx_verifactudolibarr_messages` --- - -LOCK TABLES `llx_verifactudolibarr_messages` WRITE; -/*!40000 ALTER TABLE `llx_verifactudolibarr_messages` DISABLE KEYS */; -/*!40000 ALTER TABLE `llx_verifactudolibarr_messages` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `llx_verifactudolibarr_tax_type` --- - -DROP TABLE IF EXISTS `llx_verifactudolibarr_tax_type`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8mb4 */; -CREATE TABLE `llx_verifactudolibarr_tax_type` ( - `rowid` int(11) NOT NULL AUTO_INCREMENT, - `tva_id` int(11) NOT NULL, - `veri_type` varchar(11) NOT NULL, - PRIMARY KEY (`rowid`) -) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `llx_verifactudolibarr_tax_type` --- - -LOCK TABLES `llx_verifactudolibarr_tax_type` WRITE; -/*!40000 ALTER TABLE `llx_verifactudolibarr_tax_type` DISABLE KEYS */; -INSERT INTO `llx_verifactudolibarr_tax_type` VALUES -(1,44,'01'), -(2,43,'01'), -(3,42,'01'), -(4,41,'01'); -/*!40000 ALTER TABLE `llx_verifactudolibarr_tax_type` ENABLE KEYS */; -UNLOCK TABLES; - -- -- Table structure for table `llx_webhook_history` -- From 917c2d6be860a3ccc4ef73bc8f76a80e78c4ba7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnter=20Lukas?= Date: Mon, 16 Feb 2026 20:12:21 +0100 Subject: [PATCH 30/39] Add check for empty delivery date before setting (#37254) --- htdocs/fourn/commande/card.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/htdocs/fourn/commande/card.php b/htdocs/fourn/commande/card.php index df24bc83789..11e5b7fdab3 100644 --- a/htdocs/fourn/commande/card.php +++ b/htdocs/fourn/commande/card.php @@ -1358,8 +1358,10 @@ if (empty($reshook)) { dol_syslog("Try to find source object origin=".$object->origin." originid=".$object->origin_id." to add lines"); $result = $srcobject->fetch($object->origin_id); if ($result > 0) { - $tmpdate = $srcobject->delivery_date; - $object->setDeliveryDate($user, $tmpdate); + if (empty($object->delivery_date)) { + $tmpdate = $srcobject->delivery_date; + $object->setDeliveryDate($user, $tmpdate); + } $object->set_id_projet($user, $srcobject->fk_project); $lines = $srcobject->lines; From ecf01bacd7a2a8b656a74a5cfba375375d526a64 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 Feb 2026 20:25:07 +0100 Subject: [PATCH 31/39] Fix CI --- .../blockedlog/admin/blockedlog_archives.php | 30 ++++++++++--------- htdocs/contact/agenda.php | 2 +- htdocs/contrat/agenda.php | 2 +- .../template/myobject_agenda.php | 2 +- htdocs/product/agenda.php | 2 +- htdocs/projet/agenda.php | 4 +-- htdocs/societe/agenda.php | 2 +- 7 files changed, 23 insertions(+), 21 deletions(-) diff --git a/htdocs/blockedlog/admin/blockedlog_archives.php b/htdocs/blockedlog/admin/blockedlog_archives.php index c6d711a04c1..700adef9973 100644 --- a/htdocs/blockedlog/admin/blockedlog_archives.php +++ b/htdocs/blockedlog/admin/blockedlog_archives.php @@ -376,6 +376,7 @@ if ($action == 'export' && $user->hasRight('blockedlog', 'read')) { // read is $concatenateddata = $block_static->buildKeyForSignature(); // Version line archive VE1=sha256 + $signatureexport = ''; if ($formatexport == 'VE1') { // Note: The signature on export line is not used. It has been replaced with a global signature on all file. $signatureexport = dol_hash($previoushashexport.$concatenateddata, 'sha256'); @@ -777,6 +778,7 @@ if ($action == 'check' || $action == 'checkconfirmed') { $reg = array(); $period = ''; + $regnumber = ''; $formatexport = ''; if (preg_match('/\speriod=([^\s]+)/', $line, $reg)) { $period = $reg[1]; // Get period on first line @@ -884,24 +886,24 @@ if ($action == 'check' || $action == 'checkconfirmed') { $lineanalyzed = 1; $linetech = $line[0]; - $block_static->id = $line[1]; - $block_static->date_creation = $line[2]; - $block_static->action = $lineactioncode = $line[3]; - $block_static->module_source = $line[4]; + $block_static->id = (int) $line[1]; + $block_static->date_creation = (string) $line[2]; + $block_static->action = $lineactioncode = (string) $line[3]; + $block_static->module_source = (string) $line[4]; $block_static->amounts_taxexcl = $lineamountht = $line[5]; $block_static->amounts = $lineamountttc = $line[6]; - $block_static->ref_object = $lineref = $line[7]; - $block_static->date_object = $line[8]; - $block_static->user_fullname = $line[9]; - $block_static->linktoref = $line[10]; - $block_static->linktype = $line[11]; - $block_static->object_data = json_decode($line[12]); - $block_static->object_version = $line[13]; - $block_static->object_format = $line[14]; - $block_static->signature = $line[15]; + $block_static->ref_object = $lineref = (string) $line[7]; + $block_static->date_object = (string) $line[8]; + $block_static->user_fullname = (string) $line[9]; + $block_static->linktoref = (string) $line[10]; + $block_static->linktype = (string) $line[11]; + $block_static->object_data = json_decode((string) $line[12]); + $block_static->object_version = (string) $line[13]; + $block_static->object_format = (string) $line[14]; + $block_static->signature = (string) $line[15]; // Status from file: 'Valid' or 'KO' - $statusline = $line[16]; + $statusline = (string) $line[16]; // Status revalidated from calculation using the HMAC secret key (possible only when we are on the same instance than // the one hosting the initial database of the archive) diff --git a/htdocs/contact/agenda.php b/htdocs/contact/agenda.php index edcc64b8f94..35e64572768 100644 --- a/htdocs/contact/agenda.php +++ b/htdocs/contact/agenda.php @@ -324,7 +324,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { $param .= '&search_complete='.urlencode($search_complete); } if ($search_filtert != '') { - $param .= '&search_filtert='.urlencode($search_filtert); + $param .= '&search_filtert='.urlencode((string) $search_filtert); } if ($search_dateevent_start != '') { $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); diff --git a/htdocs/contrat/agenda.php b/htdocs/contrat/agenda.php index b0a7fa92d62..438b11096dc 100644 --- a/htdocs/contrat/agenda.php +++ b/htdocs/contrat/agenda.php @@ -307,7 +307,7 @@ if ($object->id > 0) { $param .= '&search_complete='.urlencode($search_complete); } if ($search_filtert != '') { - $param .= '&search_filtert='.urlencode($search_filtert); + $param .= '&search_filtert='.urlencode((string) $search_filtert); } if ($search_dateevent_start != '') { $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); diff --git a/htdocs/modulebuilder/template/myobject_agenda.php b/htdocs/modulebuilder/template/myobject_agenda.php index 712735c0328..b93a08f2ede 100644 --- a/htdocs/modulebuilder/template/myobject_agenda.php +++ b/htdocs/modulebuilder/template/myobject_agenda.php @@ -339,7 +339,7 @@ if ($object->id > 0) { $param .= '&search_complete='.urlencode($search_complete); } if ($search_filtert != '') { - $param .= '&search_filtert='.urlencode($search_filtert); + $param .= '&search_filtert='.urlencode((string) $search_filtert); } if ($search_dateevent_start != '') { $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); diff --git a/htdocs/product/agenda.php b/htdocs/product/agenda.php index 3bfeda941b0..9b534ad266f 100644 --- a/htdocs/product/agenda.php +++ b/htdocs/product/agenda.php @@ -251,7 +251,7 @@ if (isModEnabled('agenda') && ($user->hasRight('agenda', 'myactions', 'read') || $param .= '&search_complete='.urlencode($search_complete); } if ($search_filtert != '') { - $param .= '&search_filtert='.urlencode($search_filtert); + $param .= '&search_filtert='.urlencode((string) $search_filtert); } if ($search_dateevent_start != '') { $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); diff --git a/htdocs/projet/agenda.php b/htdocs/projet/agenda.php index 9456df8ff7b..2ccbd2320d3 100644 --- a/htdocs/projet/agenda.php +++ b/htdocs/projet/agenda.php @@ -245,7 +245,7 @@ if (!empty($object->id)) { $param .= '&search_complete='.urlencode($search_complete); } if ($search_filtert != '') { - $param .= '&search_filtert='.urlencode($search_filtert); + $param .= '&search_filtert='.urlencode((string) $search_filtert); } if ($search_dateevent_start != '') { $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); @@ -258,7 +258,7 @@ if (!empty($object->id)) { $param .= '&dateevent_endday='.GETPOSTINT('dateevent_endday'); } - + // Try to know count of actioncomm from cache require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php'; $cachekey = 'count_events_project_'.$object->id; $nbEvent = dol_getcache($cachekey); diff --git a/htdocs/societe/agenda.php b/htdocs/societe/agenda.php index 9dbcb742990..6af9aff6b68 100644 --- a/htdocs/societe/agenda.php +++ b/htdocs/societe/agenda.php @@ -241,7 +241,7 @@ if (isModEnabled('agenda') && ($user->hasRight('agenda', 'myactions', 'read') || $param .= '&search_complete='.urlencode($search_complete); } if ($search_filtert != '') { - $param .= '&search_filtert='.urlencode($search_filtert); + $param .= '&search_filtert='.urlencode((string) $search_filtert); } if ($search_dateevent_start != '') { $param .= '&dateevent_startyear='.GETPOSTINT('dateevent_startyear'); From 45483a6fd91a870f1bbccdab3901f9295267f333 Mon Sep 17 00:00:00 2001 From: demiton Date: Mon, 16 Feb 2026 20:26:20 +0100 Subject: [PATCH 32/39] FIX #37246 Modifying resteapayer calculation for credit note (#37247) --- htdocs/compta/facture/class/api_invoices.class.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/htdocs/compta/facture/class/api_invoices.class.php b/htdocs/compta/facture/class/api_invoices.class.php index 369b7c53d58..cd653ce48de 100644 --- a/htdocs/compta/facture/class/api_invoices.class.php +++ b/htdocs/compta/facture/class/api_invoices.class.php @@ -1708,7 +1708,6 @@ class Invoices extends DolibarrApi $totalpaid = $this->invoice->getSommePaiement(); $totalcreditnotes = $this->invoice->getSumCreditNotesUsed(); $totaldeposits = $this->invoice->getSumDepositsUsed(); - $resteapayer = price2num($this->invoice->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT'); $this->db->begin(); @@ -1717,13 +1716,13 @@ class Invoices extends DolibarrApi // Clean parameters amount if payment is for a credit note if ($this->invoice->type == Facture::TYPE_CREDIT_NOTE) { - $resteapayer = price2num($resteapayer, 'MT'); + $resteapayer = price2num($this->invoice->total_ttc + $totalpaid - $totalcreditnotes - $totaldeposits, 'MT'); $amounts[$id] = (float) price2num(-1 * abs((float) $resteapayer), 'MT'); // Multicurrency $newvalue = price2num($this->invoice->multicurrency_total_ttc, 'MT'); $multicurrency_amounts[$id] = (float) price2num(-1 * (float) $newvalue, 'MT'); } else { - $resteapayer = price2num($resteapayer, 'MT'); + $resteapayer = price2num($this->invoice->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT'); $amounts[$id] = (float) $resteapayer; // Multicurrency $newvalue = price2num($this->invoice->multicurrency_total_ttc, 'MT'); From 8fde66127c3b66c91f6dd68da982e6b67d7f4de1 Mon Sep 17 00:00:00 2001 From: Jyhere Date: Mon, 16 Feb 2026 20:26:58 +0100 Subject: [PATCH 33/39] Add 'type' parameter to completeTabsHead hook (#37235) --- htdocs/core/lib/functions.lib.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index bf21c1f6da3..6715071466a 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -12798,7 +12798,7 @@ function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, // No need to make a return $head. Var is modified as a reference if (!empty($hookmanager)) { - $parameters = array('object' => $object, 'mode' => $mode, 'head' => &$head, 'filterorigmodule' => $filterorigmodule); + $parameters = array('object' => $object, 'mode' => $mode, 'head' => &$head, 'filterorigmodule' => $filterorigmodule, 'type' => $type); // @phan-suppress-next-line PhanTypeMismatchArgumentNullable $reshook = $hookmanager->executeHooks('completeTabsHead', $parameters, $object); if ($reshook > 0) { // Hook ask to replace completely the array From a544af5d02d484d6b284639bac59ad15884fb6d5 Mon Sep 17 00:00:00 2001 From: Delthair <41671350+Delthair@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:29:44 +0100 Subject: [PATCH 34/39] #37257 FIX for piesemicircle (#37258) --- htdocs/core/class/dolgraph.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htdocs/core/class/dolgraph.class.php b/htdocs/core/class/dolgraph.class.php index 2d17e02ec1c..c1e28930a37 100644 --- a/htdocs/core/class/dolgraph.class.php +++ b/htdocs/core/class/dolgraph.class.php @@ -1335,8 +1335,8 @@ class DolGraph if ($this->type[$firstlot] == 'piesemicircle') { - $this->stringtoshow .= 'circumference: Math.PI,' . "\n"; - $this->stringtoshow .= 'rotation: -Math.PI,' . "\n"; + $this->stringtoshow .= 'circumference: 180,' . "\n"; + $this->stringtoshow .= 'rotation: -90,' . "\n"; } $this->stringtoshow .= 'elements: { arc: {' . "\n"; // Color of each arc From 7a73ad95663413920405612ae7c878b78c0098a2 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 Feb 2026 20:43:36 +0100 Subject: [PATCH 35/39] Fix #37227 Fix #37233 --- htdocs/blockedlog/class/blockedlog.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/blockedlog/class/blockedlog.class.php b/htdocs/blockedlog/class/blockedlog.class.php index 2d1040ac9cb..6c0efef5010 100644 --- a/htdocs/blockedlog/class/blockedlog.class.php +++ b/htdocs/blockedlog/class/blockedlog.class.php @@ -1151,7 +1151,7 @@ class BlockedLog { $aaa = null; try { - $aaa = (object) jsonOrUnserialize($data); + $aaa = (object) jsonOrUnserialize($data, false); } catch (Exception $e) { // print $e->getErrs); } From 02f23d5193f6c8d056b4054d0ba0a0df4cc4a498 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 16 Feb 2026 20:54:57 +0100 Subject: [PATCH 36/39] Fix #37227 Fix #37233 --- htdocs/core/lib/functions.lib.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index 0db6dc81f21..1a31f743ab7 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -15632,11 +15632,12 @@ function showValueWithClipboardCPButton($valuetocopy, $showonlyonhover = 1, $tex * Decode an encoded string. The string can be encoded in json format (recommended) or with serialize (avoid this) * * @param string $stringtodecode String to decode (json or serialize coded) + * @param boolean $assoc true=Return is converted into associative array * @return mixed The decoded object. */ -function jsonOrUnserialize($stringtodecode) +function jsonOrUnserialize($stringtodecode, $assoc = true) { - $result = json_decode($stringtodecode); + $result = json_decode($stringtodecode, $assoc); if ($result === null) { $result = unserialize($stringtodecode); // For backward compatibility. Is no more used in recent versions. } From fbc9934e62145fe991b2bd9ce70d964adbbadefc Mon Sep 17 00:00:00 2001 From: Alexandre SPANGARO Date: Mon, 16 Feb 2026 21:29:33 +0100 Subject: [PATCH 37/39] QUAL French comment in English (#37225) * Edit label of $fields * QUAL French comment in English --- htdocs/societe/class/societe.class.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/htdocs/societe/class/societe.class.php b/htdocs/societe/class/societe.class.php index a043494c693..e9e37176fd9 100644 --- a/htdocs/societe/class/societe.class.php +++ b/htdocs/societe/class/societe.class.php @@ -216,13 +216,13 @@ class Societe extends CommonObject 'idprof4' => array('type' => 'varchar(128)', 'label' => 'Idprof4', 'enabled' => 1, 'visible' => -1, 'position' => 205), 'idprof5' => array('type' => 'varchar(128)', 'label' => 'Idprof5', 'enabled' => 1, 'visible' => -1, 'position' => 206), 'idprof6' => array('type' => 'varchar(128)', 'label' => 'Idprof6', 'enabled' => 1, 'visible' => -1, 'position' => 207), - 'tva_intra' => array('type' => 'varchar(20)', 'label' => 'Tva intra', 'enabled' => 1, 'visible' => -1, 'position' => 210), + 'tva_intra' => array('type' => 'varchar(20)', 'label' => 'VATIntra', 'enabled' => 1, 'visible' => -1, 'position' => 210), 'capital' => array('type' => 'double(24,8)', 'label' => 'Capital', 'enabled' => 1, 'visible' => -1, 'position' => 215), 'fk_stcomm' => array('type' => 'integer', 'label' => 'CommercialStatus', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 220), 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 225), 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 230), - 'client' => array('type' => 'tinyint(4)', 'label' => 'Client', 'enabled' => 1, 'visible' => -1, 'position' => 240), - 'fournisseur' => array('type' => 'tinyint(4)', 'label' => 'Fournisseur', 'enabled' => 1, 'visible' => -1, 'position' => 245), + 'client' => array('type' => 'tinyint(4)', 'label' => 'Customer', 'enabled' => 1, 'visible' => -1, 'position' => 240), + 'fournisseur' => array('type' => 'tinyint(4)', 'label' => 'Supplier', 'enabled' => 1, 'visible' => -1, 'position' => 245), 'supplier_account' => array('type' => 'varchar(32)', 'label' => 'Supplier account', 'enabled' => 1, 'visible' => -1, 'position' => 250), 'fk_prospectlevel' => array('type' => 'varchar(12)', 'label' => 'ProspectLevel', 'enabled' => 1, 'visible' => -1, 'position' => 255), 'customer_bad' => array('type' => 'tinyint(4)', 'label' => 'Customer bad', 'enabled' => 1, 'visible' => -1, 'position' => 260), @@ -1141,11 +1141,11 @@ class Societe extends CommonObject } } - // Ajout du commercial affecte + // Addition of the assigned sales representative if ($this->commercial_id != '' && $this->commercial_id != -1) { $this->add_commercial($user, $this->commercial_id); } elseif (!$user->hasRight('societe', 'client', 'voir')) { - // si un commercial cree un client il lui est affecte automatiquement + // If a sales representative creates a customer, they are automatically assigned to them. $this->add_commercial($user, $user->id); } @@ -1879,7 +1879,7 @@ class Societe extends CommonObject } } else { if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') { - // Doublon + // Duplicate $this->error = $langs->trans("ErrorDuplicateField"); $result = -1; } else { @@ -2179,7 +2179,7 @@ class Societe extends CommonObject $this->order_min_amount = $obj->order_min_amount; $this->supplier_order_min_amount = $obj->supplier_order_min_amount; - // multiprix + // multi price $this->price_level = $obj->price_level; // warehouse @@ -2567,7 +2567,7 @@ class Societe extends CommonObject if ($this->id) { $newclient = 1; if (($this->client == 2 || $this->client == 3) && !getDolGlobalInt('SOCIETE_DISABLE_PROSPECTSCUSTOMERS')) { - $newclient = 3; //If prospect, we keep prospect tag + $newclient = 3; // If prospected, we keep prospect tag } $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element; $sql .= " SET client = ".((int) $newclient); @@ -3867,7 +3867,7 @@ class Societe extends CommonObject return 1; } if ($mod->code_modifiable) { - return 1; // A mettre en dernier + return 1; // To be put last } return 0; } else { @@ -3908,7 +3908,7 @@ class Societe extends CommonObject return 1; } if ($mod->code_modifiable) { - return 1; // A mettre en dernier + return 1; // To be put last } return 0; } else { @@ -4701,7 +4701,7 @@ class Societe extends CommonObject $this->db->begin(); - // Cree et positionne $this->id + // Creates and positions $this->id $result = $this->create($user); if ($result >= 0) { @@ -4738,7 +4738,7 @@ class Societe extends CommonObject return -1; } } else { - // $this->error deja positionne + // $this->error already positioned dol_syslog(get_class($this)."::create_from_member - 2 - ".$this->error." - ".implode(',', $this->errors), LOG_ERR); $this->db->rollback(); @@ -5440,7 +5440,7 @@ class Societe extends CommonObject $result = $companybankaccount->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams); $this->last_main_doc = $companybankaccount->last_main_doc; } else { - // Positionne le modele sur le nom du modele a utiliser + // Set the model to the name of the model to be used if (!dol_strlen($modele)) { if (getDolGlobalString('COMPANY_ADDON_PDF')) { $modele = getDolGlobalString('COMPANY_ADDON_PDF'); From 16542692d5984a622e856e7f38f1d758deeb2881 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 21:29:59 +0100 Subject: [PATCH 38/39] Bump actions/upload-artifact from 4 to 6 (#37250) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Laurent Destailleur --- .github/workflows/phan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/phan.yml b/.github/workflows/phan.yml index 8851694c4f3..4fdedc212ce 100644 --- a/.github/workflows/phan.yml +++ b/.github/workflows/phan.yml @@ -90,7 +90,7 @@ jobs: cs2pr --prepend-filename --prepend-source --notices-as-warnings _phan.xml - name: Provide phan log as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: ${{ always() }} with: name: phan-srcrt From 22391f32ddf57336f8e7142e909475d2e4e9c3b1 Mon Sep 17 00:00:00 2001 From: John BOTELLA <68917336+thersane-john@users.noreply.github.com> Date: Mon, 16 Feb 2026 21:37:57 +0100 Subject: [PATCH 39/39] UI/UX : Transfert feedback css from experimental to core (#37226) * Transfert feedback css from experimental to core * fix php phan * Fix phan * Fix phan * Fix phan --- .../tools/ui/class/documentation.class.php | 30 +++++++++---------- .../index.php => content/input-feedback.php} | 11 ++++--- htdocs/theme/eldy/global.inc.php | 1 + .../eldy/input-feedback.css} | 0 htdocs/theme/md/style.css.php | 1 + 5 files changed, 22 insertions(+), 21 deletions(-) rename htdocs/admin/tools/ui/{experimental/experiments/input-feedback/index.php => content/input-feedback.php} (91%) rename htdocs/{admin/tools/ui/experimental/experiments/input-feedback/assets/feddback-01.css => theme/eldy/input-feedback.css} (100%) diff --git a/htdocs/admin/tools/ui/class/documentation.class.php b/htdocs/admin/tools/ui/class/documentation.class.php index cc145d3c5db..aabc27c4724 100644 --- a/htdocs/admin/tools/ui/class/documentation.class.php +++ b/htdocs/admin/tools/ui/class/documentation.class.php @@ -31,21 +31,21 @@ class Documentation /** * Views * - * @var array + * @var array|array */ public $view = array(); /** * Menu - Set in setMenu in order to use dol_buildpath and called in constructor * - * @var array + * @var array, submenu?: array}> */ public $menu = array(); /** * Summary - Set in setSummary and called in constructor * - * @var array + * @var array */ public $summary = array(); @@ -159,7 +159,7 @@ class Documentation ), 'Inputs' => array( 'url' => dol_buildpath($this->baseUrl.'/components/inputs.php', 1), - 'icon' => 'fas fa-comments', + 'icon' => 'far fa-edit', 'submenu' => array(), 'summary' => array( 'DocBasicUsage' => '#setinputssection-basicusage', @@ -167,6 +167,12 @@ class Documentation 'DocHelperFunctionsGetSearchFilterToolInput' => '#setinputssection-getSearchFilterToolInput', ) ), + 'ExperimentalUxInputAjaxFeedback' => array( + 'url' => dol_buildpath($this->baseUrl.'/content/input-feedback.php', 1), + 'icon' => 'far fa-share-square', + 'submenu' => array(), + 'summary' => array(), + ), ), ); @@ -247,12 +253,6 @@ class Documentation 'ExperimentalUxContributionTitle' => '#experimental-ux-contribution', ), ), - 'ExperimentalUxInputAjaxFeedback' => array( - 'url' => dol_buildpath($this->baseUrl.'/experimental/experiments/input-feedback/index.php', 1), - 'icon' => 'fas fa-flask', - 'submenu' => array(), - 'summary' => array(), - ), 'UxDolibarrContext' => array( 'url' => dol_buildpath($this->baseUrl.'/experimental/experiments/dolibarr-context/index.php', 1), 'icon' => 'fas fa-flask', @@ -369,7 +369,7 @@ class Documentation /** * Recursive function to set Menu * - * @param array $menu $this->menu or submenus + * @param array, submenu?: array}> $menu Menu entry or submenu * @param int $level level of menu * @return void */ @@ -446,9 +446,9 @@ class Documentation foreach ($this->view as $view) { $i++; if ($i == 1) { - $menu_entry = $this->menu[$view]; + $menu_entry = $this->menu[$view] ?? []; } else { - $menu_entry = $menu_entry['submenu'][$view]; + $menu_entry = $menu_entry['submenu'][$view] ?? []; } } } @@ -464,7 +464,7 @@ class Documentation /** * Recursive function for Automatic Summary * - * @param array $menu $this->menu or submenus + * @param array{summary?: array, submenu?: array} $menu $this->menu or submenus * @param int $level level of menu * @param int $showsubmenu Show Sub menus: 0 = No, 1 = Yes * @param int $showsubmenu_summary Show summary of sub menus: 0 = No, 1 = Yes @@ -518,7 +518,7 @@ class Documentation /** * Output a View Code area * - * @param array $lines Lines of code to show + * @param array $lines Lines of code to show * @param string $option Source code language ('html', 'php' etc) * @return void */ diff --git a/htdocs/admin/tools/ui/experimental/experiments/input-feedback/index.php b/htdocs/admin/tools/ui/content/input-feedback.php similarity index 91% rename from htdocs/admin/tools/ui/experimental/experiments/input-feedback/index.php rename to htdocs/admin/tools/ui/content/input-feedback.php index 9da34fba339..0706b1a1531 100644 --- a/htdocs/admin/tools/ui/experimental/experiments/input-feedback/index.php +++ b/htdocs/admin/tools/ui/content/input-feedback.php @@ -18,7 +18,7 @@ */ // Load Dolibarr environment -require '../../../../../../main.inc.php'; +require '../../../../main.inc.php'; /** * @var DoliDB $db @@ -50,7 +50,7 @@ $js = [ '/includes/ace/src/ext-language_tools.js', ]; $css = [ - $experimentAssetsPath . 'feddback-01.css' + ]; // Output html head + body - Param is Title @@ -73,11 +73,10 @@ $documentation->showSidebar(); ?> showSummary(); ?>
-

Input Feedback (Experimental)

+

Input Feedback

- This experimental feature provides visual feedback on input fields based on their processing state. - Currently, it is only available in this documentation and may be integrated into the develop branch of Dolibarr in the future. + This feature provides visual feedback on input fields based on their processing state.

How It Works

@@ -153,7 +152,7 @@ document.getElementById(\'btn-process-fail\').addEventListener(\'click\', functi });', '', ); - echo $documentation->showCode($lines, 'html'); ?> + $documentation->showCode($lines); ?>
diff --git a/htdocs/theme/eldy/global.inc.php b/htdocs/theme/eldy/global.inc.php index 586db46dc84..802773f63d8 100644 --- a/htdocs/theme/eldy/global.inc.php +++ b/htdocs/theme/eldy/global.inc.php @@ -9463,6 +9463,7 @@ include dol_buildpath($path.'/theme/'.$theme.'/progress.inc.php', 0); include dol_buildpath($path.'/theme/'.$theme.'/timeline.inc.php', 0); include dol_buildpath($path.'/theme/'.$theme.'/search-input.inc.css', 0); include dol_buildpath($path.'/theme/'.$theme.'/tooltips.inc.css', 0); +include dol_buildpath($path.'/theme/'.$theme.'/input-feedback.css', 0); // Add custom CSS if defined diff --git a/htdocs/admin/tools/ui/experimental/experiments/input-feedback/assets/feddback-01.css b/htdocs/theme/eldy/input-feedback.css similarity index 100% rename from htdocs/admin/tools/ui/experimental/experiments/input-feedback/assets/feddback-01.css rename to htdocs/theme/eldy/input-feedback.css diff --git a/htdocs/theme/md/style.css.php b/htdocs/theme/md/style.css.php index 3aa25c49b94..030bdd12828 100644 --- a/htdocs/theme/md/style.css.php +++ b/htdocs/theme/md/style.css.php @@ -9186,6 +9186,7 @@ include dol_buildpath($path.'/theme/'.$theme.'/progress.inc.php', 0); include dol_buildpath($path.'/theme/eldy/timeline.inc.php', 0); // actually md use same style as eldy theme include dol_buildpath($path.'/theme/'.$theme.'/search-input.inc.css', 0); // actually md use same style as eldy theme include dol_buildpath($path.'/theme/'.$theme.'/tooltips.inc.css', 0); +include dol_buildpath($path.'/theme/eldy/input-feedback.css', 0); // actually md use same style as eldy theme if (getDolGlobalString('THEME_CUSTOM_CSS')) { print getDolGlobalString('THEME_CUSTOM_CSS');