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:
@@ -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)}%`],
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user