diff --git a/sale_expense_manual_reinvoice/README.rst b/sale_expense_manual_reinvoice/README.rst new file mode 100644 index 000000000..dcc078852 --- /dev/null +++ b/sale_expense_manual_reinvoice/README.rst @@ -0,0 +1,120 @@ +============================== +Sale Expense Manual Re-invoice +============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:0d6172b9d26cea112d5485883c19dcddc2045a837c168e1668c95f120941e63f + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| 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 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--expense-lightgray.png?logo=github + :target: https://github.com/OCA/hr-expense/tree/16.0/sale_expense_manual_reinvoice + :alt: OCA/hr-expense +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr-expense-16-0/hr-expense-16-0-sale_expense_manual_reinvoice + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/hr-expense&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +With this module Expense products can be configured to not reinvoice expenses +automatically. Instead, they will be listed in a new menu **Expenses to Reinvoice** +for a manager to manually review them and either reinvoice or discard them. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +On the **Expense Product**, set the **Re-invoice Mode**: + +* Automatically: Expenses are automatically re-invoiced when they're posted (standard) +* Manually: Expenses have to be manually re-invoiced by a manager + + +.. image:: https://raw.githubusercontent.com/OCA/hr-expense/16.0/sale_expense_manual_reinvoice/static/description/configure.png + +Usage +===== + +After Expenses of products configured with **Manual** Re-invoice Mode have been +approved and posted, find them under the **Expenses to Reinvoice** menu. + +Review their **Customer to reinvoice** field, select the ones you want to process +and click either **Reinvoice** or **Discard** + +.. image:: https://raw.githubusercontent.com/OCA/hr-expense/16.0/sale_expense_manual_reinvoice/static/description/reinvoice.png + +Discarded expenses will be hidden from the list, but you can still access them by +removing the default search filter. + +Known issues / Roadmap +====================== + +Some abstraction could be done to have the same mechanism work for all reinvoiceable +analytic lines, like the ones generated from purchase invoices. + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Camptocamp SA + +Contributors +~~~~~~~~~~~~ + +* `Camptocamp `_ + + * Iván Todorovich +* MarwanBHL + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-ivantodorovich| image:: https://github.com/ivantodorovich.png?size=40px + :target: https://github.com/ivantodorovich + :alt: ivantodorovich + +Current `maintainer `__: + +|maintainer-ivantodorovich| + +This module is part of the `OCA/hr-expense `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_expense_manual_reinvoice/__init__.py b/sale_expense_manual_reinvoice/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/sale_expense_manual_reinvoice/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/sale_expense_manual_reinvoice/__manifest__.py b/sale_expense_manual_reinvoice/__manifest__.py new file mode 100644 index 000000000..e8abf6548 --- /dev/null +++ b/sale_expense_manual_reinvoice/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2021 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Sale Expense Manual Re-invoice", + "summary": "Allow to manually re-invoice expenses", + "version": "16.0.1.0.0", + "author": "Camptocamp SA, Odoo Community Association (OCA)", + "maintainers": ["ivantodorovich"], + "website": "https://github.com/OCA/hr-expense", + "license": "AGPL-3", + "category": "Human Resources", + "depends": ["sale_expense"], + "data": [ + "views/account_analytic_line.xml", + "views/hr_expense.xml", + "views/product_template.xml", + ], +} diff --git a/sale_expense_manual_reinvoice/i18n/es.po b/sale_expense_manual_reinvoice/i18n/es.po new file mode 100644 index 000000000..1d25b6d41 --- /dev/null +++ b/sale_expense_manual_reinvoice/i18n/es.po @@ -0,0 +1,162 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_expense_manual_reinvoice +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-10-30 19:36+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_account_analytic_line +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__analytic_line_ids +msgid "Analytic Line" +msgstr "Línea Analítica" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Are you sure you don't want to reinvoice these expenses?" +msgstr "¿Está seguro de que no quiere volver a facturar estos gastos?" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields.selection,name:sale_expense_manual_reinvoice.selection__product_template__expense_mode__auto +msgid "Automatically" +msgstr "Automáticamente" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_product_product__expense_mode +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_product_template__expense_mode +msgid "" +"Choose how to re-invoice expenses:\n" +"\n" +"* Automatically: Expenses are automatically re-invoiced when they're posted.\n" +"* Manually: Expenses have to be manually re-invoiced by a manager.\n" +msgstr "" +"Elija cómo refacturar los gastos:\n" +"\n" +"* Automáticamente: Los gastos se refacturan automáticamente cuando se " +"contabilizan.\n" +"* Manualmente: Los gastos deben ser refacturados manualmente por un gestor.\n" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Discard" +msgstr "Descartar" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_hr_expense +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__expense_id +msgid "Expense" +msgstr "Gasto" + +#. module: sale_expense_manual_reinvoice +#: code:addons/sale_expense_manual_reinvoice/models/account_analytic_line.py:0 +#, python-format +msgid "Expense already re-invoiced." +msgstr "Gasto ya refacturado." + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_account_analytic_line__expense_id +msgid "Expense where the move line come from" +msgstr "Gasto del que procede la línea de movimiento" + +#. module: sale_expense_manual_reinvoice +#: model:ir.actions.act_window,name:sale_expense_manual_reinvoice.action_hr_expense_to_reinvoice +#: model:ir.ui.menu,name:sale_expense_manual_reinvoice.menu_hr_expense_to_reinvoice +msgid "Expenses to Reinvoice" +msgstr "Gastos para Refacturar" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_account_move_line +msgid "Journal Item" +msgstr "Artículo Diario" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__manual_reinvoice +msgid "Manual Reinvoice" +msgstr "Refacturación Manual" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice_discarded +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__manual_reinvoice_discarded +msgid "Manual Reinvoice Discarded" +msgstr "Refacturación Manual Descartada" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice_done +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__manual_reinvoice_done +msgid "Manual Reinvoice Done" +msgstr "Refacturación Manual Realizada" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields.selection,name:sale_expense_manual_reinvoice.selection__product_template__expense_mode__manual +msgid "Manually" +msgstr "Manualmente" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Mark to Reinvoice" +msgstr "Marcar para Refacturar" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.actions.act_window,help:sale_expense_manual_reinvoice.action_hr_expense_to_reinvoice +msgid "Nothing to Reinvoice." +msgstr "Nada que Refacturar." + +#. module: sale_expense_manual_reinvoice +#: code:addons/sale_expense_manual_reinvoice/models/account_analytic_line.py:0 +#, python-format +msgid "Only manually re-invoice expenses can be re-invoiced." +msgstr "Sólo se pueden refacturar gastos manualmente." + +#. module: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_product_template +msgid "Product Template" +msgstr "Plantilla de Producto" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_product_product__expense_mode +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_product_template__expense_mode +msgid "Re-invoice Mode" +msgstr "Modo Re-facturación" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Reinvoice" +msgstr "Refacturación" + +#. module: sale_expense_manual_reinvoice +#: code:addons/sale_expense_manual_reinvoice/models/hr_expense.py:0 +#, python-format +msgid "" +"Some expenses are missing the Customer to Reinvoice, please fill this field " +"on all lines and try again." +msgstr "" +"En algunos gastos falta el campo Cliente a Refacturar, por favor rellene " +"este campo en todas las líneas e inténtelo de nuevo." + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice_discarded +msgid "Technical field to hide it from pending to reinvoice list." +msgstr "Campo técnico para ocultarlo de la lista de pendientes a refacturar." + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.hr_expense_view_search +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_account_analytic_line_filter +msgid "To Reinvoice" +msgstr "Para Refacturar" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "View Attachments" +msgstr "Ver Archivos Adjuntos" diff --git a/sale_expense_manual_reinvoice/i18n/it.po b/sale_expense_manual_reinvoice/i18n/it.po new file mode 100644 index 000000000..76902cb35 --- /dev/null +++ b/sale_expense_manual_reinvoice/i18n/it.po @@ -0,0 +1,164 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_expense_manual_reinvoice +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-01-03 11:34+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_account_analytic_line +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__analytic_line_ids +msgid "Analytic Line" +msgstr "Riga analitica" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Are you sure you don't want to reinvoice these expenses?" +msgstr "Si è sicuri di voler rifatturare questa spesa?" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields.selection,name:sale_expense_manual_reinvoice.selection__product_template__expense_mode__auto +msgid "Automatically" +msgstr "Automaticamente" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_product_product__expense_mode +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_product_template__expense_mode +msgid "" +"Choose how to re-invoice expenses:\n" +"\n" +"* Automatically: Expenses are automatically re-invoiced when they're " +"posted.\n" +"* Manually: Expenses have to be manually re-invoiced by a manager.\n" +msgstr "" +"Scegliere come rifatturare le spese:\n" +"\n" +"* Automaticamente: le spese sono rifatturate automaticamente quando sono " +"inserite.\n" +"* Manualmente: le spese devono essere rifatturate manualmente da un " +"responsabile.\n" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Discard" +msgstr "Abbandona" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_hr_expense +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__expense_id +msgid "Expense" +msgstr "Spesa" + +#. module: sale_expense_manual_reinvoice +#: code:addons/sale_expense_manual_reinvoice/models/account_analytic_line.py:0 +#, python-format +msgid "Expense already re-invoiced." +msgstr "Spesa già rifatturata." + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_account_analytic_line__expense_id +msgid "Expense where the move line come from" +msgstr "Spesa da cui proviene il movimento" + +#. module: sale_expense_manual_reinvoice +#: model:ir.actions.act_window,name:sale_expense_manual_reinvoice.action_hr_expense_to_reinvoice +#: model:ir.ui.menu,name:sale_expense_manual_reinvoice.menu_hr_expense_to_reinvoice +msgid "Expenses to Reinvoice" +msgstr "Spese da rifatturare" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_account_move_line +msgid "Journal Item" +msgstr "Movimento contabile" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__manual_reinvoice +msgid "Manual Reinvoice" +msgstr "Rifatturazione manuale" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice_discarded +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__manual_reinvoice_discarded +msgid "Manual Reinvoice Discarded" +msgstr "Rifatturazione manuale scartata" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice_done +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__manual_reinvoice_done +msgid "Manual Reinvoice Done" +msgstr "Rifatturazione manuale eseguita" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields.selection,name:sale_expense_manual_reinvoice.selection__product_template__expense_mode__manual +msgid "Manually" +msgstr "Manualmente" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Mark to Reinvoice" +msgstr "Segna da rifatturare" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.actions.act_window,help:sale_expense_manual_reinvoice.action_hr_expense_to_reinvoice +msgid "Nothing to Reinvoice." +msgstr "Niente da rifatturare." + +#. module: sale_expense_manual_reinvoice +#: code:addons/sale_expense_manual_reinvoice/models/account_analytic_line.py:0 +#, python-format +msgid "Only manually re-invoice expenses can be re-invoiced." +msgstr "Solo le spese di rifatturazioni manuali possono essere rifatturate." + +#. module: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_product_template +msgid "Product Template" +msgstr "Modello prodotto" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_product_product__expense_mode +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_product_template__expense_mode +msgid "Re-invoice Mode" +msgstr "Modo rifatturazione" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Reinvoice" +msgstr "Rifattura" + +#. module: sale_expense_manual_reinvoice +#: code:addons/sale_expense_manual_reinvoice/models/hr_expense.py:0 +#, python-format +msgid "" +"Some expenses are missing the Customer to Reinvoice, please fill this field " +"on all lines and try again." +msgstr "" +"Alcune spese non hanno il cliente da rifatturare, compilare il campo per " +"tutte le righe e riprovare." + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice_discarded +msgid "Technical field to hide it from pending to reinvoice list." +msgstr "Campo tecnico per nasconderla nella lista in attesa di rifatturazione." + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.hr_expense_view_search +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_account_analytic_line_filter +msgid "To Reinvoice" +msgstr "Da rifatturare" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "View Attachments" +msgstr "Visualizza allegati" diff --git a/sale_expense_manual_reinvoice/i18n/sale_expense_manual_reinvoice.pot b/sale_expense_manual_reinvoice/i18n/sale_expense_manual_reinvoice.pot new file mode 100644 index 000000000..e9294bde9 --- /dev/null +++ b/sale_expense_manual_reinvoice/i18n/sale_expense_manual_reinvoice.pot @@ -0,0 +1,152 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_expense_manual_reinvoice +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \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: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_account_analytic_line +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__analytic_line_ids +msgid "Analytic Line" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Are you sure you don't want to reinvoice these expenses?" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields.selection,name:sale_expense_manual_reinvoice.selection__product_template__expense_mode__auto +msgid "Automatically" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_product_product__expense_mode +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_product_template__expense_mode +msgid "" +"Choose how to re-invoice expenses:\n" +"\n" +"* Automatically: Expenses are automatically re-invoiced when they're posted.\n" +"* Manually: Expenses have to be manually re-invoiced by a manager.\n" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Discard" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_hr_expense +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__expense_id +msgid "Expense" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: code:addons/sale_expense_manual_reinvoice/models/account_analytic_line.py:0 +#, python-format +msgid "Expense already re-invoiced." +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_account_analytic_line__expense_id +msgid "Expense where the move line come from" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.actions.act_window,name:sale_expense_manual_reinvoice.action_hr_expense_to_reinvoice +#: model:ir.ui.menu,name:sale_expense_manual_reinvoice.menu_hr_expense_to_reinvoice +msgid "Expenses to Reinvoice" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_account_move_line +msgid "Journal Item" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__manual_reinvoice +msgid "Manual Reinvoice" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice_discarded +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__manual_reinvoice_discarded +msgid "Manual Reinvoice Discarded" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice_done +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_hr_expense__manual_reinvoice_done +msgid "Manual Reinvoice Done" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields.selection,name:sale_expense_manual_reinvoice.selection__product_template__expense_mode__manual +msgid "Manually" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Mark to Reinvoice" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.actions.act_window,help:sale_expense_manual_reinvoice.action_hr_expense_to_reinvoice +msgid "Nothing to Reinvoice." +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: code:addons/sale_expense_manual_reinvoice/models/account_analytic_line.py:0 +#, python-format +msgid "Only manually re-invoice expenses can be re-invoiced." +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model,name:sale_expense_manual_reinvoice.model_product_template +msgid "Product Template" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_product_product__expense_mode +#: model:ir.model.fields,field_description:sale_expense_manual_reinvoice.field_product_template__expense_mode +msgid "Re-invoice Mode" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "Reinvoice" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: code:addons/sale_expense_manual_reinvoice/models/hr_expense.py:0 +#, python-format +msgid "" +"Some expenses are missing the Customer to Reinvoice, please fill this field " +"on all lines and try again." +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model:ir.model.fields,help:sale_expense_manual_reinvoice.field_account_analytic_line__manual_reinvoice_discarded +msgid "Technical field to hide it from pending to reinvoice list." +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.hr_expense_view_search +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_account_analytic_line_filter +msgid "To Reinvoice" +msgstr "" + +#. module: sale_expense_manual_reinvoice +#: model_terms:ir.ui.view,arch_db:sale_expense_manual_reinvoice.view_hr_expense_toreinvoice_tree +msgid "View Attachments" +msgstr "" diff --git a/sale_expense_manual_reinvoice/models/__init__.py b/sale_expense_manual_reinvoice/models/__init__.py new file mode 100644 index 000000000..1155bd876 --- /dev/null +++ b/sale_expense_manual_reinvoice/models/__init__.py @@ -0,0 +1,4 @@ +from . import account_analytic_line +from . import account_move_line +from . import hr_expense +from . import product_template diff --git a/sale_expense_manual_reinvoice/models/account_analytic_line.py b/sale_expense_manual_reinvoice/models/account_analytic_line.py new file mode 100644 index 000000000..00dd89861 --- /dev/null +++ b/sale_expense_manual_reinvoice/models/account_analytic_line.py @@ -0,0 +1,49 @@ +# Copyright 2021 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class AccountAnalyticLine(models.Model): + _inherit = "account.analytic.line" + + expense_id = fields.Many2one( + related="move_line_id.expense_id", + store=True, + ) + manual_reinvoice = fields.Boolean( + compute="_compute_manual_reinvoice", + store=True, + ) + manual_reinvoice_done = fields.Boolean( + compute="_compute_manual_reinvoice_done", + store=True, + ) + manual_reinvoice_discarded = fields.Boolean( + help="Technical field to hide it from pending to reinvoice list." + ) + + @api.depends("product_id") + def _compute_manual_reinvoice(self): + for rec in self: + rec.manual_reinvoice = rec.expense_id.product_id.expense_mode == "manual" + + @api.depends("manual_reinvoice", "so_line") + def _compute_manual_reinvoice_done(self): + for rec in self: + rec.manual_reinvoice_done = rec.manual_reinvoice and rec.so_line + + def action_manual_reinvoice(self): + if any(not rec.manual_reinvoice for rec in self): + raise UserError(_("Only manually re-invoice expenses can be re-invoiced.")) + if any(rec.manual_reinvoice_done for rec in self): + raise UserError(_("Expense already re-invoiced.")) + sale_lines_per_move_id = self.move_line_id._sale_create_reinvoice_sale_line() + for rec in self: + sale_line = sale_lines_per_move_id.get(rec.move_line_id.id) + if sale_line: + rec.so_line = sale_line + if rec.manual_reinvoice_discarded: + rec.manual_reinvoice_discarded = False diff --git a/sale_expense_manual_reinvoice/models/account_move_line.py b/sale_expense_manual_reinvoice/models/account_move_line.py new file mode 100644 index 000000000..8c0873656 --- /dev/null +++ b/sale_expense_manual_reinvoice/models/account_move_line.py @@ -0,0 +1,16 @@ +# Copyright 2021 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + def _sale_can_be_reinvoice(self): + # OVERRIDE to skip automatic reinvoicing of expense lines, when needed + res = super()._sale_can_be_reinvoice() + if self.expense_id: + return self.expense_id.product_id.expense_mode != "manual" and res + return res diff --git a/sale_expense_manual_reinvoice/models/hr_expense.py b/sale_expense_manual_reinvoice/models/hr_expense.py new file mode 100644 index 000000000..5add0dce2 --- /dev/null +++ b/sale_expense_manual_reinvoice/models/hr_expense.py @@ -0,0 +1,71 @@ +# Copyright 2021 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class HrExpense(models.Model): + _inherit = "hr.expense" + + analytic_line_ids = fields.One2many( + "account.analytic.line", "expense_id", readonly=True + ) + analytic_account_ids = fields.Many2many( + "account.analytic.account", + compute="_compute_analytic_account_ids", + store=True, + ) + manual_reinvoice = fields.Boolean(compute="_compute_manual_reinvoice", store=True) + manual_reinvoice_done = fields.Boolean( + compute="_compute_manual_reinvoice", store=True + ) + manual_reinvoice_discarded = fields.Boolean( + compute="_compute_manual_reinvoice", store=True + ) + + @api.depends( + "analytic_line_ids", + "analytic_line_ids.manual_reinvoice", + "analytic_line_ids.manual_reinvoice_done", + "analytic_line_ids.manual_reinvoice_discarded", + ) + def _compute_manual_reinvoice(self): + for rec in self: + for fname in [ + "manual_reinvoice", + "manual_reinvoice_done", + "manual_reinvoice_discarded", + ]: + rec[fname] = fields.first(rec.analytic_line_ids)[fname] + + @api.depends("analytic_distribution") + def _compute_analytic_account_ids(self): + for rec in self: + account_ids = ( + [int(acc_id) for acc_id in rec.analytic_distribution.keys()] + if rec.analytic_distribution + else [] + ) + rec.analytic_account_ids = self.env["account.analytic.account"].browse( + account_ids + ) + + def action_manual_reinvoice(self): + if any(not rec.sale_order_id for rec in self): + raise UserError( + _( + "Some expenses are missing the Customer to Reinvoice, " + "please fill this field on all lines and try again." + ) + ) + return self.analytic_line_ids.action_manual_reinvoice() + + def action_manual_reinvoice_discard(self): + self.analytic_line_ids.manual_reinvoice_discarded = True + return True + + def action_manual_reinvoice_pending(self): + self.analytic_line_ids.manual_reinvoice_discarded = False + return True diff --git a/sale_expense_manual_reinvoice/models/product_template.py b/sale_expense_manual_reinvoice/models/product_template.py new file mode 100644 index 000000000..a456712e0 --- /dev/null +++ b/sale_expense_manual_reinvoice/models/product_template.py @@ -0,0 +1,18 @@ +# Copyright 2021 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + expense_mode = fields.Selection( + [("auto", "Automatically"), ("manual", "Manually")], + string="Re-invoice Mode", + default="auto", + help="Choose how to re-invoice expenses:\n\n" + "* Automatically: Expenses are automatically re-invoiced when they're posted.\n" + "* Manually: Expenses have to be manually re-invoiced by a manager.\n", + ) diff --git a/sale_expense_manual_reinvoice/readme/CONFIGURE.rst b/sale_expense_manual_reinvoice/readme/CONFIGURE.rst new file mode 100644 index 000000000..333fd1e67 --- /dev/null +++ b/sale_expense_manual_reinvoice/readme/CONFIGURE.rst @@ -0,0 +1,7 @@ +On the **Expense Product**, set the **Re-invoice Mode**: + +* Automatically: Expenses are automatically re-invoiced when they're posted (standard) +* Manually: Expenses have to be manually re-invoiced by a manager + + +.. image:: ../static/description/configure.png diff --git a/sale_expense_manual_reinvoice/readme/CONTRIBUTORS.rst b/sale_expense_manual_reinvoice/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..3482d3ca7 --- /dev/null +++ b/sale_expense_manual_reinvoice/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* `Camptocamp `_ + + * Iván Todorovich +* MarwanBHL \ No newline at end of file diff --git a/sale_expense_manual_reinvoice/readme/DESCRIPTION.rst b/sale_expense_manual_reinvoice/readme/DESCRIPTION.rst new file mode 100644 index 000000000..6d02d27c5 --- /dev/null +++ b/sale_expense_manual_reinvoice/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +With this module Expense products can be configured to not reinvoice expenses +automatically. Instead, they will be listed in a new menu **Expenses to Reinvoice** +for a manager to manually review them and either reinvoice or discard them. diff --git a/sale_expense_manual_reinvoice/readme/ROADMAP.rst b/sale_expense_manual_reinvoice/readme/ROADMAP.rst new file mode 100644 index 000000000..2d945c50e --- /dev/null +++ b/sale_expense_manual_reinvoice/readme/ROADMAP.rst @@ -0,0 +1,2 @@ +Some abstraction could be done to have the same mechanism work for all reinvoiceable +analytic lines, like the ones generated from purchase invoices. diff --git a/sale_expense_manual_reinvoice/readme/USAGE.rst b/sale_expense_manual_reinvoice/readme/USAGE.rst new file mode 100644 index 000000000..da4258fb9 --- /dev/null +++ b/sale_expense_manual_reinvoice/readme/USAGE.rst @@ -0,0 +1,10 @@ +After Expenses of products configured with **Manual** Re-invoice Mode have been +approved and posted, find them under the **Expenses to Reinvoice** menu. + +Review their **Customer to reinvoice** field, select the ones you want to process +and click either **Reinvoice** or **Discard** + +.. image:: ../static/description/reinvoice.png + +Discarded expenses will be hidden from the list, but you can still access them by +removing the default search filter. diff --git a/sale_expense_manual_reinvoice/static/description/configure.png b/sale_expense_manual_reinvoice/static/description/configure.png new file mode 100644 index 000000000..c7abd29e0 Binary files /dev/null and b/sale_expense_manual_reinvoice/static/description/configure.png differ diff --git a/sale_expense_manual_reinvoice/static/description/icon.png b/sale_expense_manual_reinvoice/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/sale_expense_manual_reinvoice/static/description/icon.png differ diff --git a/sale_expense_manual_reinvoice/static/description/index.html b/sale_expense_manual_reinvoice/static/description/index.html new file mode 100644 index 000000000..73472eb8c --- /dev/null +++ b/sale_expense_manual_reinvoice/static/description/index.html @@ -0,0 +1,462 @@ + + + + + +Sale Expense Manual Re-invoice + + + +
+

