Skip to content

Commit

Permalink
project_workload: several fix and simplify code
Browse files Browse the repository at this point in the history
- fix removing user
- fix creating a task without user
- simplify code by introducing a hook "_get_main_workload"
- improve test coverage
  • Loading branch information
sebastienbeau committed Jul 9, 2024
1 parent 13b760c commit 7955d70
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 83 deletions.
22 changes: 16 additions & 6 deletions project_workload/models/project_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,25 +64,35 @@ def _get_workload_sync(self):
*[(2, workload_id.id) for workload_id in self._get_obsolete_workloads()],
]

def _get_main_workloads(self):
return self.workload_ids

def _get_new_workloads(self):
self.ensure_one()
# Handle only one workload in automatic
if not self.workload_ids:
workloads = self._get_main_workloads()
# Do not create load if user_id is not set
if self.user_id and not workloads:
# Handle only one workload in automatic
return [self._prepare_workload()]
return []

def _get_updated_workloads(self):
self.ensure_one()
workloads = self._get_main_workloads()
# Remove other workloads and update the first workload values
if self.workload_ids:
return [(self.workload_ids[0], self._prepare_workload())]
if self.user_id and workloads:
return [(workloads[0], self._prepare_workload())]
return []

def _get_obsolete_workloads(self):
self.ensure_one()
workloads = self._get_main_workloads()
# All workload are removed if user_id is removed
if not self.user_id:
return workloads
# Remove other workloads and update the first workload values
if len(self.workload_ids) > 1:
return self.workload_ids[1:]
if len(workloads) > 1:
return workloads[1:]
return []

@api.depends("workload_ids.unit_ids")
Expand Down
9 changes: 8 additions & 1 deletion project_workload/models/project_task_workload.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def _check_end_date(self):
_("The end date cannot be earlier than the start date.")
)

@api.depends("date_start", "date_end", "hours")
@api.depends("date_start", "date_end", "hours", "user_id")
def _compute_unit_ids(self):
for record in self:
# We need to have the data to compute the unit (this happens at create)
Expand All @@ -71,6 +71,13 @@ def _compute_unit_ids(self):
0,
0,
{
# We have to set here the value of user_id
# as related field user_id will be not computed
# The "project.workload" are created from the computed
# field "workload_ids" and odoo do not play the compute
# field when there are trigered inside a create
# that come from a compute
"user_id": record.user_id.id,
"hours": hours,
"week": week,
},
Expand Down
8 changes: 7 additions & 1 deletion project_workload/tests/test_workload.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from datetime import timedelta

from .common import TestWorkloadCommon
from odoo.addons.project_workload.tests.common import TestWorkloadCommon


class TestWorkload(TestWorkloadCommon):
Expand All @@ -25,6 +25,12 @@ def test_change_user(self):
self.assertEqual(self.task.workload_ids.user_id, self.user_2)
self.assertEqual(self.task.workload_ids.unit_ids.user_id, self.user_2)

def test_remove_user(self):
self.task.user_id = self.user_2
self.assertTrue(self.task.workload_ids)
self.task.user_id = False
self.assertFalse(self.task.workload_ids)

def test_change_date(self):
self.task.date_end = self.task.date_start + timedelta(days=13)
workload = self.task.workload_ids
Expand Down
72 changes: 20 additions & 52 deletions project_workload_additions/models/project_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,71 +8,39 @@
class ProjectTask(models.Model):
_inherit = "project.task"

def _get_main_workloads(self):
return (
super()
._get_main_workloads()
.filtered(lambda s: not s.additional_workload_id)
)

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
# 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:
if additional_workload not in self.workload_ids.additional_workload_id:
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),
for workload in self.workload_ids:
additional_workload = workload.additional_workload_id
if additional_workload in self.project_id.additional_workload_ids:
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:
for workload in self.workload_ids:
additional_workload = workload.additional_workload_id
if (
additional_workload
and additional_workload not in self.project_id.additional_workload_ids
):
rv.append(additional_workload)
return rv

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,36 @@ class ProjectWorkloadAddition(models.Model):
_description = "Project Task Workload Addition"

