feat(buyer-tasks): surface email_error on buyer task groups
Adds an emailError field on BuyerTaskGroup so the UI can render the send-pipeline failure reason. The value is truncated to 200 chars at the service boundary to keep the card layout bounded. Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
83bfd294b7
commit
964b3d51b2
2 changed files with 41 additions and 0 deletions
|
|
@ -284,6 +284,34 @@ describe("listBuyerTasks", () => {
|
|||
expect(groups[0].supplierEmail).toBe("orders@steelco.example");
|
||||
});
|
||||
|
||||
it("populates emailError from the related PO when the PO has an email_error", async () => {
|
||||
const { admin } = makeAdmin([
|
||||
baseItem({
|
||||
purchase_order: "po-1",
|
||||
item_status: "po_generated",
|
||||
expand: {
|
||||
order: baseOrder,
|
||||
supplier: baseSupplier,
|
||||
purchase_order: {
|
||||
id: "po-1",
|
||||
status: "draft",
|
||||
po_number: "PO-26080301",
|
||||
email_error: "[webhook] Webhook returned HTTP 502",
|
||||
expand: {
|
||||
supplier: { id: "sup-1", email: "orders@steelco.example" },
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
vi.mocked(getAdminPocketBase).mockResolvedValue(admin as never);
|
||||
|
||||
const groups = await listBuyerTasks("po_draft");
|
||||
|
||||
expect(groups).toHaveLength(1);
|
||||
expect(groups[0].emailError).toBe("[webhook] Webhook returned HTTP 502");
|
||||
});
|
||||
|
||||
it("leaves poNumber and supplierEmail null when the PO has no expand", async () => {
|
||||
const { admin } = makeAdmin([
|
||||
baseItem({
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ export type BuyerTaskGroup = {
|
|||
poStatus: "draft" | "sent" | "sending" | "received" | "mail_failed" | "cancelled" | null;
|
||||
poNumber: string | null;
|
||||
supplierEmail: string | null;
|
||||
/** Last send-pipeline error. Null on a clean draft or after a successful send.
|
||||
* Pulled from `purchase_orders.email_error` and truncated to 200 chars at
|
||||
* the service boundary so a runaway SMTP error doesn't blow up the card
|
||||
* layout. The full reason remains visible in the order page's send-modal
|
||||
* banner. */
|
||||
emailError: string | null;
|
||||
step: BuyerTaskTab;
|
||||
};
|
||||
|
||||
|
|
@ -103,6 +109,13 @@ export async function listBuyerTasks(tab: BuyerTaskTab): Promise<BuyerTaskGroup[
|
|||
poStatus: po?.status ?? null,
|
||||
poNumber: po && typeof po === "object" && "po_number" in po ? String((po as { po_number?: string }).po_number ?? "") || null : null,
|
||||
supplierEmail: (po as { expand?: { supplier?: { email?: string } } } | undefined)?.expand?.supplier?.email ?? null,
|
||||
// Truncate the SMTP error to 200 chars at the service boundary so
|
||||
// a runaway error string can't blow up the card layout. The full
|
||||
// reason is still visible in the order page's send-modal banner.
|
||||
emailError:
|
||||
(po as { email_error?: string } | undefined)?.email_error
|
||||
? String((po as { email_error?: string }).email_error).slice(0, 200)
|
||||
: null,
|
||||
step: tab,
|
||||
};
|
||||
groups.set(groupId, group);
|
||||
|
|
|
|||
Loading…
Reference in a new issue