This article covers the roll-off rental term workflow — how PortaPro handles dumpster rentals, roll-off boxes, and any container that gets delivered, swapped mid-rental as it fills, and picked up at the end of the job. Roll-off rentals have their own billing model: they don't bill per cycle like construction rentals, they bill as one final statement at the end of the term.
If you're doing weekend event rentals, see the Weekend Event Rental Workflow. For monthly recurring construction rentals, see the Long-Term Rental Workflow.
---
ROL-1: What Makes a Roll-Off Rental Different
One term = one final statement
Instead of billing every cycle while the box sits on the customer's site, a roll-off rental runs as one term with a start date, an expected end date, and one big final statement at the end that consolidates every part of the job:
This is the standard roll-off industry model. Customers understand a construction dumpster as "\$X to deliver, \$Y per week, \$Z per swap, extra weight \$/ton." The final statement puts all of that on one page.
The swap job
Roll-off rentals use a special job type: swap. When the box is full mid-rental, the driver swaps the full one for an empty one at the same location. The swap job counts as a pull on the term's final statement (that's a haul revenue line) and captures the weight ticket + disposal site for the load being taken away.
Cycle billing and term billing don't mix
The exact division: a job attached to a rental_term_id bills exclusively through the term's final statement. A job without a rental_term_id (regular one-off pumping, cycle deliveries, non-term work) bills through the customer's cycle invoice. There is no case where the same job bills both — the double-billing guard closed that gap in the July 2026 fix (R1).
---
ROL-2: Create a Rental Term
Step 1: Start from the customer profile
Step 2: Fill in the term details
Step 3: Save
Saving the term auto-creates the delivery job for the start date. The term goes into scheduled status; when the delivery job completes, the term flips to active.
---
ROL-3: The Tiered Day / Week / Month Math
Roll-off rentals use "tiered DWM" pricing: three rates (per day, per week, per month) and PortaPro picks the cheapest combination that covers the actual rental duration.
Example: 6-week rental at \$10/day, \$60/week, \$250/month
Now a real spec example: 42 days at \$75/day, \$300/week, \$1,000/month:
Why cheapest-covering favors the customer
This is a deliberate product decision. The alternative — some specs use "naive full-week math" — always charges more, and confuses customers who can see that their 6 weeks fits in 1 month + 2 weeks. Cheapest-covering means the invoice matches what a rational customer would expect from the rates you published.
The rate breakdown snapshot
Every rental base line stamps a rate_breakdown label like "1 month + 2 weeks" or "3 weeks + 4 days" so the customer's invoice explains exactly how the math worked. If the label ever says "0 days" or looks wrong, one of your tier rates is 0 or missing — PortaPro treats a \$0 tier as unavailable (a \$0 week tier must not make every rental free).
Ties
When two combinations tie on total, PortaPro prefers the one with more months, then more weeks — makes for cleaner-reading invoices.
---
ROL-4: The Delivery Job
Saving the term creates the delivery job. It shows up on the dispatch board like any other delivery.
What the delivery job carries:
When the driver completes the delivery, the term flips from scheduled to active and the box is officially on rent.
What if the customer wants a deposit?
Some ops collect a deposit at delivery. Charge the customer for the deposit via a one-line invoice at delivery time (or via the invoice wizard on the delivery job). At final-statement time, PortaPro nets the paid deposit as a negative "Deposit applied" line, clamped at the subtotal (so a huge deposit against a small final can't produce a negative invoice — it just zeroes out).
---
ROL-5: Mid-Term Swaps
When the box fills up, dispatch schedules a swap.
Step 1: Create the swap job
Step 2: Driver completes the swap on-scene
What lands on the term's final statement:
Weight tickets
The weight_ticket_photo_url gets attached to the maintenance report for the swap — office staff can pull it up when disputes come in. Weight is always in tons on the invoice (industry convention).
---
ROL-6: Pickup and Term Completion
When the customer is done:
Step 1: Schedule the pickup
From the term or the customer profile, create the pickup job for the actual pickup date.
Step 2: Driver completes the pickup on-scene
Step 3: The term auto-flips to completed
When the pickup completes, the term goes to completed. If the pickup happens after the expected end date, the term stops in pending_pickup first (a signal that operations should chase — the customer went over their expected term).
Step 4: The rental base re-tiers on pickup
This is important. The rental base line on the term's delivery job was priced for the *expected* duration. When the pickup completes with an actual end date, recomputeTermCharges re-tiers the rental base for the *actual* number of days. If the customer rented for the expected 30 days and picked up at 45, the base line updates to whatever cheapest-covering combo covers 45 days.
The office can still edit the rental base line before invoicing if there's a business reason to override — the recompute doesn't happen after the term has been invoiced.
---
ROL-7: Generate the Final Statement
Step 1: Verify the term is completed and all jobs are done
Every job under the term must be completed, completed_late, or cancelled. If any job is still open, PortaPro refuses to generate the final statement and tells you which jobs to close first.
Step 2: Open the term and click Generate Final Invoice
The consolidator runs and:
The result is one draft invoice ready to review and send.
Double-invoice protection
The final statement checks each job through isJobBilled before including it. If a job was somehow already billed on a cycle invoice (shouldn't happen, but a fallback), it's excluded and reported so you can reconcile. The cycle sweep, on the other side, excludes term jobs entirely — this is the R1 fix that closed the double-billing gap in July 2026.
Voided invoices — one thing to know
isJobBilled doesn't check invoice status — a voided (not deleted) invoice still marks its jobs as billed, so they'll be excluded from the next final statement. To recover from a void: delete the invoice (not void). Deleting sets the source_job_id FK to null, which re-opens the jobs for a fresh final statement. Voiding is for accounting closure after final delivery to a customer; deleting is for reset.
---
ROL-8: Cycle Customers Who Also Get Job Fees (R6)
If a cycle-billed customer (Scenario 1: rate card + monthly invoice) also has roll-off work, the R6 fix (July 2026) ensures the roll-off job fees ride correctly on their cycle invoice:
All three types now sweep into the cycle invoice for non-term customers. Before R6, only rolloff_auto swept — manual and dry-run fees on cycle customers were invisible to the sweep and stayed unbilled until a human noticed. Cycle-billed job fees now include a stamp linking them back to their source job (invoice_items.source_job_id) so the ledger is symmetric with the term-side one.
---
ROL-9: Adjustments Between Delivery and Final Statement
Roll-off terms often run mid-project — dispatch or the office adds fees, adjusts the deposit, tweaks weights. These are all supported:
What you can't retroactively change once the final statement is issued
If a customer disputes and you need to adjust after the final statement: delete the invoice (not void), fix what needs fixing, regenerate.
---
ROL-10: Troubleshooting
"Generate Final Invoice is greyed out."
The term isn't in completed status yet. Complete the pickup job, or if the pickup hasn't happened, the term stays in active (or pending_pickup if past its expected end date). PortaPro also refuses if any job under the term is still open (unassigned, assigned, or in-progress) — close every job first.
"PortaPro says the customer was billed for a haul that was also on their cycle invoice."
This shouldn't happen post-July 2026 (R1 fix). If it does: the sweep excludes term jobs by rental_term_id IS NULL. Check the offending job's rental_term_id — if it's set, the sweep should have skipped it; if it's null, the job was miscategorized. Either delete the erroneous invoice line or reconcile via a credit.
"The customer only picked up 21 days in but the rental base charged for 30."
The rental base re-tiers on pickup completion. If it didn't, either:
"The final statement shows \$0 for a week of rental base."
One of your tier rates is \$0 or missing. Tiered math treats a \$0 rate as *unavailable* — otherwise every rental would be free. Set a non-zero week rate on the term (or on the customer's product catalog), then regenerate.
"The disposal passthrough line is missing on the final statement."
Check the term's disposal_passthrough flag. If it's off, dump fees stay in your margin. If it's on, the swap and pickup jobs must have a disposal_site_id set — without it there's no source for the passthrough amount.
"The customer paid a \$200 deposit at delivery. Where does it show up?"
As a negative "Deposit applied" line on the final statement, netted against the subtotal. If it doesn't show, verify the original deposit invoice was marked paid and attached to the customer (not to a specific job).
"I need to bill a cycle customer for a dry-run fee from a completed pickup, but it's not showing on the cycle invoice."
Post-R6 fix: yes, dry-run and manual fees on cycle customers' non-term jobs sweep into the cycle invoice. If yours isn't:
---
