The job margin tracker: find out which jobs actually made you money
One job code on everything, and a screen that puts your worst-priced work at the top
- A job code written on the work before it starts - if it is assigned afterwards from memory, the numbers will be wrong and look right
- Time recorded per job by whoever does the work, daily; a weekly reconstruction is a guess
- A loaded internal hourly cost per person: annual employer cost divided by the hours they are actually on jobs, not their gross wage
- Supplier invoices booked against the job, not just against the supplier
- About 4 GB of RAM on the box - Metabase runs on the JVM and will swap on 1 GB
- One overhead figure you are willing to defend: last year's costs that belong to no job, divided by total direct job hours
Version history
Your accounts tell you the year made money. They do not tell you which jobs made it, and which ones your good jobs paid for. This one closes that gap with tools you may already be running.
The problem
Revenue is recorded per job. You invoiced it, so it has a number and a name. The two biggest costs are not recorded per job at all: the hours end up on a paper timesheet or in someone's head, and the materials end up on a supplier invoice that names the supplier, not the job. At month end you have a bank balance and a feeling.
That gap has one expensive consequence: you keep quoting from memory, and memory keeps the jobs that went smoothly. The one with the second visit, the extra trip to the wholesaler and the hour on the phone blurs into the average. So the price stays where it was, and the job that loses money gets quoted again next month at the same price.
It is the same reason we sub-meter a building. The total bill tells you it is expensive. The sub-meter tells you it is the ventilation. The job-management platforms that give you the sub-meter are priced per user per month, which is the wrong shape when half your team touches the system twice a week, and they hold the numbers you would want to keep for ten years.
The architecture
│
┌─────┴──────────────┬────────────────────────┐
▼ ▼ ▼
[ Kimai ] [ Invoice Ninja ] [ Paperless-ngx ]
hours × internal project + expenses supplier PDF with a
rate = labour cost + customer invoice job_code custom field
│ │ │
└─────┬──────────────┴────────────────────────┘
▼
[ n8n — nightly collector ] three API pulls, one upsert
▼
[ PostgreSQL — table job_facts, one row per job ]
│ + overhead_rate table (€ per direct hour)
▼
[ Metabase — one screen, sorted worst margin first ]
Untagged spend gets its own row. If that row is big, the rest is fiction.
The whole blueprint is one rule: one short job code, written on everything, before the work starts. Kimai knows what an hour costs you. Invoice Ninja knows what you charged and what you bought. The nightly job puts the two next to each other. Nothing here is clever, and that is the point — the moment the code is optional, the numbers stop being true.
Tool choices — and why
Kimai (AGPL-3.0, 4.9k stars, release 2.63.0 on 03/08/2026) is the only piece in the stack that knows what your labour actually costs. Its documentation is explicit about the two-rate model: the hourly price is "what actually goes on an invoice", the internal price is "what your employees cost you", and both are stored on every time record. Rates can be set per user, per customer, per project or per activity. That second number is the whole reason Kimai is here rather than any other timer.
Invoice Ninja (Elastic License 2.0, 10.0k stars, v5.13.30 on 06/08/2026) carries the money side: projects, expenses, vendors and purchase orders, and the customer invoice, all behind one REST API. The job code lives in the invoice's po_number field, or in one of the four custom fields, and expenses book straight against the project. Licence in plain terms: free to run for your own business, you just may not resell it to others as a hosted service.
Paperless-ngx (GPL-3.0, 44k stars, pushed 07/08/2026) is optional. If you already run it for receipt intake, give it a job_code custom field and its API will filter on it directly. If you do not, book the supplier invoice as an Invoice Ninja expense and skip this box — one place for costs beats two.
Metabase (AGPL outside the enterprise directory, 48.6k stars, v0.63.2 on 29/07/2026) is the screen. Be honest about what it costs in RAM: Metabase's own production guide asks for 1 core and 1 GB as a baseline, plus 1 core and 2 GB for every 20 concurrent users, and it runs on the JVM. On a 1 GB box it will swap and annoy you. Budget 4 GB for the whole stack. Grafana (AGPL-3.0, 76k stars) is the lighter alternative if the box is small — you write the SQL yourself instead of clicking it together.
n8n (Sustainable Use License, free for internal business use, 200k stars) runs the nightly collection. If you already have it doing invoice chasing or receipt intake, this is one more workflow, not one more server.
Monthly cost. Software €0 across the stack. Hosting: a 4 GB VPS is roughly €8–15/month at current European list prices, and it runs everything above. If Invoice Ninja and n8n are already on that box, the marginal cost of this blueprint is the RAM for Metabase and nothing else.
Setup outline
1. Fix the job code format first: short, sortable, spoken out loud without confusion. 2608-014 is year, month, sequence. Write the rule where the work is booked in. Working when: two different people quote the same job the same way.
2. Kimai: one project per job, named with the code. Set each person's internal rate to their loaded cost, not their gross wage. Working when: a 4-hour entry shows both a customer value and a lower-or-higher internal cost.
3. Invoice Ninja: matching project, job code in po_number on the invoice, supplier invoices booked as expenses against that project. Working when: the project page shows a spend total you recognise.
4. Paperless-ngx, if you use it: add the job_code custom field and fill it at intake, not later.
5. n8n, nightly: pull Kimai timesheets, Invoice Ninja invoices and expenses, and Paperless documents. Upsert one row per job into job_facts — code, revenue, labour cost, material cost, hours.
6. Overhead: one small table, month and € per direct hour. Take last year's costs that belong to no job (rent, insurance, software, accountant, your own admin time), divide by total direct job hours, recompute once a quarter.
7. Metabase: one question. Job, revenue, labour, materials, overhead, margin in € and in %. Sort ascending on margin %, so the losses are at the top where you cannot miss them. Pin it.
8. Pilot on one job type for two weeks before you tag everything. Working when: you can explain every number on the worst job without opening a second tool.
Pitfalls — the real ones
Untagged cost does not look like a gap, it looks like profit. Your first month will show a handful of jobs at 70% margin. Those are the ones where nobody logged the hours. Put a "no job code" row on the dashboard and read it before anything else: if it holds 20% of the spend, every margin on that screen is wrong by an unknown amount.
Gross wage is not what an hour costs you. Employer contributions, holiday pay, sick days, the van, the tools, the phone. Use annual employer cost divided by the hours that person is actually on jobs — around 1,600 a year for a full-timer, not 1,800. Get this wrong and every job reads 25–40% healthier than it is, which is worse than not measuring at all.
Do not backfill. Start on the next job. Reconstructing last year from memory produces numbers that feel authoritative and are not, and it is where these projects die before they produce anything.
A dashboard you have to remember to open is a dashboard you forget. Have n8n mail you the five worst-margin jobs every Monday morning. The screen is for the follow-up question, not the alert.
Margin is not cash. A 30% job invoiced 90 days ago has not paid anyone's wages yet. Different question, different blueprint.
Your team will read it as surveillance unless you say otherwise. Report by job, never by person, and say plainly that it prices work, not people. In the EU, time records are personal data: keep them only as long as you need them, and tell staff what is collected.
Verified repos
Kimai — AGPL-3.0, 4.9k stars, release 2.63.0 (03/08/2026)
Invoice Ninja — Elastic License 2.0, 10.0k stars, v5.13.30 (06/08/2026)
Metabase — AGPL-3.0 outside the enterprise directory, 48.6k stars, v0.63.2 (29/07/2026)
Grafana — AGPL-3.0, 76k stars, active (August 2026)
Paperless-ngx — GPL-3.0, 44k stars, active (August 2026)
n8n — Sustainable Use License (free self-hosted internal use), 200k stars, active (August 2026)
Get the next blueprint as it publishes — free, practical, verified tooling only.
Subscribe · Want it audited or extended for your business? DM @RXed_EU
Get this blueprint tailored to your business — free
Tell us what you run and where. We research your sector's specifics — rules, tools, customer habits — and send you a version of this blueprint rebuilt for your situation, usually within 24 hours.