Skip to main content
Procurement rules determine how stock requests are fulfilled. This guide explains how to configure routes, rules, and automated procurement workflows.

Understanding Procurement in Stock Requests

When a stock request is confirmed, the system triggers the Odoo procurement engine:
1

Procurement Creation

The stock request creates a procurement with:
  • Product and quantity requested
  • Destination location
  • Expected date
  • Selected route (if any)
2

Rule Matching

The procurement engine finds applicable rules based on:
  • Route selection
  • Product configuration
  • Location hierarchy
  • Warehouse configuration
3

Action Execution

Matched rules execute their configured action:
  • Create stock moves (pull/push)
  • Generate purchase orders (buy)
  • Create manufacturing orders (manufacture)
4

Allocation Tracking

Stock moves are linked to the request via allocations for progress tracking.

Procurement Values Prepared by Stock Requests

Stock requests pass specific values to the procurement engine:
def _prepare_procurement_values(self, group_id=False):
    return {
        'date_planned': self.expected_date,
        'warehouse_id': self.warehouse_id,
        'stock_request_allocation_ids': self.id,
        'group_id': group_id or self.procurement_group_id.id or False,
        'route_ids': self.route_id,
        'stock_request_id': self.id,
    }
These values are passed to the procurement group’s run() method and eventually to _get_stock_move_values() in stock rules.

Stock Move Allocation

The Stock Request module extends stock rules to create allocations:
class StockRule(models.Model):
    _inherit = 'stock.rule'

    def _get_stock_move_values(self, product_id, product_qty, product_uom,
                               location_id, name, origin, company_id, values):
        result = super()._get_stock_move_values(
            product_id, product_qty, product_uom, location_id,
            name, origin, company_id, values
        )
        if values.get('stock_request_id', False):
            result['allocation_ids'] = [
                (0, 0, {
                    'stock_request_id': values.get('stock_request_id'),
                    'requested_product_uom_qty': product_qty,
                })
            ]
        return result
Every stock move created by a procurement rule for a stock request automatically gets a linked allocation record.

Configuring Routes

Route Basics

Routes define procurement paths and can be:

Warehouse Routes

Associated with specific warehouses and automatically available for those locations

Product Routes

Assigned to specific products and available wherever those products are requested

Global Routes

Available for all products and locations without restrictions

Manual Routes

Must be explicitly selected on stock requests

Creating a Transfer Route

1

Navigate to Routes

Go to Inventory > Configuration > Routes.
2

Create New Route

Click Create and enter:
  • Name: Descriptive name (e.g., “Resupply from Warehouse”)
  • Company: Select the company
  • Warehouse: Select the warehouse(s) this route applies to
3

Add Rule

In the Rules tab, click Add a line and configure:
  • Name: Rule description (e.g., “Transfer to Shop Floor”)
  • Action: Select “Pull From”
  • Operation Type: Select the internal transfer type
  • Source Location: Where products will be pulled from
  • Destination Location: Where products will be moved to
  • Procure Method:
    • “Take From Stock” if source should have stock
    • “Make To Order” to trigger further procurement
4

Set Procurement Delay

Configure lead time:
  • Delay: Days needed to complete this operation
5

Save Route

Click Save.

Example: Multi-Step Transfer Route

To set up a route that moves products through multiple locations:
# Route: Resupply Shop Floor from Main Stock
route = env['stock.route'].create({
    'name': 'Resupply Shop Floor',
    'warehouse_ids': [(4, warehouse.id)],
    'company_id': company.id,
})

# Rule 1: Pull to Shop Floor from Quality Control
env['stock.rule'].create({
    'name': 'Shop Floor <- Quality Control',
    'route_id': route.id,
    'location_src_id': qc_location.id,
    'location_dest_id': shop_floor_location.id,
    'action': 'pull',
    'picking_type_id': warehouse.int_type_id.id,
    'procure_method': 'make_to_stock',
    'delay': 1,
})

# Rule 2: Pull to Quality Control from Main Stock  
env['stock.rule'].create({
    'name': 'Quality Control <- Main Stock',
    'route_id': route.id,
    'location_src_id': warehouse.lot_stock_id.id,
    'location_dest_id': qc_location.id,
    'action': 'pull',
    'picking_type_id': warehouse.int_type_id.id,
    'procure_method': 'make_to_stock',
    'delay': 0,
})
When this route is used, a stock request for the Shop Floor will create two pickings: one from Main Stock to Quality Control, and another from Quality Control to Shop Floor.

