Adds an Invoices sub-menu under /income with a summary table (one row per invoice, click to expand the full detail) and the data model behind it. Data: - invoices + invoice_lines PocketBase collections, created idempotently by scripts/setup-invoice-collections.mjs - line items transcribed from PHI 166/167/171 with per-invoice arithmetic validation in the seed; a bad invoice is refused without blocking the rest - invoice-*.json in data/source are auto-imported, so adding an invoice is one file Booking: - manual book-in per line (Booked to), grouped by category - POST /api/invoice-lines/:id/book moves the income row rather than adding a second one, so a booking can be changed freely - scripts/book-invoice.mjs is idempotent: rows are stamped with monthly_income.invoice_line and replaced on re-run - booked amount follows lib/invoice-booking-policy.json (bookInclusiveVat), read by both the app and the CLI so they cannot disagree Money: - the R24,205.13 column on these invoices is PLUS VAT, not escalation. The seed now rejects VAT that disagrees with the stated rate. - booked amounts are VAT-inclusive per the client ruling of 2026-10-01; the VAT stays visible rather than hidden. Uploads: - drag-and-drop PDF attached to invoices.source_pdf - GET /api/invoices/:id/document proxies it — the collection is rule-locked, so a direct PocketBase /api/files link 404s in a browser Fixes a month-aggregation bug this introduced: buildIncomeRows assigned into months[i], so a machine with two income rows in one month (one per invoice, which is normal multi-site use) showed only the last. SS01 September read R20,700 instead of R35,305 while its own YTD included both. Months now accumulate, per-month profit is computed once after accumulation, and lib/rows.test.ts pins it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
39 lines
No EOL
1.5 KiB
JSON
39 lines
No EOL
1.5 KiB
JSON
{
|
|
"_comment": "Extracted from PHI 167.pdf (Std_ReqHireInvoice_DN.rpt) by coordinate-aware extraction AND confirmed by reading the rendered page visually. Totals block: SUB TOTAL 12,700.00 / Escalation 0.00 / PLUS VAT 1,905.00 / INVOICE TOTAL 14,605.00. The 1,905.00 sits under the column headed PLUS VAT - it is 15% VAT, NOT an escalation; escalation is zero. INVOICE TOTAL is therefore VAT-INCLUSIVE. Only amount_excl is booked to income. Site is taken from the invoice's own description text.",
|
|
"invoice_no": "PHI 167",
|
|
"invoice_date": "2026-09-18",
|
|
"order_no": "PHR 171",
|
|
"debtor": "Mega Infrastructure",
|
|
"debtor_no": "09MIS001",
|
|
"period_from": "2026-08-21",
|
|
"period_to": "2026-09-18",
|
|
"description": "Hire Plant SEp 2026 Halfway Gardens",
|
|
"site": "Halfway Gardens",
|
|
"section": "Plant Hire",
|
|
"category": "External Plant Hire",
|
|
"currency": "ZAR",
|
|
"vat_reg_no": "4830324812",
|
|
"source_file": "PHI 167.pdf",
|
|
"vat_pct": 15,
|
|
"subtotal": 12700,
|
|
"escalation": 0,
|
|
"vat": 1905,
|
|
"total_excl": 12700,
|
|
"total_incl": 14605,
|
|
"lines": [
|
|
{
|
|
"line_no": 1,
|
|
"machine_code": "SS-01-CASE",
|
|
"asset_code": "SS01",
|
|
"_note": "Register holds SS01 and SS02, both 'CASE420 (430)'. The invoice prints SS-01, so it maps to SS01; confirm with the client if SS02 was the one on site.",
|
|
"description": "430 Skidsteer",
|
|
"qty": 63.5,
|
|
"unit": "Hr",
|
|
"rate": 200,
|
|
"rate_type": "Dry Rate",
|
|
"amount_excl": 12700,
|
|
"vat": 1905,
|
|
"total_incl": 14605
|
|
}
|
|
]
|
|
} |