import type { AdminApiContext } from "@shopify/shopify-app-react-router/server"; /** * Raw shape of the data we need from the Shopify Admin GraphQL API to * compose an invoice. Kept narrow so the composer is testable with fixtures. */ export interface RawOrderForInvoice { id: string; name: string; orderNumber: number; createdAt: string; processedAt: string | null; currencyCode: string; displayFinancialStatus: string | null; customer: { firstName: string | null; lastName: string | null; email: string | null; locale: string | null; } | null; billingAddress: RawAddress | null; shippingAddress: RawAddress | null; lineItems: RawLineItem[]; taxLines: RawTaxLine[]; taxesIncluded: boolean; subtotalSet: { shopMoney: RawMoney } | null; totalTaxSet: { shopMoney: RawMoney } | null; totalPriceSet: { shopMoney: RawMoney } | null; purchasingEntity: { company?: { name: string; vatId: string | null; address: RawAddress | null; } | null; } | null; } export interface RawAddress { name: string | null; company: string | null; address1: string | null; address2: string | null; zip: string | null; city: string | null; province: string | null; countryCode: string | null; } export interface RawMoney { amount: string; currencyCode: string; } export interface RawLineItem { title: string; sku: string | null; quantity: number; originalUnitPriceSet: { shopMoney: RawMoney }; taxLines: RawTaxLine[]; imageUrl: string | null; } export interface RawTaxLine { title: string | null; rate: number | null; ratePercentage: number | null; priceSet: { shopMoney: RawMoney }; } const QUERY = `#graphql query OrderForInvoice($id: ID!) { order(id: $id) { id name number createdAt processedAt currencyCode displayFinancialStatus taxesIncluded customer { firstName lastName email locale } billingAddress { name company address1 address2 zip city province countryCode: countryCodeV2 } shippingAddress { name company address1 address2 zip city province countryCode: countryCodeV2 } subtotalPriceSet { shopMoney { amount currencyCode } } totalTaxSet { shopMoney { amount currencyCode } } totalPriceSet { shopMoney { amount currencyCode } } taxLines { title rate ratePercentage priceSet { shopMoney { amount currencyCode } } } lineItems(first: 250) { edges { node { title sku quantity originalUnitPriceSet { shopMoney { amount currencyCode } } image { url altText } taxLines { title rate ratePercentage priceSet { shopMoney { amount currencyCode } } } } } } purchasingEntity { ... on PurchasingCompany { company { name } location { taxRegistrationId billingAddress { address1 address2 zip city countryCode } } } } } } `; interface RawAdminResponse { data?: { order?: { id: string; name: string; number: number; createdAt: string; processedAt: string | null; currencyCode: string; displayFinancialStatus: string | null; taxesIncluded: boolean; customer: { firstName: string | null; lastName: string | null; email: string | null; locale: string | null; } | null; billingAddress: RawAddress | null; shippingAddress: RawAddress | null; subtotalPriceSet: { shopMoney: RawMoney } | null; totalTaxSet: { shopMoney: RawMoney } | null; totalPriceSet: { shopMoney: RawMoney } | null; taxLines: RawTaxLine[]; lineItems: { edges: { node: RawLineItem }[] }; purchasingEntity: { company?: { name: string } | null; location?: { taxRegistrationId: string | null; billingAddress: RawAddress | null; } | null; } | null; } | null; }; } export async function loadOrderForInvoice( admin: AdminApiContext, orderGid: string, ): Promise { const response = await admin.graphql(QUERY, { variables: { id: orderGid } }); const json = (await response.json()) as RawAdminResponse; const order = json.data?.order; if (!order) { throw new Error(`Order ${orderGid} not found.`); } const purchasingCompany = order.purchasingEntity?.company ? { name: order.purchasingEntity.company.name, vatId: order.purchasingEntity.location?.taxRegistrationId ?? null, address: order.purchasingEntity.location?.billingAddress ?? null, } : null; return { id: order.id, name: order.name, orderNumber: order.number, createdAt: order.createdAt, processedAt: order.processedAt, currencyCode: order.currencyCode, displayFinancialStatus: order.displayFinancialStatus, taxesIncluded: order.taxesIncluded, customer: order.customer, billingAddress: order.billingAddress, shippingAddress: order.shippingAddress, subtotalSet: order.subtotalPriceSet, totalTaxSet: order.totalTaxSet, totalPriceSet: order.totalPriceSet, taxLines: order.taxLines || [], lineItems: (order.lineItems?.edges || []).map((e) => { const node = e.node as unknown as RawLineItem & { image?: { url: string | null } | null }; return { title: node.title, sku: node.sku, quantity: node.quantity, originalUnitPriceSet: node.originalUnitPriceSet, taxLines: node.taxLines, imageUrl: node.image?.url ?? null, }; }), purchasingEntity: { company: purchasingCompany }, }; }