Route Selection Logic

Stock requests determine available routes based on several factors:

Warehouse-Based Routes

@api.depends('warehouse_id', 'location_id', 'stock_request_ids')
def _compute_route_ids(self):
    route_obj = self.env['stock.route']
    # Get routes for the order's warehouse
    routes = route_obj.search(
        [('warehouse_ids', 'in', self.mapped('warehouse_id').ids)]
    )
    # Filter by location hierarchy
    parents = self.get_parents().ids
    valid_routes = [
        route for route in routes
        if any(p.location_dest_id.id in parents for p in route.rule_ids)
    ]

Location Hierarchy Matching

Routes are available only if at least one rule’s destination location matches:
  • The exact request location
  • A parent location in the hierarchy
The get_parents() method traverses the location tree upward to find all parent locations:
def get_parents(self):
    location = self.location_id
    result = location
    while location.location_id:
        location = location.location_id
        result |= location
    return result

Purchase Integration

When the Stock Request Purchase module is installed, stock requests can trigger purchase orders.

Configuring Buy Routes

1

Configure Supplier on Product

Go to Inventory > Products > Products, open the product, and add a vendor in the Purchase tab.
2

Enable Buy Route

In the product form, go to Inventory tab and check Buy under Routes.
3

Create Stock Request with Buy Route

When creating a stock request, select a buy route. The system will automatically create a purchase order when the request is confirmed.

Purchase Rule Behavior

The purchase integration extends stock rules:
# In stock_request_purchase module
class StockRequest(models.Model):
    _inherit = 'stock.request'

    purchase_ids = fields.One2many(
        'purchase.order',
        compute='_compute_purchase_ids',
        string='Purchase Orders',
    )
    purchase_line_ids = fields.Many2many(
        'purchase.order.line',
        string='Purchase Order Lines',
        readonly=True,
        copy=False,
    )

Automatic PO Creation

Confirming a stock request with a buy route creates a purchase order

PO Linking

Purchase order lines are linked to stock requests for traceability

Smart Buttons

View purchase orders directly from the stock request form

Cancellation Propagation

Cancelling a stock request can cancel draft purchase orders

Viewing Purchase Orders

1

Open Stock Request

Navigate to the stock request that triggered purchases.
2

Click Purchase Orders Button

Click the Purchase Orders smart button (visible when purchase integration is active).
3

Review and Process

The purchase order list opens, filtered to show only related orders. Process them in the Purchase module.

Manufacturing Integration

When the Stock Request MRP module is installed, stock requests can trigger manufacturing orders.

Configuring Manufacturing Routes

1

Configure Bill of Materials

Create a Bill of Materials (BoM) for the product in Manufacturing > Products > Bills of Materials.
2

Enable Manufacture Route

In the product form, go to Inventory tab and check Manufacture under Routes.
3

Create Stock Request with MRP Route

Select a manufacturing route when creating the stock request. The system creates a manufacturing order when confirmed.

Manufacturing Order Tracking

# In stock_request_mrp module  
class StockRequest(models.Model):
    _inherit = 'stock.request'

    production_ids = fields.Many2many(
        'mrp.production',
        string='Manufacturing Orders',
        readonly=True,
    )
    production_count = fields.Integer(
        compute='_compute_production_ids',
    )
Manufacturing orders created from stock requests are linked via a many-to-many relationship, allowing multiple production orders to fulfill a single request (e.g., split across production runs).

Using Available Stock First

The Check available stock first feature allows the system to use existing stock before triggering procurement:

Enabling the Feature

1

Open Inventory Settings

Navigate to Inventory > Configuration > Settings.
2

Enable Check Available Stock First

Under the Stock Request section, check Check available stock first.
3

Save Configuration

Click Save.

How It Works

def _action_launch_procurement_rule(self):
    for request in self:
        # ... quantity checks ...
        
        # Check if available stock should be used first
        if request.company_id.stock_request_check_available_first:
            if request.product_id.sudo().with_context(
                location=request.location_id.id
            ).free_qty >= request.product_uom_qty:
                request._action_use_stock_available()
                continue
        
        # Otherwise trigger procurement
        self.env['procurement.group'].run(procurements)
1

