diff --git a/account_asset_import_wizard/models/account_asset_line_import.py b/account_asset_import_wizard/models/account_asset_line_import.py index 8a66d0ed19..4cc755038c 100644 --- a/account_asset_import_wizard/models/account_asset_line_import.py +++ b/account_asset_import_wizard/models/account_asset_line_import.py @@ -283,8 +283,7 @@ def _action_process(self): and not self.import_id.update_data ): same_asset_lines = self.import_id.import_line_ids.filtered( - lambda c: c.account_asset_name == (self.account_asset_name) - and c.account_asset_ref == self.account_asset_ref + lambda c: c.account_asset_id == self.account_asset_id ) if same_asset_lines: if any([line.state == "error" for line in same_asset_lines]): diff --git a/account_asset_line_menu/README.rst b/account_asset_line_menu/README.rst index 8bdea10676..4c71fdbc44 100644 --- a/account_asset_line_menu/README.rst +++ b/account_asset_line_menu/README.rst @@ -7,6 +7,7 @@ Account asset line menu ======================= * New menu "Assets lines". +* Take related fields from assets to assets lines. Bug Tracker =========== diff --git a/account_asset_line_menu/__init__.py b/account_asset_line_menu/__init__.py index e69de29bb2..0650744f6b 100644 --- a/account_asset_line_menu/__init__.py +++ b/account_asset_line_menu/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_asset_line_menu/i18n/account_asset_line_menu.pot b/account_asset_line_menu/i18n/account_asset_line_menu.pot index 1f6439735a..9c803154fe 100644 --- a/account_asset_line_menu/i18n/account_asset_line_menu.pot +++ b/account_asset_line_menu/i18n/account_asset_line_menu.pot @@ -4,10 +4,10 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-05 08:31+0000\n" -"PO-Revision-Date: 2024-03-05 08:31+0000\n" +"POT-Creation-Date: 2024-10-31 10:23+0000\n" +"PO-Revision-Date: 2024-10-31 10:23+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -15,6 +15,16 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: account_asset_line_menu +#: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_filter +msgid "Account Analytic" +msgstr "" + +#. module: account_asset_line_menu +#: model:ir.model.fields,field_description:account_asset_line_menu.field_account_asset_line__account_analytic_id +msgid "Analytic account" +msgstr "" + #. module: account_asset_line_menu #: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_tree msgid "Are you sure ?" @@ -25,10 +35,19 @@ msgstr "" msgid "Asset" msgstr "" +#. module: account_asset_line_menu +#: model:ir.model.fields,field_description:account_asset_line_menu.field_account_asset_line__profile_id +msgid "Asset Profile" +msgstr "" + +#. module: account_asset_line_menu +#: model:ir.model,name:account_asset_line_menu.model_account_asset_line +msgid "Asset depreciation table line" +msgstr "" + #. module: account_asset_line_menu #: model:ir.actions.act_window,name:account_asset_line_menu.account_asset_line_action #: model:ir.ui.menu,name:account_asset_line_menu.menu_account_asset_line -#: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_tree msgid "Assets Lines" msgstr "" @@ -47,16 +66,36 @@ msgstr "" msgid "Delete/Reverse Move" msgstr "" +#. module: account_asset_line_menu +#: model:ir.model.fields,field_description:account_asset_line_menu.field_account_asset_line__display_name +msgid "Display Name" +msgstr "" + #. module: account_asset_line_menu #: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_filter msgid "Group By" msgstr "" +#. module: account_asset_line_menu +#: model:ir.model.fields,field_description:account_asset_line_menu.field_account_asset_line__id +msgid "ID" +msgstr "" + #. module: account_asset_line_menu #: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_tree msgid "Init" msgstr "" +#. module: account_asset_line_menu +#: model:ir.model.fields,field_description:account_asset_line_menu.field_account_asset_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: account_asset_line_menu +#: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_filter +msgid "Profile" +msgstr "" + #. module: account_asset_line_menu #: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_filter msgid "Search Assets Lines" diff --git a/account_asset_line_menu/i18n/es.po b/account_asset_line_menu/i18n/es.po index 9b05480ac3..6b8b21d473 100644 --- a/account_asset_line_menu/i18n/es.po +++ b/account_asset_line_menu/i18n/es.po @@ -4,10 +4,10 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 16.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-05 08:31+0000\n" -"PO-Revision-Date: 2024-03-05 08:31+0000\n" +"POT-Creation-Date: 2024-10-31 10:24+0000\n" +"PO-Revision-Date: 2024-10-31 10:24+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -15,6 +15,15 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: account_asset_line_menu +#: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_filter +msgid "Account Analytic" +msgstr "Cuenta analítica" + +#. module: account_asset_line_menu +#: model:ir.model.fields,field_description:account_asset_line_menu.field_account_asset_line__account_analytic_id +msgid "Analytic account" + #. module: account_asset_line_menu #: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_tree msgid "Are you sure ?" @@ -25,10 +34,19 @@ msgstr "¿Está usted seguro" msgid "Asset" msgstr "Activo" +#. module: account_asset_line_menu +#: model:ir.model.fields,field_description:account_asset_line_menu.field_account_asset_line__profile_id +msgid "Asset Profile" +msgstr "Categoría del activo" + +#. module: account_asset_line_menu +#: model:ir.model,name:account_asset_line_menu.model_account_asset_line +msgid "Asset depreciation table line" +msgstr "Línea de amortización del activo" + #. module: account_asset_line_menu #: model:ir.actions.act_window,name:account_asset_line_menu.account_asset_line_action #: model:ir.ui.menu,name:account_asset_line_menu.menu_account_asset_line -#: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_tree msgid "Assets Lines" msgstr "Líneas de activos" @@ -47,16 +65,36 @@ msgstr "Fecha" msgid "Delete/Reverse Move" msgstr "Borrar/Deshacer movimiento" +#. module: account_asset_line_menu +#: model:ir.model.fields,field_description:account_asset_line_menu.field_account_asset_line__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + #. module: account_asset_line_menu #: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_filter msgid "Group By" msgstr "Agrupar por" +#. module: account_asset_line_menu +#: model:ir.model.fields,field_description:account_asset_line_menu.field_account_asset_line__id +msgid "ID" +msgstr "" + #. module: account_asset_line_menu #: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_tree msgid "Init" msgstr "Inicio" +#. module: account_asset_line_menu +#: model:ir.model.fields,field_description:account_asset_line_menu.field_account_asset_line____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: account_asset_line_menu +#: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_filter +msgid "Profile" +msgstr "Categoría" + #. module: account_asset_line_menu #: model_terms:ir.ui.view,arch_db:account_asset_line_menu.account_asset_line_view_filter msgid "Search Assets Lines" diff --git a/account_asset_line_menu/models/__init__.py b/account_asset_line_menu/models/__init__.py new file mode 100644 index 0000000000..9cd021119d --- /dev/null +++ b/account_asset_line_menu/models/__init__.py @@ -0,0 +1 @@ +from . import account_asset_line diff --git a/account_asset_line_menu/models/account_asset_line.py b/account_asset_line_menu/models/account_asset_line.py new file mode 100644 index 0000000000..19f3e68aba --- /dev/null +++ b/account_asset_line_menu/models/account_asset_line.py @@ -0,0 +1,21 @@ +# Copyright 2024 Berezi Amubieta - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class AccountAssetLine(models.Model): + _inherit = "account.asset.line" + + profile_id = fields.Many2one( + comodel_name="account.asset.profile", related="asset_id.profile_id", store=True + ) + account_analytic_id = fields.Many2one( + comodel_name="account.analytic.account", + related="asset_id.account_analytic_id", + store=True, + ) + code = fields.Char( + related="asset_id.code", + store=True, + ) diff --git a/account_asset_line_menu/views/account_asset_line_views.xml b/account_asset_line_menu/views/account_asset_line_views.xml index 57d5319047..cdf073a998 100644 --- a/account_asset_line_menu/views/account_asset_line_views.xml +++ b/account_asset_line_menu/views/account_asset_line_views.xml @@ -9,7 +9,10 @@ create="false" > + + + + @@ -79,6 +83,18 @@ name="group-type" domain="[]" context="{'group_by': 'type'}" + /> + + + + + account.move + + + + + + + + + + account.move + + + + + + + + + + account.move + + + + + + + + + + + diff --git a/account_invoice_paid_data/README.rst b/account_invoice_paid_data/README.rst new file mode 100644 index 0000000000..487160d7e8 --- /dev/null +++ b/account_invoice_paid_data/README.rst @@ -0,0 +1,33 @@ +========================= +Account invoice paid data +========================= + +.. |badge1| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +|badge1| + +This module extends accounting for Gausark: + +* paid date field in invoices tree that is filled when payment state changes to paid +* payment period field, days between invoice date and paid date + +**Table of contents** + +.. contents:: + :local: + +Credits +======= + +Authors +~~~~~~~ + +* Avanzosc + +Contributors +~~~~~~~~~~~~ + +* Oihane Crucelaegui +* Ana Juaristi diff --git a/account_invoice_paid_data/__init__.py b/account_invoice_paid_data/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/account_invoice_paid_data/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_invoice_paid_data/__manifest__.py b/account_invoice_paid_data/__manifest__.py new file mode 100644 index 0000000000..8be0ed805d --- /dev/null +++ b/account_invoice_paid_data/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2023 Oihane Crucelaegui - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Account invoice paid data", + "version": "14.0.1.1.0", + "category": "Hidden", + "license": "AGPL-3", + "author": "AvanzOSC", + "website": "https://github.com/avanzosc/odoo-addons", + "depends": ["account", "account_move_template"], + "excludes": [], + "data": [ + "views/account_move_views.xml", + ], + "installable": True, +} diff --git a/account_invoice_paid_data/i18n/account_invoice_paid_data.pot b/account_invoice_paid_data/i18n/account_invoice_paid_data.pot new file mode 100644 index 0000000000..d5863b58d9 --- /dev/null +++ b/account_invoice_paid_data/i18n/account_invoice_paid_data.pot @@ -0,0 +1,63 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_paid_data +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-11-07 14:40+0000\n" +"PO-Revision-Date: 2024-11-07 14:40+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_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move__display_name +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move_template__display_name +msgid "Display Name" +msgstr "" + +#. module: account_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move__id +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move_template__id +msgid "ID" +msgstr "" + +#. module: account_invoice_paid_data +#: model:ir.model,name:account_invoice_paid_data.model_account_move +msgid "Journal Entry" +msgstr "" + +#. module: account_invoice_paid_data +#: model:ir.model,name:account_invoice_paid_data.model_account_move_template +msgid "Journal Entry Template" +msgstr "" + +#. module: account_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move____last_update +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move_template____last_update +msgid "Last Modified on" +msgstr "" + +#. module: account_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move_template__line_ids +msgid "Lines" +msgstr "" + +#. module: account_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_bank_statement_line__paid_date +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move__paid_date +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_payment__paid_date +msgid "Paid Date" +msgstr "" + +#. module: account_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_bank_statement_line__payment_period +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move__payment_period +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_payment__payment_period +msgid "Payment Period" +msgstr "" diff --git a/account_invoice_paid_data/i18n/es.po b/account_invoice_paid_data/i18n/es.po new file mode 100644 index 0000000000..86df702f47 --- /dev/null +++ b/account_invoice_paid_data/i18n/es.po @@ -0,0 +1,63 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_paid_data +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-11-07 14:40+0000\n" +"PO-Revision-Date: 2024-11-07 14:40+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_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move__display_name +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move_template__display_name +msgid "Display Name" +msgstr "Mostrar nombre" + +#. module: account_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move__id +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move_template__id +msgid "ID" +msgstr "Identificador" + +#. module: account_invoice_paid_data +#: model:ir.model,name:account_invoice_paid_data.model_account_move +msgid "Journal Entry" +msgstr "Asiento contable" + +#. module: account_invoice_paid_data +#: model:ir.model,name:account_invoice_paid_data.model_account_move_template +msgid "Journal Entry Template" +msgstr "Plantilla de asiento" + +#. module: account_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move____last_update +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move_template____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: account_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move_template__line_ids +msgid "Lines" +msgstr "Líneas" + +#. module: account_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_bank_statement_line__paid_date +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move__paid_date +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_payment__paid_date +msgid "Paid Date" +msgstr "Fecha de pago" + +#. module: account_invoice_paid_data +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_bank_statement_line__payment_period +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_move__payment_period +#: model:ir.model.fields,field_description:account_invoice_paid_data.field_account_payment__payment_period +msgid "Payment Period" +msgstr "Periodo de pago" diff --git a/account_invoice_paid_data/models/__init__.py b/account_invoice_paid_data/models/__init__.py new file mode 100644 index 0000000000..904add1185 --- /dev/null +++ b/account_invoice_paid_data/models/__init__.py @@ -0,0 +1,2 @@ +from . import account_move_template +from . import account_move diff --git a/account_invoice_paid_data/models/account_move.py b/account_invoice_paid_data/models/account_move.py new file mode 100644 index 0000000000..6e9a91f853 --- /dev/null +++ b/account_invoice_paid_data/models/account_move.py @@ -0,0 +1,38 @@ +# Copyright 2023 Oihane Crucelaegui - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class AccountMove(models.Model): + _inherit = "account.move" + + paid_date = fields.Date( + compute="_compute_paid_date", + store=True, + copy=False, + readonly=True, + ) + payment_period = fields.Integer( + compute="_compute_payment_period", + store=True, + copy=False, + readonly=True, + ) + + @api.depends("payment_state") + def _compute_paid_date(self): + for move in self: + move.paid_date = ( + fields.Date.context_today(move) + if move.payment_state == "paid" + else False + ) + + @api.depends("paid_date", "invoice_date") + def _compute_payment_period(self): + for move in self: + period = 0 + if move.paid_date and move.invoice_date: + period = (move.paid_date - move.invoice_date).days + move.payment_period = period diff --git a/account_invoice_paid_data/models/account_move_template.py b/account_invoice_paid_data/models/account_move_template.py new file mode 100644 index 0000000000..0a3a69726a --- /dev/null +++ b/account_invoice_paid_data/models/account_move_template.py @@ -0,0 +1,9 @@ +# Copyright 2023 Alfredo de la Fuente - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class AccountMoveTemplate(models.Model): + _inherit = "account.move.template" + + line_ids = fields.One2many(copy=True) diff --git a/account_invoice_paid_data/views/account_move_views.xml b/account_invoice_paid_data/views/account_move_views.xml new file mode 100644 index 0000000000..01074b018b --- /dev/null +++ b/account_invoice_paid_data/views/account_move_views.xml @@ -0,0 +1,17 @@ + + + + account.move + + + + + + + + + diff --git a/account_invoice_tax_breakdown_export/__init__.py b/account_invoice_tax_breakdown_export/__init__.py new file mode 100644 index 0000000000..5cb1c49143 --- /dev/null +++ b/account_invoice_tax_breakdown_export/__init__.py @@ -0,0 +1 @@ +from . import wizards diff --git a/account_invoice_tax_breakdown_export/__manifest__.py b/account_invoice_tax_breakdown_export/__manifest__.py new file mode 100644 index 0000000000..536223cc36 --- /dev/null +++ b/account_invoice_tax_breakdown_export/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2024 Oihane Crucelaegui - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Account Invoice export xlsx with Tax Breakdown", + "version": "14.0.1.0.0", + "category": "Hidden/Tools", + "license": "AGPL-3", + "author": "AvanzOSC", + "website": "https://github.com/avanzosc/odoo-addons", + "depends": [ + "account", + ], + "excludes": [], + "data": [ + "security/ir.model.access.csv", + "wizards/account_invoice_xlsx_export_views.xml", + ], + "installable": True, +} diff --git a/account_invoice_tax_breakdown_export/i18n/account_invoice_tax_breakdown_export.pot b/account_invoice_tax_breakdown_export/i18n/account_invoice_tax_breakdown_export.pot new file mode 100644 index 0000000000..b7592b95af --- /dev/null +++ b/account_invoice_tax_breakdown_export/i18n/account_invoice_tax_breakdown_export.pot @@ -0,0 +1,92 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_tax_breakdown_export +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-11-04 10:01+0000\n" +"PO-Revision-Date: 2024-11-04 10:01+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_invoice_tax_breakdown_export +#: model_terms:ir.ui.view,arch_db:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_view_form +msgid "Cancel" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__create_uid +msgid "Created by" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__create_date +msgid "Created on" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__display_name +msgid "Display Name" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model_terms:ir.ui.view,arch_db:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_view_form +msgid "Export" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.actions.act_window,name:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_action +#: model_terms:ir.ui.view,arch_db:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_view_form +msgid "Export Invoices with Tax Breakdown" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__id +msgid "ID" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__invoice_ids +msgid "Invoice" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__invoice_count +msgid "Invoice Count" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model_terms:ir.ui.view,arch_db:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_view_form +msgid "Invoices XLSX file" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export____last_update +msgid "Last Modified on" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__write_date +msgid "Last Updated on" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model_terms:ir.ui.view,arch_db:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_view_form +msgid "This will create a XLSX file with a list of invoices." +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model,name:account_invoice_tax_breakdown_export.model_account_invoice_breakdown_export +msgid "account.invoice.breakdown.export" +msgstr "" diff --git a/account_invoice_tax_breakdown_export/i18n/es.po b/account_invoice_tax_breakdown_export/i18n/es.po new file mode 100644 index 0000000000..cef21b7a06 --- /dev/null +++ b/account_invoice_tax_breakdown_export/i18n/es.po @@ -0,0 +1,92 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_tax_breakdown_export +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-11-04 10:01+0000\n" +"PO-Revision-Date: 2024-11-04 10:01+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_invoice_tax_breakdown_export +#: model_terms:ir.ui.view,arch_db:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_view_form +msgid "Cancel" +msgstr "Cancelar" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: account_invoice_tax_breakdown_export +#: model_terms:ir.ui.view,arch_db:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_view_form +msgid "Export" +msgstr "Exportar" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.actions.act_window,name:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_action +#: model_terms:ir.ui.view,arch_db:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_view_form +msgid "Export Invoices with Tax Breakdown" +msgstr "Exportar facturas con desglose de impuestos" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__id +msgid "ID" +msgstr "" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__invoice_ids +msgid "Invoice" +msgstr "Factura" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__invoice_count +msgid "Invoice Count" +msgstr "Núm. facturas" + +#. module: account_invoice_tax_breakdown_export +#: model_terms:ir.ui.view,arch_db:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_view_form +msgid "Invoices XLSX file" +msgstr "Fichero XLSX de facturas" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export____last_update +msgid "Last Modified on" +msgstr "Última modificación el" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model.fields,field_description:account_invoice_tax_breakdown_export.field_account_invoice_breakdown_export__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: account_invoice_tax_breakdown_export +#: model_terms:ir.ui.view,arch_db:account_invoice_tax_breakdown_export.account_invoice_breakdown_export_view_form +msgid "This will create a XLSX file with a list of invoices." +msgstr "Esto creará un fichero XLSX con una lista de facturas." + +#. module: account_invoice_tax_breakdown_export +#: model:ir.model,name:account_invoice_tax_breakdown_export.model_account_invoice_breakdown_export +msgid "account.invoice.breakdown.export" +msgstr "" diff --git a/account_invoice_tax_breakdown_export/security/ir.model.access.csv b/account_invoice_tax_breakdown_export/security/ir.model.access.csv new file mode 100644 index 0000000000..3d0983e3ee --- /dev/null +++ b/account_invoice_tax_breakdown_export/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_wiz_account_invoice_breakdown_export,account.invoice.breakdown.export,model_account_invoice_breakdown_export,account.group_account_invoice,1,1,1,1 diff --git a/account_invoice_tax_breakdown_export/wizards/__init__.py b/account_invoice_tax_breakdown_export/wizards/__init__.py new file mode 100644 index 0000000000..103362a47b --- /dev/null +++ b/account_invoice_tax_breakdown_export/wizards/__init__.py @@ -0,0 +1 @@ +from . import account_invoice_xlsx_export diff --git a/account_invoice_tax_breakdown_export/wizards/account_invoice_xlsx_export.py b/account_invoice_tax_breakdown_export/wizards/account_invoice_xlsx_export.py new file mode 100644 index 0000000000..17f927fdb2 --- /dev/null +++ b/account_invoice_tax_breakdown_export/wizards/account_invoice_xlsx_export.py @@ -0,0 +1,280 @@ +# Copyright 2024 Oihane Crucelaegui - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +import base64 +import logging +import os +import tempfile +from collections import defaultdict + +from odoo import api, fields, models, tools + +_logger = logging.getLogger(__name__) + +try: + from statistics import mean + + STATS_PATH = tools.find_in_path("statistics") +except (ImportError, IOError) as err: + _logger.debug(err) + +try: + import xlsxwriter + +except ImportError: + _logger.debug("Can not import xlsxwriter`.") + + +class AccountInvoiceXLSXExport(models.TransientModel): + _name = "account.invoice.breakdown.export" + + invoice_ids = fields.Many2many( + comodel_name="account.move", + ) + invoice_count = fields.Integer( + compute="_compute_invoice_count", + ) + + @api.depends("invoice_ids") + def _compute_invoice_count(self): + for record in self: + record.invoice_count = len(record.invoice_ids) + + @api.model + def default_get(self, fields): + rec = super().default_get(fields) + active_model = self.env.context.get("active_model") + if active_model == "account.move": + invoices = ( + self.env[active_model] + .browse(self.env.context.get("active_ids")) + .exists() + .filtered(lambda inv: inv.is_invoice() and inv.state == "posted") + ) + rec.update(invoice_ids=[(6, 0, invoices.ids)]) + return rec + + def export_invoices(self): + self.ensure_one() + filename = "Listado_Facturas.xlsx" + filepath = tempfile.gettempdir() + "/" + filename + + workbook = xlsxwriter.Workbook(filepath, {"default_date_format": "dd/mm/yyyy"}) + currency_id = self.env["res.company"]._default_currency_id() + + # Styles + # title = workbook.add_format( + # { + # "bold": True, + # "align": "center", + # "valign": "vcenter", + # } + # ) + table_header = workbook.add_format( + { + "bold": True, + "border": 1, + "align": "center", + "valign": "vcenter", + "fg_color": "#D7E4BC", + } + ) + table_header.set_text_wrap() + table_detail_left = workbook.add_format( + { + "border": 1, + "align": "left", + "valign": "vcenter", + } + ) + table_detail_right_num = workbook.add_format( + { + "border": 1, + "align": "right", + "valign": "vcenter", + "num_format": "#,##0." + "0" * currency_id.decimal_places, + } + ) + table_detail_date = workbook.add_format( + { + "border": 1, + "align": "right", + "valign": "vcenter", + "num_format": "dd/mm/yyyy", # diamacon format + } + ) + + for move_type in self.invoice_ids.get_invoice_types(include_receipts=False): + invoices = self.invoice_ids.filtered(lambda m: m.move_type == move_type) + if invoices: + all_taxes = ( + self.invoice_ids.mapped("line_ids") + .mapped("tax_line_id") + .flatten_taxes_hierarchy() + .mapped("tax_group_id") + .sorted() + ) + + worksheet = workbook.add_worksheet(move_type) + worksheet.write(0, 0, "Invoice Number", table_header) + worksheet.write(0, 1, "Invoice Date", table_header) + worksheet.write(0, 2, "Partner", table_header) + worksheet.write(0, 3, "Concept", table_header) + worksheet.set_column(0, 3, 32) + worksheet.write(0, 4, "Amount Untaxed", table_header) + worksheet.write(0, 5, "Amount Taxed", table_header) + worksheet.set_column(4, 5, 15) + + start_tax_col_num = tax_col_num = 6 + for tax in all_taxes: + worksheet.write(0, tax_col_num, tax.display_name, table_header) + worksheet.write( + 0, tax_col_num + 1, "%s base" % (tax.display_name), table_header + ) + worksheet.write( + 0, + tax_col_num + 2, + "%s amount" % (tax.display_name), + table_header, + ) + worksheet.set_column(tax_col_num, tax_col_num + 2, 15) + tax_col_num += 3 + + row_num = 1 + for invoice in invoices: + # diamacon does not accept special characters + invoice_num = "".join( + letter for letter in invoice.name if letter.isalnum() + ) + # diamacon only accepts up to 8 characters + worksheet.write(row_num, 0, invoice_num[-8:], table_detail_left) + worksheet.write_datetime( + row_num, + 1, + invoice.invoice_date, + table_detail_date, + ) + worksheet.write( + row_num, + 2, + invoice.invoice_partner_display_name, + table_detail_left, + ) + # worksheet.write(row_num, 3, "Concept", table_detail_left) + worksheet.write( + row_num, + 4, + invoice.amount_untaxed_signed, + table_detail_right_num, + ) + worksheet.write( + row_num, 5, invoice.amount_total_signed, table_detail_right_num + ) + + tax_group_mapping = defaultdict( + lambda: { + "base_lines": set(), + "tax_percent": [], + "base_amount": 0.0, + "tax_amount": 0.0, + } + ) + + balance_multiplicator = -1 if invoice.is_inbound() else 1 + tax_lines = invoice.line_ids.filtered("tax_line_id") + base_lines = invoice.line_ids.filtered("tax_ids") + + for base_line in base_lines: + base_amount = balance_multiplicator * ( + base_line.amount_currency + if base_line.currency_id + else base_line.balance + ) + + for tax in base_line.tax_ids.flatten_taxes_hierarchy(): + + if base_line.tax_line_id.tax_group_id == tax.tax_group_id: + continue + + tax_group_vals = tax_group_mapping[tax.tax_group_id] + tax_group_vals["tax_percent"].append(tax.amount) + if base_line not in tax_group_vals["base_lines"]: + tax_group_vals["base_amount"] += base_amount + tax_group_vals["base_lines"].add(base_line) + + # Compute tax amounts. + for tax_line in tax_lines: + tax_amount = balance_multiplicator * ( + tax_line.amount_currency + if tax_line.currency_id + else tax_line.balance + ) + tax_group_vals = tax_group_mapping[ + tax_line.tax_line_id.tax_group_id + ] + tax_group_vals["tax_amount"] += tax_amount + + tax_col_num = start_tax_col_num + for tax_group in all_taxes: + tax_group_vals = tax_group_mapping[tax_group] + if not tax_group_vals: + pass + tax_percent = ( + mean(tax_group_vals["tax_percent"]) + if tax_group_vals["tax_percent"] + else 0.0 + ) + worksheet.write( + row_num, + tax_col_num, + tax_percent, + table_detail_right_num, + ) + worksheet.write( + row_num, + tax_col_num + 1, + tax_group_vals["base_amount"], + table_detail_right_num, + ) + worksheet.write( + row_num, + tax_col_num + 2, + tax_group_vals["tax_amount"], + table_detail_right_num, + ) + tax_col_num += 3 + + row_num += 1 + + workbook.close() + + fp = open(filepath, "rb") + file_data = fp.read() + excel_file = base64.encodestring(file_data) + fp.close() + + # Crear fichero temporal + datafile_fd, datafile_path = tempfile.mkstemp(suffix=filename) + os.close(datafile_fd) + + # CREAR ADJUNTO + with open(datafile_path, "r"): # as datafile: + res = { + "name": filename, + "datas": excel_file, + "res_model": self._name, + "res_id": self.id, + "type": "binary", + } + try: # Borrar archivo temporal + os.unlink(datafile_path) + except Exception: + pass + attach = self.env["ir.attachment"].create(res) + # self.env.cr.commit() + url = "/web/content/%s?download=true" % attach.id + return { + "type": "ir.actions.act_url", + "url": url, + "target": "new", + } diff --git a/account_invoice_tax_breakdown_export/wizards/account_invoice_xlsx_export_views.xml b/account_invoice_tax_breakdown_export/wizards/account_invoice_xlsx_export_views.xml new file mode 100644 index 0000000000..98c0efa7d4 --- /dev/null +++ b/account_invoice_tax_breakdown_export/wizards/account_invoice_xlsx_export_views.xml @@ -0,0 +1,41 @@ + + + + account.invoice.breakdown.export + +
+ +
+
+

