[FIX] purchase_mrp,stock: Avoid issues with backorders in Overview

Steps to reproduce:
- Set warehouse to two-steps reception
- Create a BoM using a MTO (buy) component
- Create a MO for 2 units of that BoM
- Produce 1 and create a backorder
- Open the Overview

Issue:
When there is a backorder (or if the MO is splitted), then when browsing
the dests through `_rollup_move_dests()`, at some point it will branch
into two differents path, related to each backorders. This will raise a
traceback, as we only expected a single MO at that point.

To avoid this, we made the `_rollup_move_dests()` method batchable, so
we can fetch all moves linked to a manufacturing order in a single call,
avoiding this issue.

Note: For consistency sake, updated the `_rollup_move_origs()` method to
be batchable as well, to avoid confusion when using one or the other.

opw-3849252

closes odoo/odoo#161790

X-original-commit: d2830fccbe5dc733673d048303f573bb57a398a6
Signed-off-by: Arnold Moyaux (arm) <arm@odoo.com>
Signed-off-by: Quentin Wolfs (quwo) <quwo@odoo.com>
This commit is contained in:
clesgow
2024-04-16 16:18:44 +00:00
parent d82ffde0b3
commit eb2f6cfde8
3 changed files with 68 additions and 15 deletions
@@ -16,13 +16,15 @@ class ReportMoOverview(models.AbstractModel):
for po_line in po_lines:
line_qty = po_line.product_qty
for move in po_line.move_dest_ids:
linked_production = self.env['stock.move'].browse(move._rollup_move_dests()).raw_material_production_id
# Only create specific lines for moves directly linked to a manufacturing order
if not linked_production:
# Need to fetch every move connected to a manufacturing order from this PO line. This can happen when:
# - Multiple MOs are linked to a single PO line (e.g. Same MTO component for multiple MO).
# - A MO has a backorder / is splitted.
dest_moves = self.env['stock.move'].browse(po_line.move_dest_ids._rollup_move_dests())
for move in dest_moves:
if not move.raw_material_production_id:
continue
prod_qty = min(line_qty, move.product_uom._compute_quantity(move.product_uom_qty, po_line.product_uom))
res.append(self._format_extra_replenishment(po_line, prod_qty, linked_production.id))
res.append(self._format_extra_replenishment(po_line, prod_qty, move.raw_material_production_id.id))
line_qty -= prod_qty
if line_qty:
res.append(self._format_extra_replenishment(po_line, line_qty))
@@ -982,3 +982,56 @@ class TestPurchaseMrpFlow(AccountTestInvoicingCommon):
svl = po.picking_ids[0].move_ids.stock_valuation_layer_ids
self.assertEqual(svl[0].unit_cost, 67.5)
self.assertEqual(svl[1].unit_cost, 135000000)
def test_mo_overview_mto_purchase_with_backorders(self):
self.warehouse.reception_steps = 'two_steps'
# Enable MTO route for Component
self.env.ref('stock.route_warehouse0_mto').active = True
route_buy = self.warehouse.buy_pull_id.route_id.id
route_mto = self.warehouse.mto_pull_id.route_id.id
self.component_a.write({
'seller_ids': [
Command.create({'partner_id': self.partner_a.id},
)],
'route_ids': [
Command.link(route_buy),
Command.link(route_mto),
],
})
bom = self.env['mrp.bom'].create({
'product_tmpl_id': self.component_b.product_tmpl_id.id,
'product_qty': 1.0,
'bom_line_ids': [
Command.create({
'product_id': self.component_a.id,
'product_qty': 2.0,
}),
],
})
with Form(self.env['mrp.production']) as prod_form:
prod_form.product_id = self.component_b
prod_form.bom_id = bom
prod_form.product_qty = 3
production = prod_form.save()
production.action_confirm()
self.assertEqual(production.purchase_order_count, 1)
purchase = production.procurement_group_id.stock_move_ids.created_purchase_line_ids.order_id
self.assertEqual(len(purchase), 1)
with Form(production) as prod_form:
prod_form.qty_producing = 1
production = prod_form.save()
backorder_action = production.button_mark_done()
backorder_wizard = Form(self.env['mrp.production.backorder'].with_context(**backorder_action['context']))
backorder_wizard.save().action_backorder()
backorder = production.procurement_group_id.mrp_production_ids - production
self.assertEqual(len(backorder), 1)
self.assertEqual(backorder.product_qty, 2)
report_values = self.env['report.mrp.report_mo_overview']._get_report_data(backorder.id)
self.assertEqual(report_values['summary']['quantity'], backorder.product_qty)
self.assertEqual(report_values['components'][0]['summary']['quantity'], 4)
replenishments = report_values['components'][0]['replenishments']
self.assertEqual(len(replenishments), 1)
self.assertEqual(replenishments[0]['summary']['name'], purchase.name)
+8 -10
View File
@@ -2157,25 +2157,23 @@ Please change the quantity done or the rounding precision of your unit of measur
moves_to_reserve._action_assign()
def _rollup_move_dests(self, seen=False):
self.ensure_one()
if not seen:
seen = OrderedSet()
if self.id in seen:
unseen = OrderedSet(self.ids) - seen
if not unseen:
return seen
seen.add(self.id)
for dst in self.move_dest_ids:
dst._rollup_move_dests(seen)
seen.update(unseen)
self.filtered(lambda m: m.id in unseen).move_dest_ids._rollup_move_dests(seen)
return seen
def _rollup_move_origs(self, seen=False):
self.ensure_one()
if not seen:
seen = OrderedSet()
if self.id in seen:
unseen = OrderedSet(self.ids) - seen
if not unseen:
return seen
seen.add(self.id)
for org in self.move_orig_ids:
org._rollup_move_origs(seen)
seen.update(unseen)
self.filtered(lambda m: m.id in unseen).move_orig_ids._rollup_move_origs(seen)
return seen
def _get_forecast_availability_outgoing(self, warehouse):