Skip to content

Commit

Permalink
Fixed rounding of negative zeroes
Browse files Browse the repository at this point in the history
- Added custom Invoice::round() method
- Updated InvoiceTotals
- Updated UblWriter
- Updated unit tests
  • Loading branch information
josemmo committed May 29, 2022
1 parent e671343 commit 56ea2d4
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 37 deletions.
16 changes: 16 additions & 0 deletions src/Invoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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 @@ -85,6 +86,21 @@ public function getDecimals(string $field): int {
}


/**
* Round value
* @param float $value Value to round
* @param string $field Field name
* @return float Rounded value
*/
public function round(float $value, string $field): float {
$rounded = round($value, $this->getDecimals($field));
if ($rounded == 0) {
$rounded += 0; // To fix negative zero
}
return $rounded;
}


/**
* Set rounding matrix
* @param array $matrix Rounding matrix
Expand Down
23 changes: 11 additions & 12 deletions src/Models/InvoiceTotals.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
use Einvoicing\Invoice;
use Einvoicing\Traits\VatTrait;
use function array_values;
use function round;

class InvoiceTotals {
/**
Expand Down Expand Up @@ -137,18 +136,18 @@ static public function fromInvoice(Invoice $inv, bool $round=true): InvoiceTotal

// 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'));
$totals->netAmount = $inv->round($totals->netAmount, 'invoice/netAmount');
$totals->allowancesAmount = $inv->round($totals->allowancesAmount, 'invoice/allowancesChargesAmount');
$totals->chargesAmount = $inv->round($totals->chargesAmount, 'invoice/allowancesChargesAmount');
$totals->vatAmount = $inv->round($totals->vatAmount, 'invoice/vatAmount');
$totals->taxExclusiveAmount = $inv->round($totals->taxExclusiveAmount, 'invoice/taxExclusiveAmount');
$totals->taxInclusiveAmount = $inv->round($totals->taxInclusiveAmount, 'invoice/taxInclusiveAmount');
$totals->paidAmount = $inv->round($totals->paidAmount, 'invoice/paidAmount');
$totals->roundingAmount = $inv->round($totals->roundingAmount, 'invoice/roundingAmount');
$totals->payableAmount = $inv->round($totals->payableAmount, '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'));
$item->taxableAmount = $inv->round($item->taxableAmount, 'invoice/allowancesChargesAmount');
$item->taxAmount = $inv->round($item->taxAmount, 'invoice/taxAmount');
}
}

Expand Down
49 changes: 24 additions & 25 deletions src/Writers/UblWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Einvoicing\Payments\Transfer;
use UXML\UXML;
use function in_array;
use function round;

class UblWriter extends AbstractWriter {
const NS_INVOICE = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2";
Expand Down Expand Up @@ -698,7 +697,7 @@ private function addAllowanceOrCharge(
$this->addAmountNode(
$xml,
'cbc:Amount',
round($item->getEffectiveAmount($baseAmount), $invoice->getDecimals('line/allowanceChargeAmount')),
$invoice->round($item->getEffectiveAmount($baseAmount), 'line/allowanceChargeAmount'),
$invoice->getCurrency()
);

Expand All @@ -707,7 +706,7 @@ private function addAllowanceOrCharge(
$this->addAmountNode(
$xml,
'cbc:BaseAmount',
round($baseAmount, $invoice->getDecimals('line/netAmount')),
$invoice->round($baseAmount, 'line/netAmount'),
$invoice->getCurrency()
);
}
Expand All @@ -732,7 +731,7 @@ private function addTaxTotalNodes(UXML $parent, Invoice $invoice, InvoiceTotals
$this->addAmountNode(
$xml,
'cbc:TaxAmount',
round($totals->vatAmount, $invoice->getDecimals('invoice/taxAmount')),
$invoice->round($totals->vatAmount, 'invoice/taxAmount'),
$totals->currency
);

Expand All @@ -742,13 +741,13 @@ private function addTaxTotalNodes(UXML $parent, Invoice $invoice, InvoiceTotals
$this->addAmountNode(
$vatBreakdownNode,
'cbc:TaxableAmount',
round($item->taxableAmount, $invoice->getDecimals('invoice/allowancesChargesAmount')),
$invoice->round($item->taxableAmount, 'invoice/allowancesChargesAmount'),
$totals->currency
);
$this->addAmountNode(
$vatBreakdownNode,
'cbc:TaxAmount',
round($item->taxAmount, $invoice->getDecimals('invoice/taxAmount')),
$invoice->round($item->taxAmount, 'invoice/taxAmount'),
$totals->currency
);
$this->addVatNode(
Expand All @@ -767,7 +766,7 @@ private function addTaxTotalNodes(UXML $parent, Invoice $invoice, InvoiceTotals
$this->addAmountNode(
$parent->add('cac:TaxTotal'),
'cbc:TaxAmount',
round($customVatAmount, $invoice->getDecimals('invoice/taxAmount')),
$invoice->round($customVatAmount, 'invoice/taxAmount'),
$totals->vatCurrency ?? $totals->currency
);
}
Expand All @@ -785,45 +784,45 @@ private function addDocumentTotalsNode(UXML $parent, Invoice $invoice, InvoiceTo

// Build totals matrix
$totalsMatrix = [];
$totalsMatrix['cbc:LineExtensionAmount'] = round(
$totalsMatrix['cbc:LineExtensionAmount'] = $invoice->round(
$totals->netAmount,
$invoice->getDecimals('invoice/netAmount')
'invoice/netAmount'
);
$totalsMatrix['cbc:TaxExclusiveAmount'] = round(
$totalsMatrix['cbc:TaxExclusiveAmount'] = $invoice->round(
$totals->taxExclusiveAmount,
$invoice->getDecimals('invoice/taxExclusiveAmount')
'invoice/taxExclusiveAmount'
);
$totalsMatrix['cbc:TaxInclusiveAmount'] = round(
$totalsMatrix['cbc:TaxInclusiveAmount'] = $invoice->round(
$totals->taxInclusiveAmount,
$invoice->getDecimals('invoice/taxInclusiveAmount')
'invoice/taxInclusiveAmount'
);
if ($totals->allowancesAmount > 0) {
$totalsMatrix['cbc:AllowanceTotalAmount'] = round(
$totalsMatrix['cbc:AllowanceTotalAmount'] = $invoice->round(
$totals->allowancesAmount,
$invoice->getDecimals('invoice/allowancesChargesAmount')
'invoice/allowancesChargesAmount'
);
}
if ($totals->chargesAmount > 0) {
$totalsMatrix['cbc:ChargeTotalAmount'] = round(
$totalsMatrix['cbc:ChargeTotalAmount'] = $invoice->round(
$totals->chargesAmount,
$invoice->getDecimals('invoice/allowancesChargesAmount')
'invoice/allowancesChargesAmount'
);
}
if ($totals->paidAmount > 0) {
$totalsMatrix['cbc:PrepaidAmount'] = round(
$totalsMatrix['cbc:PrepaidAmount'] = $invoice->round(
$totals->paidAmount,
$invoice->getDecimals('invoice/paidAmount')
'invoice/paidAmount'
);
}
if ($totals->roundingAmount > 0) {
$totalsMatrix['cbc:PayableRoundingAmount'] = round(
$totalsMatrix['cbc:PayableRoundingAmount'] = $invoice->round(
$totals->roundingAmount,
$invoice->getDecimals('invoice/roundingAmount')
'invoice/roundingAmount'
);
}
$totalsMatrix['cbc:PayableAmount'] = round(
$totalsMatrix['cbc:PayableAmount'] = $invoice->round(
$totals->payableAmount,
$invoice->getDecimals('invoice/payableAmount')
'invoice/payableAmount'
);

// Create and append XML nodes
Expand Down Expand Up @@ -868,7 +867,7 @@ private function addLineNode(UXML $parent, InvoiceLine $line, Invoice $invoice,
$this->addAmountNode(
$xml,
'cbc:LineExtensionAmount',
round($netAmount, $invoice->getDecimals('line/netAmount')),
$invoice->round($netAmount, 'line/netAmount'),
$invoice->getCurrency()
);
}
Expand Down Expand Up @@ -960,7 +959,7 @@ private function addLineNode(UXML $parent, InvoiceLine $line, Invoice $invoice,
$this->addAmountNode(
$priceNode,
'cbc:PriceAmount',
round($price, $invoice->getDecimals('line/price')),
$invoice->round($price, 'line/price'),
$invoice->getCurrency()
);
}
Expand Down
9 changes: 9 additions & 0 deletions tests/InvoiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ public function testCannotRemoveOutOfBoundsLines(): void {
$this->invoice->addLine(new InvoiceLine)->removeLine(1);
}

public function testCanRoundNegativeZeroes(): void {
$this->assertEquals('-1', (string) $this->invoice->round(-0.9999, 'invoice/netAmount'));
$this->assertEquals('0', (string) $this->invoice->round(-0.0001, 'invoice/netAmount'));
$this->assertEquals('0', (string) $this->invoice->round(-0, 'invoice/netAmount'));
$this->assertEquals('0', (string) $this->invoice->round(0, 'invoice/netAmount'));
$this->assertEquals('0', (string) $this->invoice->round(0.0001, 'invoice/netAmount'));
$this->assertEquals('1', (string) $this->invoice->round(0.9999, 'invoice/netAmount'));
}

public function testDecimalMatrixIsUsed(): void {
$this->invoice->setRoundingMatrix([
'invoice/paidAmount' => 4,
Expand Down

0 comments on commit 56ea2d4

Please sign in to comment.