Invoices XLSX file

+
+ This will create a XLSX file with a list of invoices. +
+
+
+
+ +
+
+ +
+
+ + + Export Invoices with Tax Breakdown + account.invoice.breakdown.export + tree,form + + new + + + list + +
diff --git a/account_invoice_with_accouunting_date/README.rst b/account_invoice_with_accouunting_date/README.rst new file mode 100644 index 0000000000..b2069b7a86 --- /dev/null +++ b/account_invoice_with_accouunting_date/README.rst @@ -0,0 +1,28 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +==================================== +Account invoice with accounting date +==================================== + +In account invoice new field accounting date. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, +please check there if your issue has already been reported. If you spotted +it first, help us smash it by providing detailed and welcomed feedback. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Contributors +------------ + +* Ana Juaristi +* Berezi Amubieta diff --git a/account_invoice_with_accouunting_date/__init__.py b/account_invoice_with_accouunting_date/__init__.py new file mode 100644 index 0000000000..4c4f242fa0 --- /dev/null +++ b/account_invoice_with_accouunting_date/__init__.py @@ -0,0 +1 @@ +from . import report diff --git a/account_invoice_with_accouunting_date/__manifest__.py b/account_invoice_with_accouunting_date/__manifest__.py new file mode 100644 index 0000000000..828ceccbbd --- /dev/null +++ b/account_invoice_with_accouunting_date/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2024 Berezi Amubieta - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Account Invoice With Accounting Date", + "version": "14.0.1.0.0", + "author": "AvanzOSC", + "website": "https://github.com/avanzosc/odoo-addons", + "category": "Invoices & Payments", + "license": "AGPL-3", + "depends": [ + "account", + ], + "data": ["report/account_invoice_report_view.xml"], + "installable": True, +} diff --git a/account_invoice_with_accouunting_date/i18n/account_invoice_with_accouunting_date.pot b/account_invoice_with_accouunting_date/i18n/account_invoice_with_accouunting_date.pot new file mode 100644 index 0000000000..d32afe6711 --- /dev/null +++ b/account_invoice_with_accouunting_date/i18n/account_invoice_with_accouunting_date.pot @@ -0,0 +1,42 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_with_accouunting_date +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-10-24 07:17+0000\n" +"PO-Revision-Date: 2024-10-24 07:17+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_invoice_with_accouunting_date +#: model:ir.model.fields,field_description:account_invoice_with_accouunting_date.field_account_invoice_report__date +#: model_terms:ir.ui.view,arch_db:account_invoice_with_accouunting_date.view_account_invoice_report_search +msgid "Accounting Date" +msgstr "" + +#. module: account_invoice_with_accouunting_date +#: model:ir.model.fields,field_description:account_invoice_with_accouunting_date.field_account_invoice_report__display_name +msgid "Display Name" +msgstr "" + +#. module: account_invoice_with_accouunting_date +#: model:ir.model.fields,field_description:account_invoice_with_accouunting_date.field_account_invoice_report__id +msgid "ID" +msgstr "" + +#. module: account_invoice_with_accouunting_date +#: model:ir.model,name:account_invoice_with_accouunting_date.model_account_invoice_report +msgid "Invoices Statistics" +msgstr "" + +#. module: account_invoice_with_accouunting_date +#: model:ir.model.fields,field_description:account_invoice_with_accouunting_date.field_account_invoice_report____last_update +msgid "Last Modified on" +msgstr "" diff --git a/account_invoice_with_accouunting_date/i18n/es.po b/account_invoice_with_accouunting_date/i18n/es.po new file mode 100644 index 0000000000..a10d21ee9c --- /dev/null +++ b/account_invoice_with_accouunting_date/i18n/es.po @@ -0,0 +1,42 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_invoice_with_accouunting_date +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-10-24 07:17+0000\n" +"PO-Revision-Date: 2024-10-24 07:17+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_invoice_with_accouunting_date +#: model:ir.model.fields,field_description:account_invoice_with_accouunting_date.field_account_invoice_report__date +#: model_terms:ir.ui.view,arch_db:account_invoice_with_accouunting_date.view_account_invoice_report_search +msgid "Accounting Date" +msgstr "Fecha contable" + +#. module: account_invoice_with_accouunting_date +#: model:ir.model.fields,field_description:account_invoice_with_accouunting_date.field_account_invoice_report__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: account_invoice_with_accouunting_date +#: model:ir.model.fields,field_description:account_invoice_with_accouunting_date.field_account_invoice_report__id +msgid "ID" +msgstr "" + +#. module: account_invoice_with_accouunting_date +#: model:ir.model,name:account_invoice_with_accouunting_date.model_account_invoice_report +msgid "Invoices Statistics" +msgstr "Estadísticas de facturas" + +#. module: account_invoice_with_accouunting_date +#: model:ir.model.fields,field_description:account_invoice_with_accouunting_date.field_account_invoice_report____last_update +msgid "Last Modified on" +msgstr "Última modificación en" diff --git a/account_invoice_with_accouunting_date/report/__init__.py b/account_invoice_with_accouunting_date/report/__init__.py new file mode 100644 index 0000000000..52e62702b3 --- /dev/null +++ b/account_invoice_with_accouunting_date/report/__init__.py @@ -0,0 +1 @@ +from . import account_invoice_report diff --git a/account_invoice_with_accouunting_date/report/account_invoice_report.py b/account_invoice_with_accouunting_date/report/account_invoice_report.py new file mode 100644 index 0000000000..9b05bf7485 --- /dev/null +++ b/account_invoice_with_accouunting_date/report/account_invoice_report.py @@ -0,0 +1,13 @@ +# Copyright 2024 Berezi Amubieta - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import api, fields, models + + +class AccountInvoiceReport(models.Model): + _inherit = "account.invoice.report" + + date = fields.Date(string="Accounting Date", readonly=True) + + @api.model + def _select(self): + return super()._select() + ", move.date" diff --git a/account_invoice_with_accouunting_date/report/account_invoice_report_view.xml b/account_invoice_with_accouunting_date/report/account_invoice_report_view.xml new file mode 100644 index 0000000000..b19a466416 --- /dev/null +++ b/account_invoice_with_accouunting_date/report/account_invoice_report_view.xml @@ -0,0 +1,34 @@ + + + + account.invoice.report + + + + + + + + + + account.invoice.report + + + + + + + + + + + + + diff --git a/account_move_date_grouping/views/account_invoice_report_views.xml b/account_move_date_grouping/views/account_invoice_report_views.xml deleted file mode 100644 index fc6ec3cb83..0000000000 --- a/account_move_date_grouping/views/account_invoice_report_views.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - account.invoice.report.tree.info - account.invoice.report - - - - - - - - - - diff --git a/account_move_date_grouping/views/account_move_views.xml b/account_move_date_grouping/views/account_move_views.xml index 7a0f1a9146..73f8714e19 100644 --- a/account_move_date_grouping/views/account_move_views.xml +++ b/account_move_date_grouping/views/account_move_views.xml @@ -1,7 +1,6 @@ - account.move.form.inherit.date.grouping account.move @@ -14,7 +13,6 @@ - account.move.tree.inherit.date.grouping account.move @@ -27,7 +25,6 @@ - account.move.tree.inherit.date.grouping account.move @@ -40,7 +37,6 @@ - account.move.search.inherit.date.grouping account.move diff --git a/account_move_line_product_category/README.rst b/account_move_line_product_category/README.rst new file mode 100644 index 0000000000..bb1397cfbd --- /dev/null +++ b/account_move_line_product_category/README.rst @@ -0,0 +1,74 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg + :target: https://www.gnu.org/licenses/agpl-3.0.html + :alt: License: AGPL-3 + +================================== +Account Move Line Product Category +================================== + +Overview +======== + +The **Account Move Line Product Category** module adds the product category field to invoice lines, allowing users to easily view and filter by product categories in the invoice line pivot views. This provides better insight into sales and accounting data, especially when analyzing performance by product categories. + +Features +======== + +- **Product Category Field**: Adds the product category field to invoice lines, which is automatically related to the product's category. +- **Enhanced Pivot Views**: The product category field is available in pivot views for better reporting and analysis. + +Usage +===== + +1. **Install the Module**: + + - Install the module via Odoo's Apps interface. + +2. **Viewing Product Category on Invoice Lines**: + + - Navigate to **Accounting > Customers > Invoices**. + - Open an invoice and view the **Product Category** field in the line items. + +3. **Reporting**: + + - In the pivot view of invoice lines, you can now group or filter data by **Product Category** to analyze sales performance by category. + +Configuration +============= + +No additional configuration is required. The product category field is automatically populated from the related product's category. + +Testing +======= + +Test the following scenarios: + +- **Product Category on Invoice Lines**: + + - Create an invoice with products that belong to different categories. + - Verify that the **Product Category** field is correctly displayed and populated for each product in the invoice lines. + +- **Pivot View**: + + - Go to the pivot view of invoice lines and confirm that the **Product Category** field is available for grouping and filtering data. + +Bug Tracker +=========== + +If you encounter any issues, please report them on the GitHub repository at `GitHub Issues `_. + +Credits +======= + +Contributors +------------ + +* Unai Beristain +* Ana Juaristi + +For module-specific questions, please contact the contributors directly. Support requests should be made through the official channels. + +License +======= + +This project is licensed under the AGPL-3 License. For more details, please refer to the LICENSE file or visit . diff --git a/account_move_line_product_category/__init__.py b/account_move_line_product_category/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/account_move_line_product_category/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_move_line_product_category/__manifest__.py b/account_move_line_product_category/__manifest__.py new file mode 100644 index 0000000000..f33caddaf5 --- /dev/null +++ b/account_move_line_product_category/__manifest__.py @@ -0,0 +1,18 @@ +{ + "name": "Account Move Line Product Category>", + "version": "14.0.1.0.0", + "summary": "Adds product category and manufacturer fields" + "to invoice lines pivot views.", + "category": "Accounting", + "author": "Avanzosc", + "website": "https://github.com/avanzosc/odoo-addons", + "license": "AGPL-3", + "depends": [ + "account", + "product", + ], + "data": [ + "views/account_move_line_views.xml", + ], + "installable": True, +} diff --git a/account_move_line_product_category/models/__init__.py b/account_move_line_product_category/models/__init__.py new file mode 100644 index 0000000000..8795b3bea6 --- /dev/null +++ b/account_move_line_product_category/models/__init__.py @@ -0,0 +1 @@ +from . import account_move_line diff --git a/account_move_line_product_category/models/account_move_line.py b/account_move_line_product_category/models/account_move_line.py new file mode 100644 index 0000000000..f2a482a81a --- /dev/null +++ b/account_move_line_product_category/models/account_move_line.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + product_category_id = fields.Many2one( + related="product_id.categ_id", + string="Product Category", + store=True, + ) diff --git a/account_move_line_product_category/views/account_move_line_views.xml b/account_move_line_product_category/views/account_move_line_views.xml new file mode 100644 index 0000000000..5b82b7c92f --- /dev/null +++ b/account_move_line_product_category/views/account_move_line_views.xml @@ -0,0 +1,15 @@ + + + + account.move.line.tree.inherit.product.category.manufacturer + account.move.line + + + + + + + + diff --git a/batch_liquidation_report/__manifest__.py b/batch_liquidation_report/__manifest__.py index 19c50d7d5a..28bf26a609 100644 --- a/batch_liquidation_report/__manifest__.py +++ b/batch_liquidation_report/__manifest__.py @@ -17,6 +17,7 @@ "stock_picking_batch_liquidation", "report_qweb_element_page_visibility", "account_invoice_report_grouped_by_picking", + "stock_picking_cmr_report", ], "data": [ "security/ir.model.access.csv", diff --git a/batch_liquidation_report/i18n/batch_liquidation_report.pot b/batch_liquidation_report/i18n/batch_liquidation_report.pot index 42b6676f39..b786b7a1f6 100644 --- a/batch_liquidation_report/i18n/batch_liquidation_report.pot +++ b/batch_liquidation_report/i18n/batch_liquidation_report.pot @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-09-25 11:11+0000\n" -"PO-Revision-Date: 2024-09-25 11:11+0000\n" +"POT-Creation-Date: 2024-11-15 08:58+0000\n" +"PO-Revision-Date: 2024-11-15 08:58+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -31,7 +31,7 @@ msgstr "" #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.external_layout_standard_picking_report msgid "" "- Page:\n" -" / \n" +" /\n" " " msgstr "" @@ -123,11 +123,6 @@ msgstr "" msgid "Entry" msgstr "" -#. module: batch_liquidation_report -#: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_picking -msgid "Expiration" -msgstr "" - #. module: batch_liquidation_report #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_fattening_cost msgid "Feed" @@ -181,6 +176,11 @@ msgstr "" msgid "Mother" msgstr "" +#. module: batch_liquidation_report +#: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_picking +msgid "Packaging Qty" +msgstr "" + #. module: batch_liquidation_report #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_picking msgid "Packaging" @@ -997,6 +997,11 @@ msgstr "" msgid "Sale Order Ticket" msgstr "" +#. module: batch_liquidation_report +#: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_picking +msgid "Second Driver:" +msgstr "" + #. module: batch_liquidation_report #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_fattening_cost msgid "Sobrante" diff --git a/batch_liquidation_report/i18n/es.po b/batch_liquidation_report/i18n/es.po index d4e604fcaf..5e87a1b08b 100644 --- a/batch_liquidation_report/i18n/es.po +++ b/batch_liquidation_report/i18n/es.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-09-25 11:11+0000\n" -"PO-Revision-Date: 2024-09-25 11:11+0000\n" +"POT-Creation-Date: 2024-11-15 08:58+0000\n" +"PO-Revision-Date: 2024-11-15 08:58+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -31,12 +31,13 @@ msgstr "'Tiquet - %s' % (object.name)" #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.external_layout_standard_picking_report msgid "" "- Page:\n" -" / \n" +" /\n" " " msgstr "" "- Página:\n" " / \n" " " + #. module: batch_liquidation_report #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.external_layout_standard_picking_report msgid "" @@ -125,11 +126,6 @@ msgstr "" msgid "Entry" msgstr "Entrada" -#. module: batch_liquidation_report -#: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_picking -msgid "Expiration" -msgstr "Caducidad" - #. module: batch_liquidation_report #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_fattening_cost msgid "Feed" @@ -183,6 +179,11 @@ msgstr "Medicación" msgid "Mother" msgstr "Madre" +#. module: batch_liquidation_report +#: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_picking +msgid "Packaging Qty" +msgstr "Cant. Paquetes" + #. module: batch_liquidation_report #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_picking msgid "Packaging" @@ -781,7 +782,7 @@ msgstr "Descripción" #: code:addons/batch_liquidation_report/report/stock_by_breeding_xlsx.py:0 #, python-format msgid "Difference" -msgstr "Diferencia" +msgstr "Deferencia" #. module: batch_liquidation_report #: model:ir.model.fields,field_description:batch_liquidation_report.field_ir_actions_report__display_name @@ -848,7 +849,7 @@ msgstr "Coste de engorde" #: code:addons/batch_liquidation_report/report/stock_by_breeding_xlsx.py:0 #, python-format msgid "Feed Amount" -msgstr "Importe FEEP" +msgstr "Importe pienso" #. module: batch_liquidation_report #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_liquidation_cost @@ -940,7 +941,7 @@ msgstr "Precio carne" #: code:addons/batch_liquidation_report/report/stock_by_breeding_xlsx.py:0 #, python-format msgid "Medicine Amount" -msgstr "Importe medicinas" +msgstr "Importe medicamentos" #. module: batch_liquidation_report #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_picking @@ -995,7 +996,7 @@ msgstr "Nº de albarán:" #. module: batch_liquidation_report #: model:ir.actions.report,name:batch_liquidation_report.action_report_picking msgid "Picking Report" -msgstr "Albarán PAASA" +msgstr "Albarán con paquetes" #. module: batch_liquidation_report #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_liquidation_cost @@ -1027,6 +1028,11 @@ msgstr "Informar acción" msgid "Sale Order Ticket" msgstr "Ticket" +#. module: batch_liquidation_report +#: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_picking +msgid "Second Driver:" +msgstr "Segundo conductor:" + #. module: batch_liquidation_report #: model_terms:ir.ui.view,arch_db:batch_liquidation_report.report_fattening_cost msgid "Sobrante" diff --git a/batch_liquidation_report/report/account_move_report.xml b/batch_liquidation_report/report/account_move_report.xml index 0256c3e4ad..0acd6f2b25 100644 --- a/batch_liquidation_report/report/account_move_report.xml +++ b/batch_liquidation_report/report/account_move_report.xml @@ -184,7 +184,7 @@ /> - + : -
Phone: - - / - +
Second Driver: + +
+
+ : +
License Plate Head: Client Ref:

