From 0dd112f606c5530df69494c8369db0db4b5b373d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?=
Date: Fri, 20 May 2022 12:51:08 +0200
Subject: [PATCH 01/11] Fixed link rot
- Updated broken links to EU Commission website
---
README.md | 6 +++---
docs/getting-started/eu-einvoicing-concepts.md | 6 +++---
docs/getting-started/installation.md | 2 +-
docs/index.md | 6 +++---
4 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index e51662f..4d85903 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,9 @@
## About
-eInvoicing is a PHP library for creating and reading electronic invoices according to the [eInvoicing Directive and European standard](https://ec.europa.eu/cefdigital/wiki/display/CEFDIGITAL/eInvoicing).
+eInvoicing is a PHP library for creating and reading electronic invoices according to the [eInvoicing Directive and European standard](https://ec.europa.eu/digital-building-blocks/wikis/display/DIGITAL/eInvoicing).
-It aims to be 100% compliant with [EN 16931](https://ec.europa.eu/cefdigital/wiki/x/kwFVBg) as well as with the most popular CIUS and extensions, such as [PEPPOL BIS](https://docs.peppol.eu/poacc/billing/3.0/bis/).
+It aims to be 100% compliant with [EN 16931](https://ec.europa.eu/digital-building-blocks/wikis/x/boTXGw) as well as with the most popular CIUS and extensions, such as [PEPPOL BIS](https://docs.peppol.eu/poacc/billing/3.0/bis/).
## Installation
First of all, make sure your environment meets the following requirements:
@@ -98,7 +98,7 @@ echo $writer->export($inv);
These are the expected features for the library and how's it going so far:
- [x] Representation of invoices, parties and invoice lines as objects
-- [x] Compatibility with the most used [CIUS and extensions](https://ec.europa.eu/cefdigital/wiki/x/5xLoAg)
+- [x] Compatibility with the most used [CIUS and extensions](https://ec.europa.eu/digital-building-blocks/wikis/display/EINVCOMMUNITY/Registry+of+CIUS+%28Core+Invoice+Usage+Specifications%29+and+Extensions)
- [x] Export invoices to UBL documents
- [x] Import invoices from UBL documents
- [ ] Export invoices to CII documents
diff --git a/docs/getting-started/eu-einvoicing-concepts.md b/docs/getting-started/eu-einvoicing-concepts.md
index e307fec..004d6c1 100644
--- a/docs/getting-started/eu-einvoicing-concepts.md
+++ b/docs/getting-started/eu-einvoicing-concepts.md
@@ -39,7 +39,7 @@ more particular cases, such us the ones mentioned before.
The most popular CIUS is [PEPPOL BIS Billing 3.0][4], a cross-border specification used by multiple countries and
international companies from both public and private sectors.
-[1]: https://ec.europa.eu/cefdigital/wiki/display/CEFDIGITAL/Navigating+the+eInvoicing+standard+documentation
-[2]: https://ec.europa.eu/cefdigital/wiki/display/CEFDIGITAL/Required+syntaxes
-[3]: https://ec.europa.eu/cefdigital/wiki/x/5xLoAg
+[1]: https://ec.europa.eu/digital-building-blocks/wikis/display/DIGITAL/Navigating+the+eInvoicing+standard+documentation
+[2]: https://ec.europa.eu/digital-building-blocks/wikis/display/DIGITAL/Required+syntaxes
+[3]: https://ec.europa.eu/digital-building-blocks/wikis/display/EINVCOMMUNITY/Registry+of+CIUS+%28Core+Invoice+Usage+Specifications%29+and+Extensions
[4]: http://docs.peppol.eu/poacc/billing/3.0/
diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md
index 9aacb43..28d462b 100644
--- a/docs/getting-started/installation.md
+++ b/docs/getting-started/installation.md
@@ -1,6 +1,6 @@
# Getting Started
eInvoicing (short for "European Invoicing") is a free and open-source library written in PHP for creating and reading
-electronic invoices compliant with [EN 16931](https://ec.europa.eu/cefdigital/wiki/x/kwFVBg).
+electronic invoices compliant with [EN 16931](https://ec.europa.eu/digital-building-blocks/wikis/x/boTXGw).
## Requirements
In order to install this library, your environment has to meet the following requirements:
diff --git a/docs/index.md b/docs/index.md
index 341f878..028f819 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,9 +1,9 @@
# European Invoicing (eInvoicing)
-eInvoicing is a PHP library for creating and reading electronic invoices according to the [eInvoicing Directive and European standard](https://ec.europa.eu/cefdigital/wiki/display/CEFDIGITAL/eInvoicing).
+eInvoicing is a PHP library for creating and reading electronic invoices according to the [eInvoicing Directive and European standard](https://ec.europa.eu/digital-building-blocks/wikis/display/DIGITAL/eInvoicing).
-It aims to be 100% compliant with [EN 16931](https://ec.europa.eu/cefdigital/wiki/x/kwFVBg) as well as with the most popular CIUS and extensions, such as [PEPPOL BIS](https://docs.peppol.eu/poacc/billing/3.0/bis/).
+It aims to be 100% compliant with [EN 16931](https://ec.europa.eu/digital-building-blocks/wikis/x/boTXGw) as well as with the most popular CIUS and extensions, such as [PEPPOL BIS](https://docs.peppol.eu/poacc/billing/3.0/bis/).
[Get Started](getting-started/installation.md){: .md-button .md-button--primary }
-[Go to GitHub](https://github.com/josemmo/einvoicing){: .md-button }
\ No newline at end of file
+[Go to GitHub](https://github.com/josemmo/einvoicing){: .md-button }
From 13aa515575836a69acfee31ba7c98628cb5dd030 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?=
Date: Thu, 26 May 2022 19:39:50 +0200
Subject: [PATCH 02/11] Fixed VAT exemption reasons
- Updated UblReader
- Added new Peppol VAT test
> UblReader incorrectly threw an exception on `null` VAT rates
---
src/Readers/UblReader.php | 9 +--
tests/Integration/IntegrationTest.php | 4 +
tests/Integration/peppol-vat-o.xml | 106 ++++++++++++++++++++++++++
3 files changed, 114 insertions(+), 5 deletions(-)
create mode 100644 tests/Integration/peppol-vat-o.xml
diff --git a/src/Readers/UblReader.php b/src/Readers/UblReader.php
index f257b7e..73ebb66 100644
--- a/src/Readers/UblReader.php
+++ b/src/Readers/UblReader.php
@@ -62,10 +62,8 @@ public function import(string $document): Invoice {
throw new InvalidArgumentException('Missing node from tax item');
}
$rateNode = $node->get("{{$cbc}}Percent");
- if ($rateNode === null) {
- throw new InvalidArgumentException('Missing node from tax item');
- }
- $key = "{$categoryNode->asText()}:{$rateNode->asText()}";
+ $rateKey = ($rateNode === null) ? '' : floatval($rateNode->asText());
+ $key = "{$categoryNode->asText()}:{$rateKey}";
// Save reasons
$taxExemptions[$key] = [
@@ -639,7 +637,8 @@ private function setVatAttributes($target, UXML $xml, array $taxExemptions) {
}
// Tax exemption reasons
- $key = "{$target->getVatCategory()}:{$target->getVatRate()}";
+ $rateKey = $target->getVatRate() ?? '';
+ $key = "{$target->getVatCategory()}:{$rateKey}";
$target->setVatExemptionReasonCode($taxExemptions[$key]['code'] ?? null);
$target->setVatExemptionReason($taxExemptions[$key]['reason'] ?? null);
}
diff --git a/tests/Integration/IntegrationTest.php b/tests/Integration/IntegrationTest.php
index 331b8e6..fc93c45 100644
--- a/tests/Integration/IntegrationTest.php
+++ b/tests/Integration/IntegrationTest.php
@@ -46,6 +46,10 @@ public function testCanRecreatePeppolVatExample(): void {
$this->importAndExportInvoice(__DIR__ . "/peppol-vat-s.xml");
}
+ public function testCanRecreatePeppolOptionalVatExample(): void {
+ $this->importAndExportInvoice(__DIR__ . "/peppol-vat-o.xml");
+ }
+
public function testCanRecreatePeppolAllowanceExample(): void {
$this->importAndExportInvoice(__DIR__ . "/peppol-allowance.xml");
}
diff --git a/tests/Integration/peppol-vat-o.xml b/tests/Integration/peppol-vat-o.xml
new file mode 100644
index 0000000..d04071e
--- /dev/null
+++ b/tests/Integration/peppol-vat-o.xml
@@ -0,0 +1,106 @@
+
+
+
+ urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0
+ urn:fdc:peppol.eu:2017:poacc:billing:01:1.0
+ Vat-O
+ 2018-08-30
+ 380
+ SEK
+ test reference
+
+
+ 7300010000001
+
+ 7300010000001
+
+
+ Main street 2, Building 4
+ Big city
+ 54321
+
+ SE
+
+
+
+ The Sellercompany Incorporated
+
+
+
+
+
+ 987654325
+
+ Anystreet 8
+ Back door
+ Anytown
+ 101
+ RegionB
+
+ NO
+
+
+
+ The Buyercompany
+
+
+
+
+ 30
+
+ SE1212341234123412
+
+ SEXDABCD
+
+
+
+
+ Payment within 30 days
+
+
+ 0
+
+ 3200
+ 0
+
+ O
+ Not subject to VAT
+
+ VAT
+
+
+
+
+
+ 3200
+ 3200
+ 3200
+ 3200
+
+
+ 1
+ 1
+ 3200
+
+ 1
+
+
+ Weight-based tax, vehicles >3000 KGM
+ Road tax
+
+ RT3000
+
+
+ O
+
+ VAT
+
+
+
+
+ 3200
+
+
+
From 8ce204e9c709db50b7780acba0aa6a2e119ec12f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?=
Date: Sat, 28 May 2022 12:55:29 +0200
Subject: [PATCH 03/11] Added support for VAT in accounting currency
- Added VAT currency and custom VAT amount properties to Invoice
- Updated UblReader and UblWriter
- Updated integration tests
> Closes #22
---
src/Invoice.php | 42 ++++++++
src/Models/InvoiceTotals.php | 18 +++-
src/Readers/UblReader.php | 18 ++++
src/Writers/UblWriter.php | 23 ++++-
tests/Integration/IntegrationTest.php | 4 +
.../Integration/cius-ro-tax-currency-code.xml | 96 +++++++++++++++++++
6 files changed, 196 insertions(+), 5 deletions(-)
create mode 100644 tests/Integration/cius-ro-tax-currency-code.xml
diff --git a/src/Invoice.php b/src/Invoice.php
index 082d657..1684c16 100644
--- a/src/Invoice.php
+++ b/src/Invoice.php
@@ -27,6 +27,7 @@ class Invoice {
protected $number = null;
protected $type = 380; // TODO: add constants
protected $currency = "EUR"; // TODO: add constants
+ protected $vatCurrency = null;
protected $issueDate = null;
protected $dueDate = null;
protected $taxPointDate = null;
@@ -38,6 +39,7 @@ class Invoice {
protected $contractReference = null;
protected $paidAmount = 0;
protected $roundingAmount = 0;
+ protected $customVatAmount = null;
protected $seller = null;
protected $buyer = null;
protected $payee = null;
@@ -194,6 +196,26 @@ public function setCurrency(string $currencyCode): self {
}
+ /**
+ * Get VAT accounting currency code
+ * @return string|null VAT accounting currency code or NULL if same as document's
+ */
+ public function getVatCurrency(): ?string {
+ return $this->vatCurrency;
+ }
+
+
+ /**
+ * Set VAT accounting currency code
+ * @param string|null $currencyCode VAT accounting currency code or NULL if same as document's
+ * @return self Invoice instance
+ */
+ public function setVatCurrency(?string $currencyCode): self {
+ $this->vatCurrency = $currencyCode;
+ return $this;
+ }
+
+
/**
* Get invoice issue date
* @return DateTime|null Invoice issue date
@@ -414,6 +436,26 @@ public function setRoundingAmount(float $roundingAmount): self {
}
+ /**
+ * Get total VAT amount in VAT accounting currency
+ * @return float|null Total amount in accounting currency
+ */
+ public function getCustomVatAmount(): ?float {
+ return $this->customVatAmount;
+ }
+
+
+ /**
+ * Set total VAT amount in VAT accounting currency
+ * @param float|null $customVatAmount Total amount in accounting currency
+ * @return self Invoice instance
+ */
+ public function setCustomVatAmount(?float $customVatAmount): self {
+ $this->customVatAmount = $customVatAmount;
+ return $this;
+ }
+
+
/**
* Get seller
* @return Party|null Seller instance
diff --git a/src/Models/InvoiceTotals.php b/src/Models/InvoiceTotals.php
index 52c0867..f58ea8e 100644
--- a/src/Models/InvoiceTotals.php
+++ b/src/Models/InvoiceTotals.php
@@ -8,11 +8,17 @@
class InvoiceTotals {
/**
- * Totals currency code
+ * Invoice currency code
* @var string
*/
public $currency;
+ /**
+ * VAT accounting currency code
+ * @var string|null
+ */
+ public $vatCurrency = null;
+
/**
* Sum of all invoice line net amounts
* @var float
@@ -61,6 +67,12 @@ class InvoiceTotals {
*/
public $roundingAmount = 0;
+ /**
+ * Total VAT amount in accounting currency
+ * @var float|null
+ */
+ public $customVatAmount = null;
+
/**
* Amount due for payment
* @var float
@@ -83,8 +95,9 @@ static public function fromInvoice(Invoice $inv, bool $round=true): InvoiceTotal
$totals = new self();
$vatMap = [];
- // Set currency code
+ // Set currency codes
$totals->currency = $inv->getCurrency();
+ $totals->vatCurrency = $inv->getVatCurrency();
// Process all invoice lines
foreach ($inv->getLines() as $line) {
@@ -116,6 +129,7 @@ static public function fromInvoice(Invoice $inv, bool $round=true): InvoiceTotal
$totals->taxInclusiveAmount = $totals->taxExclusiveAmount + $totals->vatAmount;
$totals->paidAmount = $inv->getPaidAmount();
$totals->roundingAmount = $inv->getRoundingAmount();
+ $totals->customVatAmount = $inv->getCustomVatAmount();
$totals->payableAmount = $totals->taxInclusiveAmount - $totals->paidAmount + $totals->roundingAmount;
// Attach VAT breakdown
diff --git a/src/Readers/UblReader.php b/src/Readers/UblReader.php
index 73ebb66..71b3978 100644
--- a/src/Readers/UblReader.php
+++ b/src/Readers/UblReader.php
@@ -120,6 +120,12 @@ public function import(string $document): Invoice {
$invoice->setCurrency($currencyNode->asText());
}
+ // BT-6: VAT accounting currency code
+ $vatCurrencyNode = $xml->get("{{$cbc}}TaxCurrencyCode");
+ if ($vatCurrencyNode !== null) {
+ $invoice->setVatCurrency($vatCurrencyNode->asText());
+ }
+
// BT-19: Buyer accounting reference
$buyerAccountingReferenceNode = $xml->get("{{$cbc}}AccountingCost");
if ($buyerAccountingReferenceNode !== null) {
@@ -211,6 +217,18 @@ public function import(string $document): Invoice {
$this->addAllowanceOrCharge($invoice, $node, $taxExemptions);
}
+ // BT-111: Total VAT amount in accounting currency
+ foreach ($xml->getAll("{{$cac}}TaxTotal") as $taxTotalNode) {
+ if ($taxTotalNode->get("{{$cac}}TaxSubtotal") !== null) {
+ // The other tax total node, then
+ continue;
+ }
+ $taxAmountNode = $taxTotalNode->get("{{$cbc}}TaxAmount");
+ if ($taxAmountNode !== null) {
+ $invoice->setCustomVatAmount((float) $taxAmountNode->asText());
+ }
+ }
+
// BT-113: Paid amount
$paidAmountNode = $xml->get("{{$cac}}LegalMonetaryTotal/{{$cbc}}PrepaidAmount");
if ($paidAmountNode !== null) {
diff --git a/src/Writers/UblWriter.php b/src/Writers/UblWriter.php
index 151d479..c5409a0 100644
--- a/src/Writers/UblWriter.php
+++ b/src/Writers/UblWriter.php
@@ -81,6 +81,12 @@ public function export(Invoice $invoice): string {
// BT-5: Invoice currency code
$xml->add('cbc:DocumentCurrencyCode', $invoice->getCurrency());
+ // BT-6: VAT accounting currency code
+ $vatCurrency = $invoice->getVatCurrency();
+ if ($vatCurrency !== null) {
+ $xml->add('cbc:TaxCurrencyCode', $vatCurrency);
+ }
+
// BT-19: Buyer accounting reference
$buyerAccountingReference = $invoice->getBuyerAccountingReference();
if ($buyerAccountingReference !== null) {
@@ -165,7 +171,7 @@ public function export(Invoice $invoice): string {
}
// Invoice totals
- $this->addTaxTotalNode($xml, $invoice, $totals);
+ $this->addTaxTotalNodes($xml, $invoice, $totals);
$this->addDocumentTotalsNode($xml, $invoice, $totals);
// Invoice lines
@@ -708,12 +714,12 @@ private function addAllowanceOrCharge(
/**
- * Add tax total node
+ * Add tax total nodes
* @param UXML $parent Parent element
* @param Invoice $invoice Invoice instance
* @param InvoiceTotals $totals Unrounded invoice totals
*/
- private function addTaxTotalNode(UXML $parent, Invoice $invoice, InvoiceTotals $totals) {
+ private function addTaxTotalNodes(UXML $parent, Invoice $invoice, InvoiceTotals $totals) {
$xml = $parent->add('cac:TaxTotal');
// Add tax amount
@@ -748,6 +754,17 @@ private function addTaxTotalNode(UXML $parent, Invoice $invoice, InvoiceTotals $
$item->exemptionReason
);
}
+
+ // Add tax amount in VAT accounting currency (if any)
+ $customVatAmount = $totals->customVatAmount;
+ if ($customVatAmount !== null) {
+ $this->addAmountNode(
+ $parent->add('cac:TaxTotal'),
+ 'cbc:TaxAmount',
+ round($customVatAmount, $invoice->getDecimals('invoice/taxAmount')),
+ $totals->vatCurrency ?? $totals->currency
+ );
+ }
}
diff --git a/tests/Integration/IntegrationTest.php b/tests/Integration/IntegrationTest.php
index fc93c45..9a67fed 100644
--- a/tests/Integration/IntegrationTest.php
+++ b/tests/Integration/IntegrationTest.php
@@ -53,4 +53,8 @@ public function testCanRecreatePeppolOptionalVatExample(): void {
public function testCanRecreatePeppolAllowanceExample(): void {
$this->importAndExportInvoice(__DIR__ . "/peppol-allowance.xml");
}
+
+ public function testCanRecreateCiusRoTaxCurrencyCodeExample(): void {
+ $this->importAndExportInvoice(__DIR__ . "/cius-ro-tax-currency-code.xml");
+ }
}
diff --git a/tests/Integration/cius-ro-tax-currency-code.xml b/tests/Integration/cius-ro-tax-currency-code.xml
new file mode 100644
index 0000000..8d42ff2
--- /dev/null
+++ b/tests/Integration/cius-ro-tax-currency-code.xml
@@ -0,0 +1,96 @@
+
+
+
+ urn:cen.eu:en16931:2017#compliant#urn:efactura.mfinante.ro:CIUS-RO:1.0.0
+ 10009
+ 2022-05-26
+ 2022-05-26
+ 380
+ EUR
+ RON
+
+
+
+ Name of the Street
+ Name of the City
+ RO-BH
+
+ RO
+
+
+
+ RO124533553
+
+ VAT
+
+
+
+ Legal Name
+
+
+
+
+
+
+ Another Street Name
+ Another City
+ RO-BH
+
+ RO
+
+
+
+ 23453553
+
+ VA
+
+
+
+ Another Legal Name
+
+
+
+
+ 19
+
+ 100
+ 19
+
+ S
+ 19
+
+ VAT
+
+
+
+
+
+ 93.89
+
+
+ 100
+ 100
+ 119
+ 119
+
+
+ 1
+ 1
+ 100
+
+ Item Name
+
+ S
+ 19
+
+ VAT
+
+
+
+
+ 100
+
+
+
From e0b9c1164a4bc22dbbee84f0ed0c682263b2a907 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?=
Date: Sat, 28 May 2022 13:20:38 +0200
Subject: [PATCH 04/11] Added support for additional legal information (BT-33)
- Added legal information field to Party
- Updated UblReader and UblWriter
- Updated integration tests
> Closes #23
---
src/Party.php | 21 +++++++++++++++++++++
src/Readers/UblReader.php | 8 +++++++-
src/Writers/UblWriter.php | 6 ++++++
tests/Integration/peppol-vat-s.xml | 3 ++-
4 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/src/Party.php b/src/Party.php
index a945bcf..2213453 100644
--- a/src/Party.php
+++ b/src/Party.php
@@ -11,6 +11,7 @@ class Party {
protected $companyId = null;
protected $vatNumber = null;
protected $taxRegistrationId = null;
+ protected $legalInformation = null;
protected $contactName = null;
protected $contactPhone = null;
protected $contactEmail = null;
@@ -138,6 +139,26 @@ public function setTaxRegistrationId(?Identifier $taxRegistrationId): self {
}
+ /**
+ * Get additional legal information
+ * @return string|null Additional legal information
+ */
+ public function getLegalInformation(): ?string {
+ return $this->legalInformation;
+ }
+
+
+ /**
+ * Set additional legal information
+ * @param string|null $legalInformation Additional legal information
+ * @return self Party instance
+ */
+ public function setLegalInformation(?string $legalInformation): self {
+ $this->legalInformation = $legalInformation;
+ return $this;
+ }
+
+
/**
* Get contact point name
* @return string|null Contact name
diff --git a/src/Readers/UblReader.php b/src/Readers/UblReader.php
index 71b3978..4d90d1c 100644
--- a/src/Readers/UblReader.php
+++ b/src/Readers/UblReader.php
@@ -387,13 +387,19 @@ private function parseSellerOrBuyerNode(UXML $xml): Party {
if ($legalNameNode !== null) {
$party->setName($legalNameNode->asText());
}
-
+
// Company ID
$companyIdNode = $xml->get("{{$cac}}PartyLegalEntity/{{$cbc}}CompanyID");
if ($companyIdNode !== null) {
$party->setCompanyId($this->parseIdentifierNode($companyIdNode));
}
+ // BT-33: Seller additional legal information
+ $companyLegalFormNode = $xml->get("{{$cac}}PartyLegalEntity/{{$cbc}}CompanyLegalForm");
+ if ($companyLegalFormNode !== null) {
+ $party->setLegalInformation($companyLegalFormNode->asText());
+ }
+
// Contact name
$contactNameNode = $xml->get("{{$cac}}Contact/{{$cbc}}Name");
if ($contactNameNode !== null) {
diff --git a/src/Writers/UblWriter.php b/src/Writers/UblWriter.php
index c5409a0..19d8cc5 100644
--- a/src/Writers/UblWriter.php
+++ b/src/Writers/UblWriter.php
@@ -421,6 +421,12 @@ private function addSellerOrBuyerNode(UXML $parent, Party $party) {
$this->addIdentifierNode($legalEntityNode, 'cbc:CompanyID', $companyId);
}
+ // BT-33: Seller additional legal information
+ $legalInformation = $party->getLegalInformation();
+ if ($legalInformation !== null) {
+ $legalEntityNode->add('cbc:CompanyLegalForm', $legalInformation);
+ }
+
// Contact point
if ($party->hasContactInformation()) {
$contactNode = $xml->add('cac:Contact');
diff --git a/tests/Integration/peppol-vat-s.xml b/tests/Integration/peppol-vat-s.xml
index 150a159..185068d 100644
--- a/tests/Integration/peppol-vat-s.xml
+++ b/tests/Integration/peppol-vat-s.xml
@@ -38,7 +38,8 @@
SupplierOfficialName Ltd
- GB983294
+ GB983294
+ AdditionalLegalInformation
John Doe
From e671343fc5e21958f35503e5bb1a34bed3b15de2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?=
Date: Sat, 28 May 2022 13:38:02 +0200
Subject: [PATCH 05/11] Added CIUS-RO preset
- Created Presets/CiusRo class
---
src/Presets/CiusRo.php | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
create mode 100644 src/Presets/CiusRo.php
diff --git a/src/Presets/CiusRo.php b/src/Presets/CiusRo.php
new file mode 100644
index 0000000..dec5b55
--- /dev/null
+++ b/src/Presets/CiusRo.php
@@ -0,0 +1,16 @@
+
Date: Sun, 29 May 2022 10:54:05 +0200
Subject: [PATCH 06/11] Fixed rounding of negative zeroes
- Added custom Invoice::round() method
- Updated InvoiceTotals
- Updated UblWriter
- Updated unit tests
---
src/Invoice.php | 16 ++++++++++++
src/Models/InvoiceTotals.php | 23 ++++++++---------
src/Writers/UblWriter.php | 49 ++++++++++++++++++------------------
tests/InvoiceTest.php | 9 +++++++
4 files changed, 60 insertions(+), 37 deletions(-)
diff --git a/src/Invoice.php b/src/Invoice.php
index 1684c16..3fba3e5 100644
--- a/src/Invoice.php
+++ b/src/Invoice.php
@@ -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;
@@ -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
diff --git a/src/Models/InvoiceTotals.php b/src/Models/InvoiceTotals.php
index f58ea8e..68d4111 100644
--- a/src/Models/InvoiceTotals.php
+++ b/src/Models/InvoiceTotals.php
@@ -4,7 +4,6 @@
use Einvoicing\Invoice;
use Einvoicing\Traits\VatTrait;
use function array_values;
-use function round;
class InvoiceTotals {
/**
@@ -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');
}
}
diff --git a/src/Writers/UblWriter.php b/src/Writers/UblWriter.php
index 19d8cc5..0cbca06 100644
--- a/src/Writers/UblWriter.php
+++ b/src/Writers/UblWriter.php
@@ -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";
@@ -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()
);
@@ -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()
);
}
@@ -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
);
@@ -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(
@@ -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
);
}
@@ -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
@@ -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()
);
}
@@ -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()
);
}
diff --git a/tests/InvoiceTest.php b/tests/InvoiceTest.php
index f2264b5..491d369 100644
--- a/tests/InvoiceTest.php
+++ b/tests/InvoiceTest.php
@@ -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,
From 97a133ac2585980ee104237ff9058403b63e00d8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?=
Date: Sun, 29 May 2022 11:04:54 +0200
Subject: [PATCH 07/11] Improved diffing in integration tests
- Updated IntegrationTest::normalize()
---
tests/Integration/IntegrationTest.php | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/tests/Integration/IntegrationTest.php b/tests/Integration/IntegrationTest.php
index 9a67fed..38cd09c 100644
--- a/tests/Integration/IntegrationTest.php
+++ b/tests/Integration/IntegrationTest.php
@@ -21,10 +21,18 @@ protected function setUp(): void {
}
protected function normalize(string $xml): string {
+ // Normalize input document
$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;
$doc->loadXML($xml, LIBXML_NOERROR);
- return $doc->C14N();
+ $normalizedXml = $doc->C14N();
+ unset($doc);
+
+ // Export formatted XML for better diffing
+ $doc = new DOMDocument();
+ $doc->formatOutput = true;
+ $doc->loadXML($normalizedXml, LIBXML_NOERROR);
+ return $doc->saveXML();
}
protected function importAndExportInvoice(string $xmlPath): void {
From 673a8b94bee6b8fcd64476f2a1b9fceb8a6772d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?=
Date: Sun, 29 May 2022 11:06:32 +0200
Subject: [PATCH 08/11] Upgraded dependencies
- Updated composer.json
---
composer.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/composer.json b/composer.json
index 6259839..71f28f1 100644
--- a/composer.json
+++ b/composer.json
@@ -29,7 +29,7 @@
},
"require": {
"php": ">=7.1",
- "josemmo/uxml": "^0.1.3"
+ "josemmo/uxml": "^0.1.4"
},
"require-dev": {
"ext-openssl": "*",
From 6bdb39f75b458c75fa5164385d177104cc3c87da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?=
Date: Wed, 1 Jun 2022 19:40:36 +0200
Subject: [PATCH 09/11] Added support for multiple invoice notes
- Updated Invoice class
- Updated UblReader and UblWriter
- Updated Peppol preset rules
- Updated CIUS-RO preset rules
- Updated unit tests
> Closes #24
---
src/Invoice.php | 56 ++++++++++++++++++++++++++++++++++++---
src/Presets/CiusRo.php | 20 ++++++++++++++
src/Presets/Peppol.php | 5 ++++
src/Readers/UblReader.php | 7 +++--
src/Writers/UblWriter.php | 5 ++--
tests/InvoiceTest.php | 22 +++++++++++++++
6 files changed, 105 insertions(+), 10 deletions(-)
diff --git a/src/Invoice.php b/src/Invoice.php
index 3fba3e5..8526378 100644
--- a/src/Invoice.php
+++ b/src/Invoice.php
@@ -32,7 +32,7 @@ class Invoice {
protected $issueDate = null;
protected $dueDate = null;
protected $taxPointDate = null;
- protected $note = null;
+ protected $notes = [];
protected $buyerReference = null;
protected $purchaseOrderReference = null;
protected $salesOrderReference = null;
@@ -292,12 +292,59 @@ public function setTaxPointDate(?DateTime $taxPointDate): self {
}
+ /**
+ * Get invoice notes
+ * @return string[] Invoice notes
+ */
+ public function getNotes(): array {
+ return $this->notes;
+ }
+
+
+ /**
+ * Add invoice note
+ * @param string $note Invoice note
+ * @return self Invoice instance
+ */
+ public function addNote(string $note): self {
+ $this->notes[] = $note;
+ return $this;
+ }
+
+
+ /**
+ * Remove invoice note
+ * @param int $index Invoice note index
+ * @return self Invoice instance
+ * @throws OutOfBoundsException if invoice note index is out of bounds
+ */
+ public function removeNote(int $index): self {
+ if ($index < 0 || $index >= count($this->notes)) {
+ throw new OutOfBoundsException('Could not find invoice note by index');
+ }
+ array_splice($this->notes, $index, 1);
+ return $this;
+ }
+
+
+ /**
+ * Clear all invoice notes
+ * @return self Invoice instance
+ */
+ public function clearNotes(): self {
+ $this->notes = [];
+ return $this;
+ }
+
+
/**
* Get invoice note
* @return string|null Invoice note
+ * @deprecated 0.2.1
+ * @see Invoice::getNotes()
*/
public function getNote(): ?string {
- return $this->note;
+ return $this->notes[0] ?? null;
}
@@ -305,9 +352,12 @@ public function getNote(): ?string {
* Set invoice note
* @param string|null $note Invoice note
* @return self Invoice instance
+ * @deprecated 0.2.1
+ * @see Invoice::addNote()
*/
public function setNote(?string $note): self {
- $this->note = $note;
+ // @phan-suppress-next-line PhanPartialTypeMismatchProperty
+ $this->notes = ($note === null) ? [] : [$note];
return $this;
}
diff --git a/src/Presets/CiusRo.php b/src/Presets/CiusRo.php
index dec5b55..36edf04 100644
--- a/src/Presets/CiusRo.php
+++ b/src/Presets/CiusRo.php
@@ -1,6 +1,10 @@
getNotes()) > 20) {
+ return "The allowed maximum number of occurrences of Invoice note (BG-1) is 20.";
+ }
+ };
+
+ return $res;
+ }
}
diff --git a/src/Presets/Peppol.php b/src/Presets/Peppol.php
index a61eb7a..8496ccc 100644
--- a/src/Presets/Peppol.php
+++ b/src/Presets/Peppol.php
@@ -25,6 +25,11 @@ public function getSpecification(): string {
public function getRules(): array {
$res = [];
+ $res['PEPPOL-EN16931-R002'] = static function(Invoice $inv) {
+ if (count($inv->getNotes()) > 1) {
+ return "No more than one note is allowed on document level.";
+ }
+ };
$res['PEPPOL-EN16931-R003'] = static function(Invoice $inv) {
if ($inv->getBuyerReference() !== null) return;
if ($inv->getPurchaseOrderReference() !== null) return;
diff --git a/src/Readers/UblReader.php b/src/Readers/UblReader.php
index 4d90d1c..74733a8 100644
--- a/src/Readers/UblReader.php
+++ b/src/Readers/UblReader.php
@@ -102,10 +102,9 @@ public function import(string $document): Invoice {
$invoice->setType((int) $typeNode->asText());
}
- // BT-22: Note
- $noteNode = $xml->get("{{$cbc}}Note");
- if ($noteNode !== null) {
- $invoice->setNote($noteNode->asText());
+ // BT-22: Notes
+ foreach ($xml->getAll("{{$cbc}}Note") as $noteNode) {
+ $invoice->addNote($noteNode->asText());
}
// BT-7: Tax point date
diff --git a/src/Writers/UblWriter.php b/src/Writers/UblWriter.php
index 0cbca06..225873d 100644
--- a/src/Writers/UblWriter.php
+++ b/src/Writers/UblWriter.php
@@ -65,9 +65,8 @@ public function export(Invoice $invoice): string {
// BT-3: Invoice type code
$xml->add('cbc:InvoiceTypeCode', (string) $invoice->getType());
- // BT-22: Note
- $note = $invoice->getNote();
- if ($note !== null) {
+ // BT-22: Notes
+ foreach ($invoice->getNotes() as $note) {
$xml->add('cbc:Note', $note);
}
diff --git a/tests/InvoiceTest.php b/tests/InvoiceTest.php
index 491d369..3c597b5 100644
--- a/tests/InvoiceTest.php
+++ b/tests/InvoiceTest.php
@@ -31,6 +31,28 @@ public function testCannotCreateInvoiceFromInvalidPreset(): void {
new Invoice(self::class);
}
+ public function testCanReadAndWriteNotes(): void {
+ $note = "This is a test";
+ $this->assertSame($note, $this->invoice->addNote($note)->getNotes()[0]);
+ $this->invoice->removeNote(0);
+ $this->assertEmpty($this->invoice->getNotes());
+ }
+
+ public function testCanRemoveNotes(): void {
+ $this->invoice
+ ->addNote('Note #1')
+ ->addNote('Note #2')
+ ->addNote('Note #3')
+ ->removeNote(2)
+ ->removeNote(0);
+ $this->assertSame('Note #2', $this->invoice->getNotes()[0]);
+ }
+
+ public function testCannotRemoveOutOfBoundsNotes(): void {
+ $this->expectException(OutOfBoundsException::class);
+ $this->invoice->addNote('A sample note')->removeNote(1);
+ }
+
public function testCanReadAndWriteLines(): void {
$this->assertSame($this->line, $this->invoice->addLine($this->line)->getLines()[0]);
$this->invoice->removeLine(0);
From 75bdfc4f43f192dd86efacf11413cd1cfa9716d5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?=
Date: Wed, 1 Jun 2022 20:32:00 +0200
Subject: [PATCH 10/11] Added deprecated warnings to documentation
- Updated build-docs.php script
> Related to #24
---
scripts/build-docs.php | 27 +++++++++++++++++++++------
1 file changed, 21 insertions(+), 6 deletions(-)
diff --git a/scripts/build-docs.php b/scripts/build-docs.php
index fd4f9a6..6490797 100644
--- a/scripts/build-docs.php
+++ b/scripts/build-docs.php
@@ -1,7 +1,9 @@
Array of documentable classes
*/
function getProjectFiles(): array {
$files = [];
@@ -55,7 +57,7 @@ function getProjectFiles(): array {
* Get class public elements
* @param Class_ $class Class instance
* @param string $type Element type ("constants", "properties" or "methods")
- * @param Element[string] &$project Project files
+ * @param array &$project Project files
* @return Constant[]|Property[]|Method[] Public elements
*/
function getPublicElements(Class_ $class, string $type, array &$project): array {
@@ -102,9 +104,9 @@ function getClassUrl(string $fqsen): string {
/**
* Render class
- * @param Class_ $class Class instance
- * @param Element[string] &$project Project files
- * @return string Markdown documentation
+ * @param Class_ $class Class instance
+ * @param array &$project Project files
+ * @return string Markdown documentation
*/
function renderClass(Class_ $class, array &$project): string {
$doc = "# {$class->getFqsen()}\n\n";
@@ -180,9 +182,22 @@ function renderMethod(Method $method, array $addDocblocks, Class_ $class): strin
$return = $docblock->getTagsByName('return')[0] ?? null;
// Method summary
- $doc = "## `{$method->getName()}()`\n";
+ $doc = "## `{$method->getName()}()`\n";
$doc .= $docblock->getSummary() . "\n";
+ // Deprecation warning
+ /** @var Deprecated|null */
+ $deprecated = $docblock->getTagsByName('deprecated')[0] ?? null;
+ if ($deprecated !== null) {
+ /** @var See */
+ $see = $docblock->getTagsByName('see')[0];
+ $seeRef = $see->getReference();
+ $doc .= "\n";
+ $doc .= "!!! warning \"Deprecated since v{$deprecated->getVersion()}\"\n";
+ $doc .= "\n";
+ $doc .= " Use [`$seeRef`](#" . strtolower(substr($seeRef, strpos($seeRef, '::')+2, -2)) . ") instead.\n";
+ }
+
// Signature
$doc .= "\n```php\n";
$doc .= "public " . ($method->isStatic() ? "static " : "") . $method->getName() . "(";
From 9ddfb643700e6dbccd25acdc801d225b65d7fc1e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?=
Date: Wed, 8 Jun 2022 18:06:09 +0200
Subject: [PATCH 11/11] Added NLCIUS preset
- Created Nlcius preset class
---
src/Presets/Nlcius.php | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
create mode 100644 src/Presets/Nlcius.php
diff --git a/src/Presets/Nlcius.php b/src/Presets/Nlcius.php
new file mode 100644
index 0000000..e6b904a
--- /dev/null
+++ b/src/Presets/Nlcius.php
@@ -0,0 +1,16 @@
+