From 00b99430697b14242fa3549cd7a2db3cdbcf0b58 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Nov 2024 15:15:55 +0100 Subject: [PATCH 1/7] Rename var to make debug easier --- htdocs/core/lib/price.lib.php | 61 ++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/htdocs/core/lib/price.lib.php b/htdocs/core/lib/price.lib.php index da3ea50b9f9..0e8106389dd 100644 --- a/htdocs/core/lib/price.lib.php +++ b/htdocs/core/lib/price.lib.php @@ -59,8 +59,8 @@ * 1=total_vat, (main vat only) * 2=total_ttc, (total_ht + main vat + local taxes) * 3=pu_ht, - * 4=pu_vat, (main vat only) - * 5=pu_ttc, + * 4=pu_vat, (main vat only) !! should not be used + * 5=pu_ttc, !! should not be used except if it is stored in database one day * 6=total_ht_without_discount, * 7=total_vat_without_discount, (main vat only) * 8=total_ttc_without_discount, (total_ht + main vat + local taxes) @@ -194,7 +194,7 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt } // initialize total (may be HT or TTC depending on price_base_type) - $tot_sans_remise = $pu * $qty * $progress / 100; + $tot_sans_remise = $pu * $qty * ($progress / 100); $tot_avec_remise_ligne = $tot_sans_remise * (1 - ((float) $remise_percent_ligne / 100)); $tot_avec_remise = $tot_avec_remise_ligne * (1 - ((float) $remise_percent_global / 100)); @@ -207,16 +207,26 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt // if input unit price is 'HT', we need to have the totals with main VAT for a correct calculation if ($price_base_type != 'TTC') { - $tot_sans_remise_wt = price2num($tot_sans_remise * (1 + ($txtva / 100)), 'MU'); - $tot_avec_remise_wt = price2num($tot_avec_remise * (1 + ($txtva / 100)), 'MU'); - $pu_wt = price2num($pu * (1 + ($txtva / 100)), 'MU'); + $tot_sans_remise_withvat = price2num($tot_sans_remise * (1 + ($txtva / 100)), 'MU'); + $tot_avec_remise_withvat = price2num($tot_avec_remise * (1 + ($txtva / 100)), 'MU'); + + $tot_sans_remise_withoutvat = $tot_sans_remise; + $tot_avec_remise_withoutvat = $tot_avec_remise; + + $pu_withouttax = $pu; + $pu_withmainvat = price2num($pu * (1 + ($txtva / 100)), 'MU'); } else { - $tot_sans_remise_wt = $tot_sans_remise; - $tot_avec_remise_wt = $tot_avec_remise; - $pu_wt = $pu; + $tot_sans_remise_withvat = $tot_sans_remise; + $tot_avec_remise_withvat = $tot_avec_remise; + + $tot_sans_remise_withoutvat = price2num($tot_sans_remise / (1 + ($txtva / 100)), 'MU'); + $tot_avec_remise_withoutvat = price2num($tot_avec_remise / (1 + ($txtva / 100)), 'MU'); + + $pu_withouttax = price2num($pu / (1 + ($txtva / 100)), 'MU'); + $pu_withmainvat = $pu; } - //print 'rr'.$price_base_type.'-'.$txtva.'-'.$tot_sans_remise_wt."-".$pu_wt."-".$uselocaltax1_rate."-".$localtax1_rate."-".$localtax1_type."\n"; + //print 'rr'.$price_base_type.'-'.$txtva.'-'.$tot_sans_remise_withvat."-".$pu_withmainvat."-".$uselocaltax1_rate."-".$localtax1_rate."-".$localtax1_type."\n"; $localtaxes = array(0, 0, 0); $apply_tax = false; @@ -237,13 +247,13 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt } if ($uselocaltax1_rate && $apply_tax) { - $result[14] = price2num(($tot_sans_remise_wt * (1 + ($localtax1_rate / 100))) - $tot_sans_remise_wt, 'MT'); + $result[14] = price2num(($tot_sans_remise_withvat * (1 + ($localtax1_rate / 100))) - $tot_sans_remise_withvat, 'MT'); $localtaxes[0] += $result[14]; - $result[9] = price2num(($tot_avec_remise_wt * (1 + ($localtax1_rate / 100))) - $tot_avec_remise_wt, 'MT'); + $result[9] = price2num(($tot_avec_remise_withvat * (1 + ($localtax1_rate / 100))) - $tot_avec_remise_withvat, 'MT'); $localtaxes[1] += $result[9]; - $result[11] = price2num(($pu_wt * (1 + ($localtax1_rate / 100))) - $pu_wt, 'MU'); + $result[11] = price2num(($pu_withmainvat * (1 + ($localtax1_rate / 100))) - $pu_withmainvat, 'MU'); $localtaxes[2] += $result[11]; } @@ -264,13 +274,13 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt break; } if ($uselocaltax2_rate && $apply_tax) { - $result[15] = price2num(($tot_sans_remise_wt * (1 + ($localtax2_rate / 100))) - $tot_sans_remise_wt, 'MT'); + $result[15] = price2num(($tot_sans_remise_withvat * (1 + ($localtax2_rate / 100))) - $tot_sans_remise_withvat, 'MT'); $localtaxes[0] += $result[15]; - $result[10] = price2num(($tot_avec_remise_wt * (1 + ($localtax2_rate / 100))) - $tot_avec_remise_wt, 'MT'); + $result[10] = price2num(($tot_avec_remise_withvat * (1 + ($localtax2_rate / 100))) - $tot_avec_remise_withvat, 'MT'); $localtaxes[1] += $result[10]; - $result[12] = price2num(($pu_wt * (1 + ($localtax2_rate / 100))) - $pu_wt, 'MU'); + $result[12] = price2num(($pu_withmainvat * (1 + ($localtax2_rate / 100))) - $pu_withmainvat, 'MU'); $localtaxes[2] += $result[12]; } @@ -311,13 +321,6 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt // if there's some localtax without vat, we calculate localtaxes (we will add them at end) - //If input unit price is 'TTC', we need to have the totals without main VAT for a correct calculation - if ($price_base_type == 'TTC') { - $tot_sans_remise = price2num($tot_sans_remise / (1 + ($txtva / 100)), 'MU'); - $tot_avec_remise = price2num($tot_avec_remise / (1 + ($txtva / 100)), 'MU'); - $pu = price2num($pu / (1 + ($txtva / 100)), 'MU'); - } - $apply_tax = false; switch ($localtax1_type) { case '1': // localtax on product or service @@ -335,13 +338,13 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt break; } if ($uselocaltax1_rate && $apply_tax) { - $result[14] = price2num(($tot_sans_remise * (1 + ($localtax1_rate / 100))) - $tot_sans_remise, 'MT'); // amount tax1 for total_ht_without_discount + $result[14] = price2num(($tot_sans_remise_withoutvat * (1 + ($localtax1_rate / 100))) - $tot_sans_remise_withoutvat, 'MT'); // amount tax1 for total_ht_without_discount $result[8] += $result[14]; // total_ttc_without_discount + tax1 - $result[9] = price2num(($tot_avec_remise * (1 + ($localtax1_rate / 100))) - $tot_avec_remise, 'MT'); // amount tax1 for total_ht + $result[9] = price2num(($tot_avec_remise_withoutvat * (1 + ($localtax1_rate / 100))) - $tot_avec_remise_withoutvat, 'MT'); // amount tax1 for total_ht $result[2] += $result[9]; // total_ttc + tax1 - $result[11] = price2num(($pu * (1 + ($localtax1_rate / 100))) - $pu, 'MU'); // amount tax1 for pu_ht + $result[11] = price2num(($pu_withouttax * (1 + ($localtax1_rate / 100))) - $pu_withouttax, 'MU'); // amount tax1 for pu_ht $result[5] += $result[11]; // pu_ht + tax1 } @@ -362,13 +365,13 @@ function calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocalt break; } if ($uselocaltax2_rate && $apply_tax) { - $result[15] = price2num(($tot_sans_remise * (1 + ($localtax2_rate / 100))) - $tot_sans_remise, 'MT'); // amount tax2 for total_ht_without_discount + $result[15] = price2num(($tot_sans_remise_withoutvat * (1 + ($localtax2_rate / 100))) - $tot_sans_remise_withoutvat, 'MT'); // amount tax2 for total_ht_without_discount $result[8] += $result[15]; // total_ttc_without_discount + tax2 - $result[10] = price2num(($tot_avec_remise * (1 + ($localtax2_rate / 100))) - $tot_avec_remise, 'MT'); // amount tax2 for total_ht + $result[10] = price2num(($tot_avec_remise_withoutvat * (1 + ($localtax2_rate / 100))) - $tot_avec_remise_withoutvat, 'MT'); // amount tax2 for total_ht $result[2] += $result[10]; // total_ttc + tax2 - $result[12] = price2num(($pu * (1 + ($localtax2_rate / 100))) - $pu, 'MU'); // amount tax2 for pu_ht + $result[12] = price2num(($pu_withouttax * (1 + ($localtax2_rate / 100))) - $pu_withouttax, 'MU'); // amount tax2 for pu_ht $result[5] += $result[12]; // pu_ht + tax2 } From b7821bde265e5acbf9208e62aeab43cbf486ddb7 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Nov 2024 16:17:51 +0100 Subject: [PATCH 2/7] FIX the autofix corrupted vat value must be triggered only if we are sure data is corrupted. FIX the autofix must also fix the multicurrency value. --- htdocs/core/class/commonobject.class.php | 38 +++++++++++++++++------- test/phpunit/PricesTest.php | 3 ++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index 099a74e947a..b1355bde030 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -3917,25 +3917,41 @@ abstract class CommonObject if ($diff_on_current_total) { // This should not happen, we should always have in table: total_ttc = total_ht + total_vat + total_localtax1 + total_localtax2 - $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldtva." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2])." WHERE rowid = ".((int) $obj->rowid); - dol_syslog('We found inconsistent data into detailed line (diff_on_current_total = '.$diff_on_current_total.') for line rowid = '.$obj->rowid." (ht=".$obj->total_ht." vat=".$obj->total_tva." tax1=".$obj->total_localtax1." tax2=".$obj->total_localtax2." ttc=".$obj->total_ttc."). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix, LOG_WARNING); + $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line; + $sqlfix .= " SET ".$fieldtva." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]); + $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]); + $sqlfix .= " WHERE rowid = ".((int) $obj->rowid); + dol_syslog('Warn1: We found inconsistent data into detailed line (diff_on_current_total = '.$diff_on_current_total.') for line rowid = '.$obj->rowid." (ht=".$obj->total_ht." vat=".$obj->total_tva." tax1=".$obj->total_localtax1." tax2=".$obj->total_localtax2." ttc=".$obj->total_ttc."). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix, LOG_WARNING); $resqlfix = $this->db->query($sqlfix); if (!$resqlfix) { dol_print_error($this->db, 'Failed to update line'); } $obj->total_tva = $tmpcal[1]; $obj->total_ttc = $tmpcal[2]; + $obj->multicurrency_total_tva = $tmpcal[17]; + $obj->multicurrency_total_ttc = $tmpcal[18]; } elseif ($diff_when_using_price_ht) { - // After calculation from HT, total is consistent but we have found a difference between VAT part in calculation and into database and - // we ask to force the use of rounding on line (like done on calculation) so we force update of line - $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line." SET ".$fieldtva." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2])." WHERE rowid = ".((int) $obj->rowid); - dol_syslog('We found a line with different rounding data into detailed line (diff_when_using_price_ht = '.$diff_when_using_price_ht.' and diff_on_current_total = '.$diff_on_current_total.') for line rowid = '.$obj->rowid." (total vat of line calculated=".$tmpcal[1].", database=".$obj->total_tva."). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix); - $resqlfix = $this->db->query($sqlfix); - if (!$resqlfix) { - dol_print_error($this->db, 'Failed to update line'); + // If total_ht calculated from unit price is different than the one in database, we do nothing, this may be a regular case to have also a different VAT, that can be explained + // because price was entered included tax and we round the unit price without tax to store it in database (so recalculation will give different results). + if ((float) $tmpcal[0] == (float) $obj->total_ht) { + // After calculation from HT, total is consistent and total_ht is same, but we have found a difference between VAT part calculated from unit price and the VAT part into database, + // and we ask to force the use of rounding on line (like done on calculation) so this should not happen, so we force the update of line to fix. + + // This part of code must be called only to fix corrupted data due to the use of the feature to round total instead of rounding lines. + $sqlfix = "UPDATE ".$this->db->prefix().$this->table_element_line; + $sqlfix .= " SET ".$fieldtva." = ".price2num((float) $tmpcal[1]).", total_ttc = ".price2num((float) $tmpcal[2]); + $sqlfix .= ", multicurrency_total_tva = ".price2num((float) $tmpcal[17]).", multicurrency_total_ttc = ".price2num((float) $tmpcal[18]); + $sqlfix .= " WHERE rowid = ".((int) $obj->rowid); + dol_syslog('Warn2: We found a line with different rounding data into detailed line (diff_when_using_price_ht = '.$diff_when_using_price_ht.' and diff_on_current_total = '.$diff_on_current_total.') for line rowid = '.$obj->rowid." (total vat of line calculated=".$tmpcal[1].", database=".$obj->total_tva."). We fix the total_vat and total_ttc of line by running sqlfix = ".$sqlfix); + $resqlfix = $this->db->query($sqlfix); + if (!$resqlfix) { + dol_print_error($this->db, 'Failed to update line'); + } + $obj->total_tva = $tmpcal[1]; + $obj->total_ttc = $tmpcal[2]; + $obj->multicurrency_total_tva = $tmpcal[17]; + $obj->multicurrency_total_ttc = $tmpcal[18]; } - $obj->total_tva = $tmpcal[1]; - $obj->total_ttc = $tmpcal[2]; } } diff --git a/test/phpunit/PricesTest.php b/test/phpunit/PricesTest.php index 2511367a7c0..306d867eb9b 100644 --- a/test/phpunit/PricesTest.php +++ b/test/phpunit/PricesTest.php @@ -40,6 +40,9 @@ if (empty($user->id)) { } $conf->global->MAIN_DISABLE_ALL_MAILS = 1; +$conf->global->MAIN_MAX_DECIMALS_UNIT = 5; +$conf->global->MAIN_MAX_DECIMALS_TOT = 2; + if (getDolGlobalString('MAIN_ROUNDING_RULE_TOT')) { print "Parameter MAIN_ROUNDING_RULE_TOT must be set to 0 or not set.\n"; exit(1); From a9b6e6916c4790a3c01d3b187856f0c1195c0053 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Nov 2024 16:38:32 +0100 Subject: [PATCH 3/7] FIX column position on PDF of payments --- htdocs/core/modules/rapport/pdf_paiement.class.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/htdocs/core/modules/rapport/pdf_paiement.class.php b/htdocs/core/modules/rapport/pdf_paiement.class.php index 50100967a03..909547c77e3 100644 --- a/htdocs/core/modules/rapport/pdf_paiement.class.php +++ b/htdocs/core/modules/rapport/pdf_paiement.class.php @@ -96,11 +96,11 @@ class pdf_paiement extends CommonDocGenerator $this->tab_height = $this->page_hauteur - $this->marge_haute - $this->marge_basse - $this->tab_top - 5; // must be > $this->line_height * $this->line_per_page and < $this->page_hauteur - $this->marge_haute - $this->marge_basse - $this->tab_top - 5; $this->posxdate = $this->marge_gauche + 2; - $this->posxpaymenttype = 42; - $this->posxinvoice = 82; - $this->posxbankaccount = 110; - $this->posxinvoiceamount = 132; - $this->posxpaymentamount = 162; + $this->posxpaymenttype = 32; + $this->posxinvoice = 72; + $this->posxbankaccount = 115; + $this->posxinvoiceamount = 135; + $this->posxpaymentamount = 167; if ($this->page_largeur < 210) { // To work with US executive format $this->line_per_page = 35; $this->posxpaymenttype -= 10; @@ -108,6 +108,7 @@ class pdf_paiement extends CommonDocGenerator $this->posxinvoiceamount -= 10; $this->posxpaymentamount -= 20; } + // which type of document will be generated: clients (client) or providers (fourn) invoices $this->doc_type = "client"; } From ce7a364d2f378bc6584effe7c7c6802676b93634 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Nov 2024 17:16:36 +0100 Subject: [PATCH 4/7] Add option to test fix of #31696 --- htdocs/includes/odtphp/odf.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htdocs/includes/odtphp/odf.php b/htdocs/includes/odtphp/odf.php index 90d0afca1ba..08663edd711 100644 --- a/htdocs/includes/odtphp/odf.php +++ b/htdocs/includes/odtphp/odf.php @@ -843,7 +843,7 @@ IMG; // using windows libreoffice that must be in path // using linux/mac libreoffice that must be in path // Note PHP Config "fastcgi.impersonate=0" must set to 0 - Default is 1 - $command ='soffice --headless -env:UserInstallation=file:\''.$conf->user->dir_temp.'/odtaspdf\' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); + $command ='soffice --headless -env:UserInstallation=file:'.(getDolGlobalString('MAIN_ODT_ADD_SLASH_FOR_WINDOWS') ? '///' : '').'\''.$conf->user->dir_temp.'/odtaspdf\' --convert-to pdf --outdir '. escapeshellarg(dirname($name)). " ".escapeshellarg($name); } elseif (preg_match('/unoconv/', getDolGlobalString('MAIN_ODT_AS_PDF'))) { // If issue with unoconv, see https://github.com/dagwieers/unoconv/issues/87 From 0819a57345423d252cebb6dcefae70e0c26a5e71 Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Wed, 6 Nov 2024 19:06:17 +0100 Subject: [PATCH 5/7] Better fix for #31015 --- htdocs/compta/facture/class/api_invoices.class.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/htdocs/compta/facture/class/api_invoices.class.php b/htdocs/compta/facture/class/api_invoices.class.php index 12ddc4a7a28..fff42871750 100644 --- a/htdocs/compta/facture/class/api_invoices.class.php +++ b/htdocs/compta/facture/class/api_invoices.class.php @@ -1502,7 +1502,11 @@ class Invoices extends DolibarrApi // Creation of payment line $paymentobj = new Paiement($this->db); - $paymentobj->datepaye = $datepaye; + if (is_numeric($datepaye)) { + $paymentobj->datepaye = $datepaye; + } else { + $paymentobj->datepaye = dol_stringtotime($datepaye); + } $paymentobj->amounts = $amounts; // Array with all payments dispatching with invoice id $paymentobj->multicurrency_amounts = $multicurrency_amounts; // Array with all payments dispatching $paymentobj->paiementid = $paymentid; @@ -1650,7 +1654,11 @@ class Invoices extends DolibarrApi // Creation of payment line $paymentobj = new Paiement($this->db); - $paymentobj->datepaye = $datepaye; + if (is_numeric($datepaye)) { + $paymentobj->datepaye = $datepaye; + } else { + $paymentobj->datepaye = dol_stringtotime($datepaye); + } $paymentobj->amounts = $amounts; // Array with all payments dispatching with invoice id $paymentobj->multicurrency_amounts = $multicurrency_amounts; // Array with all payments dispatching $paymentobj->paiementid = $paymentid; From bbf83c70537a7e08ab2d16db7e21a710a4a26c41 Mon Sep 17 00:00:00 2001 From: uvaldenaire-opendsi Date: Wed, 6 Nov 2024 19:17:33 +0100 Subject: [PATCH 6/7] fix dol_getdate() when timestamp is an empty string (#31714) --- htdocs/core/lib/functions.lib.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index d87132dc7a5..c3bab3ce70c 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -2980,6 +2980,10 @@ function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = */ function dol_getdate($timestamp, $fast = false, $forcetimezone = '') { + if ($timestamp === '') { + return array(); + } + $datetimeobj = new DateTime(); $datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone if ($forcetimezone) { From bc8d443898800d7b420ad18f9c07cb2dec6a470d Mon Sep 17 00:00:00 2001 From: VIAL-GOUTEYRON Quentin Date: Wed, 6 Nov 2024 19:18:24 +0100 Subject: [PATCH 7/7] FIX: Corrected typo preventing data from being saved (#31716) * FIX: Corrected typo preventing data from being saved * fix missing length --- htdocs/admin/security.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/htdocs/admin/security.php b/htdocs/admin/security.php index 182374fd3ae..3eac851874f 100644 --- a/htdocs/admin/security.php +++ b/htdocs/admin/security.php @@ -332,7 +332,7 @@ if (getDolGlobalString('USER_PASSWORD_GENERATED') == "Perso") { print '