diff --git a/src/app/api/v1/buyer-tasks/route.test.ts b/src/app/api/v1/buyer-tasks/route.test.ts index 306d126..fc86758 100644 --- a/src/app/api/v1/buyer-tasks/route.test.ts +++ b/src/app/api/v1/buyer-tasks/route.test.ts @@ -35,6 +35,8 @@ describe("GET /api/v1/buyer-tasks", () => { poNumber: null, supplierEmail: null, emailError: null, + emailPayload: null, + additionalAttachments: [], step: "needs_po", source: "customer", }, @@ -65,6 +67,8 @@ describe("GET /api/v1/buyer-tasks", () => { poNumber: "PO-26080301", supplierEmail: "orders@steelco.example", emailError: "[webhook] Webhook returned HTTP 502", + emailPayload: null, + additionalAttachments: [], step: "po_draft", source: "customer", }, diff --git a/src/components/buyer-tasks/buyer-task-group-card.test.tsx b/src/components/buyer-tasks/buyer-task-group-card.test.tsx index c4d5734..5744a1f 100644 --- a/src/components/buyer-tasks/buyer-task-group-card.test.tsx +++ b/src/components/buyer-tasks/buyer-task-group-card.test.tsx @@ -19,6 +19,8 @@ const baseGroup = { poStatus: null, poNumber: null, emailError: null, + emailPayload: null, + additionalAttachments: [], supplierEmail: null, step: "needs_po" as const, source: "customer" as const, diff --git a/src/components/buyer-tasks/buyer-tasks-page.test.tsx b/src/components/buyer-tasks/buyer-tasks-page.test.tsx index 71bf1a3..365ed4d 100644 --- a/src/components/buyer-tasks/buyer-tasks-page.test.tsx +++ b/src/components/buyer-tasks/buyer-tasks-page.test.tsx @@ -2,6 +2,16 @@ import { describe, it, expect, beforeEach, vi } from "vitest"; import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import { BuyerTasksPage } from "./buyer-tasks-page"; +vi.mock("@/lib/pocketbase", () => ({ + default: () => ({ + collection: () => ({ + // Keep the line-count lookup pending so this test observes the + // synchronous resend prefill rather than the async default-body preview. + getFullList: () => new Promise(() => {}), + }), + }), +})); + const sampleGroup = { groupId: "order-1::sup-1", orderId: "order-1", @@ -19,6 +29,8 @@ const sampleGroup = { poStatus: null, poNumber: null, supplierEmail: null, + emailPayload: null, + additionalAttachments: [], step: "needs_po" as const, }; @@ -83,7 +95,7 @@ describe("BuyerTasksPage", () => { it("clicking Re-Send on a po_sent card opens SendPoModal", async () => { const poSentGroup = { ...sampleGroup, - groupId: "order-1::sup-1::po-sent", + groupId: "order-1::sup-1::po-sent-open", purchaseOrderId: "po-1", poStatus: "sent" as const, poNumber: "PO-TEST-001", @@ -104,6 +116,55 @@ describe("BuyerTasksPage", () => { expect(await screen.findByText(/resend purchase order po-test-001/i)).toBeInTheDocument(); }); + it("clicking Re-Send pre-fills the modal with the previous email composition", async () => { + const poSentGroup = { + ...sampleGroup, + groupId: "order-1::sup-1::po-sent", + purchaseOrderId: "po-1", + poStatus: "sent" as const, + poNumber: "PO-TEST-001", + supplierEmail: "supplier@example.com", + emailPayload: { + to: "previous@example.com", + cc: "copy@example.com", + bcc: "blind@example.com", + subject: "Old subject", + body: "
Old body
", + bodyText: "Old body", + }, + additionalAttachments: ["drawing.pdf"], + step: "po_sent" as const, + }; + vi.spyOn(global, "fetch").mockImplementation(async (input) => { + const url = String(input); + const groups = url.includes("tab=po_sent") ? [poSentGroup] : []; + return new Response(JSON.stringify({ groups }), { status: 200 }); + }); + + render(Old body
", + bodyText: "Old body", + }, + additional_attachments: ["drawing.pdf"], expand: { supplier: { id: "sup-1", @@ -409,6 +418,15 @@ describe("listBuyerTasks", () => { expect(groups).toHaveLength(1); expect(groups[0].poNumber).toBe("PO-26072901"); expect(groups[0].supplierEmail).toBe("orders@steelco.example"); + expect(groups[0].emailPayload).toEqual({ + to: "previous@example.com", + cc: "copy@example.com", + bcc: "blind@example.com", + subject: "Old subject", + body: "Old body
", + bodyText: "Old body", + }); + expect(groups[0].additionalAttachments).toEqual(["drawing.pdf"]); }); it("populates emailError from the related PO when the PO has an email_error", async () => { diff --git a/src/lib/server/buyer-tasks-service.ts b/src/lib/server/buyer-tasks-service.ts index 87bfdac..1281823 100644 --- a/src/lib/server/buyer-tasks-service.ts +++ b/src/lib/server/buyer-tasks-service.ts @@ -4,6 +4,7 @@ import { generatePurchaseOrderPdf, PurchaseOrderPdfValidationError, } from "@/lib/server/purchase-orders-service"; +import type { PurchaseOrderEmailPayload } from "@/lib/types"; export type BuyerTaskTab = "needs_po" | "po_draft" | "po_sent"; @@ -50,6 +51,10 @@ export type BuyerTaskGroup = { * layout. The full reason remains visible in the order page's send-modal * banner. */ emailError: string | null; + /** Last composed outbound email, used to prefill the resend modal. */ + emailPayload: PurchaseOrderEmailPayload | null; + /** File tokens for additional supplier attachments. */ + additionalAttachments: string[]; step: BuyerTaskTab; source: BuyerTaskGroupSource; }; @@ -111,7 +116,7 @@ export async function listBuyerTasks(tab: BuyerTaskTab): Promise