See which roofing jobs actually made money in JobNimbus

By General Input

Open one screen every Friday to see contract value, cash collected, material and labor cost, and gross margin on every recent roofing job.

Integrations

  • JobNimbus
  • Google Sheets

Type

App

Categories

  • Finance
  • Operations

Build me an app that my owner and office manager open every Friday to see whether each roofing job actually made money. JobNimbus has no job costing built in, so the app has to assemble it from the records that are already there.

The main surface is a sortable job profitability table covering a lookback window that defaults to the last 90 days. One row per job, pulled with JobNimbus List Jobs. Columns: job name and number, customer, current status, contract value, cash collected, material spend, labor cost, gross profit in dollars, and gross margin percentage. Every column sorts, and the default sort puts the worst margin at the top. The margin cell turns red when it falls below a target margin the user sets in the app. Above the table put a summary strip: jobs scored, total contract value, total cash collected, blended gross margin, and how many jobs came in under target.

Joining the data is the core of this app. Every JobNimbus record carries a jnid, and the records attached to a job are found with the filter DSL clause {"must":[{"term":{"related.id":"<job jnid>"}}]}. The handlers use that clause to scope List Estimates, List Invoices, List Payments, List Material Orders and List Work Orders to each job. Contract value comes from the job's estimates via List Estimates and its invoices via List Invoices: show the estimate total and the invoiced total as separate figures so the office manager can see when the invoice never caught up with the estimate. Cash collected comes from List Payments. Material spend is the sum of the job's material orders from List Material Orders.

Labor is derived, not stored. Read the job's work orders with List Work Orders and price them against a crew and subcontractor rate sheet I keep in Google Sheets, read with Get Values. The rate sheet has one row per crew or subcontractor with the rate and whether it is hourly or per square. Store the spreadsheet id and range in app settings so I can point it at a different tab later. If a work order references a crew that is not on the rate sheet, flag that row as missing a rate rather than silently treating the labor as zero, and exclude it from the summary blend.

Jobs still in production are shown as provisional rather than scored. Let the user pick which job statuses count as finished; anything outside that set gets a provisional badge, a greyed out margin, and is excluded from the summary totals. A job that is half installed and half invoiced should never be presented as a real margin number.

Settings the user controls in the app: the target margin percentage (default it to 35 percent, since roofing labor typically runs 30 to 40 percent of the ticket leaving 20 to 30 percent for overhead and profit), the lookback window in days (default 90), which statuses count as finished, and the rate sheet location. These are shared app settings, not per user, because the owner and the office manager need to be looking at the same numbers.

Clicking a row opens a job detail page. The header repeats the margin math for that job. Below it: the estimate line items, fetched per estimate with Retrieve an Estimate so I can see quantities and prices rather than just a total; every material order on the job with vendor, date and total; every work order with the crew and the priced labor the app calculated; every invoice with its amount and status; and the full payment history with dates and amounts. Include a billed versus collected line so an unpaid balance is obvious at a glance.

Each job also gets an Investigate margin button, on the table row and on the detail page, that kicks off a background agent for that one job. The agent reads the job with Retrieve a Job, then walks its estimates, material orders, work orders, invoices and payments using the same related.id filter, pulling individual estimates with Retrieve an Estimate for line item detail. Its job is to work out where the money leaked. Have it look for the patterns that actually cause this in roofing: an extra material order placed after the estimate that was never billed, a change order that never made it onto an invoice, a second crew trip on the work orders that was never priced into the contract, and payments collected below the invoiced total.

The agent writes its findings back into the app so the owner can read them on the job detail page: a list of findings, each with a dollar impact and the specific records it used as evidence. It posts the same summary onto the job in JobNimbus as a note using Create an Activity. And whenever it finds work that was done but never invoiced, it opens a follow up task with Create a Task, assigned to the office manager with a due date a few days out, naming the amount and exactly what needs to be billed.

Show the agent's state inline on the row and the detail page: idle, running, or last investigated at a given time. Keep previous findings so I can see what an investigation said last month, and do not post a duplicate note on the job if I run the investigation twice on the same day.

Related prompts

Explore more prompts
Call overdue Xero customers with an AI collections agentLocal listing health board for every location you manageLet support send one-off Loops emails without an engineerStop cold emails to anyone with a live deal in PipedriveiMessage campaign console with pre-flight checks and delivery boardLinkedIn Ads budget pacing dashboard for every client accountFront desk appointment confirmation board for the next 3 daysGive your team Looker numbers without buying more seatsBuild audience segments from product usage and push to LoopsTurn the people who engage with your posts into Pipedrive leads