From 608a6ab97804c0b735e21ea6388460e17c4cf374 Mon Sep 17 00:00:00 2001 From: Dave Date: Tue, 11 Aug 2026 07:43:05 +0200 Subject: [PATCH] feat(invoicing): widen the recent-window filter from 3 to 5 days MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single-line behavioural change: buildRecentInvoiceRange now subtracts 5 days from today instead of 3. The "in last 3 days OR unprinted" rule shipped in the previous commit picks up the new window automatically — the window affects which old-but-unprinted rows are still considered "in the window" (purely a UX nuance; the unprinted branch already covers anything older). Also tighten the section-header + empty-state copy and the test fixtures to reflect the new window. The "INV-OLD-*" dates in the range test had to push back from 2026-07-05 to 2026-07-02 — 2026-07-05 fell inside the new 5-day window, which would have flipped the test's expectation (it was asserting that printed-old dates are hidden). Files: - src/app/(app)/invoicing/view-model.ts — buildRecentInvoiceRange: -3 → -5 - src/app/(app)/invoicing/view-model.test.ts — expected `from` date shifts by 2 days; test name updated - src/app/(app)/invoicing/page.tsx — section header + empty state + inline comments mention "5 days" instead of "3 days" - src/app/(app)/invoicing/page.test.tsx — test name updated; INV-OLD-*/INV-OLD-PRINTED fixture dates pushed back from 07-05 to 07-02 to stay outside the new 5-day window --- src/app/(app)/invoicing/page.test.tsx | 16 ++++++++-------- src/app/(app)/invoicing/page.tsx | 18 +++++++++--------- src/app/(app)/invoicing/view-model.test.ts | 4 ++-- src/app/(app)/invoicing/view-model.ts | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/app/(app)/invoicing/page.test.tsx b/src/app/(app)/invoicing/page.test.tsx index 025add4..e1e78fa 100644 --- a/src/app/(app)/invoicing/page.test.tsx +++ b/src/app/(app)/invoicing/page.test.tsx @@ -4,7 +4,7 @@ import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/re const invoiceWebhookFetchMock = vi.fn(); // ISO date "yesterday" — the Invoiced tab only shows rows from the last -// 3 days (`buildRecentInvoiceRange`). Hard-coding a date (e.g. +// 5 days (`buildRecentInvoiceRange`). Hard-coding a date (e.g. // "2026-07-08") silently breaks the test once the calendar rolls past // that range; using "yesterday" keeps the assertion stable over time. const YESTERDAY_ISO = (() => { @@ -368,7 +368,7 @@ describe("InvoicingPage", () => { expect(screen.getByText(/pending/i)).toBeInTheDocument(); }); - it("shows only invoiced rows from the default last-3-days range (or unprinted, regardless of date)", async () => { + it("shows only invoiced rows from the default last-5-days range (or unprinted, regardless of date)", async () => { vi.useFakeTimers(); try { vi.setSystemTime(new Date("2026-07-09T12:00:00.000Z")); @@ -405,7 +405,7 @@ describe("InvoicingPage", () => { }, }, }), - // Older than the 3-day window, but ALREADY printed — this + // Older than the 5-day window, but ALREADY printed — this // is the row the new filter excludes (printed === true drops // out of the unprinted branch). makeRawRow({ @@ -414,7 +414,7 @@ describe("InvoicingPage", () => { item_status: "invoiced", order_item_details: { invoice: { - date: "2026-07-05", + date: "2026-07-02", number: "INV-OLD-PRINTED", setBy: "u-1", setByName: "Op", @@ -444,7 +444,7 @@ describe("InvoicingPage", () => { }, }, }), - // Older than the 3-day window AND still unprinted — this is + // Older than the 5-day window AND still unprinted — this is // the new behaviour: surfaces on the Invoiced tab so the // operator can clear the backlog. makeRawRow({ @@ -453,7 +453,7 @@ describe("InvoicingPage", () => { item_status: "invoiced", order_item_details: { invoice: { - date: "2026-07-05", + date: "2026-07-02", number: "INV-OLD-UNPRINTED", setBy: "u-1", setByName: "Op", @@ -484,7 +484,7 @@ describe("InvoicingPage", () => { fireEvent.click(screen.getByRole("tab", { name: "Invoiced" })); expect(screen.getByText("INV-RECENT")).toBeInTheDocument(); - // Old + printed is filtered out (no longer in the 3-day window + // Old + printed is filtered out (no longer in the 5-day window // and the printed flag means the operator has already handled it). expect(screen.queryByText("INV-OLD-PRINTED")).not.toBeInTheDocument(); // Old + unprinted IS shown — the new "in 3 days OR unprinted" @@ -1114,7 +1114,7 @@ describe("InvoicingPage", () => { }, }, }), - // Older than the 3-day window BUT still unprinted → also + // Older than the 5-day window BUT still unprinted → also // printed. The new "in 3 days OR unprinted" filter surfaces // this row on the Invoiced tab, so the bulk button has to // catch it too. diff --git a/src/app/(app)/invoicing/page.tsx b/src/app/(app)/invoicing/page.tsx index 3f9054c..71fa684 100644 --- a/src/app/(app)/invoicing/page.tsx +++ b/src/app/(app)/invoicing/page.tsx @@ -1216,7 +1216,7 @@ export default function InvoicingPage() { * The PB filter is `booked_at != null` — we want every row the * accountant has ever booked, not just today's. The recent-range * filter is applied client-side via `recentStockBookings` so the - * shared 3-day window (`recentInvoiceRange`) stays consistent with + * shared 5-day window (`recentInvoiceRange`) stays consistent with * the invoices + delivery notes. */ const loadStockBookings = useCallback(async () => { @@ -1371,7 +1371,7 @@ export default function InvoicingPage() { // "All unprinted" — every invoice group that has at least one row // not yet printed, regardless of date. The Invoiced tab now // surfaces these on the operator's list (filter rule relaxed from - // "last 3 days" to "last 3 days OR unprinted"), so the bulk-print + // "last 3 days" to "last 5 days OR unprinted"), so the bulk-print // button matches what the operator sees. const allUnprintedInvoiceGroups = useMemo( () => invoicePrintGroups.filter((group) => !group.printed), @@ -1423,12 +1423,12 @@ export default function InvoicingPage() { // `invoicedGroups` (real invoices) and `deliveryNoteGroups` (items // marked as delivery notes). // - // Filter rule (was: "last 3 days only"; now: "last 3 days OR - // unprinted"): a row appears if it falls inside the 3-day window + // Filter rule (was: "last 3 days only"; now: "last 5 days OR + // unprinted"): a row appears if it falls inside the 5-day window // OR if it has not been printed yet. This means an invoice or DN - // that falls off the 3-day window still surfaces here until the + // that falls off the 5-day window still surfaces here until the // operator prints it, so the list doubles as the "what's still - // outstanding" view. Stock bookings use a stricter 3-day window + // outstanding" view. Stock bookings use a stricter 5-day window // only — there's no print-tracking on `stock_purchases` yet so // "unprinted stock booking" is undefined. const invoicedGroups = useMemo(() => { @@ -1468,7 +1468,7 @@ export default function InvoicingPage() { }, [invoicingRows, recentInvoiceRange.from, recentInvoiceRange.to, searchText]); /** - * Stock bookings filtered to the recent 3-day window. The query + * Stock bookings filtered to the recent 5-day window. The query * is intentionally permissive (every row with `booked_at != null`) * because the Invoiced tab is a "what paperwork landed today" * view, and the range filter matches the invoices + delivery @@ -2122,7 +2122,7 @@ export default function InvoicingPage() { )} {rows !== null && rowsView.length > 0 && activeTab === "invoiced" && invoicedGroups.length === 0 && deliveryNoteGroups.length === 0 && stockBookingGroups.length === 0 && ( - + )} {rows !== null && activeTab === "to_invoice" && toInvoiceCards.length > 0 && ( @@ -2241,7 +2241,7 @@ export default function InvoicingPage() { {rows !== null && activeTab === "invoiced" && (invoicedGroups.length > 0 || deliveryNoteGroups.length > 0 || stockBookingGroups.length > 0) && (
- {`Showing invoices and delivery notes from ${recentInvoiceRange.from} to ${recentInvoiceRange.to}, plus any unprinted — stock bookings are windowed to the last 3 days only`} + {`Showing invoices and delivery notes from ${recentInvoiceRange.from} to ${recentInvoiceRange.to}, plus any unprinted — stock bookings are windowed to the last 5 days only`}
{/* Invoices sub-section — only the "real" invoiced items go here. */} diff --git a/src/app/(app)/invoicing/view-model.test.ts b/src/app/(app)/invoicing/view-model.test.ts index 6d5028e..0721661 100644 --- a/src/app/(app)/invoicing/view-model.test.ts +++ b/src/app/(app)/invoicing/view-model.test.ts @@ -381,11 +381,11 @@ describe("buildTodayUnprintedInvoiceGroups", () => { }); describe("buildRecentInvoiceRange", () => { - it("defaults recent invoice filters to the last 3 days", () => { + it("defaults recent invoice filters to the last 5 days", () => { const today = new Date("2026-07-08T12:00:00.000Z"); const range = buildRecentInvoiceRange(today); - expect(range.from).toBe("2026-07-05"); + expect(range.from).toBe("2026-07-03"); expect(range.to).toBe("2026-07-08"); }); }); diff --git a/src/app/(app)/invoicing/view-model.ts b/src/app/(app)/invoicing/view-model.ts index be4016e..2bd4824 100644 --- a/src/app/(app)/invoicing/view-model.ts +++ b/src/app/(app)/invoicing/view-model.ts @@ -312,7 +312,7 @@ export function buildBulkInvoiceRows( export function buildRecentInvoiceRange(today = new Date()): { from: string; to: string } { const end = new Date(today); const start = new Date(today); - start.setDate(start.getDate() - 3); + start.setDate(start.getDate() - 5); return { from: start.toISOString().slice(0, 10),