diff --git a/project_workload_additions/__manifest__.py b/project_workload_additions/__manifest__.py index b3d4ee94..67ecc998 100644 --- a/project_workload_additions/__manifest__.py +++ b/project_workload_additions/__manifest__.py @@ -15,5 +15,11 @@ "depends": [ "project_workload", ], - "data": [], + "data": [ + "security/ir.model.access.csv", + "views/project_task_view.xml", + "views/project_task_workload_addition_type_views.xml", + "views/project_project_view.xml", + "views/menu_view.xml", + ], } diff --git a/project_workload_additions/models/__init__.py b/project_workload_additions/models/__init__.py index e69de29b..24ba8ae4 100644 --- a/project_workload_additions/models/__init__.py +++ b/project_workload_additions/models/__init__.py @@ -0,0 +1,5 @@ +from . import project_project +from . import project_task_workload +from . import project_task +from . import project_task_workload_addition_type +from . import project_task_workload_addition diff --git a/project_workload_additions/models/project_project.py b/project_workload_additions/models/project_project.py new file mode 100644 index 00000000..dbe054e0 --- /dev/null +++ b/project_workload_additions/models/project_project.py @@ -0,0 +1,14 @@ +# Copyright 2024 Akretion (https://www.akretion.com). +# @author Sébastien BEAU +# @author Florian Mounier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ProjectProject(models.Model): + _inherit = "project.project" + + additional_workload_ids = fields.One2many( + "project.task.workload.addition", "project_id", "Additional Workload" + ) diff --git a/project_workload_additions/models/project_task.py b/project_workload_additions/models/project_task.py new file mode 100644 index 00000000..9017f98e --- /dev/null +++ b/project_workload_additions/models/project_task.py @@ -0,0 +1,84 @@ +# Copyright 2024 Akretion (https://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + + +class ProjectTask(models.Model): + _inherit = "project.task" + + def _get_new_workloads(self): + rv = super()._get_new_workloads() + # super creates a new workload if there are none + # but here we can have only additional workloads + # so we also need to check if the existing workloads are additional + if not rv and all( + workload.additional_workload_id for workload in self.workload_ids + ): + rv.append(self._prepare_workload()) + + additional_workloads = { + workload.additional_workload_id: workload + for workload in self.workload_ids + if workload.additional_workload_id + } + # Now we need to create a new workload for each additional workload + for additional_workload in self.project_id.additional_workload_ids: + if additional_workload not in additional_workloads: + rv.append(self._prepare_additional_workload(additional_workload)) + + return rv + + def _get_updated_workloads(self): + # We sort the workloads by additional_workload_id to ensure that the first workload is the main one + self.workload_ids = self.workload_ids.sorted( + key=lambda w: w.additional_workload_id + ) + rv = super()._get_updated_workloads() + if rv and rv[0][0].additional_workload_id: + rv = [] + + additional_workloads = { + workload.additional_workload_id: workload + for workload in self.workload_ids + if workload.additional_workload_id + } + # Now we need to update the existing workload for each additional workload + for additional_workload, workload in additional_workloads.items(): + rv.append( + ( + workload, + self._prepare_additional_workload(additional_workload), + ) + ) + + return rv + + def _get_obsolete_workloads(self): + self.workload_ids = self.workload_ids.sorted( + key=lambda w: w.additional_workload_id + ) + rv = super()._get_obsolete_workloads() + if rv: + # Do not delete additional workloads + rv = [workload for workload in rv if not workload.additional_workload_id] + + # Remove all additional workloads that are not in the project anymore + additional_workloads = { + workload.additional_workload_id: workload + for workload in self.workload_ids + if workload.additional_workload_id + } + for additional_workload in additional_workloads: + if additional_workload not in self.project_id.additional_workload_ids: + rv.append(additional_workload) + return rv + + def _prepare_additional_workload(self, additional_workload, **extra): + return self._prepare_workload( + additional_workload_id=additional_workload.id, + hours=additional_workload._compute_hours_from_task(self), + user_id=additional_workload.user_id.id, + **extra, + ) diff --git a/project_workload_additions/models/project_task_workload.py b/project_workload_additions/models/project_task_workload.py new file mode 100644 index 00000000..3b4034d6 --- /dev/null +++ b/project_workload_additions/models/project_task_workload.py @@ -0,0 +1,14 @@ +# Copyright 2024 Akretion (https://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +from odoo import fields, models + + +class ProjectTaskWorkload(models.Model): + _inherit = "project.task.workload" + + additional_workload_id = fields.Many2one( + "project.task.workload.addition", string="Additional Workload Reference" + ) diff --git a/project_workload_additions/models/project_task_workload_addition.py b/project_workload_additions/models/project_task_workload_addition.py new file mode 100644 index 00000000..9f692781 --- /dev/null +++ b/project_workload_additions/models/project_task_workload_addition.py @@ -0,0 +1,43 @@ +# Copyright 2024 Akretion (https://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models + + +class ProjectWorkloadAddition(models.Model): + _name = "project.task.workload.addition" + _description = "Project Task Workload Addition" + + project_id = fields.Many2one("project.project", string="Project", required=True) + type = fields.Many2one( + "project.task.workload.addition.type", string="Addition Type", required=True + ) + percentage = fields.Float( + required=True, + string="Added Percentage", + ) + user_id = fields.Many2one("res.users", string="User", required=True) + task_id = fields.Many2one("project.task", string="Task", required=True) + + @api.onchange("type") + def _onchange_type(self): + self.percentage = self.type.default_percentage + + def _compute_hours_from_task(self, task): + self.ensure_one() + return task.planned_hours * (self.percentage / 100) + + def name_get(self): + result = [] + for record in self: + result.append( + ( + record.id, + _( + "%s additional workload (%d%%)" + % (record.task_id.name, record.percentage) + ), + ) + ) + return result diff --git a/project_workload_additions/models/project_task_workload_addition_type.py b/project_workload_additions/models/project_task_workload_addition_type.py new file mode 100644 index 00000000..674a82b4 --- /dev/null +++ b/project_workload_additions/models/project_task_workload_addition_type.py @@ -0,0 +1,16 @@ +# Copyright 2024 Akretion (https://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +from odoo import fields, models + + +class ProjectWorkloadAdditionType(models.Model): + _name = "project.task.workload.addition.type" + _description = "Project Task Workload Addition Type" + + name = fields.Char(required=True) + description = fields.Text() + default_percentage = fields.Float(required=True, default=10) + active = fields.Boolean(default=True) diff --git a/project_workload_additions/security/ir.model.access.csv b/project_workload_additions/security/ir.model.access.csv new file mode 100644 index 00000000..23f0d227 --- /dev/null +++ b/project_workload_additions/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 +access_edit_project_task_workload_addition,edit project task workload addition,model_project_task_workload_addition,project.group_project_user,1,1,1,1 +access_edit_project_task_workload_addition_type,edit project task workload addition type,model_project_task_workload_addition_type,project.group_project_user,1,1,1,1 diff --git a/project_workload_additions/views/menu_view.xml b/project_workload_additions/views/menu_view.xml new file mode 100644 index 00000000..7dd4bd7c --- /dev/null +++ b/project_workload_additions/views/menu_view.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/project_workload_additions/views/project_project_view.xml b/project_workload_additions/views/project_project_view.xml new file mode 100644 index 00000000..b2487ae1 --- /dev/null +++ b/project_workload_additions/views/project_project_view.xml @@ -0,0 +1,33 @@ + + + + + project.project + + + + + + + + + + + + + + + + + + + + diff --git a/project_workload_additions/views/project_task_view.xml b/project_workload_additions/views/project_task_view.xml new file mode 100644 index 00000000..efa32de6 --- /dev/null +++ b/project_workload_additions/views/project_task_view.xml @@ -0,0 +1,14 @@ + + + + + project.task + + + + + + + + + diff --git a/project_workload_additions/views/project_task_workload_addition_type_views.xml b/project_workload_additions/views/project_task_workload_addition_type_views.xml new file mode 100644 index 00000000..d4193b68 --- /dev/null +++ b/project_workload_additions/views/project_task_workload_addition_type_views.xml @@ -0,0 +1,60 @@ + + + + + project.task.workload.addition.type + + + + + + + + + + + project.task.workload.addition.type + +
+ +
+

+ +

+
+ + + + + + + +
+
+
+
+ + + project.task.workload.addition.type + + + + + + + + + + Workload Addition Types + ir.actions.act_window + project.task.workload.addition.type + tree,form + + [] + {} + + +