fix(po): store the price from the form so POs stop printing £0.00

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.
This commit is contained in:
Dave 2026-10-23 12:08:03 +02:00
parent 2b56053e1c
commit 2682eeb360
2 changed files with 27 additions and 6 deletions

View file

@ -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,
}))

View file

@ -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)}
/>
</FormControl>
{!(Number(field.value || 0) > 0) && (
<FormDescription className="text-xs text-amber-600 dark:text-amber-400">
No GBP cost price on this stock item — enter the supplier&apos;s GBP price.
</FormDescription>
)}
<FormMessage />
</FormItem>
)}