Chapter 1 — What 3PL OS Gives You
3PL OS turns a single-customer warehouse into a multi-customer operation. It unlocks customer-scoped inventory, storage billing, customer onboarding, and the customer portal.
What it adds on top of Warehouse OS
| Capability | Why it matters |
|---|---|
| Customer master records (M25) | Multiple downstream customers, each with their own users, contacts, billing. |
| Customer-owned inventory (M16) | Track who owns what stock — your inventory vs. customer A vs. customer B. |
| Storage rules + accrual (M30) | Daily/monthly storage charges based on volume, units, or flat rate. |
| Storage aging | See how long stock has been sitting — flag dormant inventory. |
| Customer-scoped pricing rules | Negotiate different rates per customer. |
| Customer portal (/console/customer/*) | Customers self-serve inventory views, invoices, payment. |
| Operational documents (M26) | Casework, contracts, photos attached to customer records. |
Default landing path
/console/business/3pl/shipments for company_admin focused on the 3PL view.
Chapter 2 — Roles That Light Up with 3PL OS
| Role | What they do in 3PL OS |
|---|---|
| company_admin | Owns 3PL config — customer onboarding, storage rules, customer-specific pricing, portal access for customers. |
| operations_manager | Manages multi-customer warehouse — segregates inventory, oversees storage accrual. |
| customer_service | Customer-facing — answers customer questions, opens cases, sees customer-owned inventory. |
| accounting | Bills customers — service catalog, pricing rules, invoice append, storage accrual review, payments. |
| customer_admin (portal) | Manages users for their own customer org. Places orders, views invoices, pays. |
| customer_user (portal) | Standard portal user. Places orders, views invoices, pays. |
Chapter 3 — Onboarding a New Customer
Adding a 3PL customer is a 6-step workflow. This chapter walks each step end-to-end so a new account manager can do it without supervision.
Walkthrough — onboard customer "Acme Corp" end-to-end
Prerequisites
- company_admin (full setup) or customer_service (create record only).
- Signed contract or commitment with the customer.
- You know: their legal company name, primary contact email + phone, billing address, agreed payment terms.
- Stripe is connected for tenant (if customer will self-pay).
Step 1 — Create the customer record
- Go to
/console/customer-service/customers. Header: "Customers". Top-right: Add Customer. - Click Add Customer. Modal opens.
- Fill:
- Display Name → "Acme Corp".
- Customer Slug → "acme-corp" (lowercase, hyphens, no spaces). Cannot change after save.
- Status → active.
- Industry → optional dropdown ("Apparel", "Electronics", etc.).
- Portal Module Access → tick the OSes they should see (3pl always; commerce_os if you have it; ground_ops if you do air cargo for them).
- Primary Contact:
- Name → "Jane Smith".
- Email → "jane@acme.com".
- Phone → "+1-555-0100".
- Billing Address → street, city, state, zip, country.
- Tax ID → optional but useful for tax-exempt customers.
- Click Create Customer. Toast: "Customer Acme Corp created." The customer record appears in the list with slug acme-corp.
Step 2 — Configure billing profile
- From the customer detail, click the Billing tab.
- Click Edit Billing Profile. Modal opens.
- Fill:
- Billable → checkbox. Tick it. (Untick for free/internal customers.)
- Currency → USD / CAD / EUR / etc.
- Payment Terms → Net 7 / Net 14 / Net 30 / Net 60 / Prepaid.
- Billing Email → "ap@acme.com" (their accounts-payable inbox).
- Invoice Email Template → defaults to your tenant's template; override per customer if needed.
- Stripe Customer ID → click Connect Stripe Customer. Trenvar creates a Stripe customer record using the billing info; the Stripe ID is stored.
- Tax Exempt → checkbox. Tick if they have a valid exemption certificate. Upload the cert in Documents tab afterwards.
- Save.
Step 3 — Set up storage rule(s) for the customer
See Chapter 5 for full walkthrough. In short: go to /console/business/storage/rules, create a rule scoped to this customer, link to their inventory.
Step 4 — Set up pricing rule(s) for the customer
See Chapter 7. In short: go to /console/business/billing/pricing-rules, create a fulfillment pricing rule with Customer = Acme Corp.
Step 5 — Invite portal users
- Go to
/console/business/settings/users. - Click Add User.
- Email → "jane@acme.com".
- Display Name → "Jane Smith".
- Role → customer_admin (gives her ability to manage other Acme users + Shopify integration).
- Linked Customer → "Acme Corp".
- Save. Trenvar sends Jane an invite email. She accepts, sets a password, logs in to
/console/customer/inventory. - Repeat for any other portal users Acme needs (typically a customer_user or two).
Step 6 — Verify with a test transaction
- Have Acme send a small test pre-alert (you can simulate this from
/console/ops-manager/pre-alert). - Receive 1 unit of one SKU.
- Wait for storage accrual to run (next day).
- Open a draft invoice for Acme, click Append Accruals.
- Confirm 1 day of storage shows up at the expected rate.
- If everything checks out, you're live.
Chapter 4 — Customer SKUs and Customer-Owned Inventory
In Warehouse-only mode, all stock is yours. In 3PL OS, every inventory item has an owner_customer_id. This chapter walks through how customer ownership flows through the system.
Walkthrough A — create a SKU owned by a customer
Step-by-step
- Go to
/console/business/wms/item-master. - Click Create SKU.
- Fill the standard fields (see Warehouse OS Chapter 4 for the deep walkthrough).
- Critical for 3PL: in the Identification section, find the Owner field. By default it's "Internal" (your own stock). Click the toggle / dropdown and switch to Client.
- A Client / Customer dropdown appears. Pick "Acme Corp".
- Save. The SKU is now associated with Acme. Acme will see it in their portal inventory; billing will treat it as Acme's stock.
Walkthrough B — receive customer-owned goods
- Create the pre-alert (Warehouse OS Chapter 5) but in the Customer field, pick "Acme Corp".
- Operator receives normally (Warehouse OS Chapter 6).
- Trenvar auto-tags the resulting handling units and inventory items with
owner_customer_id = Acme Corp. - Storage accrual starts immediately for Acme (per their storage rule).
Walkthrough C — transfer ownership of inventory
Use case: Acme sells some of their on-hand stock to "Beta Industries". The physical inventory doesn't move — only the ownership.
Step-by-step
- Go to
/console/ops-manager/inventory. - Find the inventory item to transfer. Click the row.
- Detail panel opens. Click Transfer Ownership.
- Modal opens:
- From Owner → pre-filled (Acme Corp). Read-only.
- To Owner → typeahead. Pick "Beta Industries".
- Quantity → number. Defaults to all available units. Edit to transfer a partial.
- Reason → dropdown: Sale between customers / Consignment switch / Bulk reassignment / Other.
- Notes → optional free text.
- Click Submit Transfer. Confirmation: "Transfer 50 units of BLUE-SHIRT-M from Acme Corp to Beta Industries. Continue?"
- Confirm. Trenvar:
- Decrements Acme's owned_units by 50.
- Increments Beta's owned_units by 50.
- Writes a single audit event with both sides recorded.
- Storage accrual immediately switches to Beta's storage rule for those 50 units.
Inventory states under 3PL
| State | Meaning |
|---|---|
| owned_units | Belongs to a specific customer. |
| unassigned_units | No owner — internal stock. |
Chapter 5 — Storage Rules & Accrual
Storage billing now uses a free-form three-layer model: tenants define their own units (BARREL, CBM, LITRE, DRUM, BOX — whatever your contracts call them), and the engine matches SKUs to rules via a shared Unit Code. This chapter walks through the full setup.
The three-layer model
| Layer | What it does |
|---|---|
| 1. Storage Rule | Defines the billing contract: unit code, unit label, quantity source, cadence, unit price, free days, minimum, customer scope. |
| 2. Unit Code | The shared key (e.g., BARREL, CBM) that links a rule to the SKUs it bills. |
| 3. Item Master / Customer Catalog | Tells the system which SKU uses which Unit Code via Storage Billing Unit Code on the SKU. |
Storage Billing Unit Code = BARREL, the engine finds the best active BARREL rule for that customer. If the SKU's Unit Code doesn't match any active rule, the SKU isn't billed.
Quantity Source — how each unit is measured
Every storage rule picks one Quantity Source. This tells the engine what to count.
| Quantity Source | Engine measures | Use when |
|---|---|---|
| Quantity Units | Inventory unit count | The stocked unit count itself is the billable count. |
| Volume (CBM) | Cubic metres of space | Charge based on space consumed; resolved from inventory volume_cm3 or item dimensions. |
| Assigned SKU Storage Units | SKU's own Storage Billing Qty/Unit × stocked units | You want a free-form unit like BARREL, LITRE, DRUM, BOX. Tenant decides how many of the unit each stocked item equals. |
| Handling Units | Total HU count in storage | Per-container pricing; every HU counts. |
| Pallets | Pallet-typed HU count | Customer ships in pallets; you rent floor positions. |
| Occupied Bins | Bins with stock | Bin-rental contracts. |
| Flat Rental | 1 per customer per period | Simple monthly retainer. |
Walkthrough A — create a Volume (CBM) rule
Step-by-step
- Go to
/console/business/storage/rules. Top-right: Create Rule. - Click Create Rule. Modal opens.
- Fill:
- Code → "CBM-MONTH" (internal identifier).
- Name → "CBM Storage" (tenant-facing).
- Unit Code →
CBM. This is the matching key. - Unit Label → "CBM" (display name shown to users).
- Quantity Source → Volume (CBM).
- Cadence → Monthly.
- Unit Price → 60.00 ($60 per CBM per month).
- Free Days → optional grace period.
- Minimum Charge (Period) → optional floor.
- Customer Scope → blank for tenant default, or pick a downstream client.
- Save.
Walkthrough B — create an Assigned SKU Storage Units rule (free-form unit, e.g., BARREL)
Use case: you store liquid in barrels. The contract bills $20 per barrel per month.
Step-by-step
- Click Create Rule.
- Fill:
- Code → "BARREL-MONTH".
- Name → "Barrel Storage".
- Unit Code →
BARREL. (Tenant-defined; can be anything as long as SKU assignments use the same string.) - Unit Label → "Barrel".
- Quantity Source → Assigned SKU Storage Units.
- Cadence → Monthly.
- Unit Price → 20.00.
- Customer Scope → optional.
- Save.
- Now go to Item Master and assign each barrel-stored SKU to
Storage Billing Unit Code = BARREL(Walkthrough D below).
Walkthrough C — create a Pallets rule
- Create Rule.
- Code → "PALLET-MONTH". Name → "Pallet Storage". Unit Code →
PALLET. Unit Label → "Pallet". - Quantity Source → Pallets. Cadence → Monthly. Unit Price → 25.00.
- Customer Scope → "Acme Corp" (or blank for default).
- Save.
Walkthrough D — assign a SKU to a storage unit (Item Master)
This is how the engine knows whether a SKU bills as BARREL, CBM, PALLET, or anything else.
Step-by-step
- Go to
/console/business/wms/item-master. - Open a SKU.
- Find the Storage Billing section. Set:
- Storage Billing Unit Code → e.g.,
BARREL(must match the rule's Unit Code exactly). - Storage Billing Qty/Unit → how many of the unit one stocked unit equals:
1if one stocked unit bills as one barrel.200if one stocked unit bills as 200 litres (and the rule's Unit Code isLITRE).- Blank when the rule uses Volume (CBM) and quantity comes from dimensions instead.
- Storage Billing Unit Code → e.g.,
- Save.
Walkthrough E — set a client's General Storage Billing Rule (client-level fallback)
Use case: 3PL Client A is generally billed by CBM on every SKU. Instead of setting Storage Billing Unit Code = CBM on every one of their SKUs, set one client-level fallback. The engine uses the fallback unless a more specific rule overrides.
Step-by-step
- Go to Business Console → Customers.
- Open the downstream client's record (e.g., 3PL Client A).
- Scroll to the Storage Billing section.
- Find General Storage Billing Rule. Pick the rule that applies as the client's default — e.g., the client's CBM Storage rule.
- Save the customer record.
What this does
- Sets the client-level fallback storage billing rule.
- Useful when the client is mostly billed one way (CBM, Pallet, Bin, BARREL — whatever you've defined).
- Reduces the need to assign storage rules on every SKU individually.
Walkthrough F — override a SKU's storage unit for one customer (Customer Catalog)
Use case: SKU "BLUE-LIQUID" defaults to BARREL in Item Master. But for one downstream client (Beta Industries), it should bill as CBM instead — a per-SKU, per-client override.
CBM) exists and is active in Storage → Rules — otherwise it won't appear in the dropdown.
Step-by-step
- Go to Business Console → WMS → Item Master. Switch to the 3PL context tab (the client view).
- Find the downstream client SKU row you want to override (filtered by Beta Industries).
- Click Edit Rules on that row. A modal opens with the SKU's billing rule fields.
- Find the Storage Billing Unit Code dropdown. The list is populated from active storage rules. Pick
CBM. - Set Storage Billing Qty / Unit when needed. (For CBM-with-Volume-source, leave blank — quantity resolves from dimensions. For BARREL-with-Assigned-SKU-Storage-Units source, set to 1 or whatever the SKU equals.)
- Save the rules. For Beta Industries' inventory of this SKU, the engine now uses CBM. For other customers, it still uses BARREL.
Resolution priority (5 layers)
When the engine bills a stock row, it picks a Unit Code in this order — first match wins:
| Priority | Source | When to use |
|---|---|---|
| 1 (highest) | Inventory row's explicit storage_rule_id or storage_rule_code | Rare one-off operational exceptions only. |
| 2 | Customer Catalog storage assignment (WMS → Item Master → 3PL context → Edit Rules) | One downstream client needs a different unit for a specific SKU than the tenant default. |
| 3 | Client General Storage Billing Rule (Customers → [client] → General Storage Billing Rule) | The whole downstream client is generally billed by one rule (e.g., everything CBM, everything Pallet). |
| 4 | Item Master storage assignment | Tenant-wide default for the SKU across all clients. |
| 5 (lowest) | Auto-match / default tenant rule | Implicit fallback when nothing more specific exists. |
Decision guide — where to configure the rule
| Configure here | Use when | Example |
|---|---|---|
| Customer Details → General Storage Billing Rule | The whole client is generally billed the same way. | Client A is mostly CBM. Set their General Storage Billing Rule to the CBM rule and stop fiddling with each SKU. |
| WMS → Item Master | The SKU normally bills the same way across all clients. | A bulk liquid SKU is usually billed as BARREL for everyone. Set Storage Billing Unit Code = BARREL on the SKU. |
| WMS → Item Master → 3PL context → Edit Rules | One client needs a different storage unit for one specific SKU than the Item Master default. | SKU "BLUE-LIQUID" is BARREL by default but Beta Industries needs it billed as CBM. |
Inventory row → storage_rule_id / storage_rule_code | Rare one-off operational exception. | One specific stock row needs a different rule for one period. |
Worked example — vape 3PL client billed in BARREL + CBM in the same month
3PL Client A stores both liquid (in barrels) and solid (by volume) goods. You set up two rules scoped to Client A:
Rule 1 — Barrel Storage
- Unit Code =
BARREL, Unit Label = "Barrel", Quantity Source = Assigned SKU Storage Units, Cadence = Monthly, Unit Price = $20, Customer Scope = 3PL Client A.
Rule 2 — CBM Storage
- Unit Code =
CBM, Unit Label = "CBM", Quantity Source = Volume (CBM), Cadence = Monthly, Unit Price = $60, Customer Scope = 3PL Client A.
Item Master assignment
- Liquid SKUs → Storage Billing Unit Code =
BARREL, Storage Billing Qty/Unit = 1. - Solid SKUs → Storage Billing Unit Code =
CBM, Storage Billing Qty/Unit = blank.
Month-end usage
- 1 barrel-assigned SKU unit in storage.
- 1 CBM of CBM-assigned product in storage.
Result
- Barrel storage: 1 × $20 = $20.
- CBM storage: 1 × $60 = $60.
- Total: $80.
Same customer. Same month. Two different storage models. No code changes needed.
Data quality requirements per Quantity Source
| Quantity Source | Required upstream data |
|---|---|
| Quantity Units | Accurate inventory unit counts (basic). |
| Volume (CBM) | Inventory rows carry volume_cm3, OR Item Master / Customer Catalog has dimensions for CBM resolution. |
| Assigned SKU Storage Units | SKU has Storage Billing Unit Code matching the rule's Unit Code, AND Storage Billing Qty/Unit filled in if the billed quantity comes from the SKU assignment. |
| Handling Units | HUs accurate; merging consolidations done before period close. |
| Pallets | Stock is in pallet-typed HUs; HU type stays accurate. |
| Occupied Bins | Operators put away to actual bins, not generic zones. |
| Flat Rental | Active rule and a billable customer. |
| All sources | Inventory ownership tagged to the correct downstream customer (owner_customer_id). |
Storage Billing Unit Code doesn't match any active rule's Unit Code (case-sensitive string match), the engine doesn't bill that SKU. Audit your unit codes if "expected revenue is missing" tickets show up.
Walkthrough C — review today's accrual
- Go to
/console/business/storage/accrual. Header: "Storage Accrual". - Top: cards summarizing today's run — total accrual amount, customers billed, items processed, exceptions count.
- Below: a table of accrual entries, one row per inventory-item-day. Columns: Date, Customer, SKU, Qty, Rate, Charge, Status (pending / appended / adjusted).
- Filter by customer / date / status as needed.
- Click a row to see the calculation breakdown.
Walkthrough D — manually run accrual for back-fill
The cron normally runs daily at 02:00 UTC. If a day was missed (e.g., system was down), run it manually.
- From
/console/business/storage/accrualclick Run Accrual Manually. - Modal: pick the date range (e.g., 2026-04-30 to 2026-05-02 for a 3-day back-fill).
- Click Run. Spinner: "Processing 3 days × 142 inventory items × 12 customers..."
- Done. Toast shows count of accrual entries created.
Walkthrough E — adjust an accrual entry
Use case: customer was supposed to have 5 free days but the system charged them — credit the entry.
- Find the offending entry in the accrual table.
- Click the row. Detail panel.
- Click Adjust.
- Modal: New Amount (e.g., 0), Reason (dropdown), Notes.
- Submit. Audit log captures original vs. adjusted amount.
Walkthrough F — storage aging report
- Go to
/console/business/storage/aging. - Filter by customer or all.
- Table shows each inventory item with: SKU, owner, location, qty, days in storage, total accrual to date.
- Sort by days descending to spot dormant inventory.
- Click Export CSV to send to the customer for "please tell us what to do with these old units."
Chapter 6 — The Service Catalog
Every invoice line references a service. The catalog is the master list. Set it up once; reuse on every invoice.
Walkthrough — create a service
Step-by-step
- Go to
/console/business/billing/services. Header: "Service Catalog". Top-right: Create Service. - Click Create Service. Modal opens.
- Fill:
- Code → "FULFILL-ORDER" (uppercase, hyphens). Auto-derived from name if blank.
- Name → "Order Fulfillment".
- Category → dropdown: fulfillment / qc / storage / extra_work / additional_work / shipping_label / other.
- Unit → text. "order" / "hour" / "day" / "label" / "kg" / "pallet".
- Default Unit Price → number. The fallback price when no pricing rule matches.
- Currency → USD / CAD / EUR.
- Description → optional. Internal notes.
- Taxable → checkbox. Tick if tax applies (storage and labour usually yes; pure shipping label cost passthrough usually no).
- Active → on by default.
- Click Create Service. Toast: "Service created."
A starter set for a 3PL — copy these in order
| Code | Name | Category | Unit | Default Price | Taxable |
|---|---|---|---|---|---|
| FULFILL-ORDER | Order Fulfillment | fulfillment | order | $1.50 | Yes |
| STORAGE-DAY | Daily Storage | storage | day | $0.35 | Yes |
| QC-UNIT | QC Check | qc | unit | $0.50 | Yes |
| SHIP-LABEL | Shipping Label | shipping_label | label | $0.20 | No |
| EXTRA-LABOR | Extra Handling | extra_work | hour | $35.00 | Yes |
| RECEIVE-PALLET | Receiving | additional_work | pallet | $7.00 | Yes |
Chapter 7 — Pricing Rules
Rules tell Trenvar how to turn warehouse activity into invoice lines. Rules can be tenant-wide or scoped to one customer. Fulfillment rules support two pricing models — pick the one that matches your contract.
Source types most relevant in 3PL OS
| Source Type | Used for |
|---|---|
| fulfillment | Per-order, per-package, per-SKU, per-picked-unit charges. Supports two pricing models (see below). |
| storage | Daily/monthly accrual. |
| qc | Quality-check labour. |
| extra_work / additional_work | One-off labour (kitting, repacking). |
| shipping_label | Per-label charges. |
| manual | Added by hand at invoice time. |
| other | Anything that doesn't fit the categories above. Use sparingly so reports stay clean. |
Fulfillment pricing models
When you pick Source Type = Billing: Fulfillment, you also pick a Pricing Model. There are now two:
| Pricing Model | What it does |
|---|---|
| standard | The classic model. Bills per order / package / SKU / picked-unit, with optional weight tiers. This is what every existing rule defaults to. |
| base_plus_additional_distinct_sku | NEW. Bills a flat base amount that includes up to N distinct SKUs per fulfillment, plus an additional charge for every distinct SKU beyond that. Weight tiers are not used in this mode. |
Walkthrough A — Standard model with weight tiers
The classic 3PL fulfillment package pricing flow.
- Click Create Rule.
- Source Type → "Billing: Fulfillment".
- Rule Name → "3PL Fulfillment Package Pricing".
- Customer → leave blank for all customers, or pick one for negotiated rate.
- Pricing Model → standard.
- Billable Fulfillment Unit → package (or order / sku / picked_unit).
- Default Amount → fallback rate.
- Weight Basis → Exact package weight (or shipment_total / package_average / none).
- Add weight tiers. Each tier has three fields entered in a small grid:
upToKg(number, leave blank for "and above"),amount(number),label(text, optional).
The conceptual format upToKg=amount=label is still the easiest way to write tiers down on paper, and matches what you enter in the UI three-field grid:
2=2.75=Up to 2kg
5=4.25=Up to 5kg
10=6.5=Up to 10kg
=9=Over 10kg
Walkthrough B — Base + additional distinct SKU model
Bills one base amount per fulfillment that includes a fixed number of distinct SKUs, plus a per-SKU charge for anything beyond. Useful when you charge for SKU-mix complexity rather than weight.
Step-by-step
- Click Create Rule.
- Source Type → "Billing: Fulfillment".
- Rule Name → e.g. "Base + SKU Pricing".
- Customer → optional scope.
- Pricing Method → Base + additional distinct SKU.
- Base Amount → the starting fulfillment charge (e.g., $1.50).
- Included Distinct SKUs → how many distinct SKUs are included in the base amount (e.g., 1).
- Additional Distinct SKU Charge → the extra amount for each distinct SKU above the included count (e.g., $0.50).
- Save.
The formula
base amount + max(0, distinct SKU count − included distinct SKU count) × additional distinct SKU charge
Worked examples
Rule: Base $1.50, Included 1, Additional $0.50.
- Order with 1 distinct SKU (3 units of SKU A, all in 1 package) → $1.50 base + 0 extras = $1.50.
- Order with 2 distinct SKUs (3 units of SKU A + 3 units of SKU B, all in 1 package) → $1.50 + 1 × $0.50 = $2.00.
- Order with 5 distinct SKUs → $1.50 + 4 × $0.50 = $3.50.
Unit quantities don't affect this method. Three units of one SKU billed the same as one unit of one SKU.
Walkthrough C — shared-package small SKU setup (multiple small SKUs in one parcel)
Use this when multiple small SKUs are allowed in the same package and should bill as one parcel — not one fee per SKU.
The example
3 small SKUs → operator packs them into 1 package → 1 shipping label → 1 parcel charge on the invoice.
Step 1 — configure SKU packaging rules
- Go to
/console/business/wms/item-master. - Use the Client view for downstream 3PL customer products, or Internal for your own warehouse stock.
- For each SKU that can share a package, set:
- Fulfillment Billing → package.
- Ship Alone → false.
- Cannot Mix Groups → leave blank unless you need restrictions.
- Packaging Group → optional but recommended for similar items (e.g.,
small-parcel). Helps maintain a consistent packing policy.
- Save each SKU.
Step 2 — configure the fulfillment pricing rule
- Go to
/console/business/billing/pricing-rules. Create or edit a fulfillment rule. - Set:
- Source Type → fulfillment.
- Billing Unit → package.
- Pick a Weight Basis matching your model:
- Exact package weight — real parcel weight determines the price.
- Average package weight — simpler model using the order's mean parcel weight.
- No weight tiers — every package is the same charge.
- Save.
Step 3 — what billing actually does
If the order has 3 SKUs that are all package-billed and not Ship Alone, and the operator packs them into 1 parcel:
- Shipment has 1 package.
- Shipping label has 1 parcel.
- Append bills 1 package charge.
The SKU rules allow sharing; the actual pack and label record determine the count. Invoicing follows the real package count, not the SKU count.
Using Packaging Group
Define groups for SKUs that are usually compatible:
- SKU A → Packaging Group = small-parcel.
- SKU B → Packaging Group = small-parcel.
- SKU C → Packaging Group = small-parcel.
This signals to operators (and any future packing logic) that these items should travel together. For exceptions, use Cannot Mix Groups to block specific combinations without forcing all items into Ship Alone.
Customer-specific overrides
Two rules: default ($1.50/package, customer blank) + Acme rule ($1.25/package, customer = Acme). Acme uses theirs; everyone else uses default. Customer-specific rules can use a different pricing model than the default — for example, Acme on base_plus_additional_distinct_sku while everyone else stays on standard.
Activation
No effective dates. Use the Active toggle. Changes apply only to new invoice lines after the change.
Chapter 8 — Tax Setup
Tax regions tell Trenvar what rate to apply to taxable invoice lines. You define one region per tax jurisdiction you sell into.
Walkthrough — create a tax region
Prerequisites
- company_admin or accounting role with
tax.regions.configure. - You know the rate, country, and state/province.
Step-by-step
- Go to
/console/business/billing/tax. Header: "Tax Regions". Top-right: Create Region. - Click Create Region. Modal opens.
- Fill:
- Code → "ON_HST" (uppercase, underscores, unique).
- Name → "Ontario HST".
- Country → ISO code dropdown. Pick CA.
- State / Province → ON.
- Tax Rate % → 13.0 (just the number, not "13%").
- Inclusive → off (tax adds on top of price). Tick if your prices already include tax.
- Effective From → optional date.
- Effective To → optional date (use to schedule rate changes ahead of time).
- Description → optional notes.
- Active → on.
- Click Create. Toast: "Tax region ON_HST created."
How it gets applied
When you finalize an invoice (Chapter 9), Trenvar:
- Reads the customer's billing address.
- Matches country + state to an active tax region.
- Applies the rate to every invoice line where the service has Taxable ticked.
- Adds a "Tax" summary line to the invoice with the region's name and rate.
Worked example
Acme Corp's billing address is in Ontario, Canada. Their invoice has $1,500 in taxable fulfillment + $500 in non-taxable shipping label costs. Trenvar matches Country=CA + Province=ON to the ON_HST region (13%). Tax line: $1,500 × 13% = $195. Invoice total: $1,500 + $500 + $195 = $2,195.
Chapter 9 — Invoicing Customers
Invoice lifecycle: Draft → Issued → (Sent) → Paid → (Overdue) → (Void). This chapter walks through every step from draft to paid.
Walkthrough A — create a draft invoice manually
Prerequisites
- accounting role with
billing.invoices.update(or company_admin). - Customer record exists with a billing profile.
- Service catalog has the relevant services.
What the screen looks like
Go to /console/accounting/billing. Header: "Invoices". Filter pills: All / Draft / Issued / Paid / Overdue / Void. Top-right: Create Invoice.
Step-by-step
- Click Create Invoice. New page opens (full-page editor, not modal).
- Fill the header:
- Customer → typeahead. Pick "Acme Corp".
- Invoice Number → auto-fills based on tenant pattern (e.g., INV-2026-001234). Edit only if you have a strong reason.
- Currency → defaults from customer billing profile.
- Invoice Date → defaults to today.
- Due Date → calculated from customer's payment terms (e.g., today + 30 days).
- Period From / To → optional. Use for "billing for May 2026".
- PO Number → optional. Customer's reference.
- Lines section appears below. Click Add Line:
- Service → typeahead from catalog. Pick "Order Fulfillment". Unit auto-fills ("order"); Default Price auto-fills ($1.50).
- Source Type → manual / fulfillment / storage / shipping_label / etc. Default manual for hand-typed lines.
- Source Ref → only required for storage / shipping_label (the accrual entry ID or label ID).
- Quantity → 120.
- Unit Price → defaults from pricing rule if customer has one, else service default. Editable.
- Description → optional override of the service description.
- Taxable → inherited from service. Tickable.
- Add more lines as needed.
- Click Add Adjustment for any non-line credits/discounts/fees:
- Type → Discount / Credit / Fee.
- Amount → number (negative for discount/credit).
- Description → "Volume discount", "Early-pay credit", etc.
- Right sidebar shows running totals: Subtotal, Adjustments, Subtotal after adjustments, Tax (calculated on finalize), Total Due.
- Click Save Draft at the bottom. Toast: "Draft saved." Invoice appears in the Drafts filter.
Walkthrough B — append storage accruals (3PL shortcut)
Skip the manual line-typing for storage. Append in one click.
- From the draft invoice, click Append Accruals (right sidebar).
- Modal: pick the date range to append (defaults to invoice's Period From/To).
- Click Append.
- Trenvar fetches all unappended storage accrual entries for this customer + window, groups them by storage rule, and adds them as invoice lines (e.g., "30 days × ACME-STD storage @ $0.05 × 142 units = $213.00").
- Toast: "8 accrual lines appended." Subtotal updates.
Walkthrough C — append fulfillment work (3PL shortcut)
- From the draft, click Append Fulfillment.
- Modal: date range, optional filter by carrier or order type.
- Click Append.
- Trenvar finds all completed, uninvoiced fulfillment orders for this customer + window. For each: reads linked shipping label parcels, applies the customer's fulfillment pricing rule (standard with weight tiers, or base + distinct SKU), creates one or more invoice lines.
- Toast: "47 fulfillment orders → 53 invoice lines."
Walkthrough D — finalize and send
- Review draft totals. Make any final adjustments.
- Click Finalize Invoice (top-right).
- Confirmation: "Finalize INV-2026-001234 for Acme Corp ($2,195.00)? Lines will be locked. Tax will be calculated. Invoice number assigned. Email will be sent to ap@acme.com."
- Click Confirm Finalize.
- Trenvar:
- Calculates tax (Chapter 8).
- Locks lines (no more edits).
- Assigns the next invoice number.
- Generates PDF.
- Emails customer's billing email via Resend.
- Status flips to Issued.
- Toast: "Invoice finalized + emailed." The right sidebar now shows: Issued At, Email Status (sent / failed / bounced), Stripe Pay link.
Walkthrough E — re-send invoice email
- Open the issued invoice.
- Click Send Email in the top-right (now visible because status is Issued, not Draft).
- Modal: pre-filled "to" address (billing email), customizable subject + body. Defaults to "Reminder: Invoice INV-2026-001234".
- Click Send. Trenvar emails again via Resend. Email status updates.
Walkthrough F — void an invoice
Use case: invoice was issued but contains errors and you need to cancel it cleanly.
- Open the issued invoice.
- Three-dot menu → Void.
- Modal: enter a void reason (mandatory). Pick action: Just void / Void + create credit memo for customer.
- Click Void Invoice.
- Status flips to Void. Audit log records actor, reason, time. If credit memo selected, a credit-memo invoice is auto-created.
Chapter 10 — Recording Payments
Two paths: Stripe pays you automatically, or you record a payment manually. Both update the invoice status.
Walkthrough A — Stripe automatic (most common)
- Customer opens their invoice in the portal (
/console/customer/invoices/[invoiceId]). - Customer clicks Pay Now. Stripe Checkout opens.
- Customer pays. Stripe POSTs to
/api/stripe/webhooks. - Trenvar matches the Stripe charge to the invoice ID, marks the invoice Paid, records
paidAt. - Both customer and your accounting team get email confirmations.
You don't do anything during this flow. It's all automatic.
Walkthrough B — manual payment entry (cheque, wire, ACH)
Use case: customer pays you outside Stripe — by cheque, wire transfer, ACH, or in person.
- Open the issued invoice in
/console/accounting/billing. - Click Record Payment (right sidebar).
- Modal:
- Amount → number. Defaults to outstanding balance.
- Payment Date → date picker. Defaults to today.
- Payment Method → dropdown: Cheque / Wire Transfer / ACH / Cash / Other.
- Reference → free text. "Cheque #1234", "Wire ref ABC-789".
- Notes → optional.
- Click Record Payment.
- If the amount equals the full outstanding balance, status flips to Paid. If less, the invoice stays Issued with payments-to-date tracked, and the next-due-amount updates.
Walkthrough C — partial payments
- Customer paid $1,000 against a $2,195 invoice.
- Open the invoice. Record Payment for $1,000.
- Status stays Issued. Right sidebar shows: Total $2,195, Paid $1,000, Outstanding $1,195.
- When customer pays the rest, repeat Record Payment for $1,195. Status flips to Paid.
Walkthrough D — refund (Stripe)
- Open the paid invoice.
- Three-dot menu → Refund Payment.
- Modal: refund amount (full or partial), reason, notes.
- Click Refund via Stripe.
- Trenvar calls Stripe's refund API. Stripe processes (1–2 weeks for the customer to see it back on their card).
- Invoice status flips to Issued if fully refunded; new outstanding balance.
- If partial refund, an adjustment line is added; original invoice keeps its number.
Chapter 11 — Customer Portal Access
Once a customer has portal users, they sign in at the same login URL but land on a restricted, customer-scoped surface. This chapter walks through what they see and how to give them access.
Walkthrough — invite a customer's portal user
Prerequisites
- company_admin (you).
- Customer record exists with portal access toggled on (Chapter 3).
- You have the user's email + name.
Step-by-step
- Go to
/console/business/settings/users. Header: "Users". - Click Add User.
- Fill:
- Email → "jane@acme.com".
- Display Name → "Jane Smith".
- Role → tick customer_admin (or customer_user for standard access).
- Linked Customer → "Acme Corp".
- Click Save & Send Invite. Trenvar sends Jane an invite email.
- Jane clicks the link, sets a password, and lands at her default portal screen (
/console/customer/inventoryfor customer_admin).
What Jane sees in 3PL OS
| Screen | Shows |
|---|---|
| /console/customer/inventory | Acme's stock by SKU, lot, location, status (available / awaiting / reserved). She cannot see other customers' stock. |
| /console/customer/incoming | Acme's inbound pre-alerts and shipments. Status updates live. |
| /console/customer/orders | Acme's fulfillment orders (placed by Acme or by you on Acme's behalf). |
| /console/customer/invoices | Acme's invoices + Stripe Pay Now button on each Issued invoice. |
| /console/customer/wallet | Jane's saved Stripe payment methods. |
(Catalog, Ship-Now, and B2B Storefront screens require Commerce OS — see that guide.)
Portal role differences
| Role | Extras over baseline |
|---|---|
| customer_admin | Manages other portal users for own org. Connects Shopify integration. Sees Integrations screen. |
| customer_user | Standard. View, create order, pay. Cannot manage users or integrations. |
Walkthrough — customer_admin invites another user from the portal
- Jane (customer_admin) signs in.
- Clicks Settings (or Users) in the sidebar.
- Sees a list of her org's portal users.
- Clicks Add User.
- Fills: email, display name, role (only customer_user available — she can't create another customer_admin without your approval).
- Saves. Trenvar sends invite. Same flow as before.
Walkthrough — customer creates a 3PL receiving order (inbound pre-alert) from the portal
This is how a downstream client tells you "I'm sending you these goods — please be ready to receive them." It creates a pre-alert that lands in your warehouse's receiving queue.
Prerequisites (customer side)
- Portal user with role customer_admin or customer_user — both have
customer.orders.create. - The SKUs they want to send are already in their catalog (you set this up during onboarding).
- They know the expected arrival date and quantities.
What the screen looks like
Customer signs in; lands at /console/customer/incoming (or clicks Incoming in their sidebar). Header reads "Incoming Shipments" with a subtitle: "Create inbound orders so your 3PL business can schedule receiving before arrival." The page shows their existing pre-alerts as a table (status, prealert code, expected arrival, units expected vs received). Top-right: a New 3PL Receiving Order button (visible only to roles with create permission).
Step-by-step (customer)
- Click New 3PL Receiving Order. An editable grid form opens.
- Fill the header:
- Expected Arrival — date or date+time picker.
- Notes — free text ("Driver Mike Johnson, ETA 9 AM, dock 4").
- Documents — optional. Attach packing slips, commercial invoices, or any paperwork.
- Add expected lines in the grid. For each row:
- SKU — typeahead (autocompletes from the customer's catalog only — they can't pre-alert SKUs they don't own).
- Expected Units — number.
- Expected Volume (CBM) — auto-calculates from SKU dimensions if available; editable.
- Lot — required if the SKU is lot-controlled.
- Serial — required if the SKU is serial-controlled.
- Expiry Date — required if the SKU is expiry-required.
- Batch No — optional reference.
- Add as many lines as needed. Single-unit-per-line SKUs get one row per unit.
- Click Submit.
- Toast confirms: "3PL receiving order submitted (PA-20260503-001)." The new pre-alert appears in the customer's table with status open.
What the tenant sees
- The pre-alert immediately appears in the tenant's queue at
/console/ops-manager/pre-alertwith Source = customer_portal (so ops managers know it came from the customer, not from in-house). - Customer field is auto-set to that downstream client.
- An audit entry is logged with the customer user's UID.
- The tenant doesn't need to re-enter anything. Receiving (Warehouse OS Chapter 6) runs against this pre-alert as if your ops manager had created it.
customer.orders.create (e.g., a future read-only role). Have their customer_admin promote them, or their company_admin (you) update the role.
Walkthrough — customer creates a fulfillment order from the portal (3PL OS, no Commerce OS)
This is how a downstream client tells you "ship this to this address" without going through a B2B catalog. Pure 3PL OS — no Commerce OS / no storefront required.
Prerequisites (customer side)
- Portal user with role customer_admin, customer_user, customer_buyer, or customer_ship_now — all have
customer.orders.create. - The SKUs they want to ship exist in their catalog and have on-hand inventory.
- They have the recipient's full address.
What the screen looks like
Customer signs in; clicks Orders in their sidebar. The page lists their existing orders (status pill, financial status, items, date). Top-right: a Create Manual Order button.
Step-by-step (customer)
- Click Create Manual Order. Modal form opens.
- Fill the header:
- Order Number — optional. Their internal reference. Leave blank to auto-generate.
- Notes — optional free text for the warehouse.
- Fill the recipient (Ship-To):
- Recipient Name — required.
- Phone, Email — recommended (carrier delivery notifications).
- Address Line 1 — Google Maps autocomplete kicks in as they type. Picking a suggestion auto-fills city, state, zip, country.
- Address Line 2, City, State/Province, Zip / Postal Code, Country — finish or correct as needed.
- Add lines. For each row:
- SKU — typeahead from their catalog.
- Requested Units — number. Cannot exceed available inventory for that SKU under their ownership.
- Lot / Serial — only if the SKU requires them.
- Add as many lines as the order needs.
- Click Submit Order.
- Trenvar generates an idempotency key client-side so duplicate clicks don't create duplicate orders.
- Toast confirms: "Order submitted." The new order appears in the customer's list with initial status (depends on Fulfillment Automation setting — see below).
Fulfillment Automation toggle
At the top of the customer's Orders page is a Fulfillment Automation card with two modes:
| Mode | What it does |
|---|---|
| Always send to 3PL | Every customer-submitted order routes to your warehouse fulfillment queue immediately. |
| Require my decision first | Orders land in a customer-side review state. Customer approves each one before it reaches your warehouse — useful when they want a human gate. |
Customers pick the mode they want. Most pick Always send to 3PL to minimize friction.
What the tenant sees
- The order lands in your fulfillment queue at
/console/business/wms/fulfillment(or the operator's pick queue) with Source = customer_portal. - Customer field auto-set to that client.
- Pick task auto-generated.
- From here, the operator picks/packs/labels exactly as if the order had been entered by your team — see Warehouse OS Chapters 11–13.
- Status updates flow back to the customer's portal in real time as the order moves through fulfillment.
Worked example — Acme self-serves an order
- Mon 10:00 — Jane (customer_admin at Acme) signs in to
/console/customer/orders. - 10:01 — Clicks Create Manual Order.
- 10:02 — Order Number = "ACM-PO-2026-512", Notes = "Standard ground service".
- 10:03 — Recipient Name = "Beta Industries", picks address from Google Maps autocomplete (12 Industrial Way, Calgary AB).
- 10:04 — Adds 2 lines: 12 × SKU-BLUE-SHIRT-M, 8 × SKU-RED-SHIRT-L.
- 10:05 — Clicks Submit Order.
- 10:05 — Order O-20260503-014 lands in your operator's pick queue (because Acme set automation to "Always send to 3PL").
- 10:30 — Operator picks. Status flips to picked; Jane sees it in her portal.
- 11:15 — Pack + label complete. Status dispatched. Jane sees the FedEx tracking link.
customer_buyer (B2B Storefront, requires Commerce OS) don't see the manual-order form — they go through the catalog/cart instead. The Create Manual Order button is hidden for that role. Use customer_user or customer_admin for free-form manual orders.
Chapter 12 — Reports & Exports
| Report | What it shows |
|---|---|
| Invoice Settlement | Invoices in a date range, by status. |
| Storage Revenue | Accrual aggregated by customer, rule, window. |
| Tax Report | Tax collected by region. |
| Storage Aging | How long stock has sat per customer. |
QuickBooks export
Per-invoice Export to QuickBooks. Maps to QB invoice object via OAuth connection. Status fields track export success and errors.
Chapter 13 — Daily Flow per Role
Accounting's day in 3PL OS
- Open invoice queue.
- For each customer with billable activity: create draft invoice → Append Accruals → Append Fulfillment → review adjustments → Finalize.
- Record any manual payments received.
- Run aging report; chase overdue.
Customer service's day
- Triage cases.
- Look up customer-specific shipments and inventory in response to questions.
- Send notices for service updates.
Customer portal user's day
- Log in.
- Check inventory levels.
- Notify the warehouse of incoming goods (pre-alert if enabled).
- Place an order if needed.
- Pay outstanding invoices.
Appendix A — 3PL OS Console Paths
| Path | Screen |
|---|---|
| /console/business/3pl/shipments | 3PL shipments view |
| /console/business/customers | Customer list |
| /console/customer-service/customers | Customer list (CS) |
| /console/customer-service/customers/[customerId] | Customer detail |
| /console/business/storage/rules | Storage rules |
| /console/business/storage/accrual | Storage accrual runs |
| /console/business/storage/aging | Aging report |
| /console/business/billing/services | Service catalog |
| /console/business/billing/pricing-rules | Pricing rules |
| /console/business/billing/invoices | Invoices (admin) |
| /console/business/billing/payments | Payments |
| /console/business/billing/exports | Exports |
| /console/business/billing/imports | Imports |
| /console/business/billing/tax | Tax regions |
| /console/accounting/billing | Accounting invoice queue |
| /console/accounting/billing/[invoiceId] | Invoice detail |
| /console/accounting/storage | Storage from accounting view |
| /console/accounting/storage/aging | Aging (accounting) |
| /console/accounting/reports | Reports + exports |
| /console/accounting/customers | Customer list (accounting) |
| /console/accounting/customers/[customerId] | Customer detail (accounting) |
| /console/customer/inventory | Customer portal — inventory |
| /console/customer/incoming | Customer portal — pre-alerts |
| /console/customer/orders | Customer portal — orders |
| /console/customer/invoices | Customer portal — invoices |
| /console/customer/invoices/[invoiceId] | Invoice detail (portal) |
| /console/customer/integrations | Customer portal — integrations |
| /console/customer/wallet | Customer portal — payment methods |
Appendix B — 3PL OS Permissions
| Permission | Held by |
|---|---|
| customers.view, orders.view, invoices.view, payments.view | operations_manager, customer_service, accounting |
| storage.rules.read | operations_manager, accounting |
| storage.accrual.read | operations_manager, accounting |
| billing.service_catalog.read / configure | accounting |
| billing.pricing.read / configure | accounting |
| billing.invoices.list / read | operations_manager, accounting |
| billing.payments.read | operations_manager, accounting |
| billing.exports.export | accounting |
| billing.imports.read / run | accounting |
| billing.reconciliation.read / upload / review / approve | operations_manager (read+upload+review); accounting (full incl. approve) |
| tax.regions.read / configure | accounting |
| crm.customers.list / read / create / update | operations_manager (full); customer_service, operator, accounting (read) |
| cases.list | customer_service |
| customer.orders.create / view | customer_admin, customer_user |
| customer.invoices.view, customer.payments.pay | customer_admin, customer_user |
| customer.integrations.shopify.manage | customer_admin |
Appendix C — Glossary
Every 3PL-OS-specific term, in plain English.
3PL receiving order — Customer-portal name for an inbound pre-alert that the customer submits. Same as a tenant-side pre-alert; the form is just on their portal.
Accrual entry — One day of storage charge for one inventory item under one rule. The daily accrual job creates these.
Append — Bulk-add accrual or fulfillment lines onto a draft invoice with one click.
Billing profile — Per-customer billing setup: currency, payment terms, billable on/off, Stripe customer ID, billing email, tax exempt.
Cadence — How often a storage rule charges: Daily or Monthly.
Customer / Client — A downstream company you serve. Your tenant has many customers.
Customer Catalog — Per-customer SKU settings layer. Used to override Item Master defaults for one client.
Customer Scope — A field on storage rules and pricing rules: blank for tenant default, or one customer for a contract-specific rule.
Free Days — Grace period at the start of storage when no charge accrues.
General Storage Billing Rule — Client-level fallback storage rule on the customer record. Used when a whole client is mostly billed one way.
Invoice append — See Append.
Owner / owner_customer_id — Which downstream customer owns a unit of inventory. Tags every receiving event.
Packaging Group — Optional label on a SKU (e.g., "small-parcel") signalling which SKUs are usually compatible to share a package.
Pricing Method — A fulfillment-rule field: Standard (weight tiers) or Base + additional distinct SKU.
Pricing Rule — Tells Trenvar how to turn warehouse activity into invoice lines. Source types include fulfillment, storage, qc, extra_work, shipping_label, manual, other.
Quantity Source — A storage-rule field defining how the unit is measured: Quantity Units, Volume (CBM), Assigned SKU Storage Units, Pallets, Handling Units, Occupied Bins, Flat Rental.
Service Catalog — Master list of billable services (Order Fulfillment, Daily Storage, etc.). Every invoice line references a service.
Stripe Customer — A Stripe-side record that lets the customer self-pay invoices via Stripe Checkout.
Storage Billing Qty/Unit — A SKU field. How many of the storage unit one stocked unit equals (e.g., 1 = one barrel; 200 = 200 litres).
Storage Billing Unit Code — A SKU field. The free-form unit code (e.g., BARREL, CBM) linking the SKU to a storage rule.
Storage Rule — A billing contract: Unit Code, Quantity Source, Cadence, Unit Price, Free Days, Minimum, Customer Scope.
Tax Region — A jurisdiction's tax rate. Defined per country / state with a percentage and inclusive/exclusive flag.
Tenant — Your company. The 3PL operator. Distinct from "customer" (which is a downstream client of yours).
Unit Code — A free-form key on a storage rule (e.g., BARREL, CBM). SKUs reference this code to get billed by that rule.
Unit Label — Display name shown to users (e.g., "Barrel"). The Unit Code is the technical match key; the Unit Label is the human-readable label.
Weight Tiers — Per-fulfillment-rule pricing bands. Format upToKg=amount=label.
End of 3PL OS Guide · v1.0 · May 2026