fix(invoice): drop fulfillmentOrders query (scope-denied), keep shippingLine pickup heuristic

Querying Order.fulfillmentOrders.deliveryMethod requires the
read_merchant_managed_fulfillment_orders scope (not read_orders, despite
what shopify.dev claims) and was failing with 'Access denied for
fulfillmentOrders field' against real stores.

Adding that scope would force every install to re-grant permissions, so
instead we rely on shippingLine alone:

- Shopify Local Pickup app: shippingLine.code = 'Pickup' (caught by the
  regex) and shippingLine.title is the chosen location name itself   (e.g. 'Lager Graz') \u2014 perfect as pickupLocationName.
- Custom-rate pickup ('Abholung im Lager'): regex matches title/code,
  title is used as the location hint.

Removes RawDeliveryMethod, the deliveryMethods field on RawOrderForInvoice,
and the fulfillmentOrders edges from RawAdminResponse.
This commit is contained in:
Gerhard Scheikl
2026-05-15 15:04:16 +02:00
parent d742e75419
commit f16ef4e103
5 changed files with 209 additions and 78 deletions
+5 -21
View File
@@ -137,7 +137,6 @@ function buildAtB2BOrder(): RawOrderForInvoice {
paymentGatewayNames: ["manual"],
taxesIncluded: false,
discountCodes: [],
deliveryMethods: [],
customer: {
firstName: "Lukas",
lastName: "Schmidhofer",
@@ -299,18 +298,14 @@ function buildDiscountedOrder(): RawOrderForInvoice {
function buildPickupOrder(): RawOrderForInvoice {
const o = buildAtB2BOrder();
o.shippingLine = {
title: "Local Pickup — Lager Graz",
code: "PICKUP",
source: "shopify-local-pickup",
title: "Lager Graz",
code: "Pickup",
source: "shopify",
carrierIdentifier: null,
originalPriceSet: { shopMoney: { amount: "0.00", currencyCode: "EUR" } },
discountedPriceSet: { shopMoney: { amount: "0.00", currencyCode: "EUR" } },
taxLines: [],
};
// Primary signal — what Shopify Local Pickup actually populates.
o.deliveryMethods = [
{ methodType: "PICK_UP", locationName: "Lager Graz" },
];
return o;
}
@@ -598,8 +593,8 @@ async function main() {
settings: settings as never,
invoiceNumber: "RE-1030",
});
assert("isPickup detected via DeliveryMethodType=PICK_UP", pickupVm.isPickup);
assertEq("pickupLocationName propagated", pickupVm.pickupLocationName, "Lager Graz");
assert("isPickup detected via shippingLine heuristic", pickupVm.isPickup);
assertEq("pickupLocationName propagated from shippingLine.title", pickupVm.pickupLocationName, "Lager Graz");
assert("shippingMethod cleared for pickup (renderer uses pickup row instead)",
pickupVm.shippingMethod == null);
assert(
@@ -616,17 +611,6 @@ async function main() {
!pickupText.includes("Lieferadresse"),
);
// Shopify-Local-Pickup-app fallback: methodType missing but shippingLine
// is named "Pickup at …".
const legacyPickupOrder = buildPickupOrder();
legacyPickupOrder.deliveryMethods = [];
const legacyPickupVm = composeInvoice({
order: legacyPickupOrder,
settings: settings as never,
invoiceNumber: "RE-1032",
});
assert("legacy heuristic still detects pickup from shippingLine", legacyPickupVm.isPickup);
// EN translation
const pickupEnVm = composeInvoice({
order: pickupOrder,