Remove quantity option from add/edit forms
Build and push image / build (push) Successful in 43s

Each discrete item (pre-roll, edible, etc.) is now always one
physical unit with its own asset ID. The quantity field is gone
from both add and edit flows, countOriginal is hardcoded to 1,
and price is just "Price" instead of "Price per unit."

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 19:05:11 -04:00
parent 839dbf0430
commit 670d56ba4c
3 changed files with 25 additions and 101 deletions
+3 -27
View File
@@ -216,35 +216,11 @@ export function ProductDetail({
> >
{( {(
[ [
["Price", fmt.money(item.price)],
[ [
"Price", item.kind === "discrete" ? "Unit weight" : "Size",
item.kind === "discrete" && item.countOriginal > 0 ? (
<>
{fmt.money(item.price / item.countOriginal)}
<span style={{ fontSize: 14, color: "var(--ink-3)", marginLeft: 4 }}>
/unit
</span>
<div
style={{
fontSize: 11,
color: "var(--ink-3)",
fontWeight: 400,
marginTop: 2,
fontFamily: "var(--mono)",
letterSpacing: 0,
}}
>
{fmt.money(item.price)} total
</div>
</>
) : (
fmt.money(item.price)
),
],
[
item.kind === "discrete" ? "Quantity" : "Size",
item.kind === "discrete" item.kind === "discrete"
? `${item.countOriginal} ${cfg?.unit ?? "ct"}` ? `${item.unitWeight} g`
: `${item.weight} ${cfg?.unit ?? "g"}`, : `${item.weight} ${cfg?.unit ?? "g"}`,
], ],
["THC", `${item.thc.toFixed(1)}%`], ["THC", `${item.thc.toFixed(1)}%`],
+4 -27
View File
@@ -397,7 +397,6 @@ function InstanceDetailsStep({
shopId: last?.shopId ?? data.shops[0]?.id ?? NEW_SHOP, shopId: last?.shopId ?? data.shops[0]?.id ?? NEW_SHOP,
binId: data.bins[0]?.id ?? NEW_BIN, binId: data.bins[0]?.id ?? NEW_BIN,
weight: last?.weight ?? (isDiscrete ? 0 : 3.5), weight: last?.weight ?? (isDiscrete ? 0 : 3.5),
countOriginal: last?.countOriginal ?? (isDiscrete ? 1 : 0),
unitWeight: last?.unitWeight ?? (isDiscrete ? 0.7 : 0), unitWeight: last?.unitWeight ?? (isDiscrete ? 0.7 : 0),
price: initialPrice, price: initialPrice,
thc: last?.thc ?? 22, thc: last?.thc ?? 22,
@@ -414,7 +413,6 @@ function InstanceDetailsStep({
const update = <K extends keyof typeof form>(k: K, v: (typeof form)[K]) => const update = <K extends keyof typeof form>(k: K, v: (typeof form)[K]) =>
setForm((f) => ({ ...f, [k]: v })); setForm((f) => ({ ...f, [k]: v }));
const totalPrice = isDiscrete ? form.price * form.countOriginal : form.price;
const cpg = !isDiscrete && form.weight > 0 ? form.price / form.weight : 0; const cpg = !isDiscrete && form.weight > 0 ? form.price / form.weight : 0;
const assetIdValid = ASSET_ID_RE.test(assetId); const assetIdValid = ASSET_ID_RE.test(assetId);
const assetIdConflict = const assetIdConflict =
@@ -447,9 +445,9 @@ function InstanceDetailsStep({
shopId, shopId,
binId, binId,
weight: isDiscrete ? undefined : form.weight, weight: isDiscrete ? undefined : form.weight,
countOriginal: isDiscrete ? form.countOriginal : undefined, countOriginal: isDiscrete ? 1 : undefined,
unitWeight: isDiscrete ? form.unitWeight : undefined, unitWeight: isDiscrete ? form.unitWeight : undefined,
price: totalPrice, price: form.price,
thc: form.thc, thc: form.thc,
cbd: form.cbd, cbd: form.cbd,
totalCannabinoids: form.totalCannabinoids, totalCannabinoids: form.totalCannabinoids,
@@ -608,16 +606,7 @@ function InstanceDetailsStep({
}} }}
> >
{isDiscrete ? ( {isDiscrete ? (
<> <Field label="Unit weight (g)" span={2} hint="Weight of one unit — for grams stats">
<Field label={`Quantity (${cfg!.unit})`}>
<Input
type="number"
step="1"
value={form.countOriginal}
onChange={(e) => update("countOriginal", +e.target.value)}
/>
</Field>
<Field label="Per-unit weight (g)" hint="For grams stats">
<Input <Input
type="number" type="number"
step="0.1" step="0.1"
@@ -625,7 +614,6 @@ function InstanceDetailsStep({
onChange={(e) => update("unitWeight", +e.target.value)} onChange={(e) => update("unitWeight", +e.target.value)}
/> />
</Field> </Field>
</>
) : ( ) : (
<Field label={`Size (${cfg?.unit ?? "g"})`} span={2}> <Field label={`Size (${cfg?.unit ?? "g"})`} span={2}>
<Input <Input
@@ -636,7 +624,7 @@ function InstanceDetailsStep({
/> />
</Field> </Field>
)} )}
<Field label={isDiscrete ? "Price per unit ($)" : "Price ($)"}> <Field label="Price ($)">
<Input <Input
type="number" type="number"
step="0.01" step="0.01"
@@ -661,17 +649,6 @@ function InstanceDetailsStep({
</span> </span>
</div> </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 <div
className="smallcaps" className="smallcaps"
@@ -22,19 +22,13 @@ export function EditInventoryFlow({
const qc = useQueryClient(); const qc = useQueryClient();
const isDiscrete = item.kind === "discrete"; const isDiscrete = item.kind === "discrete";
// form.price is total for bulk, per-unit for discrete. Convert at I/O boundaries.
const initialPrice =
isDiscrete && item.countOriginal > 0
? item.price / item.countOriginal
: item.price;
const [form, setForm] = useState({ const [form, setForm] = useState({
shopId: item.shopId ?? NEW_SHOP, shopId: item.shopId ?? NEW_SHOP,
binId: item.binId ?? NEW_BIN, binId: item.binId ?? NEW_BIN,
weight: item.weight, weight: item.weight,
countOriginal: item.countOriginal,
unitWeight: item.unitWeight, unitWeight: item.unitWeight,
price: initialPrice, price: item.price,
thc: item.thc, thc: item.thc,
cbd: item.cbd, cbd: item.cbd,
totalCannabinoids: item.totalCannabinoids, totalCannabinoids: item.totalCannabinoids,
@@ -50,7 +44,6 @@ export function EditInventoryFlow({
setForm((f) => ({ ...f, [k]: v })); setForm((f) => ({ ...f, [k]: v }));
const cfg = TYPES.find((t) => t.id === item.type); const cfg = TYPES.find((t) => t.id === item.type);
const totalPrice = isDiscrete ? form.price * form.countOriginal : form.price;
const cpg = !isDiscrete && form.weight > 0 ? form.price / form.weight : 0; const cpg = !isDiscrete && form.weight > 0 ? form.price / form.weight : 0;
const save = useMutation({ const save = useMutation({
@@ -76,9 +69,8 @@ export function EditInventoryFlow({
shopId, shopId,
binId, binId,
weight: isDiscrete ? undefined : form.weight, weight: isDiscrete ? undefined : form.weight,
countOriginal: isDiscrete ? form.countOriginal : undefined,
unitWeight: isDiscrete ? form.unitWeight : undefined, unitWeight: isDiscrete ? form.unitWeight : undefined,
price: totalPrice, price: form.price,
thc: form.thc, thc: form.thc,
cbd: form.cbd, cbd: form.cbd,
totalCannabinoids: form.totalCannabinoids, totalCannabinoids: form.totalCannabinoids,
@@ -223,16 +215,7 @@ export function EditInventoryFlow({
}} }}
> >
{isDiscrete ? ( {isDiscrete ? (
<> <Field label="Unit weight (g)" span={2} hint="Weight of one unit — for grams stats">
<Field label={`Quantity (${cfg!.unit})`}>
<Input
type="number"
step="1"
value={form.countOriginal}
onChange={(e) => update("countOriginal", +e.target.value)}
/>
</Field>
<Field label="Per-unit weight (g)" hint="For grams stats">
<Input <Input
type="number" type="number"
step="0.1" step="0.1"
@@ -240,7 +223,6 @@ export function EditInventoryFlow({
onChange={(e) => update("unitWeight", +e.target.value)} onChange={(e) => update("unitWeight", +e.target.value)}
/> />
</Field> </Field>
</>
) : ( ) : (
<Field label={`Size (${cfg?.unit ?? "g"})`} span={2}> <Field label={`Size (${cfg?.unit ?? "g"})`} span={2}>
<Input <Input
@@ -251,7 +233,7 @@ export function EditInventoryFlow({
/> />
</Field> </Field>
)} )}
<Field label={isDiscrete ? "Price per unit ($)" : "Price ($)"}> <Field label="Price ($)">
<Input <Input
type="number" type="number"
step="0.01" step="0.01"
@@ -276,17 +258,6 @@ export function EditInventoryFlow({
</span> </span>
</div> </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 <div
className="smallcaps" className="smallcaps"