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
- Included in the rate: set the Included tons per pull and the Overage $/ton above it
- Landfill cost + markup: each pull bills the disposal site's tipping fee for the ticket (by the ton when the site has a per-ton rate) plus your Markup (%) (0 to 500; blank means none). See Disposal Sites
The two models are exclusive, and the database enforces it: a rental can't carry an included-tons allowance and a markup at the same time. Picking one model in the wizard, the term editor or the container's catalog defaults clears the other's fields.
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. Put the deposit on the quote, or open the delivery job and choose Request Deposit: either way the deposit is its own invoice, paid online or recorded with Collect Payment. When the final statement is created, the paid deposit is credited to it automatically and the balance due drops by that amount. See Deposits.
---
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:
How the lines read. On the job's Items card each charge is named (Roll-off haul, Roll-off tonnage overage, Roll-off rental overage, Roll-off disposal) and shows its total, with the tons or days beside it. Under the name is what the customer will read, and on a marked-up disposal line one more line that only your office sees, for example "Landfill $58.00/ton + 15% markup. Not shown on the invoice." These lines are written from the weight ticket and the rental's rates, so in edit mode they read "From the rental and its ticket" instead of a quantity box: change the ticket or the rental, not the line.
What the customer sees. On the final statement each line carries a description: the haul says which pull and when ("Swap · Sep 19, 2026"), the disposal line names the site, the material, the tons at the customer's rate, the ticket number and the day ("County Landfill · Construction & demolition (C&D) · 3.42 tons @ $66.70/ton · Ticket 451207 · Sep 30, 2026"), a tonnage overage gives the tons over the allowance, the weight, the rate and the ticket, and a rental overage gives the days and the date it ran past ("4 days past Sep 19, 2026 · $10.00/day"). The landfill's own rate and your markup never appear on the customer's copy.
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 Final invoice
On a Landfill cost + markup rental, PortaPro first checks that every pull's disposal can be priced. If a pull has no weight on a per-ton site, no disposal site, or a site with no rate, a dialog titled "Disposal can't be priced yet" lists the pulls ("1 pull has disposal that can't be priced yet: J-1042 (no weight on the ticket). Fix the ticket first, or issue the statement without those disposal charges.") and explains that issuing anyway drafts the statement without those disposal charges, which you can add to the invoice by hand, or fix the tickets and try again. Choose Fix the tickets first to go back, or Issue anyway to draft the statement without those lines. A material the site takes for free is priced at $0 and never holds the statement up.
The consolidator then 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 removes its lines and clears each job's billed stamp, unless another invoice still bills that job, 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
"Final invoice isn't offered."
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 line is missing on the final statement."
Check the term's How is disposal billed? setting. On Included in the rate, no tipping fee passes through by design. On Landfill cost + markup, each swap and pickup needs a disposal site on its weight ticket, a net weight when the site charges by the ton, and a rate at that site (see Disposal Sites DS-6). Final invoice stops with "Disposal can't be priced yet" for exactly those pulls, so a line is only missing if someone chose Issue anyway, or if the site prices that material at $0 (free, no line by design).
"Final invoice says 'Disposal can't be priced yet'."
A Landfill cost + markup pull is missing a weight (per-ton site), a disposal site, or a rate at that site; the dialog names each pull and the reason. Fix the tickets first, correct the pulls on their Weight Ticket cards, and run Final invoice again. Or Issue anyway and add the disposal charges to the draft by hand.
"The customer paid a \$200 deposit at delivery. Where does it show up?"
As a credit on the final statement: the paid deposit is applied to it automatically and the balance due drops by that amount. If it doesn't show, check that the deposit invoice is paid and that it was requested from the quote or the delivery job (Request Deposit), not typed as an ordinary invoice.
"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:
---
