From 070730e4ee2cdb3fa46582eb25c21919bd664f75 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Fri, 25 Sep 2026 17:03:05 -0400 Subject: [PATCH] fix(web): keep mock catalog dates and updates in sync --- src/mocks/index.test.ts | 21 +++++++++++ src/mocks/invoices.ts | 80 ++++++++++++++++++++++++++++++++++------- 2 files changed, 89 insertions(+), 12 deletions(-) diff --git a/src/mocks/index.test.ts b/src/mocks/index.test.ts index 774dcd9..506a8f0 100644 --- a/src/mocks/index.test.ts +++ b/src/mocks/index.test.ts @@ -67,6 +67,7 @@ describe("mocks module", () => { expect(invoice.lastUpdateActorName.length).toBeGreaterThan(0); expect(invoice.lastUpdateAt.length).toBeGreaterThan(0); expect(invoice.invoiceDate).toMatch(/^\d{4}-\d{2}-\d{2}$/); + expect(invoice.invoiceDate).not.toBe(invoice.dueDate); expect(invoice.processingBegan).toMatch(/^\d{4}-\d{2}-\d{2}$/); expect(typeof invoice.tagging).toBe("string"); expect(typeof invoice.fs).toBe("string"); @@ -101,6 +102,8 @@ describe("mocks module", () => { const updated = updateMockInvoicePayDate("inv-1", "2026-08-18"); expect(updated.payDate).toBe("2026-08-18"); + expect(updated.lastUpdateMessage).toBe("Pay date updated"); + expect(updated.lastUpdateAt).not.toBe(before?.lastUpdateAt); const after = getMockInvoices().find((invoice) => invoice.id === "inv-1"); expect(after?.payDate).toBe("2026-08-18"); @@ -148,6 +151,7 @@ describe("mocks module", () => { authorUserId: "user-1", authorName: "Alex Rivera", body: "Need another look", + createdAt: "2026-08-11T09:00:00.000Z", }); expect(withComment.comments.some((comment) => comment.body === "Need another look")).toBe(true); @@ -168,6 +172,23 @@ describe("mocks module", () => { expect(getMockApprovals().find((item) => item.invoiceId === "inv-2")?.status).toBe("approved"); }); + it("refreshes last update from a comment and payment account from the method", () => { + const commented = addMockInvoiceComment("inv-1", { + authorUserId: "user-1", + authorName: "Alex Rivera", + body: "Checked the total", + createdAt: "2026-09-01T15:00:00.000Z", + }); + expect(commented.lastUpdateAt).toBe("2026-09-01T15:00:00.000Z"); + expect(commented.lastUpdate).toBe("2026-09-01"); + expect(commented.lastUpdateMessage).toBe("Checked the total"); + expect(commented.lastUpdateActorName).toBe("Alex Rivera"); + + const method = updateMockInvoiceHeader("inv-1", { paymentMethod: "ach" }); + expect(method.paymentAccount).toBe("Operating ACH"); + expect(getMockInvoice("inv-1")?.paymentAccount).toBe("Operating ACH"); + }); + it("refreshes description and rowCount when header or lines change", () => { const before = getMockInvoice("inv-1"); expect(before?.rowCount).toBe(2); diff --git a/src/mocks/invoices.ts b/src/mocks/invoices.ts index 81cc2b5..a565190 100644 --- a/src/mocks/invoices.ts +++ b/src/mocks/invoices.ts @@ -116,17 +116,46 @@ function actionLabelForStatus(status: MockInvoiceStatus, index: number): string return NON_PENDING_ACTION_OPTIONS[index % NON_PENDING_ACTION_OPTIONS.length]; } -function derivedActivityFields(invoice: Pick) { - const latestActivity = invoice.activity[invoice.activity.length - 1]; - const lastUpdateAt = latestActivity?.createdAt ?? `${invoice.dueDate}T12:00:00.000Z`; +function derivedActivityFields(invoice: Pick) { + const events = [ + ...invoice.activity.map((entry) => ({ + at: entry.createdAt, + message: entry.message, + actor: entry.actorName, + })), + ...invoice.comments.map((comment) => ({ + at: comment.createdAt, + message: comment.body, + actor: comment.authorName, + })), + ]; + const latest = events.reduce<(typeof events)[number] | undefined>((current, event) => { + if (!current || event.at > current.at) { + return event; + } + return current; + }, undefined); + const lastUpdateAt = latest?.at ?? `${invoice.dueDate}T12:00:00.000Z`; return { lastUpdate: lastUpdateAt.slice(0, 10), - lastUpdateMessage: latestActivity?.message ?? "Invoice updated", - lastUpdateActorName: latestActivity?.actorName ?? "System", + lastUpdateMessage: latest?.message ?? "Invoice updated", + lastUpdateActorName: latest?.actor ?? "System", lastUpdateAt, }; } +function paymentAccountFor(paymentMethod: MockInvoice["paymentMethod"]): string { + return paymentMethod === "ach" ? "Operating ACH" : "Operating Check"; +} + +/** Invoice date is independent of the due date. Offset varies per seed. */ +function invoiceDateBeforeDue(dueDate: string, index: number): string { + const [year, month, day] = dueDate.split("-").map(Number); + const date = new Date(Date.UTC(year, month - 1, day)); + date.setUTCDate(date.getUTCDate() - (14 + index)); + return date.toISOString().slice(0, 10); +} + /** Keep catalog-derived fields in sync after mock mutations. */ function withRefreshedCatalogFields(invoice: MockInvoice, index: number): MockInvoice { return { @@ -134,6 +163,7 @@ function withRefreshedCatalogFields(invoice: MockInvoice, index: number): MockIn actionLabel: actionLabelForStatus(invoice.status, index), ...derivedActivityFields(invoice), description: invoice.memo, + paymentAccount: paymentAccountFor(invoice.paymentMethod), rowCount: invoice.lines.length, }; } @@ -150,14 +180,14 @@ function enrichInvoice(core: MockInvoiceCore, index: number): MockInvoice { lastUpdateMessage: "", lastUpdateActorName: "", lastUpdateAt: "", - invoiceDate: core.dueDate, + invoiceDate: invoiceDateBeforeDue(core.dueDate, index), processingBegan: core.activity[0]?.createdAt?.slice(0, 10) ?? core.dueDate, tagging: index % 2 === 0 ? "Ops" : "", fs: index % 2 === 0 ? "synced" : "", description: core.memo, invoiceType: "Standard", location: "Ronkonkoma", - paymentAccount: core.paymentMethod === "ach" ? "Operating ACH" : "Operating Check", + paymentAccount: paymentAccountFor(core.paymentMethod), relatedInvoice: null, rowCount: core.lines.length, }, @@ -549,7 +579,27 @@ export function updateMockInvoicePayDate(id: string, payDate: string | null): Mo throw new Error(`Mock invoice not found: ${id}`); } - return replaceInvoice(index, { ...invoiceStore[index], payDate }); + const current = invoiceStore[index]; + return replaceInvoice( + index, + withRefreshedCatalogFields( + { + ...current, + payDate, + activity: [ + ...current.activity, + { + id: `activity-pay-date-${Date.now()}`, + actorUserId: "user-1", + actorName: "System", + message: "Pay date updated", + createdAt: new Date().toISOString(), + }, + ], + }, + index, + ), + ); } export function updateMockInvoiceHeader(id: string, patch: MockInvoiceHeaderPatch): MockInvoice { @@ -603,10 +653,16 @@ export function addMockInvoiceComment( createdAt: comment.createdAt ?? new Date().toISOString(), }; - return replaceInvoice(index, { - ...current, - comments: [...current.comments, nextComment], - }); + return replaceInvoice( + index, + withRefreshedCatalogFields( + { + ...current, + comments: [...current.comments, nextComment], + }, + index, + ), + ); } export function transitionMockInvoiceStatus(