Two changes to the income overview, sharing one rule for what "the month" means (lib/income-month.ts). POSTED INCOME PER SITE /income gains Vehicles / Sites views, and /sites defaults to the SAME posted-income view with a separate Internal billed invoices view. buildSiteIncomeRows (lib/site-income.ts) is the shared calculation: actual monthly_income supplies the amount, year and month, and the linked invoice supplies its historical site. It never uses assets.site, fuel-log locations or today's map assignment. - Unposted invoice lines are billing, not posted income. - Historical workbook income with no site evidence stays in Unallocated historical income; external income, whose site field names the SUPPLIER, stays in Unallocated external income. - Missing source references stay visible as unallocated invoice income. No group is dropped to make a site list look cleaner and no site is guessed to force a total. - No site profit is calculated without an explicit cost-allocation rule. - Invoice writes use revalidateInvoicePages, which invalidates both registers, Income, Sites/details, Charts and Dashboard. THE LIVE MONTH The tiles show the year-to-date income and profit AND the current month, and every vehicle card shows the current month income beside its year figure. - The month is the live CALENDAR month and it is shown even at R0. Income for a month is usually entered near the end of it, so the current month is a figure that starts at R0 and grows. - The month index is computed on the SERVER and passed down as a number. The browser never works it out for itself: two clocks disagreeing would show two different months on the same page. - A month profit is a dash until that month costs are posted. September and October carry invoice income with no posted fixed costs (their costs exist only as forecast rows), so a profit there would be income minus zero - a 100% margin that does not tie back to the year-to-date profit. It becomes a number by itself the moment the workbook posts that month. - The 12-month strip shows the live month as R0 rather than a dash, so a card cannot contradict itself. Months after it stay a dash. - The sparkline on a collapsed card is hidden below xl: the content column is only ~700px wide between md and xl, and the vehicle name must not be truncated to pay for a decorative trend.
25 lines
1.3 KiB
TypeScript
25 lines
1.3 KiB
TypeScript
import { revalidatePath } from "next/cache";
|
|
import { INVOICE_PATHS } from "./invoice-routes";
|
|
|
|
// SERVER-SIDE ONLY — imports `next/cache`, so nothing client-side may pull this in. Route
|
|
// paths live in ./invoice-routes.ts for exactly that reason.
|
|
//
|
|
// Every mutation path must refresh BOTH halves of the register. Booking a line, importing
|
|
// a PDF and retrying an intake can all land on either page depending on the debtor, so
|
|
// revalidating only one leaves the other showing stale rows.
|
|
//
|
|
// Revalidating a page that did not change costs a re-render, not correctness — so the
|
|
// asymmetry is free to fix and expensive to miss. Use `revalidateInvoicePages` instead of a
|
|
// bare `revalidatePath` for anything that writes to `invoices` or `invoice_lines`.
|
|
|
|
// Invoice amounts and bookings feed every one of these readers. Keep them here so
|
|
// upload, retry, clear and reassignment cannot each invalidate a different subset.
|
|
// Dynamic detail patterns require the "page" type in Next's revalidation API.
|
|
const READERS = [...INVOICE_PATHS, "/income", "/sites", "/charts", "/", "/sites/[id]"];
|
|
|
|
export function revalidateInvoicePages(...extra: string[]) {
|
|
for (const path of new Set([...READERS, ...extra])) {
|
|
if (path.includes("[")) revalidatePath(path, "page");
|
|
else revalidatePath(path);
|
|
}
|
|
}
|