"use client"; /* Receive a delivery — tick lines against the invoice as you unpack. Receiving closes the order: anything short is raised as its own back order, so the shortfall is chased on a live order rather than left sitting on a closed one. */ import { useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { useSnap } from "@/lib/client"; import { fmtDate, itemMap, label, OPEN_STATUSES, sizeIndexOf, staffMap, staffName, variantName } from "@/lib/compute"; import { INK, MBar, MBody, MEmpty, MError, MRow, MRule, MSection, MStepper, MTop } from "@/components/m"; export default function MReceive() { const { s, mutate, busy } = useSnap(); const router = useRouter(); const [pick, setPick] = useState(null); const [got, setGot] = useState>({}); const [invoice, setInvoice] = useState(""); const [err, setErr] = useState(""); /* The shortfall is banked here at the moment of receipt, not read off the order afterwards: receiving closes the order, so the refresh that follows drops it out of the open list and both `lines` and `short` fall empty. Reading them on this screen told a storeperson who had just stepped two tunics down to zero that everything on the order arrived, and the back order sat unchased on Ordering. */ const [done, setDone] = useState<{ code: string; short: number } | null>(null); const byId = useMemo(() => itemMap(s), [s]); const staffById = useMemo(() => staffMap(s), [s]); const open = useMemo(() => s.orders.filter((o) => OPEN_STATUSES.includes(o.status) && o.status !== "Draft"), [s]); const order = open.find((o) => o.id === pick); // What's still outstanding on each line after any earlier partial receipt. const lines = useMemo(() => { if (!order) return []; return order.lines.map((l) => { const already = order.receipts.reduce((t, r) => t + r.lines.filter((x) => x.itemId === l.itemId && x.size === l.size).reduce((a, x) => a + x.qty, 0), 0); return { ...l, already, outstanding: Math.max(0, l.qty - already), name: `${variantName(byId[l.itemId], l.size)}`, ok: sizeIndexOf(byId[l.itemId], l.size) >= 0 }; }).filter((l) => l.outstanding > 0); }, [order, byId]); const q = (id: string, fallback: number) => got[id] ?? fallback; const arriving = lines.reduce((t, l) => t + q(l.id, l.outstanding), 0); const short = lines.filter((l) => q(l.id, l.outstanding) < l.outstanding); const receive = async () => { if (!order) return; const r = await mutate("order.receive", { id: order.id, invoice: invoice.trim(), lines: lines.map((l) => ({ lineId: l.id, itemId: l.itemId, size: l.size, arrived: q(l.id, l.outstanding), dest: order.staffId ? "pickup" : "shelf" })), }); if (!r.ok) { setErr(r.error); return; } setDone({ code: order.code, short: short.length }); }; if (done) return ( <> router.push("/m/more")} /> ); return ( <> setErr("")} /> {!order ? ( <> {open.length === 0 ? : open.map((o) => ( { setPick(o.id); setGot({}); setInvoice(o.invoice || ""); }} mark="ink" title={`${o.supplier || "Supplier"} · ${o.code}`} sub={[o.staffId ? `For ${staffName(staffById[o.staffId])}` : "For stock", o.expected ? `expected ${fmtDate(o.expected)}` : o.status].filter(Boolean).join(" · ")} right={{o.lines.reduce((t, l) => t + l.qty, 0)}} /> ))} ) : ( <>
{order.supplier || "Supplier"}
{order.code}
Ordered {fmtDate(order.date)}{order.expected ? ` · expected ${fmtDate(order.expected)}` : ""}
setInvoice(e.target.value)} placeholder="Invoice number (optional)" aria-label="Invoice number" style={{ width: "100%", minHeight: 48, padding: "10px 12px", border: "2px solid " + INK, background: "var(--color-bg)", fontSize: 16, fontWeight: 600, marginTop: 14 }} />
{lines.length === 0 ? : lines.map((l) => (
{l.name}
{l.outstanding} outstanding{l.already ? ` · ${l.already} already received` : ""} {q(l.id, l.outstanding) < l.outstanding ? ` · ${l.outstanding - q(l.id, l.outstanding)} short` : ""}
setGot((x) => ({ ...x, [l.id]: n }))} max={l.outstanding} />
))} {lines.length > 0 && (

{order.staffId ? "Goes onto the pickup list for the staff member it was ordered for." : "Goes onto the shelf."} {short.length > 0 && ` ${short.length} line${short.length === 1 ? "" : "s"} short — ${order.code} closes as received and the shortfall goes onto a new back order.`}

)} )}
{order && lines.length > 0 && } ); }