[REF] stock, mrp: remove constraint, add onchanges

We added a constraint in [1] to improve the usability of introducing
tracked move lines in the system. However, this contraint proved to be
too costly (trigger multiple time during the validation process, never
called in batch as it is set on the model at the end of a o2m).

We replaces the constraint by checks on the validating process or by
(non-blocking) onchanges.

With these changes, validating the receipt of 3000 tracked move lines
goes from 17 minutes to around 4.

[1] 482407464a

opw-803887
This commit is contained in:
Simon Lejeune
2018-01-18 14:37:13 +01:00
parent 1bb6887bb6
commit 6ef460b6a7
3 changed files with 54 additions and 54 deletions
+6
View File
@@ -195,6 +195,12 @@ class StockMove(models.Model):
move_lines = self.move_line_ids.filtered(lambda ml: ml.lot_id == lot and not ml.lot_produced_id)
else:
move_lines = self.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_produced_id)
# Sanity check: if the product is a serial number and `lot` is already present in the other
# consumed move lines, raise.
if lot and self.product_id.tracking == 'serial' and lot in self.move_line_ids.filtered(lambda ml: ml.qty_done).mapped('lot_id'):
raise UserError(_('You cannot consume the same serial number twice. Please correct the serial numbers encoded.'))
for ml in move_lines:
rounding = ml.product_uom_id.rounding
if float_compare(qty_to_add, 0, precision_rounding=rounding) <= 0:
+16 -16
View File
@@ -125,6 +125,8 @@ class MrpProductProduce(models.TransientModel):
raise UserError(_('You need to provide a lot for the finished product'))
existing_move_line = produce_move.move_line_ids.filtered(lambda x: x.lot_id == self.lot_id)
if existing_move_line:
if self.product_id.tracking == 'serial':
raise UserError(_('You cannot produce the same serial number twice.'))
existing_move_line.product_uom_qty += self.product_qty
existing_move_line.qty_done += self.product_qty
else:
@@ -181,27 +183,25 @@ class MrpProductProduceLine(models.TransientModel):
@api.onchange('lot_id')
def _onchange_lot_id(self):
""" When the user is encoding a produce line for a tracked product, we apply some logic to
help him. This onchange will automatically switch `qty_done` to 1.0.
"""
res = {}
if self.product_id.tracking == 'serial':
self.qty_done = 1
return res
@api.constrains('lot_id')
def _check_lot_id(self):
for ml in self:
if ml.product_id.tracking == 'serial':
produce_lines_to_check = ml.product_produce_id.produce_line_ids.filtered(lambda l: l.product_id == ml.product_id and l.lot_id)
message = produce_lines_to_check._check_for_duplicated_serial_numbers()
if message:
raise ValidationError(message)
def _check_for_duplicated_serial_numbers(self):
if self.mapped('lot_id'):
lots_map = [(ml.product_id.id, ml.lot_id.name) for ml in self]
recorded_serials_counter = Counter(lots_map)
for (product_id, lot_id), occurrences in recorded_serials_counter.items():
if occurrences > 1 and lot_id is not False:
return _('You cannot consume the same serial number twice. Please correct the serial numbers encoded.')
@api.onchange('qty_done')
def _onchange_qty_done(self):
""" When the user is encoding a produce line for a tracked product, we apply some logic to
help him. This onchange will warn him if he set `qty_done` to a non-supported value.
"""
res = {}
if self.product_id.tracking == 'serial':
if float_compare(self.qty_done, 1.0, precision_rounding=self.move_id.product_id.uom_id.rounding) != 0:
message = _('You can only process 1.0 %s for products with unique serial number.') % self.product_id.uom_id.name
res['warning'] = {'title': _('Warning'), 'message': message}
return res
@api.onchange('product_id')
def _onchange_product_id(self):
+32 -38
View File
@@ -109,36 +109,49 @@ class StockMoveLine(models.Model):
@api.onchange('lot_name', 'lot_id')
def onchange_serial_number(self):
""" When the user is encoding a move line for a tracked product, we apply some logic to
help him. This includes:
- automatically switch `qty_done` to 1.0
- warn if he has already encoded `lot_name` in another move line
"""
res = {}
if self.product_id.tracking == 'serial':
self.qty_done = 1
move_lines_to_check = self._get_similar_move_lines() - self
message = move_lines_to_check._check_for_duplicated_serial_numbers()
if not self.qty_done:
self.qty_done = 1
message = None
if self.lot_name or self.lot_id:
move_lines_to_check = self._get_similar_move_lines() - self
if self.lot_name:
counter = Counter(move_lines_to_check.mapped('lot_name'))
if counter.get(self.lot_name) and counter[self.lot_name] > 1:
message = _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')
elif self.lot_id:
counter = Counter(move_lines_to_check.mapped('lot_id.id'))
if counter.get(self.lot_id.id) and counter[self.lot_id.id] > 1:
message = _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')
if message:
res['warning'] = {'title': _('Warning'), 'message': message}
return res
@api.onchange('qty_done')
def _onchange_qty_done(self):
""" When the user is encoding a move line for a tracked product, we apply some logic to
help him. This onchange will warn him if he set `qty_done` to a non-supported value.
"""
res = {}
if self.product_id.tracking == 'serial':
if float_compare(self.qty_done, 1.0, precision_rounding=self.move_id.product_id.uom_id.rounding) != 0:
message = _('You can only process 1.0 %s for products with unique serial number.') % self.product_id.uom_id.name
res['warning'] = {'title': _('Warning'), 'message': message}
return res
@api.constrains('qty_done')
def _check_positive_qty_done(self):
if any([ml.qty_done < 0 for ml in self]):
raise ValidationError(_('You can not enter negative quantities!'))
@api.constrains('lot_id', 'lot_name', 'qty_done')
def _check_unique_serial_number(self):
for ml in self.filtered(lambda ml: ml.move_id.product_id.tracking == 'serial' and (ml.lot_id or ml.lot_name)):
move_lines_to_check = ml._get_similar_move_lines()
message = move_lines_to_check._check_for_duplicated_serial_numbers()
if message:
raise ValidationError(message)
if float_compare(ml.qty_done, 1.0, precision_rounding=ml.move_id.product_id.uom_id.rounding) == 1:
raise UserError(_(
'You can only process 1.0 %s for products with unique serial number.') % ml.product_id.uom_id.name)
if ml.lot_name:
already_exist = self.env['stock.production.lot'].search(
[('name', '=', ml.lot_name), ('product_id', '=', ml.product_id.id)])
if already_exist:
return _('You have already assigned this serial number to this product. Please correct the serial numbers encoded.')
def _get_similar_move_lines(self):
self.ensure_one()
lines = self.env['stock.move.line']
@@ -147,25 +160,6 @@ class StockMoveLine(models.Model):
lines |= picking_id.move_line_ids.filtered(lambda ml: ml.product_id == self.product_id and (ml.lot_id or ml.lot_name))
return lines
def _check_for_duplicated_serial_numbers(self):
""" This method is used in _check_unique_serial_number and in onchange_serial_number to check that a same serial number is not used twice amongst the recordset passed.
:return: an error message directed to the user if needed else False
"""
if self.mapped('lot_id'):
lots_map = [(ml.product_id.id, ml.lot_id.name) for ml in self]
recorded_serials_counter = Counter(lots_map)
for (product_id, lot_id), occurrences in recorded_serials_counter.items():
if occurrences > 1 and lot_id is not False:
return _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')
elif self.mapped('lot_name'):
lots_map = [(ml.product_id.id, ml.lot_name) for ml in self]
recorded_serials_counter = Counter(lots_map)
for (product_id, lot_id), occurrences in recorded_serials_counter.items():
if occurrences > 1 and lot_id is not False:
return _('You cannot use the same serial number twice. Please correct the serial numbers encoded.')
return False
@api.model
def create(self, vals):
vals['ordered_qty'] = vals.get('product_uom_qty')