Skip to content

Commit

Permalink
#119 Update to ZUGFeRD 2.3 (Support for InvoiceReferencedDocument)
Browse files Browse the repository at this point in the history
  • Loading branch information
HorstOeko committed Sep 23, 2024
1 parent d7fe3bd commit 10520c3
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 18 deletions.
33 changes: 27 additions & 6 deletions src/ZugferdDocumentBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2176,22 +2176,43 @@ public function addDocumentAdditionalReferencedDocument(string $issuerassignedid
/**
* Set a Reference to the previous invoice
*
* __Note__: To be used if:
* To be used if:
* - a previous invoice is corrected
* - reference is made to previous partial invoices from a final invoice
* - Reference is made to previous invoices for advance payments from a final invoice
* - reference is made from a final invoice to previous partial invoices
* - reference is made from a final invoice to previous invoices for advance payments. *
*
* @param string $issuerassignedid __BT-25, From BASIC WL__ Number of the previous invoice
* @param string $issuerassignedid __BT-25, From BASIC WL__ The identification of an invoice previously sent by the seller
* @param string|null $typecode Type of previous invoice (code)
* @param DateTime|null $issueddate __BT-26, From BASIC WL__ Date of the previous invoice
* @return ZugferdDocumentBuilder
*/
public function setDocumentInvoiceReferencedDocument(string $issuerassignedid, ?DateTime $issueddate = null): ZugferdDocumentBuilder
public function setDocumentInvoiceReferencedDocument(string $issuerassignedid, ?string $typecode = null, ?DateTime $issueddate = null): ZugferdDocumentBuilder
{
$invoicerefdoc = $this->getObjectHelper()->getReferencedDocumentType($issuerassignedid, null, null, null, null, null, $issueddate, null);
$invoicerefdoc = $this->getObjectHelper()->getReferencedDocumentType($issuerassignedid, null, null, $typecode, null, null, $issueddate, null);
$this->getObjectHelper()->tryCallIfMethodExists($this->headerTradeSettlement, "addToInvoiceReferencedDocument", "setInvoiceReferencedDocument", [$invoicerefdoc], $invoicerefdoc);
return $this;
}

/**
* Add a Reference to the previous invoice
*
* To be used if:
* - a previous invoice is corrected
* - reference is made from a final invoice to previous partial invoices
* - reference is made from a final invoice to previous invoices for advance payments. *
*
* @param string $issuerassignedid __BT-25, From BASIC WL__ The identification of an invoice previously sent by the seller
* @param string|null $typecode Type of previous invoice (code)
* @param DateTime|null $issueddate __BT-26, From BASIC WL__ Date of the previous invoice
* @return ZugferdDocumentBuilder
*/
public function addDocumentInvoiceReferencedDocument(string $issuerassignedid, ?string $typecode = null, ?DateTime $issueddate = null): ZugferdDocumentBuilder
{
$invoicerefdoc = $this->getObjectHelper()->getReferencedDocumentType($issuerassignedid, null, null, $typecode, null, null, $issueddate, null);
$this->getObjectHelper()->tryCall($this->headerTradeSettlement, "addToInvoiceReferencedDocument", $invoicerefdoc);
return $this;
}

/**
* Set Details of a project reference
*
Expand Down
83 changes: 82 additions & 1 deletion src/ZugferdDocumentReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
class ZugferdDocumentReader extends ZugferdDocument
{
/**
* Undocumented variable
* Internal pointer for documents additional documents
*
* @var integer
*/
Expand Down Expand Up @@ -148,6 +148,13 @@ class ZugferdDocumentReader extends ZugferdDocument
*/
private $documentPayeeContactPointer = 0;

/**
* Internal pointer for documents invoice reference documents
*
* @var integer
*/
private $documentInvRefDocPointer = 0;

/**
* Internal pointer for the position
*
Expand Down Expand Up @@ -2481,6 +2488,80 @@ public function getDocumentAdditionalReferencedDocuments(?array &$refdocs): Zugf
return $this;
}

/**
* Get first reference to the previous invoice
* Returns true if an additional referenced document is available, otherwise false
*
* @return boolean
*/
public function firstDocumentInvoiceReferencedDocument(): bool
{
$this->documentInvRefDocPointer = 0;
$addRefDoc = $this->getInvoiceValueByPath("getSupplyChainTradeTransaction.getApplicableHeaderTradeSettlement.getInvoiceReferencedDocument", []);
return isset($addRefDoc[$this->documentInvRefDocPointer]);
}

/**
* Get next reference to the previous invoice
* Returns true when another additional referenced document is available, otherwise false
*
* @return boolean
*/
public function nextDocumentInvoiceReferencedDocument(): bool
{
$this->documentInvRefDocPointer++;
$addRefDoc = $this->getInvoiceValueByPath("getSupplyChainTradeTransaction.getApplicableHeaderTradeSettlement.getInvoiceReferencedDocument", []);
return isset($addRefDoc[$this->documentInvRefDocPointer]);
}

/**
* Get reference to the previous invoice
*
* @param string|null $issuerassignedid
* __BT-X-331__ Reference to the previous invoice
* @param string|null $typecode
* __BT-X-332__ Type of previous invoice (code)
* @param DateTime|null $issueddate
* __BT-X-333-00__ Document date
*/
public function getDocumentInvoiceReferencedDocument(?string &$issuerassignedid, ?string &$typecode, ?DateTime &$issueddate = null): ZugferdDocumentReader
{
$invoiceRefDoc = $this->getInvoiceValueByPath("getSupplyChainTradeTransaction.getApplicableHeaderTradeSettlement.getInvoiceReferencedDocument", []);
$invoiceRefDoc = $invoiceRefDoc[$this->documentInvRefDocPointer];

$issuerassignedid = $this->getInvoiceValueByPathFrom($invoiceRefDoc, "getIssuerAssignedID.value", "");
$typecode = $this->getInvoiceValueByPathFrom($invoiceRefDoc, "getTypeCode.value", "");
$issueddate = $this->getObjectHelper()->toDateTime(
$this->getInvoiceValueByPathFrom($invoiceRefDoc, "getFormattedIssueDateTime.getDateTimeString.value", ""),
$this->getInvoiceValueByPathFrom($invoiceRefDoc, "getFormattedIssueDateTime.getDateTimeString.getFormat", "")
);

return $this;
}

/**
* Get all references to the previous invoice
*
* @param array|null $refdocs
* Array contains all invoice referenced documents, but without extracting attached binary objects. If you
* want to access attached binary objects you have to use ZugferdDocumentReader::getDocumentInvoiceReferencedDocument
* @return ZugferdDocumentReader
*/
public function getDocumentInvoiceReferencedDocuments(?array &$invoiceRefDocs): ZugferdDocumentReader
{
$invoiceRefDocs = $this->getInvoiceValueByPath("getSupplyChainTradeTransaction.getApplicableHeaderTradeSettlement.getInvoiceReferencedDocument", []);
$invoiceRefDocs = $this->convertToArray(
$invoiceRefDocs,
[
"IssuerAssignedID" => ["getIssuerAssignedID.value", ""],
"TypeCode" => ["getTypeCode.value", ""],
"FormattedIssueDateTime" => ["getFormattedIssueDateTime.getDateTimeString.value", ""],
]
);

return $this;
}

/**
* Get Details of a project reference
*
Expand Down
2 changes: 1 addition & 1 deletion src/quick/ZugferdQuickDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ public function doSetDeliveryNoteReferenceDocument(string $deliveryNoteNo, DateT
*/
public function doSetInvoiceReferencedDocument(string $id, ?DateTime $issueDateTime = null): ZugferdQuickDescriptor
{
$this->setDocumentInvoiceReferencedDocument($id, $issueDateTime);
$this->setDocumentInvoiceReferencedDocument($id, null, $issueDateTime);
return $this;
}

Expand Down
14 changes: 14 additions & 0 deletions tests/assets/extended_invoice.xml
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,20 @@
<ram:TotalPrepaidAmount>0.00</ram:TotalPrepaidAmount>
<ram:DuePayableAmount>480.22</ram:DuePayableAmount>
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
<ram:InvoiceReferencedDocument>
<ram:IssuerAssignedID>S-INV1</ram:IssuerAssignedID>
<ram:TypeCode>83</ram:TypeCode>
<ram:FormattedIssueDateTime>
<ram:DateTimeString format="102">2024-09-30</ram:DateTimeString>
</ram:FormattedIssueDateTime>
</ram:InvoiceReferencedDocument>
<ram:InvoiceReferencedDocument>
<ram:IssuerAssignedID>S-INV2</ram:IssuerAssignedID>
<ram:TypeCode>84</ram:TypeCode>
<ram:FormattedIssueDateTime>
<ram:DateTimeString format="102">2024-09-30</ram:DateTimeString>
</ram:FormattedIssueDateTime>
</ram:InvoiceReferencedDocument>
</ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>
20 changes: 17 additions & 3 deletions tests/testcases/BuilderEn16931Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -1306,11 +1306,25 @@ public function testAddDocumentAdditionalReferencedDocumentWithAttachment(): voi

public function testSetDocumentInvoiceReferencedDocument(): void
{
(self::$document)->setDocumentInvoiceReferencedDocument("INV-1", new DateTime());
(self::$document)->setDocumentInvoiceReferencedDocument("INV-1", "71", new DateTime());

$this->disableRenderXmlContent();
$this->assertXPathValue('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID', "INV-1");
$this->assertXPathValueWithAttribute('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString', (new DateTime())->format("Ymd"), "format", "102");
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID', 0, "INV-1");
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:TypeCode', 0, "71");
$this->assertXPathValueWithIndexAndAttribute('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString', 0, (new DateTime())->format("Ymd"), "format", "102");
}

public function testAddDocumentInvoiceReferencedDocument(): void
{
(self::$document)->addDocumentInvoiceReferencedDocument("INV-2", "82", new DateTime());

$this->disableRenderXmlContent();
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID', 0, "INV-1");
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:TypeCode', 0, "71");
$this->assertXPathValueWithIndexAndAttribute('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString', 0, (new DateTime())->format("Ymd"), "format", "102");
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID', 1, "INV-2");
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:TypeCode', 1, "82");
$this->assertXPathValueWithIndexAndAttribute('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString', 1, (new DateTime())->format("Ymd"), "format", "102");
}

public function testSetDocumentProcuringProject(): void
Expand Down
23 changes: 19 additions & 4 deletions tests/testcases/BuilderExtendedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

namespace horstoeko\zugferd\tests\testcases;

use DateTime;
use \horstoeko\zugferd\tests\TestCase;
use \horstoeko\zugferd\tests\traits\HandlesXmlTests;
use \horstoeko\zugferd\ZugferdProfiles;
use \horstoeko\zugferd\ZugferdDocumentBuilder;
use \horstoeko\zugferd\tests\traits\HandlesXmlTests;

class BuilderExtendedTest extends TestCase
{
Expand Down Expand Up @@ -1351,11 +1352,25 @@ public function testAddDocumentAdditionalReferencedDocumentWithAttachment(): voi

public function testSetDocumentInvoiceReferencedDocument(): void
{
(self::$document)->setDocumentInvoiceReferencedDocument("INV-1", new \DateTime());
(self::$document)->setDocumentInvoiceReferencedDocument("INV-1", "71", new DateTime());

$this->disableRenderXmlContent();
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID', 0, "INV-1");
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:TypeCode', 0, "71");
$this->assertXPathValueWithIndexAndAttribute('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString', 0, (new DateTime())->format("Ymd"), "format", "102");
}

public function testAddDocumentInvoiceReferencedDocument(): void
{
(self::$document)->addDocumentInvoiceReferencedDocument("INV-2", "82", new DateTime());

$this->disableRenderXmlContent();
$this->assertXPathValue('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID', "INV-1");
$this->assertXPathValueWithAttribute('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString', (new \DateTime())->format("Ymd"), "format", "102");
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID', 0, "INV-1");
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:TypeCode', 0, "71");
$this->assertXPathValueWithIndexAndAttribute('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString', 0, (new DateTime())->format("Ymd"), "format", "102");
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID', 1, "INV-2");
$this->assertXPathValueWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:TypeCode', 1, "82");
$this->assertXPathValueWithIndexAndAttribute('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString', 1, (new DateTime())->format("Ymd"), "format", "102");
}

public function testSetDocumentProcuringProject(): void
Expand Down
20 changes: 17 additions & 3 deletions tests/testcases/BuilderMinimumTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1304,11 +1304,25 @@ public function testAddDocumentAdditionalReferencedDocumentWithAttachment(): voi

public function testSetDocumentInvoiceReferencedDocument(): void
{
(self::$document)->setDocumentInvoiceReferencedDocument("INV-1", new DateTime());
(self::$document)->setDocumentInvoiceReferencedDocument("INV-1", "71", new DateTime());

$this->disableRenderXmlContent();
$this->assertXPathNotExists('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID');
$this->assertXPathNotExists('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString');
$this->assertXPathNotExistsWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID', 0);
$this->assertXPathNotExistsWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString', 0);
$this->assertXPathNotExistsWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:TypeCode', 0);
}

public function testAddDocumentInvoiceReferencedDocument(): void
{
(self::$document)->addDocumentInvoiceReferencedDocument("INV-2", "82", new DateTime());

$this->disableRenderXmlContent();
$this->assertXPathNotExistsWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID', 0);
$this->assertXPathNotExistsWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString', 0);
$this->assertXPathNotExistsWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:TypeCode', 0);
$this->assertXPathNotExistsWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:IssuerAssignedID', 1);
$this->assertXPathNotExistsWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:FormattedIssueDateTime/a:DateTimeString', 1);
$this->assertXPathNotExistsWithIndex('/rsm:CrossIndustryInvoice/rsm:SupplyChainTradeTransaction/ram:ApplicableHeaderTradeSettlement/ram:InvoiceReferencedDocument/ram:TypeCode', 1);
}

public function testSetDocumentProcuringProject(): void
Expand Down
Loading

0 comments on commit 10520c3

Please sign in to comment.