import type { AdminApiContext } from "@shopify/shopify-app-react-router/server"; import type { RawAddress, RawLineItem, RawMoney, RawOrderForInvoice, RawTaxLine, } from "./loadOrderForInvoice.server"; /** * Loads a Shopify DraftOrder and adapts it to the same `RawOrderForInvoice` * shape used for completed orders, so the rest of the pipeline (composer, * PDF, etc.) doesn't need to know whether it's rendering an invoice or an * offer. * * Drafts have no `processedAt` (we use createdAt) and no * `displayFinancialStatus` (we treat them as not paid). */ const QUERY = `#graphql query DraftOrderForOffer($id: ID!) { draftOrder(id: $id) { id name createdAt currencyCode 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?: { draftOrder?: { id: string; name: string; createdAt: string; currencyCode: string; 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 & { image?: { url: string | null } | null } }[] }; purchasingEntity: { company?: { name: string } | null; location?: { taxRegistrationId: string | null; billingAddress: RawAddress | null; } | null; } | null; } | null; }; } export async function loadDraftOrderForOffer( admin: AdminApiContext, draftOrderGid: string, ): Promise { const response = await admin.graphql(QUERY, { variables: { id: draftOrderGid } }); const json = (await response.json()) as RawAdminResponse; const draft = json.data?.draftOrder; if (!draft) { throw new Error(`Draft order ${draftOrderGid} not found.`); } const purchasingCompany = draft.purchasingEntity?.company ? { name: draft.purchasingEntity.company.name, vatId: draft.purchasingEntity.location?.taxRegistrationId ?? null, address: draft.purchasingEntity.location?.billingAddress ?? null, } : null; // Drafts don't have a numeric "order number" — use a hash of the GID as a // numeric proxy for the invoice-counter signature (not actually used when // generating offers, but kept non-zero to satisfy downstream code). const orderNumber = parseInt(draft.id.replace(/[^0-9]/g, "").slice(-9), 10) || 0; return { id: draft.id, name: draft.name, orderNumber, createdAt: draft.createdAt, processedAt: null, currencyCode: draft.currencyCode, displayFinancialStatus: null, paymentGatewayNames: [], requiresShipping: false, shippingLine: null, fulfillments: [], discountCodes: [], taxesIncluded: draft.taxesIncluded, customer: draft.customer, billingAddress: draft.billingAddress, shippingAddress: draft.shippingAddress, subtotalSet: draft.subtotalPriceSet, totalTaxSet: draft.totalTaxSet, totalPriceSet: draft.totalPriceSet, taxLines: draft.taxLines || [], lineItems: (draft.lineItems?.edges || []).map((e) => { const node = e.node; return { title: node.title, sku: node.sku, quantity: node.quantity, originalUnitPriceSet: node.originalUnitPriceSet, discountedUnitPriceSet: null, taxLines: node.taxLines, imageUrl: node.image?.url ?? null, }; }), purchasingEntity: { company: purchasingCompany }, }; }