Skip to content

Commit

Permalink
Merge pull request #10 from josemmo/develop
Browse files Browse the repository at this point in the history
v0.1.4
  • Loading branch information
josemmo authored Sep 26, 2021
2 parents c9ce3bc + 15f2dc5 commit e938d70
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 0 deletions.
38 changes: 38 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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.
23 changes: 23 additions & 0 deletions src/Invoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -48,6 +50,7 @@ class Invoice {
use BuyerAccountingReferenceTrait;
use PeriodTrait;
use InvoiceValidationTrait;
use PrecedingInvoiceReferencesTrait;

/**
* Invoice constructor
Expand Down Expand Up @@ -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
Expand Down
59 changes: 59 additions & 0 deletions src/InvoiceReference.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php
namespace Einvoicing;

use DateTime;

class InvoiceReference {
protected $value;
protected $issueDate;

/**
* Class constructor
* @param string $value Value
* @param DateTime|null $issueDate Issue date
*/
public function __construct(string $value, ?DateTime $issueDate=null) {
$this->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;
}
}
21 changes: 21 additions & 0 deletions src/Readers/UblReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
53 changes: 53 additions & 0 deletions src/Traits/PrecedingInvoiceReferencesTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
namespace Einvoicing\Traits;

use Einvoicing\InvoiceReference;
use OutOfBoundsException;

trait PrecedingInvoiceReferencesTrait {
protected $precedingInvoiceReferences = [];

/**
* Get preceding invoice references
* @return InvoiceReference[] Array of preceding invoice references
*/
public function getPrecedingInvoiceReferences(): array {
return $this->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;
}
}
16 changes: 16 additions & 0 deletions src/Writers/UblWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 14 additions & 0 deletions tests/Integration/peppol-base.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@
<cac:OrderReference>
<cbc:ID>854777</cbc:ID>
</cac:OrderReference>
<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID>INV-122</cbc:ID>
<cbc:IssueDate>2021-09-21</cbc:IssueDate>
</cac:InvoiceDocumentReference>
</cac:BillingReference>
<cac:BillingReference>
<cac:InvoiceDocumentReference>
<cbc:ID>INV-123</cbc:ID>
</cac:InvoiceDocumentReference>
</cac:BillingReference>
<cac:OriginatorDocumentReference>
<cbc:ID>PPID-123</cbc:ID>
</cac:OriginatorDocumentReference>
<cac:AdditionalDocumentReference>
<cbc:ID>INV-123</cbc:ID>
<cbc:DocumentTypeCode>130</cbc:DocumentTypeCode>
Expand Down
1 change: 1 addition & 0 deletions tests/Readers/UblReaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
5 changes: 5 additions & 0 deletions tests/Readers/peppol-example.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
<ns:DocumentCurrencyCode>EUR</ns:DocumentCurrencyCode>
<ns:AccountingCost>4025:123:4343</ns:AccountingCost>
<ns:BuyerReference>0150abc</ns:BuyerReference>
<cac:BillingReference>
<cac:InvoiceDocumentReference>
<ns:ID>INV-123</ns:ID>
</cac:InvoiceDocumentReference>
</cac:BillingReference>
<cac:AdditionalDocumentReference>
<ns:ID>ABC-123</ns:ID>
<ns:DocumentDescription>Invoice ABC-123</ns:DocumentDescription>
Expand Down
3 changes: 3 additions & 0 deletions tests/Writers/UblWriterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit e938d70

Please sign in to comment.