diff --git a/ChangeLog b/ChangeLog
index 30c741222d4..43dc578e49b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,8 +6,8 @@ English Dolibarr ChangeLog
For users:
- New: [ task #877 ] Reorganize menus.
- New: [ task #858 ] Holiday module: note on manual holiday assignation.
-- New: [ task #892 ] Add hidden option in thirdparty customer/supplier module to
- hide non active companies in select_company method.
+- New: [ task #892 ] Add hidden option in thirdparty customer/supplier module to hide non active
+ companies in select_company method.
- New: [ task #531 ] Add a workload field on tasks.
- New: Add graph of bank account input/output into input-output report page.
- New: Add script export-bank-receipts.php
@@ -24,7 +24,8 @@ For users:
- New: [ task #928 ] Add extrafield feature on invoice lines.
- New: Add option ADHERENT_LOGIN_NOT_REQUIRED.
- New: Add a cron module to define scheduled jobs.
-- New: Add new graphical boxes (customer invoices per month).
+- New: Add new graphical boxes (customer invoices and orders per month).
+- New: [ task #286 ] Enhance rounding function of prices to allow round of sum instead of sum of rounding.
- Qual: Implement same rule for return value of all command line scripts (0 when success, <>0 if error).
For translators:
diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php
index 1bb675a6a0b..0de5f388b42 100644
--- a/htdocs/core/class/commonobject.class.php
+++ b/htdocs/core/class/commonobject.class.php
@@ -42,7 +42,7 @@ abstract class CommonObject
public $firstname;
public $civility_id;
public $import_key;
-
+
public $array_options=array();
public $linkedObjectsIds;
@@ -53,7 +53,7 @@ abstract class CommonObject
/**
* Method to output saved errors
- *
+ *
* @return string String with errors
*/
function errorsToString()
@@ -1464,15 +1464,18 @@ abstract class CommonObject
}
/**
- * Update total_ht, total_ttc and total_vat for an object (sum of lines)
+ * Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
+ * Must be called at end of methods addline, updateline.
*
* @param int $exclspec Exclude special product (product_type=9)
- * @param int $roundingadjust -1=Use default method (MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND or 0), 0=Use total of rounding, 1=Use rounding of total
+ * @param int $roundingadjust -1=Use default method (MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND if defined, or 0), 0=Force use total of rounding, 1=Force use rounding of total
* @param int $nodatabaseupdate 1=Do not update database. Update only properties of object.
* @return int <0 if KO, >0 if OK
*/
function update_price($exclspec=0,$roundingadjust=-1,$nodatabaseupdate=0)
{
+ global $conf;
+
include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
if ($roundingadjust < 0 && isset($conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND)) $roundingadjust=$conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND;
@@ -1486,7 +1489,7 @@ abstract class CommonObject
$fieldlocaltax2='total_localtax2';
if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') $fieldtva='tva';
- $sql = 'SELECT qty, total_ht, '.$fieldtva.' as total_tva, total_ttc, '.$fieldlocaltax1.' as total_localtax1, '.$fieldlocaltax2.' as total_localtax2,';
+ $sql = 'SELECT rowid, qty, total_ht, '.$fieldtva.' as total_tva, total_ttc, '.$fieldlocaltax1.' as total_localtax1, '.$fieldlocaltax2.' as total_localtax2,';
$sql.= ' tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type';
$sql.= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line;
$sql.= ' WHERE '.$this->fk_element.' = '.$this->id;
@@ -1496,6 +1499,7 @@ abstract class CommonObject
if ($this->table_element_line == 'contratdet') $product_field=''; // contratdet table has no product_type field
if ($product_field) $sql.= ' AND '.$product_field.' <> 9';
}
+ $sql.= ' ORDER by rowid'; // We want to be sure to always use same order of line to not change lines differently when option MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND is used
dol_syslog(get_class($this)."::update_price sql=".$sql);
$resql = $this->db->query($sql);
@@ -1506,8 +1510,9 @@ abstract class CommonObject
$this->total_localtax1 = 0;
$this->total_localtax2 = 0;
$this->total_ttc = 0;
- $vatrates = array();
- $vatrates_alllines = array();
+ $total_ht_by_vats = array();
+ $total_tva_by_vats = array();
+ $total_ttc_by_vats = array();
$num = $this->db->num_rows($resql);
$i = 0;
@@ -1515,42 +1520,34 @@ abstract class CommonObject
{
$obj = $this->db->fetch_object($resql);
- $this->total_ht += $obj->total_ht;
+ $this->total_ht += $obj->total_ht; // The only field visible at line level
$this->total_tva += $obj->total_tva;
$this->total_localtax1 += $obj->total_localtax1;
$this->total_localtax2 += $obj->total_localtax2;
$this->total_ttc += $obj->total_ttc;
+ $total_ht_by_vats[$obj->vatrate] += $obj->total_ht;
+ $total_tva_by_vats[$obj->vatrate] += $obj->total_tva;
+ $total_ttc_by_vats[$obj->vatrate] += $obj->total_ttc;
- // Check if there is a global invoice tax for this vat rate
- // FIXME: We should have no database access into this function. Also localtax 7 seems to have problem so i add condition to avoid it into standard usage without loosing it.
- if (! empty($conf->global->MAIN_USE_LOCALTAX_TYPE_7))
+ if ($roundingadjust) // Check if we need adjustement onto line for vat
{
- if ($this->total_localtax1 == 0)
- {
- // Search to know if there is a localtax of type 7
- // TODO : store local taxes types into object lines and remove this. We should use here $obj->localtax1_type but it is not yet filled into database, so we search into table of vat rate
- global $mysoc;
- $localtax1_array=getLocalTaxesFromRate($vatrate,1,$mysoc);
- if (empty($obj->localtax1_type))
- {
- $obj->localtax1_type = $localtax1_array[0];
- $obj->localtax1_tx = $localtax1_array[1];
- }
- //end TODO
- }
- if ($this->total_localtax2 == 0)
- {
- // Search to know if there is a localtax of type 7
- // TODO : store local taxes types into object lines and remove this. We should use here $obj->localtax1_type but it is not yet filled into database, so we search into table of vat rate
- global $mysoc;
- $localtax2_array=getLocalTaxesFromRate($vatrate,2,$mysoc);
- if (empty($obj->localtax2_type))
- {
- $obj->localtax2_type = $localtax2_array[0];
- $obj->localtax2_tx = $localtax2_array[1];
- }
- //end TODO
- }
+ $tmpvat=price2num($total_ht_by_vats[$obj->vatrate] * $obj->vatrate / 100, 'MT', 1);
+ $diff=price2num($total_tva_by_vats[$obj->vatrate]-$tmpvat, 'MT', 1);
+ //print 'Line '.$i.' rowid='.$obj->rowid.' vat_rate='.$obj->vatrate.' total_ht='.$obj->total_ht.' total_tva='.$obj->total_tva.' total_ttc='.$obj->total_ttc.' total_ht_by_vats='.$total_ht_by_vats[$obj->vatrate].' total_tva_by_vats='.$total_tva_by_vats[$obj->vatrate].' (new calculation = '.$tmpvat.') total_ttc_by_vats='.$total_ttc_by_vats[$obj->vatrate].($diff?" => DIFF":"")."
\n";
+ if ($diff)
+ {
+ if ($diff > 0.1) { dol_print_error('','A rounding difference was detected but is to high to be corrected'); exit; }
+ $sqlfix="UPDATE ".MAIN_DB_PREFIX.$this->table_element_line." SET ".$fieldtva." = ".($obj->total_tva - $diff).", total_ttc = ".($obj->total_ttc - $diff)." WHERE rowid = ".$obj->rowid;
+ //print 'We found a difference of '.$diff.' for line rowid = '.$obj->rowid.". Run sqlfix = ".$sqlfix."
\n";
+ dol_syslog('We found a difference of '.$diff.' for line rowid = '.$obj->rowid.". Run sqlfix = ".$sqlfix);
+ $resqlfix=$this->db->query($sqlfix);
+ if (! $resqlfix) dol_print_error($this->db,'Failed to update line');
+ $this->total_tva -= $diff;
+ $this->total_ttc -= $diff;
+ $total_tva_by_vats[$obj->vatrate] -= $diff;
+ $total_ttc_by_vats[$obj->vatrate] -= $diff;
+
+ }
}
$i++;
@@ -1567,6 +1564,7 @@ abstract class CommonObject
$fieldlocaltax1='localtax1';
$fieldlocaltax2='localtax2';
$fieldttc='total_ttc';
+ // Specific code for backward compatibility with old field names
if ($this->element == 'facture' || $this->element == 'facturerec') $fieldht='total';
if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') $fieldtva='total_tva';
if ($this->element == 'propal') $fieldttc='total';
diff --git a/test/phpunit/FactureTestRounding.php b/test/phpunit/FactureTestRounding.php
index f000b74289d..446b6893ae0 100644
--- a/test/phpunit/FactureTestRounding.php
+++ b/test/phpunit/FactureTestRounding.php
@@ -1,5 +1,5 @@
+/* Copyright (C) 2012-2013 Laurent Destailleur
*
* 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
@@ -157,9 +157,9 @@ class FactureTestRounding extends PHPUnit_Framework_TestCase
/**
* testFactureRoundingCreate2
- *
+ *
* @return int
- *
+ *
* @depends testFactureRoundingCreate1
* Test according to page http://wiki.dolibarr.org/index.php/Draft:VAT_calculation_and_rounding#Standard_usage
*/
@@ -194,5 +194,133 @@ class FactureTestRounding extends PHPUnit_Framework_TestCase
//$this->assertEquals($newlocalobject->total_ttc, 2.73);
return $result;
}
+
+
+ /**
+ * testFactureAddLine1
+ *
+ * @return void
+ */
+ public function testFactureAddLine1()
+ {
+ global $conf,$user,$langs,$db;
+ $conf=$this->savconf;
+ $user=$this->savuser;
+ $langs=$this->savlangs;
+ $db=$this->savdb;
+
+ // With option MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND = 0
+ $conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND=0;
+ $localobject1a=new Facture($this->savdb);
+ $localobject1a->initAsSpecimen('nolines');
+ $facid=$localobject1a->create($user);
+ $localobject1a->addline($facid, 'Line 1', 6.36, 15, 21); // This include update_price
+ print __METHOD__." id=".$facid." total_ttc=".$localobject1a->total_ttc."\n";
+ $this->assertEquals( 95.40, $localobject1a->total_ht);
+ $this->assertEquals( 20.03, $localobject1a->total_tva);
+ $this->assertEquals(115.43, $localobject1a->total_ttc);
+
+ // With option MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND = 1
+ $conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND=1;
+ $localobject1b=new Facture($this->savdb);
+ $localobject1b->initAsSpecimen('nolines');
+ $facid=$localobject1b->create($user);
+ $localobject1b->addline($facid, 'Line 1', 6.36, 15, 21); // This include update_price
+ print __METHOD__." id=".$facid." total_ttc=".$localobject1b->total_ttc."\n";
+ $this->assertEquals( 95.40, $localobject1b->total_ht, 'testFactureAddLine1 total_ht');
+ $this->assertEquals( 20.03, $localobject1b->total_tva, 'testFactureAddLine1 total_tva');
+ $this->assertEquals(115.43, $localobject1b->total_ttc, 'testFactureAddLine1 total_ttc');
+ }
+
+ /**
+ * testFactureAddLine2
+ *
+ * @return void
+ *
+ * @depends testFactureAddLine1
+ * The depends says test is run only if previous is ok
+ */
+ public function testFactureAddLine2()
+ {
+ global $conf,$user,$langs,$db;
+ $conf=$this->savconf;
+ $user=$this->savuser;
+ $langs=$this->savlangs;
+ $db=$this->savdb;
+
+ // With option MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND = 0
+ $conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND=0;
+ $localobject2=new Facture($this->savdb);
+ $localobject2->initAsSpecimen('nolines');
+ $facid=$localobject2->create($user);
+ $localobject2->addline($facid, 'Line 1', 6.36, 5, 21);
+ $localobject2->addline($facid, 'Line 2', 6.36, 5, 21);
+ $localobject2->addline($facid, 'Line 3', 6.36, 5, 21);
+ print __METHOD__." id=".$facid." total_ttc=".$localobject2->total_ttc."\n";
+ $this->assertEquals( 95.40, $localobject2->total_ht);
+ $this->assertEquals( 20.04, $localobject2->total_tva);
+ $this->assertEquals(115.44, $localobject2->total_ttc);
+
+ // With option MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND = 1
+ $conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND=1;
+ $localobject2=new Facture($this->savdb);
+ $localobject2->initAsSpecimen('nolines');
+ $facid=$localobject2->create($user);
+ $localobject2->addline($facid, 'Line 1', 6.36, 5, 21);
+ $localobject2->addline($facid, 'Line 2', 6.36, 5, 21);
+ $localobject2->addline($facid, 'Line 3', 6.36, 5, 21);
+ print __METHOD__." id=".$facid." total_ttc=".$localobject2->total_ttc."\n";
+ $this->assertEquals( 95.40, $localobject2->total_ht);
+ $this->assertEquals( 20.03, $localobject2->total_tva);
+ $this->assertEquals(115.43, $localobject2->total_ttc);
+ }
+
+ /**
+ * testFactureAddLine3
+ *
+ * @return void
+ *
+ * @depends testFactureAddLine2
+ * The depends says test is run only if previous is ok
+ */
+ public function testFactureAddLine3()
+ {
+ global $conf,$user,$langs,$db;
+ $conf=$this->savconf;
+ $user=$this->savuser;
+ $langs=$this->savlangs;
+ $db=$this->savdb;
+
+ // With option MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND = 0
+ $conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND=0;
+ $localobject3=new Facture($this->savdb);
+ $localobject3->initAsSpecimen('nolines');
+ $facid=$localobject3->create($user);
+ $localobject3->addline($facid, 'Line 1', 6.36, 3, 21);
+ $localobject3->addline($facid, 'Line 2', 6.36, 3, 21);
+ $localobject3->addline($facid, 'Line 3', 6.36, 3, 21);
+ $localobject3->addline($facid, 'Line 4', 6.36, 3, 21);
+ $localobject3->addline($facid, 'Line 5', 6.36, 3, 21);
+ print __METHOD__." id=".$facid." total_ttc=".$localobject3->total_ttc."\n";
+ $this->assertEquals( 95.40, $localobject3->total_ht);
+ $this->assertEquals( 20.05, $localobject3->total_tva);
+ $this->assertEquals(115.45, $localobject3->total_ttc);
+
+ // With option MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND = 1
+ $conf->global->MAIN_ROUNDOFTOTAL_NOT_TOTALOFROUND=1;
+ $localobject3=new Facture($this->savdb);
+ $localobject3->initAsSpecimen('nolines');
+ $facid=$localobject3->create($user);
+ $localobject3->addline($facid, 'Line 1', 6.36, 3, 21);
+ $localobject3->addline($facid, 'Line 2', 6.36, 3, 21);
+ $localobject3->addline($facid, 'Line 3', 6.36, 3, 21);
+ $localobject3->addline($facid, 'Line 4', 6.36, 3, 21);
+ $localobject3->addline($facid, 'Line 5', 6.36, 3, 21);
+ print __METHOD__." id=".$facid." total_ttc=".$localobject3->total_ttc."\n";
+ $this->assertEquals( 95.40, $localobject3->total_ht);
+ $this->assertEquals( 20.03, $localobject3->total_tva);
+ $this->assertEquals(115.43, $localobject3->total_ttc);
+ }
+
}
?>
\ No newline at end of file