diff --git a/sale_timesheet_rounded/README.rst b/sale_timesheet_rounded/README.rst index 16ba3c7789..2f06eabc4b 100644 --- a/sale_timesheet_rounded/README.rst +++ b/sale_timesheet_rounded/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ====================== Sale Timesheet Rounded ====================== @@ -17,7 +13,7 @@ Sale Timesheet Rounded .. |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/license-AGPL--3-blue.png +.. |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 diff --git a/sale_timesheet_rounded/__manifest__.py b/sale_timesheet_rounded/__manifest__.py index 3e59d1071e..f1ad1d72c6 100644 --- a/sale_timesheet_rounded/__manifest__.py +++ b/sale_timesheet_rounded/__manifest__.py @@ -9,12 +9,17 @@ "license": "AGPL-3", "category": "Sales", "website": "https://github.com/OCA/timesheet", - "depends": ["project", "hr_timesheet", "sale_timesheet"], + "depends": [ + "project", + "hr_timesheet", + "sale_timesheet", + ], "data": [ # Views "views/account_analytic_line.xml", "views/project_project.xml", "views/project_task.xml", + "views/sale_order.xml", ], "installable": True, "pre_init_hook": "pre_init_hook", diff --git a/sale_timesheet_rounded/models/__init__.py b/sale_timesheet_rounded/models/__init__.py index abed3dc64e..9315d5027d 100644 --- a/sale_timesheet_rounded/models/__init__.py +++ b/sale_timesheet_rounded/models/__init__.py @@ -1,4 +1,6 @@ from . import account_analytic_line from . import project_project -from . import sale +from . import project_task +from . import sale_order_line +from . import sale_order from . import account_move diff --git a/sale_timesheet_rounded/models/project_task.py b/sale_timesheet_rounded/models/project_task.py new file mode 100644 index 0000000000..a0a9927c6a --- /dev/null +++ b/sale_timesheet_rounded/models/project_task.py @@ -0,0 +1,75 @@ +# Copyright 2026 Camptocamp SA +# 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" + + effective_hours_rounded = fields.Float( + string="Rounded Time Spent", + compute="_compute_effective_hours_rounded", + compute_sudo=True, + store=True, + ) + subtask_effective_hours_rounded = fields.Float( + string="Rounded Subtasks Time Spent", + compute="_compute_subtask_effective_hours_rounded", + recursive=True, + store=True, + ) + remaining_hours_rounded = fields.Float( + string="Rounded Time Remaining", + compute="_compute_remaining_hours_rounded", + compute_sudo=True, + store=True, + ) + total_hours_spent_rounded = fields.Float( + string="Rounded Total Time Spent", + compute="_compute_total_hours_spent_rounded", + store=True, + ) + + @api.depends("timesheet_ids.unit_amount_rounded") + def _compute_effective_hours_rounded(self): + timesheet_rg = self.env["account.analytic.line"]._read_group( + [("task_id", "in", self.ids)], + ["task_id"], + ["unit_amount_rounded:sum"], + ) + per_task = {task.id: amount for task, amount in timesheet_rg} + for task in self: + task.effective_hours_rounded = per_task.get(task.id, 0.0) + + @api.depends( + "child_ids.effective_hours_rounded", "child_ids.subtask_effective_hours_rounded" + ) + def _compute_subtask_effective_hours_rounded(self): + for task in self.with_context(active_test=False): + task.subtask_effective_hours_rounded = sum( + child_task.effective_hours_rounded + + child_task.subtask_effective_hours_rounded + for child_task in task.child_ids + ) + + @api.depends( + "effective_hours_rounded", "subtask_effective_hours_rounded", "allocated_hours" + ) + def _compute_remaining_hours_rounded(self): + for task in self: + if not task.allocated_hours: + task.remaining_hours_rounded = 0.0 + else: + task.remaining_hours_rounded = ( + task.allocated_hours + - task.effective_hours_rounded + - task.subtask_effective_hours_rounded + ) + + @api.depends("effective_hours_rounded", "subtask_effective_hours_rounded") + def _compute_total_hours_spent_rounded(self): + for task in self: + task.total_hours_spent_rounded = ( + task.effective_hours_rounded + task.subtask_effective_hours_rounded + ) diff --git a/sale_timesheet_rounded/models/sale_order.py b/sale_timesheet_rounded/models/sale_order.py new file mode 100644 index 0000000000..1fc1e0464c --- /dev/null +++ b/sale_timesheet_rounded/models/sale_order.py @@ -0,0 +1,30 @@ +# Copyright 2026 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + timesheet_total_duration_rounded = fields.Integer( + string="Rounded Timesheet Total Duration", + compute="_compute_timesheet_total_duration_rounded", + compute_sudo=True, + groups="hr_timesheet.group_hr_timesheet_user", + ) + + def _compute_timesheet_total_duration_rounded(self): + group_data = self.env["account.analytic.line"]._read_group( + [("order_id", "in", self.ids), ("project_id", "!=", False)], + ["order_id"], + ["unit_amount_rounded:sum"], + ) + rounded_dict = {order.id: rounded for order, rounded in group_data} + for sale_order in self: + total_time = sale_order.company_id.project_time_mode_id._compute_quantity( + rounded_dict.get(sale_order.id, 0.0), + sale_order.timesheet_encode_uom_id, + rounding_method="HALF-UP", + ) + sale_order.timesheet_total_duration_rounded = round(total_time) diff --git a/sale_timesheet_rounded/models/sale.py b/sale_timesheet_rounded/models/sale_order_line.py similarity index 100% rename from sale_timesheet_rounded/models/sale.py rename to sale_timesheet_rounded/models/sale_order_line.py diff --git a/sale_timesheet_rounded/static/description/index.html b/sale_timesheet_rounded/static/description/index.html index d99dc6bc81..d72f0a511b 100644 --- a/sale_timesheet_rounded/static/description/index.html +++ b/sale_timesheet_rounded/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Sale Timesheet Rounded -
+
+

Sale Timesheet Rounded

- - -Odoo Community Association - -
-

Sale Timesheet Rounded

-

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

+

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

Round timesheet lines amounts in sales based on project’ settings.

A typical use case is: you work 5 minutes but you want to invoice 15 minutes.

@@ -406,7 +401,7 @@

Sale Timesheet Rounded

-

Configuration

+

Configuration

Go to a project and set the following fields according to your needs:

  • Timesheet rounding unit
  • @@ -428,13 +423,13 @@

    Configuration

    result = round(amount * percentage, unit)
-

Bug Tracker

+

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 @@ -442,15 +437,15 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

  • Camptocamp
-

Contributors

+

Contributors

-

Other credits

+

Other credits

The migration of this sale_timesheet_rounded from 16.0 to 17.0 was financially supported by Camptocamp

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -479,6 +474,5 @@

Maintainers

-
diff --git a/sale_timesheet_rounded/views/project_task.xml b/sale_timesheet_rounded/views/project_task.xml index d6561e8158..fa0c9330a0 100644 --- a/sale_timesheet_rounded/views/project_task.xml +++ b/sale_timesheet_rounded/views/project_task.xml @@ -4,15 +4,189 @@ hr.timesheet.view.task.form2.inherited.inherit project.task + 99 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricActualRounded
+ + + + +
+ + + + + +
+ + + + +
+ + + + +
+
+
+
+ + + + project.task.kanban.rounded.hours + project.task + + 99 + + + + + + + + + + + +
+ +
+
+
diff --git a/sale_timesheet_rounded/views/sale_order.xml b/sale_timesheet_rounded/views/sale_order.xml new file mode 100644 index 0000000000..a053bb5c32 --- /dev/null +++ b/sale_timesheet_rounded/views/sale_order.xml @@ -0,0 +1,31 @@ + + + + sale.order.form.rounded.timesheet + sale.order + + 99 + + +
+ + + / + + + + Recorded (Rounded / Actual) +
+
+
+
+