fix(stock-purchase-send): stop auto-flipping stock_purchase_items on success
Same operator-flow fix we made for customer POs in 79b9309: sending a
stock PO no longer auto-flips the linked stock_purchase_items from
`collection_pool` to `assigned_to_driver`. Items now stay in
`collection_pool` after the supplier email goes out; the operator
marks them ready-to-collect from the stock-order page only after the
supplier confirms, and that's the action that promotes them to
`assigned_to_driver` (so the driver-app sees them as ready).
Without this, the stock-order "PO Sent" state was invisible — the
items jumped straight to driver-ready the moment the email left,
before any supplier confirmation. Now the PO Sent → Ready to Collect
→ Assign Driver flow is observable end-to-end.
3 new tests, full suite 1210/1210 passing.
Co-Authored-By: Claude <noreply@anthropic.com>
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
a8231fec07
commit
e75f0d6157
2 changed files with 109 additions and 27 deletions
|
|
@ -179,6 +179,7 @@ import {
|
|||
getStockPurchaseItems,
|
||||
receiveStockPurchase,
|
||||
markStockPurchaseSent,
|
||||
recordStockPurchaseSendOutcome,
|
||||
} from "./stock-purchases-service";
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -812,3 +813,92 @@ describe("listStockPurchases — total value", () => {
|
|||
expect(y.received_value).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// recordStockPurchaseSendOutcome
|
||||
// ============================================================================
|
||||
|
||||
describe("recordStockPurchaseSendOutcome", () => {
|
||||
it("does NOT auto-flip stock_purchase_items.status on success (operator must mark them ready to collect manually)", async () => {
|
||||
const p = await pbMock.collection("stock_purchases").create({
|
||||
purchase_number: "PO26071501STK",
|
||||
status: "sending",
|
||||
email_payload: { sentAt: "", sentMessageId: "" },
|
||||
});
|
||||
const it1 = await pbMock.collection("stock_purchase_items").create({
|
||||
purchase: p.id,
|
||||
status: "collection_pool",
|
||||
});
|
||||
const it2 = await pbMock.collection("stock_purchase_items").create({
|
||||
purchase: p.id,
|
||||
status: "collection_pool",
|
||||
});
|
||||
|
||||
const result = await recordStockPurchaseSendOutcome({
|
||||
purchaseId: p.id,
|
||||
outcome: "success",
|
||||
messageId: "<abc@wfsales.co.za>",
|
||||
});
|
||||
|
||||
// The stock_purchase itself flips to "sent" + stamps the message ID.
|
||||
expect(result.status).toBe("sent");
|
||||
expect(result.email_message_id).toBe("<abc@wfsales.co.za>");
|
||||
|
||||
// The items MUST stay in collection_pool — the operator marks them
|
||||
// ready-to-collect from the stock-order page only after the supplier
|
||||
// has confirmed. Same pattern as customer POs.
|
||||
const itemsAfter = await pbMock
|
||||
.collection("stock_purchase_items")
|
||||
.getFullList({ filter: `purchase = "${p.id}"` });
|
||||
expect(itemsAfter.find((i) => i.id === it1.id)?.status).toBe("collection_pool");
|
||||
expect(itemsAfter.find((i) => i.id === it2.id)?.status).toBe("collection_pool");
|
||||
});
|
||||
|
||||
it("flips stock_purchase to sent + stamps email_message_id on success", async () => {
|
||||
const p = await pbMock.collection("stock_purchases").create({
|
||||
purchase_number: "PO26071501STK",
|
||||
status: "sending",
|
||||
email_payload: { sentAt: "", sentMessageId: "" },
|
||||
});
|
||||
|
||||
const result = await recordStockPurchaseSendOutcome({
|
||||
purchaseId: p.id,
|
||||
outcome: "success",
|
||||
messageId: "<abc@wfsales.co.za>",
|
||||
});
|
||||
|
||||
expect(result.status).toBe("sent");
|
||||
expect(result.email_message_id).toBe("<abc@wfsales.co.za>");
|
||||
expect(result.email_error).toBe("");
|
||||
expect((result.email_payload as Record<string, unknown>).sentAt).toBeTruthy();
|
||||
expect((result.email_payload as Record<string, unknown>).sentMessageId).toBe(
|
||||
"<abc@wfsales.co.za>",
|
||||
);
|
||||
});
|
||||
|
||||
it("flips stock_purchase to mail_failed + stamps errorMessage on failure (does not touch items)", async () => {
|
||||
const p = await pbMock.collection("stock_purchases").create({
|
||||
purchase_number: "PO26071501STK",
|
||||
status: "sending",
|
||||
});
|
||||
const it1 = await pbMock.collection("stock_purchase_items").create({
|
||||
purchase: p.id,
|
||||
status: "collection_pool",
|
||||
});
|
||||
|
||||
const result = await recordStockPurchaseSendOutcome({
|
||||
purchaseId: p.id,
|
||||
outcome: "failure",
|
||||
errorMessage: "SMTP 550 mailbox unavailable",
|
||||
});
|
||||
|
||||
expect(result.status).toBe("mail_failed");
|
||||
expect(result.email_error).toBe("[smtp] SMTP 550 mailbox unavailable");
|
||||
|
||||
// Items are NOT touched on failure (and were never touched to begin with).
|
||||
const itemsAfter = await pbMock
|
||||
.collection("stock_purchase_items")
|
||||
.getFullList({ filter: `purchase = "${p.id}"` });
|
||||
expect(itemsAfter.find((i) => i.id === it1.id)?.status).toBe("collection_pool");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1036,11 +1036,13 @@ async function markPreSendFailure(
|
|||
* after the email goes out (success or failure).
|
||||
*
|
||||
* `outcome = "success"` flips status to `sent` + stamps delivery
|
||||
* fields + flips linked `stock_purchase_items.status` from
|
||||
* `collection_pool` to `assigned_to_driver` (so the driver-app
|
||||
* can pick them up). Note: stock lines use `assigned_to_driver`
|
||||
* instead of `assigned` because their per-line state machine has
|
||||
* different values than the customer side.
|
||||
* fields. It deliberately does NOT touch linked
|
||||
* `stock_purchase_items.status` — the operator marks items
|
||||
* ready-to-collect manually from the stock-order page after the
|
||||
* supplier confirms, which then moves them to `assigned_to_driver`.
|
||||
* Auto-flipping on send would hide the item before the supplier has
|
||||
* even confirmed (mirrors the customer-PO behaviour adopted in
|
||||
* commit 79b9309).
|
||||
*
|
||||
* `outcome = "failure"` flips to `mail_failed` + stamps error.
|
||||
*/
|
||||
|
|
@ -1081,29 +1083,19 @@ export async function recordStockPurchaseSendOutcome(
|
|||
.collection(STOCK_PURCHASES_COLL)
|
||||
.update(input.purchaseId, patch)) as StockPurchase;
|
||||
|
||||
// Flip linked stock_purchase_items from `collection_pool` to
|
||||
// `assigned_to_driver`. We don't touch `drv_id` — that's set
|
||||
// later by the assign-driver action on /collection-pool. Status
|
||||
// flip here just signals "PO is in flight; ready for the operator
|
||||
// to assign a driver when the supplier confirms".
|
||||
// Stock_purchase_items stay in `collection_pool` after the send.
|
||||
// The operator marks them ready-to-collect from the stock-order
|
||||
// page after the supplier confirms, which is what moves them to
|
||||
// `assigned_to_driver` (so the driver-app can pick them up).
|
||||
// We don't touch `drv_id` here — that's set later by the
|
||||
// assign-driver action on /collection-pool. The status names
|
||||
// align with the v4 stock-order driver flow:
|
||||
// collection_pool → assigned_to_driver → collected → booked.
|
||||
//
|
||||
// v1 note: this mirrors the old `markStockPurchaseSent` behaviour
|
||||
// (which also flipped all lines to collection_pool at the time
|
||||
// of send). The status names align with the new v4 stock-order
|
||||
// driver flow: collection_pool → assigned_to_driver → collected
|
||||
// → booked.
|
||||
const linked = await pb
|
||||
.collection(STOCK_PURCHASE_ITEMS_COLL)
|
||||
.getFullList({
|
||||
filter: `purchase = "${input.purchaseId}" && status = "collection_pool"`,
|
||||
fields: "id,status",
|
||||
});
|
||||
for (const it of linked) {
|
||||
await pb.collection(STOCK_PURCHASE_ITEMS_COLL).update(it.id, {
|
||||
status: "assigned_to_driver",
|
||||
});
|
||||
}
|
||||
|
||||
// v1 note: this used to auto-flip on send (mirroring the old
|
||||
// `markStockPurchaseSent` behaviour), but that hid items before
|
||||
// the supplier had even confirmed. Now consistent with customer
|
||||
// POs (see commit 79b9309).
|
||||
return updated;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue