From 6ef460b6a7f0ec46e009da0b339dc676bc850b89 Mon Sep 17 00:00:00 2001 From: Simon Lejeune Date: Mon, 15 Jan 2018 17:59:17 +0100 Subject: [PATCH] [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] 482407464ace7936e2a911b558b953475c588a7e opw-803887 --- addons/mrp/models/stock_move.py | 6 ++ addons/mrp/wizard/mrp_product_produce.py | 32 +++++------ addons/stock/models/stock_move_line.py | 70 +++++++++++------------- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index f9436f66870..73f4c861727 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -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: diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py index 33ffb3d48f9..15c1684561e 100644 --- a/addons/mrp/wizard/mrp_product_produce.py +++ b/addons/mrp/wizard/mrp_product_produce.py @@ -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): diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 4582c907575..cb9244e4511 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -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')