[FIX] stock: speed up _find_delivery_ids_by_lot

Rewrite _find_delivery_ids_by_lot to reduce the number
of recursive calls by grouping the producing move lines
together before each recursive calls. This reduces
the number of search calls, speeding up the whole method.

Add a unittest to check if the delivery_ids field
of a lot recordset is properly computed.

opw-2824464

X-original-commit: e7b9f14d4a9ce4ed61451b24b567f751fb4bcbe2
Part-of: odoo/odoo#95398
This commit is contained in:
Aurélien (avd)
2022-07-06 13:11:48 +02:00
committed by Alvaro Fuentes
parent e7750dce2a
commit 6c0744d672
2 changed files with 119 additions and 11 deletions
+93
View File
@@ -434,3 +434,96 @@ class TestTraceability(TestMrpCommon):
self.assertEqual(lot_B02.product_qty, 0)
self.assertEqual(lot_B03.product_qty, 15)
self.assertEqual(productA.qty_available, 15)
def test_last_delivery_traceability(self):
"""
Suppose this structure (-> means 'produces')
1 x Subcomponent A -> 1 x Component A -> 1 x EndProduct A
All three tracked by lots. Ensure that after validating Picking A (out)
for EndProduct A, all three lots' delivery_ids are set to
Picking A.
"""
stock_location = self.env.ref('stock.stock_location_stock')
customer_location = self.env.ref('stock.stock_location_customers')
# Create the three lot-tracked products.
subcomponentA = self._create_product('lot')
componentA = self._create_product('lot')
endproductA = self._create_product('lot')
# Create production lots.
lot_subcomponentA, lot_componentA, lot_endProductA = self.env['stock.lot'].create([{
'name': 'lot %s' % product,
'product_id': product.id,
'company_id': self.env.company.id,
} for product in (subcomponentA, componentA, endproductA)])
# Create two boms, one for Component A and one for EndProduct A
self.env['mrp.bom'].create([{
'product_id': finished.id,
'product_tmpl_id': finished.product_tmpl_id.id,
'product_uom_id': self.uom_unit.id,
'product_qty': 1.0,
'type': 'normal',
'bom_line_ids': [(0, 0, {'product_id': component.id, 'product_qty': 1})],
} for finished, component in [(endproductA, componentA), (componentA, subcomponentA)]])
self.env['stock.quant']._update_available_quantity(subcomponentA, stock_location, 1, lot_id=lot_subcomponentA)
# Produce 1 component A
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = componentA
mo_form.product_qty = 1
mo = mo_form.save()
mo.action_confirm()
mo_form = Form(mo)
mo_form.qty_producing = 1
mo_form.lot_producing_id = lot_componentA
mo = mo_form.save()
mo.move_raw_ids[0].quantity_done = 1.0
mo.button_mark_done()
# Produce 1 endProduct A
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = endproductA
mo_form.product_qty = 1
mo = mo_form.save()
mo.action_confirm()
mo_form = Form(mo)
mo_form.qty_producing = 1
mo_form.lot_producing_id = lot_endProductA
mo = mo_form.save()
mo.move_raw_ids[0].quantity_done = 1.0
mo.button_mark_done()
# Create out picking for EndProduct A
pickingA_out = self.env['stock.picking'].create({
'picking_type_id': self.env.ref('stock.picking_type_out').id,
'location_id': stock_location.id,
'location_dest_id': customer_location.id})
moveA = self.env['stock.move'].create({
'name': 'Picking A move',
'product_id': endproductA.id,
'product_uom_qty': 1,
'product_uom': endproductA.uom_id.id,
'picking_id': pickingA_out.id,
'location_id': stock_location.id,
'location_dest_id': customer_location.id})
# Confirm and assign pickingA
pickingA_out.action_confirm()
pickingA_out.action_assign()
# Set move_line lot_id to the mrp.production lot_producing_id
moveA.move_line_ids[0].write({
'qty_done': 1.0,
'lot_id': lot_endProductA.id,
})
# Transfer picking
pickingA_out._action_done()
# Use concat so that delivery_ids is computed in batch.
for lot in lot_subcomponentA.concat(lot_componentA, lot_endProductA):
self.assertEqual(lot.delivery_ids.ids, pickingA_out.ids)
+26 -11
View File
@@ -192,27 +192,42 @@ class StockLot(models.Model):
def _find_delivery_ids_by_lot(self, lot_path=None, delivery_by_lot=None):
if lot_path is None:
lot_path = set()
domain = [
('lot_id', 'in', self.ids),
('state', '=', 'done'),
'|', ('picking_code', '=', 'outgoing'), ('produce_line_ids', '!=', False)
]
move_lines = self.env['stock.move.line'].search(domain)
moves_by_lot = {
lot_id: {'producing_lines': set(), 'barren_lines': set()}
for lot_id in move_lines.lot_id.ids
}
for line in move_lines:
if line.produce_line_ids:
moves_by_lot[line.lot_id.id]['producing_lines'].add(line.id)
else:
moves_by_lot[line.lot_id.id]['barren_lines'].add(line.id)
if delivery_by_lot is None:
delivery_by_lot = dict()
for lot in self:
delivery_ids = set()
if lot.id in lot_path:
continue
for line in move_lines.filtered(lambda ml: ml.lot_id.id == lot.id):
if line.produce_line_ids:
# Do the same process for lot_id contained in produce_line_ids,
# to fetch the end product deliveries
if moves_by_lot.get(lot.id):
producing_move_lines = self.env['stock.move.line'].browse(moves_by_lot[lot.id]['producing_lines'])
barren_move_lines = self.env['stock.move.line'].browse(moves_by_lot[lot.id]['barren_lines'])
if producing_move_lines:
lot_path.add(lot.id)
for delivery_ids_set in line.produce_line_ids.lot_id._find_delivery_ids_by_lot(lot_path=lot_path, delivery_by_lot=delivery_by_lot).values():
delivery_ids.update(delivery_ids_set)
else:
delivery_ids.add(line.picking_id.id)
next_lots = producing_move_lines.produce_line_ids.lot_id.filtered(lambda l: l.id not in lot_path)
next_lots_ids = set(next_lots.ids)
# If some producing lots are in lot_path, it means that they have been previously processed.
# Their results are therefore already in delivery_by_lot and we add them to delivery_ids directly.
delivery_ids.update(*[set(delivery_by_lot.get(lot_id)) for lot_id in (producing_move_lines.produce_line_ids.lot_id - next_lots).ids])
for lot_id, delivery_ids_set in next_lots._find_delivery_ids_by_lot(lot_path=lot_path, delivery_by_lot=delivery_by_lot).items():
if lot_id in next_lots_ids:
delivery_ids.update(delivery_ids_set)
delivery_ids.update(barren_move_lines.picking_id.ids)
delivery_by_lot[lot.id] = list(delivery_ids)
return delivery_by_lot