Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/Extension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace NumNum\UBL;

use function Sabre\Xml\Deserializer\keyValue;
use function Sabre\Xml\Deserializer\mixedContent;

use Sabre\Xml\Reader;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlDeserializable;
use Sabre\Xml\XmlSerializable;
use Doctrine\Common\Collections\ArrayCollection;

class Extension implements XmlSerializable, XmlDeserializable
{
private $content;
private array $attributes = [];

public function getContent()
{
return $this->content;
}

public function setContent($content): self
{
$this->content = $content;

return $this;
}

public function getAttributes(): array
{
return $this->attributes;
}

public function setAttributes(array $attributes): self
{
$this->attributes = $attributes;

return $this;
}

public function xmlSerialize(Writer $writer): void
{
$writer->write([
[
'name' => Schema::EXT . 'UBLExtension',
'value' => [
Schema::EXT . 'ExtensionContent' => $this->content
],
'attributes' => $this->attributes
]
]);
}

public static function xmlDeserialize(Reader $reader)
{
$keyValues = keyValue($reader);
$mixedContent = mixedContent($reader);
$collection = new ArrayCollection($mixedContent);

$extensionContentTag = ReaderHelper::getTag(Schema::EXT . 'ExtensionContent', $collection);

return (new static())
->setContent($keyValues[Schema::EXT . 'ExtensionContent'] ?? null)
->setAttributes(isset($extensionContentTag, $extensionContentTag['attributes']) ? $extensionContentTag['attributes'] : [])
;
}
}
10 changes: 9 additions & 1 deletion src/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Generator
{
public static $currencyID;

public static function invoice(Invoice $invoice, $currencyId = 'EUR')
public static function invoice(Invoice $invoice, $currencyId = 'EUR', array $additionalNamespaces = [])
{
self::$currencyID = $currencyId;

Expand All @@ -20,6 +20,14 @@ public static function invoice(Invoice $invoice, $currencyId = 'EUR')
'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2' => 'cac'
];

if ($invoice->getExtensions()) {
$xmlService->namespaceMap['urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2'] = 'ext';
}

foreach ($additionalNamespaces as $namespace => $prefix) {
$xmlService->namespaceMap[$namespace] = $prefix;
}

return $xmlService->write($invoice->xmlTagName, [
$invoice
]);
Expand Down
45 changes: 45 additions & 0 deletions src/Invoice.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class Invoice implements XmlSerializable, XmlDeserializable
private $despatchDocumentReference;
private $receiptDocumentReference;
private $originatorDocumentReference;
/** @var Extension[] $extensions */
private array $extensions = [];

/**
* @return string
Expand Down Expand Up @@ -675,6 +677,36 @@ public function setOriginatorDocumentReference(
return $this;
}

/**
* @param Extension $extension
* @return static
*/
public function addExtension(Extension $extension): self
{
$this->extensions[] = $extension;

return $this;
}

/**
* @param Extension[] $extensions
* @return static
*/
public function setExtensions(array $extensions): self
{
$this->extensions = $extensions;

return $this;
}

/**
* @return Extension[]
*/
public function getExtensions(): array
{
return $this->extensions;
}

/**
* The validate function that is called during xml writing to valid the data of the object.
*
Expand Down Expand Up @@ -729,6 +761,13 @@ public function xmlSerialize(Writer $writer): void
{
$this->validate();

if ($this->extensions) {
$writer->write([
'name' => Schema::EXT . 'UBLExtensions',
'value' => $this->extensions
]);
}

$writer->write([
Schema::CBC . "UBLVersionID" => $this->UBLVersionID,
Schema::CBC . "CustomizationID" => $this->customizationID,
Expand Down Expand Up @@ -963,6 +1002,12 @@ protected static function deserializedTag(array $mixedContent)
);

return (new static())
->setExtensions(
ReaderHelper::getArrayValue(
Schema::EXT . "UBLExtensions",
$collection,
),
)
->setUBLVersionId(
ReaderHelper::getTagValue(
Schema::CBC . "UBLVersionID",
Expand Down
1 change: 1 addition & 0 deletions src/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public static function ubl($currencyId = 'EUR'): Service
Schema::CAC. 'TaxScheme' => fn ($reader) => TaxScheme::xmlDeserialize($reader),
Schema::CAC. 'TaxSubtotal' => fn ($reader) => TaxSubTotal::xmlDeserialize($reader),
Schema::CAC. 'TaxTotal' => fn ($reader) => TaxTotal::xmlDeserialize($reader),
Schema::EXT. 'UBLExtension' => fn ($reader) => Extension::xmlDeserialize($reader),
];

return $xmlService;
Expand Down
1 change: 1 addition & 0 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ class Schema
public const CREDITNOTE = '{urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2}';
public const CBC = '{urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2}';
public const CAC = '{urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2}';
public const EXT = '{urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2}';
}
207 changes: 207 additions & 0 deletions tests/Write/ExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<?php

namespace NumNum\UBL\Tests\Write;

use PHPUnit\Framework\TestCase;

/**
* Test an UBL2.1 invoice document
*/
class ExtensionTest extends TestCase
{
private $schema = 'http://docs.oasis-open.org/ubl/os-UBL-2.1/xsd/maindoc/UBL-Invoice-2.1.xsd';

/** @test */
public function testIfXMLIsValid()
{
$country = (new \NumNum\UBL\Country())
->setIdentificationCode('BE');

$address = (new \NumNum\UBL\Address())
->setStreetName('Korenmarkt')
->setBuildingNumber(1)
->setCityName('Gent')
->setPostalZone('9000')
->setCountry($country);

$supplierCompany = (new \NumNum\UBL\Party())
->setName('Supplier Company Name')
->setPhysicalLocation($address)
->setPostalAddress($address);

$clientContact = (new \NumNum\UBL\Contact())
->setName('Client name')
->setElectronicMail('email@client.com')
->setTelephone('0032 472 123 456')
->setTelefax('0032 9 1234 567');

$clientCompany = (new \NumNum\UBL\Party())
->setName('My client')
->setPostalAddress($address)
->setContact($clientContact);

$legalMonetaryTotal = (new \NumNum\UBL\LegalMonetaryTotal())
->setPayableAmount(10 + 2)
->setAllowanceTotalAmount(0);

$taxScheme = (new \NumNum\UBL\TaxScheme())
->setId(0);

$productItem = (new \NumNum\UBL\Item())
->setName('Product Name')
->setDescription('Product Description')
->setSellersItemIdentification('SELLERID');

$price = (new \NumNum\UBL\Price())
->setBaseQuantity(1)
->setUnitCode(\NumNum\UBL\UnitCode::UNIT)
->setPriceAmount(10);

$lineTaxTotal = (new \NumNum\UBL\TaxTotal())
->setTaxAmount(2.5);

$invoicePeriod = (new \NumNum\UBL\InvoicePeriod())
->setStartDate(new \DateTime());

$invoiceLines = [];

$orderLineReference = (new \NumNum\UBL\OrderLineReference())
->setLineId('#ABC123');

$invoiceLines[] = (new \NumNum\UBL\InvoiceLine())
->setId(0)
->setItem($productItem)
->setInvoicePeriod($invoicePeriod)
->setPrice($price)
->setTaxTotal($lineTaxTotal)
->setInvoicedQuantity(1)
->setOrderLineReference($orderLineReference);

$invoiceLines[] = (new \NumNum\UBL\InvoiceLine())
->setId(0)
->setItem($productItem)
->setInvoicePeriod($invoicePeriod)
->setPrice($price)
->setAccountingCost('Product 123')
->setTaxTotal($lineTaxTotal)
->setInvoicedQuantity(1)
->setOrderLineReference($orderLineReference);

$invoiceLines[] = (new \NumNum\UBL\InvoiceLine())
->setId(0)
->setItem($productItem)
->setInvoicePeriod($invoicePeriod)
->setPrice($price)
->setAccountingCostCode('Product 123')
->setTaxTotal($lineTaxTotal)
->setInvoicedQuantity(1)
->setOrderLineReference($orderLineReference);

$taxCategory = (new \NumNum\UBL\TaxCategory())
->setId(0)
->setName('VAT25%')
->setPercent(.25)
->setTaxScheme($taxScheme);

$taxSubTotal = (new \NumNum\UBL\TaxSubTotal())
->setTaxableAmount(10)
->setTaxAmount(2.5)
->setTaxCategory($taxCategory);

$taxTotal = (new \NumNum\UBL\TaxTotal())
->addTaxSubTotal($taxSubTotal)
->setTaxAmount(2.5);

$accountingSupplierParty = (new \NumNum\UBL\AccountingParty())
->setParty($supplierCompany);

$accountingCustomerParty = (new \NumNum\UBL\AccountingParty())
->setSupplierAssignedAccountId('10001')
->setParty($clientCompany);

$extension = (new \NumNum\UBL\Extension())
->setContent([
'hrextac:HRFISK20Data' => [
'hrextac:HRTaxTotal' => [
[
'name' => 'cbc:TaxAmount',
'value' => 2.5,
'attributes' => [
'currencyID' => 'EUR'
]
],
'hrextac:HRTaxSubtotal' => [
[
[
'name' => 'cbc:TaxableAmount',
'value' => 10,
'attributes' => [
'currencyID' => 'EUR'
]
],
[
'name' => 'cbc:TaxAmount',
'value' => 2.5,
'attributes' => [
'currencyID' => 'EUR'
]
],
'hrextac:HRTaxCategory' => [
'cbc:ID' => 'S',
'cbc:Name' => 'HR:PDV25',
'cbc:Percent' => '25',
'hrextac:HRTaxScheme' => [
'cbc:ID' => 'VAT'
]
]
]
]
],
'hrextac:HRLegalMonetaryTotal' => [
[
'name' => 'cbc:TaxExclusiveAmount',
'value' => 10,
'attributes' => [
'currencyID' => 'EUR'
]
],
[
'name' => 'hrextac:OutOfScopeOfVATAmount',
'value' => 0,
'attributes' => [
'currencyID' => 'EUR'
]
]
]
]
])
->setAttributes([
'xmlns:cct' => 'urn:un:unece:uncefact:data:specification:CoreComponentTypeSchemaModule:2',
'xmlns:p3' => 'urn:oasis:names:specification:ubl:schema:xsd:UnqualifiedDataTypes-2',
'xmlns:xsi' => 'https://www.w3.org/2001/XMLSchema-instance'
]);

$invoice = (new \NumNum\UBL\Invoice())
->setId(1234)
->setCopyIndicator(false)
->setIssueDate(new \DateTime())
->setAccountingSupplierParty($accountingSupplierParty)
->setAccountingCustomerParty($accountingCustomerParty)
->setInvoiceLines($invoiceLines)
->setLegalMonetaryTotal($legalMonetaryTotal)
->setTaxTotal($taxTotal)
->addExtension($extension);

$generator = new \NumNum\UBL\Generator();
$outputXMLString = $generator->invoice($invoice, 'EUR', [
'urn:mfin.gov.hr:schema:xsd:HRExtensionAggregateComponents-1' => 'hrextac'
]);

$dom = new \DOMDocument();
$dom->loadXML($outputXMLString);

$dom->save('./tests/ExtensionTest.xml');

$this->assertEquals(true, $dom->schemaValidate($this->schema));
}
}