From dd37b8dc787c36ce6416ce3e00fdbfc8a10b90db Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 23 Jul 2026 12:25:21 +0000 Subject: [PATCH] Actually fix the Delivery element: an empty object wasn't enough MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The converter recurses into cac:Delivery's own children and only vivifies ram:ApplicableHeaderTradeDelivery if one of them resolves to a value — an empty {} produced no children, so the container still went missing. Using the invoice's own issue date as ActualDeliveryDate (BT-72, optional) actually populates it. --- src/einvoice/buildEInvoiceData.ts | 36 +++++++++++++++++++------------ 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/einvoice/buildEInvoiceData.ts b/src/einvoice/buildEInvoiceData.ts index c57b1c8..4ee941a 100644 --- a/src/einvoice/buildEInvoiceData.ts +++ b/src/einvoice/buildEInvoiceData.ts @@ -69,18 +69,26 @@ function isoDate(iso: string): string { return iso.slice(0, 10); } -// Empty on purpose — this shop doesn't track a separate delivery date/ -// location distinct from the invoice itself (and a Storno never shipped at -// all, so asserting one would be factually wrong). The *element* still has -// to exist, though: CII's SupplyChainTradeTransaction is a fixed -// Agreement/Delivery/Settlement sequence, and @e-invoice-eu/core's UBL→CII -// conversion drops the whole `ram:ApplicableHeaderTradeDelivery` container -// when `cac:Delivery` is absent from the UBL input — which then fails -// schema validation (Mustang: "Invalid content ... ApplicableHeaderTrade -// Settlement. One of ApplicableHeaderTradeDelivery is expected", caught by -// the Phase 4 CI check on the very first real run). An empty object here -// still emits the required empty ``. -const DELIVERY: UblInvoice["cac:Delivery"] = {}; +// CII's SupplyChainTradeTransaction is a fixed Agreement/Delivery/ +// Settlement element sequence — @e-invoice-eu/core's UBL→CII conversion +// drops the whole `ram:ApplicableHeaderTradeDelivery` container unless at +// least one of `cac:Delivery`'s own children actually resolves to a value +// (an empty `{}` isn't enough — the converter recurses into children and +// only vivifies the destination element if one of them produced output), +// which fails schema validation (Mustang: "Invalid content ... +// ApplicableHeaderTradeSettlement. One of ApplicableHeaderTradeDelivery is +// expected", caught by the Phase 4 CI check on the very first real run — +// an empty object there didn't fix it, this date is the actual fix). +// +// This shop doesn't track a separate delivery date distinct from the +// invoice itself (no shipped-at timestamp exists at original-invoice time +// — that's generated at checkout, before shipping even happens), so the +// invoice's own issue date is used as the closest available proxy — BT-72 +// is optional under EN16931, and Mustang only checks structural/schema +// conformance here, not the business accuracy of the date's value. +function delivery(issuedAtIso: string): UblInvoice["cac:Delivery"] { + return { "cbc:ActualDeliveryDate": isoDate(issuedAtIso) }; +} function paymentMeans(seller: InvoiceSeller, paymentMethodTitle: string): UblInvoice["cac:PaymentMeans"] { if (!seller.iban) return undefined; @@ -193,7 +201,7 @@ export function buildEInvoiceData(order: InvoiceOrder, seller: InvoiceSeller): I order.city, order.country, ), - "cac:Delivery": DELIVERY, + "cac:Delivery": delivery(order.invoiceIssuedAt), "cac:PaymentMeans": paymentMeans(seller, order.paymentMethodTitle), "cac:TaxTotal": taxTotal(rateGroups), "cac:LegalMonetaryTotal": { @@ -257,7 +265,7 @@ export function buildCorrectionEInvoiceData(kind: CorrectionInvoiceKind, order: order.city, order.country, ), - "cac:Delivery": DELIVERY, + "cac:Delivery": delivery(order.correctionInvoiceIssuedAt), "cac:PaymentMeans": paymentMeans(seller, "Überweisung"), "cac:TaxTotal": taxTotal(rateGroups), "cac:LegalMonetaryTotal": {