fix(stock-quick-draft): new-SKU supplier picker reachable, closure guard, submit test
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
75342ed88c
commit
1b7552ffd2
2 changed files with 84 additions and 4 deletions
|
|
@ -291,4 +291,48 @@ describe("QuickDraftModal — add & next", () => {
|
|||
// Search region is hidden in new-SKU mode.
|
||||
expect(screen.queryByTestId("quick-draft-search")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("new-SKU path POSTs newStockItem and no stockItemId", async () => {
|
||||
render(<QuickDraftModal open onOpenChange={() => {}} />);
|
||||
fireEvent.change(screen.getByTestId("quick-draft-search"), {
|
||||
target: { value: "cutting" },
|
||||
});
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(250);
|
||||
});
|
||||
fireEvent.click(screen.getByText(/115mm Cutting Disc/));
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
fireEvent.click(screen.getByTestId("quick-draft-toggle-new-sku"));
|
||||
fireEvent.change(screen.getByTestId("quick-draft-new-sku-name"), {
|
||||
target: { value: "115mm Cutting Disc" },
|
||||
});
|
||||
fireEvent.change(screen.getByTestId("quick-draft-new-sku-sku"), {
|
||||
target: { value: "STK-CD-115" },
|
||||
});
|
||||
fireEvent.click(screen.getByLabelText(/Steel & Pipe Co/));
|
||||
fireEvent.change(screen.getByTestId("quick-draft-qty"), {
|
||||
target: { value: "50" },
|
||||
});
|
||||
fireEvent.change(screen.getByTestId("quick-draft-cost"), {
|
||||
target: { value: "20" },
|
||||
});
|
||||
fireEvent.click(screen.getByTestId("quick-draft-add"));
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
});
|
||||
|
||||
const addCalls = (global.fetch as ReturnType<typeof vi.fn>).mock.calls.filter(
|
||||
(c) => typeof c[0] === "string" && c[0].includes("/add-line"),
|
||||
);
|
||||
expect(addCalls.length).toBeGreaterThan(0);
|
||||
const [, init] = addCalls[0];
|
||||
const body = JSON.parse(init.body);
|
||||
expect(body.newStockItem).toEqual({ name: "115mm Cutting Disc", sku: "STK-CD-115" });
|
||||
expect(body.stockItemId).toBeUndefined();
|
||||
expect(body.supplierId).toBe("sup-1");
|
||||
expect(body.quantity).toBe(50);
|
||||
expect(body.costPrice).toBe(20);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ import type {
|
|||
* Quick-Draft Modal — item-first stock-order entry on /stock-orders.
|
||||
*
|
||||
* See docs/superpowers/specs/2026-07-30-stock-quick-draft-design.md
|
||||
* for the full design. This file is the modal in its entirety; the
|
||||
* three regions (search, cards, footer) live in sub-components
|
||||
* further down so the test surface stays manageable.
|
||||
* for the full design.
|
||||
*
|
||||
* Data flow:
|
||||
* - search & history hit /api/v1/stock-quick-draft/{search,supplier-history}
|
||||
|
|
@ -57,6 +55,7 @@ export function QuickDraftModal({ open, onOpenChange }: QuickDraftModalProps) {
|
|||
const [supplierChoice, setSupplierChoice] = useState<string | null>(null);
|
||||
const [qty, setQty] = useState("");
|
||||
const [cost, setCost] = useState("");
|
||||
// New-SKU toggle state (named for readability; plan uses isNewSku/newName/newSku)
|
||||
const [newSkuMode, setNewSkuMode] = useState(false);
|
||||
const [newSkuName, setNewSkuName] = useState("");
|
||||
const [newSkuSku, setNewSkuSku] = useState("");
|
||||
|
|
@ -147,6 +146,7 @@ export function QuickDraftModal({ open, onOpenChange }: QuickDraftModalProps) {
|
|||
};
|
||||
|
||||
const onAddAndNext = async () => {
|
||||
if (submitting) return; // closure-time guard against double-click races
|
||||
if (!canSubmit) return;
|
||||
setSubmitting(true);
|
||||
try {
|
||||
|
|
@ -317,7 +317,7 @@ export function QuickDraftModal({ open, onOpenChange }: QuickDraftModalProps) {
|
|||
+ New SKU not in stock yet
|
||||
</button>
|
||||
) : (
|
||||
<div className="space-y-2 rounded-md border bg-muted/20 p-3">
|
||||
<div className="space-y-3 rounded-md border bg-muted/20 p-3">
|
||||
<div>
|
||||
<Label htmlFor="quick-draft-new-sku-name">Name</Label>
|
||||
<Input
|
||||
|
|
@ -336,6 +336,42 @@ export function QuickDraftModal({ open, onOpenChange }: QuickDraftModalProps) {
|
|||
onChange={(e) => setNewSkuSku(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{historyLoading && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Loading supplier history…
|
||||
</div>
|
||||
)}
|
||||
{!historyLoading && history.length > 0 && (
|
||||
<div className="text-xs space-y-1" role="radiogroup">
|
||||
{history.map((entry) => (
|
||||
<label
|
||||
key={entry.supplierId}
|
||||
className="flex cursor-pointer items-center justify-between gap-2"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="quick-draft-supplier"
|
||||
value={entry.supplierId}
|
||||
checked={supplierChoice === entry.supplierId}
|
||||
onChange={() => setSupplierChoice(entry.supplierId)}
|
||||
className="size-4 accent-primary"
|
||||
/>
|
||||
<span>{entry.supplierName}</span>
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
last paid: <strong>R {entry.lastCost.toFixed(2)}</strong>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!historyLoading && history.length === 0 && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Pick a supplier — Task 10 will add a full supplier dropdown
|
||||
when there's no history.
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs underline text-muted-foreground"
|
||||
|
|
|
|||
Loading…
Reference in a new issue