Skip to content

Commit

Permalink
Merge pull request #18 from ChristianVermeulen/fix-rounding
Browse files Browse the repository at this point in the history
Move rounding to last writing step
  • Loading branch information
josemmo authored Apr 25, 2022
2 parents 5a25f75 + faed93d commit 6d3b140
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 106 deletions.
6 changes: 2 additions & 4 deletions src/AllowanceOrCharge.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
namespace Einvoicing;

use Einvoicing\Traits\VatTrait;
use function round;

class AllowanceOrCharge {
protected $reasonCode = null;
Expand Down Expand Up @@ -104,15 +103,14 @@ public function markAsFixedAmount(): self {
/**
* Get effective amount relative to base amount
* @param float $baseAmount Base amount
* @param int $decimals Number of decimal places
* @return float Effective amount
*/
public function getEffectiveAmount(float $baseAmount, int $decimals): float {
public function getEffectiveAmount(float $baseAmount): float {
$amount = $this->getAmount();
if ($this->isPercentage()) {
$amount = $baseAmount * ($amount / 100);
}
$amount = round($amount, $decimals);

return $amount;
}
}
14 changes: 6 additions & 8 deletions src/Invoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use function array_splice;
use function count;
use function is_subclass_of;
use function round;

class Invoice {
const DEFAULT_DECIMALS = 8;
Expand Down Expand Up @@ -377,11 +376,10 @@ public function setContractReference(?string $contractReference): self {

/**
* Get invoice prepaid amount
* NOTE: may be rounded according to the CIUS specification
* @return float Invoice prepaid amount
*/
public function getPaidAmount(): float {
return round($this->paidAmount, $this->getDecimals('invoice/paidAmount'));
return $this->paidAmount;
}


Expand All @@ -398,11 +396,10 @@ public function setPaidAmount(float $paidAmount): self {

/**
* Get invoice rounding amount
* NOTE: may be rounded according to the CIUS specification
* @return float Invoice rounding amount
*/
public function getRoundingAmount(): float {
return round($this->roundingAmount, $this->getDecimals('invoice/roundingAmount'));
return $this->roundingAmount;
}


Expand Down Expand Up @@ -564,9 +561,10 @@ public function clearLines(): self {

/**
* Get invoice total
* @return InvoiceTotals Invoice totals
* @param boolean $round Whether to round values or not
* @return InvoiceTotals Invoice totals
*/
public function getTotals(): InvoiceTotals {
return InvoiceTotals::fromInvoice($this);
public function getTotals(bool $round=true): InvoiceTotals {
return InvoiceTotals::fromInvoice($this, $round);
}
}
29 changes: 12 additions & 17 deletions src/InvoiceLine.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Einvoicing\Traits\ClassificationIdentifiersTrait;
use Einvoicing\Traits\PeriodTrait;
use Einvoicing\Traits\VatTrait;
use function round;

class InvoiceLine {
protected $id = null;
Expand Down Expand Up @@ -297,42 +296,39 @@ public function setBaseQuantity(float $baseQuantity): self {

/**
* Get total net amount (without VAT) before allowances/charges
* @param int $decimals Number of decimal places
* @return float|null Net amount before allowances/charges
*/
public function getNetAmountBeforeAllowancesCharges(int $decimals=Invoice::DEFAULT_DECIMALS): ?float {
public function getNetAmountBeforeAllowancesCharges(): ?float {
if ($this->price === null) {
return null;
}
return round(($this->price / $this->baseQuantity) * $this->quantity, $decimals);
return ($this->price / $this->baseQuantity) * $this->quantity;
}


/**
* Get allowances total amount
* @param int $decimals Number of decimal places
* @return float Allowances total amount
*/
public function getAllowancesAmount(int $decimals=Invoice::DEFAULT_DECIMALS): float {
public function getAllowancesAmount(): float {
$allowancesAmount = 0;
$baseAmount = $this->getNetAmountBeforeAllowancesCharges($decimals) ?? 0;
$baseAmount = $this->getNetAmountBeforeAllowancesCharges() ?? 0.0;
foreach ($this->getAllowances() as $item) {
$allowancesAmount += $item->getEffectiveAmount($baseAmount, $decimals);
$allowancesAmount += $item->getEffectiveAmount($baseAmount);
}
return $allowancesAmount;
}


/**
* Get charges total amount
* @param int $decimals Number of decimal places
* @return float Charges total amount
*/
public function getChargesAmount(int $decimals=Invoice::DEFAULT_DECIMALS): float {
public function getChargesAmount(): float {
$chargesAmount = 0;
$baseAmount = $this->getNetAmountBeforeAllowancesCharges($decimals) ?? 0;
$baseAmount = $this->getNetAmountBeforeAllowancesCharges() ?? 0.0;
foreach ($this->getCharges() as $item) {
$chargesAmount += $item->getEffectiveAmount($baseAmount, $decimals);
$chargesAmount += $item->getEffectiveAmount($baseAmount);
}
return $chargesAmount;
}
Expand All @@ -341,16 +337,15 @@ public function getChargesAmount(int $decimals=Invoice::DEFAULT_DECIMALS): float
/**
* Get total net amount (without VAT)
* NOTE: inclusive of line level allowances and charges
* @param int $decimals Number of decimal places
* @return float|null Net amount
*/
public function getNetAmount(int $decimals=Invoice::DEFAULT_DECIMALS): ?float {
$netAmount = $this->getNetAmountBeforeAllowancesCharges($decimals);
public function getNetAmount(): ?float {
$netAmount = $this->getNetAmountBeforeAllowancesCharges();
if ($netAmount === null) {
return null;
}
$netAmount -= $this->getAllowancesAmount($decimals);
$netAmount += $this->getChargesAmount($decimals);
$netAmount -= $this->getAllowancesAmount();
$netAmount += $this->getChargesAmount();
return $netAmount;
}
}
50 changes: 33 additions & 17 deletions src/Models/InvoiceTotals.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,55 +13,55 @@ class InvoiceTotals {
*/
public $currency;

/**
/**
* Sum of all invoice line net amounts
* @var float
*/
public $netAmount = 0;

/**
/**
* Sum of all allowances on document level
* @var float
*/
public $allowancesAmount = 0;

/**
/**
* Sum of all charges on document level
* @var float
*/
public $chargesAmount = 0;

/**
/**
* Total VAT amount for the invoice
* @var float
*/
public $vatAmount = 0;

/**
/**
* Invoice total amount without VAT
* @var float
*/
public $taxExclusiveAmount = 0;

/**
/**
* Invoice total amount with VAT
* @var float
*/
public $taxInclusiveAmount = 0;

/**
/**
* The sum of amounts which have been paid in advance
* @var float
*/
public $paidAmount = 0;

/**
/**
* The amount to be added to the invoice total to round the amount to be paid
* @var float
*/
public $roundingAmount = 0;

/**
/**
* Amount due for payment
* @var float
*/
Expand All @@ -75,10 +75,11 @@ class InvoiceTotals {

/**
* Create instance from invoice
* @param Invoice $inv Invoice instance
* @return self Totals instance
* @param Invoice $inv Invoice instance
* @param boolean $round Whether to round values or not
* @return self Totals instance
*/
static public function fromInvoice(Invoice $inv): InvoiceTotals {
static public function fromInvoice(Invoice $inv, bool $round=true): InvoiceTotals {
$totals = new self();
$vatMap = [];

Expand All @@ -87,28 +88,26 @@ static public function fromInvoice(Invoice $inv): InvoiceTotals {

// Process all invoice lines
foreach ($inv->getLines() as $line) {
$lineNetAmount = $line->getNetAmount($inv->getDecimals('line/netAmount')) ?? 0;
$lineNetAmount = $line->getNetAmount() ?? 0.0;
$totals->netAmount += $lineNetAmount;
self::updateVatMap($vatMap, $line, $lineNetAmount);
}

// Apply allowance and charge totals
$allowancesChargesDecimals = $inv->getDecimals('invoice/allowancesChargesAmount');
foreach ($inv->getAllowances() as $item) {
$allowanceAmount = $item->getEffectiveAmount($totals->netAmount, $allowancesChargesDecimals);
$allowanceAmount = $item->getEffectiveAmount($totals->netAmount);
$totals->allowancesAmount += $allowanceAmount;
self::updateVatMap($vatMap, $item, -$allowanceAmount);
}
foreach ($inv->getCharges() as $item) {
$chargeAmount = $item->getEffectiveAmount($totals->netAmount, $allowancesChargesDecimals);
$chargeAmount = $item->getEffectiveAmount($totals->netAmount);
$totals->chargesAmount += $chargeAmount;
self::updateVatMap($vatMap, $item, $chargeAmount);
}

// Calculate VAT amounts
foreach ($vatMap as $item) {
$item->taxAmount = $item->taxableAmount * ($item->rate / 100);
$item->taxAmount = round($item->taxAmount, $inv->getDecimals('invoice/taxAmount'));
$totals->vatAmount += $item->taxAmount;
}

Expand All @@ -122,6 +121,23 @@ static public function fromInvoice(Invoice $inv): InvoiceTotals {
// Attach VAT breakdown
$totals->vatBreakdown = array_values($vatMap);

// Round values
if ($round) {
$totals->netAmount = round($totals->netAmount, $inv->getDecimals('invoice/netAmount'));
$totals->allowancesAmount = round($totals->allowancesAmount, $inv->getDecimals('invoice/allowancesChargesAmount'));
$totals->chargesAmount = round($totals->chargesAmount, $inv->getDecimals('invoice/allowancesChargesAmount'));
$totals->vatAmount = round($totals->vatAmount, $inv->getDecimals('invoice/vatAmount'));
$totals->taxExclusiveAmount = round($totals->taxExclusiveAmount, $inv->getDecimals('invoice/taxExclusiveAmount'));
$totals->taxInclusiveAmount = round($totals->taxInclusiveAmount, $inv->getDecimals('invoice/taxInclusiveAmount'));
$totals->paidAmount = round($totals->paidAmount, $inv->getDecimals('invoice/paidAmount'));
$totals->roundingAmount = round($totals->roundingAmount, $inv->getDecimals('invoice/roundingAmount'));
$totals->payableAmount = round($totals->payableAmount, $inv->getDecimals('invoice/payableAmount'));
foreach ($totals->vatBreakdown as $item) {
$item->taxableAmount = round($item->taxableAmount, $inv->getDecimals('invoice/allowancesChargesAmount'));
$item->taxAmount = round($item->taxAmount, $inv->getDecimals('invoice/taxAmount'));
}
}

return $totals;
}

Expand Down
Loading

0 comments on commit 6d3b140

Please sign in to comment.