diff --git a/sale_timesheet_task_exclude/README.rst b/sale_timesheet_task_exclude/README.rst new file mode 100644 index 000000000..54c1fd6a9 --- /dev/null +++ b/sale_timesheet_task_exclude/README.rst @@ -0,0 +1,103 @@ +============================================= +Sales Timesheet: exclude Task from Sale Order +============================================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/13.0/sale_timesheet_task_exclude + :alt: OCA/timesheet +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/timesheet-13-0/timesheet-13-0-sale_timesheet_task_exclude + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/117/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Allows to exclude specific *Task* and all *Timesheets* tracked towards it from +Sale Order. + +This feature proves itself useful for *By Employee* billing approach, when +any timesheet entry tracked towards a specific task need to be excluded +from the Sale Order. + +This functionality is not available in Odoo, reported in `odoo/odoo#31042 `_. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + + +# Go to *Project > All Tasks* +# Open specific task for editing +# Check *Exclude From Billing* + +Known issues / Roadmap +====================== + + * **Important**: The use of this module makes that the hours of timesheets in the + tasks excluded, will not appear or taken in consideration as + "Quantity Delivered" in the Sales Order Lines and Sale Order Reports. + +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 smashing 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 + +* `Guadaltech Soluciones Tecnológicas, S.L. `_: + + * Fernando La Chica + +* Dhara Solanki + +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_task_exclude/__init__.py b/sale_timesheet_task_exclude/__init__.py new file mode 100644 index 000000000..4b76c7b2d --- /dev/null +++ b/sale_timesheet_task_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_task_exclude/__manifest__.py b/sale_timesheet_task_exclude/__manifest__.py new file mode 100644 index 000000000..4c9b08104 --- /dev/null +++ b/sale_timesheet_task_exclude/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 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 Task from Sale Order", + "version": "14.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 Task and related Timesheets from Sale Order", + "depends": ["sale_timesheet"], + "data": ["views/project_task.xml"], +} diff --git a/sale_timesheet_task_exclude/i18n/de.po b/sale_timesheet_task_exclude/i18n/de.po new file mode 100644 index 000000000..52673a8b2 --- /dev/null +++ b/sale_timesheet_task_exclude/i18n/de.po @@ -0,0 +1,34 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_timesheet_task_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_task_exclude +#: model:ir.model.fields,help:sale_timesheet_task_exclude.field_project_task__exclude_from_sale_order +msgid "Checking this would exclude any timesheet entries logged towards this task from Sale Order" +msgstr "" +"Wenn der Haken gesetzt ist, werden alle Zeiterfassungen dieser Aufgabe für " +"die Abrechnung über einen Verkaufsauftrag ausgeschlossen." + +#. module: sale_timesheet_task_exclude +#: model:ir.model.fields,field_description:sale_timesheet_task_exclude.field_project_task__exclude_from_sale_order +msgid "Exclude from Sale Order" +msgstr "von Abrechnung ausschließen" + +#. module: sale_timesheet_task_exclude +#: model:ir.model,name:sale_timesheet_task_exclude.model_project_task +msgid "Task" +msgstr "Aufgabe" diff --git a/sale_timesheet_task_exclude/i18n/es.po b/sale_timesheet_task_exclude/i18n/es.po new file mode 100644 index 000000000..adf578886 --- /dev/null +++ b/sale_timesheet_task_exclude/i18n/es.po @@ -0,0 +1,34 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_timesheet_task_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_task_exclude +#: model:ir.model.fields,help:sale_timesheet_task_exclude.field_project_task__exclude_from_sale_order +msgid "Checking this would exclude any timesheet entries logged towards this task from Sale Order" +msgstr "" +"Marcando esto excluirá cualquier entrada del Parte de horas registrada para " +"esta tarea del pedido de venta" + +#. module: sale_timesheet_task_exclude +#: model:ir.model.fields,field_description:sale_timesheet_task_exclude.field_project_task__exclude_from_sale_order +msgid "Exclude from Sale Order" +msgstr "Excluir del pedido de venta" + +#. module: sale_timesheet_task_exclude +#: model:ir.model,name:sale_timesheet_task_exclude.model_project_task +msgid "Task" +msgstr "Tarea" diff --git a/sale_timesheet_task_exclude/i18n/sale_timesheet_task_exclude.pot b/sale_timesheet_task_exclude/i18n/sale_timesheet_task_exclude.pot new file mode 100644 index 000000000..073143e78 --- /dev/null +++ b/sale_timesheet_task_exclude/i18n/sale_timesheet_task_exclude.pot @@ -0,0 +1,31 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * sale_timesheet_task_exclude +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.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_task_exclude +#: model:ir.model.fields,help:sale_timesheet_task_exclude.field_project_task__exclude_from_sale_order +msgid "" +"Checking this would exclude any timesheet entries logged towards this task " +"from Sale Order" +msgstr "" + +#. module: sale_timesheet_task_exclude +#: model:ir.model.fields,field_description:sale_timesheet_task_exclude.field_project_task__exclude_from_sale_order +msgid "Exclude from Sale Order" +msgstr "" + +#. module: sale_timesheet_task_exclude +#: model:ir.model,name:sale_timesheet_task_exclude.model_project_task +msgid "Task" +msgstr "" diff --git a/sale_timesheet_task_exclude/models/__init__.py b/sale_timesheet_task_exclude/models/__init__.py new file mode 100644 index 000000000..efeeba8d3 --- /dev/null +++ b/sale_timesheet_task_exclude/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import project_task diff --git a/sale_timesheet_task_exclude/models/project_task.py b/sale_timesheet_task_exclude/models/project_task.py new file mode 100644 index 000000000..0f3864a7e --- /dev/null +++ b/sale_timesheet_task_exclude/models/project_task.py @@ -0,0 +1,40 @@ +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ProjectTask(models.Model): + _inherit = "project.task" + + exclude_from_sale_order = fields.Boolean( + string="Exclude from Sale Order", + help=( + "Checking this would exclude any timesheet entries logged towards" + " this task from Sale Order" + ), + ) + + @api.depends( + "sale_line_id", + "project_id", + "allow_billable", + "non_allow_billable", + "exclude_from_sale_order", + ) + def _compute_sale_order_id(self): + super(ProjectTask, self)._compute_sale_order_id() + excluded = self.filtered("exclude_from_sale_order") + for task in excluded: + task.sale_order_id = False + + def write(self, vals): + res = super().write(vals) + if "exclude_from_sale_order" in vals: + # If tasks changed their exclude_from_sale_order, update all AALs + # that have not been invoiced yet + for timesheet in self.timesheet_ids.filtered( + lambda line: not line.timesheet_invoice_id + ): + timesheet._onchange_task_id_employee_id() + return res diff --git a/sale_timesheet_task_exclude/readme/CONTRIBUTORS.rst b/sale_timesheet_task_exclude/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..7eddb1a50 --- /dev/null +++ b/sale_timesheet_task_exclude/readme/CONTRIBUTORS.rst @@ -0,0 +1,9 @@ +* `CorporateHub `__ + + * Alexey Pelykh + +* `Guadaltech Soluciones Tecnológicas, S.L. `_: + + * Fernando La Chica + +* Dhara Solanki diff --git a/sale_timesheet_task_exclude/readme/DESCRIPTION.rst b/sale_timesheet_task_exclude/readme/DESCRIPTION.rst new file mode 100644 index 000000000..e9a5e65ef --- /dev/null +++ b/sale_timesheet_task_exclude/readme/DESCRIPTION.rst @@ -0,0 +1,8 @@ +Allows to exclude specific *Task* and all *Timesheets* tracked towards it from +Sale Order. + +This feature proves itself useful for *By Employee* billing approach, when +any timesheet entry tracked towards a specific task need to be excluded +from the Sale Order. + +This functionality is not available in Odoo, reported in `odoo/odoo#31042 `_. diff --git a/sale_timesheet_task_exclude/readme/ROADMAP.rst b/sale_timesheet_task_exclude/readme/ROADMAP.rst new file mode 100644 index 000000000..0fc66ee57 --- /dev/null +++ b/sale_timesheet_task_exclude/readme/ROADMAP.rst @@ -0,0 +1,3 @@ + * **Important**: The use of this module makes that the hours of timesheets in the + tasks excluded, will not appear or taken in consideration as + "Quantity Delivered" in the Sales Order Lines and Sale Order Reports. diff --git a/sale_timesheet_task_exclude/readme/USAGE.rst b/sale_timesheet_task_exclude/readme/USAGE.rst new file mode 100644 index 000000000..8d931df3f --- /dev/null +++ b/sale_timesheet_task_exclude/readme/USAGE.rst @@ -0,0 +1,4 @@ + +# Go to *Project > Tasks* +# Open specific task for editing +# Check *Exclude From Billing* diff --git a/sale_timesheet_task_exclude/static/description/icon.png b/sale_timesheet_task_exclude/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/sale_timesheet_task_exclude/static/description/icon.png differ diff --git a/sale_timesheet_task_exclude/static/description/index.html b/sale_timesheet_task_exclude/static/description/index.html new file mode 100644 index 000000000..176d8590c --- /dev/null +++ b/sale_timesheet_task_exclude/static/description/index.html @@ -0,0 +1,449 @@ + + + + + + +Sales Timesheet: exclude Task from Sale Order + + + +
+

