feat(invoice): per-line + cart discounts, fulfillment delivery date, pickup label, header layout refresh
- discounts: read discountedUnitPriceSet (per-line) and discountCode/discountCodes (order-level) from Shopify; render discounted unit price with strikethrough original on the invoice line and add a 'Rabattcode'/'Discount code' meta row when codes were used. - delivery date: pick the latest fulfillment.createdAt for §11 UStG instead of hard-coding processedAt; fall back to invoice date when unfulfilled. - pickup: detect Shopify Local Pickup (and 'Abholung'/'Pickup' custom rates) via shippingLine.source/code/title; suppress the pickup-location 'shipping address' block and render localized 'Abholung'/'Pick-up' as the shipping method. - layout: move the company logo to the top-left and the meta block to the top-right, putting recipient (and any separate delivery address) on its own row below; drop the standalone invoice-/order-number meta rows and surface them inside the title (e.g. 'Rechnung Nr. RE-1004 · Bestellnummer: #1004') to recover vertical space. - tests: smoke fixtures cover discount, pickup, and fulfillment-date variants without disturbing the AT B2B totals.
This commit is contained in:
@@ -25,6 +25,10 @@ export interface RawOrderForInvoice {
|
||||
taxLines: RawTaxLine[];
|
||||
shippingLine: RawShippingLine | null;
|
||||
fulfillments: RawFulfillment[];
|
||||
/** Discount codes applied to the cart (e.g. `["SUMMER10"]`). Empty when
|
||||
* no codes were used. Manual / automatic discounts without a code are
|
||||
* not exposed here. */
|
||||
discountCodes: string[];
|
||||
taxesIncluded: boolean;
|
||||
subtotalSet: { shopMoney: RawMoney } | null;
|
||||
totalTaxSet: { shopMoney: RawMoney } | null;
|
||||
@@ -59,6 +63,10 @@ export interface RawLineItem {
|
||||
sku: string | null;
|
||||
quantity: number;
|
||||
originalUnitPriceSet: { shopMoney: RawMoney };
|
||||
/** Per-unit price after Shopify has allocated cart-level discounts to this
|
||||
* line. May be null when no discount applied (in which case use the
|
||||
* original price). */
|
||||
discountedUnitPriceSet: { shopMoney: RawMoney } | null;
|
||||
taxLines: RawTaxLine[];
|
||||
imageUrl: string | null;
|
||||
}
|
||||
@@ -87,6 +95,10 @@ export interface RawTrackingInfo {
|
||||
}
|
||||
|
||||
export interface RawFulfillment {
|
||||
/** ISO timestamp of when the fulfillment was created (i.e. when the goods
|
||||
* were dispatched / handed over). Used for the legally-required delivery
|
||||
* date on the invoice when present. */
|
||||
createdAt: string | null;
|
||||
trackingInfo: RawTrackingInfo[];
|
||||
}
|
||||
|
||||
@@ -137,6 +149,8 @@ const QUERY = `#graphql
|
||||
ratePercentage
|
||||
priceSet { shopMoney { amount currencyCode } }
|
||||
}
|
||||
discountCode
|
||||
discountCodes
|
||||
shippingLine {
|
||||
title
|
||||
code
|
||||
@@ -152,6 +166,7 @@ const QUERY = `#graphql
|
||||
}
|
||||
}
|
||||
fulfillments(first: 10) {
|
||||
createdAt
|
||||
trackingInfo {
|
||||
number
|
||||
url
|
||||
@@ -165,6 +180,7 @@ const QUERY = `#graphql
|
||||
sku
|
||||
quantity
|
||||
originalUnitPriceSet { shopMoney { amount currencyCode } }
|
||||
discountedUnitPriceSet { shopMoney { amount currencyCode } }
|
||||
image { url altText }
|
||||
taxLines {
|
||||
title
|
||||
@@ -220,6 +236,8 @@ interface RawAdminResponse {
|
||||
totalTaxSet: { shopMoney: RawMoney } | null;
|
||||
totalPriceSet: { shopMoney: RawMoney } | null;
|
||||
taxLines: RawTaxLine[];
|
||||
discountCode: string | null;
|
||||
discountCodes: string[] | null;
|
||||
shippingLine: RawShippingLine | null;
|
||||
fulfillments: RawFulfillment[] | null;
|
||||
lineItems: { edges: { node: RawLineItem }[] };
|
||||
@@ -270,6 +288,9 @@ export async function loadOrderForInvoice(
|
||||
totalTaxSet: order.totalTaxSet,
|
||||
totalPriceSet: order.totalPriceSet,
|
||||
taxLines: order.taxLines || [],
|
||||
discountCodes: order.discountCodes && order.discountCodes.length > 0
|
||||
? order.discountCodes
|
||||
: (order.discountCode ? [order.discountCode] : []),
|
||||
shippingLine: order.shippingLine ?? null,
|
||||
fulfillments: order.fulfillments ?? [],
|
||||
lineItems: (order.lineItems?.edges || []).map((e) => {
|
||||
@@ -279,6 +300,7 @@ export async function loadOrderForInvoice(
|
||||
sku: node.sku,
|
||||
quantity: node.quantity,
|
||||
originalUnitPriceSet: node.originalUnitPriceSet,
|
||||
discountedUnitPriceSet: node.discountedUnitPriceSet ?? null,
|
||||
taxLines: node.taxLines,
|
||||
imageUrl: node.image?.url ?? null,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user