diff --git a/src/app/(app)/quotes/approvals/[id]/page.tsx b/src/app/(app)/quotes/approvals/[id]/page.tsx index 6ca2941..e036693 100644 --- a/src/app/(app)/quotes/approvals/[id]/page.tsx +++ b/src/app/(app)/quotes/approvals/[id]/page.tsx @@ -59,6 +59,7 @@ import { quoteStatusClassName, formatQuoteStatus } from "@/lib/quote-status"; import { MIN_MARKUP_PERCENT, applyMarkupFloor, + markupFromCostAndSelling, sellingFromCostAndMarkup, } from "@/lib/quote-pricing"; import { AiItemHistoryTrigger } from "@/components/ai-item-history-trigger"; @@ -366,7 +367,15 @@ export default function ApprovalReviewPage() { const getEffectiveMarkup = (item: QuoteItem): number => { const change = pendingChanges[item.id]; if (change?.markup !== undefined) return change.markup; - return Number(item.markup) || 0; + // Older lines (e.g. raised from a delivery note) were saved with + // markup 0 although cost and selling differ — derive it then. + const stored = Number(item.markup) || 0; + const cost = Number(item.cost_price) || 0; + const selling = Number(item.selling_price) || 0; + if (stored === 0 && cost > 0 && selling > cost) { + return markupFromCostAndSelling(cost, selling); + } + return stored; }; const getEffectiveCost = (item: QuoteItem): number => Number(item.cost_price) || 0; diff --git a/src/lib/server/delivery-notes-service.ts b/src/lib/server/delivery-notes-service.ts index 9e8ba4f..528b14d 100644 --- a/src/lib/server/delivery-notes-service.ts +++ b/src/lib/server/delivery-notes-service.ts @@ -38,7 +38,10 @@ import { renderDeliveryNotePdf } from "@/lib/pdf/render"; import { compressBuffer } from "@/lib/server/attachment-compressor"; import { buildDeliveryNoteLine } from "@/lib/pdf/delivery-note-document"; import { firstFileName } from "@/lib/file-fields"; -import { sellingFromCostAndMarkup } from "@/lib/quote-pricing"; +import { + markupFromCostAndSelling, + sellingFromCostAndMarkup, +} from "@/lib/quote-pricing"; import { DELIVERY_NOTE_LETTERHEAD_CODE, deliveryNoteLetterheadCode, @@ -370,6 +373,14 @@ export async function createQuoteFromDeliveryNote( // Selling price is best-effort when unoverridden: copy if set, // otherwise 0 and pricing_source="manual" so the UI flags it. selling_price: sellingPrice, + // The markup the approval screen shows. Without it every line + // saved as 0% even though cost and selling differ. + markup: + costPrice > 0 && sellingPrice > 0 + ? override + ? Number(override.markupPercent) || 0 + : markupFromCostAndSelling(costPrice, sellingPrice) + : 0, notes: it.notes ?? "", sku: it.sku ?? "", // A line counts as PROCESSED once it has both a cost and a