Skip to main content
All reports articles

Job profitability — which work actually makes money

Per-job revenue minus parts + labor + other costs. Service-type margins surface what to push, what to reprice, what to drop.

Updated 2026-05-01

/portal/reports/jobs is the report most contractors have never seen because it's a math problem nobody had time to solve manually: take revenue, subtract parts + labor + other costs, divide by revenue, that's your margin. Repeat for every job. Average by service type. Now you know which work to push and which to reprice.

What the math is

RevenueSum of line_items on invoices for this customer within ±14 days of the appointment's completed_at. Proximity matching since invoices don't have a hard appointment_id FK yet.
PartsSum of expenses tagged appointment_id with category in (materials, parts, permits).
LaborSum of time_entries.cost_cents for the appointment. Each time entry snapshots the tech's hourly_rate_cents at clock-in so historical math doesn't drift if rates change.
Other costsOther expense categories tagged to the appointment — fuel allocation, subcontractor labor, etc.
NetRevenue − Parts − Labor − Other.
MarginNet ÷ Revenue, expressed as a percentage.

What to do with it

  1. 1
    Sort by margin: low to high

    The first 5 rows are your money-losers. Either reprice (most common — flat-rate pricing book undercharges for emergency callouts), drop the service entirely, or assign your fastest tech.

  2. 2
    Read the service-type panel

    Right side of the page shows average margin per service. "Drain cleanings 71% / AC tune-ups 58% / Water heater installs 41%" lets you push more drain calls in your marketing instead of more water heaters.

  3. 3
    Find your billing leaks

    If a job shows revenue $0 with $400 of parts + 3 hours of labor, you forgot to invoice. The report flags it loudly with a negative net column.

Per-job tile on the job page

Every completed appointment shows the same numbers inline at /portal/tech/[id]. Owner + billing roles see it; tech role doesn't (it's their labor on the line, surfacing it to them is a culture call). The tile links straight to /portal/reports/jobs for the full grid.

Setting tech hourly rates

Settings → Team → click a tech → set hourly rate. This is the FULLY-LOADED cost (payroll + payroll tax + truck overhead), not the pay rate. Rates are stored in cents on the tech record and snapshotted onto every time entry at clock-in. Updating the rate doesn't change past entries — they keep the rate that was in effect when the work was done.

The "batched invoice" warning icon

Some rows show a small alert icon next to the revenue column — that means the matched invoice covered multiple appointments (typical for monthly commercial customers). The dollar amount got split evenly across the appointments it could match. Treat those numbers as approximate, not exact.

No revenue, no margin

Jobs without an attached invoice (free callbacks, warranty work) show $0 revenue and a negative net = parts + labor cost only. That's correct — those jobs cost you money. Surface them so you know how much warranty work is actually costing.

Ready to try this in the actual product?

14-day free trial, no card charged for 14 days, cancel anytime.

More reports articles