Skip to main content

Overview

The stock.request.abstract model is an abstract model that provides common fields, computed fields, constraints, and business logic shared by both stock.request and stock.request.order models. It cannot be instantiated directly but serves as a foundation for concrete models. Model Name: stock.request.abstract Type: Abstract Model Inherits: mail.thread, mail.activity.mixin

Fields

Core Fields

name
Char
required
Unique identifier for the record.Properties: copy=False, required=True, readonly=True, default="/"Typically auto-generated from a sequence in concrete models.

Location & Warehouse Fields

warehouse_id
Many2one
required
Reference to stock.warehouse. The warehouse where stock is requested or stored.Properties: check_company=True, ondelete="cascade", required=True
location_id
Many2one
required
Reference to stock.location. The specific location within the warehouse.Domain: [('usage', 'in', ['internal', 'transit'])] (when virtual locations not allowed)Properties: ondelete="cascade", required=True
allow_virtual_location
Boolean
Whether virtual locations are allowed for this record.Related: company_id.stock_request_allow_virtual_locProperties: readonly=True

Product Fields

product_id
Many2one
required
Reference to product.product. The product being requested.Domain: [('type', 'in', ['product', 'consu'])]Properties: ondelete="cascade", required=TrueOnly storable and consumable products are allowed.
allowed_uom_categ_id
Many2one
Reference to uom.category. The allowed UoM category for this product.Related: product_id.uom_id.category_id
product_uom_id
Many2one
required
Reference to uom.uom. Product unit of measure.Domain: [('category_id', '=?', allowed_uom_categ_id)]Properties: required=TrueDefault: From context product_uom_id if available
product_uom_qty
Float
required
Quantity specified in the unit of measure indicated in the request.Digits: Product Unit of MeasureProperties: required=TrueHelp: Quantity, specified in the unit of measure indicated in the request.
product_qty
Float
Real quantity in the default UoM of the product.Properties: compute="_compute_product_qty", store=True, copy=FalseDigits: Product Unit of MeasureHelp: Quantity in the default UoM of the product

Procurement & Routing Fields

procurement_group_id
Many2one
Reference to procurement.group. Moves created through this stock request will be put in this procurement group.Help: If none is given, the moves generated by procurement rules will be grouped into one big picking.
route_id
Many2one
Reference to stock.route. The route to use for procurement.Domain: [('id', 'in', route_ids)]Properties: ondelete="restrict"
route_ids
Many2many
Reference to stock.route records. Available routes based on product, warehouse, and location.Properties: compute="_compute_route_ids", readonly=True

Company Field

company_id
Many2one
required
Reference to res.company. The company this record belongs to.Default: self.env.companyProperties: required=True

Methods

Compute Methods

_compute_product_qty
method
Converts product quantity from request UoM to product’s default UoM.Depends on:
  • product_id
  • product_uom_id
  • product_uom_qty
  • product_id.product_tmpl_id.uom_id
Behavior:
  • Uses UoM conversion with HALF-UP rounding
  • Converts from product_uom_id to product template’s base UoM
stock_request/models/stock_request_abstract.py:26
@api.depends(
    "product_id",
    "product_uom_id",
    "product_uom_qty",
    "product_id.product_tmpl_id.uom_id",
)
def _compute_product_qty(self):
    for rec in self:
        rec.product_qty = rec.product_uom_id._compute_quantity(
            rec.product_uom_qty,
            rec.product_id.product_tmpl_id.uom_id,
            rounding_method="HALF-UP",
        )
_compute_route_ids
method
Computes available routes based on product, warehouse, and location.Depends on: product_id, warehouse_id, location_idLogic:
  1. Gets routes from product (direct and category routes)
  2. Gets routes from warehouse
  3. Filters routes where any rule’s destination location is a parent of location_id
  4. Returns union of matching routes
