summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Gilbert <flg@odoo.com>2019-10-16 10:11:43 +0000
committerFlorian Gilbert <flg@odoo.com>2019-11-28 14:47:12 +0000
commitd8629653240e5f4287b329a89c599989b7ef596e (patch)
tree3b87b85bd59f1800fcc7aa71c144d24e9f91c8d9
parentfd9c6bdcc611dc787541a32ceb4e28b0e1c82683 (diff)
[IMP] account: Add tracking for account move line in account move chatter.
Purpose: In V13, you can reset to draft a posted move if this move isn't in a hashed journal. But it's little bit dangerous and we want to track changes for some fields. It works for editing in editable list view and form view. Task ID: 2061399
-rw-r--r--addons/account/models/account_move.py95
-rw-r--r--addons/mail/models/mail_thread.py27
2 files changed, 110 insertions, 12 deletions
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 748fb5b1586..d9cc34b9ff3 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -97,7 +97,7 @@ class AccountMove(models.Model):
date = fields.Date(string='Date', required=True, index=True, readonly=True,
states={'draft': [('readonly', False)]},
default=fields.Date.context_today)
- ref = fields.Char(string='Reference', copy=False)
+ ref = fields.Char(string='Reference', copy=False, tracking=True)
narration = fields.Text(string='Internal Note')
state = fields.Selection(selection=[
('draft', 'Draft'),
@@ -2396,11 +2396,12 @@ class AccountMoveLine(models.Model):
country_id = fields.Many2one(comodel_name='res.country', related='move_id.company_id.country_id')
account_id = fields.Many2one('account.account', string='Account',
index=True, ondelete="cascade",
- domain=[('deprecated', '=', False)])
+ domain=[('deprecated', '=', False)],
+ tracking=True)
account_internal_type = fields.Selection(related='account_id.user_type_id.type', string="Internal Type", store=True, readonly=True)
account_root_id = fields.Many2one(related='account_id.root_id', string="Account Root", store=True, readonly=True)
sequence = fields.Integer(default=10)
- name = fields.Char(string='Label')
+ name = fields.Char(string='Label', tracking=True)
quantity = fields.Float(string='Quantity',
default=1.0, digits='Product Unit of Measure',
help="The optional quantity expressed by this line, eg: number of product sold. "
@@ -2422,7 +2423,7 @@ class AccountMoveLine(models.Model):
reconciled = fields.Boolean(compute='_amount_residual', store=True)
blocked = fields.Boolean(string='No Follow-up', default=False,
help="You can check this box to mark this journal item as a litigation with the associated partner")
- date_maturity = fields.Date(string='Due Date', index=True,
+ date_maturity = fields.Date(string='Due Date', index=True, tracking=True,
help="This field is used for payable and receivable journal entries. You can put the limit date for the payment of this line.")
currency_id = fields.Many2one('res.currency', string='Currency')
partner_id = fields.Many2one('res.partner', string='Partner', ondelete='restrict')
@@ -2456,7 +2457,7 @@ class AccountMoveLine(models.Model):
string="Originator Tax Repartition Line", ondelete='restrict', readonly=True,
help="Tax repartition line that caused the creation of this move line, if any")
tag_ids = fields.Many2many(string="Tags", comodel_name='account.account.tag', ondelete='restrict',
- help="Tags assigned to this line by the tax creating it, if any. It determines its impact on financial reports.")
+ help="Tags assigned to this line by the tax creating it, if any. It determines its impact on financial reports.", tracking=True)
tax_audit = fields.Char(string="Tax Audit String", compute="_compute_tax_audit", store=True,
help="Computed field, listing the tax grids impacted by this line, and the amount it applies to each of them.")
@@ -3214,8 +3215,59 @@ class AccountMoveLine(models.Model):
or (account_type != 'payable' and account_to_write.user_type_id.type == 'payable'):
raise UserError(_("You can only set an account having the payable type on payment terms lines for vendor bill."))
+ # Get all tracked fields (without related fields because these fields must be manage on their own model)
+ tracking_fields = []
+ for value in vals:
+ field = self._fields[value]
+ if hasattr(field, 'related') and field.related:
+ continue # We don't want to track related field.
+ if hasattr(field, 'tracking') and field.tracking:
+ tracking_fields.append(value)
+ ref_fields = self.env['account.move.line'].fields_get(tracking_fields)
+
+ # Get initial values for each line
+ move_initial_values = {}
+ for line in self.filtered(lambda l: l.move_id.name != '/'): # Only lines with posted once move.
+ for field in tracking_fields:
+ # Group initial values by move_id
+ if line.move_id.id not in move_initial_values:
+ move_initial_values[line.move_id.id] = {}
+ move_initial_values[line.move_id.id].update({field: line[field]})
+
result = super(AccountMoveLine, self).write(vals)
+ # Create the dict for the message post
+ tracking_values = {} # Tracking values to write in the message post
+ for move_id, modified_lines in move_initial_values.items():
+ tmp_move = {move_id: []}
+ for line in self.filtered(lambda l: l.move_id.id == move_id):
+ tracked_field = self.env['mail.thread'].static_message_track(line, ref_fields, modified_lines) # Return a tuple like (changed field, ORM command)
+ tmp = {'line_id': line.id}
+ if len(tracked_field[1]) > 0:
+ selected_field = tracked_field[1][0][2] # Get the last element of the tuple in the list of ORM command. (changed, [(0, 0, THIS)])
+ tmp.update({
+ **{'field_name': selected_field.get('field_desc')},
+ **self._get_formated_values(selected_field)
+ })
+ elif len(tracked_field[0]):
+ field_name = line._fields[tracked_field[0].pop()].string # Get the field name
+ tmp.update({
+ 'error': True,
+ 'field_error': field_name
+ })
+ else:
+ continue
+ tmp_move[move_id].append(tmp)
+ if len(tmp_move[move_id]) > 0:
+ tracking_values.update(tmp_move)
+
+ # Write in the chatter.
+ for move in self.mapped('move_id'):
+ fields = tracking_values.get(move.id, [])
+ if len(fields) > 0:
+ msg = self._get_tracking_field_string(tracking_values.get(move.id))
+ move.message_post(body=msg) # Write for each concerned move the message in the chatter
+
for line in self:
if not line.move_id.is_invoice(include_receipts=True):
continue
@@ -3324,6 +3376,39 @@ class AccountMoveLine(models.Model):
return result
# -------------------------------------------------------------------------
+ # TRACKING METHODS
+ # -------------------------------------------------------------------------
+
+ def _get_formated_values(self, tracked_field):
+ if tracked_field.get('field_type') in ('date', 'datetime'):
+ return {
+ 'old_value': format_date(self.env, fields.Datetime.from_string(tracked_field.get('old_value_datetime'))),
+ 'new_value': format_date(self.env, fields.Datetime.from_string(tracked_field.get('new_value_datetime'))),
+ }
+ elif tracked_field.get('field_type') in ('one2many', 'many2many', 'many2one'):
+ return {
+ 'old_value': tracked_field.get('old_value_char', ''),
+ 'new_value': tracked_field.get('new_value_char', '')
+ }
+ else:
+ return {
+ 'old_value': [val for key, val in tracked_field.items() if 'old_value' in key][0], # Get the first element because we create a list like ['Elem']
+ 'new_value': [val for key, val in tracked_field.items() if 'new_value' in key][0], # Get the first element because we create a list like ['Elem']
+ }
+
+ def _get_tracking_field_string(self, fields):
+ ARROW_RIGHT = '<span aria-label="Changed" class="fas fa-long-arrow-alt-right" role="img" title="Changed"></span>'
+ msg = '<ul>'
+ for field in fields:
+ redirect_link = '<a href=# data-oe-model=account.move.line data-oe-id=%d>#%d</a>' % (field['line_id'], field['line_id']) # Account move line link
+ if field.get('error', False):
+ msg += '<li>%s: A modication has been operated on the line %s.</li>' % (field['field_error'], redirect_link)
+ else:
+ msg += '<li>%s: %s %s %s (%s)</li>' % (field['field_name'], field['old_value'], ARROW_RIGHT, field['new_value'], redirect_link)
+ msg += '</ul>'
+ return _(msg)
+
+ # -------------------------------------------------------------------------
# RECONCILIATION
# -------------------------------------------------------------------------
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 54a1a004a76..ac7442fa13e 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -596,33 +596,46 @@ class MailThread(models.AbstractModel):
self.message_post_with_template(template.id, **post_kwargs)
return True
- def _message_track(self, tracked_fields, initial):
+ @api.model
+ def static_message_track(self, record, tracked_fields, initial):
""" For a given record, fields to check (tuple column name, column info)
and initial values, return a structure that is a tuple containing :
- a set of updated column names
- - a list of ORM (0, 0, values) commands to create 'mail.tracking.value' """
- self.ensure_one()
+ - a list of ORM (0, 0, values) commands to create 'mail.tracking.value'
+
+ This static method is usefull when you don't want to inherit from mail.thread but
+ you want to use '_message_track()' method.
+ """
+ record.ensure_one()
changes = set() # contains onchange tracked fields that changed
tracking_value_ids = []
# generate tracked_values data structure: {'col_name': {col_info, new_value, old_value}}
for col_name, col_info in tracked_fields.items():
initial_value = initial[col_name]
- new_value = self[col_name]
+ new_value = record[col_name]
if new_value != initial_value and (new_value or initial_value): # because browse null != False
- tracking_sequence = getattr(self._fields[col_name], 'tracking',
- getattr(self._fields[col_name], 'track_sequence', 100)) # backward compatibility with old parameter name
+ tracking_sequence = getattr(record._fields[col_name], 'tracking',
+ getattr(record._fields[col_name], 'track_sequence', 100)) # backward compatibility with old parameter name
if tracking_sequence is True:
tracking_sequence = 100
- tracking = self.env['mail.tracking.value'].create_tracking_values(initial_value, new_value, col_name, col_info, tracking_sequence, self._name)
+ tracking = record.env['mail.tracking.value'].create_tracking_values(initial_value, new_value, col_name, col_info, tracking_sequence, record._name)
if tracking:
tracking_value_ids.append([0, 0, tracking])
changes.add(col_name)
return changes, tracking_value_ids
+ def _message_track(self, tracked_fields, initial):
+ """ For a given record, fields to check (tuple column name, column info)
+ and initial values, return a structure that is a tuple containing :
+
+ - a set of updated column names
+ - a list of ORM (0, 0, values) commands to create 'mail.tracking.value' """
+ return self.static_message_track(self, tracked_fields, initial)
+
def message_track(self, tracked_fields, initial_values):
""" Track updated values. Comparing the initial and current values of
the fields given in tracked_fields, it generates a message containing