fix(orders): clamp the AI fill's markup at 0 like every other order line

A line that sells below the quote's cost produced a negative markup.
Every other write to an order line's markup stores
Math.max(0, computedMarkup), so the fill now does the same. The margin
chip is unaffected — it works from cost and selling, not from this
field, so a loss-making line still shows its real margin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dave 2026-10-24 15:44:19 +02:00
parent 34d3422586
commit 0294015ba2
2 changed files with 18 additions and 1 deletions

View file

@ -550,6 +550,19 @@ describe("buildOrderLineFillPatch", () => {
expect(patch?.markup_percent).toBe(50);
});
it("clamps a loss-making markup at 0, like every other order-line write", () => {
// The customer's price is below the quote's cost. The margin chip
// shows the real margin from cost + selling; the stored markup
// follows the app's convention of never going negative.
const patch = buildOrderLineFillPatch(
fillLine({ selling_price: 8 }),
fillQuoteLine({ cost_price: 10, selling_price: 12 }),
FILL_CTX,
);
expect(patch?.markup).toBe(0);
expect(patch?.markup_percent).toBe(0);
});
it("adds the price doubt when the customer price differs by more than 5%", () => {
const patch = buildOrderLineFillPatch(
fillLine({ selling_price: 16.64 }),

View file

@ -260,7 +260,11 @@ export function buildOrderLineFillPatch(
// The resulting selling price: the customer's own price when there is
// one, otherwise the quoted price.
const selling = customerPrice > 0 ? customerPrice : quotedPrice;
const markup = cost > 0 && selling > 0 ? round2(((selling - cost) / cost) * 100) : 0;
// Clamped at 0, like every other write to an order line's markup
// (`orders-service.ts` stores `Math.max(0, computedMarkup)`). A line
// that sells below cost still shows its true margin on screen, because
// the margin chip works from cost and selling, not from this field.
const markup = cost > 0 && selling > 0 ? Math.max(0, round2(((selling - cost) / cost) * 100)) : 0;
const supplier = (line.supplier ?? "").trim() || (quoteLine.supplier ?? "");
const sku = (line.sku ?? "").trim() || (quoteLine.sku ?? "");