Add optional per-line SKU + shipping recipient company/contact fields (v0.2.7)
Validate e-invoices / mustang (push) Successful in 43s

Invoice PDFs (visual + EN16931 XML) and correction invoices now show an
"Art.-Nr." line when an order item has a SKU snapshot, and the
Lieferadresse block includes company name/contact email/phone when set.
This commit is contained in:
Marco
2026-07-30 08:18:58 +00:00
parent d6e83db5cf
commit e66007cc4a
4 changed files with 31 additions and 2 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@einfach-produktiv/invoicing",
"version": "0.2.6",
"version": "0.2.7",
"private": true,
"description": "Shared invoice / correction-invoice (Stornorechnung, Gutschrift) PDF generation and VAT-breakdown math, consumed as a git dependency by both the einfach-produktiv frontend and the payload backend — not published to npm.",
"type": "module",
+2
View File
@@ -132,6 +132,7 @@ export type CorrectionInvoiceItem = {
variantName?: string | null;
returnQuantity?: number;
imageUrl?: string | null;
sku?: string | null;
};
export type CorrectionInvoiceOrder = {
@@ -303,6 +304,7 @@ function CorrectionInvoiceDocument({ kind, order, seller }: { kind: CorrectionIn
{item.variantName ? ` (${item.variantName})` : ""}
</Text>
{item.bundleContents ? <Text style={styles.bundleLine}>{item.bundleContents}</Text> : null}
{item.sku ? <Text style={styles.bundleLine}>Art.-Nr. {item.sku}</Text> : null}
</View>
<Text style={styles.colQty}>{effectiveQuantity}</Text>
<Text style={styles.colPrice}>{formatPrice(item.unitPrice)}</Text>
+13 -1
View File
@@ -220,7 +220,18 @@ function buyerParty(name: string, street: string | null | undefined, zip: string
};
}
type LineInput = { productName: string; variantName?: string | null; quantity: number; unitPrice: number; taxRatePercent: number };
type LineInput = {
productName: string;
variantName?: string | null;
quantity: number;
unitPrice: number;
taxRatePercent: number;
// BT-155 (Item seller's identifier) when present — variant SKU if one
// was selected, else the product's own (see Orders.items.sku in the
// Payload backend). Omitted entirely when neither is set, same as the
// visual PDF's own optional "Art.-Nr." line.
sku?: string | null;
};
type ComputedLine = { item: LineInput; netCents: number };
type RateGroup = { rate: number; lineNetCents: number; allowanceCents: number; chargeCents: number; taxableCents: number; taxCents: number };
@@ -337,6 +348,7 @@ function invoiceLines(lines: ComputedLine[], vatMode: VatMode): UblInvoice["cac:
"cac:Item": {
"cbc:Name": item.variantName ? `${item.productName} (${item.variantName})` : item.productName,
"cac:ClassifiedTaxCategory": vatTaxCategory(item.taxRatePercent, vatMode),
...(item.sku ? { "cac:SellersItemIdentification": { "cbc:ID": item.sku } } : {}),
},
// Unit price, not a summed/reconciled total — kept as the plain
// (unrounded-to-cent) net unit price for reference; BR-CO-10 only
+15
View File
@@ -160,6 +160,11 @@ export type InvoiceItem = {
bundleContents?: string | null;
variantName?: string | null;
imageUrl?: string | null;
// Snapshot from the order (Orders.items.sku in the Payload backend) —
// variant SKU if one was selected, else the product's own, else absent.
// Shown next to productName only when actually set; a product/variant
// with no SKU configured renders exactly as before this field existed.
sku?: string | null;
};
export type InvoiceOrder = {
@@ -206,6 +211,10 @@ export type InvoiceOrder = {
hasDifferentShippingAddress?: boolean;
shippingFirstName?: string | null;
shippingLastName?: string | null;
// Optional, mirrors companyName above — no shipping-side vatId (billing-
// only concept). Contact email/phone are handed to the shipping carrier,
// shown here for the customer's own reference on their invoice copy.
shippingCompanyName?: string | null;
shippingDeliveryMethod?: "address" | "packstation" | null;
shippingStreet?: string | null;
shippingPackstationNumber?: string | null;
@@ -213,6 +222,8 @@ export type InvoiceOrder = {
shippingZip?: string | null;
shippingCity?: string | null;
shippingCountry?: string | null;
shippingContactEmail?: string | null;
shippingContactPhone?: string | null;
paymentMethodTitle: string;
items: InvoiceItem[];
subtotal: number;
@@ -345,6 +356,7 @@ export function InvoiceDocument({ order, seller }: { order: InvoiceOrder; seller
{order.hasDifferentShippingAddress && (
<View style={addressBlockStyle}>
<Text style={styles.addressLabel}>Lieferadresse</Text>
{order.shippingCompanyName && <Text style={styles.addressLine}>{order.shippingCompanyName}</Text>}
<Text style={styles.addressLine}>
{order.shippingFirstName} {order.shippingLastName}
</Text>
@@ -358,6 +370,8 @@ export function InvoiceDocument({ order, seller }: { order: InvoiceOrder; seller
{order.shippingZip} {order.shippingCity}
</Text>
<Text style={styles.addressLine}>{order.shippingCountry}</Text>
{order.shippingContactEmail && <Text style={styles.addressLine}>{order.shippingContactEmail}</Text>}
{order.shippingContactPhone && <Text style={styles.addressLine}>{order.shippingContactPhone}</Text>}
</View>
)}
</View>
@@ -396,6 +410,7 @@ export function InvoiceDocument({ order, seller }: { order: InvoiceOrder; seller
{item.variantName ? ` (${item.variantName})` : ""}
</Text>
{item.bundleContents ? <Text style={styles.bundleLine}>{item.bundleContents}</Text> : null}
{item.sku ? <Text style={styles.bundleLine}>Art.-Nr. {item.sku}</Text> : null}
</View>
<Text style={styles.colQty}>{item.quantity}</Text>
<Text style={styles.colPrice}>{formatPrice(item.unitPrice)}</Text>