Skip to content
Merged
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
94 changes: 70 additions & 24 deletions src/SettlementPeriod.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -57,16 +68,19 @@ public function setEndDate(DateTime $endDate)
/**
* The validate function that is called during xml writing to valid the data of the object.
*
* 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
* @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: 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)');
}
}

Expand All @@ -80,34 +94,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 optional (0..1)
if ($this->startDate !== null) {
$data[Schema::CBC . "StartDate"] = $this->startDate->format("Y-m-d");
}

// EndDate is optional (0..1)
if ($this->endDate !== null) {
$data[Schema::CBC . "EndDate"] = $this->endDate->format("Y-m-d");
}

$writer->write($data);

// Only write DurationMeasure when both dates are present
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 optional (0..1) per Peppol BIS 3.0 spec
if (isset($keyValues[Schema::CBC . "StartDate"])) {
$instance->setStartDate(
Carbon::parse(
$keyValues[Schema::CBC . "StartDate"],
)->toDateTime(),
);
}

// EndDate is optional (0..1) per Peppol BIS 3.0 spec
if (isset($keyValues[Schema::CBC . "EndDate"])) {
$instance->setEndDate(
Carbon::parse(
$keyValues[Schema::CBC . "EndDate"],
)->toDateTime(),
);
}

return $instance;
}
}