From 2682eeb360755fa68defb75dfe50e86b4f20aec8 Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 23 Oct 2026 12:08:03 +0200 Subject: [PATCH] =?UTF-8?q?fix(po):=20store=20the=20price=20from=20the=20f?= =?UTF-8?q?orm=20so=20POs=20stop=20printing=20=C2=A30.00?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every purchase order created from a stock item printed a £0.00 unit price, even though a cost price was on the record and the form showed the right number. Cause: the PO snapshot took costPriceGBP from the stock item's stored cost_price_gbp instead of from the form. The price the user sees (and types) into the "Unit Price (£ GBP)" field landed in snapshot.costPrice, which nothing renders, while the PO PDF renders snapshot.costPriceGBP (pdf.ts: `£${item.costPriceGBP}`). So a price typed into the form was discarded and the stock item's cached value was printed instead. Because the stock item's cost_price_gbp was only written back to the database AFTER the PO was created, the first PO of an item always showed £0.00 and only the next one showed the price. Verified in production: PO-20261023-002 stored costPrice 333.78 / costPriceGBP 0 and printed £0.00, while the linked stock item's cost_price_gbp was 0 at the time and was updated to 333.78 by the sync step three minutes later. Fixes: - PurchaseOrderForm: snapshot.costPriceGBP is now the price from the form; snapshot.costPrice is the stock item's ZAR landed cost. - PurchaseOrderForm: selecting a stock item with no GBP cost price leaves the price field blank instead of showing £0.00, with a hint that the supplier's GBP price still has to be entered. Most stock items only carry a ZAR cost price, so £0.00 looked like a real price. - EditPurchaseOrderDialog: preserve the ZAR cost price per row instead of zeroing it when a PO is edited. --- .../EditPurchaseOrderDialog.tsx | 9 +++++-- .../purchase-orders/PurchaseOrderForm.tsx | 24 +++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/components/purchase-orders/EditPurchaseOrderDialog.tsx b/src/components/purchase-orders/EditPurchaseOrderDialog.tsx index 2eb8276..f3098a4 100644 --- a/src/components/purchase-orders/EditPurchaseOrderDialog.tsx +++ b/src/components/purchase-orders/EditPurchaseOrderDialog.tsx @@ -134,13 +134,18 @@ export function EditPurchaseOrderDialog({ open, onOpenChange, order, refreshData })) // Build updated snapshot — keep stable ids + entry mode so the audit - // trail remains meaningful. + // trail remains meaningful. costPrice is the ZAR landed cost, so carry + // the existing value over per row instead of zeroing it; costPriceGBP is + // the price the user sees in the form and is what the PO PDF renders. + const existingSnapshot = order.snapshotItems || [] const updatedSnapshot = normalizedRows.map((r) => ({ stockItemId: r.stockItemId, itemName: r.itemName, sku: r.sku, quantity: r.quantity, - costPrice: 0, + costPrice: Number( + existingSnapshot.find((s) => s.stockItemId === r.stockItemId)?.costPrice || 0, + ), costPriceGBP: r.unitPriceGBP, entryMode: r.entryMode, })) diff --git a/src/components/purchase-orders/PurchaseOrderForm.tsx b/src/components/purchase-orders/PurchaseOrderForm.tsx index acd23cc..fb7a57a 100644 --- a/src/components/purchase-orders/PurchaseOrderForm.tsx +++ b/src/components/purchase-orders/PurchaseOrderForm.tsx @@ -11,6 +11,7 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group" import { Form, FormControl, + FormDescription, FormField, FormItem, FormLabel, @@ -260,8 +261,12 @@ export function PurchaseOrderForm({ setOpen, refreshData }: PurchaseOrderFormPro form.setValue(`items.${index}.stockItemId`, selectedStockItem.id, { shouldDirty: true }); form.setValue(`items.${index}.itemName`, selectedStockItem.name, { shouldDirty: true }); form.setValue(`items.${index}.sku`, selectedStockItem.sku, { shouldDirty: true }); - // Use GBP price for purchase orders - form.setValue(`items.${index}.costPrice`, selectedStockItem.costPriceGBP, { shouldDirty: true, shouldValidate: true }); + // Use GBP price for purchase orders. Most stock items only carry a ZAR + // cost price (cost_price), so leave the field BLANK instead of silently + // showing £0.00 — a blank field tells the user the supplier's GBP price + // still has to be entered, whereas "£0.00" looks like a real price. + const gbpPrice = Number(selectedStockItem.costPriceGBP || 0); + form.setValue(`items.${index}.costPrice`, gbpPrice > 0 ? gbpPrice : ("" as any), { shouldDirty: true, shouldValidate: true }); // Leave quantity blank — user types the amount they want. form.setValue(`items.${index}.quantity`, "", { shouldDirty: true, shouldValidate: true }); } @@ -357,8 +362,14 @@ export function PurchaseOrderForm({ setOpen, refreshData }: PurchaseOrderFormPro itemName: item.itemName, sku: item.sku, quantity: item.quantity, - costPrice: Number(item.costPrice || 0), - costPriceGBP: Number(stockItem?.costPriceGBP || 0), + // ZAR landed cost, for reference only — the PO itself is priced in GBP. + costPrice: Number(stockItem?.costPrice ?? 0), + // The GBP unit price taken from the FORM. This is the only price + // field the PO PDF renders (£${item.costPriceGBP}), so it must be the + // value the user sees and edits. Reading it from the stock item + // instead silently discarded every price typed into the form and + // printed £0.00 on the PO. + costPriceGBP: Number(item.costPrice || 0), entryMode: 'stock', }); } @@ -755,6 +766,11 @@ export function PurchaseOrderForm({ setOpen, refreshData }: PurchaseOrderFormPro onChange={(v) => field.onChange(v)} /> + {!(Number(field.value || 0) > 0) && ( + + No GBP cost price on this stock item — enter the supplier's GBP price. + + )} )}