Sale Expense Manual Re-invoice

+ + +

Beta License: AGPL-3 OCA/hr-expense Translate me on Weblate Try me on Runboat

+

With this module Expense products can be configured to not reinvoice expenses +automatically. Instead, they will be listed in a new menu Expenses to Reinvoice +for a manager to manually review them and either reinvoice or discard them.

+

Table of contents

+ +
+

Configuration

+

On the Expense Product, set the Re-invoice Mode:

+
    +
  • Automatically: Expenses are automatically re-invoiced when they’re posted (standard)
  • +
  • Manually: Expenses have to be manually re-invoiced by a manager
  • +
+https://raw.githubusercontent.com/OCA/hr-expense/16.0/sale_expense_manual_reinvoice/static/description/configure.png +
+
+

Usage

+

After Expenses of products configured with Manual Re-invoice Mode have been +approved and posted, find them under the Expenses to Reinvoice menu.

+

Review their Customer to reinvoice field, select the ones you want to process +and click either Reinvoice or Discard

+https://raw.githubusercontent.com/OCA/hr-expense/16.0/sale_expense_manual_reinvoice/static/description/reinvoice.png +

Discarded expenses will be hidden from the list, but you can still access them by +removing the default search filter.

+
+
+

Known issues / Roadmap

+

Some abstraction could be done to have the same mechanism work for all reinvoiceable +analytic lines, like the ones generated from purchase invoices.

