feat(invoice): add Shopify order #, shipping address/method/cost and tracking

- Query Order.shippingLine and Order.fulfillments.trackingInfo from Admin GraphQL.
- Surface orderName (#1004) so customers recognise their order alongside the sequential invoice number.
- Render shipping cost as a synthetic line item (folds into the VAT breakdown).
- Show shipping method (Versandart / Shipping method) and tracking numbers (clickable when URL present) in the meta block.
- Render a separate delivery-address block when the shipping address differs from billing.
- DE strings stay informal (Versandart / Sendungsnummer / Lieferadresse / Versand).
This commit is contained in:
Gerhard Scheikl
2026-05-15 13:41:53 +02:00
parent 55a0dd03f2
commit 8780b4a68a
7 changed files with 345 additions and 16 deletions
@@ -23,6 +23,8 @@ export interface RawOrderForInvoice {
shippingAddress: RawAddress | null;
lineItems: RawLineItem[];
taxLines: RawTaxLine[];
shippingLine: RawShippingLine | null;
fulfillments: RawFulfillment[];
taxesIncluded: boolean;
subtotalSet: { shopMoney: RawMoney } | null;
totalTaxSet: { shopMoney: RawMoney } | null;
@@ -68,6 +70,26 @@ export interface RawTaxLine {
priceSet: { shopMoney: RawMoney };
}
export interface RawShippingLine {
title: string | null;
code: string | null;
source: string | null;
carrierIdentifier: string | null;
originalPriceSet: { shopMoney: RawMoney } | null;
discountedPriceSet: { shopMoney: RawMoney } | null;
taxLines: RawTaxLine[];
}
export interface RawTrackingInfo {
number: string | null;
url: string | null;
company: string | null;
}
export interface RawFulfillment {
trackingInfo: RawTrackingInfo[];
}
const QUERY = `#graphql
query OrderForInvoice($id: ID!) {
order(id: $id) {
@@ -115,6 +137,27 @@ const QUERY = `#graphql
ratePercentage
priceSet { shopMoney { amount currencyCode } }
}
shippingLine {
title
code
source
carrierIdentifier
originalPriceSet { shopMoney { amount currencyCode } }
discountedPriceSet { shopMoney { amount currencyCode } }
taxLines {
title
rate
ratePercentage
priceSet { shopMoney { amount currencyCode } }
}
}
fulfillments(first: 10) {
trackingInfo {
number
url
company
}
}
lineItems(first: 250) {
edges {
node {
@@ -177,6 +220,8 @@ interface RawAdminResponse {
totalTaxSet: { shopMoney: RawMoney } | null;
totalPriceSet: { shopMoney: RawMoney } | null;
taxLines: RawTaxLine[];
shippingLine: RawShippingLine | null;
fulfillments: RawFulfillment[] | null;
lineItems: { edges: { node: RawLineItem }[] };
purchasingEntity: {
company?: { name: string } | null;
@@ -225,6 +270,8 @@ export async function loadOrderForInvoice(
totalTaxSet: order.totalTaxSet,
totalPriceSet: order.totalPriceSet,
taxLines: order.taxLines || [],
shippingLine: order.shippingLine ?? null,
fulfillments: order.fulfillments ?? [],
lineItems: (order.lineItems?.edges || []).map((e) => {
const node = e.node as unknown as RawLineItem & { image?: { url: string | null } | null };
return {