From 1b744fdf051dde30b976268fe1eada8d99b25205 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 6 Apr 2026 07:03:28 +0000 Subject: [PATCH 1/2] Add separate base cost and margin pricing formulas Co-authored-by: Jakub Duras --- src/app/[locale]/order/item/[id]/form.tsx | 4 +- src/app/[locale]/order/items.tsx | 4 +- src/db/validation.ts | 18 ++++++- src/hooks/useCart/actions.ts | 14 +++-- src/utils/pricing.ts | 63 ++++++++++++++++++++--- 5 files changed, 88 insertions(+), 15 deletions(-) diff --git a/src/app/[locale]/order/item/[id]/form.tsx b/src/app/[locale]/order/item/[id]/form.tsx index da49d39..7f48efc 100644 --- a/src/app/[locale]/order/item/[id]/form.tsx +++ b/src/app/[locale]/order/item/[id]/form.tsx @@ -26,7 +26,7 @@ import { Product } from "@/db/schema"; import { MinusIcon, PlusIcon } from "@heroicons/react/24/solid"; import { useMemo, useState } from "react"; import { debounce } from "radash"; -import { calculateItemPrice } from "@/utils/pricing"; +import { calculateItemPricing } from "@/utils/pricing"; const Files = dynamic(() => import("./files/files"), { ssr: false, @@ -83,7 +83,7 @@ const Form = ({ session, initialCart, itemId, product }: Props) => { pieces: quantity, }, }; - const computedPrice = calculateItemPrice(product, previewItem); + const computedPrice = calculateItemPricing(product, previewItem).price; return ( <> diff --git a/src/app/[locale]/order/items.tsx b/src/app/[locale]/order/items.tsx index f1d8cbd..89b4729 100644 --- a/src/app/[locale]/order/items.tsx +++ b/src/app/[locale]/order/items.tsx @@ -16,7 +16,7 @@ import { Tooltip, } from "@radix-ui/themes"; import { useFormatter, useLocale, useTranslations } from "next-intl"; -import { calculateItemPrice } from "@/utils/pricing"; +import { calculateItemPricing } from "@/utils/pricing"; type Props = { cart: ShoppingCart; @@ -46,7 +46,7 @@ const OrderItems = ({ cart: initialCart, products }: Props) => { (product) => product.id === item.productId ); const itemPrice = - item.price ?? (product ? calculateItemPrice(product, item) : 0); + item.price ?? (product ? calculateItemPricing(product, item).price : 0); return ( { return semantics(match).eval(context); }; -export const calculateItemPrice = ( - product: Product, - item: Partial -) => { - const formula = product.pricing?.formula; - if (!formula) return 0; +export type ItemPricingBreakdown = { + baseCost: number; + margin: number; + price: number; +}; - const context = buildPriceContext(product, item); +const evaluateFormulaSafely = (formula: string | undefined, context: unknown) => { + if (!formula) return 0; try { const result = evaluatePriceFormula(formula, context); @@ -200,3 +200,52 @@ export const calculateItemPrice = ( return 0; } }; + +export const calculateItemPricing = ( + product: Product, + item: Partial +): ItemPricingBreakdown => { + const baseCostFormula = product.pricing?.baseCostFormula; + const marginFormula = product.pricing?.marginFormula; + const legacyFormula = product.pricing?.formula; + + const context = buildPriceContext(product, item); + + if (baseCostFormula || marginFormula) { + const baseCost = evaluateFormulaSafely(baseCostFormula, context); + const margin = evaluateFormulaSafely(marginFormula, { + ...context, + pricing: { baseCost }, + }); + + return { + baseCost, + margin, + price: baseCost + margin, + }; + } + + if (legacyFormula) { + const legacyPrice = evaluateFormulaSafely(legacyFormula, context); + return { + // Legacy formulas only define total price; we keep margin at 0 to avoid + // inventing profit data and treat the total as production cost. + baseCost: legacyPrice, + margin: 0, + price: legacyPrice, + }; + } + + return { + baseCost: 0, + margin: 0, + price: 0, + }; +}; + +export const calculateItemPrice = ( + product: Product, + item: Partial +) => { + return calculateItemPricing(product, item).price; +}; From 6c414bda23bc59fea1d5d0695d4b64d8479f6bd9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 6 Jun 2026 11:38:16 +0000 Subject: [PATCH 2/2] Drop legacy total pricing formula support Co-authored-by: Jakub Duras --- src/db/validation.ts | 7 ------- src/hooks/useCart/actions.ts | 1 - src/utils/pricing.ts | 12 ------------ 3 files changed, 20 deletions(-) diff --git a/src/db/validation.ts b/src/db/validation.ts index d42fdf1..98b77ae 100644 --- a/src/db/validation.ts +++ b/src/db/validation.ts @@ -133,12 +133,6 @@ export const productSchema = z.object({ .describe( "Margin formula. Supports numbers, variables, +, -, *, /, ^ and parentheses. Example: size.area * 0.01 + files.totalPieces * 0.05 + configTotals.margin", ), - formula: z - .string() - .optional() - .describe( - "Legacy total price formula kept for backward compatibility. Prefer baseCostFormula and marginFormula.", - ), }) .optional() .default({}) @@ -189,7 +183,6 @@ export const orderItemSchema = z.object({ .object({ baseCostFormula: z.string().optional(), marginFormula: z.string().optional(), - formula: z.string().optional(), }) .optional(), files: z.object({ diff --git a/src/hooks/useCart/actions.ts b/src/hooks/useCart/actions.ts index 347ce1a..779bafb 100644 --- a/src/hooks/useCart/actions.ts +++ b/src/hooks/useCart/actions.ts @@ -620,7 +620,6 @@ const hydrateItemPricing = async ( item.pricing = { baseCostFormula: product.pricing?.baseCostFormula, marginFormula: product.pricing?.marginFormula, - formula: product.pricing?.formula, }; item.baseCost = pricing.baseCost; item.margin = pricing.margin; diff --git a/src/utils/pricing.ts b/src/utils/pricing.ts index 5c46282..7b976e2 100644 --- a/src/utils/pricing.ts +++ b/src/utils/pricing.ts @@ -207,7 +207,6 @@ export const calculateItemPricing = ( ): ItemPricingBreakdown => { const baseCostFormula = product.pricing?.baseCostFormula; const marginFormula = product.pricing?.marginFormula; - const legacyFormula = product.pricing?.formula; const context = buildPriceContext(product, item); @@ -225,17 +224,6 @@ export const calculateItemPricing = ( }; } - if (legacyFormula) { - const legacyPrice = evaluateFormulaSafely(legacyFormula, context); - return { - // Legacy formulas only define total price; we keep margin at 0 to avoid - // inventing profit data and treat the total as production cost. - baseCost: legacyPrice, - margin: 0, - price: legacyPrice, - }; - } - return { baseCost: 0, margin: 0,