fix(quotes): delivery-note quotes saved markup as 0 on every line

The DN->quote batch copied cost and selling but never wrote markup, so
the approval screen showed 0% beside differing cost/selling. Write it at
creation, and derive it on the approval page for older lines.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Dave 2026-10-12 12:29:00 +02:00
parent 841121097c
commit ad4ef77bfd
2 changed files with 22 additions and 2 deletions

View file

@ -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;

View file

@ -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