Inventory Module¶
Stock picking management for incoming (WH-IN) and outgoing (WH-OUT) operations.
Overview¶
The Inventory module provides:
- Incoming pickings list (receipts)
- Outgoing pickings list (deliveries)
- Picking detail with product lines
- Receive/Ship validation with full quantities
- Stock reservation (check availability)
- Customer and PO name display
Routes¶
Inventory Dashboard¶
GET /inventory
Redirects to incoming pickings (default view).
Incoming Pickings¶
GET /inventory/incoming
Lists WH-IN (incoming) pickings with state filtering.
| Parameter | Type | Default | Description |
|---|---|---|---|
| state | string | assigned |
Filter: assigned, confirmed, done, all |
Incoming Detail¶
GET /inventory/incoming/{picking_id}
Displays picking details with move lines.
Outgoing Pickings¶
GET /inventory/outgoing
Lists WH-OUT (outgoing) pickings with state filtering.
| Parameter | Type | Default | Description |
|---|---|---|---|
| state | string | assigned |
Filter: assigned, confirmed, done, all |
Outgoing Detail¶
GET /inventory/outgoing/{picking_id}
Displays picking details with move lines and reservation status.
Actions¶
Receive (WH-IN)¶
POST /inventory/incoming/{picking_id}/receive
Validates incoming picking with full demand quantities.
Process:
1. Set quantity_done = product_uom_qty for all moves
2. Call button_validate
3. Handle immediate transfer wizard if needed
4. No backorders created
Ship (WH-OUT)¶
POST /inventory/outgoing/{picking_id}/ship
Validates outgoing picking with full demand quantities.
Process:
1. Set quantity_done = product_uom_qty for all moves
2. Call button_validate
3. Handle backorder wizard (cancel backorder)
Check Availability¶
POST /inventory/outgoing/{picking_id}/check-availability
Reserves stock for outgoing picking.
Calls Odoo's action_assign to attempt reservation.
Unreserve Stock¶
POST /inventory/outgoing/{picking_id}/unreserve
Releases reserved stock back to available.
Calls Odoo's do_unreserve.
API Endpoints¶
Get Picking¶
GET /inventory/api/picking/{picking_id}
Response:
{
"success": true,
"picking": {
"id": 123,
"name": "WH/IN/00045",
"partner_id": [10, "Supplier Inc"],
"scheduled_date": "2024-03-15",
"state": "assigned",
"origin": "PO00123",
"jdx_packages_names": "PKG001, PKG002",
"customer_name": "John Doe"
},
"moves": [
{
"id": 456,
"name": "Premium Blinds 48x72",
"product_id": [78, "Premium Blinds 48x72"],
"product_uom_qty": 5.0,
"quantity_done": 0.0,
"reserved_availability": 5.0,
"is_reserved": true
}
]
}
Validate Picking¶
POST /inventory/api/picking/{picking_id}/validate
Response:
Picking States¶
| State | Label | Description |
|---|---|---|
draft |
Draft | Not confirmed |
waiting |
Waiting Another Operation | Depends on other picking |
confirmed |
Waiting | Waiting for stock |
assigned |
Ready | Stock reserved, ready to process |
done |
Done | Completed |
cancel |
Cancelled | Cancelled |
State Filtering¶
Default view shows Ready (assigned) pickings:
def incoming():
state_filter = request.args.get('state', 'assigned')
pickings = odoo_api.get_incoming_pickings(
state=state_filter if state_filter != 'all' else None
)
Picking List Display¶
Incoming (WH-IN)¶
| Column | Description |
|---|---|
| Name | Picking reference (WH/IN/XXXXX) |
| Packages | JDX package names |
| Customer | Customer name from linked SO |
| Scheduled | Expected date |
| Origin | Source document (PO number) |
Outgoing (WH-OUT)¶
| Column | Description |
|---|---|
| Name | Picking reference (WH/OUT/XXXXX) |
| PO Order | Customer's PO reference |
| Customer | Customer name from linked SO |
| Scheduled | Expected ship date |
Move Lines¶
Each picking has move lines showing products:
| Field | Description |
|---|---|
| Product | Product name |
| Demand | Quantity ordered (product_uom_qty) |
| Done | Quantity processed (quantity_done) |
| Reserved | Quantity reserved (reserved_availability) |
Reservation Status¶
For outgoing pickings, moves show reservation status:
def get_picking_moves(picking_id):
moves = odoo_api._jsonrpc_call(...)
for move in moves:
demand = move.get('product_uom_qty', 0)
reserved = move.get('reserved_availability', 0)
move['is_reserved'] = reserved >= demand if demand > 0 else False
return moves
Validation Flow¶
sequenceDiagram
participant User
participant PWA
participant Odoo
User->>PWA: Click "Receive" / "Ship"
PWA->>Odoo: Get picking moves
Odoo-->>PWA: List of moves
loop For each move
PWA->>Odoo: Set quantity_done = demand
end
PWA->>Odoo: button_validate
alt Immediate Transfer Wizard
Odoo-->>PWA: Wizard action
PWA->>Odoo: Process wizard
else Backorder Wizard
Odoo-->>PWA: Wizard action
PWA->>Odoo: Cancel backorder
end
Odoo-->>PWA: Done
PWA-->>User: Success message
Wizard Handling¶
The PWA automatically handles Odoo's validation wizards:
Immediate Transfer¶
When quantities aren't set before validation:
if wizard_model == 'stock.immediate.transfer' and wizard_id:
return self.call('stock.immediate.transfer', 'process', args=[[wizard_id]])
Backorder Confirmation¶
When not all quantities are received:
if wizard_model == 'stock.backorder.confirmation' and wizard_id:
# Cancel backorder - process without creating backorder
return self.call('stock.backorder.confirmation', 'process_cancel_backorder',
args=[[wizard_id]])
JDX Packages (WH-IN)¶
Incoming pickings may have linked JDX packages:
def get_incoming_pickings(state=None):
pickings = self.get_pickings_by_type('incoming', state)
for picking in pickings:
package_ids = picking.get('jdx_packages_ids', [])
if package_ids:
packages = self._jsonrpc_call('jdx.packages', 'search_read',
[[('id', 'in', package_ids)]],
{'fields': ['name']}
)
picking['jdx_packages_names'] = ', '.join([p['name'] for p in packages])
Customer Name Lookup¶
For both incoming and outgoing, customer name is fetched from linked sale order:
sale_id = picking.get('sale_id')
if sale_id:
sale_order = self._jsonrpc_call('sale.order', 'search_read',
[[('id', '=', sale_id[0])]],
{'fields': ['partner_id'], 'limit': 1}
)
if sale_order and sale_order[0].get('partner_id'):
picking['customer_name'] = sale_order[0]['partner_id'][1]
PO Order Name (WH-OUT)¶
Outgoing pickings show customer's PO reference: