From 763cd36f65a2b3265476c49d2c3efea0291e1b1b Mon Sep 17 00:00:00 2001 From: kasp-odoo Date: Thu, 25 May 2023 14:26:14 +0530 Subject: [PATCH] [ADD] account_audit_trail: separate module from l10n_in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit ================== The `account_audit_trail` was part of the `l10n_in` module, which restricted its usage to Indian localization. After this commit ================= The `account_audit_trail` module has been separated from the `l10n_in` module. It is now a standalone module, allowing it to be used independently in various locations if needed. task_id: 3326258 closes odoo/odoo#123650 Signed-off-by: William André (wan) --- .tx/config | 9 ++ addons/account_audit_trail/__init__.py | 4 + addons/account_audit_trail/__manifest__.py | 14 ++ .../i18n/account_audit_trail.pot | 139 ++++++++++++++++++ addons/account_audit_trail/models/__init__.py | 7 + .../models/account_move.py | 61 ++++++++ .../models/mail_message.py | 74 ++++++++++ .../account_audit_trail/models/res_company.py | 10 ++ .../models/res_config_settings.py | 18 +++ .../report/audit_trail_report_views.xml | 59 ++++++++ .../views/res_config_settings_views.xml | 22 +++ 11 files changed, 417 insertions(+) create mode 100644 addons/account_audit_trail/__init__.py create mode 100644 addons/account_audit_trail/__manifest__.py create mode 100644 addons/account_audit_trail/i18n/account_audit_trail.pot create mode 100644 addons/account_audit_trail/models/__init__.py create mode 100644 addons/account_audit_trail/models/account_move.py create mode 100644 addons/account_audit_trail/models/mail_message.py create mode 100644 addons/account_audit_trail/models/res_company.py create mode 100644 addons/account_audit_trail/models/res_config_settings.py create mode 100644 addons/account_audit_trail/report/audit_trail_report_views.xml create mode 100644 addons/account_audit_trail/views/res_config_settings_views.xml diff --git a/.tx/config b/.tx/config index 1687cbc0c68..0d7c1745f8d 100644 --- a/.tx/config +++ b/.tx/config @@ -10,6 +10,15 @@ resource_name = account replace_edited_strings = false keep_translations = false +[o:odoo:p:odoo-17:r:account_audit_trail] +file_filter = addons/account_audit_trail/i18n/.po +source_file = addons/account_audit_trail/i18n/account_audit_trail.pot +type = PO +minimum_perc = 0 +resource_name = account_audit_trail +replace_edited_strings = false +keep_translations = false + [o:odoo:p:odoo-17:r:account_check_printing] file_filter = addons/account_check_printing/i18n/.po source_file = addons/account_check_printing/i18n/account_check_printing.pot diff --git a/addons/account_audit_trail/__init__.py b/addons/account_audit_trail/__init__.py new file mode 100644 index 00000000000..dc5e6b693d1 --- /dev/null +++ b/addons/account_audit_trail/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/account_audit_trail/__manifest__.py b/addons/account_audit_trail/__manifest__.py new file mode 100644 index 00000000000..d2ee1222c51 --- /dev/null +++ b/addons/account_audit_trail/__manifest__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name': 'Account Audit Trail', + 'version': '1.0', + 'category': 'Accounting/Accounting', + 'summary': 'Account Audit Trail', + 'depends': ['account'], + 'data': [ + 'report/audit_trail_report_views.xml', + 'views/res_config_settings_views.xml', + ], + 'license': 'LGPL-3', +} diff --git a/addons/account_audit_trail/i18n/account_audit_trail.pot b/addons/account_audit_trail/i18n/account_audit_trail.pot new file mode 100644 index 00000000000..de667161602 --- /dev/null +++ b/addons/account_audit_trail/i18n/account_audit_trail.pot @@ -0,0 +1,139 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_audit_trail +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-12-15 06:37+0000\n" +"PO-Revision-Date: 2023-12-15 06:37+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: account_audit_trail +#: model_terms:ir.ui.view,arch_db:account_audit_trail.res_config_settings_view_form_inherit_account_audit_trail +msgid "Activate Audit Trail" +msgstr "" + +#. module: account_audit_trail +#: model:ir.actions.act_window,name:account_audit_trail.action_account_audit_trail_report +#: model:ir.model.fields,field_description:account_audit_trail.field_res_company__check_account_audit_trail +#: model:ir.model.fields,field_description:account_audit_trail.field_res_config_settings__check_account_audit_trail +#: model:ir.ui.menu,name:account_audit_trail.account_audit_trail_menu +#: model_terms:ir.ui.view,arch_db:account_audit_trail.res_config_settings_view_form_inherit_account_audit_trail +msgid "Audit Trail" +msgstr "" + +#. module: account_audit_trail +#. odoo-python +#: code:addons/account_audit_trail/models/res_config_settings.py:0 +#, python-format +msgid "Can't disable audit trail when there are existing records." +msgstr "" + +#. module: account_audit_trail +#. odoo-python +#: code:addons/account_audit_trail/models/mail_message.py:0 +#, python-format +msgid "Changed" +msgstr "" + +#. module: account_audit_trail +#: model:ir.model,name:account_audit_trail.model_res_company +msgid "Companies" +msgstr "" + +#. module: account_audit_trail +#: model:ir.model,name:account_audit_trail.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: account_audit_trail +#: model_terms:ir.ui.view,arch_db:account_audit_trail.view_message_tree_audit_log_search +msgid "Date" +msgstr "" + +#. module: account_audit_trail +#: model:ir.model.fields,field_description:account_audit_trail.field_mail_mail__account_audit_log_preview +#: model:ir.model.fields,field_description:account_audit_trail.field_mail_message__account_audit_log_preview +msgid "Description" +msgstr "" + +#. module: account_audit_trail +#: model_terms:ir.ui.view,arch_db:account_audit_trail.res_config_settings_view_form_inherit_account_audit_trail +msgid "Go to Audit Trail" +msgstr "" + +#. module: account_audit_trail +#: model_terms:ir.ui.view,arch_db:account_audit_trail.view_message_tree_audit_log_search +msgid "Group By" +msgstr "" + +#. module: account_audit_trail +#: model:ir.model,name:account_audit_trail.model_account_move +#: model:ir.model.fields,field_description:account_audit_trail.field_mail_mail__account_audit_log_move_id +#: model:ir.model.fields,field_description:account_audit_trail.field_mail_message__account_audit_log_move_id +#: model_terms:ir.ui.view,arch_db:account_audit_trail.view_message_tree_audit_log_search +msgid "Journal Entry" +msgstr "" + +#. module: account_audit_trail +#: model:ir.model,name:account_audit_trail.model_mail_message +msgid "Message" +msgstr "" + +#. module: account_audit_trail +#: model_terms:ir.ui.view,arch_db:account_audit_trail.view_message_tree_audit_log_search +msgid "Messages Search" +msgstr "" + +#. module: account_audit_trail +#. odoo-python +#: code:addons/account_audit_trail/models/mail_message.py:0 +#: code:addons/account_audit_trail/models/mail_message.py:0 +#, python-format +msgid "Operation not supported" +msgstr "" + +#. module: account_audit_trail +#: model:ir.model.fields,field_description:account_audit_trail.field_mail_mail__show_audit_log +#: model:ir.model.fields,field_description:account_audit_trail.field_mail_message__show_audit_log +msgid "Show Audit Log" +msgstr "" + +#. module: account_audit_trail +#. odoo-python +#: code:addons/account_audit_trail/models/account_move.py:0 +#, python-format +msgid "" +"To keep the audit trail, you can not delete journal entries once they have been posted.\n" +"Instead, you can cancel the journal entry." +msgstr "" + +#. module: account_audit_trail +#: model_terms:ir.actions.act_window,help:account_audit_trail.action_account_audit_trail_report +msgid "" +"To see the audit log you have to enable the audit trail option from settings" +msgstr "" + +#. module: account_audit_trail +#: model_terms:ir.ui.view,arch_db:account_audit_trail.view_message_tree_audit_log_search +msgid "Update Only" +msgstr "" + +#. module: account_audit_trail +#. odoo-python +#: code:addons/account_audit_trail/models/mail_message.py:0 +#, python-format +msgid "Updated" +msgstr "" + +#. module: account_audit_trail +#: model_terms:ir.ui.view,arch_db:account_audit_trail.view_message_tree_audit_log_search +msgid "date" +msgstr "" diff --git a/addons/account_audit_trail/models/__init__.py b/addons/account_audit_trail/models/__init__.py new file mode 100644 index 00000000000..ccf4af22f83 --- /dev/null +++ b/addons/account_audit_trail/models/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import mail_message +from . import account_move +from . import res_config_settings +from . import res_company diff --git a/addons/account_audit_trail/models/account_move.py b/addons/account_audit_trail/models/account_move.py new file mode 100644 index 00000000000..17a6b53ab12 --- /dev/null +++ b/addons/account_audit_trail/models/account_move.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from collections import defaultdict +import logging + +from odoo import api, models, _ +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class AccountMove(models.Model): + _inherit = "account.move" + + @api.ondelete(at_uninstall=False) + def _unlink_account_audit_trail_except_once_post(self): + if not self._context.get('force_delete') and any( + move.posted_before and move.company_id.check_account_audit_trail + for move in self + ): + raise UserError(_("To keep the audit trail, you can not delete journal entries once they have been posted.\nInstead, you can cancel the journal entry.")) + + def unlink(self): + # Add logger here because in api ondelete account.move.line is deleted and we can't get total amount + logger_msg = False + if any(m.posted_before and m.company_id.check_account_audit_trail for m in self): + if self._context.get('force_delete'): + moves_details = [] + for move in self: + entry_details = "{move_name} ({move_id}) amount {amount_total} {currency} and partner {partner_name}".format( + move_name=move.name, + move_id=move.id, + amount_total=move.amount_total, + currency=move.currency_id.name, + partner_name=move.partner_id.display_name, + ) + account_balances_per_account = defaultdict(float) + for line in move.line_ids: + account_balances_per_account[line.account_id] += line.balance + account_details = "\n".join( + "- {account_name} ({account_id}) with balance {balance} {currency}".format( + account_name=account.name, + account_id=account.id, + balance=balance, + currency=line.currency_id.name, + ) + for account, balance in account_balances_per_account.items() + ) + moves_details.append("{entry_details}\n{account_details}".format( + entry_details=entry_details, account_details=account_details + )) + logger_msg = "\nForce deleted Journal Entries by {user_name} ({user_id})\nEntries\n{moves_details}".format( + user_name=self.env.user.name, + user_id=self.env.user.id, + moves_details="\n".join(moves_details), + ) + res = super().unlink() + if logger_msg: + _logger.info(logger_msg) + return res diff --git a/addons/account_audit_trail/models/mail_message.py b/addons/account_audit_trail/models/mail_message.py new file mode 100644 index 00000000000..81d18889f75 --- /dev/null +++ b/addons/account_audit_trail/models/mail_message.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from markupsafe import Markup + +from odoo import fields, models, _ +from odoo.exceptions import UserError + + +class Message(models.Model): + _inherit = 'mail.message' + + account_audit_log_preview = fields.Html(string="Description", compute="_compute_account_audit_log_preview") + account_audit_log_move_id = fields.Many2one( + comodel_name='account.move', + string="Journal Entry", + compute="_compute_account_audit_log_move_id", + search="_search_account_audit_log_move_id", + ) + show_audit_log = fields.Boolean(compute="_compute_account_audit_log_move_id", search="_search_show_audit_log") + + def _compute_account_audit_log_preview(self): + for message in self: + title = message.subject or message.preview + tracking_value_ids = message.sudo().tracking_value_ids.filtered(lambda tracking: not tracking.field_groups or self.env.is_superuser() or self.user_has_groups(tracking.field_groups)) + 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("
%s
") % title + for fmt_vals in tracking_value_ids._tracking_value_format(): + field_desc = fmt_vals['changedField'] + old_value = fmt_vals['oldValue']['value'] + new_value = fmt_vals['newValue']['value'] + audit_log_preview += Markup( + "
  • %(old_value)s %(new_value)s (%(field)s)
  • " + ) % { + 'old_value': old_value, + 'new_value': new_value, + 'title': _("Changed"), + 'field': field_desc, + } + message.account_audit_log_preview = audit_log_preview + + def _compute_account_audit_log_move_id(self): + messages_of_account_move = self.filtered(lambda m: m.model == 'account.move' and m.res_id) + recordset_difference = (self - messages_of_account_move) + recordset_difference.update({ + 'account_audit_log_move_id': False, + 'show_audit_log': False, + }) + moves = self.env['account.move'].sudo().search([ + ('id', 'in', messages_of_account_move.mapped('res_id')), + ('company_id.check_account_audit_trail', '=', True), + ]) + moves_by_id = {m.id: m for m in moves} + for message in messages_of_account_move: + message.account_audit_log_move_id = moves_by_id.get(message.res_id, False) + message.show_audit_log = bool(moves_by_id.get(message.res_id)) + + def _search_account_audit_log_move_id(self, operator, value): + if operator in ['=', 'like', 'ilike', '!=', 'not ilike', 'not like'] and isinstance(value, str): + res_id_domain = [('res_id', 'in', self.env['account.move']._name_search(value, operator=operator))] + elif operator in ['=', 'in', '!=', 'not in']: + res_id_domain = [('res_id', operator, value)] + else: + raise UserError(_('Operation not supported')) + return [('model', '=', 'account.move')] + res_id_domain + + def _search_show_audit_log(self, operator, value): + if operator not in ['=', '!='] or not isinstance(value, bool): + raise UserError(_('Operation not supported')) + move_query = self.env['account.move']._search([('company_id.check_account_audit_trail', operator, value)]) + return [('model', '=', 'account.move'), ('res_id', 'in', move_query)] diff --git a/addons/account_audit_trail/models/res_company.py b/addons/account_audit_trail/models/res_company.py new file mode 100644 index 00000000000..9bb9bab657a --- /dev/null +++ b/addons/account_audit_trail/models/res_company.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class Company(models.Model): + _inherit = "res.company" + + check_account_audit_trail = fields.Boolean(string='Audit Trail') diff --git a/addons/account_audit_trail/models/res_config_settings.py b/addons/account_audit_trail/models/res_config_settings.py new file mode 100644 index 00000000000..a7803ad1fed --- /dev/null +++ b/addons/account_audit_trail/models/res_config_settings.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + check_account_audit_trail = fields.Boolean(string='Audit Trail', related='company_id.check_account_audit_trail', readonly=False) + + @api.constrains('check_account_audit_trail') + def _check_audit_trail_records(self): + if not self.check_account_audit_trail: + move_count = self.env['account.move'].search_count([('company_id', '=', self.company_id.id)], limit=1) + if move_count > 0: + raise UserError(_("Can't disable audit trail when there are existing records.")) diff --git a/addons/account_audit_trail/report/audit_trail_report_views.xml b/addons/account_audit_trail/report/audit_trail_report_views.xml new file mode 100644 index 00000000000..03541d38c92 --- /dev/null +++ b/addons/account_audit_trail/report/audit_trail_report_views.xml @@ -0,0 +1,59 @@ + + + + + mail.message.tree.inherit.audit.log + mail.message + 99 + primary + + + + + + + + + + + + + mail.message.search + mail.message + 99 + primary + + + + + + + + + + + + + + + + Audit Trail + mail.message + + tree + +

    + To see the audit log you have to enable the audit trail option from settings +

    +
    + [ + ('model', '=', 'account.move'), + ('message_type', '=', 'notification'), + ('account_audit_log_move_id', '!=', False), + ('show_audit_log', '=', True), + ] + +
    + + +
    diff --git a/addons/account_audit_trail/views/res_config_settings_views.xml b/addons/account_audit_trail/views/res_config_settings_views.xml new file mode 100644 index 00000000000..36069c0d6fc --- /dev/null +++ b/addons/account_audit_trail/views/res_config_settings_views.xml @@ -0,0 +1,22 @@ + + + + res.config.settings.form.inherit.account.audit.trail + res.config.settings + + + + + + +