---
name: evolution-invoiceitems-jobid-source-of-truth
description: "invoiceitems.jobid (line-level) is the source of truth for job/project revenue attribution, not invoices.jobid (header)"
metadata: 
  node_type: memory
  type: project
  originSessionId: f92a7981-4c77-467a-8023-f76d11c21f2c
---

For attributing invoice revenue to a job/project, group on **line-level `invoiceitems.jobid`**, never header `invoices.jobid`.

**Why:** header holds one job; a single invoice can span multiple jobs, and lines can be job-tagged while the header jobid is blank. Dev tenant `85-1384947162` (2026-07): 32080/33722 items carry a jobid; 15 lines had a job while their header had none, 3 lines differed from a non-zero header, only 1 reverse orphan. So the line column is more complete AND catches multi-job invoices the header cannot.

**How manual lines get their jobid** (`invoiceaddsave.php` ~L625-626): new/manual lines don't post `invoiceitemjobid[]` (the JS row builder omits it — only server-rendered existing rows emit it), so `$itemjobid = $jobid` (header) on insert; L626 clears line jobid when the header job is removed. Net: line jobid tracks the header for the normal single-job case, and diverges only for genuine per-line job assignment.

**How to apply:** project-revenue reports must all group on `invoiceitems.jobid`. Fixed 2026-07-16: `reports/tsProjectLabourMarginSave.php` (revenue query) and `reports/projectClosedVariation.inc` (invoiced subquery) migrated from header to line to match `projectWipSummary`/`projectBillableItems`. Note projectClosedVariation now sums `invoiceitems.total` (like projectWipSummary) instead of `invoices.total`, so its numbers shift cent-level (FLOAT formula diff — see [[evolution_float_money_columns]] / [[evolution_invoice_total_reconciliation]]) plus larger for genuinely reattributed invoices. `invoicesByRep*`/`salesreports.php` also join on header jobid but attribute to sales rep, not project — different purpose, left as-is.
