# Roll-Off Rentals Dispatcher Page
This article covers the Roll-Off Rentals page at `/roll-off-rentals` — the single dispatcher screen that shows every roll-off container you have on the ground, what's coming due, what's in overage, and what money the rental side of the business is currently on the hook for. It's the day-to-day ledger the office scans every morning.
For the deep billing mechanics — how a rental term turns into an invoice, what the tiered day/week/month math does, how the final statement consolidates — see the Roll-Off Swap Cycle Workflow. This article is about the page and the operator actions on it.
---
ROLD-1: When to open this page
Open Roll-Off Rentals when you want to answer any of these questions:
Which containers are past their expected term today? (i.e. who's in overage right now)Which pickups are due in the next week?What's the total monthly rental revenue I have on rent right now?Which of my open rentals are missing rates and would silently ship a $0 invoice?Which of Driver Ana's containers are on the ground?The page is scoped to your organization and to open (or completed) rental terms. It doesn't show one-off jobs, cycle-billed pumping, or customer-portal traffic — just the roll-off rental ledger.
Who sees it: office staff (admin or dispatcher). Drivers and customers do not see this page.
---
ROLD-2: The page shell
The page has four regions, top to bottom:
Header — title, New Rental button, tabs for Open vs CompletedKPI bar — four tiles, always computed from open rentals regardless of tabSearch + filter + sort row — one input and four dropdownsRental table — one row per rental term, with per-row actionsThe customer-profile Rentals & Service section (on a customer's profile page) shows the same rows scoped to that one customer, with the same edit / schedule / add-fee / final-invoice actions. Everything works the same way; the two surfaces share the same components.
---
ROLD-3: The four KPI tiles
The KPI bar always reflects open rentals only. Even when you flip to the Completed tab, the tiles keep showing "here's what's happening right now with rentals still on the ground." The four:
Open Rentals — how many rental terms are currently `scheduled`, `active`, or `pending_pickup`. This is your live inventory count on the customer side.In Overage — how many open rentals are past their included term. Loud red when non-zero. This is the money-at-risk tile. A term counts as in-overage when it's been delivered (or flagged `pending_pickup`) and today is past `expected_end_date`. Subline says "Past included term."Pickups Due This Week — how many delivered, in-term rentals have expected pickup landing within the next 7 days. Your morning-planning number for tomorrow's routes.Total Monthly Rental Revenue — sum of the `base_rate` on every open rental. Subline says "Contracted base rate, open rentals." This is what the rental side of the business would bill this month if every term picked up on time.If a tile ever reads $0 or 0 unexpectedly, cross-check the actual list — a filter or an empty-org state, not a broken KPI.
---
ROLD-4: The tabs (Open vs Completed)
Open — statuses `scheduled`, `active`, `pending_pickup`. This is the default.Completed — statuses `completed`, `cancelled`. Historical rentals + anything you voided before delivery.Row actions differ by tab because the operative questions differ:
Open-tab rows have Swap, Pickup, Add Fee, and Edit actions.Completed-tab rows have Generate Final Invoice (for terms with no invoice yet) and Add Fee where applicable.The status column derives from the live state of each row (see ROLD-8), so a row that looks fine on Open ("In-term") and a row that reads "Overage" both live under the same table — the sort/filter combo is how you slice them.
---
ROLD-5: Search + filters + sort
There's one search field and four dropdowns; they compose (all applied together).
Search — matches the customer name, container type label, container item code (`#1019`), and debris type. Case-insensitive substring. Empty = no search filter.Container type — populated from the container types actually on the current tab. If you rent only 20-yard boxes, that's the only option. "All container types" clears it.Overage status — All / In-term / Overage. "In-term" hides anything past its expected end; "Overage" shows only those past. Uses the same predicate as the In Overage KPI.Driver — populated from drivers who actually appear on the current tab's open next-action jobs. If Ana has no active swaps/pickups/deliveries queued, she doesn't appear here.Sort by — Days out (default) / Customer / Term start. "Days out" surfaces longest-on-site first — that's where overage risk concentrates.Empty results show one of two messages:
No rentals at all on this tab — you haven't started one yet, or every term is on the other tab. The Open-tab empty state suggests creating a delivery job with a roll-off container to start one.Filters excluded everything — you have terms, but no row matches your current filters. Adjust or clear.---
New Rental doesn't open a modal — it drops you into the Jobs & Dispatch wizard with the create-job flow pre-opened. You pick Delivery as the job type and choose a roll-off container from the product list. Saving the delivery job auto-creates the rental term.
Reasons it works this way:
The wizard already knows how to pick customer, service location, driver, and scheduling — no reason to duplicate that flow.The delivery job is the term's origin. The term inherits the customer, site, product, start date, and initial base rate from the delivery.You can create the delivery without setting all the term rates yet (see ROLD-9). The term saves in `scheduled`; rates can be filled in from Edit later.On mobile, the New Rental button is full-width below the header. On desktop it sits inline in the top-right of the header.
---
ROLD-7: One row explained
Each row is a rental term. Columns:
Customer — the customer name and, if set, the `debris_type` as a small sub-line. Debris type drives disposal routing when the container comes in (e.g. "Construction & Demolition" vs "Roofing"); it's optional.Container — the product label (e.g. "20-Yard Roll-Off"), plus the container's item code (`#1019`) if a specific serialized unit is assigned. Also shows two possible sub-lines: - `N completed swap(s)` — how many mid-term pulls this term has already logged (a swap counts once it's completed with a weight ticket)
- `N pull(s) missing weight` in amber — completed swaps/pickups where the driver didn't record `weight_tons` on terms that bill tonnage overage. Money at risk: no weight = no tonnage overage on the final statement. Chase the driver.
Status — a colored pill. See ROLD-8 for what each label means.Term — `start_date → expected_end_date` with a sub-line showing days remaining (or "N day(s) overdue" in red when past the expected end). If the term hasn't been delivered yet, the cell reads "Not delivered · N day term" using `included_days` from the term.Rates — a small stack of rate summaries: haul price per pull, included tons + overage-per-ton, daily overage rate. If none of the three are set, shows "Rates not set" in amber.Actions — the button row (see ROLD-10).---
ROLD-8: The status column and its live derivation
The status pill is computed at render time from the term's shape, not stored on the row. That means an overdue container never sits behind a stale "Scheduled" label. The priority order:
Closed (gray) — the term is `completed` or `cancelled`.Overage (red) — the term is delivered and either flagged `pending_pickup` by the system, or today is past `expected_end_date`. Overage trumps any next-action label: if a pickup is scheduled but the term is already past its end date, the row reads Overage, not "Pickup scheduled."Pickup scheduled (blue) — there's an open pickup job on the books.Swap scheduled (blue) — there's an open swap job on the books.Scheduled (blue) — delivery hasn't happened yet.Nearing overage (amber) — delivered, still in-term, but within 3 days of `expected_end_date`. This is your "chase for pickup confirmation" window.In-term (green) — everything is fine, box is out, plenty of time left.The base row status pill (from `TERM_STATUS_BADGE`) uses the stored status — Scheduled / Active / Pickup due / Completed / Cancelled — so raw storage state stays visible too, but the operational read is the live derivation above.
---
ROLD-9: Editing rental term rates
Click the pencil on an active row to open the term editor. Fields:
Base rate — dollars per pull (delivery, swap, pickup each count as one pull, each stages one haul line)Included tons — the tonnage allowance included in each pull (before overage kicks in)Overage per ton — dollars per ton above the allowanceDaily overage rate — dollars per day past `expected_end_date` (only fires on final pickup, and only if the term didn't get re-tiered — see the swap-cycle article for the interaction)Expected end date — you can push it out or pull it in mid-term; changes take effect for the next scheduling and final-statement calculationDebris type — free-text; affects disposal routing and shows in the customer sub-lineRates entered late auto-recompute past pulls. If a swap completed while the term had no haul rate, saving a rate on the term retro-recomputes the term's already-completed but un-invoiced pulls. The toast tells you how many lines were staged. The pull's original completion is not re-opened — only the billing lines get regenerated, and only when the pull isn't already on an invoice.
The editor is available only while the term is not `completed` or `cancelled`. Once completed, edit via the final-invoice flow instead.
---
ROLD-10: Row actions
Which actions show depends on the row's status.
On `active` or `pending_pickup` rows
Swap (`RefreshCw` icon) — opens the scheduling dialog. You pick a date; PortaPro creates a swap job for that date, attaches it to the term, and drops it on Dispatch as `unassigned` for someone to route to a driver. If the term has no haul rate set, the dialog warns you (see ROLD-11) but doesn't block.Pickup (`Truck` icon) — opens the same scheduling dialog with `jobType = 'pickup'`. Only shown when the term has no open pickup job yet. Creating the pickup stamps `pickup_job_id` on the term, and the "Pickup" button disappears from the row so you can't accidentally double-book.Receipt icon (Add Fee) — opens the fee dialog (see ROLD-12).Pencil icon (Edit) — opens the term editor (see ROLD-9).On `scheduled` rows
Only Pencil shows. You haven't delivered yet, so there's nothing to swap or pick up. Add Fee is possible in principle but the dialog needs a completed, un-invoiced job to attach the fee to, and a `scheduled` term has neither.On `completed` rows (Completed tab)
Generate Final Invoice — shows only when `tenantId` is loaded. Consolidates every un-invoiced job of the term into one draft invoice. Post-generation, the button disappears (the term is now invoiced). See ROLD-13.Receipt icon (Add Fee) — still available on completed terms, as long as the term isn't yet fully invoiced. If the final invoice already ran, the fee dialog will show no jobs to attach to and the save will be blocked.On `cancelled` rows
No actions. The term is a historical record.---
ROLD-11: The scheduling dialog (Swap / Pickup)
The dialog is compact: one date field and a confirm button. The date defaults to tomorrow — a Friday call for a Monday swap shouldn't need a post-edit in Dispatch. Pick any date from today forward.
If the term has no haul rate set, an amber warning appears: "No haul rate is set on this rental — the swap will stage $0 until rates are entered." Two options:
Set rates now — a link that closes the schedule dialog and opens the term editor. Set the rate, save, then re-open the schedule dialog.Schedule anyway — the confirm button label switches from "Schedule" to "Schedule anyway." The job still goes on the board, but the pull will stage $0 for the haul line until you fix the rate. When you do fix it, the term editor's save triggers a recompute that staged the missing lines automatically (see ROLD-9).On confirm, PortaPro:
Inserts a job row (`job_type = 'swap'` or `'pickup'`) tied to the term, unassigned, with a parent-job pointer at the term's delivery job for lineage.For pickup only: stamps `pickup_job_id` on the term. Idempotent — never overwrites an existing pickup job pointer.Fires a toast confirming the job number, the date, and reminding you to assign it from Dispatch.The new job now shows up on Jobs & Dispatch. Assign it to a driver, and the driver runs the pull the normal way (arrive, weigh, close with a weight ticket + disposal site). Completing the pull automatically stages the billing lines on the term.
---
ROLD-12: The Add Fee dialog
Click the receipt icon on a row to open Add Fee. Fields:
Fee — a dropdown with three presets: Damage fee, Contamination fee, Wasted trip fee. Or Custom… to type your own label (e.g. "Gate access fee", "After-hours delivery fee").Fee name — appears only when Custom is chosen. Free-text.Amount ($) — the dollar amount to charge.Attach to job — a dropdown of the term's un-invoiced, non-cancelled jobs (including the delivery job itself). Pick the pull the fee logically belongs to: - Damage discovered when you pulled the container? Attach it to that swap.
- Contamination at the transfer station? Attach it to the pull the load came from.
- Permit reimbursement? Usually attach to the delivery.
How the fee flows: on save, PortaPro POSTs to `/api/jobs/{jobId}/fee-line` and stages a fee line on the chosen job. The fee inherits the same route the auto-charges take: attributed to the term, swept into the final statement when you generate it.
If the dropdown is empty ("No un-invoiced jobs on this rental"), every job on the term has either been cancelled or already invoiced. If the term isn't complete yet, this usually means you're trying to attach a fee to an active term with zero completed pulls — schedule the delivery/swap first. If the term is complete and everything's on an invoice, you can't retroactively add fees without deleting the invoice first (see ROLD-13 and the swap-cycle article).
Cycle-billed customers (not roll-off-term customers): fees on their non-term jobs sweep into their cycle invoice automatically per the R6 fix (July 2026).
---
ROLD-13: Generate Final Invoice
Available on completed-tab rows. The Generate Final Invoice button POSTs to `/api/rental-terms/{id}/final-invoice`, which consolidates every un-invoiced job of the term into one draft invoice:
Tiered rental base for the actual duration (re-tiered on pickup — see the swap-cycle article for the day/week/month math)One haul line per completed pull (base_rate × pulls)Tonnage overage per pull, when a pull's weight exceeded the included allowanceDisposal passthrough per pull, when the term has `disposal_passthrough` on and the pull recorded a disposal siteAny manual fees you attached mid-term (damage, contamination, wasted trip, custom)Deposit applied as a negative line, clamped at the subtotal (a huge deposit against a small final zeros out — never goes negative)The toast on success shows the draft invoice number and how many jobs were included. The draft opens in your Financials Invoices list — review it, adjust if needed, then send.
The final-invoice call refuses if any job under the term is still open (unassigned, assigned, or in-progress). Close every job first. It also refuses if the term isn't in `completed` status yet — complete the pickup job to flip the term.
Once generated, PortaPro stamps every included job's `invoiced_at` so a second Generate never re-sweeps them. This makes the button idempotent: click it twice, get the same draft; the second click is a no-op.
If you need to redo the final invoice (customer disputed a line, driver corrected a weight after the fact): delete the invoice (not void), fix the underlying data, generate again. Voiding an invoice leaves the jobs marked billed — the swap-cycle article covers this edge case.
---
ROLD-14: The customer-profile Rentals & Service section
Every customer profile has a Rentals & Service section that renders the same term rows for that one customer, with the same Swap / Pickup / Add Fee / Edit / Final Invoice actions. It's the customer-specific view of exactly the same data.
Behavior differences:
No KPI bar, filters, or sort — it's just the list of that customer's terms.Renders nothing (invisible section) when the customer has no rental terms. Porta-potty-only or septic-only customers don't see an empty roll-off section.Includes a one-line explainer at the top: "Deliveries open a term, swaps keep it running, pickups close it."Use the customer-profile section when you're already investigating one customer's history. Use the `/roll-off-rentals` dispatcher page when you're scanning across all customers.
---
ROLD-15: Common workflows
"It's Monday morning; give me my rental-side punch list."
Open `/roll-off-rentals`.Look at In Overage. Every red count is money-at-risk. Sort by Days out (default) and start calling from the top.Look at Pickups Due This Week. Confirm each with the customer, or reschedule.Scan the table for the amber "Nearing overage" status. These are the 3-day window terms — call them today.Any rows in amber "N pull(s) missing weight" under Container? Chase those drivers before the customer disputes tonnage on the final invoice."A customer's box is full; schedule the swap."
Search for the customer, or filter to their driver.Click Swap on the row.Pick the date, confirm. If the term has no haul rate, either set rates now or Schedule anyway (the swap will stage $0 until you fix rates).Assign the new swap job from Dispatch."The customer is done; do the pickup and final invoice."
Find the row, click Pickup. Confirm the date.Assign from Dispatch. Driver runs the pull.When the pickup completes, the term flips to `completed` (or to `pending_pickup` first if it was already past the expected end date). PortaPro re-tiers the rental base for the actual stay length.Flip to the Completed tab. Click Generate Final Invoice on the row.Review the draft in Financials → Invoices. Send."The customer damaged the container. Charge them."
Find the row on the Open tab, click the receipt icon.Fee: Damage fee. Amount: your damage charge.Attach to the swap where you pulled the damaged container.Save. The fee shows on that swap's job items and sweeps into the final invoice."I need to change a term's rates mid-rental."
Click the pencil on the row.Update the rates. Save.If any completed pulls staged $0 lines under the old rates, PortaPro automatically re-stages them and the toast tells you how many lines were rewritten.Verify by opening the term's jobs in Dispatch (or generating a preview final invoice at term end)."I need to cancel a scheduled swap."
From the row's status, note the "Swap scheduled" state.Open Jobs & Dispatch. Find the swap job (linked by `rental_term_id`).Cancel it there. Back on Roll-Off Rentals, the row's status collapses back to In-term (assuming no other next action).Cancelled jobs don't stage any billing lines, so nothing needs cleaning up on the term.---
ROLD-16: Troubleshooting
"The Open tab is empty and I know we have rentals."
Every rental term you have is either `completed`, `cancelled`, or hasn't been created yet. Check the Completed tab. If terms are there, you're up to date. If you have live containers but no terms, someone shipped a container without going through the rental-term flow — create the delivery job through the wizard (New Rental button) and the term auto-creates on the delivery.
"I clicked Swap and got 'Scheduling failed'."
The insert into `jobs` failed — usually a missing service_location on the term, or a missing tenant. Check the browser console for the specific error. If the term is genuinely missing a service location, edit the term or the delivery job to attach one.
"The In Overage tile is zero but I have a red 'Overage' row in the table."
Should not be possible — the same predicate powers both. If you see it, the KPI tile may be reading a stale query. Refresh the page. If it persists, the tile's `openTerms` query and the table's `terms` query are out of sync; usually a browser cache issue, and a hard reload fixes it.
"The row shows 'Rates not set' but the customer's invoice looks right."
Rates set on the customer's product catalog (or the term's original delivery-job line) can produce invoice numbers even when the term's own `base_rate` and overage fields are null. This is fine but fragile — set the rates on the term itself so the row shows a proper Rates summary and swaps don't stage $0.
"Add Fee shows no jobs to attach to."
Either every job on the term has been cancelled/invoiced, or the term has no pulls yet (still `scheduled`). If the term is complete and fully invoiced, delete the final invoice first, add the fee, regenerate. If the term is scheduled with no completed pulls yet, wait for delivery — a fee needs a real pull to sit on.
"Generate Final Invoice is greyed out on a completed term."
Either the term isn't fully in `completed` status yet (any open job blocks it), or the final invoice already ran and every job is stamped `invoiced_at`. If the invoice already ran, look for it in Financials → Invoices by the customer name.
"The row shows the Pickup button even though we already scheduled one."
The Pickup button hides itself when the term has `pickup_job_id` set. If you're seeing it, the previous pickup job was cancelled and the pointer was cleared. Re-schedule.
"A driver appears in the Driver filter but I know they have no rentals."
The Driver filter is populated from open next-action jobs (delivery / swap / pickup) on the current tab. Drivers who last touched a completed pull won't appear on Open; they'll appear on Completed. If a driver appears on Open, they have at least one active job — cross-check Dispatch.
"Rates set on the term but the row still says 'Rates not set'."
The row's "Rates not set" fires only when all three — base rate, overage-per-ton, and daily overage rate — are null. If any one is set, the row shows what's set and leaves the other slots empty. If you see "Rates not set" while `base_rate` is on the term, the write may have failed silently — reopen the editor and re-save.
---
ROLD-17: Related articles
Roll-Off Swap Cycle Workflow — the deep billing model behind this page: how rental terms produce invoices, the tiered day/week/month math, the double-billing guards, the final statement consolidator.Long-Term Rental Workflow — the other rental model, for recurring monthly construction rentals that bill per cycle.Weekend Event Rental Workflow — per-event flat-price rentals with public accept + pay.Inventory setup for roll-off products — creating the container types that populate the term's Product picker.Weight tickets & disposal sites — capturing the pull data that drives tonnage overage and disposal passthrough.