Odoo中的路线规则浅析

发布时间 2023-05-16 22:24:27作者: CrossPython

路线规则的优先级

我们知道,odoo中的路线可以在四个地方设置,分别是产品路线、产品分类路线、仓库路线和订单明细行中的路线。这四种路线的优先级为:

1
销售订单明细行 ——> 产品路线——> 产品分类路线——> 仓库路线

这个很好理解,当几个路线对某个库位设置的规则冲突时,依据本优先级进行应用。现在问题来了,我们知道产品路线中有多个路线可以选择,当产品路线中的多个路线的多个规则定义了相同的库位的规则时,会怎样呢?

隐藏的规则

相同级别的路线如果定义了相同的库位规则,官方并没有给出文档解释,我们从代码中可以窥见这隐秘的规则:

我们知道在早期的版本中,驱动销售产生采购、生产单的背后的大佬是需求单(procurement.order),到了13.0中已经演变成了procurement.group,需求单也不复存在,而且这家伙并不为普罗大众所熟知,造成的结果就是,很容易陷在路线的泥潭里而百思不得其解。

我们先来看procurement.group中是如何运行路线规则的:

@api.model
def run(self, procurements):
    """ Method used in a procurement case. The purpose is to supply the
    product passed as argument in the location also given as an argument.
    In order to be able to find a suitable location that provide the product
    it will search among stock.rule.
    """
    actions_to_run = defaultdict(list)
    errors = []
    for procurement in procurements:
        procurement.values.setdefault('company_id', self.env.company)
        procurement.values.setdefault('priority', '1')
        procurement.values.setdefault('date_planned', fields.Datetime.now())
        if (
            procurement.product_id.type not in ('consu', 'product') or
            float_is_zero(procurement.product_qty, precision_rounding=procurement.product_uom.rounding)
        ):
            continue
        rule = self._get_rule(procurement.product_id, procurement.location_id, procurement.values)
        if not rule:
            errors.append(_('No rule has been found to replenish "%s" in "%s".\nVerify the routes configuration on the product.') %
                (procurement.product_id.display_name, procurement.location_id.display_name))
        else:
            action = 'pull' if rule.action == 'pull_push' else rule.action
            actions_to_run[action].append((procurement, rule))

    ...

  

由此可见,关键方法_get_rule负责根据需求寻找对应的路线规则。所以,我们来看一下 _get_rule的具体内容:

 

@api.model
def _get_rule(self, product_id, location_id, values):
    """ Find a pull rule for the location_id, fallback on the parent
    locations if it could not be found.
    """
    result = False
    location = location_id
    while (not result) and location:
        domain = self._get_rule_domain(location, values)
        result = self._search_rule(values.get('route_ids', False), product_id, values.get('warehouse_id', False), domain)
        location = location.location_id
    return result

  

这里有两个方法,_get_rule_domain负责查找库位的domain,_search_rule负责根据传入的库位查找规则。_get_rule_domain的功能比较简单,就是在规则中查找源库位为location_id且不是推式物流的规则。而_search_rule才是真正查找路线规则的关键先生。

@api.model
def _search_rule(self, route_ids, product_id, warehouse_id, domain):
    """ First find a rule among the ones defined on the procurement
    group, then try on the routes defined for the product, finally fallback
    on the default behavior
    """
    if warehouse_id:
        domain = expression.AND([['|', ('warehouse_id', '=', warehouse_id.id), ('warehouse_id', '=', False)], domain])
    Rule = self.env['stock.rule']
    res = self.env['stock.rule']
    if route_ids:
        res = Rule.search(expression.AND([[('route_id', 'in', route_ids.ids)], domain]), order='route_sequence, sequence', limit=1)
    if not res:
        product_routes = product_id.route_ids | product_id.categ_id.total_route_ids
        if product_routes:
            res = Rule.search(expression.AND([[('route_id', 'in', product_routes.ids)], domain]), order='route_sequence, sequence', limit=1)
    if not res and warehouse_id:
        warehouse_routes = warehouse_id.route_ids
        if warehouse_routes:
            res = Rule.search(expression.AND([[('route_id', 'in', warehouse_routes.ids)], domain]), order='route_sequence, sequence', limit=1)
    return res

  

这个方法印证了我们之前的结论:

  1. odoo会先查找给定的路线,如果没有指定路线,则在产品路线或者产品分类路线中查找,如果没有则在仓库的路线中查找。
  2. 相同优先级的路线规则,则依据规则的route_sequence和sequene进行排序,取第一个规则。

实际中的例子

其实关于odoo路线的讨论文章已经不少了,今天之所以会重新总结一遍,是因为在实际使用过程中又一次在这里摔了跟头。简单回顾一下问题发生的过程:

  1. 首先,odoo中制造的路线,默认的生产库位为WH/Stock。
  2. 因为某种原因,我们把Stock改成了视图库位,并在其下设置了很多内部库位。
  3. 销售某产品,产品路线设置MTO和Manufacture。生成的生产单无法完成,因为目的库位Stock为视图。
  4. 修改Manufacture的路线规则,将成品库位设置为Stock/A.
  5. 报错,提示在Input库位没有找到合适的路线规则。
  6. 一脸懵逼,怎么会跟Input库位有关系,经排查,产品分类中设置了两步入库、两步出库,两步出库第二步Stock->Output后,Stock为目的库位的规则只有一个,就是两步入库中的Input-> Stock,因为产品没有选购买,因此找不到Input的下一步规则。
  7. 发现错误,新增Stock/A->Output的规则到Manufacture, 并没有生效。
  8. 查看代码,发现优先级问题,调整规则的优先级,成功。

排查的过程真是耗费了大量的时间,因此为了节省时间作为以后排查的依据,整理的结论如下:

路线规则

  1. 销售订单明细行 ——> 产品路线——> 产品分类路线——> 仓库路线
  2. 路线规则的优先级,首先看路线的序号,然后看规则的序号,取找到的第一个规则应用。

思考题

不知道你是否想过 官方的原生的MTO路线是如何实现的?如果让你手动定义一个MTO路线,你会怎么定义?

本例中,如何设置Stock/A->Output的规则,可以实现销售单做完以后,生成两步出库单,并自动在Stock/A上生成生产单?