+
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Camptocamp SA
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

ivantodorovich

+

This module is part of the OCA/hr-expense project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/sale_expense_manual_reinvoice/static/description/reinvoice.png b/sale_expense_manual_reinvoice/static/description/reinvoice.png new file mode 100644 index 000000000..e49433997 Binary files /dev/null and b/sale_expense_manual_reinvoice/static/description/reinvoice.png differ diff --git a/sale_expense_manual_reinvoice/tests/__init__.py b/sale_expense_manual_reinvoice/tests/__init__.py new file mode 100644 index 000000000..097251cc1 --- /dev/null +++ b/sale_expense_manual_reinvoice/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sale_expense_manual_reinvoice diff --git a/sale_expense_manual_reinvoice/tests/test_sale_expense_manual_reinvoice.py b/sale_expense_manual_reinvoice/tests/test_sale_expense_manual_reinvoice.py new file mode 100644 index 000000000..defba960d --- /dev/null +++ b/sale_expense_manual_reinvoice/tests/test_sale_expense_manual_reinvoice.py @@ -0,0 +1,225 @@ +# Copyright 2021 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from ast import literal_eval + +from odoo import fields +from odoo.exceptions import UserError +from odoo.tests import tagged + +from odoo.addons.hr_expense.tests.common import TestExpenseCommon + + +@tagged("post_install", "-at_install") +class TestReInvoiceManual(TestExpenseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.product_expense_auto = cls.env["product.product"].create( + { + "name": "Expense Auto (default)", + "lst_price": 1000.0, + "expense_policy": "sales_price", + "expense_mode": "auto", + } + ) + cls.product_expense_manual = cls.env["product.product"].create( + { + "name": "Expense Manual", + "lst_price": 1000.0, + "expense_policy": "sales_price", + "expense_mode": "manual", + } + ) + cls.order = cls.env["sale.order"].create({"partner_id": cls.partner_a.id}) + cls.order._create_analytic_account() + cls.order.action_confirm() + cls.expense_sheet = cls.env["hr.expense.sheet"].create( + { + "name": "Expense Sheet", + "employee_id": cls.expense_employee.id, + "journal_id": cls.company_data["default_journal_purchase"].id, + "accounting_date": fields.Date.today(), + } + ) + cls.expense = cls.env["hr.expense"].create( + { + "sheet_id": cls.expense_sheet.id, + "employee_id": cls.expense_employee.id, + "name": "Expense", + "date": fields.Date.today(), + "product_id": cls.product_expense_manual.id, + "unit_amount": cls.product_expense_manual.lst_price, + "sale_order_id": cls.order.id, + "analytic_distribution": {cls.analytic_account_1.id: 100}, + "total_amount": 1000.0, + } + ) + + def _get_expenses_to_reinvoice(self, with_discarded=False): + """Gets the expenses to reinvoice from the UI menu action""" + xml_id = "sale_expense_manual_reinvoice.action_hr_expense_to_reinvoice" + action = self.env["ir.actions.act_window"]._for_xml_id(xml_id) + domain = literal_eval(action["domain"].strip()) + if not with_discarded: + domain += [("manual_reinvoice_discarded", "=", False)] + return self.env["hr.expense"].search(domain) + + def test_expense_manual_reinvoice(self): + """Test the full manual reinvoice flow""" + self.expense_sheet.approve_expense_sheets() + self.expense_sheet.action_sheet_move_create() + self.assertTrue(self.expense.manual_reinvoice) + self.assertFalse(self.order.order_line, "No expense should've been created yet") + # Check the re-invoice menu + self.assertIn( + self._get_expenses_to_reinvoice(), + self.expense, + "The expense should've been found in the to re-invoice menu", + ) + # Check that we can re-invoice the expense + self.expense.action_manual_reinvoice() + self.assertTrue(self.expense.manual_reinvoice_done) + self.assertTrue(self.order.order_line, "The expense should've been reinvoiced") + # Check that we can't re-invoice it again + with self.assertRaisesRegex(UserError, "Expense already re-invoiced"): + self.expense.action_manual_reinvoice() + + def test_expense_manual_reinvoice_without_sale_order(self): + """Test case without sale order on hr.expense""" + self.expense.sale_order_id = False + self.expense_sheet.approve_expense_sheets() + self.expense_sheet.action_sheet_move_create() + self.assertFalse(self.order.order_line, "No expense should've been created yet") + # Check the re-invoice menu + self.assertIn( + self._get_expenses_to_reinvoice(), + self.expense, + "The expense should've been found in the to re-invoice menu", + ) + # Check that we can't re-invoice without the user filling the targeted order id + error_message = ( + "Some expenses are missing the Customer to Reinvoice, " + "please fill this field on all lines and try again." + ) + with self.assertRaisesRegex(UserError, error_message): + self.expense.action_manual_reinvoice() + # Check that we can re-invoice the expense if we fill the sale order + self.expense.sale_order_id = self.order + self.expense.action_manual_reinvoice() + self.assertTrue(self.expense.manual_reinvoice_done) + self.assertTrue(self.order.order_line, "The expense should've been reinvoiced") + + def test_expense_auto_reinvoice(self): + """Test that the normal flow still works""" + self.expense.product_id = self.product_expense_auto + self.expense.unit_amount = 1500.0 # amount resets after product change + self.expense_sheet.approve_expense_sheets() + self.expense_sheet.action_sheet_move_create() + self.assertFalse(self.expense.manual_reinvoice) + self.assertTrue(self.order.order_line, "The expense should've been reinvoiced") + # Check the re-invoice menu + self.assertNotIn( + self._get_expenses_to_reinvoice(), + self.expense, + "The expense shouldn't have been found in the to re-invoice menu", + ) + # Check that we can't re-invoice this expense + with self.assertRaisesRegex( + UserError, + "Only manually re-invoice expenses can be re-invoiced", + ): + self.expense.action_manual_reinvoice() + + def test_expense_manual_reinvoice_discard(self): + self.expense_sheet.approve_expense_sheets() + self.expense_sheet.action_sheet_move_create() + # Check the re-invoice menu + self.assertIn( + self._get_expenses_to_reinvoice(), + self.expense, + "The expense should've been found in the to re-invoice menu", + ) + self.expense.action_manual_reinvoice_discard() + self.assertTrue(self.expense.manual_reinvoice_discarded) + # Check the re-invoice menu + self.assertNotIn( + self._get_expenses_to_reinvoice(), + self.expense, + "The expense shouldn't have been found in the to re-invoice menu", + ) + # We should still be able to find it removing the "to reinvoice" filter + self.assertIn( + self._get_expenses_to_reinvoice(with_discarded=True), + self.expense, + "The expense should've been found in the to re-invoice menu", + ) + # We should be able to re-invoice it, even if it was discarded + self.expense.action_manual_reinvoice() + self.assertTrue(self.expense.manual_reinvoice_done) + self.assertFalse(self.expense.manual_reinvoice_discarded, "Back to false") + self.assertTrue(self.order.order_line, "The expense should've been reinvoiced") + + def test_analytic_account_ids_computation(self): + """Test that analytic_account_ids is correctly computed from analytic_distribution.""" + self.expense.analytic_distribution = { + str(self.analytic_account_1.id): 100, + } + + # Trigger computation + self.expense._compute_analytic_account_ids() + + # Verify the Many2many field + self.assertEqual( + len(self.expense.analytic_account_ids), + 1, + "Should have 1 linked analytic account", + ) + self.assertIn( + self.analytic_account_1, + self.expense.analytic_account_ids, + "analytic_account_1 should be in analytic_account_ids", + ) + + def test_analytic_account_ids_computation_empty(self): + """Test that analytic_account_ids is empty when analytic_distribution is empty.""" + self.expense.analytic_distribution = {} + + # Trigger computation + self.expense._compute_analytic_account_ids() + + # Verify the Many2many field + self.assertEqual( + len(self.expense.analytic_account_ids), + 0, + "Should have 0 linked analytic account", + ) + + def test_analytic_account_ids_computation_multiple(self): + """Test that analytic_account_ids is correctly computed with multiple + analytic_distribution.""" + self.expense.analytic_distribution = { + str(self.analytic_account_1.id): 60, + str(self.analytic_account_2.id): 40, + } + + # Trigger computation + self.expense._compute_analytic_account_ids() + + # Verify the Many2many field + self.assertEqual( + len(self.expense.analytic_account_ids), + 2, + "Should have 2 linked analytic accounts", + ) + self.assertIn( + self.analytic_account_1, + self.expense.analytic_account_ids, + "analytic_account_1 should be in analytic_account_ids", + ) + self.assertIn( + self.analytic_account_2, + self.expense.analytic_account_ids, + "analytic_account_2 should be in analytic_account_ids", + ) diff --git a/sale_expense_manual_reinvoice/views/account_analytic_line.xml b/sale_expense_manual_reinvoice/views/account_analytic_line.xml new file mode 100644 index 000000000..6abadbb02 --- /dev/null +++ b/sale_expense_manual_reinvoice/views/account_analytic_line.xml @@ -0,0 +1,30 @@ + + + + + + account.analytic.line + + + + + + + + + + diff --git a/sale_expense_manual_reinvoice/views/hr_expense.xml b/sale_expense_manual_reinvoice/views/hr_expense.xml new file mode 100644 index 000000000..97aea5dd7 --- /dev/null +++ b/sale_expense_manual_reinvoice/views/hr_expense.xml @@ -0,0 +1,142 @@ + + + + + hr.expense + + + + + + + + + + + hr.expense + + +
+
+ + + + + + + + +