Skip to content

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:

{
  "success": true,
  "result": true
}

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:

if sale_id:
    sale_order = self._jsonrpc_call('sale.order', 'search_read',
        [[('id', '=', sale_id[0])]],
        {'fields': ['po_order_name', 'partner_id'], 'limit': 1}
    )
    picking['po_order_name'] = sale_order[0].get('po_order_name', '')