diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..05214d8
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,38 @@
+# Contributing
+Thanks for taking your time to contribute to this project!
+This document will get you on the right track to help improve eInvoicing.
+
+## How to Get Started
+Use the following sites to get more information about the European electronic invocing specification:
+
+- [EU e-Invoicing core concepts](https://josemmo.github.io/einvoicing/getting-started/eu-einvoicing-concepts/)
+- [Compliance with the European standard on eInvoicing](https://ec.europa.eu/cefdigital/wiki/x/ggTvB)
+- [Obtaining a copy of the European standard on eInvoicing](https://ec.europa.eu/cefdigital/wiki/x/kgLvB)
+- [UBL Invoice fields](https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/tree/)
+- [CEF eInvoicing Validator](https://www.itb.ec.europa.eu/invoice/upload)
+
+## PR Requirements
+Before opening a Pull Request, please make sure your code meets the following requirements:
+
+### 1. Uses `develop` as the base branch
+The main repository branch is only for stable releases.
+
+### 2. Passes static analysis inspection
+```
+vendor/bin/phan --testdox
+```
+
+### 3. Passes all tests
+```
+vendor/bin/simple-phpunit
+```
+
+### 4. Complies with EN 16931
+Although the most popular European Invoicing CIUS is [PEPPOL BIS Billing 3.0](https://docs.peppol.eu/poacc/billing/3.0/),
+the real deal is the "European Standard for Electronic invocing" or EN 16931.
+
+This means that, while most users will use the [Peppol](src/Presets/Peppol.php) preset for reading and writing invoices,
+there are other CIUS/extensions from various member states and business sectors which might not be an exact match to PEPPOL.
+
+Because the one thing all these specifications have in common is EN 16931, fields and methods you add to the library
+must have the same names as they do in the European Standard.
diff --git a/src/Invoice.php b/src/Invoice.php
index cee62cb..d83da5f 100644
--- a/src/Invoice.php
+++ b/src/Invoice.php
@@ -10,6 +10,7 @@
use Einvoicing\Traits\BuyerAccountingReferenceTrait;
use Einvoicing\Traits\InvoiceValidationTrait;
use Einvoicing\Traits\PeriodTrait;
+use Einvoicing\Traits\PrecedingInvoiceReferencesTrait;
use InvalidArgumentException;
use OutOfBoundsException;
use function array_splice;
@@ -34,6 +35,7 @@ class Invoice {
protected $buyerReference = null;
protected $purchaseOrderReference = null;
protected $salesOrderReference = null;
+ protected $tenderOrLotReference = null;
protected $paidAmount = 0;
protected $roundingAmount = 0;
protected $seller = null;
@@ -48,6 +50,7 @@ class Invoice {
use BuyerAccountingReferenceTrait;
use PeriodTrait;
use InvoiceValidationTrait;
+ use PrecedingInvoiceReferencesTrait;
/**
* Invoice constructor
@@ -331,6 +334,26 @@ public function setSalesOrderReference(?string $salesOrderReference): self {
}
+ /**
+ * Get tender or lot reference
+ * @return string|null Tender or lot reference
+ */
+ public function getTenderOrLotReference(): ?string {
+ return $this->tenderOrLotReference;
+ }
+
+
+ /**
+ * Set tender or lot reference
+ * @param string|null $tenderOrLotReference Tender or lot reference
+ * @return self Invoice instance
+ */
+ public function setTenderOrLotReference(?string $tenderOrLotReference): self {
+ $this->tenderOrLotReference = $tenderOrLotReference;
+ return $this;
+ }
+
+
/**
* Get invoice prepaid amount
* NOTE: may be rounded according to the CIUS specification
diff --git a/src/InvoiceReference.php b/src/InvoiceReference.php
new file mode 100644
index 0000000..782df4b
--- /dev/null
+++ b/src/InvoiceReference.php
@@ -0,0 +1,59 @@
+setValue($value);
+ $this->setIssueDate($issueDate);
+ }
+
+
+ /**
+ * Get value
+ * @return string Value
+ */
+ public function getValue(): string {
+ return $this->value;
+ }
+
+
+ /**
+ * Set value
+ * @param string $value Value
+ * @return self Invoice reference instance
+ */
+ public function setValue(string $value): self {
+ $this->value = $value;
+ return $this;
+ }
+
+
+ /**
+ * Get issue date
+ * @return DateTime|null Issue date
+ */
+ public function getIssueDate(): ?DateTime {
+ return $this->issueDate;
+ }
+
+
+ /**
+ * Set issue date
+ * @param DateTime|null $issueDate Issue date
+ * @return self Invoice reference instance
+ */
+ public function setIssueDate(?DateTime $issueDate): self {
+ $this->issueDate = $issueDate;
+ return $this;
+ }
+}
diff --git a/src/Readers/UblReader.php b/src/Readers/UblReader.php
index 0ec0df5..ce33d32 100644
--- a/src/Readers/UblReader.php
+++ b/src/Readers/UblReader.php
@@ -9,6 +9,7 @@
use Einvoicing\Identifier;
use Einvoicing\Invoice;
use Einvoicing\InvoiceLine;
+use Einvoicing\InvoiceReference;
use Einvoicing\Party;
use Einvoicing\Payments\Card;
use Einvoicing\Payments\Mandate;
@@ -142,6 +143,26 @@ public function import(string $document): Invoice {
$invoice->setSalesOrderReference($salesOrderReferenceNode->asText());
}
+ // BG-3: Preceding invoice references
+ foreach ($xml->getAll("{{$cac}}BillingReference/{{$cac}}InvoiceDocumentReference") as $node) {
+ $invoiceReferenceValueNode = $node->get("{{$cbc}}ID");
+ if ($invoiceReferenceValueNode === null) {
+ continue;
+ }
+ $invoiceReference = new InvoiceReference($invoiceReferenceValueNode->asText());
+ $invoiceReferenceIssueDateNode = $node->get("{{$cbc}}IssueDate");
+ if ($invoiceReferenceIssueDateNode !== null) {
+ $invoiceReference->setIssueDate(new DateTime($invoiceReferenceIssueDateNode->asText()));
+ }
+ $invoice->addPrecedingInvoiceReference($invoiceReference);
+ }
+
+ // BT-17: Tender or lot reference
+ $tenderOrLotReferenceNode = $xml->get("{{$cac}}OriginatorDocumentReference/{{$cbc}}ID");
+ if ($tenderOrLotReferenceNode !== null) {
+ $invoice->setTenderOrLotReference($tenderOrLotReferenceNode->asText());
+ }
+
// BG-24: Attachment nodes
foreach ($xml->getAll("{{$cac}}AdditionalDocumentReference") as $node) {
$invoice->addAttachment($this->parseAttachmentNode($node));
diff --git a/src/Traits/PrecedingInvoiceReferencesTrait.php b/src/Traits/PrecedingInvoiceReferencesTrait.php
new file mode 100644
index 0000000..3f17cb6
--- /dev/null
+++ b/src/Traits/PrecedingInvoiceReferencesTrait.php
@@ -0,0 +1,53 @@
+precedingInvoiceReferences;
+ }
+
+
+ /**
+ * Add preceding invoice reference
+ * @param InvoiceReference $reference Preceding invoice reference
+ * @return self This instance
+ */
+ public function addPrecedingInvoiceReference(InvoiceReference $reference): self {
+ $this->precedingInvoiceReferences[] = $reference;
+ return $this;
+ }
+
+
+ /**
+ * Remove preceding invoice reference
+ * @param int $index Preceding invoice reference index
+ * @return self This instance
+ * @throws OutOfBoundsException if preceding invoice reference index is out of bounds
+ */
+ public function removePrecedingInvoiceReference(int $index): self {
+ if ($index < 0 || $index >= count($this->precedingInvoiceReferences)) {
+ throw new OutOfBoundsException('Could not find preceding invoice reference by index');
+ }
+ array_splice($this->precedingInvoiceReferences, $index, 1);
+ return $this;
+ }
+
+
+ /**
+ * Clear all preceding invoice references
+ * @return self This instance
+ */
+ public function clearPrecedingInvoiceReferences(): self {
+ $this->precedingInvoiceReferences = [];
+ return $this;
+ }
+}
diff --git a/src/Writers/UblWriter.php b/src/Writers/UblWriter.php
index 8ee3db4..fb31768 100644
--- a/src/Writers/UblWriter.php
+++ b/src/Writers/UblWriter.php
@@ -96,6 +96,22 @@ public function export(Invoice $invoice): string {
// Order reference node
$this->addOrderReferenceNode($xml, $invoice);
+ // BG-3: Preceding invoice reference
+ foreach ($invoice->getPrecedingInvoiceReferences() as $invoiceReference) {
+ $invoiceDocumentReferenceNode = $xml->add('cac:BillingReference')->add('cac:InvoiceDocumentReference');
+ $invoiceDocumentReferenceNode->add('cbc:ID', $invoiceReference->getValue());
+ $invoiceReferenceIssueDate = $invoiceReference->getIssueDate();
+ if ($invoiceReferenceIssueDate !== null) {
+ $invoiceDocumentReferenceNode->add('cbc:IssueDate', $invoiceReferenceIssueDate->format('Y-m-d'));
+ }
+ }
+
+ // BT-17: Tender or lot reference
+ $tenderOrLotReference = $invoice->getTenderOrLotReference();
+ if ($tenderOrLotReference !== null) {
+ $xml->add('cac:OriginatorDocumentReference')->add('cbc:ID', $tenderOrLotReference);
+ }
+
// BG-24: Attachments node
foreach ($invoice->getAttachments() as $attachment) {
$this->addAttachmentNode($xml, $attachment);
diff --git a/tests/Integration/peppol-base.xml b/tests/Integration/peppol-base.xml
index c52bf23..14a6ed0 100644
--- a/tests/Integration/peppol-base.xml
+++ b/tests/Integration/peppol-base.xml
@@ -15,6 +15,20 @@
854777
+
+
+ INV-122
+ 2021-09-21
+
+
+
+
+ INV-123
+
+
+
+ PPID-123
+
INV-123
130
diff --git a/tests/Readers/UblReaderTest.php b/tests/Readers/UblReaderTest.php
index 38435c6..e97de37 100644
--- a/tests/Readers/UblReaderTest.php
+++ b/tests/Readers/UblReaderTest.php
@@ -27,6 +27,7 @@ public function testCanReadInvoice(): void {
$this->assertEquals(1656.25, $totals->payableAmount);
$this->assertEquals('S', $totals->vatBreakdown[0]->category);
$this->assertEquals(25, $totals->vatBreakdown[0]->rate);
+ $this->assertEquals('INV-123', $invoice->getPrecedingInvoiceReferences()[0]->getValue());
$this->assertEquals('This is a sample string', $invoice->getAttachments()[0]->getContents());
}
}
diff --git a/tests/Readers/peppol-example.xml b/tests/Readers/peppol-example.xml
index e9de3c0..d56efac 100644
--- a/tests/Readers/peppol-example.xml
+++ b/tests/Readers/peppol-example.xml
@@ -12,6 +12,11 @@
EUR
4025:123:4343
0150abc
+
+
+ INV-123
+
+
ABC-123
Invoice ABC-123
diff --git a/tests/Writers/UblWriterTest.php b/tests/Writers/UblWriterTest.php
index 14783f7..31a73ee 100644
--- a/tests/Writers/UblWriterTest.php
+++ b/tests/Writers/UblWriterTest.php
@@ -7,6 +7,7 @@
use Einvoicing\Identifier;
use Einvoicing\Invoice;
use Einvoicing\InvoiceLine;
+use Einvoicing\InvoiceReference;
use Einvoicing\Party;
use Einvoicing\Presets\Peppol;
use Einvoicing\Writers\UblWriter;
@@ -70,6 +71,8 @@ private function getSampleInvoice(): Invoice {
->setIssueDate(new DateTime('-3 days'))
->setDueDate(new DateTime('+30 days'))
->setBuyerReference('REF-0172637')
+ ->addPrecedingInvoiceReference(new InvoiceReference('INV-123'))
+ ->setTenderOrLotReference('PPID-123')
->setSeller($seller)
->setBuyer($buyer)
->addLine($complexLine)