diff --git a/project_task_report/README.rst b/project_task_report/README.rst new file mode 100644 index 00000000..7ba71679 --- /dev/null +++ b/project_task_report/README.rst @@ -0,0 +1,17 @@ +=================== +Project Task Report +=================== + +Task wizard to sum up the progress of some projects tasks between two dates + +Usage +===== + +To use this module, you need to: + +#. Go to Project > Reporting > Tasks Report +#. Select the related Projects and the Start and End dates of the report +#. The resulting report is a list of the Tasks with new timesheets and/or with stage change between the two selected dates + + + diff --git a/project_task_report/__init__.py b/project_task_report/__init__.py new file mode 100644 index 00000000..5cb1c491 --- /dev/null +++ b/project_task_report/__init__.py @@ -0,0 +1 @@ +from . import wizards diff --git a/project_task_report/__manifest__.py b/project_task_report/__manifest__.py new file mode 100644 index 00000000..3e90704b --- /dev/null +++ b/project_task_report/__manifest__.py @@ -0,0 +1,17 @@ +# Copyright 2024 Akretion +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Project Task Report", + "summary": """Task wizard to sum up the progress between two dates""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "Akretion", + "website": "http://akretion.com", + "depends": ["project", "hr_timesheet"], + "data": [ + "security/ir.model.access.csv", + "wizards/project_task_report.xml", + ], + "demo": ["data/project_task_report_demo.xml"], +} diff --git a/project_task_report/data/project_task_report_demo.xml b/project_task_report/data/project_task_report_demo.xml new file mode 100644 index 00000000..2e001090 --- /dev/null +++ b/project_task_report/data/project_task_report_demo.xml @@ -0,0 +1,104 @@ + + + + + + + Task 1 + + + + Task 2 + + + + + + Timesheet 1.1 + + + + 1.00 + + + Timesheet 2.1 + + + + + + Timesheet 2.2 + + + + 10.00 + + + Timesheet 2.3 + + + + 2.00 + + + + + project.task + + notification + + + + + + + Stage + New + In Progress + many2one + 1 + 2 + + + + + project.task + + notification + + + + + + + Stage + In Progress + Done + many2one + 2 + 3 + + + + + project.task + + notification + + + + + + + Stage + Done + Canceled + many2one + 3 + 4 + + + + + + diff --git a/project_task_report/security/ir.model.access.csv b/project_task_report/security/ir.model.access.csv new file mode 100644 index 00000000..f7e0a741 --- /dev/null +++ b/project_task_report/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +project_task_report.access_project_task_report,access_project_task_report,project_task_report.model_project_task_report,base.group_user,1,1,1,1 +project_task_report.access_project_task_report_line,access_project_task_report_line,project_task_report.model_project_task_report_line,base.group_user,1,1,1,1 diff --git a/project_task_report/tests/__init__.py b/project_task_report/tests/__init__.py new file mode 100644 index 00000000..7768b833 --- /dev/null +++ b/project_task_report/tests/__init__.py @@ -0,0 +1 @@ +from . import test_project_task_report diff --git a/project_task_report/tests/test_project_task_report.py b/project_task_report/tests/test_project_task_report.py new file mode 100644 index 00000000..040976b0 --- /dev/null +++ b/project_task_report/tests/test_project_task_report.py @@ -0,0 +1,38 @@ +# Copyright 2024 Akretion +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from datetime import datetime +from odoo.tests.common import TransactionCase + + +class TestProjectTaskReport(TransactionCase): + + def setUp(self): + super().setUp() + self.task_report_id = self.env["project.task.report"].create( + { + "start_date": datetime(2024, 2, 2), + "end_date": datetime(2024, 6, 1), + "project_ids": self.env.ref("project.project_project_1").ids, + } + ) + line_ids = self.task_report_id._create_line_ids() + self.line_ids = line_ids.sorted(lambda l: l.task_id.name) + + self.stage_new = self.env.ref("project.project_stage_0") + self.stage_in_progress = self.env.ref("project.project_stage_1") + self.stage_canceled = self.env.ref("project.project_stage_3") + + def test_task_report_no_stage_change(self): + task_line_id = self.line_ids[0] + self.assertEqual(task_line_id.task_id.name, "Task 1") + self.assertEqual(task_line_id.hours_spent, 1) + self.assertEqual(task_line_id.start_stage_id, self.stage_new) + self.assertEqual(task_line_id.end_stage_id, self.stage_new) + + def test_task_report_with_stage_change(self): + task_line_id = self.line_ids[1] + self.assertEqual(task_line_id.task_id.name, "Task 2") + self.assertEqual(task_line_id.hours_spent, 12) + self.assertEqual(task_line_id.start_stage_id, self.stage_in_progress) + self.assertEqual(task_line_id.end_stage_id, self.stage_canceled) diff --git a/project_task_report/wizards/__init__.py b/project_task_report/wizards/__init__.py new file mode 100644 index 00000000..b8aa2ac1 --- /dev/null +++ b/project_task_report/wizards/__init__.py @@ -0,0 +1 @@ +from . import project_task_report diff --git a/project_task_report/wizards/project_task_report.py b/project_task_report/wizards/project_task_report.py new file mode 100644 index 00000000..a57401a5 --- /dev/null +++ b/project_task_report/wizards/project_task_report.py @@ -0,0 +1,84 @@ +# Copyright 2024 Akretion +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models + + +class ProjectTaskReport(models.TransientModel): + _name = "project.task.report" + _description = "Task Report" + + start_date = fields.Date() + end_date = fields.Date() + project_ids = fields.Many2many("project.project", string="Projects") + + def _create_line_ids(self): + line_vals = [] + task_ids = self.env["project.task"].search( + [("project_id", "in", self.project_ids.ids)] + ) + field_stage_id = self.env["ir.model.fields"].search( + [("model", "=", "project.task"), ("name", "=", "stage_id")] + ) + + for task_id in task_ids: + line_val = {"task_id": task_id.id, "timesheet_ids": []} + + # Catch the timesheets between the start and end dates + for timesheet_id in task_id.timesheet_ids: + if self.start_date < timesheet_id.date < self.end_date: + line_val["timesheet_ids"].append(timesheet_id.id) + + # Catch the historic stage changes between the start and end dates + track_ids = task_id.message_ids.tracking_value_ids.filtered( + lambda t: t.field == field_stage_id + and self.start_date < t.mail_message_id.date.date() < self.end_date + ).sorted(lambda t: t.mail_message_id.date) + + if track_ids: + line_val.update( + { + "start_stage_id": track_ids[0].old_value_integer, + "end_stage_id": track_ids[-1].new_value_integer, + } + ) + + if line_val["timesheet_ids"] or line_val.get("start_stage_id"): + # Fill stage fields in case there have been timesheets without stage change + if not line_val.get("start_stage_id"): + line_val.update( + { + "start_stage_id": task_id.stage_id.id, + "end_stage_id": task_id.stage_id.id, + } + ) + line_vals.append(line_val) + + return self.env["project.task.report.line"].create(line_vals) + + def action_view_task_report(self): + self.ensure_one() + + line_ids = self._create_line_ids() + + action_xml_id = "project_task_report.project_task_report_line_act_window" + action = self.env["ir.actions.act_window"]._for_xml_id(action_xml_id) + action.update({"domain": [("id", "in", line_ids.ids)]}) + return action + + +class ProjectTaskReportLine(models.TransientModel): + _name = "project.task.report.line" + _description = "Task Report Line" + + task_id = fields.Many2one("project.task", readonly=True) + start_stage_id = fields.Many2one("project.task.type", readonly=True) + end_stage_id = fields.Many2one("project.task.type", readonly=True) + + timesheet_ids = fields.Many2many("account.analytic.line", readonly=True) + hours_spent = fields.Float(compute="_compute_hours_spent", store=True) + + @api.depends("timesheet_ids.unit_amount") + def _compute_hours_spent(self): + for rec in self: + rec.hours_spent = sum(rec.timesheet_ids.mapped("unit_amount")) diff --git a/project_task_report/wizards/project_task_report.xml b/project_task_report/wizards/project_task_report.xml new file mode 100644 index 00000000..55d76c6e --- /dev/null +++ b/project_task_report/wizards/project_task_report.xml @@ -0,0 +1,82 @@ + + + + + + + project.task.report + +
+ + + + + +
+
+
+
+
+ + + Tasks Report + project.task.report + form + new + + + + project.task.report.line.tree + project.task.report.line + + + + + + + + + + + + project.task.report.line + +
+ +
+
+ + + + + + + +
+
+
+
+ + + Tasks Report + project.task.report.line + tree,form + + + + Tasks Report + + + + +