project_id = fields.Many2one("project.project", string="Project", required=True)
type = fields.Many2one(
type_id = fields.Many2one(
"project.task.workload.addition.type", string="Addition Type", required=True
)
percentage = fields.Float(
required=True,
string="Added Percentage",
store=True,
compute="_compute_percentage",
readonly=False,
)
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
@api.model_create_multi
def create(self, list_vals):
# TODO remove on next version when computed field are run before the create
# for now we have to do it manually as the field is required
for vals in list_vals:
if not vals.get("percentage") and vals.get("type_id"):
vals["percentage"] = (
self.env["project.task.workload.addition.type"]
.browse(vals["type_id"])
.default_percentage
)
return super().create(list_vals)

@api.depends("type_id")
def _compute_percentage(self):
for record in self:
record.percentage = record.type_id.default_percentage

def _compute_hours_from_task(self, task):
self.ensure_one()
Expand Down
1 change: 1 addition & 0 deletions project_workload_additions/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_workload_addition
105 changes: 105 additions & 0 deletions project_workload_additions/tests/test_workload_addition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Copyright 2024 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# @author Florian Mounier <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from datetime import datetime, timedelta

from odoo.addons.project_workload.tests.common import TestWorkloadCommon


class TestWorkload(TestWorkloadCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.task_management, cls.task_review = cls.env["project.task"].create(
[
{"name": "Management", "project_id": cls.project.id},
{"name": "Review", "project_id": cls.project.id},
]
)
cls.type_management, cls.type_review = cls.env[
"project.task.workload.addition.type"
].create(
[
{"name": "Management", "default_percentage": 15},
{"name": "Review", "default_percentage": 20},
]
)
cls.add_work_management, cls.add_work_review = cls.env[
"project.task.workload.addition"
].create(
[
{
"project_id": cls.project.id,
"type_id": cls.type_management.id,
"user_id": cls.user_1.id,
"task_id": cls.task_management.id,
},
{
"project_id": cls.project.id,
"type_id": cls.type_review.id,
"user_id": cls.user_2.id,
"task_id": cls.task_review.id,
},
]
)

def _create_task(self, user_id=None):
now = datetime.now()
return self.env["project.task"].create(
{
"name": "Task 1",
"project_id": self.project.id,
"user_id": user_id,
"date_start": now,
"date_end": now + timedelta(days=20),
"planned_hours": 21,
}
)

def test_task_assign_with_hours(self):
task = self._create_task(self.user_1.id)
workloads = task.workload_ids

self.assertEqual(len(workloads), 3)
worload, workload_management, workload_review = workloads

self.assertEqual(workload_management.date_start, task.date_start.date())
self.assertEqual(workload_management.date_end, task.date_end.date())
self.assertEqual(workload_management.hours, 3.15)
self.assertEqual(workload_management.user_id, self.user_1)
self.assertEqual(
workload_management.additional_workload_id, self.add_work_management
)

self.assertEqual(workload_review.date_start, task.date_start.date())
self.assertEqual(workload_review.date_end, task.date_end.date())
self.assertEqual(workload_review.hours, 4.2)
self.assertEqual(workload_review.user_id, self.user_2)
self.assertEqual(workload_review.additional_workload_id, self.add_work_review)

def _assert_only_additionnal_workload(self, workloads):
self.assertEqual(len(workloads), 2)
workload_management, workload_review = workloads
self.assertEqual(
workload_management.additional_workload_id, self.add_work_management
)
self.assertEqual(workload_review.additional_workload_id, self.add_work_review)

def test_create_unasign_task(self):
task = self._create_task(None)
self._assert_only_additionnal_workload(task.workload_ids)

def test_remove_user(self):
task = self._create_task(self.user_1.id)
task.user_id = False
self._assert_only_additionnal_workload(task.workload_ids)

def test_update_hours(self):
task = self._create_task(self.user_1.id)
task.planned_hours = 42
self.assertEqual(len(task.workload_ids), 3)
worload, workload_management, workload_review = task.workload_ids
self.assertEqual(workload_management.hours, 6.30)
self.assertEqual(workload_review.hours, 8.4)
4 changes: 2 additions & 2 deletions project_workload_additions/views/project_project_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
attrs="{'invisible': [('use_workload', '=', False)]}"
>
<group>
<field name="additional_workload_ids">
<field name="additional_workload_ids" nolabel="1">
<tree editable="bottom">
<field name="type" />
<field name="type_id" />
<field name="percentage" />
<field name="user_id" />
<field
Expand Down
33 changes: 16 additions & 17 deletions project_workload_milestone/models/project_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@
# @author Florian Mounier <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, models
from odoo import api, fields, models


class ProjectTask(models.Model):
_inherit = "project.task"

def _prepare_task_dates_vals_from_milestone(self, vals):
# If we create a task with a milestone or affect it after,
# we set the task dates to the milestone dates
date_start = fields.Datetime(
compute="_compute_date_start_end",
store=True,
readonly=False,
)
date_end = fields.Datetime(
compute="_compute_date_start_end",
store=True,
readonly=False,
)

if "milestone_id" in vals:
milestone = self.env["project.milestone"].browse(vals["milestone_id"])
if milestone:
vals["date_end"] = milestone.target_date
vals["date_start"] = milestone.start_date
return vals

@api.model
def create(self, vals):
return super().create(self._prepare_task_dates_vals_from_milestone(vals))

def write(self, vals):
return super().write(self._prepare_task_dates_vals_from_milestone(vals))
@api.depends("milestone_id.start_date", "milestone_id.target_date")
def _compute_date_start_end(self):
for record in self:
record.date_end = record.milestone_id.target_date
record.date_start = record.milestone_id.start_date

0 comments on commit 7955d70

Please sign in to comment.