Delivery Manifests¶
Who this is for
Warehouse team. You'll use this when a sales order is confirmed and you need to physically pick, pack, and ship the devices.
What you'll accomplish: scan the devices into their packing box, which automatically picks them on the manifest, posts COGS, creates the customer invoice, and (for consignment devices) generates settlement reports.
Before you start: the sales order must be confirmed and have devices allocated. The delivery manifest and packing box are both auto-created at confirmation time.
The flow¶
Since v2.26, delivery is a single scan per device. The packing box is the one place you scan — no separate pick step.
SO confirmed → Scan each IMEI into packing box → Mark Ready to Ship → Mark Shipped → Everything auto-posts
(one scan picks + packs)
Three automatic things happen the moment the box is marked shipped:
- COGS entry posts (DR COGS / CR Valuation)
- Customer invoice is generated and posted
- Settlement reports are created for consignment devices (owner + consignee, paired)

Step 1 — Find the order to pack¶
When a sales order is confirmed, Odoo auto-creates:
- A delivery manifest (type
delivery), pre-populated with the allocated IMEIs - A packing box (if the order isn't inter-company) with the allocations pre-assigned
You can jump to either from the SO's smart buttons (IMEI Manifest or Packing Boxes).
Which do I open — the manifest or the packing box?
Start with the packing box — that's where you'll scan. The manifest auto-updates from the box. You can open the manifest afterward to confirm picking is 100%, but you don't need to touch it to scan.
On a delivery manifest, you'll see a banner pointing you to the linked packing box and a Pack & Scan button that takes you there:

Step 2 — Scan devices into the packing box¶
Open the packing box (from the SO, via the Packing Boxes smart button, or from Inventory → Device Inventory → Packing Boxes).

Use the barcode scanner or the box's scan-to-pack UI. For each device:
- Scan the IMEI.
- The box's line is confirmed as
packed. - The matching manifest line automatically flips to
received(picked). - The progress counters on both documents tick up.

What each scan does¶
- If the IMEI matches an allocation on this order — it's packed and picked in one step.
- If the IMEI isn't on this order but could be (e.g. you swapped in an equivalent device last-minute) — the box auto-allocates it via
_handle_unexpected_scan()so you don't get stuck. The allocation is added to the SO and the manifest. - If the IMEI is already on another open order — the scan is rejected with a clear error. Resolve the conflict before re-scanning.
Scanning the same device twice¶
Second scan gets rejected. Each IMEI can be packed into at most one box.
QC/cost exceptions at shipment¶
Packing can scan devices that were allocated with a manager override, but Mark Shipped re-checks sale readiness before posting accounting. If a packed device is not QC Complete or has zero/missing cost and the allocation does not carry an override reason, shipment is blocked. Go back to the SO allocation, complete QC/fix cost, or have a manager approve the exception before shipping.
Step 3 — Mark the packing box ready, then shipped¶
When the box has every expected device scanned, click Mark Ready to Ship. The box moves to Ready to Ship and the Mark Shipped button appears. Then click Mark Shipped.
That's the completion event. The system then:
- Sets the box state to
Shipped. - Marks each device as
Soldon itsstock.lot, stamping the sale date and SO reference. - Posts the COGS journal entry (DR COGS / CR Valuation).
- Generates and posts the Customer Invoice.
- For each consignment device, creates the paired Owner Settlement Report (Axis Mobile) and Consignee Settlement Report (Reyder). Both are auto-confirmed, which triggers the vendor bill for Axis's owner amount.
- Marks the delivery manifest as
Done.

From this point on, sales, warehouse, and accounting all see the order in its finished state. Accounting's next step is to mark the settlement reports as paid once the vendor bill clears.
GL entries posted on ship¶
The COGS entry posts to the Device Stock Journal:
| Account | DR | CR |
|---|---|---|
| Device COGS (expense) | sum of purchase_cost |
|
| Device Valuation (asset) | sum of purchase_cost |
The customer invoice (posted to the standard sales journal) follows normal Odoo invoicing — product revenue credited, AR debited, with the customer's tax settings applied.
See Accounting Reference for the full breakdown of every entry including the settlement-report vendor bill.
Inter-company orders¶
When the customer on the SO is the other Reyder company (inter-company), the flow is slightly different:
- Marking the box shipped generates a mirror receiving manifest in the destination company's books so the devices land as receipts on the other side.
- The COGS entry is posted in the selling company.
- The customer invoice is an inter-company
out_invoice; Odoo's inter-company mirroring automatically creates the matching draftin_invoice(vendor bill) in the destination company for your accounting team to post.
See Inter-Company Transfers for the full inter-company flow.
Smart buttons on the manifest¶
| Button | What it opens |
|---|---|
| Received Devices | List of devices that have been scanned/picked |
| Packing Boxes | The packing box(es) associated with this order |
| Journal Entries | The COGS move and any related GL entries |
| Sale Order | The originating SO |
| Purchase Order | For receiving manifests only — the originating PO |
Common problems¶
Mark Shipped button is disabled
If the box is still Draft or Packing, first scan every expected device and click Mark Ready to Ship. Mark Shipped appears only once the box is in Ready to Ship. If the box is missing devices, the box shows packed / expected. Pack the remaining devices, or if a device is genuinely unavailable, go to the SO's Device Allocation tab and remove that allocation first — then the box expected-count drops and you can ship.
I scanned a device that the system says belongs to another order
That IMEI is allocated to a different open SO. Options: (a) deallocate it from that SO first if it was allocated in error, or (b) allocate a different IMEI to this order. The scanner won't let you double-book.
The manifest shows 100% picked but the box isn't shipped yet
Correct — those are different states. Scanning picks the manifest line and packs into the box. Mark Ready to Ship confirms the box is complete; Mark Shipped is the separate accounting/shipment confirmation that triggers COGS/invoice/settlement. If you want the manifest done, ship the box.
The customer invoice came out wrong
The invoice is generated from the SO lines, not the manifest. If the SO line had the wrong price or quantity, cancel the invoice, fix the SO, and re-post. The COGS entry is based on the actual purchase_cost of the shipped IMEIs and doesn't change with invoice edits.
I need to un-ship (reverse the delivery)
That's not a casual operation — it means reversing COGS, credit-noting the invoice, and deleting or reversing the settlement reports. Talk to accounting first and then use the device-by-device return flow rather than trying to un-ship in bulk.
Under the hood¶
Technical details for developers
Unified delivery flow (v2.26 change)
Before v2.26, picking and packing were separate — scan to pick on the manifest, then scan again to pack into a box. Commit ec1cff7 unified them: the packing box is now the single scan surface. The manifest no longer has its own barcode UI for delivery; its "Start Picking" / "Barcode Scanner" buttons hide when a packing box exists.
Models
device.manifestwithmanifest_type='delivery'device.manifest.line— one per allocated IMEI, statusexpected → receivedpacking.box— grouping container; statedraft → packing → ready → shippedpacking.box.line— one per packed IMEI
Scan → pick + pack
packing_box.scan_device(imei) is the entry point. It:
- Acquires
pg_try_advisory_xact_lockon the lot ID. - Resolves the IMEI to a
stock.lot. - Validates allocation: looks for a
sale.order.line.devicewithlot_id == loton this order. If missing and allocation-by-swap is possible, calls_handle_unexpected_scan()which creates the allocation. - Creates a
packing.box.linerow. - Calls
_quick_pick(lot)on the matching delivery-manifest line — flipsstatustoreceivedand linksstock_lot_id.
Mark shipped
packing_box.action_mark_shipped() orchestrates:
_validate_delivery_quantities()— catches over-delivery_process_customer_delivery()on the delivery manifest:stock.lot.action_mark_sold()per device →device_status='sold', stamps sale date_create_cogs_accounting_entry()→account.move, stored asmanifest.cogs_move_id_create_settlement_reports()(consignment only) → paired owner + consigneeconsignment.settlement.report. Uses.sudo()to cross the company boundary for the owner-side write._create_customer_invoice()→ callssale.order._create_invoices()and auto-posts; stored asmanifest.customer_invoice_id- For inter-company orders,
_create_buyer_receiving_manifest()mirrors the manifest in the destination company. manifest.state = 'done'box.state = 'shipped'
Each of the accounting steps is idempotent — if called twice (e.g. after a retry), the existing moves are reused via *_move_id checks.
Key fields — device.manifest
| Field | Notes |
|---|---|
manifest_type |
delivery for outbound, receiving for inbound |
sale_order_id |
Link to the originating SO |
state |
draft / in_progress / done |
cogs_move_id |
COGS journal entry |
customer_invoice_id |
Customer invoice |
account_move_id |
Inventory receipt GL (used on receiving manifests) |
expected_count / received_count / progress_percent |
Picking progress |