feat(invoicing): widen the recent-window filter from 3 to 5 days

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
This commit is contained in:
Dave 2026-08-11 07:43:05 +02:00
parent 228caf1680
commit 608a6ab978
4 changed files with 20 additions and 20 deletions

View file

@ -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.

View file

@ -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 && (
<EmptyState message="No invoices, delivery notes, or stock bookings match the current filters (last 3 days + unprinted)." />
<EmptyState message="No invoices, delivery notes, or stock bookings match the current filters (last 5 days + unprinted)." />
)}
{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) && (
<section className="space-y-6">
<div className="text-sm text-muted-foreground">
{`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`}
</div>
{/* Invoices sub-section — only the "real" invoiced items go here. */}

View file

@ -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");
});
});

View file

@ -312,7 +312,7 @@ export function buildBulkInvoiceRows<T extends InvoicingRow>(
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),