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" && 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" ? "Unit weight" : "Size",
item.kind === "discrete"
? `${item.countOriginal} ${cfg?.unit ?? "ct"}`
? `${item.unitWeight} g`
: `${item.weight} ${cfg?.unit ?? "g"}`,
],
["THC", `${item.thc.toFixed(1)}%`],
+11 -34
View File
@@ -397,7 +397,6 @@ function InstanceDetailsStep({
shopId: last?.shopId ?? data.shops[0]?.id ?? NEW_SHOP,
binId: data.bins[0]?.id ?? NEW_BIN,
weight: last?.weight ?? (isDiscrete ? 0 : 3.5),
countOriginal: last?.countOriginal ?? (isDiscrete ? 1 : 0),
unitWeight: last?.unitWeight ?? (isDiscrete ? 0.7 : 0),
price: initialPrice,
thc: last?.thc ?? 22,
@@ -414,7 +413,6 @@ function InstanceDetailsStep({
const update = <K extends keyof typeof form>(k: K, v: (typeof form)[K]) =>
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 assetIdValid = ASSET_ID_RE.test(assetId);
const assetIdConflict =
@@ -447,9 +445,9 @@ function InstanceDetailsStep({
shopId,
binId,
weight: isDiscrete ? undefined : form.weight,
countOriginal: isDiscrete ? form.countOriginal : undefined,
countOriginal: isDiscrete ? 1 : undefined,
unitWeight: isDiscrete ? form.unitWeight : undefined,
price: totalPrice,
price: form.price,
thc: form.thc,
cbd: form.cbd,
totalCannabinoids: form.totalCannabinoids,
@@ -608,24 +606,14 @@ function InstanceDetailsStep({
}}
>
{isDiscrete ? (
<>
<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
type="number"
step="0.1"
value={form.unitWeight}
onChange={(e) => update("unitWeight", +e.target.value)}
/>
</Field>
</>
<Field label="Unit weight (g)" span={2} hint="Weight of one unit — for grams stats">
<Input
type="number"
step="0.1"
value={form.unitWeight}
onChange={(e) => update("unitWeight", +e.target.value)}
/>
</Field>
) : (
<Field label={`Size (${cfg?.unit ?? "g"})`} span={2}>
<Input
@@ -636,7 +624,7 @@ function InstanceDetailsStep({
/>
</Field>
)}
<Field label={isDiscrete ? "Price per unit ($)" : "Price ($)"}>
<Field label="Price ($)">
<Input
type="number"
step="0.01"
@@ -661,17 +649,6 @@ function InstanceDetailsStep({
</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"
+11 -40
View File
@@ -22,19 +22,13 @@ export function EditInventoryFlow({
const qc = useQueryClient();
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({
shopId: item.shopId ?? NEW_SHOP,
binId: item.binId ?? NEW_BIN,
weight: item.weight,
countOriginal: item.countOriginal,
unitWeight: item.unitWeight,
price: initialPrice,
price: item.price,
thc: item.thc,
cbd: item.cbd,
totalCannabinoids: item.totalCannabinoids,
@@ -50,7 +44,6 @@ export function EditInventoryFlow({
setForm((f) => ({ ...f, [k]: v }));
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 save = useMutation({
@@ -76,9 +69,8 @@ export function EditInventoryFlow({
shopId,
binId,
weight: isDiscrete ? undefined : form.weight,
countOriginal: isDiscrete ? form.countOriginal : undefined,
unitWeight: isDiscrete ? form.unitWeight : undefined,
price: totalPrice,
price: form.price,
thc: form.thc,
cbd: form.cbd,
totalCannabinoids: form.totalCannabinoids,
@@ -223,24 +215,14 @@ export function EditInventoryFlow({
}}
>
{isDiscrete ? (
<>
<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
type="number"
step="0.1"
value={form.unitWeight}
onChange={(e) => update("unitWeight", +e.target.value)}
/>
</Field>
</>
<Field label="Unit weight (g)" span={2} hint="Weight of one unit — for grams stats">
<Input
type="number"
step="0.1"
value={form.unitWeight}
onChange={(e) => update("unitWeight", +e.target.value)}
/>
</Field>
) : (
<Field label={`Size (${cfg?.unit ?? "g"})`} span={2}>
<Input
@@ -251,7 +233,7 @@ export function EditInventoryFlow({
/>
</Field>
)}
<Field label={isDiscrete ? "Price per unit ($)" : "Price ($)"}>
<Field label="Price ($)">
<Input
type="number"
step="0.01"
@@ -276,17 +258,6 @@ export function EditInventoryFlow({
</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"