New: [ task #286 ] Enhance rounding function of prices to allow round of

sum instead of sum of rounding
This commit is contained in:
Laurent Destailleur 2013-07-04 00:11:40 +02:00
parent 33ed654745
commit eef039e02f
3 changed files with 170 additions and 43 deletions

View File

@ -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:

View File

@ -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":"")."<br>\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."<br>\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';

View File

@ -1,5 +1,5 @@
<?php
/* Copyright (C) 2012 Laurent Destailleur <eldy@users.sourceforge.net>
/* Copyright (C) 2012-2013 Laurent Destailleur <eldy@users.sourceforge.net>
*
* 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);
}
}
?>