diff --git a/htdocs/admin/dict.php b/htdocs/admin/dict.php index 47832ddf7d6..32acb4d3cf9 100644 --- a/htdocs/admin/dict.php +++ b/htdocs/admin/dict.php @@ -2443,6 +2443,10 @@ if ($id > 0) { $valuetoshow = $langs->trans($valuetoshow ? $valuetoshow : $tmpid); } elseif ($tabname[$id] == 'c_exp_tax_cat') { $valuetoshow = $langs->trans($valuetoshow); + } elseif ($value == 'libelle' && ($tabname[$id] == 'c_stcomm' || $tabname[$id] == 'c_stcommcontact')) { + $key = 'StatusProspect'.$obj->rowid; + $trans = $langs->trans($key); + $valuetoshow = ($trans != $key ? $trans : $obj->{$value}); } elseif ($value == 'label' && $tabname[$id] == 'c_units') { $langs->load('other'); $key = $langs->trans($obj->label); diff --git a/htdocs/api/class/api_setup.class.php b/htdocs/api/class/api_setup.class.php index 2ca653ecde6..aba0d2e0a3a 100644 --- a/htdocs/api/class/api_setup.class.php +++ b/htdocs/api/class/api_setup.class.php @@ -277,11 +277,10 @@ class Setup extends DolibarrApi // Add sql filters if ($sqlfilters) { $errormessage = ''; - if (!DolibarrApi::_checkFilters($sqlfilters, $errormessage)) { + $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage); + if ($errormessage) { throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage); } - $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)'; - $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")"; } $sql .= $this->db->order($sortfield, $sortorder); @@ -2143,11 +2142,10 @@ class Setup extends DolibarrApi // Add sql filters if ($sqlfilters) { $errormessage = ''; - if (!DolibarrApi::_checkFilters($sqlfilters, $errormessage)) { + $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage); + if ($errormessage) { throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage); } - $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)'; - $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")"; } diff --git a/htdocs/compta/bank/class/account.class.php b/htdocs/compta/bank/class/account.class.php index 8a53d6c77a2..1b221485042 100644 --- a/htdocs/compta/bank/class/account.class.php +++ b/htdocs/compta/bank/class/account.class.php @@ -485,6 +485,22 @@ class Account extends CommonObject public function add_url_line($line_id, $url_id, $url, $label, $type) { // phpcs:enable + // Avoid uk_bank_url collision when the same target (line_id, url_id, type) is linked twice + // e.g. two distinct credit transfers for the same employee dispatched on the same bank line. + $sqlcheck = "SELECT rowid FROM ".MAIN_DB_PREFIX."bank_url"; + $sqlcheck .= " WHERE fk_bank = ".((int) $line_id); + $sqlcheck .= " AND url_id = ".((int) $url_id); + $sqlcheck .= " AND type = '".$this->db->escape($type)."'"; + $resqlcheck = $this->db->query($sqlcheck); + if ($resqlcheck) { + $obj = $this->db->fetch_object($resqlcheck); + if ($obj) { + $this->db->free($resqlcheck); + return (int) $obj->rowid; + } + $this->db->free($resqlcheck); + } + $sql = "INSERT INTO ".MAIN_DB_PREFIX."bank_url ("; $sql .= "fk_bank"; $sql .= ", url_id"; diff --git a/htdocs/core/class/discount.class.php b/htdocs/core/class/discount.class.php index 29e663f4e27..8f40ec1b6b5 100644 --- a/htdocs/core/class/discount.class.php +++ b/htdocs/core/class/discount.class.php @@ -278,13 +278,15 @@ class DiscountAbsolute extends CommonObject $sql .= " (entity, datec, fk_soc, discount_type, fk_user, description,"; $sql .= " amount_ht, amount_tva, amount_ttc, tva_tx, vat_src_code,"; $sql .= " multicurrency_amount_ht, multicurrency_amount_tva, multicurrency_amount_ttc,"; - $sql .= " fk_facture_source, fk_invoice_supplier_source"; + $sql .= " fk_facture_source, fk_invoice_supplier_source, multicurrency_code, multicurrency_tx"; $sql .= ")"; $sql .= " VALUES (".$conf->entity.", '".$this->db->idate($this->datec != '' ? $this->datec : dol_now())."', ".((int) $this->socid).", ".(empty($this->discount_type) ? 0 : intval($this->discount_type)).", ".((int) $userid).", '".$this->db->escape($this->description)."',"; $sql .= " ".price2num($this->amount_ht).", ".price2num($this->amount_tva).", ".price2num($this->amount_ttc).", ".price2num($this->tva_tx).", '".$this->db->escape($this->vat_src_code)."',"; $sql .= " ".price2num($this->multicurrency_amount_ht).", ".price2num($this->multicurrency_amount_tva).", ".price2num($this->multicurrency_amount_ttc).", "; $sql .= " ".($this->fk_facture_source ? ((int) $this->fk_facture_source) : "null").","; - $sql .= " ".($this->fk_invoice_supplier_source ? ((int) $this->fk_invoice_supplier_source) : "null"); + $sql .= " ".($this->fk_invoice_supplier_source ? ((int) $this->fk_invoice_supplier_source) : "null").","; + $sql .= " ".($this->multicurrency_code ? "'".$this->db->escape($this->multicurrency_code)."'" : "null").","; + $sql .= " ".($this->multicurrency_tx ? price2num($this->multicurrency_tx) : "null"); $sql .= ")"; dol_syslog(get_class($this)."::create", LOG_DEBUG); diff --git a/htdocs/core/modules/modProduct.class.php b/htdocs/core/modules/modProduct.class.php index 776b3012f15..0542ed36d44 100644 --- a/htdocs/core/modules/modProduct.class.php +++ b/htdocs/core/modules/modProduct.class.php @@ -517,7 +517,7 @@ class modProduct extends DolibarrModules 'p.price_min' => "MinPrice", 'p.price_ttc' => "SellingPriceTTC", //with tax 'p.price_min_ttc' => "SellingMinPriceTTC", - 'p.price_base_type' => "PriceBaseType", //price base: with-tax (TTC) or without (HT) tax. Displays accordingly in Product card + 'p.price_base_type' => "PriceBaseType*", //price base: with-tax (TTC) or without (HT) tax. Displays accordingly in Product card 'p.tva_tx' => 'VATRate', 'p.datec' => 'DateCreation', 'p.cost_price' => "CostPrice" diff --git a/htdocs/fichinter/list.php b/htdocs/fichinter/list.php index 450396fbe2b..952421f3668 100644 --- a/htdocs/fichinter/list.php +++ b/htdocs/fichinter/list.php @@ -9,7 +9,7 @@ * Copyright (C) 2021-2023 Frédéric France * Copyright (C) 2022 Charlène Benke * Copyright (C) 2024 William Mead - * Copyright (C) 2024 MDW + * Copyright (C) 2024-2026 MDW * Copyright (C) 2024 Benjamin Falière * * This program is free software; you can redistribute it and/or modify @@ -325,7 +325,7 @@ if ($search_desc) { } } if ($search_status != '' && $search_status >= 0) { - $sql .= ' AND f.fk_statut = '.urlencode($search_status); + $sql .= ' AND f.fk_statut = '.((int) ($search_status)); } if (!getDolGlobalString('FICHINTER_DISABLE_DETAILS') && $atleastonefieldinlines) { if ($search_date_start) { diff --git a/htdocs/fourn/class/paiementfourn.class.php b/htdocs/fourn/class/paiementfourn.class.php index 8c221fff4d9..bfb158a3532 100644 --- a/htdocs/fourn/class/paiementfourn.class.php +++ b/htdocs/fourn/class/paiementfourn.class.php @@ -6,11 +6,12 @@ * Copyright (C) 2010-2011 Juanjo Menent * Copyright (C) 2014 Marcos García * Copyright (C) 2018 Nicolas ZABOURI - * Copyright (C) 2018 Frédéric France + * Copyright (C) 2018 Frédéric France * Copyright (C) 2023 Joachim Kueter * Copyright (C) 2023 Sylvain Legrand - * Copyright (C) 2024 Frédéric France - * Copyright (C) 2024 MDW + * Copyright (C) 2024 Frédéric France + * Copyright (C) 2024 MDW + * Copyright (C) 2026 Lionel Vessiller * * 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 @@ -202,16 +203,31 @@ class PaiementFourn extends Paiement $this->error = $langs->trans('FailedToFoundTheConversionRateForInvoice'); return -1; } - if (empty($currencyofpayment)) { - $currencyofpayment = $this->multicurrency_code[$key]; + // Fallback: read invoice multicurrency code/tx if caller did not fill the arrays + $invoice_multicurrency_code = $this->multicurrency_code[$key] ?? ''; + $invoice_multicurrency_tx = $this->multicurrency_tx[$key] ?? ''; + if (empty($invoice_multicurrency_code) || empty($invoice_multicurrency_tx)) { + $tmparray = MultiCurrency::getInvoiceRate($key, 'facture_fourn'); + if ($tmparray !== false) { + if (empty($invoice_multicurrency_code)) { + $invoice_multicurrency_code = $tmparray['invoice_multicurrency_code']; + } + if (empty($invoice_multicurrency_tx)) { + $invoice_multicurrency_tx = $tmparray['invoice_multicurrency_tx']; + } + } } - if ($currencyofpayment != $this->multicurrency_code[$key]) { + + if (empty($currencyofpayment)) { + $currencyofpayment = $invoice_multicurrency_code; + } + if ($currencyofpayment != $invoice_multicurrency_code) { // If we have invoices with different currencies in the payment, we stop here $this->error = 'ErrorYouTryToPayInvoicesWithDifferentCurrenciesInSamePayment'; return -1; } if (empty($currencytxofpayment)) { - $currencytxofpayment = $this->multicurrency_tx[$key]; + $currencytxofpayment = $invoice_multicurrency_tx; } $totalamount_converted += $value_converted; @@ -302,6 +318,8 @@ class PaiementFourn extends Paiement $discount->fk_soc = $invoice->socid; $discount->socid = $invoice->socid; $discount->fk_invoice_supplier_source = $invoice->id; + $discount->multicurrency_code = $invoice->multicurrency_code; + $discount->multicurrency_tx = $invoice->multicurrency_tx; // Loop on each vat rate $i = 0; @@ -321,9 +339,14 @@ class PaiementFourn extends Paiement $discount->amount_ht = abs($amount_ht[$tva_tx]); $discount->amount_tva = abs($amount_tva[$tva_tx]); $discount->amount_ttc = abs($amount_ttc[$tva_tx]); - $discount->multicurrency_amount_ht = abs($multicurrency_amount_ht[$tva_tx]); - $discount->multicurrency_amount_tva = abs($multicurrency_amount_tva[$tva_tx]); - $discount->multicurrency_amount_ttc = abs($multicurrency_amount_ttc[$tva_tx]); + // multi-currency + $discount->multicurrency_total_ht = abs($multicurrency_amount_ht[$tva_tx]); + $discount->multicurrency_total_tva = abs($multicurrency_amount_tva[$tva_tx]); + $discount->multicurrency_total_ttc = abs($multicurrency_amount_ttc[$tva_tx]); + // keep compatibility + $discount->multicurrency_amount_ht = $discount->multicurrency_total_ht; + $discount->multicurrency_amount_tva = $discount->multicurrency_total_tva; + $discount->multicurrency_amount_ttc = $discount->multicurrency_total_ttc; $discount->tva_tx = abs($tva_tx); $result = $discount->create($user); diff --git a/htdocs/fourn/facture/card.php b/htdocs/fourn/facture/card.php index f1ab09df0d6..a9a7d52913d 100644 --- a/htdocs/fourn/facture/card.php +++ b/htdocs/fourn/facture/card.php @@ -13,7 +13,8 @@ * Copyright (C) 2019 Ferran Marcet * Copyright (C) 2022 Gauthier VERDOL * Copyright (C) 2023 Nick Fragoulis - * Copyright (C) 2024 MDW + * Copyright (C) 2024 MDW + * Copyright (C) 2026 Lionel Vessiller * * 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 @@ -698,6 +699,15 @@ if (empty($reshook)) { $discount->tva_tx = 0; $discount->vat_src_code = ''; + // multi-currency + $discount->multicurrency_code = $object->multicurrency_code; + $discount->multicurrency_tx = $object->multicurrency_tx; + $discount->multicurrency_total_ht = $discount->multicurrency_total_ttc = (float) price2num((float) $discount->amount_ttc * (float) $object->multicurrency_tx, 'MT'); + $discount->multicurrency_total_tva = 0; + // keep compatibility + $discount->multicurrency_amount_ht = $discount->multicurrency_amount_ttc = $discount->multicurrency_total_ttc; + $discount->multicurrency_amount_tva = 0; + $result = $discount->create($user); if ($result < 0) { $error++; @@ -708,9 +718,16 @@ if (empty($reshook)) { $discount->amount_ht = abs((float) $amount_ht[$tva_tx]); $discount->amount_tva = abs((float) $amount_tva[$tva_tx]); $discount->amount_ttc = abs((float) $amount_ttc[$tva_tx]); - $discount->multicurrency_amount_ht = abs((float) $multicurrency_amount_ht[$tva_tx]); - $discount->multicurrency_amount_tva = abs((float) $multicurrency_amount_tva[$tva_tx]); - $discount->multicurrency_amount_ttc = abs((float) $multicurrency_amount_ttc[$tva_tx]); + // multi-currency + $discount->multicurrency_code = $object->multicurrency_code; + $discount->multicurrency_tx = $object->multicurrency_tx; + $discount->multicurrency_total_ht = abs((float) $multicurrency_amount_ht[$tva_tx]); + $discount->multicurrency_total_tva = abs((float) $multicurrency_amount_tva[$tva_tx]); + $discount->multicurrency_total_ttc = abs((float) $multicurrency_amount_ttc[$tva_tx]); + // keep compatibility + $discount->multicurrency_amount_ht = abs((float) $discount->multicurrency_total_ht); + $discount->multicurrency_amount_tva = abs((float) $discount->multicurrency_total_tva); + $discount->multicurrency_amount_ttc = abs((float) $discount->multicurrency_total_ttc); // Clean vat code $reg = array(); diff --git a/htdocs/imports/import.php b/htdocs/imports/import.php index 786da7f840f..73f08dd3f94 100644 --- a/htdocs/imports/import.php +++ b/htdocs/imports/import.php @@ -36,7 +36,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/import.lib.php'; // Load translation files required by the page -$langs->loadLangs(array('exports', 'compta', 'errors', 'projects', 'admin')); +$langs->loadLangs(array('exports', 'compta', 'errors', 'projects', 'admin', 'products', 'margins')); // Security check $result = restrictedArea($user, 'import'); diff --git a/htdocs/includes/odtphp/odf.php b/htdocs/includes/odtphp/odf.php index e021cc022e9..d0f2b72d262 100644 --- a/htdocs/includes/odtphp/odf.php +++ b/htdocs/includes/odtphp/odf.php @@ -83,6 +83,10 @@ class Odf $this->tmpfile = $this->tmpdir.'/'.$md5uniqid.'.odt'; // We keep .odt extension to allow OpenOffice usage during debug. // A working directory is required for some zip proxy like PclZipProxy + if (in_array($this->config['ZIP_PROXY'], array('PclZipProxy')) && ! is_dir($this->config['PATH_TO_TMP'])) { + $result = mkdir($this->config['PATH_TO_TMP']); + } + // Check the dir has been created if (in_array($this->config['ZIP_PROXY'], array('PclZipProxy')) && ! is_dir($this->config['PATH_TO_TMP'])) { throw new OdfException('Temporary directory '.$this->config['PATH_TO_TMP'].' must exists'); } diff --git a/htdocs/multicurrency/class/api_multicurrencies.class.php b/htdocs/multicurrency/class/api_multicurrencies.class.php index 09ad43b6dbe..2092be69bec 100644 --- a/htdocs/multicurrency/class/api_multicurrencies.class.php +++ b/htdocs/multicurrency/class/api_multicurrencies.class.php @@ -69,11 +69,10 @@ class MultiCurrencies extends DolibarrApi // Add sql filters if ($sqlfilters) { $errormessage = ''; - if (!DolibarrApi::_checkFilters($sqlfilters, $errormessage)) { - throw new RestException(503, 'Error when validating parameter sqlfilters -> '.$errormessage); + $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage); + if ($errormessage) { + throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage); } - $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)'; - $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")"; } $sql .= $this->db->order($sortfield, $sortorder); diff --git a/htdocs/societe/class/societe.class.php b/htdocs/societe/class/societe.class.php index dcec1fb32db..293e5e5d623 100644 --- a/htdocs/societe/class/societe.class.php +++ b/htdocs/societe/class/societe.class.php @@ -2505,15 +2505,26 @@ class Societe extends CommonObject $discount->socid = $this->id; $discount->discount_type = $discount_type; + $discount->multicurrency_code = $this->multicurrency_code; + list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code); + $discount->multicurrency_tx = $this->multicurrency_tx; if ($price_base_type == 'TTC') { - $discount->amount_ttc = $discount->multicurrency_amount_ttc = price2num($remise, 'MT'); - $discount->amount_ht = $discount->multicurrency_amount_ht = price2num((float) $remise / (1 + (float) $vatrate / 100), 'MT'); - $discount->amount_tva = $discount->multicurrency_amount_tva = price2num((float) $discount->amount_ttc - (float) $discount->amount_ht, 'MT'); + $discount->amount_ttc = price2num($remise, 'MT'); + $discount->amount_ht = price2num((float) $remise / (1 + (float) $vatrate / 100), 'MT'); + $discount->amount_tva = price2num((float) $discount->amount_ttc - (float) $discount->amount_ht, 'MT'); + + $discount->multicurrency_amount_ttc = price2num($remise * (float) $discount->multicurrency_tx, 'MT'); + $discount->multicurrency_amount_ht = price2num(((float) $remise / (1 + (float) $vatrate / 100)) * (float) $discount->multicurrency_tx, 'MT'); + $discount->multicurrency_amount_tva = price2num(((float) $discount->amount_ttc - (float) $discount->amount_ht) * (float) $discount->multicurrency_tx, 'MT'); } else { - $discount->amount_ht = $discount->multicurrency_amount_ht = price2num($remise, 'MT'); - $discount->amount_tva = $discount->multicurrency_amount_tva = price2num((float) $remise * (float) $vatrate / 100, 'MT'); - $discount->amount_ttc = $discount->multicurrency_amount_ttc = price2num((float) $discount->amount_ht + (float) $discount->amount_tva, 'MT'); + $discount->amount_ht = price2num($remise, 'MT'); + $discount->amount_tva = price2num((float) $remise * (float) $vatrate / 100, 'MT'); + $discount->amount_ttc = price2num((float) $discount->amount_ht + (float) $discount->amount_tva, 'MT'); + + $discount->multicurrency_amount_ht = price2num($remise * (float) $discount->multicurrency_tx, 'MT'); + $discount->multicurrency_amount_tva = price2num(((float) $remise * (float) $vatrate / 100) * (float) $discount->multicurrency_tx, 'MT'); + $discount->multicurrency_amount_ttc = price2num(((float) $discount->amount_ht + (float) $discount->amount_tva) * (float) $discount->multicurrency_tx, 'MT'); } $discount->tva_tx = (float) price2num($vatrate); diff --git a/htdocs/takepos/invoice.php b/htdocs/takepos/invoice.php index e6988e443ba..17b7e466126 100644 --- a/htdocs/takepos/invoice.php +++ b/htdocs/takepos/invoice.php @@ -198,6 +198,14 @@ if ($reshook < 0) { setEventMessages($hookmanager->error, $hookmanager->errors, 'errors'); } +// Enforce the "edit lines" permission on every action that modifies an existing line +// (delete, quantity, price, discount). Adding a line, a free zone or a note is gated by +// the "run" permission elsewhere and must stay available to a plain cashier (#38949). +if (in_array($action, array('deleteline', 'updateqty', 'updateprice', 'updatereduction', 'update_reduction_global')) && !$user->hasRight('takepos', 'editlines')) { + dol_htmloutput_errors($langs->trans("NotEnoughPermissions", "TakePos"), null, 1); + $action = ''; +} + if (empty($reshook)) { // Action to record a payment on a TakePOS invoice if ($action == 'valid' && $user->hasRight('facture', 'creer')) { diff --git a/htdocs/user/note.php b/htdocs/user/note.php index 08f55c75b51..d65ce482554 100644 --- a/htdocs/user/note.php +++ b/htdocs/user/note.php @@ -50,7 +50,9 @@ if (($object->id != $user->id) && (!$user->hasRight("user", "user", "read"))) { } // Permissions -$permissionnote = $user->hasRight("user", "self", "write"); // Used by the include of actions_setnotes.inc.php +// Writing notes on own record needs the "self" write right, writing on another user's record needs the "user" write right. +// Without this distinction a user holding only "self" write could edit the notes of any user, including admins. +$permissionnote = ((($object->id == $user->id) && $user->hasRight("user", "self", "write")) || (($object->id != $user->id) && $user->hasRight("user", "user", "write"))); // Used by the include of actions_setnotes.inc.php // Security check $socid = 0;