From cca1eb258c95ae1e439265dfc7315b7655e19628 Mon Sep 17 00:00:00 2001 From: Christian Vermeulen Date: Wed, 11 Jan 2023 12:28:14 +0100 Subject: [PATCH 1/5] Support credit note type code --- src/Invoice.php | 4 +- src/Readers/UblReader.php | 5 + tests/Readers/UblReaderTest.php | 31 ++- tests/Readers/peppol-credit-note-example.xml | 223 ++++++++++++++++++ ...example.xml => peppol-invoice-example.xml} | 0 5 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 tests/Readers/peppol-credit-note-example.xml rename tests/Readers/{peppol-example.xml => peppol-invoice-example.xml} (100%) diff --git a/src/Invoice.php b/src/Invoice.php index 22af0d4..bafc795 100644 --- a/src/Invoice.php +++ b/src/Invoice.php @@ -20,13 +20,15 @@ class Invoice { const DEFAULT_DECIMALS = 8; + const TYPE_INVOICE = 380; + const TYPE_CREDIT_NOTE = 381; protected $preset = null; protected $roundingMatrix = null; protected $specification = null; protected $businessProcess = null; protected $number = null; - protected $type = 380; // TODO: add constants + protected $type = self::TYPE_INVOICE; protected $currency = "EUR"; // TODO: add constants protected $vatCurrency = null; protected $issueDate = null; diff --git a/src/Readers/UblReader.php b/src/Readers/UblReader.php index 74733a8..9a4adf7 100644 --- a/src/Readers/UblReader.php +++ b/src/Readers/UblReader.php @@ -102,6 +102,11 @@ public function import(string $document): Invoice { $invoice->setType((int) $typeNode->asText()); } + $typeNode = $xml->get("{{$cbc}}CreditNoteTypeCode"); + if ($typeNode !== null) { + $invoice->setType((int) $typeNode->asText()); + } + // BT-22: Notes foreach ($xml->getAll("{{$cbc}}Note") as $noteNode) { $invoice->addNote($noteNode->asText()); diff --git a/tests/Readers/UblReaderTest.php b/tests/Readers/UblReaderTest.php index fabc350..084f141 100644 --- a/tests/Readers/UblReaderTest.php +++ b/tests/Readers/UblReaderTest.php @@ -1,13 +1,15 @@ reader = new UblReader(); } + public function testItCanReadCreditNote(): void { + $creditNote = $this->reader->import(file_get_contents(self::CREDIT_NOTE_PATH)); + $creditNote->validate(); + + $this->assertSame(Invoice::TYPE_CREDIT_NOTE, $creditNote->getType()); + + $lines = $creditNote->getLines(); + $this->assertEquals('1', $lines[0]->getId()); + $this->assertEquals('2', $lines[1]->getId()); + + $totals = $creditNote->getTotals(); + $this->assertEquals(1300, $totals->netAmount); + $this->assertEquals(1325, $totals->taxExclusiveAmount); + $this->assertEquals(331.25, $totals->vatAmount); + $this->assertEquals(0, $totals->allowancesAmount); + $this->assertEquals(25, $totals->chargesAmount); + $this->assertEquals(1656.25, $totals->payableAmount); + $this->assertEquals('S', $totals->vatBreakdown[0]->category); + $this->assertEquals(25, $totals->vatBreakdown[0]->rate); + $this->assertEquals('INV-123', $creditNote->getPrecedingInvoiceReferences()[0]->getValue()); + $this->assertEquals('This is a sample string', $creditNote->getAttachments()[0]->getContents()); + } + public function testCanReadInvoice(): void { - $invoice = $this->reader->import(file_get_contents(self::DOCUMENT_PATH)); + $invoice = $this->reader->import(file_get_contents(self::INVOICE_PATH)); $invoice->validate(); + $this->assertSame(Invoice::TYPE_INVOICE, $invoice->getType()); + $lines = $invoice->getLines(); $this->assertEquals('1', $lines[0]->getId()); $this->assertEquals('2', $lines[1]->getId()); diff --git a/tests/Readers/peppol-credit-note-example.xml b/tests/Readers/peppol-credit-note-example.xml new file mode 100644 index 0000000..9740846 --- /dev/null +++ b/tests/Readers/peppol-credit-note-example.xml @@ -0,0 +1,223 @@ + + + + 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 + Snippet1 + 2017-11-13 + 2017-12-01 + 381 + EUR + 4025:123:4343 + 0150abc + + + INV-123 + + + + ABC-123 + Invoice ABC-123 + + VGhpcyBpcyBhIHNhbXBsZSBzdHJpbmc= + + + + + 9482348239847239874 + + 99887766 + + + SupplierTradingName Ltd. + + + Main street 1 + Postbox 123 + London + GB 123 EW + + GB + + + + GB1232434 + + VAT + + + + SupplierOfficialName Ltd + GB983294 + + + + + + FR23342 + + FR23342 + + + BuyerTradingName AS + + + Hovedgatan 32 + Po box 878 + Stockholm + 456 34 + + SE + + + + SE4598375937 + + VAT + + + + Buyer Official Name + 39937423947 + + + Lisa Johnson + 23434234 + lj@buyer.se + + + + + 2017-11-01 + + 9483759475923478 + + Delivery street 2 + Building 56 + Stockholm + 21234 + + SE + + + + + + Delivery party Name + + + + + 30 + Snippet1 + + IBAN32423940 + AccountName + + BIC324098 + + + + + Payment within 10 days, 2% discount + + + true + Insurance + 25 + + S + 25.0 + + VAT + + + + + 331.25 + + 1325 + 331.25 + + S + 25.0 + + VAT + + + + + + 1300 + 1325 + 1656.25 + 25 + 1656.25 + + + + 1 + 7 + 2800 + Konteringsstreng + + 123 + + + Description of item + item name + + 21382183120983 + + + NO + + + 09348023 + + + S + 25.0 + + VAT + + + + + 400 + + + + 2 + -3 + -1500 + + 123 + + + Description 2 + item name 2 + + 21382183120983 + + + NO + + + 09348023 + + + S + 25.0 + + VAT + + + + + 500 + + + diff --git a/tests/Readers/peppol-example.xml b/tests/Readers/peppol-invoice-example.xml similarity index 100% rename from tests/Readers/peppol-example.xml rename to tests/Readers/peppol-invoice-example.xml From 40682f2ee6c308a1f0f988eb808d83995baf4569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?= Date: Sat, 14 Jan 2023 11:09:20 +0100 Subject: [PATCH 2/5] Added invoice type constants - Added constants from UNCL1001 subset to Invoice class --- src/Invoice.php | 219 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 217 insertions(+), 2 deletions(-) diff --git a/src/Invoice.php b/src/Invoice.php index bafc795..97fc946 100644 --- a/src/Invoice.php +++ b/src/Invoice.php @@ -18,17 +18,232 @@ use function is_subclass_of; use function round; +/** @phan-suppress PhanUnreferencedPublicClassConstant */ class Invoice { const DEFAULT_DECIMALS = 8; - const TYPE_INVOICE = 380; + + /** + * Request for payment + * + * Document/message issued by a creditor to a debtor to request payment of one or more invoices past due. + */ + const TYPE_REQUEST_FOR_PAYMENT = 71; + + /** + * Debit note related to goods or services + * + * Debit information related to a transaction for goods or services to the relevant party. + */ + const TYPE_DEBIT_NOTE_RELATED_TO_GOODS_OR_SERVICES = 80; + + /** + * Metered services invoice + * + * Document/message claiming payment for the supply of metered services (e.g., gas, electricity, etc.) supplied to + * a fixed meter whose consumption is measured over a period of time. + */ + const TYPE_METERED_SERVICES_INVOICE = 82; + + /** + * Debit note related to financial adjustments + * + * Document/message for providing debit information related to financial adjustments to the relevant party. + */ + const TYPE_DEBIT_NOTE_RELATED_TO_FINANCIAL_ADJUSTMENTS = 84; + + /** + * Tax notification + * + * Used to specify that the message is a tax notification. + */ + const TYPE_TAX_NOTIFICATION = 102; + + /** + * Final payment request based on completion of work + * + * The final payment request of a series of payment requests submitted upon completion of all the work. + */ + const TYPE_FINAL_PAYMENT_REQUEST_BASED_ON_COMPLETION_OF_WORK = 218; + + /** + * Payment request for completed units + * + * A request for payment for completed units. + */ + const TYPE_PAYMENT_REQUEST_FOR_COMPLETED_UNITS = 219; + + /** + * Commercial invoice which includes a packing list + * + * Commercial transaction (invoice) will include a packing list. + */ + const TYPE_COMMERCIAL_INVOICE_WHICH_INCLUDES_A_PACKING_LIST = 331; + + /** + * Commercial invoice + * + * Document/message claiming payment for goods or services supplied under conditions agreed between seller and + * buyer. + */ + const TYPE_COMMERCIAL_INVOICE = 380; + + /** + * Commission note + * + * Document/message in which a seller specifies the amount of commission, the percentage of the invoice amount, or + * some other basis for the calculation of the commission to which a sales agent is entitled. + */ + const TYPE_COMMISSION_NOTE = 382; + + /** + * Debit note + * + * Document/message for providing debit information to the relevant party. + */ + const TYPE_DEBIT_NOTE = 383; + + /** + * Prepayment invoice + * + * An invoice to pay amounts for goods and services in advance; these amounts will be subtracted from the final + * invoice. + */ + const TYPE_PREPAYMENT_INVOICE = 386; + + /** + * Tax invoice + * + * An invoice for tax purposes. + */ + const TYPE_TAX_INVOICE = 388; + + /** + * Factored invoice + * + * Invoice assigned to a third party for collection. + */ + const TYPE_FACTORED_INVOICE = 393; + + /** + * Consignment invoice + * + * Commercial invoice that covers a transaction other than one involving a sale. + */ + const TYPE_CONSIGNMENT_INVOICE = 395; + + /** + * Forwarder's invoice discrepancy report + * + * Document/message reporting invoice discrepancies indentified by the forwarder. + */ + const TYPE_FORWARDERS_INVOICE_DISCREPANCY_REPORT = 553; + + /** + * Insurer's invoice + * + * Document/message issued by an insurer specifying the cost of an insurance which has been effected and claiming + * payment therefore. + */ + const TYPE_INSURERS_INVOICE = 575; + + /** + * Forwarder's invoice + * + * Invoice issued by a freight forwarder specifying services rendered and costs incurred and claiming payment + * therefore. + */ + const TYPE_FORWARDERS_INVOICE = 623; + + /** + * Freight invoice + * + * Document/message issued by a transport operation specifying freight costs and charges incurred for a transport + * operation and stating conditions of payment. + */ + const TYPE_FREIGHT_INVOICE = 780; + + /** + * Claim notification + * + * Document notifying a claim. + */ + const TYPE_CLAIM_NOTIFICATION = 817; + + /** + * Consular invoice + * + * Document/message to be prepared by an exporter in his country and presented to a diplomatic representation of the + * importing country for endorsement and subsequently to be presented by the importer in connection with the import + * of the goods described therein. + */ + const TYPE_CONSULAR_INVOICE = 870; + + /** + * Partial construction invoice + * + * Partial invoice in the context of a specific construction project. + */ + const TYPE_PARTIAL_CONSTRUCTION_INVOICE = 875; + + /** + * Partial final construction invoice + * + * Invoice concluding all previous partial construction invoices of a completed partial rendered service in the + * context of a specific construction project. + */ + const TYPE_PARTIAL_FINAL_CONSTRUCTION_INVOICE = 876; + + /** + * Final construction invoice + * + * Invoice concluding all previous partial invoices and partial final construction invoices in the context of a + * specific construction project. + */ + const TYPE_FINAL_CONSTRUCTION_INVOICE = 877; + + /** + * Credit note related to goods or services + * + * Document message used to provide credit information related to a transaction for goods or services to the + * relevant party. + */ + const TYPE_CREDIT_NOTE_RELATED_TO_GOODS_OR_SERVICES = 81; + + /** + * Credit note related to financial adjustments + * + * Document message for providing credit information related to financial adjustments to the relevant party, + * e.g., bonuses. + */ + const TYPE_CREDIT_NOTE_RELATED_TO_FINANCIAL_ADJUSTMENTS = 83; + + /** + * Credit note + * + * Document/message for providing credit information to the relevant party. + */ const TYPE_CREDIT_NOTE = 381; + /** + * Factored credit note + * + * Credit note related to assigned invoice(s). + */ + const TYPE_FACTORED_CREDIT_NOTE = 396; + + /** + * Forwarder's credit note + * + * Document/message for providing credit information to the relevant party. + */ + const TYPE_FORWARDERS_CREDIT_NOTE = 532; + protected $preset = null; protected $roundingMatrix = null; protected $specification = null; protected $businessProcess = null; protected $number = null; - protected $type = self::TYPE_INVOICE; + protected $type = self::TYPE_COMMERCIAL_INVOICE; protected $currency = "EUR"; // TODO: add constants protected $vatCurrency = null; protected $issueDate = null; From 6d04d510b414a4ca9dd1a471d523d45cbab3cfa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?= Date: Sat, 14 Jan 2023 11:19:29 +0100 Subject: [PATCH 3/5] Added support for credit notes to UBL writer - Updated UblWriter class - Updated unit tests --- src/Writers/UblWriter.php | 101 +++++++++++++++++++++++++------- tests/Writers/UblWriterTest.php | 11 ++++ 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/src/Writers/UblWriter.php b/src/Writers/UblWriter.php index 18af343..ea9b302 100644 --- a/src/Writers/UblWriter.php +++ b/src/Writers/UblWriter.php @@ -1,6 +1,7 @@ getTotals(); - $xml = UXML::newInstance('Invoice', null, [ - 'xmlns' => self::NS_INVOICE, + $isCreditNoteProfile = $this->isCreditNoteProfile($invoice); + + // Create root element + $rootElementName = $isCreditNoteProfile ? 'CreditNote' : 'Invoice'; + $xml = UXML::newInstance($rootElementName, null, [ + 'xmlns' => $isCreditNoteProfile ? self::NS_CREDIT_NOTE : self::NS_INVOICE, 'xmlns:cac' => self::NS_CAC, 'xmlns:cbc' => self::NS_CBC ]); @@ -56,14 +62,15 @@ public function export(Invoice $invoice): string { $xml->add('cbc:IssueDate', $issueDate->format('Y-m-d')); } - // BT-9: Due date + // BT-9: Due date (for invoice profile) $dueDate = $invoice->getDueDate(); - if ($dueDate !== null) { + if (!$isCreditNoteProfile && $dueDate !== null) { $xml->add('cbc:DueDate', $dueDate->format('Y-m-d')); } // BT-3: Invoice type code - $xml->add('cbc:InvoiceTypeCode', (string) $invoice->getType()); + $typeCodeName = $isCreditNoteProfile ? "cbc:CreditNoteTypeCode" : "cbc:InvoiceTypeCode"; + $xml->add($typeCodeName, (string) $invoice->getType()); // BT-22: Notes foreach ($invoice->getNotes() as $note) { @@ -113,10 +120,9 @@ public function export(Invoice $invoice): string { } } - // BT-17: Tender or lot reference - $tenderOrLotReference = $invoice->getTenderOrLotReference(); - if ($tenderOrLotReference !== null) { - $xml->add('cac:OriginatorDocumentReference')->add('cbc:ID', $tenderOrLotReference); + // BT-17: Tender or lot reference (for invoice profile) + if (!$isCreditNoteProfile) { + $this->addTenderOrLotReferenceNode($xml, $invoice); } // BT-12: Contract reference @@ -130,6 +136,11 @@ public function export(Invoice $invoice): string { $this->addAttachmentNode($xml, $attachment); } + // BT-17: Tender or lot reference (for credit note profile) + if ($isCreditNoteProfile) { + $this->addTenderOrLotReferenceNode($xml, $invoice); + } + // Seller node $seller = $invoice->getSeller(); if ($seller !== null) { @@ -157,7 +168,7 @@ public function export(Invoice $invoice): string { // Payment nodes $payment = $invoice->getPayment(); if ($payment !== null) { - $this->addPaymentNodes($xml, $payment); + $this->addPaymentNodes($xml, $payment, $isCreditNoteProfile ? $dueDate : null); } // Allowances and charges @@ -183,13 +194,30 @@ public function export(Invoice $invoice): string { } } foreach ($lines as $line) { - $this->addLineNode($xml, $line, $invoice, $lastGenId, $usedIds); + $this->addLineNode($xml, $line, $invoice, $isCreditNoteProfile, $lastGenId, $usedIds); } return $xml->asXML(); } + /** + * Is credit note profile + * @param Invoice $invoice Invoice invoice + * @return boolean Whether document should use invoice or credit note profiles + */ + private function isCreditNoteProfile(Invoice $invoice): bool { + $type = $invoice->getType(); + return in_array($type, [ + Invoice::TYPE_CREDIT_NOTE_RELATED_TO_GOODS_OR_SERVICES, + Invoice::TYPE_CREDIT_NOTE_RELATED_TO_FINANCIAL_ADJUSTMENTS, + Invoice::TYPE_CREDIT_NOTE, + Invoice::TYPE_FACTORED_CREDIT_NOTE, + Invoice::TYPE_FORWARDERS_CREDIT_NOTE + ]); + } + + /** * Add identifier node * @param UXML $parent Parent element @@ -252,6 +280,19 @@ private function addOrderReferenceNode(UXML $parent, Invoice $invoice) { } + /** + * Add tender or lot reference node + * @param UXML $parent Parent element + * @param Invoice $invoice Invoice instance + */ + private function addTenderOrLotReferenceNode(UXML $parent, Invoice $invoice) { + $tenderOrLotReference = $invoice->getTenderOrLotReference(); + if ($tenderOrLotReference !== null) { + $parent->add('cac:OriginatorDocumentReference')->add('cbc:ID', $tenderOrLotReference); + } + } + + /** * Add amount node * @param UXML $parent Parent element @@ -522,10 +563,11 @@ private function addDeliveryNode(UXML $parent, Delivery $delivery) { /** * Add payment nodes - * @param UXML $parent Invoice element - * @param Payment $payment Payment instance + * @param UXML $parent Invoice element + * @param Payment $payment Payment instance + * @param DateTime|null $dueDate Invoice due date (for credit note profile) */ - private function addPaymentNodes(UXML $parent, Payment $payment) { + private function addPaymentNodes(UXML $parent, Payment $payment, ?DateTime $dueDate) { $xml = $parent->add('cac:PaymentMeans'); // BT-81: Payment means code @@ -537,6 +579,11 @@ private function addPaymentNodes(UXML $parent, Payment $payment) { $xml->add('cbc:PaymentMeansCode', $meansCode, $attrs); } + // BT-9: Due date (for credit note profile) + if ($dueDate !== null) { + $xml->add('cbc:PaymentDueDate', $dueDate->format('Y-m-d')); + } + // BT-83: Payment ID $paymentId = $payment->getId(); if ($paymentId !== null) { @@ -794,14 +841,23 @@ private function addDocumentTotalsNode(UXML $parent, InvoiceTotals $totals) { /** * Add invoice line - * @param UXML $parent Parent XML element - * @param InvoiceLine $line Invoice line - * @param Invoice $invoice Invoice instance - * @param int &$lastGenId Last used auto-generated ID - * @param string[] &$usedIds Used invoice line IDs + * @param UXML $parent Parent XML element + * @param InvoiceLine $line Invoice line + * @param Invoice $invoice Invoice instance + * @param boolean $isCreditNoteProfile Is credit note profile + * @param int &$lastGenId Last used auto-generated ID + * @param string[] &$usedIds Used invoice line IDs */ - private function addLineNode(UXML $parent, InvoiceLine $line, Invoice $invoice, int &$lastGenId, array &$usedIds) { - $xml = $parent->add('cac:InvoiceLine'); + private function addLineNode( + UXML $parent, + InvoiceLine $line, + Invoice $invoice, + bool $isCreditNoteProfile, + int &$lastGenId, + array &$usedIds + ) { + $lineElementName = $isCreditNoteProfile ? "cac:CreditNoteLine" : "cac:InvoiceLine"; + $xml = $parent->add($lineElementName); // BT-126: Invoice line identifier $lineId = $line->getId(); @@ -819,7 +875,8 @@ private function addLineNode(UXML $parent, InvoiceLine $line, Invoice $invoice, } // BT-129: Invoiced quantity - $xml->add('cbc:InvoicedQuantity', (string) $line->getQuantity(), ['unitCode' => $line->getUnit()]); + $quantityElementName = $isCreditNoteProfile ? "cbc:CreditedQuantity" : "cbc:InvoicedQuantity"; + $xml->add($quantityElementName, (string) $line->getQuantity(), ['unitCode' => $line->getUnit()]); // BT-131: Line net amount $netAmount = $line->getNetAmount(); diff --git a/tests/Writers/UblWriterTest.php b/tests/Writers/UblWriterTest.php index 5cd3336..d1ade72 100644 --- a/tests/Writers/UblWriterTest.php +++ b/tests/Writers/UblWriterTest.php @@ -9,6 +9,7 @@ use Einvoicing\InvoiceLine; use Einvoicing\InvoiceReference; use Einvoicing\Party; +use Einvoicing\Payments\Payment; use Einvoicing\Presets\Peppol; use Einvoicing\Writers\UblWriter; use PHPUnit\Framework\TestCase; @@ -117,6 +118,7 @@ private function validateInvoice(string $contents, string $type): bool { ]); $res = curl_exec($ch); curl_close($ch); + unset($ch); // Validate response return (strpos($res, 'SUCCESS') !== false); @@ -129,6 +131,15 @@ public function testCanGenerateValidInvoice(): void { $this->assertTrue($this->validateInvoice($contents, 'ubl')); } + public function testCanGenerateValidCreditNote(): void { + $invoice = $this->getSampleInvoice(); + $invoice->setType(Invoice::TYPE_CREDIT_NOTE); + $invoice->setPayment((new Payment)->setMeansCode('10')->setMeansText('In cash')); + $invoice->validate(); + $contents = $this->writer->export($invoice); + $this->assertTrue($this->validateInvoice($contents, 'credit')); + } + public function testCanHaveLinesWithForcedDuplicateIdentifiers(): void { $invoice = $this->getSampleInvoice(); $invoice->getLines()[1]->setId('DuplicateId'); From b808aa2012a75fa38e5d855f11e613a51b0b12c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?= Date: Sat, 14 Jan 2023 11:49:36 +0100 Subject: [PATCH 4/5] Added support for credit notes to UBL reader - Updated UblReader class - Updated unit tests --- src/Readers/UblReader.php | 13 +- tests/Readers/UblReaderTest.php | 20 +-- tests/Readers/peppol-credit-note-example.xml | 170 ++++++------------- 3 files changed, 67 insertions(+), 136 deletions(-) diff --git a/src/Readers/UblReader.php b/src/Readers/UblReader.php index 9a4adf7..8078d46 100644 --- a/src/Readers/UblReader.php +++ b/src/Readers/UblReader.php @@ -91,18 +91,13 @@ public function import(string $document): Invoice { } // BT-9: Due date - $dueDateNode = $xml->get("{{$cbc}}DueDate"); + $dueDateNode = $xml->get("{{$cbc}}DueDate | {{$cac}}PaymentMeans/{{$cbc}}PaymentDueDate"); if ($dueDateNode !== null) { $invoice->setDueDate(new DateTime($dueDateNode->asText())); } // BT-3: Invoice type code - $typeNode = $xml->get("{{$cbc}}InvoiceTypeCode"); - if ($typeNode !== null) { - $invoice->setType((int) $typeNode->asText()); - } - - $typeNode = $xml->get("{{$cbc}}CreditNoteTypeCode"); + $typeNode = $xml->get("{{$cbc}}InvoiceTypeCode | {{$cbc}}CreditNoteTypeCode"); if ($typeNode !== null) { $invoice->setType((int) $typeNode->asText()); } @@ -246,7 +241,7 @@ public function import(string $document): Invoice { } // Invoice lines - foreach ($xml->getAll("{{$cac}}InvoiceLine") as $node) { + foreach ($xml->getAll("{{$cac}}InvoiceLine | {{$cac}}CreditNoteLine") as $node) { $invoice->addLine($this->parseInvoiceLine($node, $taxExemptions)); } @@ -749,7 +744,7 @@ private function parseInvoiceLine(UXML $xml, array &$taxExemptions): InvoiceLine } // Quantity - $quantityNode = $xml->get("{{$cbc}}InvoicedQuantity"); + $quantityNode = $xml->get("{{$cbc}}InvoicedQuantity | {{$cbc}}CreditedQuantity"); if ($quantityNode !== null) { $line->setQuantity((float) $quantityNode->asText()); $line->setUnit($quantityNode->element()->getAttribute('unitCode')); diff --git a/tests/Readers/UblReaderTest.php b/tests/Readers/UblReaderTest.php index 084f141..c8496e0 100644 --- a/tests/Readers/UblReaderTest.php +++ b/tests/Readers/UblReaderTest.php @@ -18,34 +18,32 @@ protected function setUp(): void { $this->reader = new UblReader(); } - public function testItCanReadCreditNote(): void { + public function testCanReadCreditNote(): void { $creditNote = $this->reader->import(file_get_contents(self::CREDIT_NOTE_PATH)); $creditNote->validate(); - $this->assertSame(Invoice::TYPE_CREDIT_NOTE, $creditNote->getType()); + $this->assertSame(Invoice::TYPE_CREDIT_NOTE_RELATED_TO_GOODS_OR_SERVICES, $creditNote->getType()); $lines = $creditNote->getLines(); - $this->assertEquals('1', $lines[0]->getId()); - $this->assertEquals('2', $lines[1]->getId()); + $this->assertEquals('1000', $lines[0]->getId()); + $this->assertEquals('2000', $lines[1]->getId()); $totals = $creditNote->getTotals(); $this->assertEquals(1300, $totals->netAmount); - $this->assertEquals(1325, $totals->taxExclusiveAmount); - $this->assertEquals(331.25, $totals->vatAmount); + $this->assertEquals(1300, $totals->taxExclusiveAmount); + $this->assertEquals(325, $totals->vatAmount); $this->assertEquals(0, $totals->allowancesAmount); - $this->assertEquals(25, $totals->chargesAmount); - $this->assertEquals(1656.25, $totals->payableAmount); + $this->assertEquals(0, $totals->chargesAmount); + $this->assertEquals(1625, $totals->payableAmount); $this->assertEquals('S', $totals->vatBreakdown[0]->category); $this->assertEquals(25, $totals->vatBreakdown[0]->rate); - $this->assertEquals('INV-123', $creditNote->getPrecedingInvoiceReferences()[0]->getValue()); - $this->assertEquals('This is a sample string', $creditNote->getAttachments()[0]->getContents()); } public function testCanReadInvoice(): void { $invoice = $this->reader->import(file_get_contents(self::INVOICE_PATH)); $invoice->validate(); - $this->assertSame(Invoice::TYPE_INVOICE, $invoice->getType()); + $this->assertSame(Invoice::TYPE_COMMERCIAL_INVOICE, $invoice->getType()); $lines = $invoice->getLines(); $this->assertEquals('1', $lines[0]->getId()); diff --git a/tests/Readers/peppol-credit-note-example.xml b/tests/Readers/peppol-credit-note-example.xml index 9740846..c021adc 100644 --- a/tests/Readers/peppol-credit-note-example.xml +++ b/tests/Readers/peppol-credit-note-example.xml @@ -1,29 +1,15 @@ - - + + xmlns="urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2"> 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 Snippet1 2017-11-13 - 2017-12-01 - 381 + 81 EUR - 4025:123:4343 0150abc - - - INV-123 - - - - ABC-123 - Invoice ABC-123 - - VGhpcyBpcyBhIHNhbXBsZSBzdHJpbmc= - - 9482348239847239874 @@ -89,60 +75,14 @@ - - 2017-11-01 - - 9483759475923478 - - Delivery street 2 - Building 56 - Stockholm - 21234 - - SE - - - - - - Delivery party Name - - - - - 30 - Snippet1 - - IBAN32423940 - AccountName - - BIC324098 - - - - - Payment within 10 days, 2% discount - - - true - Insurance - 25 - - S - 25.0 - - VAT - - - - 331.25 + 325 - 1325 - 331.25 + 1300 + 325 S - 25.0 + 25 VAT @@ -151,21 +91,19 @@ 1300 - 1325 - 1656.25 - 25 - 1656.25 + 1300 + 1625 + 1625 - - - 1 - 7 - 2800 + + 1000 + 7 + 2800 Konteringsstreng - + 123 - + Description of item item name @@ -179,45 +117,45 @@ S - 25.0 + 25 + + VAT + + + + + 400 + + + + 2000 + -3 + -1500 + + 123 + + + Description 2 + item name 2 + + 21382183120983 + + + NO + + + 09348023 + + + S + 25 VAT - - 400 - - - - 2 - -3 - -1500 - - 123 - - - Description 2 - item name 2 - - 21382183120983 - - - NO - - - 09348023 - - - S - 25.0 - - VAT - - - - - 500 - - - + + 500 + + + From aaf7949fd87e9d1e6010db917e435bc9fff106c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel=20Moreno?= Date: Sat, 14 Jan 2023 11:53:36 +0100 Subject: [PATCH 5/5] Added integration test for invoice notes - Updated tests - Created peppol-credit-note.xml example --- tests/Integration/IntegrationTest.php | 4 + tests/Integration/peppol-credit-note.xml | 217 +++++++++++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 tests/Integration/peppol-credit-note.xml diff --git a/tests/Integration/IntegrationTest.php b/tests/Integration/IntegrationTest.php index 68b9cc1..ba0b57d 100644 --- a/tests/Integration/IntegrationTest.php +++ b/tests/Integration/IntegrationTest.php @@ -50,6 +50,10 @@ public function testCanRecreatePeppolBaseExample(): void { $this->importAndExportInvoice(__DIR__ . "/peppol-base.xml"); } + public function testCanRecreatePeppolCreditNoteExample(): void { + $this->importAndExportInvoice(__DIR__ . "/peppol-credit-note.xml"); + } + public function testCanRecreatePeppolVatExample(): void { $this->importAndExportInvoice(__DIR__ . "/peppol-vat-s.xml"); } diff --git a/tests/Integration/peppol-credit-note.xml b/tests/Integration/peppol-credit-note.xml new file mode 100644 index 0000000..ff8c521 --- /dev/null +++ b/tests/Integration/peppol-credit-note.xml @@ -0,0 +1,217 @@ + + + + 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 + Snippet1 + 2017-11-13 + 381 + Please note we have a new phone number: 22 22 22 22 + EUR + 4025:123:4343 + 0150abc + + + Snippet1 + + + + + 9482348239847239874 + + 99887766 + + + SupplierTradingName Ltd. + + + Main street 1 + Postbox 123 + London + GB 123 EW + + GB + + + + GB1232434 + + VAT + + + + SupplierOfficialName Ltd + GB983294 + + + + + + FR23342 + + FR23342 + + + BuyerTradingName AS + + + Hovedgatan 32 + Po box 878 + Stockholm + 456 34 + + SE + + + + SE4598375937 + + VAT + + + + Buyer Official Name + 39937423947 + + + Lisa Johnson + 23434234 + lj@buyer.se + + + + + 2017-11-01 + + 9483759475923478 + + Delivery street 2 + Building 56 + Stockholm + 21234 + + SE + + + + + + Delivery party Name + + + + + 30 + 2017-12-02 + Snippet1 + + IBAN32423940 + AccountName + + BIC324098 + + + + + Payment within 10 days, 2% discount + + + true + Insurance + 25 + + S + 25 + + VAT + + + + + 331.25 + + 1325 + 331.25 + + S + 25 + + VAT + + + + + + 1300 + 1325 + 1656.25 + 25 + 1656.25 + + + + 1 + 7 + 2800 + Konteringsstreng + + 123 + + + Description of item + item name + + 21382183120983 + + + NO + + + 09348023 + + + S + 25 + + VAT + + + + + 400 + + + + 2 + -3 + -1500 + + 123 + + + Description 2 + item name 2 + + 21382183120983 + + + NO + + + 09348023 + + + S + 25 + + VAT + + + + + 500 + + +