stock_request/models/stock_request_abstract.py:116
@api.depends("product_id", "warehouse_id", "location_id")
def _compute_route_ids(self):
    route_obj = self.env["stock.route"]
    routes = route_obj.search(
        [("warehouse_ids", "in", self.mapped("warehouse_id").ids)]
    )
    routes_by_warehouse = {}
    for route in routes:
        for warehouse in route.warehouse_ids:
            routes_by_warehouse.setdefault(warehouse.id, self.env["stock.route"])
            routes_by_warehouse[warehouse.id] |= route
    for record in self:
        routes = route_obj
        if record.product_id:
            routes += record.product_id.mapped(
                "route_ids"
            ) | record.product_id.mapped("categ_id").mapped("total_route_ids")
        if record.warehouse_id and routes_by_warehouse.get(record.warehouse_id.id):
            routes |= routes_by_warehouse[record.warehouse_id.id]
        parents = record.get_parents().ids
        record.route_ids = routes.filtered(
            lambda r: any(p.location_dest_id.id in parents for p in r.rule_ids)
        )

Utility Methods

get_parents
method
Returns all parent locations up to the root for the record’s location.Returns: stock.location recordset containing the location and all its parentsBehavior:
  • Starts with location_id
  • Recursively adds parent locations via location_id field
  • Returns full hierarchy from location to root
stock_request/models/stock_request_abstract.py:141
def get_parents(self):
    location = self.location_id
    result = location
    while location.location_id:
        location = location.location_id
        result |= location
    return result
default_get
method
Provides default values for warehouse and location based on company.Decorator: @api.modelBehavior:
  • Searches for a warehouse in the specified company
  • Sets warehouse_id and location_id (to warehouse’s lot_stock_id)
stock_request/models/stock_request_abstract.py:13
@api.model
def default_get(self, fields):
    res = super().default_get(fields)
    warehouse = None
    if "warehouse_id" not in res and res.get("company_id"):
        warehouse = self.env["stock.warehouse"].search(
            [("company_id", "=", res["company_id"])], limit=1
        )
    if warehouse:
        res["warehouse_id"] = warehouse.id
        res["location_id"] = warehouse.lot_stock_id.id
    return res

Onchange Methods

onchange_warehouse_id
method
Updates location and company when warehouse changes.Decorator: @api.onchange("warehouse_id")Behavior:
  • Updates location_id to warehouse’s lot_stock_id if warehouse changed
  • Updates company_id if warehouse company differs
  • Skips for stock.request records with an order_id (handled at order level)
stock_request/models/stock_request_abstract.py:210
@api.onchange("warehouse_id")
def onchange_warehouse_id(self):
    if self._name == "stock.request" and self.order_id:
        return
    if self.warehouse_id:
        loc_wh = self.location_id.warehouse_id
        if self.warehouse_id != loc_wh:
            self.location_id = self.warehouse_id.lot_stock_id.id
        if self.warehouse_id.company_id != self.company_id:
            self.company_id = self.warehouse_id.company_id
onchange_location_id
method
Updates warehouse when location changes.Decorator: @api.onchange("location_id")Behavior:
  • If location has a warehouse, updates warehouse_id to match
  • Calls onchange_warehouse_id() with context to prevent child updates
stock_request/models/stock_request_abstract.py:226
@api.onchange("location_id")
def onchange_location_id(self):
    if self.location_id:
        loc_wh = self.location_id.warehouse_id
        if loc_wh and self.warehouse_id != loc_wh:
            self.warehouse_id = loc_wh
            self.with_context(no_change_childs=True).onchange_warehouse_id()
onchange_company_id
method
Sets a default warehouse when the company is changed.Decorator: @api.onchange("company_id")Behavior:
  • Searches for a warehouse in the new company
  • Calls onchange_warehouse_id() to update related fields
stock_request/models/stock_request_abstract.py:234
@api.onchange("company_id")
def onchange_company_id(self):
    if self.company_id and (
        not self.warehouse_id or self.warehouse_id.company_id != self.company_id
    ):
        self.warehouse_id = self.env["stock.warehouse"].search(
            [
                "|",
                ("company_id", "=", False),
                ("company_id", "=", self.company_id.id),
            ],
            limit=1,
        )
        self.onchange_warehouse_id()
onchange_product_id
method
Sets the product’s default UoM when product changes.Decorator: @api.onchange("product_id")
stock_request/models/stock_request_abstract.py:250
@api.onchange("product_id")
def onchange_product_id(self):
    if self.product_id:
        self.product_uom_id = self.product_id.uom_id

