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:
parent
228caf1680
commit
608a6ab978
4 changed files with 20 additions and 20 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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. */}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Reference in a new issue