-
- Picking Nº: -

-

@@ -237,17 +224,20 @@ - + - + @@ -255,14 +245,12 @@
Lot ExpirationContainer PackagingPackaging Qty Quantity
- + - + diff --git a/custom_breeding_apps/views/stock_move_line_view.xml b/custom_breeding_apps/views/stock_move_line_view.xml index c454c1ff4f..6c747da461 100644 --- a/custom_breeding_apps/views/stock_move_line_view.xml +++ b/custom_breeding_apps/views/stock_move_line_view.xml @@ -113,6 +113,7 @@ {'column_invisible':['|', ('parent.egg_production', '=', True), ('parent.picking_type_code', '=', 'outgoing')]} + show bottom - + 1 + and line.order_id.update_line_qty + and all([c.state == "done" for c in line.order_id.picking_ids]) + and all( + [ + c.product_qty == c.qty_received + for c in (line.order_id.order_line) + ] + ) + ): + line.order_id.update_line_qty = False + + def _create_or_update_picking(self): + for line in self: + if ( + line.product_id + and line.product_id.type in ("product", "consu") + and line.order_id.update_line_qty + and line.return_qty + ): + continue + else: + return super()._create_or_update_picking() diff --git a/custom_p/models/sale_order.py b/custom_p/models/sale_order.py index 9c525a6cc4..de931a4da2 100644 --- a/custom_p/models/sale_order.py +++ b/custom_p/models/sale_order.py @@ -16,10 +16,44 @@ class SaleOrder(models.Model): copy=False, ) + def button_confirm_pickings(self): + if self.auto_purchase_order_id: + for line in self.order_line: + if line.auto_purchase_line_id: + lot = self.env["stock.production.lot"] + if line.lot_id: + lot_name = line.lot_id.name + company = line.sudo().auto_purchase_line_id.company_id + product = line.product_id + lot_domain = [ + ("name", "=", lot_name), + ("company_id", "=", company.id), + ("product_id", "=", product.id), + ] + lot = lot.sudo().search(lot_domain) + if not lot: + lot = lot.sudo().action_create_lot( + product, lot_name, company + ) + line.auto_purchase_line_id.sudo().write( + { + "lot_id": lot.id or False, + "return_qty": line.return_qty, + } + ) + return super().button_confirm_pickings() + def button_return_picking(self): self.update_line_qty = True return super().button_return_picking() + def action_view_sale_lines(self): + result = super().action_view_sale_lines() + view_tree_id = self.env.ref("custom_p.view_order_line_tree").id + result["views"] = [(view_tree_id, "tree")] + result["view"] = view_tree_id + return result + def action_last_month_partner_sales(self): self.ensure_one() if not self.partner_id: diff --git a/custom_p/models/sale_order_line.py b/custom_p/models/sale_order_line.py index b22fb079b2..a9008456dc 100644 --- a/custom_p/models/sale_order_line.py +++ b/custom_p/models/sale_order_line.py @@ -1,5 +1,6 @@ # Copyright 2024 Berezi Amubieta - AvanzOSC # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from datetime import timedelta from odoo import api, fields, models @@ -26,6 +27,7 @@ def _compute_qty_delivered(self): and line.return_qty ): line.product_uom_qty = line.qty_delivered + line._compute_qty_amount_pending_delivery() if ( len(line.order_id.picking_ids) > 1 and line.order_id.update_line_qty @@ -62,3 +64,51 @@ def _compute_possible_lot_ids(self): ) lot_ids += lots line.possible_lot_ids = [(6, 0, lot_ids.ids)] + + @api.depends("invoice_lines.move_id.state", "invoice_lines.quantity") + def _get_invoice_qty(self): + super()._get_invoice_qty() + for line in self: + if line.invoice_lines.filtered(lambda c: c.out_refund_from_invoice): + qty_invoiced = 0.0 + for invoice_line in line.invoice_lines: + if invoice_line.move_id.state != "cancel": + if invoice_line.move_id.move_type == "out_invoice": + qty_invoiced += ( + invoice_line.product_uom_id._compute_quantity( + invoice_line.quantity, line.product_uom + ) + ) + elif ( + invoice_line.filtered( + lambda c: not c.out_refund_from_invoice + ).move_id.move_type + == "out_refund" + ): + qty_invoiced -= ( + invoice_line.product_uom_id._compute_quantity( + invoice_line.quantity, line.product_uom + ) + ) + line.qty_invoiced = qty_invoiced + + @api.model + def create(self, values): + if "customer_lead" not in values: + order = self.env["sale.order"].browse(values.get("order_id")) + product = self.env["product.product"].browse(values.get("product_id")) + values.update( + {"customer_lead": order._get_customer_lead(product.product_tmpl_id)} + ) + result = super(SaleOrderLine, self).create(values) + if not result.order_id.commitment_date: + order_date = fields.Datetime.from_string( + result.order_id.date_order + if result.order_id.date_order + and result.order_id.state in ["sale", "done"] + else fields.Datetime.now() + ) + result.order_id.commitment_date = order_date + timedelta( + days=result.customer_lead or 0.0 + ) + return result diff --git a/custom_p/models/stock_move_line.py b/custom_p/models/stock_move_line.py new file mode 100644 index 0000000000..263b97ed6c --- /dev/null +++ b/custom_p/models/stock_move_line.py @@ -0,0 +1,18 @@ +# Copyright 2024 Berezi Amubieta - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class StockMoveLine(models.Model): + _inherit = "stock.move.line" + + @api.model + def _prepare_stock_move_vals(self): + result = super()._prepare_stock_move_vals() + result.update( + { + "name": self.product_id.display_name, + } + ) + return result diff --git a/custom_p/models/stock_picking.py b/custom_p/models/stock_picking.py index ab7a9974e7..bc8acea380 100644 --- a/custom_p/models/stock_picking.py +++ b/custom_p/models/stock_picking.py @@ -6,6 +6,7 @@ class StockPicking(models.Model): _inherit = "stock.picking" + _order = "custom_date_done desc, priority desc, scheduled_date asc, id desc" def button_validate(self): for picking in self: diff --git a/custom_p/views/account_move_view.xml b/custom_p/views/account_move_view.xml index b353a7c6d8..f9baac4d70 100644 --- a/custom_p/views/account_move_view.xml +++ b/custom_p/views/account_move_view.xml @@ -9,4 +9,14 @@ + + + account.move + + + + + + + diff --git a/custom_p/views/hr_employee_view.xml b/custom_p/views/hr_employee_view.xml new file mode 100644 index 0000000000..2ff0c64813 --- /dev/null +++ b/custom_p/views/hr_employee_view.xml @@ -0,0 +1,12 @@ + + + + hr.employee + + + + + + + + diff --git a/custom_p/views/sale_order_line_view.xml b/custom_p/views/sale_order_line_view.xml new file mode 100644 index 0000000000..9588b0a6c2 --- /dev/null +++ b/custom_p/views/sale_order_line_view.xml @@ -0,0 +1,96 @@ + + + + sale.order.line + + primary + + + 0 + bottom + + + + 1 + + + + hide + + + hide + + + hide + + + + + + + + + + + hide + + + hide + + + hide + + + hide + + + hide + + + 1 + + + 1 + + + + + + + + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + + + + + + diff --git a/custom_p/views/stock_picking_view.xml b/custom_p/views/stock_picking_view.xml index c10fa2e18b..75bee3e493 100644 --- a/custom_p/views/stock_picking_view.xml +++ b/custom_p/views/stock_picking_view.xml @@ -7,6 +7,11 @@ + + {'readonly': [('state', '=', 'cancel')]} + diff --git a/custom_p/wizards/__init__.py b/custom_p/wizards/__init__.py index d7bf376da3..02e520b858 100644 --- a/custom_p/wizards/__init__.py +++ b/custom_p/wizards/__init__.py @@ -1,3 +1,4 @@ from . import sale_order_line_price_history_line from . import product_pricelist_print from . import stock_inventory_warning_wizard +from . import account_move_reversal diff --git a/custom_p/wizards/account_move_reversal.py b/custom_p/wizards/account_move_reversal.py new file mode 100644 index 0000000000..cb4086ac2b --- /dev/null +++ b/custom_p/wizards/account_move_reversal.py @@ -0,0 +1,18 @@ +# Copyright 2024 Berezi Amubieta - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class AccountMoveReversal(models.TransientModel): + _inherit = "account.move.reversal" + + def reverse_moves(self): + result = super().reverse_moves() + model = result.get("res_model") + if model == "account.move": + id = result.get("res_id") + move = self.env[model].browse(id) + for line in move.invoice_line_ids: + line.out_refund_from_invoice = True + return result diff --git a/custom_purchase_import_wizard/i18n/custom_purchase_import_wizard.pot b/custom_purchase_import_wizard/i18n/custom_purchase_import_wizard.pot index 7dfea14b69..a7ecb4e143 100644 --- a/custom_purchase_import_wizard/i18n/custom_purchase_import_wizard.pot +++ b/custom_purchase_import_wizard/i18n/custom_purchase_import_wizard.pot @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-08-28 09:35+0000\n" -"PO-Revision-Date: 2023-08-28 09:35+0000\n" +"POT-Creation-Date: 2024-10-22 06:20+0000\n" +"PO-Revision-Date: 2024-10-22 06:20+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -177,6 +177,7 @@ msgstr "" #. module: custom_purchase_import_wizard #: code:addons/custom_purchase_import_wizard/models/purchase_order_import.py:0 +#: code:addons/custom_purchase_import_wizard/models/purchase_order_import.py:0 #, python-format msgid "Error: More than one shipping method found." msgstr "" @@ -211,6 +212,7 @@ msgstr "" #. module: custom_purchase_import_wizard #: code:addons/custom_purchase_import_wizard/models/purchase_order_import.py:0 +#: code:addons/custom_purchase_import_wizard/models/purchase_order_import.py:0 #, python-format msgid "Error: No shipping method found." msgstr "" @@ -355,7 +357,8 @@ msgstr "" #. module: custom_purchase_import_wizard #: model:ir.actions.act_window,name:custom_purchase_import_wizard.purchase_order_import_action -#: model:ir.ui.menu,name:custom_purchase_import_wizard.purchase_order_import_menu +#: model:ir.ui.menu,name:custom_purchase_import_wizard.purchase_order_import_menu_config +#: model:ir.ui.menu,name:custom_purchase_import_wizard.purchase_order_import_menu_purchase msgid "Import Purchase Orders" msgstr "" @@ -451,11 +454,6 @@ msgstr "" msgid "Next Activity Type" msgstr "" -#. module: custom_purchase_import_wizard -#: model:ir.model.fields.selection,name:custom_purchase_import_wizard.selection__purchase_order_import_line__action__nothing -msgid "Nothing" -msgstr "" - #. module: custom_purchase_import_wizard #: model:ir.model.fields,field_description:custom_purchase_import_wizard.field_purchase_order_import__message_needaction_counter msgid "Number of Actions" @@ -577,6 +575,11 @@ msgstr "" msgid "Shipping Method" msgstr "" +#. module: custom_purchase_import_wizard +#: model:ir.model.fields,field_description:custom_purchase_import_wizard.field_purchase_order_import__split_size +msgid "Split Size" +msgstr "" + #. module: custom_purchase_import_wizard #: model:ir.model.fields,field_description:custom_purchase_import_wizard.field_purchase_order_import__state #: model:ir.model.fields,field_description:custom_purchase_import_wizard.field_purchase_order_import_line__state diff --git a/custom_purchase_import_wizard/i18n/es.po b/custom_purchase_import_wizard/i18n/es.po index 8e0ebc1452..6fc97fa87f 100644 --- a/custom_purchase_import_wizard/i18n/es.po +++ b/custom_purchase_import_wizard/i18n/es.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-08-28 09:36+0000\n" -"PO-Revision-Date: 2023-08-28 09:36+0000\n" +"POT-Creation-Date: 2024-10-22 06:21+0000\n" +"PO-Revision-Date: 2024-10-22 06:21+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -180,6 +180,7 @@ msgstr "" #. module: custom_purchase_import_wizard #: code:addons/custom_purchase_import_wizard/models/purchase_order_import.py:0 +#: code:addons/custom_purchase_import_wizard/models/purchase_order_import.py:0 #, python-format msgid "Error: More than one shipping method found." msgstr "Error: Más de un método de envío encontrado." @@ -203,7 +204,7 @@ msgstr "Error: Se ha encontrado más de un almacén con nombre {}." #: code:addons/custom_purchase_import_wizard/models/purchase_order_import.py:0 #, python-format msgid "Error: No picking type found." -msgstr "Error: No se ha encontrado ningún tipo de operación." +msgstr "Error: Tipo de operación no encontrado." #. module: custom_purchase_import_wizard #: code:addons/custom_purchase_import_wizard/models/purchase_order_import.py:0 @@ -214,6 +215,7 @@ msgstr "Error: Producto no encontrado." #. module: custom_purchase_import_wizard #: code:addons/custom_purchase_import_wizard/models/purchase_order_import.py:0 +#: code:addons/custom_purchase_import_wizard/models/purchase_order_import.py:0 #, python-format msgid "Error: No shipping method found." msgstr "Error: Método de envío no encontrado." @@ -359,7 +361,8 @@ msgstr "Importar líneas de pedido de compra" #. module: custom_purchase_import_wizard #: model:ir.actions.act_window,name:custom_purchase_import_wizard.purchase_order_import_action -#: model:ir.ui.menu,name:custom_purchase_import_wizard.purchase_order_import_menu +#: model:ir.ui.menu,name:custom_purchase_import_wizard.purchase_order_import_menu_config +#: model:ir.ui.menu,name:custom_purchase_import_wizard.purchase_order_import_menu_purchase msgid "Import Purchase Orders" msgstr "Importar pedidos de compra" @@ -455,11 +458,6 @@ msgstr "Resumen de la siguiente actividad" msgid "Next Activity Type" msgstr "Resumen de la siguiente actividad" -#. module: custom_purchase_import_wizard -#: model:ir.model.fields.selection,name:custom_purchase_import_wizard.selection__purchase_order_import_line__action__nothing -msgid "Nothing" -msgstr "Nada" - #. module: custom_purchase_import_wizard #: model:ir.model.fields,field_description:custom_purchase_import_wizard.field_purchase_order_import__message_needaction_counter msgid "Number of Actions" @@ -581,6 +579,11 @@ msgstr "Coste de envío" msgid "Shipping Method" msgstr "Método de envío" +#. module: custom_purchase_import_wizard +#: model:ir.model.fields,field_description:custom_purchase_import_wizard.field_purchase_order_import__split_size +msgid "Split Size" +msgstr "" + #. module: custom_purchase_import_wizard #: model:ir.model.fields,field_description:custom_purchase_import_wizard.field_purchase_order_import__state #: model:ir.model.fields,field_description:custom_purchase_import_wizard.field_purchase_order_import_line__state diff --git a/custom_purchase_import_wizard/views/purchase_order_import_line_views.xml b/custom_purchase_import_wizard/views/purchase_order_import_line_views.xml index c28e24a64e..ef2f3ede1d 100644 --- a/custom_purchase_import_wizard/views/purchase_order_import_line_views.xml +++ b/custom_purchase_import_wizard/views/purchase_order_import_line_views.xml @@ -154,12 +154,12 @@ diff --git a/custom_purchase_import_wizard/views/purchase_order_import_views.xml b/custom_purchase_import_wizard/views/purchase_order_import_views.xml index d9949133a5..ab77b8ce42 100644 --- a/custom_purchase_import_wizard/views/purchase_order_import_views.xml +++ b/custom_purchase_import_wizard/views/purchase_order_import_views.xml @@ -173,14 +173,14 @@ `_. In case of trouble, +please check there if your issue has already been reported. If you spotted +it first, help us smash it by providing detailed and welcomed feedback. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Contributors +------------ + +* Berezi Amubieta +* Ana Juaristi diff --git a/custom_stock_orderpoint_qty/__init__.py b/custom_stock_orderpoint_qty/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/custom_stock_orderpoint_qty/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/custom_stock_orderpoint_qty/__manifest__.py b/custom_stock_orderpoint_qty/__manifest__.py new file mode 100644 index 0000000000..f3dc0d4f20 --- /dev/null +++ b/custom_stock_orderpoint_qty/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2025 Berezi Amubieta - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Custom Stock Orderpoint Qty", + "version": "14.0.1.0.0", + "category": "Stock", + "license": "AGPL-3", + "author": "AvanzOSC", + "website": "https://github.com/avanzosc/odoo-addons", + "depends": [ + "stock", + ], + "data": [ + "data/ir_cron_data.xml", + ], + "installable": True, +} diff --git a/custom_stock_orderpoint_qty/data/ir_cron_data.xml b/custom_stock_orderpoint_qty/data/ir_cron_data.xml new file mode 100644 index 0000000000..f7ae7a5ad0 --- /dev/null +++ b/custom_stock_orderpoint_qty/data/ir_cron_data.xml @@ -0,0 +1,14 @@ + + + + Recalculate qty to order in orderpoint + + code + model._compute_qty_to_order() + + 1 + days + -1 + True + + diff --git a/custom_stock_orderpoint_qty/models/__init__.py b/custom_stock_orderpoint_qty/models/__init__.py new file mode 100644 index 0000000000..84144d1d6c --- /dev/null +++ b/custom_stock_orderpoint_qty/models/__init__.py @@ -0,0 +1 @@ +from . import stock_orderpoint diff --git a/custom_stock_orderpoint_qty/models/stock_orderpoint.py b/custom_stock_orderpoint_qty/models/stock_orderpoint.py new file mode 100644 index 0000000000..2eb0cca4d3 --- /dev/null +++ b/custom_stock_orderpoint_qty/models/stock_orderpoint.py @@ -0,0 +1,9 @@ +# Copyright 2025 Berezi Amubieta - AvanzOSC +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from odoo import fields, models + + +class StockOrderpoint(models.Model): + _inherit = "stock.warehouse.orderpoint" + + qty_to_order = fields.Float(compute=False) diff --git a/fleet_extension/models/fleet_vehicle.py b/fleet_extension/models/fleet_vehicle.py index 4688bf162a..fa071a1779 100644 --- a/fleet_extension/models/fleet_vehicle.py +++ b/fleet_extension/models/fleet_vehicle.py @@ -23,9 +23,7 @@ class FleetVehicle(models.Model): house_number = fields.Char(string="House Number") upholstery = fields.Char(string="Upholstery") furniture = fields.Char(string="Furniture") - mam = fields.Integer( - string="Maximum authorized mass", related="product_id.mam", store=True - ) + mam = fields.Integer(string="Maximum Authorized Mass") purchase_price = fields.Float(string="Purchase price") retail_price = fields.Float(string="Retail price") promotion_price = fields.Float(string="Promotion price") @@ -70,6 +68,7 @@ def onchange_product_id(self): self.chassis_model_id = self.product_id.chassis_model_id self.displacement = self.product_id.displacement self.horsepower = self.product_id.horsepower + self.mam = self.product_id.mam @api.model def name_search(self, name="", args=None, operator="ilike", limit=100): diff --git a/mail_chatter_statistics/README.rst b/mail_chatter_statistics/README.rst new file mode 100644 index 0000000000..35fdf60fa0 --- /dev/null +++ b/mail_chatter_statistics/README.rst @@ -0,0 +1,75 @@ +.. image:: https://img.shields.io/badge/license-LGPL--3-blue.svg + :target: https://opensource.org/licenses/LGPL-3.0 + :alt: License: LGPL-3 + +======================= +Mail Chatter Statistics +======================= + +Overview +======== + +The **Mail Chatter Statistics** module enhances Odoo's email tracking capabilities by providing features to monitor email opens and clicks. It allows users to track the engagement of their sent emails through unique tracking links embedded in the content. + +Features +======== + +- **Open Tracking**: Records when a recipient opens an email by loading a tracking image. + +- **Click Tracking**: Monitors when a recipient clicks on links within the email, updating the tracking information accordingly. + +- **Integration with Odoo Chatter**: Displays mailing statistics directly within the Odoo message interface. + +Usage +===== + +1. **Install the Module**: + + - Install the module via Odoo's Apps interface. + +2. **Sending Emails**: + + - When sending emails through the Odoo mail interface, the tracking functionality is automatically applied. + +3. **Viewing Statistics**: + + - Open any message in the Odoo chatter to see the tracking statistics, including open and click rates. + +Configuration +============= + +No additional configuration is required. The module is ready to use once installed. + +Testing +======= + +Test the following scenarios: + +- **Email Open Tracking**: + + - Send an email and open it to verify that the open is logged correctly. + +- **Email Click Tracking**: + + - Click on a link within the email and ensure the click is registered in the tracking information. + +Bug Tracker +=========== + +If you encounter any issues, please report them on the GitHub repository at `GitHub Issues `_. + +Credits +======= + +Contributors +------------ + +* Unai Beristain +* Ana Juaristi + +For module-specific questions, please contact the contributors directly. Support requests should be made through the official channels. + +License +======= + +This project is licensed under the LGPL-3 License. For more details, please refer to the LICENSE file or visit . diff --git a/mail_chatter_statistics/__init__.py b/mail_chatter_statistics/__init__.py new file mode 100644 index 0000000000..91c5580fed --- /dev/null +++ b/mail_chatter_statistics/__init__.py @@ -0,0 +1,2 @@ +from . import controllers +from . import models diff --git a/mail_chatter_statistics/__manifest__.py b/mail_chatter_statistics/__manifest__.py new file mode 100644 index 0000000000..c88af3334a --- /dev/null +++ b/mail_chatter_statistics/__manifest__.py @@ -0,0 +1,16 @@ +{ + "name": "Mail Chatter Statistics", + "version": "14.0.1.0.0", + "author": "Avanzosc", + "summary": "Add email tracking functionality to Odoo chatter.", + "website": "https://github.com/avanzosc/odoo-addons", + "license": "LGPL-3", + "depends": ["mail", "mass_mailing"], + "data": [ + "views/assets.xml", + ], + "qweb": ["static/src/xml/chatter_inherit.xml"], + "installable": True, + "application": False, + "external_dependencies": {"python": ["bs4"]}, +} diff --git a/mail_chatter_statistics/controllers/__init__.py b/mail_chatter_statistics/controllers/__init__.py new file mode 100644 index 0000000000..352326ebe3 --- /dev/null +++ b/mail_chatter_statistics/controllers/__init__.py @@ -0,0 +1 @@ +from . import mail_track diff --git a/mail_chatter_statistics/controllers/mail_track.py b/mail_chatter_statistics/controllers/mail_track.py new file mode 100644 index 0000000000..d5d0db5ccf --- /dev/null +++ b/mail_chatter_statistics/controllers/mail_track.py @@ -0,0 +1,57 @@ +import base64 +import logging +from io import BytesIO + +from PIL import Image + +from odoo import http +from odoo.http import request + +from odoo.addons.mail.controllers.main import MailController + +_logger = logging.getLogger(__name__) + + +class MailController(MailController): + @http.route() + def mail_action_view(self, model=None, res_id=None, access_token=None, **kwargs): + response = super(MailController, self).mail_action_view( + model, res_id, access_token, **kwargs + ) + + if kwargs.get("mail_chatter_statistics"): + trace_id = kwargs.get("mail_chatter_statistics_trace_id") + if trace_id: + _logger.info("Tracking click for trace ID: %s", trace_id) + trace = ( + request.env["mailing.trace"] + .sudo() + .search([("id", "=", trace_id)], limit=1) + ) + if trace: + trace.track_click() + + return response + + +class MailTrackingController(http.Controller): + @http.route("/mail/track/open/", type="http", auth="public") + def track_open(self, trace_id): + _logger.info("Tracking open for trace ID: %s", trace_id) + trace = ( + request.env["mailing.trace"].sudo().search([("id", "=", trace_id)], limit=1) + ) + + if trace: + trace.track_open() + + image = Image.new("RGB", (1, 1), color="white") + img_byte_arr = BytesIO() + image.save(img_byte_arr, format="PNG") + img_byte_arr.seek(0) + + img_base64 = base64.b64encode(img_byte_arr.getvalue()).decode() + + return http.Response( + f"data:image/png;base64,{img_base64}", content_type="image/png" + ) diff --git a/mail_chatter_statistics/models/__init__.py b/mail_chatter_statistics/models/__init__.py new file mode 100644 index 0000000000..5aff43109d --- /dev/null +++ b/mail_chatter_statistics/models/__init__.py @@ -0,0 +1,3 @@ +from . import mail_mail +from . import mail_message +from . import mailing_trace diff --git a/mail_chatter_statistics/models/mail_mail.py b/mail_chatter_statistics/models/mail_mail.py new file mode 100644 index 0000000000..a354bc551d --- /dev/null +++ b/mail_chatter_statistics/models/mail_mail.py @@ -0,0 +1,95 @@ +import logging +from urllib.parse import parse_qs, urlencode, urlparse, urlunparse + +from bs4 import BeautifulSoup + +from odoo import fields, models + +_logger = logging.getLogger(__name__) + + +class MailMail(models.Model): + _inherit = "mail.mail" + + def send(self, auto_commit=False, raise_exception=False): + for mail in self: + if mail.mail_message_id and (mail.email_to or mail.recipient_ids): + trace = self._create_tracking_trace(mail) + mail.body_html = self._add_tracking(mail.body_html, trace.id) + + return super().send(auto_commit=auto_commit, raise_exception=raise_exception) + + def write(self, vals): + """Check is_bounced when mail state is changed""" + bounced_mails = self.filtered( + lambda mail: mail.state != "exception" and vals.get("state") == "exception" + ) + result = super(MailMail, self).write(vals) + + for mail in bounced_mails: + mail_message = mail.mail_message_id + if mail_message: + mail_message.is_bounced() + return result + + def _create_tracking_trace(self, mail): + email = mail.email_to or self._get_first_recipient_email(mail.recipient_ids) + tracking_data = { + "mail_message_id": mail.mail_message_id.id + if mail.mail_message_id + else False, + "email": email if email else "", + "message_id": mail.message_id, + "status": "tracking_added", + "sent_at": fields.Datetime.now(), + } + return self.env["mailing.trace"].create(tracking_data) + + def _get_first_recipient_email(self, recipient_ids): + if recipient_ids: + first_recipient = self.env["res.partner"].browse(recipient_ids.ids[:1]) + return first_recipient.email + return None + + def _add_tracking(self, body_html, trace_id): + base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url") + + tracking_pixel = f'' + + last_div_index = body_html.rfind("") + if last_div_index != -1: + body_html = ( + body_html[:last_div_index] + tracking_pixel + body_html[last_div_index:] + ) + + return self._replace_links_with_tracked(body_html, trace_id) + + def _replace_links_with_tracked(self, body_html, trace_id): + soup = BeautifulSoup(body_html, "html.parser") + + for a_tag in soup.find_all("a", href=True): + original_url = a_tag["href"] + + if "/mail/view" in original_url: + parsed_url = urlparse(original_url) + query_params = parse_qs(parsed_url.query) + + query_params["mail_chatter_statistics"] = ["True"] + query_params["mail_chatter_statistics_trace_id"] = [trace_id] + + new_query = urlencode(query_params, doseq=True) + tracked_url = urlunparse( + ( + parsed_url.scheme, + parsed_url.netloc, + parsed_url.path, + parsed_url.params, + new_query, + parsed_url.fragment, + ) + ) + + a_tag["href"] = tracked_url + + return str(soup) diff --git a/mail_chatter_statistics/models/mail_message.py b/mail_chatter_statistics/models/mail_message.py new file mode 100644 index 0000000000..c55fa80b11 --- /dev/null +++ b/mail_chatter_statistics/models/mail_message.py @@ -0,0 +1,85 @@ +from datetime import datetime + +from odoo import api, fields, models, tools + + +class MailMessage(models.Model): + _inherit = "mail.message" + + mailing_trace_ids = fields.One2many( + comodel_name="mailing.trace", + inverse_name="mail_message_id", + string="Mailing Traces", + help="Mailing traces related to this message.", + ) + + @api.onchange("parent_id") + def _onchange_parent_id_update_replied(self): + """ + Detects if the current message is a reply (has a `parent_id`), + and updates `replied_at` in the associated `mailing.trace` for the original message. + """ + if self.parent_id: + mailing_trace = self.env["mailing.trace"].search( + [("mail_message_id", "=", self.parent_id.id)], limit=1 + ) + if mailing_trace: + mailing_trace.replied_at = datetime.now() + + def is_bounced(self): + """ + Checks if the message has been rejected or bounced. + If so, updates the `bounced_at` field in the `mailing.trace`. + Returns True if the message is in `exception` state in mail.mail. + """ + mail_mail = self.env["mail.mail"].search( + [("mail_message_id", "=", self.id), ("state", "=", "exception")], limit=1 + ) + + if mail_mail: + mailing_trace = self.env["mailing.trace"].search( + [("mail_message_id", "=", self.id)], limit=1 + ) + if mailing_trace: + mailing_trace.bounced_at = datetime.now() + return True + return False + + @api.model + def message_process_incoming(self, message, message_dict): + super(MailMessage, self).message_process_incoming(message, message_dict) + + thread_references = message_dict.get("references") or message_dict.get( + "in_reply_to" + ) + msg_references = ( + tools.mail_header_msgid_re.findall(thread_references) + if thread_references + else [] + ) + + if msg_references: + self.env["mailing.trace"].set_opened(mail_message_ids=msg_references) + self.env["mailing.trace"].set_replied(mail_message_ids=msg_references) + + @api.model + def get_mailing_trace(self, mail_message_id): + if mail_message_id: + mail_message_id = int(mail_message_id) + mailing_trace = self.env["mailing.trace"].search( + [("mail_message_id", "=", mail_message_id)], limit=1 + ) + + if mailing_trace: + return mailing_trace.read( + [ + "id", + "sent_at", + "clicked_at", + "opened_at", + "bounced_at", + "replied_at", + "user_agent", + ] + )[0] + return False diff --git a/mail_chatter_statistics/models/mailing_trace.py b/mail_chatter_statistics/models/mailing_trace.py new file mode 100644 index 0000000000..e8af4a67c8 --- /dev/null +++ b/mail_chatter_statistics/models/mailing_trace.py @@ -0,0 +1,173 @@ +import logging + +from odoo import api, fields, models + +_logger = logging.getLogger(__name__) + + +class MailingTrace(models.Model): + _inherit = "mailing.trace" + + mail_message_id = fields.Many2one( + "mail.message", + string="Chatter Message", + help="The chatter message related to this email trace.", + ) + + mail_message_id_int = fields.Integer( + string="Chatter ID (tech)", + help="ID of the related mail_message. This field is an integer field because " + "the related mail_message can be deleted separately from its statistics. " + "However, the ID is needed for several actions and controllers.", + index=True, + ) + + email = fields.Char(string="Email") + message_id = fields.Char(string="Message ID") + + status = fields.Selection( + [ + ("draft", "Draft"), + ("tracking_added", "Tracking Added"), + ("sent", "Sent"), + ("clicked", "Clicked"), + ("opened", "Opened"), + ("bounced", "Bounced"), + ("replied", "Replied"), + ], + string="Status", + default="draft", + ) + + sent_at = fields.Datetime(string="Sent At") + clicked_at = fields.Datetime(string="Clicked At") + opened_at = fields.Datetime(string="Opened At") + bounced_at = fields.Datetime(string="Bounced At", computed="_compute_bounced_at") + replied_at = fields.Datetime(string="Replied At") + user_agent = fields.Char(string="User Agent") + + @api.model_create_multi + def create(self, values_list): + for values in values_list: + if "mail_message_id" in values: + values["mail_message_id_int"] = values["mail_message_id"] + return super(MailingTrace, self).create(values_list) + + def _get_records(self, mail_mail_ids=None, mail_message_ids=None, domain=None): + base_domain = [] + if not self.ids and mail_mail_ids: + base_domain = [("mail_message_id_int", "in", mail_mail_ids)] + elif not self.ids and mail_message_ids: + base_domain = [("mail_message_id_int", "in", mail_message_ids)] + else: + base_domain = [("id", "in", self.ids)] + + if domain: + base_domain = ["&"] + domain + base_domain + + return self.search(base_domain) + + @api.model + def get_chatter_id(self, model_name, record_id): + _logger.info( + "Getting Chatter Message ID for model: %s, record ID: %s", + model_name, + record_id, + ) + + record_id = int(record_id) + if not record_id: + _logger.warning("Record ID is not an integer: %s", record_id) + return {"chatter_message_id": None, "mailing_trace_ids": []} + + record = self.env[model_name].sudo().browse(record_id) + + if record.exists(): + _logger.info("Record found: %s", record) + + chatter_messages = self.env["mail.message"].search( + [("res_id", "=", record_id), ("model", "=", model_name)] + ) + message_trace_mapping = {} + + for message in chatter_messages: + mailing_trace_ids = message.mailing_trace_ids.ids + message_trace_mapping[message.id] = mailing_trace_ids + + if not mailing_trace_ids: + _logger.warning( + "No Mailing Trace IDs found for Chatter Message ID: %s", + message.id, + ) + + return message_trace_mapping + else: + _logger.warning( + "Record not found for model: %s, ID: %s", model_name, record_id + ) + return {"chatter_message_id": None, "mailing_trace_ids": []} + + def track_open(self): + self.status = "opened" + self.opened_at = fields.Datetime.now() + + def track_click(self): + self.status = "clicked" + self.clicked_at = fields.Datetime.now() + + @api.depends("mail_message_id.mail_notification_ids.notification_status") + def _compute_bounced_at(self): + for record in self: + notifications = record.mail_message_id.mail_notification_ids.sorted( + key=lambda n: n.create_date, reverse=True + ) + if notifications and notifications[0].notification_status == "bounced": + record.bounced_at = fields.Datetime.now() + else: + record.bounced_at = False + + def track_reply(self): + self.status = "replied" + self.replied_at = fields.Datetime.now() + + @api.model + def default_get(self, fields): + res = super(MailingTrace, self).default_get(fields) + res.update( + { + "sent_at": self.replied, + "clicked_at": self.clicked, + "opened_at": self.opened, + "bounced_at": self.bounced, + "replied_at": self.replied, + } + ) + return res + + @api.onchange("replied", "clicked", "opened", "bounced", "invoiced") + def _onchange_dates(self): + self.sent_at = self.replied + self.clicked_at = self.clicked + self.opened_at = self.opened + self.bounced_at = self.bounced + self.replied_at = self.replied + + def set_opened(self, mail_mail_ids=None, mail_message_ids=None): + super(MailingTrace, self).set_opened(mail_mail_ids, mail_message_ids) + if mail_message_ids: + self.mail_message_id = mail_message_ids[0] + + def set_clicked(self, mail_mail_ids=None, mail_message_ids=None): + super(MailingTrace, self).set_clicked(mail_mail_ids, mail_message_ids) + if mail_message_ids: + self.mail_message_id = mail_message_ids[0] + + def set_replied(self, mail_mail_ids=None, mail_message_ids=None): + super(MailingTrace, self).set_replied(mail_mail_ids, mail_message_ids) + if mail_message_ids: + self.mail_message_id = mail_message_ids[0] + + def set_bounced(self, mail_mail_ids=None, mail_message_ids=None): + super(MailingTrace, self).set_bounced(mail_mail_ids, mail_message_ids) + if mail_message_ids: + self.mail_message_id = mail_message_ids[0] diff --git a/mail_chatter_statistics/static/src/js/main.js b/mail_chatter_statistics/static/src/js/main.js new file mode 100644 index 0000000000..07a4635da5 --- /dev/null +++ b/mail_chatter_statistics/static/src/js/main.js @@ -0,0 +1,118 @@ +odoo.define("mail_chatter_statistics.main", function (require) { + "use strict"; + + const rpc = require("web.rpc"); + + const Message = require("mail/static/src/components/message/message.js"); + + let statisticsInitialized = false; + + const OriginalMounted = Message.prototype.mounted; + const OriginalUpdated = Message.prototype.updated; + const OriginalBeforeUpdate = Message.prototype.beforeUpdate; + + async function updateMailingTrace() { + const parentGroups = document.querySelectorAll( + `div[role="group"][data-message-local-id^="mail.message_"]` + ); + if (parentGroups.length) { + for (const parentGroup of parentGroups) { + const dataMessageLocalId = parentGroup.getAttribute("data-message-local-id"); + const mailMessageId = dataMessageLocalId?.split("_")[1]; + + if (mailMessageId) { + try { + const mailingTrace = await this.getMailingTrace(mailMessageId); + updateMailingTraceInDOM(mailingTrace, parentGroup); + } catch (error) { + console.error("Error fetching mailing trace:", error); + } + } else { + console.warn( + "No valid mailMessageId found in data-message-local-id:", + dataMessageLocalId + ); + } + } + } + } + + Message.prototype.mounted = async function () { + if (!statisticsInitialized) { + statisticsInitialized = true; + await updateMailingTrace.call(this); + } + + if (OriginalMounted) { + OriginalMounted.call(this); + } + }; + + Message.prototype.updated = async function () { + await updateMailingTrace.call(this); + if (OriginalUpdated) { + OriginalUpdated.call(this); + } + }; + + Message.prototype.beforeUpdate = async function () { + await updateMailingTrace.call(this); + if (OriginalBeforeUpdate) { + OriginalBeforeUpdate.call(this); + } + }; + + Message.prototype.getMailingTrace = function (mailMessageId) { + return rpc + .query({ + model: "mail.message", + method: "get_mailing_trace", + args: [mailMessageId], + }) + .then(function (result) { + return result; + }) + .catch(function (error) { + console.error("Error al obtener el mailing trace:", error); + return {}; + }); + }; + + function updateMailingTraceInDOM(mailingTrace, parentGroup) { + const row = parentGroup.querySelector(".your-mailing-trace-ids-selector .row"); + + if (row) { + row.children[0].innerHTML = + mailingTrace && mailingTrace.sent_at + ? createHoverText(mailingTrace.sent_at) + : ""; + row.children[1].innerHTML = + mailingTrace && mailingTrace.clicked_at + ? createHoverText(mailingTrace.clicked_at) + : ""; + row.children[2].innerHTML = + mailingTrace && mailingTrace.opened_at + ? createHoverText(mailingTrace.opened_at) + : ""; + row.children[3].innerHTML = + mailingTrace && mailingTrace.bounced_at + ? createHoverText(mailingTrace.bounced_at) + : ""; + row.children[4].innerHTML = + mailingTrace && mailingTrace.replied_at + ? createHoverText(mailingTrace.replied_at) + : ""; + } + } + + function createHoverText(dateTime) { + const date = new Date(dateTime); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + const fullDate = date.toLocaleDateString(); // Get the full date + + return ` + ${hours}:${minutes} + `; + } +}); diff --git a/mail_chatter_statistics/static/src/xml/chatter_inherit.xml b/mail_chatter_statistics/static/src/xml/chatter_inherit.xml new file mode 100644 index 0000000000..ad95f27e11 --- /dev/null +++ b/mail_chatter_statistics/static/src/xml/chatter_inherit.xml @@ -0,0 +1,97 @@ + + + + +
+ + + + + + +
+
+
+ Sent +
+
+ Clicked +
+
+ Opened +
+
+ Bounced +
+
+ Replied +
+
+ + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
diff --git a/mail_chatter_statistics/views/assets.xml b/mail_chatter_statistics/views/assets.xml new file mode 100644 index 0000000000..bdd1a58a45 --- /dev/null +++ b/mail_chatter_statistics/views/assets.xml @@ -0,0 +1,12 @@ + + +