[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
This commit is contained in:
@@ -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
|
||||
@@ -3323,6 +3375,39 @@ class AccountMoveLine(models.Model):
|
||||
result.append((line.id, name))
|
||||
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
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user