Constraints

SQL Constraints

name_uniq: Name must be unique per company.
_sql_constraints = [
    ("name_uniq", "unique(name, company_id)", "Name must be unique")
]

Python Constraints

_check_company_constrains
Validates that related models belong to the same company.Constraint: company_id, product_id, warehouse_id, location_id, route_idChecks:
  • Product company (if set) must match record company
  • Location company (if set) must match record company
  • Warehouse company must match record company
  • Route company (if set) must match record company
Raises: ValidationError with specific message for each mismatch
stock_request/models/stock_request_abstract.py:149
@api.constrains(
    "company_id", "product_id", "warehouse_id", "location_id", "route_id"
)
def _check_company_constrains(self):
    for rec in self:
        if (
            rec.product_id.company_id
            and rec.product_id.company_id != rec.company_id
        ):
            raise ValidationError(
                _(
                    "You have entered a product that is assigned "
                    "to another company."
                )
            )
        # ... additional checks for location, warehouse, route
_check_product_uom
Validates that the UoM category matches the product’s UoM category.Constraint: product_idRule: product_id.uom_id.category_id must equal product_uom_id.category_idRaises: ValidationError
stock_request/models/stock_request_abstract.py:194
@api.constrains("product_id")
def _check_product_uom(self):
    if any(
        request.product_id.uom_id.category_id != request.product_uom_id.category_id
        for request in self
    ):
        raise ValidationError(
            _(
                "You have to select a product unit of measure in the "
                "same category than the default unit "
                "of measure of the product"
            )
        )

Field Behaviors

UoM Handling

The abstract model handles unit of measure conversions:
# User specifies quantity in a convenient UoM
product_uom_qty = 10.0  # e.g., 10 boxes
product_uom_id = uom_box  # Box UoM

Route Filtering

Routes are filtered based on multiple criteria:

Location Hierarchy

The get_parents() method is used for route filtering:
# Example location hierarchy
Physical Locations
└── WH/Stock  (id=10)
    └── WH/Stock/Shelf  (id=20)
        └── WH/Stock/Shelf/A  (id=30)

# For location_id = 30 (Shelf A)
get_parents() returns: [30, 20, 10]

# Routes with rules pointing to any of these locations are valid

Domain Specifications

Product Domain

product_id
domain
Domain: [('type', 'in', ['product', 'consu'])]Only allows:
  • product - Storable products (tracked inventory)
  • consu - Consumable products (not tracked, but can be requested)
Excludes:
  • service - Service products (no inventory)

Location Domain

location_id
domain
Domain: [('usage', 'in', ['internal', 'transit'])] (when virtual locations not allowed)Allows:
  • internal - Internal locations (normal warehouse locations)
  • transit - Transit locations (inter-warehouse transfers)
Note: Domain is dynamic based on allow_virtual_location setting

UoM Domain

product_uom_id
domain
Domain: [('category_id', '=?', allowed_uom_categ_id)]Restricts UoM selection to the same category as the product’s base UoM.Example: If product uses “Units” (category: Unit), can select “Units”, “Dozens”, “Dozens”, but not “kg” or “Liters”

Route Domain

route_id
domain
Domain: [('id', 'in', route_ids)]Restricts route selection to computed available routes based on product, warehouse, and location.

Usage in Inheritance

Concrete Models

Two models inherit from this abstract model:
class StockRequest(models.Model):
    _name = "stock.request"
    _inherit = "stock.request.abstract"
    
    # Adds state, allocations, moves, etc.
    state = fields.Selection(...)
    allocation_ids = fields.One2many(...)
    # ... additional fields
Actually, looking at the code, stock.request.order does NOT inherit from stock.request.abstract. It defines its own similar fields. Only stock.request inherits from the abstract model.

Shared Logic

The abstract model provides shared logic for:
  • UoM conversion via _compute_product_qty()
  • Route computation via _compute_route_ids()
  • Company constraints via _check_company_constrains()
  • Onchange handlers for warehouse, location, company, product

See Also

Build docs developers (and LLMs) love