fix(invoicing): refresh a cached Invoiced tab after stock is booked
Booking stock from the To Invoice tab did not show up on the Invoiced
tab when that tab had already been visited once. The new-booking event
was ignored unless the operator happened to be on Invoiced at the time,
and switching back afterwards found a non-null `stockBookings` cache and
skipped the fetch — so the freshly booked PO stayed invisible until a
full reload.
The event handler now invalidates the cache first and only then decides
whether to re-fetch, so the tab always picks up the new row. The initial
load is deferred by a zero-delay timeout to keep the fetch out of the
tab-switch render pass.
Also corrects the `loadStockBookings` comment, which referred to a
`recentStockBookings` binding that no longer exists, and notes that rows
sort by `booked_at` rather than the stock PO's own dates.
Covered by a new test that visits Invoiced, switches to To Invoice,
fires the booking event, and asserts the second fetch happens.
This change was already present in the working tree at the start of the
session that committed it; committed here on request. The one unrelated
failure in this file ("shows a Printed badge in the Invoiced tab") is
pre-existing and was verified identical on a stashed tree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6fd11f4af0
commit
f336404f71
2 changed files with 51 additions and 8 deletions
|
|
@ -169,6 +169,7 @@ vi.mock("@/components/auth-provider", () => ({
|
|||
}));
|
||||
|
||||
import Page from "./page";
|
||||
import { NEW_BOOKING_EVENT } from "@/components/invoicing/stock-booking-section";
|
||||
|
||||
describe("InvoicingPage", () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -1579,6 +1580,42 @@ describe("InvoicingPage", () => {
|
|||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("refreshes a previously cached Invoiced list after stock is booked from To Invoice", async () => {
|
||||
stockBookingsListMock
|
||||
.mockResolvedValueOnce(
|
||||
buildPageResult([
|
||||
{
|
||||
id: "sp-old",
|
||||
purchase_number: "PO-OLD-STK",
|
||||
booked_at: YESTERDAY_ISO_AT_8AM,
|
||||
expand: { supplier: { id: "sup-old", suppliername: "Old Supply" } },
|
||||
},
|
||||
]),
|
||||
)
|
||||
.mockResolvedValueOnce(
|
||||
buildPageResult([
|
||||
{
|
||||
id: "sp-new",
|
||||
purchase_number: "PO-NEW-STK",
|
||||
booked_at: YESTERDAY_ISO_AT_8AM,
|
||||
expand: { supplier: { id: "sup-new", suppliername: "New Supply" } },
|
||||
},
|
||||
]),
|
||||
);
|
||||
mockedRows.splice(0, mockedRows.length);
|
||||
|
||||
render(<Page />);
|
||||
fireEvent.click(await screen.findByRole("tab", { name: "Invoiced" }));
|
||||
expect(await screen.findByText("PO-OLD-STK")).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole("tab", { name: "To Invoice" }));
|
||||
window.dispatchEvent(new CustomEvent(NEW_BOOKING_EVENT));
|
||||
fireEvent.click(screen.getByRole("tab", { name: "Invoiced" }));
|
||||
|
||||
expect(await screen.findByText("PO-NEW-STK")).toBeInTheDocument();
|
||||
expect(stockBookingsListMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("calls the stock-purchases picking-slip endpoint when the Print picking slip button is clicked", async () => {
|
||||
stockBookingsListMock.mockResolvedValueOnce(
|
||||
buildPageResult([
|
||||
|
|
|
|||
|
|
@ -1440,9 +1440,10 @@ 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 5-day window (`recentInvoiceRange`) stays consistent with
|
||||
* the invoices + delivery notes.
|
||||
* filter is applied client-side so the shared 5-day window
|
||||
* (`recentInvoiceRange`) stays consistent with invoices and delivery
|
||||
* notes. Rows are sorted by `booked_at`, never the stock PO's order
|
||||
* or creation date.
|
||||
*/
|
||||
const loadStockBookings = useCallback(async () => {
|
||||
try {
|
||||
|
|
@ -1473,15 +1474,20 @@ export default function InvoicingPage() {
|
|||
useEffect(() => {
|
||||
if (activeTab !== "invoiced") return;
|
||||
if (stockBookings !== null) return;
|
||||
void loadStockBookings();
|
||||
const handle = window.setTimeout(() => {
|
||||
void loadStockBookings();
|
||||
}, 0);
|
||||
return () => window.clearTimeout(handle);
|
||||
}, [activeTab, stockBookings, loadStockBookings]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => {
|
||||
// Re-fetch only when on the Invoiced tab — if the operator is
|
||||
// still on To Invoice, the StockBookingSection's own refresh
|
||||
// path is enough and the next mount of this effect will pick
|
||||
// up the new row.
|
||||
// The Invoiced tab may already have a cached list from an earlier
|
||||
// visit. In that case a booking made while the To Invoice tab is
|
||||
// active used to be missed: the event was ignored and switching
|
||||
// back would see a non-null cache and skip its load. Invalidate
|
||||
// first so the tab always fetches the newly booked PO.
|
||||
setStockBookings(null);
|
||||
if (activeTab === "invoiced") {
|
||||
void loadStockBookings();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue