From 1b763ae99db09aaee4df764c49769ed9255a0f6b Mon Sep 17 00:00:00 2001 From: David Date: Thu, 8 Oct 2026 12:07:07 +0200 Subject: [PATCH] Keep a site's invoice table consistent with its own totals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit buildSiteSummaries counts only internal invoice lines towards a site, but this page built its line-item table from every line whose invoice named the site. On a site carrying both, the table would have listed lines the totals excluded — the two halves of one page disagreeing. Both now filter on the internal party, so the table and the totals are always the same set. A site left with no internal invoices has no summary and notFound() fires, which is correct: "Move containers" now returns a 404 rather than being presented as a yard. --- app/(dashboard)/sites/[id]/page.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/(dashboard)/sites/[id]/page.tsx b/app/(dashboard)/sites/[id]/page.tsx index 580d55d..f942d53 100644 --- a/app/(dashboard)/sites/[id]/page.tsx +++ b/app/(dashboard)/sites/[id]/page.tsx @@ -4,6 +4,7 @@ import { notFound } from "next/navigation"; import { getAssets, getInvoices, getInvoiceLines, getSites, joinBookedLines, MONTH_NAMES } from "@/lib/server-data"; import { buildSiteSummaries } from "@/lib/sites"; import { INTERNAL_INVOICES_PATH, EXTERNAL_INVOICES_PATH } from "@/lib/invoice-routes"; +import { isInternalParty } from "@/lib/invoice-party"; import { ZAR2 } from "@/lib/utils"; import { ScrollFade } from "@/components/ScrollFade"; import { ArrowLeft, MapPin, FileText } from "lucide-react"; @@ -38,8 +39,13 @@ export default async function SiteDetailPage({ params }: { params: Promise<{ id: const invById = new Map(invoices.map((i) => [i.id, i])); const assetById = new Map(assets.map((a) => [a.id, a])); + // Internal invoices only, exactly as buildSiteSummaries counts them. Without this the + // table below would list lines the site's totals exclude — the two must not disagree. const siteLines = joined - .filter((l) => invById.get(l.invoice)?.site === site.name) + .filter((l) => { + const inv = invById.get(l.invoice); + return inv?.site === site.name && isInternalParty(inv); + }) .sort((a, b) => (invById.get(b.invoice)!.invoice_date).localeCompare(invById.get(a.invoice)!.invoice_date) || a.line_no - b.line_no); return (