From fa492d87f42f97d05ba93255a77e24c35158fe62 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 5 Jun 2019 06:53:46 +0000 Subject: [PATCH 01/20] [IMP] mail, website_mail, base: extra info on error messages Display extra information on error messages: - Access error based on ACLs: user, fields (if applies) - Access error based on record rules: user, ids - Missing error: model, operation, user, ids closes odoo/odoo#33945 Signed-off-by: Nicolas Martinelli (nim) Co-authored-by: jev-odoo --- addons/mail/models/mail_message.py | 10 +++--- addons/website_mail/models/mail_message.py | 5 ++- odoo/addons/base/ir/ir_actions.py | 5 ++- odoo/addons/base/ir/ir_model.py | 1 + odoo/models.py | 38 ++++++++++++++++------ 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 95ff558c900..9087de93f0a 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -582,8 +582,9 @@ class Message(models.Model): WHERE message.message_type = %%s AND (message.subtype_id IS NULL OR subtype.internal IS TRUE) AND message.id = ANY (%%s)''' % (self._table), ('comment', self.ids,)) if self._cr.fetchall(): raise AccessError( - _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % - (self._description, operation)) + _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation) + + ' - ({} {}, {} {})'.format(_('Records:'), self.ids[:6], _('User:'), self._uid) + ) # Read mail_message.ids to have their values message_values = dict((res_id, {}) for res_id in self.ids) @@ -682,8 +683,9 @@ class Message(models.Model): if not other_ids: return raise AccessError( - _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % - (self._description, operation)) + _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation) + + ' - ({} {}, {} {})'.format(_('Records:'), list(other_ids)[:6], _('User:'), self._uid) + ) @api.model def _get_record_name(self, values): diff --git a/addons/website_mail/models/mail_message.py b/addons/website_mail/models/mail_message.py index e60392f62bd..c3d64567e1b 100644 --- a/addons/website_mail/models/mail_message.py +++ b/addons/website_mail/models/mail_message.py @@ -54,7 +54,10 @@ class MailMessage(models.Model): if self.user_has_groups('base.group_public'): self.env.cr.execute('SELECT id FROM "%s" WHERE website_published IS FALSE AND id = ANY (%%s)' % (self._table), (self.ids,)) if self.env.cr.fetchall(): - raise AccessError(_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation)) + raise AccessError( + _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation) + + ' - ({} {}, {} {})'.format(_('Records:'), self.ids[:6], _('User:'), self._uid) + ) return super(MailMessage, self).check_access_rule(operation=operation) diff --git a/odoo/addons/base/ir/ir_actions.py b/odoo/addons/base/ir/ir_actions.py index e947147d0d9..2664b90093f 100644 --- a/odoo/addons/base/ir/ir_actions.py +++ b/odoo/addons/base/ir/ir_actions.py @@ -360,7 +360,10 @@ class IrActionsActWindow(models.Model): existing = self.filtered(lambda rec: rec.id in ids) if len(existing) < len(self): # mark missing records in cache with a failed value - exc = MissingError(_("Record does not exist or has been deleted.")) + exc = MissingError( + _("Record does not exist or has been deleted.") + + '\n\n({} {}, {} {})'.format(_('Records:'), (self - existing).ids[:6], _('User:'), self._uid) + ) (self - existing)._cache.update(fields.FailedValue(exc)) return existing diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index 60f248c4ba4..3a12f2d7c82 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -958,6 +958,7 @@ class IrModelAccess(models.Model): else: msg_tail = _("Please contact your system administrator if you think this is an error.") + "\n\n(" + _("Document model") + ": %s)" msg_params = (model_name,) + msg_tail += ' - ({} {}, {} {})'.format(_('Operation:'), mode, _('User:'), self._uid) _logger.info('Access Denied by ACLs for operation: %s, uid: %s, model: %s', mode, self._uid, model_name) msg = '%s %s' % (msg_heads[mode], msg_tail) raise AccessError(msg % msg_params) diff --git a/odoo/models.py b/odoo/models.py index de5fb780cda..577ef080197 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -2981,9 +2981,13 @@ class BaseModel(object): if invalid_fields: _logger.info('Access Denied by ACLs for operation: %s, uid: %s, model: %s, fields: %s', operation, self._uid, self._name, ', '.join(invalid_fields)) - raise AccessError(_('The requested operation cannot be completed due to security restrictions. ' - 'Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \ - (self._description, operation)) + raise AccessError( + _( + 'The requested operation cannot be completed due to security restrictions. ' + 'Please contact your system administrator.\n\n(Document type: %s, Operation: %s)' + ) % (self._description, operation) + + ' - ({} {}, {} {})'.format(_('User:'), self._uid, _('Fields:'), ', '.join(invalid_fields)) + ) return fields @@ -3197,8 +3201,8 @@ class BaseModel(object): if forbidden: # store an access error exception in existing records exc = AccessError( - _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \ - (self._name, 'read') + _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, 'read') + + ' - ({} {}, {} {})'.format(_('Records:'), self.ids[:6], _('User:'), self._uid) ) forbidden._cache.update(FailedValue(exc)) @@ -3282,8 +3286,10 @@ class BaseModel(object): if self._uid == SUPERUSER_ID: return _logger.info('Access Denied by record rules for operation: %s on record ids: %r, uid: %s, model: %s', operation, forbidden_ids, self._uid, self._name) - raise AccessError(_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \ - (self._description, operation)) + raise AccessError( + _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation) + + ' - ({} {}, {}, {})'.format(_('Records:'), forbidden_ids[:6], _('User:'), self._uid) + ) else: # If we get here, the missing_ids are not in the database if operation in ('read','unlink'): @@ -3292,7 +3298,13 @@ class BaseModel(object): # errors for non-transactional search/read sequences coming from clients return _logger.info('Failed operation on deleted record(s): %s, uid: %s, model: %s', operation, self._uid, self._name) - raise MissingError(_('Missing document(s)') + ':' + _('One of the documents you are trying to access has been deleted, please try again after refreshing.')) + raise MissingError( + _('Missing document(s)') + ':' + _('One of the documents you are trying to access has been deleted, please try again after refreshing.') + + '\n\n({} {}, {} {}, {} {}, {} {})'.format( + _('Document type:'), self._description, _('Operation:'), operation, + _('Records:'), missing_ids[:6], _('User:'), self._uid, + ) + ) @api.model def check_access_rights(self, operation, raise_exception=True): @@ -3680,7 +3692,10 @@ class BaseModel(object): for sub_ids in cr.split_for_in_conditions(set(self.ids)): cr.execute(query, params + (sub_ids,)) if cr.rowcount != len(sub_ids): - raise MissingError(_('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description) + raise MissingError( + _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description + + '\n\n({} {}, {} {})'.format(_('Records:'), sub_ids[:6], _('User:'), self._uid) + ) # TODO: optimize for name in direct: @@ -4457,7 +4472,10 @@ class BaseModel(object): existing = self.browse(ids + new_ids) if len(existing) < len(self): # mark missing records in cache with a failed value - exc = MissingError(_("Record does not exist or has been deleted.")) + exc = MissingError( + _("Record does not exist or has been deleted.") + + '\n\n({} {}, {} {})'.format(_('Records:'), (self - existing).ids[:6], _('User:'), self._uid) + ) (self - existing)._cache.update(FailedValue(exc)) return existing From 963475bcfd75fe0a35401c7343d14937aa7f0d74 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 26 Jun 2019 07:46:19 +0000 Subject: [PATCH 02/20] [FIX] stock: use float_compare Use proper `float_compare` method when comparing floats. opw-1958608 closes odoo/odoo#34370 Signed-off-by: Nicolas Martinelli (nim) --- addons/stock/models/stock_move.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 2e14e1c1360..309e32a5b7b 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -392,10 +392,12 @@ class StockMove(models.Model): receipt_moves_to_reassign = self.env['stock.move'] if 'product_uom_qty' in vals: for move in self.filtered(lambda m: m.state not in ('done', 'draft') and m.picking_id): - if vals['product_uom_qty'] != move.product_uom_qty: + if float_compare(vals['product_uom_qty'], move.product_uom_qty, precision_rounding=move.product_uom.rounding): self.env['stock.move.line']._log_message(move.picking_id, move, 'stock.track_move_template', vals) if self.env.context.get('do_not_unreserve') is None: - move_to_unreserve = self.filtered(lambda m: m.state not in ['draft', 'done', 'cancel'] and m.reserved_availability > vals.get('product_uom_qty')) + move_to_unreserve = self.filtered( + lambda m: m.state not in ['draft', 'done', 'cancel'] and float_compare(m.reserved_availability, vals.get('product_uom_qty'), precision_rounding=m.product_uom.rounding) == 1 + ) move_to_unreserve._do_unreserve() (self - move_to_unreserve).filtered(lambda m: m.state == 'assigned').write({'state': 'partially_available'}) # When editing the initial demand, directly run again action assign on receipt moves. From a5a4b154ee4e4d83f99b35b8899ad406dbee6b12 Mon Sep 17 00:00:00 2001 From: Nans Lefebvre Date: Fri, 14 Jun 2019 08:50:32 +0000 Subject: [PATCH 03/20] [FIX] stock_account: round quantities to UoM precision when updating moves Have a product P values in FIFO, create an incoming stock move. Suppose P has UoM U with rounding R, and the stock move has quantity Q. Edit the quantity done to be Q + e, with e << R. By the semantic of the UoM rounding, this should mean that no change has been done. However no such check was done, so the move was updated. As a result, this would mess up the valuation of P. opw 2006831 --- addons/stock_account/models/stock.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py index 23276976ff5..d6f30d301b7 100644 --- a/addons/stock_account/models/stock.py +++ b/addons/stock_account/models/stock.py @@ -88,7 +88,10 @@ class StockMoveLine(models.Model): if 'qty_done' in vals: moves_to_update = {} for move_line in self.filtered(lambda ml: ml.state == 'done' and (ml.move_id._is_in() or ml.move_id._is_out())): - moves_to_update[move_line.move_id] = vals['qty_done'] - move_line.qty_done + rounding = move_line.product_uom_id.rounding + qty_difference = float_round(vals['qty_done'] - move_line.qty_done, precision_rounding=rounding) + if not float_is_zero(qty_difference, precision_rounding=rounding): + moves_to_update[move_line.move_id] = qty_difference for move_id, qty_difference in moves_to_update.items(): move_vals = {} From c6ae7eea039df929ae340d87609a103783fcf726 Mon Sep 17 00:00:00 2001 From: Nans Lefebvre Date: Fri, 14 Jun 2019 11:26:14 +0000 Subject: [PATCH 04/20] [FIX] stock: do not update moves when writing quantities lower than UoM precision Suppose product P has UoM U with rounding R, and the stock move has quantity Q. Edit the quantity done to be Q + e, with e << R. By the semantic of the UoM rounding, this should mean that no change has been done. However no such check was done, so a message was posted in chatter, of the form: The done move line has been corrected. P: Quantity : 1000.0 -> 1000.0000000000001 opw 2006831 closes odoo/odoo#34124 Signed-off-by: Arnold Moyaux --- addons/stock/models/stock_move_line.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 870de679a38..d1e121df9c2 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -276,7 +276,10 @@ class StockMoveLine(models.Model): # When editing a done move line, the reserved availability of a potential chained move is impacted. Take care of running again `_action_assign` on the concerned moves. next_moves = self.env['stock.move'] if updates or 'qty_done' in vals: - for ml in self.filtered(lambda ml: ml.move_id.state == 'done' and ml.product_id.type == 'product'): + mls = self.filtered(lambda ml: ml.move_id.state == 'done' and ml.product_id.type == 'product') + if not updates: # we can skip those where qty_done is already good up to UoM rounding + mls = mls.filtered(lambda ml: not float_is_zero(ml.qty_done - vals['qty_done'], precision_rounding=ml.product_uom_id.rounding)) + for ml in mls: # undo the original move line qty_done_orig = ml.move_id.product_uom._compute_quantity(ml.qty_done, ml.move_id.product_id.uom_id, rounding_method='HALF-UP') in_date = Quant._update_available_quantity(ml.product_id, ml.location_dest_id, -qty_done_orig, lot_id=ml.lot_id, From 87685fc9b33d1e5c1788a1960db11c4b34c765e7 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Wed, 26 Jun 2019 09:10:06 +0000 Subject: [PATCH 05/20] [FIX] stock: Inventory of one product only Steps to reproduce: - Enable serial numbers - Create two products P1 and P2 tracked by lots - Create an inventory adjustment and specify inventory of one product only - Select P1 and create a serial number for it - Save the adjustment and change P1 to P2, adapt the quantity to 10 and validate the inventory adjustment Bug: Go to Lots and serial numbers and the created lot was linked to P1 intead of P2 opw:2010934 closes odoo/odoo#34373 Signed-off-by: Simon Goffin (sig) --- addons/stock/views/stock_inventory_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/views/stock_inventory_views.xml b/addons/stock/views/stock_inventory_views.xml index c6490b92589..91ead5e14b5 100644 --- a/addons/stock/views/stock_inventory_views.xml +++ b/addons/stock/views/stock_inventory_views.xml @@ -158,7 +158,7 @@