From 3f6e34f1b6f10f0800008f7139055f527e071eee Mon Sep 17 00:00:00 2001 From: Matthieu Playe Date: Thu, 22 Jan 2026 17:29:52 +0100 Subject: [PATCH 1/3] Fix: make StartDate and EndDate optional per Peppol BIS 3.0 spec (BR-CO-19) --- src/SettlementPeriod.php | 83 ++++++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 24 deletions(-) diff --git a/src/SettlementPeriod.php b/src/SettlementPeriod.php index 533aa60..c9269de 100644 --- a/src/SettlementPeriod.php +++ b/src/SettlementPeriod.php @@ -57,16 +57,19 @@ public function setEndDate(DateTime $endDate) /** * The validate function that is called during xml writing to valid the data of the object. * + * Volgens Peppol BIS 3.0 spec: + * - StartDate (BT-73): 0..1 (optioneel) + * - EndDate (BT-74): 0..1 (optioneel) + * - BR-CO-19: "If Invoicing period is used, the start date or end date shall be filled, or both." + * * @throws InvalidArgumentException An error with information about required data that is missing to write the XML * @return void */ public function validate() { - if ($this->startDate === null) { - throw new InvalidArgumentException('Missing startDate'); - } - if ($this->endDate === null) { - throw new InvalidArgumentException('Missing endDate'); + // BR-CO-19: minstens startDate of endDate moet aanwezig zijn + if ($this->startDate === null && $this->endDate === null) { + throw new InvalidArgumentException('Missing startDate or endDate - at least one is required (BR-CO-19)'); } } @@ -80,34 +83,66 @@ public function xmlSerialize(Writer $writer): void { $this->validate(); - $writer->write([ - Schema::CBC . 'StartDate' => $this->startDate->format('Y-m-d'), - Schema::CBC . 'EndDate' => $this->endDate->format('Y-m-d'), - ]); - - $writer->write([ - [ - 'name' => Schema::CBC . 'DurationMeasure', - 'value' => $this->endDate->diff($this->startDate)->format('%d'), - 'attributes' => [ - 'unitCode' => 'DAY' - ] - ] - ]); + $data = []; + + // StartDate is optioneel (0..1) + if ($this->startDate !== null) { + $data[Schema::CBC . "StartDate"] = $this->startDate->format("Y-m-d"); + } + + // EndDate is optioneel (0..1) + if ($this->endDate !== null) { + $data[Schema::CBC . "EndDate"] = $this->endDate->format("Y-m-d"); + } + + $writer->write($data); + + // DurationMeasure alleen schrijven als beide datums aanwezig zijn + if ($this->startDate !== null && $this->endDate !== null) { + $writer->write([ + [ + "name" => Schema::CBC . "DurationMeasure", + "value" => $this->endDate + ->diff($this->startDate) + ->format("%d"), + "attributes" => [ + "unitCode" => "DAY", + ], + ], + ]); + } } /** * The xmlDeserialize method is called during xml reading. - * @param Reader $xml + * + * @param Reader $reader * @return static */ public static function xmlDeserialize(Reader $reader) { $keyValues = keyValue($reader); - return (new static()) - ->setStartDate(Carbon::parse($keyValues[Schema::CBC . 'StartDate'])->toDateTime()) - ->setEndDate(Carbon::parse($keyValues[Schema::CBC . 'EndDate'])->toDateTime()) - ; + $instance = new static(); + + // StartDate is optioneel (0..1) volgens Peppol BIS 3.0 spec + if (isset($keyValues[Schema::CBC . "StartDate"])) { + $instance->setStartDate( + Carbon::parse( + $keyValues[Schema::CBC . "StartDate"], + )->toDateTime(), + ); + } + + // EndDate is optioneel (0..1) volgens Peppol BIS 3.0 spec + if (isset($keyValues[Schema::CBC . "EndDate"])) { + $instance->setEndDate( + Carbon::parse( + $keyValues[Schema::CBC . "EndDate"], + )->toDateTime(), + ); + } + + return $instance; } } From 21c6b1982b0d6af1eb97060552818182d5130c79 Mon Sep 17 00:00:00 2001 From: Matthieu Playe Date: Thu, 22 Jan 2026 17:47:11 +0100 Subject: [PATCH 2/3] Fix: make StartDate and EndDate optional per Peppol BIS 3.0 spec (BR-CO-19) Both StartDate (BT-73) and EndDate (BT-74) are optional (0..1) according to the Peppol BIS Billing 3.0 specification. However, BR-CO-19 requires that at least one of them must be present when an invoicing period is used. This fixes parsing of invoices that only have StartDate without EndDate. Refs: - https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/cac-InvoicePeriod/cbc-StartDate/ - https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/cac-InvoicePeriod/cbc-EndDate/ --- src/SettlementPeriod.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/SettlementPeriod.php b/src/SettlementPeriod.php index c9269de..9cae4d8 100644 --- a/src/SettlementPeriod.php +++ b/src/SettlementPeriod.php @@ -13,6 +13,17 @@ use Sabre\Xml\XmlDeserializable; use Sabre\Xml\XmlSerializable; +/** + * Represents the invoicing period (cac:InvoicePeriod / cac:SettlementPeriod). + * + * According to Peppol BIS Billing 3.0 specification: + * - StartDate (BT-73): 0..1 - Optional + * - EndDate (BT-74): 0..1 - Optional + * - BR-CO-19: At least one of StartDate or EndDate must be present + * + * @see https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/cac-InvoicePeriod/cbc-StartDate/ + * @see https://docs.peppol.eu/poacc/billing/3.0/syntax/ubl-invoice/cac-InvoicePeriod/cbc-EndDate/ + */ class SettlementPeriod implements XmlSerializable, XmlDeserializable { private $startDate; From a1acdfdab7df7764c0e3c344e7af207dc34dafc4 Mon Sep 17 00:00:00 2001 From: Matthieu Playe Date: Thu, 22 Jan 2026 17:48:45 +0100 Subject: [PATCH 3/3] Translate comments from Dutch to English in SettlementPeriod --- src/SettlementPeriod.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/SettlementPeriod.php b/src/SettlementPeriod.php index 9cae4d8..d123fd5 100644 --- a/src/SettlementPeriod.php +++ b/src/SettlementPeriod.php @@ -68,9 +68,9 @@ public function setEndDate(DateTime $endDate) /** * The validate function that is called during xml writing to valid the data of the object. * - * Volgens Peppol BIS 3.0 spec: - * - StartDate (BT-73): 0..1 (optioneel) - * - EndDate (BT-74): 0..1 (optioneel) + * According to Peppol BIS 3.0 spec: + * - StartDate (BT-73): 0..1 (optional) + * - EndDate (BT-74): 0..1 (optional) * - BR-CO-19: "If Invoicing period is used, the start date or end date shall be filled, or both." * * @throws InvalidArgumentException An error with information about required data that is missing to write the XML @@ -78,7 +78,7 @@ public function setEndDate(DateTime $endDate) */ public function validate() { - // BR-CO-19: minstens startDate of endDate moet aanwezig zijn + // BR-CO-19: at least startDate or endDate must be present if ($this->startDate === null && $this->endDate === null) { throw new InvalidArgumentException('Missing startDate or endDate - at least one is required (BR-CO-19)'); } @@ -96,19 +96,19 @@ public function xmlSerialize(Writer $writer): void $data = []; - // StartDate is optioneel (0..1) + // StartDate is optional (0..1) if ($this->startDate !== null) { $data[Schema::CBC . "StartDate"] = $this->startDate->format("Y-m-d"); } - // EndDate is optioneel (0..1) + // EndDate is optional (0..1) if ($this->endDate !== null) { $data[Schema::CBC . "EndDate"] = $this->endDate->format("Y-m-d"); } $writer->write($data); - // DurationMeasure alleen schrijven als beide datums aanwezig zijn + // Only write DurationMeasure when both dates are present if ($this->startDate !== null && $this->endDate !== null) { $writer->write([ [ @@ -136,7 +136,7 @@ public static function xmlDeserialize(Reader $reader) $instance = new static(); - // StartDate is optioneel (0..1) volgens Peppol BIS 3.0 spec + // StartDate is optional (0..1) per Peppol BIS 3.0 spec if (isset($keyValues[Schema::CBC . "StartDate"])) { $instance->setStartDate( Carbon::parse( @@ -145,7 +145,7 @@ public static function xmlDeserialize(Reader $reader) ); } - // EndDate is optioneel (0..1) volgens Peppol BIS 3.0 spec + // EndDate is optional (0..1) per Peppol BIS 3.0 spec if (isset($keyValues[Schema::CBC . "EndDate"])) { $instance->setEndDate( Carbon::parse(