Skip to content

Commit b5fe48b

Browse files
Integrate inventory tracking
Enhanced Shopify sync by adding inventory queries, location retrieval, and inventorySetQuantities mutation. Wired setInventoryQuantities into the sync flow to update variant quantities at the primary location, ensuring accurate stock levels on Shopify. Includes helpers to fetch location ID, map inventory items to CSV SKUs, and handle errors with logging. X-Lovable-Edit-ID: edt-4160f644-b185-44f7-b1aa-c3b06fe3cda9
2 parents c8e8e30 + 23b3ec3 commit b5fe48b

1 file changed

Lines changed: 105 additions & 0 deletions

File tree

scripts/sync-shopify-catalog.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,103 @@ const VARIANT_BULK_UPDATE = `
325325
}
326326
`;
327327

328+
// ---------------------------------------------------------------------------
329+
// Inventory queries & mutations
330+
// ---------------------------------------------------------------------------
331+
332+
const LOCATIONS_QUERY = `
333+
query GetLocations {
334+
locations(first: 1) {
335+
edges { node { id name } }
336+
}
337+
}
338+
`;
339+
340+
const VARIANT_INVENTORY_ITEMS = `
341+
query VariantInventoryItems($productId: ID!) {
342+
product(id: $productId) {
343+
variants(first: 100) {
344+
edges {
345+
node {
346+
id
347+
sku
348+
inventoryItem { id }
349+
}
350+
}
351+
}
352+
}
353+
}
354+
`;
355+
356+
const INVENTORY_SET_QUANTITIES = `
357+
mutation InventorySetQuantities($input: InventorySetQuantitiesInput!) {
358+
inventorySetQuantities(input: $input) {
359+
inventoryAdjustmentGroup { reason }
360+
userErrors { field message }
361+
}
362+
}
363+
`;
364+
365+
let cachedLocationId: string | null = null;
366+
367+
async function getPrimaryLocationId(): Promise<string> {
368+
if (cachedLocationId) return cachedLocationId;
369+
const data = await adminGraphQL(LOCATIONS_QUERY);
370+
const loc = data?.locations?.edges?.[0]?.node;
371+
if (!loc) throw new Error("No Shopify locations found. Create a location first.");
372+
cachedLocationId = loc.id;
373+
console.log(` 📍 Primary location: ${loc.name} (${loc.id})`);
374+
return loc.id;
375+
}
376+
377+
async function setInventoryQuantities(
378+
productId: string,
379+
variants: { sku: string; inventoryQty: number }[],
380+
tag: string
381+
) {
382+
const locationId = await getPrimaryLocationId();
383+
384+
// Fetch inventoryItemIds for the product's variants
385+
const data = await adminGraphQL(VARIANT_INVENTORY_ITEMS, { productId });
386+
const variantEdges = data?.product?.variants?.edges || [];
387+
388+
const quantities: { inventoryItemId: string; locationId: string; quantity: number }[] = [];
389+
390+
for (const edge of variantEdges) {
391+
const node = edge.node;
392+
const inventoryItemId = node.inventoryItem?.id;
393+
if (!inventoryItemId) continue;
394+
395+
// Match by SKU or by position
396+
const csvMatch = variants.find((v) => v.sku && v.sku === node.sku);
397+
const qty = csvMatch?.inventoryQty ?? variants[variantEdges.indexOf(edge)]?.inventoryQty;
398+
399+
if (qty !== undefined && qty >= 0) {
400+
quantities.push({ inventoryItemId, locationId, quantity: qty });
401+
}
402+
}
403+
404+
if (quantities.length === 0) return;
405+
406+
try {
407+
const result = await adminGraphQL(INVENTORY_SET_QUANTITIES, {
408+
input: {
409+
name: "available",
410+
reason: "correction",
411+
quantities,
412+
},
413+
});
414+
const errors = result?.inventorySetQuantities?.userErrors;
415+
if (errors?.length) {
416+
console.warn(` ⚠️ ${tag} inventory warnings:`, errors);
417+
} else {
418+
console.log(` 📦 ${tag}: set inventory for ${quantities.length} variant(s)`);
419+
}
420+
} catch (invErr: any) {
421+
console.warn(` ⚠️ ${tag} inventory update failed: ${invErr.message}`);
422+
}
423+
}
424+
328425
// ---------------------------------------------------------------------------
329426
// Sync logic
330427
// ---------------------------------------------------------------------------
@@ -454,6 +551,14 @@ async function syncProduct(product: ProductGroup, index: number) {
454551
}
455552
}
456553
}
554+
// 3. Set inventory quantities at the primary location
555+
if (variantEdges?.length > 0 && product.variants.some((v) => v.inventoryQty > 0)) {
556+
await setInventoryQuantities(
557+
productId,
558+
product.variants.map((v) => ({ sku: v.sku, inventoryQty: v.inventoryQty })),
559+
tag
560+
);
561+
}
457562

458563
return { status: existing ? "updated" : "created", handle: product.handle };
459564
}

0 commit comments

Comments
 (0)