diff --git a/sale_timesheet_line_exclude/README.rst b/sale_timesheet_line_exclude/README.rst new file mode 100644 index 0000000000..1b0b3d947c --- /dev/null +++ b/sale_timesheet_line_exclude/README.rst @@ -0,0 +1,102 @@ +======================================================= +Sales Timesheet: exclude Timesheet Line from Sale Order +======================================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:7567c7037cfe201f77314ef79d26efafc193e53aef07dcde43160198dffb4e15 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Ftimesheet-lightgray.png?logo=github + :target: https://github.com/OCA/timesheet/tree/18.0/sale_timesheet_line_exclude + :alt: OCA/timesheet +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/timesheet-18-0/timesheet-18-0-sale_timesheet_line_exclude + :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/timesheet&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Allows to exclude specific *Timesheet* line from Sale Order. + +This feature proves itself useful for *By Task* billing approach, when a +specific timesheet entry tracked towards a billable task needs to be +excluded from the Sale Order. + +This functionality is not available in Odoo, reported in +`odoo/odoo#31043 `__. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To exclude a specific timesheet entry from invoicing: + + # Go to *Timesheets > Timesheet > All Timesheets* # On the Timesheet + list view, check *Non-billable* for specific timesheet entries + +or: + + # Go to *Project > All Tasks*, ans open a specific Task form # On the + Task form, *Timesheets* tab, check *Non-billable* for specific + timesheet entries + +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 +------- + +* CorporateHub + +Contributors +------------ + +- `CorporateHub `__ + + - Alexey Pelykh + - Freni Patel + +- ``Heliconia Solutions Pvt. Ltd. ``\ \_ + +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. + +This module is part of the `OCA/timesheet `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sale_timesheet_line_exclude/__init__.py b/sale_timesheet_line_exclude/__init__.py new file mode 100644 index 0000000000..4b76c7b2d5 --- /dev/null +++ b/sale_timesheet_line_exclude/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import models diff --git a/sale_timesheet_line_exclude/__manifest__.py b/sale_timesheet_line_exclude/__manifest__.py new file mode 100644 index 0000000000..4723453933 --- /dev/null +++ b/sale_timesheet_line_exclude/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2018-2019 Brainbean Apps (https://brainbeanapps.com) +# Copyright 2020 CorporateHub (https://corporatehub.eu) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Sales Timesheet: exclude Timesheet Line from Sale Order", + "version": "18.0.1.0.0", + "category": "Sales", + "website": "https://github.com/OCA/timesheet", + "author": "CorporateHub, " "Odoo Community Association (OCA)", + "license": "AGPL-3", + "installable": True, + "application": False, + "summary": "Exclude Timesheet Line from Sale Order", + "depends": ["sale_timesheet"], + "data": ["views/account_analytic_line.xml", "views/project_task.xml"], +} diff --git a/sale_timesheet_line_exclude/i18n/de.po b/sale_timesheet_line_exclude/i18n/de.po new file mode 100644 index 0000000000..f9d27c8c57 --- /dev/null +++ b/sale_timesheet_line_exclude/i18n/de.po @@ -0,0 +1,50 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_timesheet_line_exclude +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2019-07-12 13:43+0000\n" +"Last-Translator: Maria Sparenberg \n" +"Language-Team: none\n" +"Language: de\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 3.7.1\n" + +#. module: sale_timesheet_line_exclude +#: model:ir.model,name:sale_timesheet_line_exclude.model_account_analytic_line +msgid "Analytic Line" +msgstr "Kostenstellenbuchung" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,help:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Checking this would exclude this timesheet entry from Sale Order" +msgstr "" +"Wenn der Haken gesetzt ist, wird diese Zeiterfassung für die Abrechnung über " +"einen Verkaufsauftrag ausgeschlossen." + +#. module: sale_timesheet_line_exclude +#: model_terms:ir.ui.view,arch_db:sale_timesheet_line_exclude.account_analytic_line_search +msgid "Excluded From Sale Order" +msgstr "von Abrechnung ausgeschlossen" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,field_description:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Non-billable" +msgstr "von Abrechnung ausschließen" + +#. module: sale_timesheet_line_exclude +#. odoo-python +#: code:addons/sale_timesheet_line_exclude/models/account_analytic_line.py:0 +#, python-format +msgid "" +"You can not modify timesheets in a way that would affect invoices since " +"these timesheets were already invoiced." +msgstr "" +"Es ist nicht erlaubt, Zeiterfassungen zu verändern, die bereits abgerechnet " +"sind." diff --git a/sale_timesheet_line_exclude/i18n/es.po b/sale_timesheet_line_exclude/i18n/es.po new file mode 100644 index 0000000000..10ece8d579 --- /dev/null +++ b/sale_timesheet_line_exclude/i18n/es.po @@ -0,0 +1,49 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_timesheet_line_exclude +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-05-18 20:19+0000\n" +"Last-Translator: Josep M \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 3.10\n" + +#. module: sale_timesheet_line_exclude +#: model:ir.model,name:sale_timesheet_line_exclude.model_account_analytic_line +msgid "Analytic Line" +msgstr "Línea analítica" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,help:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Checking this would exclude this timesheet entry from Sale Order" +msgstr "" +"Marcando esto excluirá esta entrada del Parte de horas del pedido de venta" + +#. module: sale_timesheet_line_exclude +#: model_terms:ir.ui.view,arch_db:sale_timesheet_line_exclude.account_analytic_line_search +msgid "Excluded From Sale Order" +msgstr "Excluido del pedido de venta" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,field_description:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Non-billable" +msgstr "Excluir del pedido de venta" + +#. module: sale_timesheet_line_exclude +#. odoo-python +#: code:addons/sale_timesheet_line_exclude/models/account_analytic_line.py:0 +#, python-format +msgid "" +"You can not modify timesheets in a way that would affect invoices since " +"these timesheets were already invoiced." +msgstr "" +"No puede modificar los Partes de horas de forma que afecte las facturas ya " +"que estos Partes de horas ya se facturaron." diff --git a/sale_timesheet_line_exclude/i18n/fr.po b/sale_timesheet_line_exclude/i18n/fr.po new file mode 100644 index 0000000000..3f3195aa07 --- /dev/null +++ b/sale_timesheet_line_exclude/i18n/fr.po @@ -0,0 +1,51 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_timesheet_line_exclude +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2022-10-05 15:29+0000\n" +"Last-Translator: Vincent Hatakeyama \n" +"Language-Team: none\n" +"Language: fr\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.3.2\n" + +#. module: sale_timesheet_line_exclude +#: model:ir.model,name:sale_timesheet_line_exclude.model_account_analytic_line +msgid "Analytic Line" +msgstr "Ligne analytique" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,help:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Checking this would exclude this timesheet entry from Sale Order" +msgstr "" +"Cocher cette case exclut cette entrée de feuille de temps de la commande " +"client" + +#. module: sale_timesheet_line_exclude +#: model_terms:ir.ui.view,arch_db:sale_timesheet_line_exclude.account_analytic_line_search +msgid "Excluded From Sale Order" +msgstr "Exclue de la commande client" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,field_description:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Non-billable" +msgstr "Non facturable" + +#. module: sale_timesheet_line_exclude +#. odoo-python +#: code:addons/sale_timesheet_line_exclude/models/account_analytic_line.py:0 +#, python-format +msgid "" +"You can not modify timesheets in a way that would affect invoices since " +"these timesheets were already invoiced." +msgstr "" +"Vous ne pouvez pas modifier les feuilles de temps d'une manière qui " +"affecterait les factures puisque ces feuilles de temps étaient déjà " +"facturées." diff --git a/sale_timesheet_line_exclude/i18n/it.po b/sale_timesheet_line_exclude/i18n/it.po new file mode 100644 index 0000000000..0eecb3afea --- /dev/null +++ b/sale_timesheet_line_exclude/i18n/it.po @@ -0,0 +1,50 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_timesheet_line_exclude +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-01-10 22:44+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.14.1\n" + +#. module: sale_timesheet_line_exclude +#: model:ir.model,name:sale_timesheet_line_exclude.model_account_analytic_line +msgid "Analytic Line" +msgstr "Riga analitica" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,help:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Checking this would exclude this timesheet entry from Sale Order" +msgstr "" +"Con questa selezione la registrazione nel foglio ore verrà esclusa " +"dall'ordine di vendita" + +#. module: sale_timesheet_line_exclude +#: model_terms:ir.ui.view,arch_db:sale_timesheet_line_exclude.account_analytic_line_search +msgid "Excluded From Sale Order" +msgstr "Esclusa dall'ordine di vendita" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,field_description:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Non-billable" +msgstr "Non fatturabile" + +#. module: sale_timesheet_line_exclude +#. odoo-python +#: code:addons/sale_timesheet_line_exclude/models/account_analytic_line.py:0 +#, python-format +msgid "" +"You can not modify timesheets in a way that would affect invoices since " +"these timesheets were already invoiced." +msgstr "" +"Non è possibile modificare i fogli ore in modo tale da influire sulle " +"fatture poiché questi fogli ore sono già stati fatturati." diff --git a/sale_timesheet_line_exclude/i18n/pt_BR.po b/sale_timesheet_line_exclude/i18n/pt_BR.po new file mode 100644 index 0000000000..855a93db1b --- /dev/null +++ b/sale_timesheet_line_exclude/i18n/pt_BR.po @@ -0,0 +1,49 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_timesheet_line_exclude +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-10-29 08:28+0000\n" +"Last-Translator: Adriano Prado \n" +"Language-Team: none\n" +"Language: pt_BR\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_timesheet_line_exclude +#: model:ir.model,name:sale_timesheet_line_exclude.model_account_analytic_line +msgid "Analytic Line" +msgstr "Linha Analítica" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,help:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Checking this would exclude this timesheet entry from Sale Order" +msgstr "" +"Marcar isto excluiria esta entrada da planilha de horas do pedido de venda" + +#. module: sale_timesheet_line_exclude +#: model_terms:ir.ui.view,arch_db:sale_timesheet_line_exclude.account_analytic_line_search +msgid "Excluded From Sale Order" +msgstr "Excluído do pedido de venda" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,field_description:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Non-billable" +msgstr "Não faturável" + +#. module: sale_timesheet_line_exclude +#. odoo-python +#: code:addons/sale_timesheet_line_exclude/models/account_analytic_line.py:0 +#, python-format +msgid "" +"You can not modify timesheets in a way that would affect invoices since " +"these timesheets were already invoiced." +msgstr "" +"Você não pode modificar planilhas de horas de uma forma que afete as " +"faturas, uma vez que essas planilhas de horas já foram faturadas." diff --git a/sale_timesheet_line_exclude/i18n/sale_timesheet_line_exclude.pot b/sale_timesheet_line_exclude/i18n/sale_timesheet_line_exclude.pot new file mode 100644 index 0000000000..335112dd15 --- /dev/null +++ b/sale_timesheet_line_exclude/i18n/sale_timesheet_line_exclude.pot @@ -0,0 +1,43 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_timesheet_line_exclude +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 17.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_timesheet_line_exclude +#: model:ir.model,name:sale_timesheet_line_exclude.model_account_analytic_line +msgid "Analytic Line" +msgstr "" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,help:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Checking this would exclude this timesheet entry from Sale Order" +msgstr "" + +#. module: sale_timesheet_line_exclude +#: model_terms:ir.ui.view,arch_db:sale_timesheet_line_exclude.account_analytic_line_search +msgid "Excluded From Sale Order" +msgstr "" + +#. module: sale_timesheet_line_exclude +#: model:ir.model.fields,field_description:sale_timesheet_line_exclude.field_account_analytic_line__exclude_from_sale_order +msgid "Non-billable" +msgstr "" + +#. module: sale_timesheet_line_exclude +#. odoo-python +#: code:addons/sale_timesheet_line_exclude/models/account_analytic_line.py:0 +#, python-format +msgid "" +"You can not modify timesheets in a way that would affect invoices since " +"these timesheets were already invoiced." +msgstr "" diff --git a/sale_timesheet_line_exclude/models/__init__.py b/sale_timesheet_line_exclude/models/__init__.py new file mode 100644 index 0000000000..9776396461 --- /dev/null +++ b/sale_timesheet_line_exclude/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import account_analytic_line diff --git a/sale_timesheet_line_exclude/models/account_analytic_line.py b/sale_timesheet_line_exclude/models/account_analytic_line.py new file mode 100644 index 0000000000..b6aca3782a --- /dev/null +++ b/sale_timesheet_line_exclude/models/account_analytic_line.py @@ -0,0 +1,51 @@ +# Copyright 2018-2019 Brainbean Apps +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class AccountAnalyticLine(models.Model): + _inherit = "account.analytic.line" + + exclude_from_sale_order = fields.Boolean( + string="Non-billable", + help="Checking this would exclude this timesheet entry from Sale Order", + ) + + @api.constrains("exclude_from_sale_order") + def _constrains_exclude_from_sale_order(self): + for line in self: + if ( + line.timesheet_invoice_id + and line.so_line.product_id.invoice_policy == "delivery" + ): + raise ValidationError( + _( + "You can not modify timesheets in a way that would affect " + "invoices since these timesheets were already invoiced." + ) + ) + + @api.depends("exclude_from_sale_order") + def _compute_timesheet_invoice_type(self): + res = super()._compute_timesheet_invoice_type() + for line in self: + if line.exclude_from_sale_order: + line.timesheet_invoice_type = "non_billable" + return res + + @api.depends("exclude_from_sale_order") + def _compute_so_line_on_exclude(self): + self._compute_so_line() + + def _timesheet_determine_sale_line(self): + self.ensure_one() + if self.exclude_from_sale_order: + return False + return super()._timesheet_determine_sale_line() + + def _timesheet_postprocess(self, values): + if "exclude_from_sale_order" in values: + self._compute_so_line() + return super()._timesheet_postprocess(values) diff --git a/sale_timesheet_line_exclude/pyproject.toml b/sale_timesheet_line_exclude/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/sale_timesheet_line_exclude/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/sale_timesheet_line_exclude/readme/CONTRIBUTORS.md b/sale_timesheet_line_exclude/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..d7c05e356f --- /dev/null +++ b/sale_timesheet_line_exclude/readme/CONTRIBUTORS.md @@ -0,0 +1,4 @@ +- [CorporateHub](https://corporatehub.eu/) + - Alexey Pelykh \<\> + - Freni Patel \<\> +- `Heliconia Solutions Pvt. Ltd. `_ diff --git a/sale_timesheet_line_exclude/readme/DESCRIPTION.md b/sale_timesheet_line_exclude/readme/DESCRIPTION.md new file mode 100644 index 0000000000..3e6b7dfafd --- /dev/null +++ b/sale_timesheet_line_exclude/readme/DESCRIPTION.md @@ -0,0 +1,8 @@ +Allows to exclude specific *Timesheet* line from Sale Order. + +This feature proves itself useful for *By Task* billing approach, when a +specific timesheet entry tracked towards a billable task needs to be +excluded from the Sale Order. + +This functionality is not available in Odoo, reported in +[odoo/odoo#31043](https://github.com/odoo/odoo/pull/31043). diff --git a/sale_timesheet_line_exclude/readme/USAGE.md b/sale_timesheet_line_exclude/readme/USAGE.md new file mode 100644 index 0000000000..a38eb7986f --- /dev/null +++ b/sale_timesheet_line_exclude/readme/USAGE.md @@ -0,0 +1,11 @@ +To exclude a specific timesheet entry from invoicing: + +> \# Go to *Timesheets \> Timesheet \> All Timesheets* \# On the +> Timesheet list view, check *Non-billable* for specific timesheet +> entries + +or: + +> \# Go to *Project \> All Tasks*, ans open a specific Task form \# On +> the Task form, *Timesheets* tab, check *Non-billable* for specific +> timesheet entries diff --git a/sale_timesheet_line_exclude/static/description/icon.png b/sale_timesheet_line_exclude/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/sale_timesheet_line_exclude/static/description/icon.png differ diff --git a/sale_timesheet_line_exclude/static/description/index.html b/sale_timesheet_line_exclude/static/description/index.html new file mode 100644 index 0000000000..8ec7af18eb --- /dev/null +++ b/sale_timesheet_line_exclude/static/description/index.html @@ -0,0 +1,446 @@ + + + + + +Sales Timesheet: exclude Timesheet Line from Sale Order + + + +
+

