From 582c0ba1b90d0d001fac9e5a7c440f853fb5b2f9 Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 23 Sep 2026 13:37:32 +0200 Subject: [PATCH] fix(orders): keep stock-PO button visible after per-row in-stock toggle The per-row in-stock checkbox flips item_status to ready_invoice (legacy behaviour, no PO created by that path). The previous markInStockReady filter required item_status in [pending, processed] and the render used that same filter to gate the button count. Result: the moment a line was ticked in-stock via the checkbox, it dropped out of that filter and the {supplier} stock PO button disappeared, leaving the operator no way to fire the paper-trail PO from the same page. Loosen both filters to in_stock=true AND purchase_order is empty, regardless of item_status. The bottom button now stays clickable after a per-row tick, and re-clicking is a no-op (the purchase_order guard skips already-linked lines). Lifecycle for the item is unchanged: ready_invoice, qty_collected = quantity, no driver collection. The PO is the only thing that gets created when this button fires. --- src/app/(app)/orders/[id]/process/page.tsx | 38 ++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/app/(app)/orders/[id]/process/page.tsx b/src/app/(app)/orders/[id]/process/page.tsx index d89a934..c72ed48 100644 --- a/src/app/(app)/orders/[id]/process/page.tsx +++ b/src/app/(app)/orders/[id]/process/page.tsx @@ -1441,14 +1441,26 @@ export default function ProcessOrderPage({ params }: { params: Promise<{ id: str const markInStockReady = async (supplierId: string) => { const supplier = suppliers.find((s) => s.id === supplierId); if (!supplier) return; + // Filter is loose on purpose: the per-row in-stock checkbox + // flips `item_status` to `ready_invoice` (legacy behaviour, no + // PO created by that path) which used to push these lines out + // of this filter and hide the bottom button. We want to issue + // the paper-trail PO from this button for ANY in-stock line + // that doesn't already have one, regardless of item_status, + // so the operator can tick the checkbox and then fire the PO + // from the same page without losing access to the action. + // + // The `purchase_order` guard skips lines already linked to a + // PO — a re-click must not duplicate. const inStock = items.filter( (it) => (it.supplier || "") === supplierId && - ["pending", "processed"].includes(it.item_status) && - it.in_stock, + it.in_stock && + (typeof it.purchase_order !== "string" || + it.purchase_order === ""), ); if (inStock.length === 0) { - toast.error("No in-stock items for this supplier"); + toast.error("No in-stock items need a PO"); return; } @@ -2086,12 +2098,26 @@ export default function ProcessOrderPage({ params }: { params: Promise<{ id: str const editable = list.filter((it) => ["pending", "processed"].includes(it.item_status), ); - if (editable.length === 0) return null; + // Lines that still need a stock-replenishment PO: + // any `in_stock=true` line without one yet, regardless + // of item_status. The per-row in-stock checkbox flips + // a line to `ready_invoice` (without creating a PO), + // which would otherwise push it out of `editable` and + // hide this button — yet the operator still needs to + // fire the paper-trail PO for it. + const needsStockPo = list.filter( + (it) => + it.in_stock && + (typeof it.purchase_order !== "string" || + it.purchase_order === ""), + ); + if (editable.length === 0 && needsStockPo.length === 0) { + return null; + } const nonInStockCount = editable.filter( (it) => !it.in_stock, ).length; - const inStockCount = editable.filter((it) => it.in_stock) - .length; + const inStockCount = needsStockPo.length; const isBusy = sendingSupplier === supplierId || markingInStockSupplier === supplierId;