Sales Timesheet: exclude Task from Sale Order

+ + +

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

+

Allows to exclude specific Task and all Timesheets tracked towards it from +Sale Order.

+

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

+

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

+

Table of contents

+ +
+

Usage

+

# Go to Project > All Tasks +# Open specific task for editing +# Check Exclude From Billing

+
+
+

Known issues / Roadmap

+
+
    +
  • Important: The use of this module makes that the hours of timesheets in the +tasks excluded, will not appear or taken in consideration as +“Quantity Delivered” in the Sales Order Lines and Sale Order Reports.
  • +
+
+
+
+

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

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • CorporateHub
  • +
+
+ +
+

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_task_exclude/tests/__init__.py b/sale_timesheet_task_exclude/tests/__init__.py new file mode 100644 index 000000000..3d46fdc73 --- /dev/null +++ b/sale_timesheet_task_exclude/tests/__init__.py @@ -0,0 +1,10 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo.addons.sale_timesheet.tests import common +from odoo.addons.sale_timesheet.tests import test_sale_timesheet +from odoo.addons.sale_timesheet.tests import test_sale_service +from odoo.addons.sale_timesheet.tests import test_project_billing +from odoo.addons.sale_timesheet.tests import test_reinvoice +from odoo.addons.sale_timesheet.tests import test_reporting + +from . import test_sale_timesheet_exclude_task diff --git a/sale_timesheet_task_exclude/tests/test_sale_timesheet_exclude_task.py b/sale_timesheet_task_exclude/tests/test_sale_timesheet_exclude_task.py new file mode 100644 index 000000000..4edd97094 --- /dev/null +++ b/sale_timesheet_task_exclude/tests/test_sale_timesheet_exclude_task.py @@ -0,0 +1,141 @@ +# Copyright 2019 Brainbean Apps (https://brainbeanapps.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo.tests import common + + +class TestSaleTimesheetExcludeTask(common.TransactionCase): + def setUp(self): + super().setUp() + + self.uom_hour = self.env.ref("uom.product_uom_hour") + self.user_type_payable = self.env.ref("account.data_account_type_payable") + self.user_type_receivable = self.env.ref("account.data_account_type_receivable") + self.user_type_revenue = self.env.ref("account.data_account_type_revenue") + 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.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.ProjectCreateSaleOrder = self.env["project.create.sale.order"] + + def test_exclude_from_sale_order(self): + account = self.SudoAccountAccount.create( + { + "code": "TEST-1", + "name": "Sales #1", + "reconcile": True, + "user_type_id": self.user_type_revenue.id, + } + ) + project = self.SudoProject.create( + {"name": "Project #1", "allow_timesheets": True, "allow_billable": True} + ) + 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": project.id, + "taxes_id": False, + "property_account_income_id": account.id, + } + ) + employee = self.SudoEmployee.create( + {"name": "Employee #1", "timesheet_cost": 42} + ) + account_payable = self.SudoAccountAccount.create( + { + "code": "AP1", + "name": "Payable #1", + "user_type_id": self.user_type_payable.id, + "reconcile": True, + } + ) + account_receivable = self.SudoAccountAccount.create( + { + "code": "AR1", + "name": "Receivable #1", + "user_type_id": self.user_type_receivable.id, + "reconcile": True, + } + ) + partner = self.SudoPartner.create( + { + "name": "Partner #1", + "email": "partner1@localhost", + "property_account_payable_id": account_payable.id, + "property_account_receivable_id": account_receivable.id, + } + ) + sale_order = self.SudoSaleOrder.create( + { + "partner_id": partner.id, + "partner_invoice_id": partner.id, + "partner_shipping_id": partner.id, + } + ) + sale_order_line = self.SudoSaleOrderLine.create( + { + "order_id": sale_order.id, + "name": product.name, + "product_id": product.id, + "product_uom_qty": 0, + "product_uom": self.uom_hour.id, + "price_unit": product.list_price, + } + ) + sale_order.action_confirm() + task = self.SudoProjectTask.search([("sale_line_id", "=", sale_order_line.id)]) + timesheet = self.SudoAccountAnalyticLine.create( + { + "project_id": task.project_id.id, + "task_id": task.id, + "name": "Entry #1", + "unit_amount": 1, + "employee_id": employee.id, + } + ) + task.exclude_from_sale_order = True + task.allow_billable = False + self.assertFalse(timesheet.so_line) + + task.exclude_from_sale_order = False + task.allow_billable = True + self.assertTrue(timesheet.so_line) + + payment = ( + self.env["sale.advance.payment.inv"] + .with_context( + { + "active_model": "sale.order", + "active_ids": [sale_order.id], + "active_id": sale_order.id, + } + ) + .create({"advance_payment_method": "delivered"}) + ) + payment.create_invoices() + + task.exclude_from_sale_order = True + self.assertTrue(timesheet.so_line) diff --git a/sale_timesheet_task_exclude/views/project_task.xml b/sale_timesheet_task_exclude/views/project_task.xml new file mode 100644 index 000000000..1be6b6caa --- /dev/null +++ b/sale_timesheet_task_exclude/views/project_task.xml @@ -0,0 +1,17 @@ + + + + + project.task.form + project.task + + + + + + + + diff --git a/setup/sale_timesheet_task_exclude/odoo/addons/sale_timesheet_task_exclude b/setup/sale_timesheet_task_exclude/odoo/addons/sale_timesheet_task_exclude new file mode 120000 index 000000000..4b67d60fe --- /dev/null +++ b/setup/sale_timesheet_task_exclude/odoo/addons/sale_timesheet_task_exclude @@ -0,0 +1 @@ +../../../../sale_timesheet_task_exclude \ No newline at end of file diff --git a/setup/sale_timesheet_task_exclude/setup.py b/setup/sale_timesheet_task_exclude/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/sale_timesheet_task_exclude/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)