Available Quantity Check

System checks free_qty (available quantity) in the destination location.
2

Immediate Fulfillment

If sufficient stock exists, creates and completes a stock move immediately:
def _action_use_stock_available(self):
    quants = self.env['stock.quant']._gather(
        self.product_id, self.location_id
    )
    for quant in quants.filtered(lambda x: x.available_quantity >= 0):
        qty_move = min(pending_qty, quant.available_quantity)
        move = stock_move_model.create(self._prepare_stock_move(qty_move))
        move._action_confirm()
        allocation_model.create(
            self._prepare_stock_request_allocation(move)
        )
        move.quantity = move.product_uom_qty
        move.picked = True
        move._action_done()
3

Fallback to Procurement

If stock is insufficient, falls back to the configured route and triggers normal procurement.
This feature is particularly useful for requests where internal stock should be used preferentially, with procurement (purchase/manufacturing) as a fallback.

Advanced Procurement Scenarios

Conditional Route Selection

You can implement logic to select routes dynamically:
# Example: Select route based on product availability
if product.qty_available > 0:
    route = env.ref('stock.route_warehouse0_mto')  # Transfer route
else:
    route = env.ref('purchase.route_warehouse0_buy')  # Buy route

stock_request = env['stock.request'].create({
    'product_id': product.id,
    'product_uom_qty': 100,
    'location_id': location.id,
    'route_id': route.id,
})

Multi-Warehouse Procurement

For multi-warehouse setups, configure routes to pull from other warehouses:
# Route: Transfer from Warehouse B to Warehouse A
env['stock.rule'].create({
    'name': 'WH-A <- WH-B',
    'route_id': inter_warehouse_route.id,
    'location_src_id': warehouse_b.lot_stock_id.id,
    'location_dest_id': warehouse_a.lot_stock_id.id,
    'action': 'pull',
    'picking_type_id': warehouse_a.int_type_id.id,
    'procure_method': 'make_to_stock',
    'warehouse_id': warehouse_a.id,
})

Dropshipping via Stock Requests

Configure a route that delivers directly to customers:
# Dropship route: Buy and deliver directly
env['stock.rule'].create({
    'name': 'Dropship to Customer',
    'route_id': dropship_route.id,
    'location_src_id': supplier_location.id,
    'location_dest_id': customer_location.id,
    'action': 'buy',
    'picking_type_id': dropship_type.id,
})

Troubleshooting Procurement Issues

Possible Causes:
  • Product type is “Service” (only consumable and storable products can be procured)
  • No route selected or configured
  • Route rules don’t match the destination location
Solutions:
  • Verify product type in product form
  • Select an appropriate route on the stock request
  • Check route rules have destination locations matching the request location hierarchy
Possible Causes:
  • Multiple routes apply and wrong one was selected
  • Route rules have incorrect action type
Solutions:
  • Explicitly select the desired route on the stock request
  • Review route rules in Inventory > Configuration > Routes
  • Check rule priority and warehouse associations
Possible Causes:
  • Stock rule doesn’t include allocation logic
  • Procurement values don’t include stock_request_id
Solutions:
  • Verify the Stock Request module properly inherits stock.rule
  • Check that _get_stock_move_values includes allocation creation
  • Review allocation records in Inventory > Operations > Stock Request Allocations
Possible Causes:
  • Route is not associated with the selected warehouse
  • Route rules don’t match the destination location
  • Route is product-specific and doesn’t apply
Solutions:
  • Check route configuration in Inventory > Configuration > Routes
  • Verify warehouse association
  • Ensure at least one rule’s destination location matches the request location or its parents

Best Practices

Use descriptive route and rule names: Names like “Resupply Assembly Line from Main Stock” are more maintainable than “Route 1”.
  • Configure warehouse-specific routes rather than global routes for better control
  • Set realistic delay values on rules to improve expected date calculations
  • Use procurement groups to keep related moves together in pickings
  • Enable “Check available stock first” for cost savings and faster fulfillment
  • Document custom routes and their intended use cases
  • Test routes with sample stock requests before deploying to production
  • Monitor procurement failures and adjust route configurations accordingly
  • Use multi-step routes for quality control and staged operations
  • Configure appropriate push rules for automatic replenishment scenarios
  • Review route performance periodically and optimize based on actual usage patterns

Build docs developers (and LLMs) love