[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:
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user