Invoice your customers
Billing & AR is one screen with two halves. The top half is the pending-charges queue — work you have done that nobody has been invoiced for yet. The bottom half is every invoice, unbilled row, and balance you can filter. This page walks the whole path from a charge on a delivery order to a payment recorded against an invoice, and it is blunt about the three places where the button you expect is not wired up.
11 minute read · Updated July 27, 2026
Two controls on the invoice page do nothing.
On an invoice, Download PDF works. Print and Email are rendered with no click handler at all — selecting them has no effect and produces no error. There is also no void control anywhere in the interface. Read the sending and voiding sections before you plan a billing run around them.
The console layout
The console lives at /protected/billing and is headed Billing & AR, with the count of matching invoices underneath. In a carrier workspace, billing access is granted only to tenant admins and tenant managers. Anyone else is redirected to /protected/unauthorized before the page renders, and a signed-out visitor goes to the login screen instead.
Top to bottom, you get four things:
- The sticky filter bar
- A date-range chip (Last 90 Days by default) and one toggle chip per status: Unbilled Dlv, Unbilled Ret, then a divider, then Draft, Sent, Overdue, Partial, Paid, Void — each with its own count. Below the large breakpoint this collapses to a Status dropdown and a Filters button.
- The Pending Charges Queue card
- Only rendered when at least one pending charge exists. Its header reads “Pending Charges Queue” with “{n} pending” on the right. If the card is missing, you have nothing pending — it is not a loading state.
- The invoice table
- Columns are Invoice #, Customer, Amount, Balance, Status, Aging, Invoice Date, Due Date. Tablet width drops Aging and Due Date; phone width switches to cards. Every column header sorts the rows already on screen.
- The Billing Management strip
- Three cards at the bottom: Billing Settings, Customer Billing, and Accessorial Rate Cards. Those are configuration surfaces, not part of a billing run.
Clicking a table row navigates to it; hold Command or Control to open it in a new tab instead. Two row behaviors surprise people. Rows with an Unbilled Dlv or Unbilled Ret status show — for Amount, Balance, and Due Date, and clicking one takes you to the delivery order in Operations rather than to any invoice — there is no invoice yet. And the Balance column also shows — when the balance still equals the total, so a blank balance means “nothing paid,” not “nothing owed.”
The footer under the table reads Showing X of Y invoices, with a Load more invoices button that raises the row cap by 100 each time.
The pending-charges queue
The queue lists unbilled charges with status pending only, newest service date first. Columns are PT# / Container, Bill To, Charge, Description, Amount, and Date; Bill To, Charge, Amount, and Date are sortable. The Amount column shows the charge plus its fuel surcharge, with a +X% FSC line underneath when a surcharge applies.
- Narrow the list. The search box — Search PT#, bill to, description... — matches the tracking number, the billed-party name, the description, and the container number. The All Bill To dropdown filters to one billed party.
- Select what you are invoicing. The header checkbox is Select all visible charges — it selects everything the current search and Bill To filter leave on screen, not everything pending.
- Check the running total. A bar appears reading {n} charges selected and Total: $0.00, with Clear and Generate Invoice. On a phone the same action sits in a fixed bar at the bottom of the screen showing the count and total.
The line under the queue reads Showing X of Y charges. When nothing matches you get No unbilled charges found.
The queue does not obey the console's filters. Status, date range, charge type, QB export status, and aging apply to the invoice table below it and are ignored by the queue; only the issuer and bill-to filters carried in the URL reach it. Do not assume a date range narrowed the charges you are about to invoice.
Adding, voiding, and reassigning charges
Adding a charge
You cannot add a charge from the billing console. The Add Charge button lives on the delivery order in Operations, because a charge has to be linked to a delivery order to be invoiceable at all. If that delivery order has no customer organization the button is not rendered; if no charge types are configured it renders disabled and reads No Charge Types.
The dialog asks for Charge Type, Description, Rate ($), Quantity, and Service Date. A Bill To selector appears only when the delivery order offers more than one candidate. Lane Rate (Optional) and Fuel Surcharge % appear only for charge types that carry a fuel surcharge; picking a lane rate fills the description as Origin → Destination and sets the rate. A totals block shows Amount, FSC, and Total as you type.
The server rejects a bad charge with a specific sentence: “Quantity must be greater than 0”, “Rate must be greater than 0”, “Fuel surcharge must be between 0 and 100%”, “This delivery order is inactive and cannot be billed.”, “This delivery order does not have a default billed party.”, or “This delivery order does not have a billing service context.” For a charge staged from a job shell you may instead see “This job is missing a freight billed party. Set the job shell freight bill-to before creating charges.” — with an accessorial variant of the same sentence.
Voiding a charge
Each queue row ends with an × button labeled Void {description}. On a phone the same action is behind More actions and reads Void charge.
Voiding a charge has no confirmation step. One click marks it void and it disappears from the queue immediately. Only pending charges can be voided — the update is scoped to pending rows, so a charge already attached to an invoice is untouched — and there is no un-void control in the interface. If you void the wrong charge, add it again on the delivery order.
Reassigning the billed party
The billed-party name in each row is a button with a pencil icon. Selecting it opens a popover headed Reassign Bill To. An unlinked charge shows Unlinked as its current value.
Read the candidate list carefully. It is not limited to the parties on that delivery order: it lists every active organization that is not a trucker and has a party type, sorted by name. The small role label beside a name — Customer, BCO, Broker, Forwarder, or Warehouse — is only filled in for organizations that actually appear on that delivery order. Everything else shows Active tenant. Reassigning to an org with no role label is allowed and will produce an invoice addressed to a party with no connection to the shipment.
Failures appear as red text at the top of the popover: “Only pending charges can be reassigned.”, “Charge is not linked to a delivery order.”, “Charge not found or not accessible.”, or “Selected billed party must be an active tenant organization.” When the charge has no delivery order the popover simply reads No candidates available.
Generating invoices
Select charges and press Generate Invoice. One run produces exactly one invoice, and every selected charge must belong to the same group: same issuer, same billed party, and same service context. Mixing them fails with “Selected charges must share the same issuer, billed party, and service context before invoice generation.”
The service context is the first party present on the delivery order, in this fixed order: Customer, BCO, Broker, Forwarder, Warehouse. Two charges billed to the same company can still refuse to combine if they sit under different service contexts. Invoice one group, then the next.
What the new invoice inherits:
| Field | Where it comes from |
|---|---|
| Status | Always draft. Nothing is sent by generating. |
| Due date | Today plus the payment terms for that billed party; falling back to your workspace default, and finally to 30 days. |
| Bill To block | Name and address copied from the billed-party organization at generation time. Later edits to that org do not rewrite an existing invoice. |
| Reference | For a broker or forwarder service context only, the delivery order's broker reference — and only when every selected charge resolves to the same one. Mixed references leave the invoice reference blank. |
| QuickBooks | Export status starts at pending. |
On success the queue shows Created 1 invoice(s) successfully. and the charges flip to invoiced and leave the queue. If no per-customer billing configuration exists you get an amber notice instead: Created 1 invoice(s). Warning: No billed-party billing config was found for {name}; default billing settings were used. The invoice is real — check that the terms on it are the ones you meant.
Generation is all-or-nothing. If the line items cannot be written, or not every selected charge can be marked invoiced, Conterminal deletes the invoice and its items and returns the charges to pending. You will never be left with a half-built invoice.
Payment terms and the fuel-surcharge default come from your invoice settings and per-customer terms. Charge rates come from accessorial rate cards and lane rates.
Sending and emailing an invoice
This is the weakest part of the workflow, and it is worth knowing exactly what exists before you promise a customer an emailed invoice.
On an invoice at /protected/billing/invoices/{id} there are three buttons in the header. Download PDF opens the generated PDF in a new tab and is the one to rely on. Print and Email have no behavior attached — they are placeholders. There is also no Mark as sent control, so a draft invoice stays a draft on this page no matter what you do with it outside Conterminal.
The practical path today is: open the invoice, select Download PDF, and send it from your own mail client. Record the payment back in Conterminal when it arrives. Nothing on the carrier invoice page changes the invoice's status.
One working automated send does exist, on a different surface. Where Conterminal renders the Invoice Generation card — the workspace billing view for a tenant admin whose organization is a carrier — the selected-charges bar offers both Generate and Generate & Send. The standard Billing & AR console does not render that card, so if all you see is the Pending Charges Queue, this path is not available to you.
When it does run, Generate & Send creates the invoice, renders the PDF, and emails it as an attachment. The message is sent from billing@conterminal.com under the display name {Your company} Billing, with the reply-to set to the email address in your invoice settings and the subject Invoice {number} from {your company}. The body shows the bill-to block, the balance due, and up to eight line items. A successful send flips a draft invoice to sent.
The recipient is the email address on the billed-party organization — there is no per-contact list and no CC. If that field is empty the invoice is still created and you get an amber notice reading Invoice {number} created. Customer does not have a billing email address configured. The same shape of message reports a missing email-service configuration or a rejection from the mail provider. In every one of those cases the invoice exists; only the email failed.
Recording payments
Payments are recorded on the invoice, not on the console. Open the invoice and use Record Payment beside Payment History. The button is disabled once the remaining balance reaches zero.
- Check the three totals. The dialog header shows Invoice Total, Paid to Date, and Remaining.
- Set the amount. Payment Amount is prefilled with the full remaining balance. Reduce it for a partial payment.
- Choose a method. The list is Manual Entry, Check, ACH (Melio) - Coming Soon, and Credit Card (Elavon) - Coming Soon. The last two are disabled — they are labels for planned integrations, not options. The default is Check.
- Add a reference. The Reference Number placeholder changes to Check # when the method is Check. This is the field your customer will ask you to quote later — fill it in.
- Confirm the date. Payment Date defaults to today. If the invoice is settled by this payment, that date becomes the invoice's paid date, so back-date it when you are catching up on a stack of checks.
The dialog blocks two mistakes before submitting: “Payment amount must be greater than zero” and “Payment cannot exceed remaining balance”. Once saved, Conterminal totals every payment on the invoice and sets the status to paid when the payments reach the invoice total, or partial when they fall short. Recording a payment against a voided invoice is refused with “Cannot record payment on voided invoice”.
The overpayment guard lives only in the dialog, and payments are append-only — there is no control to edit or delete one you have already recorded. Check the amount and the date before you submit; a correction means recording a compensating entry, not fixing the original.
The Payment History table below shows Date, Method, Reference, and Amount, and reads No payments recorded until the first entry.
Voiding an invoice
There is no void button.
Voiding an invoice is implemented on the server but nothing in the interface calls it. You cannot void an invoice yourself today. If a generated invoice is wrong, contact support rather than searching the screen for a control that is not there.
It is still worth knowing what a void does, because it is not a delete. A paid invoice cannot be voided at all — the attempt is refused with “Cannot void a paid invoice”. For anything else, the invoice is marked void and every unbilled charge attached to it is returned to pending with its invoice link cleared. Those charges reappear in the Pending Charges Queue and can be reassigned and reinvoiced normally.
A voided invoice stays visible to you on the console under the Void status chip, but it is filtered out of what a billed party sees on their own invoice view, which is limited to Sent, Overdue, Partial, and Paid.
The charge-level void described earlier is a different action and does work — that is the × in the pending-charges queue, and it applies to a charge that has not been invoiced yet.
Sweeping overdue
An overdue sweep exists: it takes every invoice currently sent or partial whose due date has passed and rewrites its stored status to overdue.
Nothing in the application triggers that sweep. There is no button for it and no scheduled job in this codebase that calls it, so the stored status column on an invoice can sit at sentindefinitely after its due date has passed.
In practice this matters less than it sounds, because the console does not read the stored status. The invoice table, the status chips, and the aging buckets are all computed by the reporting query at read time, which is why an invoice past its due date shows Overdue and lands in a 31-60d or 90+ bucket without anyone running a sweep.
The visible consequence is a mismatch. The invoice's own page renders the stored status, so an invoice can read Overdue in the list and Sent on its detail page at the same time. Trust the list for aging and collections work; treat the badge on the detail page as the raw stored value.
Filters, and the delimiter that catches people
Every filter is held in the URL, so a filtered view is a shareable link and the browser back button steps through your filter history. The sidebar holds Search (placeholder Invoice #, bill to...), Bill To, Charge Type, QB Export Status, Aging, Invoice Date, and Due Date. Status and date range live in the top bar instead.
Status uses commas. Everything else uses pipes.
status is comma-separated — ?status=sent,overdue. But customer, chargeType, qbStatus, and aging are all pipe-separated — ?aging=31-60|61-90. Hand-editing one of those with a comma does not error: the whole string is read as a single value, fails validation, and is dropped. The page then loads unfiltered and looks like the filter was ignored. It was.
- Date range
- Last 7 Days, Last 30 Days, Last 90 Days (the default), Last 180 Days, Last Year, and Current Year. Changing it also resets the row cap back to 100.
- Bill To is exclusive
- The sidebar's multi-select Bill To list and a single billToOrgId link filter are mutually exclusive — setting either one clears the other, by design.
- Two different resets
- “Clear all” in the pill row above the table clears the sidebar filters and the keyword but keeps your status, date range, and row cap. “Reset filters” in the empty state clears everything.
- The Filters badge
- The count on the mobile Filters button covers only the sidebar filters and the keyword. Status chips and the date range are never counted, so a heavily filtered view can show no badge.
Active filters appear as removable chips above the table, next to a {n} matching invoices count. An issuer filter shows its raw identifier rather than a company name, which is expected — it is a link-level filter, not something the sidebar sets.
Troubleshooting
The console loads but every action fails
This is not a plain member's problem — in a carrier workspace a member without billing access never reaches the page and is redirected to /protected/unauthorized first. The case that produces a rendering console with dead buttons is a platform-scope session with no carrier tenant workspace: the page admits you, but every write requires a carrier tenant admin or manager role and redirects instead. Switch into the carrier workspace and reload.
“Customer does not have a billing email address configured.”
The billed-party organization has no email address, and there is no fallback. The invoice was created — only the send failed. Add the address to that organization, then use Download PDF and send it yourself, since nothing on the carrier invoice page retries the email.
Exactly 100 rows, and “Load more invoices” is still showing
The Load more button appears whenever the returned row count reaches the current cap, so a result set of exactly 100 always looks truncated. Press Load more once: the cap goes to 200, the same 100 rows come back, and the button disappears. Nothing was hidden from you.
“Selected charges must share the same issuer, billed party, and service context before invoice generation.”
You selected across groups. Same billed party is not enough — the service context (the first of Customer, BCO, Broker, Forwarder, Warehouse present on the delivery order) must match too. Use the Bill To dropdown to isolate one party, invoice the charges that group cleanly, then repeat.
“One or more selected charges are no longer pending or available for invoice generation.”
Between loading the page and pressing Generate Invoice, at least one selected charge was invoiced, voided, or moved by someone else. Nothing was created. Reload the console and reselect.
“This delivery order is inactive and cannot be billed.”
The delivery order was superseded or is no longer active — commonly after a corrected document replaced it. Bill against the current delivery order instead. The same condition blocks invoice generation with “One or more selected charges are linked to an inactive delivery order and cannot be invoiced.”
The Pending Charges Queue card is missing
The card is rendered only when at least one pending charge exists, so an empty queue means no card at all rather than an empty table. If you expected charges, check that they were added against an active delivery order and have not already been invoiced or voided.
“No candidates available” when reassigning Bill To
The charge has no delivery order linked to it, so there is nothing to resolve candidates against. That charge also cannot be invoiced — invoice generation requires a linked delivery order. Void it and re-add the charge from the delivery order.
An invoice reads Overdue in the list and Sent on its page
Expected. The list computes status and aging at read time; the detail page shows the stored status column, and nothing sweeps that column to overdue. Use the list for collections.
Print and Email do nothing on the invoice page
Correct — both buttons are rendered without a click handler. Use Download PDF, then print or send the file yourself. Report it if you need those wired up; there is no setting that enables them.
“No billed-party billing config was found for {name}; default billing settings were used.”
The invoice was created, but with your workspace's default payment terms rather than terms specific to that customer — or with 30 days if you have no default set. Check the due date on the invoice, then add the customer's terms under Customer Billing so the next invoice is right.
The queue ignored my date range
It is supposed to. Only the invoice table below responds to status, date range, charge type, QB status, and aging. Narrow the queue with its own search box and Bill To dropdown instead.