[IMP] l10n_in: Audit Trail

In Indian government required audit trail report for private limited companies
So from this commit user can't delete journal entries after posting once.

Create an audit trail report from mail.message to show a full log of journal entries

task - 3239932

closes odoo/odoo#118945

X-original-commit: f4365ee1c6e979e352ece66b03f3c00e830f2117
Signed-off-by: Josse Colpaert <jco@odoo.com>
This commit is contained in:
Jigar Vaghela
2023-04-18 19:29:12 +02:00
parent 33a9db123a
commit 6eb8213d0e
5 changed files with 133 additions and 1 deletions
+1
View File
@@ -40,6 +40,7 @@ Sheet, now only Vertical format has been permitted Which is Supported By Odoo.
'views/res_partner_views.xml',
'views/account_tax_views.xml',
'views/uom_uom_views.xml',
'report/audit_trail_report_views.xml',
],
'demo': [
'demo/demo_company.xml',
+1
View File
@@ -9,3 +9,4 @@ from . import res_config_settings
from . import res_country_state
from . import res_partner
from . import uom_uom
from . import mail_message
+29 -1
View File
@@ -1,8 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError, RedirectWarning
from odoo.exceptions import ValidationError, RedirectWarning, UserError
_logger = logging.getLogger(__name__)
class AccountMove(models.Model):
@@ -94,3 +98,27 @@ class AccountMove(models.Model):
# TO OVERRIDE
self.ensure_one()
return False
@api.ondelete(at_uninstall=False)
def _unlink_l10n_in_except_once_post(self):
# Prevent deleting entries once it's posted for Indian Company only
if any(m.country_code == 'IN' and m.posted_before for m in self) and not self._context.get('force_delete'):
raise UserError(_("To keep the audit trail, you can not delete journal entries once they have been posted"))
def unlink(self):
# Add logger here becouse in api ondelete account.move.line is deleted and we can't get total amount
logger_msg = False
if any(m.country_code == 'IN' and m.posted_before for m in self):
if self._context.get('force_delete'):
moves_details = ", ".join("{entry_number} ({move_id}) amount {amount_total} {currency} and partner {partner_name}".format(
entry_number=m.name,
move_id=m.id,
amount_total=m.amount_total,
currency=m.currency_id.name,
partner_name=m.partner_id.display_name)
for m in self)
logger_msg = 'Force deleted Journal Entries %s by %s (%s)' % (moves_details, self.env.user.name, self.env.user.id)
res = super().unlink()
if logger_msg:
_logger.info(logger_msg)
return res
+50
View File
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from markupsafe import Markup
from odoo import fields, api, models, _
from odoo.exceptions import UserError
class Message(models.Model):
_inherit = 'mail.message'
l10n_in_audit_log_preview = fields.Html(string="Description", compute="_compute_l10n_in_audit_log_preview")
l10n_in_audit_log_account_move_id = fields.Many2one('account.move', string="Journal Entry", compute="_compute_l10n_in_audit_log_document_name", search="_search_l10n_in_audit_log_document_name")
@api.depends('body', 'subject', 'tracking_value_ids', 'subtype_id')
def _compute_l10n_in_audit_log_preview(self):
for message in self:
title = message.subject or message.preview
tracking_value_ids = message.sudo().tracking_value_ids
if not title and tracking_value_ids:
title = _("Updated")
elif not title and message.subtype_id and not message.subtype_id.internal:
title = message.subtype_id.display_name
audit_log_preview = Markup("<div>%s</div>") % (title)
for value in tracking_value_ids:
audit_log_preview += Markup("<li>%s <i class='o_TrackingValue_separator fa fa-long-arrow-rightmx-1 text-600' title='%s' role='img' aria-label='%s'></i>%s (%s)</li>") % (
_("Changed"), _("Changed"), value._get_old_display_value()[0], value._get_new_display_value()[0], value.field.field_description)
message.l10n_in_audit_log_preview = audit_log_preview
@api.depends('model', 'res_id')
def _compute_l10n_in_audit_log_document_name(self):
messages_of_account_move = self.filtered(lambda m: m.model == 'account.move' and m.res_id)
(self - messages_of_account_move).l10n_in_audit_log_account_move_id = False
moves = self.env['account.move'].search([('id', 'in', messages_of_account_move.mapped('res_id'))])
moves_by_id = {m.id: m for m in moves}
for message in messages_of_account_move:
message.l10n_in_audit_log_account_move_id = moves_by_id.get(message.res_id, False)
def _search_l10n_in_audit_log_document_name(self, operator, value):
is_set = False
if operator == '!=' and isinstance(value, bool):
is_set = True
elif operator not in ['=', 'ilike'] or not isinstance(value, str):
raise UserError(_('Operation not supported'))
move_domain = [('company_id.account_fiscal_country_id.code', '=', 'IN')]
if not is_set:
move_domain += [('name', operator, value)]
move_query = self.env['account.move']._search(move_domain)
return [('model', '=', 'account.move'), ('res_id', 'in', move_query)]
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_message_tree_audit_log" model="ir.ui.view">
<field name="name">mail.message.tree.inherit.audit.log</field>
<field name="model">mail.message</field>
<field name="priority">99</field>
<field name="arch" type="xml">
<tree edit="0" delete="0" create="0">
<field name="res_id" invisible="1"/>
<field name="date"/>
<field name="author_id" widget="many2one_avatar"/>
<field name="l10n_in_audit_log_account_move_id"/>
<field name="l10n_in_audit_log_preview"/>
<button name="action_open_document" type="object" icon="fa-arrow-right" string="View Related Document" attrs="{'invisible': [('res_id', 'in', (0, False, ''))]}" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_message_tree_audit_log_search">
<field name="name">mail.message.search</field>
<field name="model">mail.message</field>
<field name="priority">99</field>
<field name="arch" type="xml">
<search string="Messages Search">
<field name="l10n_in_audit_log_account_move_id"/>
<field name="author_id"/>
<field name="date" string="Date"/>
<filter string="Update Only" name="update_only" domain="[('tracking_value_ids', '!=', False)]"/>
<group expand="0" string="Group By">
<filter string="date" name="group_by_date" domain="[]" context="{'group_by': 'date'}"/>
</group>
</search>
</field>
</record>
<record id="action_l10n_in_audit_trail_report" model="ir.actions.act_window">
<field name="name">Audit trail</field>
<field name="res_model">mail.message</field>
<field name="view_id" ref="view_message_tree_audit_log"/>
<field name="view_mode">tree</field>
<field name="domain">[
('model', '=', 'account.move'),
('message_type', '=', 'notification'),
('l10n_in_audit_log_account_move_id', '!=', False),
]</field>
<field name="search_view_id" ref="view_message_tree_audit_log_search"/>
</record>
<menuitem id="l10n_in_audit_trail_report_menu" name="Audit trail" action="action_l10n_in_audit_trail_report" parent="l10n_in.account_reports_in_statements_menu" sequence="2"
groups="account.group_account_readonly"/>
</odoo>