Pre-rolls, edibles, and vaporizers are sold and reasoned about per unit
("$10 each") rather than as a bag total. The Add and Edit forms now ask
for "Price per unit" when the kind is discrete, and the product drawer
displays the per-unit number with the total as a small subline. Bulk
products (flower, concentrate, tincture) still take and show a total.
The stored price column remains the total, so existing data, spend
totals, sort-by-price, and bin value calculations all keep working
unchanged. Conversion (pricePerUnit × countOriginal) happens at the
form boundary on save and the inverse on edit-modal load.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,8 @@ export function AddProductFlow({ data, onClose }: { data: Bootstrap; onClose: ()
|
||||
|
||||
const cfg = TYPES.find((t) => t.id === form.type);
|
||||
const isDiscrete = cfg?.kind === "discrete";
|
||||
// form.price is total for bulk, per-unit for discrete.
|
||||
const totalPrice = isDiscrete ? form.price * form.countOriginal : form.price;
|
||||
const cpg = !isDiscrete && form.weight > 0 ? form.price / form.weight : 0;
|
||||
|
||||
// Find an existing strain matching the current name + brand + type.
|
||||
@@ -107,6 +109,7 @@ export function AddProductFlow({ data, onClose }: { data: Bootstrap; onClose: ()
|
||||
shopId,
|
||||
binId,
|
||||
kind: isDiscrete ? "discrete" : "bulk",
|
||||
price: totalPrice,
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -274,7 +277,7 @@ export function AddProductFlow({ data, onClose }: { data: Bootstrap; onClose: ()
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
<Field label="Price ($)">
|
||||
<Field label={isDiscrete ? "Price per unit ($)" : "Price ($)"}>
|
||||
<Input
|
||||
type="number"
|
||||
step="0.01"
|
||||
@@ -297,6 +300,15 @@ export function AddProductFlow({ data, onClose }: { data: Bootstrap; onClose: ()
|
||||
<span className="mono" style={{ color: "var(--ink-2)" }}>{fmt.money(cpg)}</span>
|
||||
</div>
|
||||
)}
|
||||
{isDiscrete && form.price > 0 && form.countOriginal > 0 && (
|
||||
<div style={{ marginTop: 12, fontSize: 12, color: "var(--ink-3)" }}>
|
||||
Total:{" "}
|
||||
<span className="mono" style={{ color: "var(--ink-2)" }}>{fmt.money(totalPrice)}</span>
|
||||
<span style={{ marginLeft: 6 }}>
|
||||
({form.countOriginal} × {fmt.money(form.price)})
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="smallcaps" style={{ color: "var(--ink-3)", margin: "28px 0 16px" }}>Cannabinoid profile</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 16 }}>
|
||||
|
||||
@@ -22,6 +22,13 @@ export function EditProductFlow({
|
||||
}) {
|
||||
const qc = useQueryClient();
|
||||
|
||||
const isDiscrete = product.kind === "discrete";
|
||||
// form.price is total for bulk, per-unit for discrete. Convert at I/O boundaries.
|
||||
const initialPrice =
|
||||
isDiscrete && product.countOriginal > 0
|
||||
? product.price / product.countOriginal
|
||||
: product.price;
|
||||
|
||||
const [form, setForm] = useState({
|
||||
name: product.name,
|
||||
brandId: product.brandId ?? NEW_BRAND,
|
||||
@@ -30,7 +37,7 @@ export function EditProductFlow({
|
||||
weight: product.weight,
|
||||
countOriginal: product.countOriginal,
|
||||
unitWeight: product.unitWeight,
|
||||
price: product.price,
|
||||
price: initialPrice,
|
||||
thc: product.thc,
|
||||
cbd: product.cbd,
|
||||
totalCannabinoids: product.totalCannabinoids,
|
||||
@@ -49,7 +56,7 @@ export function EditProductFlow({
|
||||
setForm((f) => ({ ...f, [k]: v }));
|
||||
|
||||
const cfg = TYPES.find((t) => t.id === product.type);
|
||||
const isDiscrete = product.kind === "discrete";
|
||||
const totalPrice = isDiscrete ? form.price * form.countOriginal : form.price;
|
||||
const cpg = !isDiscrete && form.weight > 0 ? form.price / form.weight : 0;
|
||||
|
||||
const save = useMutation({
|
||||
@@ -86,7 +93,7 @@ export function EditProductFlow({
|
||||
weight: isDiscrete ? undefined : form.weight,
|
||||
countOriginal: isDiscrete ? form.countOriginal : undefined,
|
||||
unitWeight: isDiscrete ? form.unitWeight : undefined,
|
||||
price: form.price,
|
||||
price: totalPrice,
|
||||
thc: form.thc,
|
||||
cbd: form.cbd,
|
||||
totalCannabinoids: form.totalCannabinoids,
|
||||
@@ -262,7 +269,7 @@ export function EditProductFlow({
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
<Field label="Price ($)">
|
||||
<Field label={isDiscrete ? "Price per unit ($)" : "Price ($)"}>
|
||||
<Input
|
||||
type="number"
|
||||
step="0.01"
|
||||
@@ -285,6 +292,15 @@ export function EditProductFlow({
|
||||
<span className="mono" style={{ color: "var(--ink-2)" }}>{fmt.money(cpg)}</span>
|
||||
</div>
|
||||
)}
|
||||
{isDiscrete && form.price > 0 && form.countOriginal > 0 && (
|
||||
<div style={{ marginTop: 12, fontSize: 12, color: "var(--ink-3)" }}>
|
||||
Total:{" "}
|
||||
<span className="mono" style={{ color: "var(--ink-2)" }}>{fmt.money(totalPrice)}</span>
|
||||
<span style={{ marginLeft: 6 }}>
|
||||
({form.countOriginal} × {fmt.money(form.price)})
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="smallcaps" style={{ color: "var(--ink-3)", margin: "28px 0 16px" }}>Cannabinoid profile</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 16 }}>
|
||||
|
||||
Reference in New Issue
Block a user