Odoo中的路线规则浅析
路线规则的优先级
我们知道,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
这个方法印证了我们之前的结论:
- odoo会先查找给定的路线,如果没有指定路线,则在产品路线或者产品分类路线中查找,如果没有则在仓库的路线中查找。
- 相同优先级的路线规则,则依据规则的route_sequence和sequene进行排序,取第一个规则。
实际中的例子
其实关于odoo路线的讨论文章已经不少了,今天之所以会重新总结一遍,是因为在实际使用过程中又一次在这里摔了跟头。简单回顾一下问题发生的过程:
- 首先,odoo中制造的路线,默认的生产库位为WH/Stock。
- 因为某种原因,我们把Stock改成了视图库位,并在其下设置了很多内部库位。
- 销售某产品,产品路线设置MTO和Manufacture。生成的生产单无法完成,因为目的库位Stock为视图。
- 修改Manufacture的路线规则,将成品库位设置为Stock/A.
- 报错,提示在Input库位没有找到合适的路线规则。
- 一脸懵逼,怎么会跟Input库位有关系,经排查,产品分类中设置了两步入库、两步出库,两步出库第二步Stock->Output后,Stock为目的库位的规则只有一个,就是两步入库中的Input-> Stock,因为产品没有选购买,因此找不到Input的下一步规则。
- 发现错误,新增Stock/A->Output的规则到Manufacture, 并没有生效。
- 查看代码,发现优先级问题,调整规则的优先级,成功。
排查的过程真是耗费了大量的时间,因此为了节省时间作为以后排查的依据,整理的结论如下:
路线规则
- 销售订单明细行 ——> 产品路线——> 产品分类路线——> 仓库路线
- 路线规则的优先级,首先看路线的序号,然后看规则的序号,取找到的第一个规则应用。
思考题
不知道你是否想过 官方的原生的MTO路线是如何实现的?如果让你手动定义一个MTO路线,你会怎么定义?
本例中,如何设置Stock/A->Output的规则,可以实现销售单做完以后,生成两步出库单,并自动在Stock/A上生成生产单?