[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:
committed by
Alvaro Fuentes
parent
e7750dce2a
commit
6c0744d672
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user