Skip to content

Commit

Permalink
Merge pull request #34 from josemmo/develop
Browse files Browse the repository at this point in the history
v0.2.3
  • Loading branch information
josemmo authored Dec 17, 2022
2 parents 353321f + e93d4ef commit 9b1bc79
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 110 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version: ['7.1', '7.2', '7.3', '7.4', '8.0']
php-version: ['7.1', '7.2', '7.3', '7.4', '8.0', '8.1']
include:
- php-version: '8.1'
deploy: ${{ github.ref == 'refs/heads/master' }}
- php-version: '8.2'
deploy: ${{ github.ref == 'refs/heads/master' }}
- php-version: '8.3'
experimental: true
steps:
# Download code from repository
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3

# Setup PHP
- name: Setup PHP
Expand Down Expand Up @@ -52,7 +52,7 @@ jobs:
# Deploy documentation
- name: Deploy documentation
if: ${{ success() && matrix.deploy || false }}
uses: JamesIves/github-pages-deploy-action@v4.2.5
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: site
Expand Down
7 changes: 3 additions & 4 deletions src/Invoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -669,10 +669,9 @@ public function clearLines(): self {

/**
* Get invoice total
* @param boolean $round Whether to round values or not
* @return InvoiceTotals Invoice totals
* @return InvoiceTotals Invoice totals
*/
public function getTotals(bool $round=true): InvoiceTotals {
return InvoiceTotals::fromInvoice($this, $round);
public function getTotals(): InvoiceTotals {
return InvoiceTotals::fromInvoice($this);
}
}
65 changes: 34 additions & 31 deletions src/Models/InvoiceTotals.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,10 @@ class InvoiceTotals {

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

Expand All @@ -100,56 +99,60 @@ static public function fromInvoice(Invoice $inv, bool $round=true): InvoiceTotal

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

// Apply allowance and charge totals
// Process allowances
foreach ($inv->getAllowances() as $item) {
$allowanceAmount = $item->getEffectiveAmount($totals->netAmount);
$allowanceAmount = $inv->round($item->getEffectiveAmount($totals->netAmount), 'line/allowanceChargeAmount');
$totals->allowancesAmount += $allowanceAmount;
self::updateVatMap($vatMap, $item, -$allowanceAmount);
}
$totals->allowancesAmount = $inv->round($totals->allowancesAmount, 'invoice/allowancesChargesAmount');

// Process charges
foreach ($inv->getCharges() as $item) {
$chargeAmount = $item->getEffectiveAmount($totals->netAmount);
$chargeAmount = $inv->round($item->getEffectiveAmount($totals->netAmount), 'line/allowanceChargeAmount');
$totals->chargesAmount += $chargeAmount;
self::updateVatMap($vatMap, $item, $chargeAmount);
}
$totals->chargesAmount = $inv->round($totals->chargesAmount, 'invoice/allowancesChargesAmount');

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

// Calculate rest of properties
$totals->taxExclusiveAmount = $totals->netAmount - $totals->allowancesAmount + $totals->chargesAmount;
$totals->taxInclusiveAmount = $totals->taxExclusiveAmount + $totals->vatAmount;
$totals->paidAmount = $inv->getPaidAmount();
$totals->roundingAmount = $inv->getRoundingAmount();
// Add custom VAT amount
$totals->customVatAmount = $inv->getCustomVatAmount();
$totals->payableAmount = $totals->taxInclusiveAmount - $totals->paidAmount + $totals->roundingAmount;
if ($totals->customVatAmount !== null) {
$totals->customVatAmount = $inv->round($inv->getCustomVatAmount(), 'invoice/vatAmount');
}

// Attach VAT breakdown
$totals->vatBreakdown = array_values($vatMap);

// Round values
if ($round) {
$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 = $inv->round($item->taxableAmount, 'invoice/allowancesChargesAmount');
$item->taxAmount = $inv->round($item->taxAmount, 'invoice/taxAmount');
}
}
// Calculate rest of properties
$totals->taxExclusiveAmount = $inv->round(
$totals->netAmount - $totals->allowancesAmount + $totals->chargesAmount,
'invoice/taxExclusiveAmount'
);
$totals->taxInclusiveAmount = $inv->round(
$totals->taxExclusiveAmount + $totals->vatAmount,
'invoice/taxInclusiveAmount'
);
$totals->paidAmount = $inv->round($inv->getPaidAmount(), 'invoice/paidAmount');
$totals->roundingAmount = $inv->round($inv->getRoundingAmount(), 'invoice/roundingAmount');
$totals->payableAmount = $inv->round(
$totals->taxInclusiveAmount - $totals->paidAmount + $totals->roundingAmount,
'invoice/payableAmount'
);

return $totals;
}
Expand Down
85 changes: 22 additions & 63 deletions src/Writers/UblWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class UblWriter extends AbstractWriter {
* @inheritdoc
*/
public function export(Invoice $invoice): string {
$totals = $invoice->getTotals(false);
$totals = $invoice->getTotals();
$xml = UXML::newInstance('Invoice', null, [
'xmlns' => self::NS_INVOICE,
'xmlns:cac' => self::NS_CAC,
Expand Down Expand Up @@ -169,8 +169,8 @@ public function export(Invoice $invoice): string {
}

// Invoice totals
$this->addTaxTotalNodes($xml, $invoice, $totals);
$this->addDocumentTotalsNode($xml, $invoice, $totals);
$this->addTaxTotalNodes($xml, $totals);
$this->addDocumentTotalsNode($xml, $totals);

// Invoice lines
$lines = $invoice->getLines();
Expand Down Expand Up @@ -657,7 +657,7 @@ private function addPaymentMandateNode(UXML $parent, Mandate $mandate) {
* @param AllowanceOrCharge $item Allowance or charge instance
* @param boolean $isCharge Is charge (TRUE) or allowance (FALSE)
* @param Invoice $invoice Invoice instance
* @param InvoiceTotals|null $totals Unrounded invoice totals or NULL in case at line level
* @param InvoiceTotals|null $totals Invoice totals or NULL in case at line level
* @param InvoiceLine|null $line Invoice line or NULL in case of at document level
*/
private function addAllowanceOrCharge(
Expand Down Expand Up @@ -721,36 +721,20 @@ private function addAllowanceOrCharge(

/**
* Add tax total nodes
* @param UXML $parent Parent element
* @param Invoice $invoice Invoice instance
* @param InvoiceTotals $totals Unrounded invoice totals
* @param UXML $parent Parent element
* @param InvoiceTotals $totals Invoice totals
*/
private function addTaxTotalNodes(UXML $parent, Invoice $invoice, InvoiceTotals $totals) {
private function addTaxTotalNodes(UXML $parent, InvoiceTotals $totals) {
$xml = $parent->add('cac:TaxTotal');

// Add tax amount
$this->addAmountNode(
$xml,
'cbc:TaxAmount',
$invoice->round($totals->vatAmount, 'invoice/taxAmount'),
$totals->currency
);
$this->addAmountNode($xml, 'cbc:TaxAmount', $totals->vatAmount, $totals->currency);

// Add each tax details
foreach ($totals->vatBreakdown as $item) {
$vatBreakdownNode = $xml->add('cac:TaxSubtotal');
$this->addAmountNode(
$vatBreakdownNode,
'cbc:TaxableAmount',
$invoice->round($item->taxableAmount, 'invoice/allowancesChargesAmount'),
$totals->currency
);
$this->addAmountNode(
$vatBreakdownNode,
'cbc:TaxAmount',
$invoice->round($item->taxAmount, 'invoice/taxAmount'),
$totals->currency
);
$this->addAmountNode($vatBreakdownNode, 'cbc:TaxableAmount', $item->taxableAmount, $totals->currency);
$this->addAmountNode($vatBreakdownNode, 'cbc:TaxAmount', $item->taxAmount, $totals->currency);
$this->addVatNode(
$vatBreakdownNode,
'cac:TaxCategory',
Expand All @@ -767,7 +751,7 @@ private function addTaxTotalNodes(UXML $parent, Invoice $invoice, InvoiceTotals
$this->addAmountNode(
$parent->add('cac:TaxTotal'),
'cbc:TaxAmount',
$invoice->round($customVatAmount, 'invoice/taxAmount'),
$customVatAmount,
$totals->vatCurrency ?? $totals->currency
);
}
Expand All @@ -776,55 +760,30 @@ private function addTaxTotalNodes(UXML $parent, Invoice $invoice, InvoiceTotals

/**
* Add document totals node
* @param UXML $parent Parent element
* @param Invoice $invoice Invoice instance
* @param InvoiceTotals $totals Unrounded invoice totals
* @param UXML $parent Parent element
* @param InvoiceTotals $totals Invoice totals
*/
private function addDocumentTotalsNode(UXML $parent, Invoice $invoice, InvoiceTotals $totals) {
private function addDocumentTotalsNode(UXML $parent, InvoiceTotals $totals) {
$xml = $parent->add('cac:LegalMonetaryTotal');

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

// Create and append XML nodes
foreach ($totalsMatrix as $field=>$amount) {
Expand Down
4 changes: 4 additions & 0 deletions tests/Integration/IntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ public function testCanRecreatePeppolAllowanceExample(): void {
$this->importAndExportInvoice(__DIR__ . "/peppol-allowance.xml");
}

public function testCanRecreatePeppolRoundingExample(): void {
$this->importAndExportInvoice(__DIR__ . "/peppol-rounding.xml");
}

public function testCanRecreateCiusRoTaxCurrencyCodeExample(): void {
$this->importAndExportInvoice(__DIR__ . "/cius-ro-tax-currency-code.xml");
}
Expand Down
Loading

0 comments on commit 9b1bc79

Please sign in to comment.