[ADD] account_audit_trail: separate module from l10n_in
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) <wan@odoo.com>
This commit is contained in:
@@ -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/<lang>.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/<lang>.po
|
||||
source_file = addons/account_check_printing/i18n/account_check_printing.pot
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -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',
|
||||
}
|
||||
@@ -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 ""
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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("<div>%s</div>") % 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(
|
||||
"<li>%(old_value)s <i class='o_TrackingValue_separator fa fa-long-arrow-right mx-1 text-600' title='%(title)s' role='img' aria-label='%(title)s'></i>%(new_value)s (%(field)s)</li>"
|
||||
) % {
|
||||
'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)]
|
||||
@@ -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')
|
||||
@@ -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."))
|
||||
@@ -0,0 +1,59 @@
|
||||
<?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="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree edit="0" delete="0" create="0" action="action_open_document" type="object">
|
||||
<field name="res_id" column_invisible="True"/>
|
||||
<field name="date"/>
|
||||
<field name="author_id" widget="many2one_avatar"/>
|
||||
<field name="account_audit_log_move_id"/>
|
||||
<field name="account_audit_log_preview"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_message_tree_audit_log_search" model="ir.ui.view">
|
||||
<field name="name">mail.message.search</field>
|
||||
<field name="model">mail.message</field>
|
||||
<field name="priority">99</field>
|
||||
<field name="mode">primary</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Messages Search">
|
||||
<field name="account_audit_log_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'}"/>
|
||||
<filter string="Journal Entry" name="group_by_log_move_id" domain="[]" context="{'group_by': 'res_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_account_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="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
To see the audit log you have to enable the audit trail option from settings
|
||||
</p>
|
||||
</field>
|
||||
<field name="domain">[
|
||||
('model', '=', 'account.move'),
|
||||
('message_type', '=', 'notification'),
|
||||
('account_audit_log_move_id', '!=', False),
|
||||
('show_audit_log', '=', True),
|
||||
]</field>
|
||||
<field name="search_view_id" ref="view_message_tree_audit_log_search"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="account_audit_trail_menu" name="Audit Trail" action="action_account_audit_trail_report" parent="account.account_reports_management_menu"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="res_config_settings_view_form_inherit_account_audit_trail" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.form.inherit.account.audit.trail</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<app name="account" position="inside">
|
||||
<block title="Audit Trail" name="audit_trail_setting_container">
|
||||
<setting string="Audit Trail" company_dependent="1" help="Activate Audit Trail">
|
||||
<field name="check_account_audit_trail"/>
|
||||
<button name="%(account_audit_trail.action_account_audit_trail_report)d"
|
||||
type="action"
|
||||
string="Go to Audit Trail"
|
||||
class="oe_highlight"
|
||||
invisible="check_account_audit_trail == False"/>
|
||||
</setting>
|
||||
</block>
|
||||
</app>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user