Sales Timesheet: exclude Timesheet Line from Sale Order

+ + +

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

+

Allows to exclude specific Timesheet line from Sale Order.

+

This feature proves itself useful for By Task billing approach, when a +specific timesheet entry tracked towards a billable task needs to be +excluded from the Sale Order.

+

This functionality is not available in Odoo, reported in +odoo/odoo#31043.

+

Table of contents

+ +
+

Usage

+

To exclude a specific timesheet entry from invoicing:

+
+# Go to Timesheets > Timesheet > All Timesheets # On the Timesheet +list view, check Non-billable for specific timesheet entries
+

or:

+
+# Go to Project > All Tasks, ans open a specific Task form # On the +Task form, Timesheets tab, check Non-billable for specific +timesheet entries
+
+
+

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

+
    +
  • CorporateHub
  • +
+
+
+

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.

+

This module is part of the OCA/timesheet project on GitHub.

+

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

+
+
+
+ + diff --git a/sale_timesheet_line_exclude/tests/__init__.py b/sale_timesheet_line_exclude/tests/__init__.py new file mode 100644 index 0000000000..a07542af22 --- /dev/null +++ b/sale_timesheet_line_exclude/tests/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +from . import test_sale_timesheet_line_exclude diff --git a/sale_timesheet_line_exclude/tests/test_sale_timesheet_line_exclude.py b/sale_timesheet_line_exclude/tests/test_sale_timesheet_line_exclude.py new file mode 100644 index 0000000000..46c0930655 --- /dev/null +++ b/sale_timesheet_line_exclude/tests/test_sale_timesheet_line_exclude.py @@ -0,0 +1,337 @@ +# Copyright 2018-2019 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo.exceptions import ValidationError +from odoo.tests import common + + +class TestSaleTimesheetLineExclude(common.TransactionCase): + def setUp(self): + super().setUp() + + self.uom_hour = self.env.ref("uom.product_uom_hour") + self.Partner = self.env["res.partner"] + self.SudoPartner = self.Partner.sudo() + self.Employee = self.env["hr.employee"] + self.SudoEmployee = self.Employee.sudo() + self.AccountAccount = self.env["account.account"] + self.AccountAccountPlan = self.env["account.analytic.plan"] + self.SudoAccountAccount = self.AccountAccount.sudo() + self.Project = self.env["project.project"] + self.SudoProject = self.Project.sudo() + self.ProjectTask = self.env["project.task"] + self.SudoProjectTask = self.ProjectTask.sudo() + self.AccountAnalyticLine = self.env["account.analytic.line"] + self.SudoAccountAnalyticLine = self.AccountAnalyticLine.sudo() + self.ProductProduct = self.env["product.product"] + self.SudoProductProduct = self.ProductProduct.sudo() + self.SaleOrder = self.env["sale.order"] + self.SudoSaleOrder = self.SaleOrder.sudo() + self.SaleOrderLine = self.env["sale.order.line"] + self.SudoSaleOrderLine = self.SaleOrderLine.sudo() + + self.analytic_plan = self.AccountAccountPlan.create( + { + "name": "Plan Test", + } + ) + + self.analytic_account_sale = self.env["account.analytic.account"].create( + { + "name": "Project for selling timesheet - AA", + "code": "AA-20300", + "plan_id": self.analytic_plan.id, + } + ) + + self.account = self.SudoAccountAccount.create( + { + "code": "TEST1", + "name": "Sales #1", + "reconcile": True, + "account_type": "expense_direct_cost", + } + ) + self.project = self.SudoProject.create( + { + "name": "Project #1", + "allow_timesheets": True, + "account_id": self.analytic_account_sale.id, + "allow_billable": True, + } + ) + self.product = self.SudoProductProduct.create( + { + "name": "Service #1", + "standard_price": 30, + "list_price": 90, + "type": "service", + "invoice_policy": "delivery", + "uom_id": self.uom_hour.id, + "uom_po_id": self.uom_hour.id, + "default_code": "CODE-1", + "service_type": "timesheet", + "service_tracking": "task_global_project", + "project_id": self.project.id, + "taxes_id": False, + "property_account_income_id": self.account.id, + } + ) + self.employee = self.SudoEmployee.create( + {"name": "Employee #1", "hourly_cost": 42} + ) + self.account_payable = self.SudoAccountAccount.create( + { + "code": "AP4", + "name": "Payable #1", + "account_type": "liability_payable", + "reconcile": True, + } + ) + self.account_receivable = self.SudoAccountAccount.create( + { + "code": "AR1", + "name": "Receivable #1", + "account_type": "asset_receivable", + "reconcile": True, + } + ) + self.partner = self.SudoPartner.create( + { + "name": "Partner #1", + "email": "partner1@localhost", + "property_account_payable_id": self.account_payable.id, + "property_account_receivable_id": self.account_receivable.id, + } + ) + self.sale_order = self.SudoSaleOrder.create( + { + "partner_id": self.partner.id, + "partner_invoice_id": self.partner.id, + "partner_shipping_id": self.partner.id, + } + ) + self.sale_order_line = self.SudoSaleOrderLine.create( + { + "order_id": self.sale_order.id, + "name": self.product.name, + "product_id": self.product.id, + "product_uom_qty": 2, + "product_uom": self.uom_hour.id, + "price_unit": self.product.list_price, + } + ) + self.sale_order.action_confirm() + self.task = self.SudoProjectTask.search( + [("sale_line_id", "=", self.sale_order_line.id)] + ) + + def test_create_without_exclude_from_sale_order(self): + timesheet = self.SudoAccountAnalyticLine.create( + { + "project_id": self.task.project_id.id, + "task_id": self.task.id, + "name": "Entry #1-1", + "unit_amount": 1, + "employee_id": self.employee.id, + "account_id": self.project.account_id.id, + } + ) + self.assertEqual(timesheet.timesheet_invoice_type, "billable_time") + self.assertEqual(self.sale_order_line.qty_delivered, 1) + self.assertEqual(self.sale_order_line.qty_to_invoice, 1) + self.assertEqual(self.sale_order_line.qty_invoiced, 0) + + def test_create_with_exclude_from_sale_order(self): + timesheet = self.SudoAccountAnalyticLine.create( + { + "project_id": self.task.project_id.id, + "task_id": self.task.id, + "name": "Entry #1-1", + "unit_amount": 1, + "employee_id": self.employee.id, + "exclude_from_sale_order": True, + "account_id": self.project.account_id.id, + } + ) + self.assertEqual(timesheet.timesheet_invoice_type, "non_billable") + self.assertEqual(self.sale_order_line.qty_delivered, 0) + self.assertEqual(self.sale_order_line.qty_to_invoice, 0) + self.assertEqual(self.sale_order_line.qty_invoiced, 0) + + def test_write_exclude_from_sale_order(self): + timesheet = self.SudoAccountAnalyticLine.create( + { + "project_id": self.task.project_id.id, + "task_id": self.task.id, + "name": "Entry #1-1", + "unit_amount": 1, + "employee_id": self.employee.id, + "exclude_from_sale_order": False, + "account_id": self.project.account_id.id, + } + ) + timesheet.write({"exclude_from_sale_order": True}) + + self.assertEqual(timesheet.timesheet_invoice_type, "non_billable") + self.assertEqual(self.sale_order_line.qty_delivered, 0) + self.assertEqual(self.sale_order_line.qty_to_invoice, 0) + self.assertEqual(self.sale_order_line.qty_invoiced, 0) + + def test_write_remove_exclude_from_sale_order(self): + timesheet = self.SudoAccountAnalyticLine.create( + { + "project_id": self.task.project_id.id, + "task_id": self.task.id, + "name": "Entry #1-1", + "unit_amount": 1, + "employee_id": self.employee.id, + "exclude_from_sale_order": True, + "account_id": self.project.account_id.id, + } + ) + timesheet.write({"exclude_from_sale_order": False}) + + self.assertTrue(timesheet.so_line) + self.assertEqual(timesheet.timesheet_invoice_type, "billable_time") + self.assertEqual(self.sale_order_line.qty_delivered, 1) + self.assertEqual(self.sale_order_line.qty_to_invoice, 1) + self.assertEqual(self.sale_order_line.qty_invoiced, 0) + + def test_create_invoice(self): + timesheet1 = self.SudoAccountAnalyticLine.create( + { + "project_id": self.task.project_id.id, + "task_id": self.task.id, + "name": "Entry #1-1", + "unit_amount": 1, + "employee_id": self.employee.id, + "account_id": self.project.account_id.id, + } + ) + + timesheet2 = self.SudoAccountAnalyticLine.create( + { + "project_id": self.task.project_id.id, + "task_id": self.task.id, + "name": "Entry #1-1", + "unit_amount": 1, + "employee_id": self.employee.id, + "exclude_from_sale_order": True, + "account_id": self.project.account_id.id, + } + ) + + self.assertEqual(timesheet1.timesheet_invoice_type, "billable_time") + self.assertEqual(timesheet2.timesheet_invoice_type, "non_billable") + self.assertEqual(self.sale_order_line.qty_delivered, 1) + self.assertEqual(self.sale_order_line.qty_to_invoice, 1) + self.assertEqual(self.sale_order_line.qty_invoiced, 0) + self.sale_order._create_invoices() + self.assertTrue(timesheet1.timesheet_invoice_id) + self.assertEqual(self.sale_order_line.qty_delivered, 1) + self.assertEqual(self.sale_order_line.qty_to_invoice, 0) + self.assertEqual(self.sale_order_line.qty_invoiced, 1) + + def test_write_invoiced(self): + timesheet1 = self.SudoAccountAnalyticLine.create( + { + "project_id": self.task.project_id.id, + "task_id": self.task.id, + "name": "Entry #1-1", + "unit_amount": 1, + "employee_id": self.employee.id, + "account_id": self.project.account_id.id, + } + ) + + timesheet2 = self.SudoAccountAnalyticLine.create( + { + "project_id": self.task.project_id.id, + "task_id": self.task.id, + "name": "Entry #1-1", + "unit_amount": 1, + "employee_id": self.employee.id, + "exclude_from_sale_order": True, + "account_id": self.project.account_id.id, + } + ) + + self.assertEqual(timesheet1.timesheet_invoice_type, "billable_time") + self.assertEqual(timesheet2.timesheet_invoice_type, "non_billable") + self.assertEqual(self.sale_order_line.qty_delivered, 1) + self.assertEqual(self.sale_order_line.qty_to_invoice, 1) + self.assertEqual(self.sale_order_line.qty_invoiced, 0) + self.sale_order._create_invoices() + self.assertTrue(timesheet1.timesheet_invoice_id) + self.assertEqual(self.sale_order_line.qty_delivered, 1) + self.assertEqual(self.sale_order_line.qty_to_invoice, 0) + self.assertEqual(self.sale_order_line.qty_invoiced, 1) + + with self.assertRaises(ValidationError): + timesheet1.write({"exclude_from_sale_order": True}) + + def test_1(self): + timesheet1 = self.SudoAccountAnalyticLine.create( + { + "project_id": self.task.project_id.id, + "task_id": self.task.id, + "name": "Entry #1-1", + "unit_amount": 1, + "employee_id": self.employee.id, + "account_id": self.project.account_id.id, + } + ) + timesheet2 = self.SudoAccountAnalyticLine.create( + { + "project_id": self.task.project_id.id, + "task_id": self.task.id, + "name": "Entry #1-2", + "unit_amount": 1, + "employee_id": self.employee.id, + "exclude_from_sale_order": False, + "account_id": self.project.account_id.id, + } + ) + + self.assertEqual(timesheet1.timesheet_invoice_type, "billable_time") + self.assertEqual(timesheet2.timesheet_invoice_type, "billable_time") + self.assertEqual(self.sale_order_line.qty_delivered, 2) + self.assertEqual(self.sale_order_line.qty_to_invoice, 2) + self.assertEqual(self.sale_order_line.qty_invoiced, 0) + + timesheet3 = self.SudoAccountAnalyticLine.create( + { + "project_id": self.task.project_id.id, + "task_id": self.task.id, + "name": "Entry #1-3", + "unit_amount": 1, + "employee_id": self.employee.id, + "account_id": self.project.account_id.id, + } + ) + self.assertEqual(timesheet3.timesheet_invoice_type, "billable_time") + self.assertTrue(timesheet3.so_line) + self.assertEqual(self.sale_order_line.qty_delivered, 3) + self.assertEqual(self.sale_order_line.qty_to_invoice, 3) + self.assertEqual(self.sale_order_line.qty_invoiced, 0) + + self.assertEqual(timesheet1.timesheet_invoice_type, "billable_time") + self.assertTrue(timesheet1.so_line) + + timesheet2.write({"exclude_from_sale_order": True}) + self.assertEqual(timesheet2.timesheet_invoice_type, "non_billable") + self.assertFalse(timesheet2.so_line) + + self.assertEqual(self.sale_order_line.qty_delivered, 2) + self.assertEqual(self.sale_order_line.qty_to_invoice, 2) + self.assertEqual(self.sale_order_line.qty_invoiced, 0) + + self.assertFalse(timesheet1.timesheet_invoice_id) + self.sale_order._create_invoices() + self.assertTrue(timesheet1.timesheet_invoice_id) + self.assertEqual(self.sale_order_line.qty_delivered, 2) + self.assertEqual(self.sale_order_line.qty_to_invoice, 0) + self.assertEqual(self.sale_order_line.qty_invoiced, 2) + with self.assertRaises(ValidationError): + timesheet1.write({"exclude_from_sale_order": True}) diff --git a/sale_timesheet_line_exclude/views/account_analytic_line.xml b/sale_timesheet_line_exclude/views/account_analytic_line.xml new file mode 100644 index 0000000000..6004522fe0 --- /dev/null +++ b/sale_timesheet_line_exclude/views/account_analytic_line.xml @@ -0,0 +1,45 @@ + + + + + + account.analytic.line.tree + account.analytic.line + + + + + + + + + account.analytic.line.search + account.analytic.line + + + + + + + + + + + + account.analytic.line.form.billable + account.analytic.line + + + + + + + + diff --git a/sale_timesheet_line_exclude/views/project_task.xml b/sale_timesheet_line_exclude/views/project_task.xml new file mode 100644 index 0000000000..dcba877bda --- /dev/null +++ b/sale_timesheet_line_exclude/views/project_task.xml @@ -0,0 +1,24 @@ + + + + + + project.task.form.inherit.timesheet + project.task + + + + + + + +