Skip to content

Commit

Permalink
project_workload: start build a module for managing project workload …
Browse files Browse the repository at this point in the history
…per week
  • Loading branch information
sebastienbeau committed Jul 27, 2023
1 parent 2c3ee0c commit cc829bd
Show file tree
Hide file tree
Showing 24 changed files with 624 additions and 0 deletions.
1 change: 1 addition & 0 deletions project_workload/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
30 changes: 30 additions & 0 deletions project_workload/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Project Workload",
"summary": "Ressource Workload Management",
"version": "14.0.1.0.0",
"development_status": "Alpha",
"category": "Uncategorized",
"website": "https://github.com/akretion/ak-odoo-incubator",
"author": " Akretion",
"license": "AGPL-3",
"external_dependencies": {
"python": [],
"bin": [],
},
"depends": [
"project_timeline",
],
"data": [
"security/ir.model.access.csv",
"views/project_capacity_unit_view.xml",
"views/project_task_view.xml",
"views/project_project_view.xml",
"views/project_user_capacity_view.xml",
"views/menu_view.xml",
],
"demo": [],
}
54 changes: 54 additions & 0 deletions project_workload/models/$
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models

WORKLOAD_FIELDS = [
"date_start",
"date_end",
"user_id",
"planned_hours",
]


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

workload_ids = fields.One2many(
'project.task.workload',
'task_id',
'Task')
use_workload = fields.Boolean
related="project_id.use_workload"
)
config_workload_manualy = fields.Boolean()

def _prepare_workload(self):
return {
"task_id": self.id,
"date_start": self.date_start,
"date_end": self.date_end,
"hours": self.planned_hours,
"user_id": self.user_id.id,
}

def _sync_workload(self):
# Remove workload and regenerate it
for record in self:
if not record.config_workload_manualy:
vals = self._prepare_workload()
if record.workload_ids:
record.workload.write(vals)
self.env["project.task.workload"].create(vals)

def create(self, vals_list):
records = super().create(vals_list)
records._sync_workload()
return records

def write(self, vals):
res = super().write(vals)
if set(vals.keys()).intersection(WORKLOAD_FIELDS):
self._sync_workload()
return res
6 changes: 6 additions & 0 deletions project_workload/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from . import project_task_workload
from . import project_project
from . import project_task
from . import project_capacity_unit
from . import project_user_capacity
from . import project_user_capacity_line
65 changes: 65 additions & 0 deletions project_workload/models/project_capacity_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).


from datetime import datetime

from odoo import api, fields, models

WEEK_FORMAT = "%Y-%W"


def week_name(value):
if value:
return value.strftime(WEEK_FORMAT)
return None


class ProjectCapacityUnit(models.Model):
_name = "project.capacity.unit"
_description = "Project Capacity Unit"
_rec_name = "week"
_order = "week asc"

week = fields.Char()
capacity_id = fields.Many2one("project.user.capacity", "Capacity")
hours = fields.Float(compute="_compute_capacity", store=True)

_sql_constraints = [
(
"week_capacity_uniq",
"unique(week, capacity_id)",
"The week must be uniq per capacity",
),
]

@api.depends(
"capacity_id.line_ids.hours",
"capacity_id.line_ids.date_start",
"capacity_id.line_ids.date_end",
"capacity_id.line_ids.modulo",
)
def _compute_capacity(self):
for record in self:
hours = 0
for line in record.capacity_id.line_ids:
start = week_name(line.date_start)
stop = week_name(line.date_end)
if record.week >= start and (not stop or record.week <= stop):
if line.modulo > 1:
nbr_week = round(
(
datetime.strptime(
record.week + "-1", WEEK_FORMAT + "-%w"
).date()
- line.date_start
).days
/ 7
)
if nbr_week % line.modulo:
# week is not a multi skip it
continue
hours = line.hours
break
record.hours = hours
11 changes: 11 additions & 0 deletions project_workload/models/project_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models


class ProjectProject(models.Model):
_inherit = "project.project"

use_workload = fields.Boolean()
51 changes: 51 additions & 0 deletions project_workload/models/project_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models

WORKLOAD_FIELDS = [
"date_start",
"date_end",
"user_id",
"planned_hours",
"config_workload_manually",
]


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

workload_ids = fields.One2many("project.task.workload", "task_id", "Task")
use_workload = fields.Boolean(related="project_id.use_workload")
config_workload_manually = fields.Boolean()

def _prepare_workload(self):
return {

Check warning on line 24 in project_workload/models/project_task.py

View check run for this annotation

Codecov / codecov/patch

project_workload/models/project_task.py#L24

Added line #L24 was not covered by tests
"task_id": self.id,
"date_start": self.date_start,
"date_end": self.date_end,
"hours": self.planned_hours,
"user_id": self.user_id.id,
}

def _sync_workload(self):
for record in self:
if not record.config_workload_manually:
vals = self._prepare_workload()

Check warning on line 35 in project_workload/models/project_task.py

View check run for this annotation

Codecov / codecov/patch

project_workload/models/project_task.py#L35

Added line #L35 was not covered by tests
if record.workload_ids:
record.workload_ids[1:].unlink()
record.workload_ids.write(vals)

Check warning on line 38 in project_workload/models/project_task.py

View check run for this annotation

Codecov / codecov/patch

project_workload/models/project_task.py#L37-L38

Added lines #L37 - L38 were not covered by tests
else:
self.env["project.task.workload"].create([vals])

Check warning on line 40 in project_workload/models/project_task.py

View check run for this annotation

Codecov / codecov/patch

project_workload/models/project_task.py#L40

Added line #L40 was not covered by tests

def create(self, vals_list):
records = super().create(vals_list)
records._sync_workload()
return records

Check warning on line 45 in project_workload/models/project_task.py

View check run for this annotation

Codecov / codecov/patch

project_workload/models/project_task.py#L43-L45

Added lines #L43 - L45 were not covered by tests

def write(self, vals):
res = super().write(vals)

Check warning on line 48 in project_workload/models/project_task.py

View check run for this annotation

Codecov / codecov/patch

project_workload/models/project_task.py#L48

Added line #L48 was not covered by tests
if set(vals.keys()).intersection(WORKLOAD_FIELDS):
self._sync_workload()
return res

Check warning on line 51 in project_workload/models/project_task.py

View check run for this annotation

Codecov / codecov/patch

project_workload/models/project_task.py#L50-L51

Added lines #L50 - L51 were not covered by tests
17 changes: 17 additions & 0 deletions project_workload/models/project_task_workload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).


from odoo import fields, models


class ProjectTaskWorkload(models.Model):
_name = "project.task.workload"
_description = "Project Task Workload"

task_id = fields.Many2one("project.task", "Task", required=True)
date_start = fields.Date(required=True)
date_end = fields.Date(required=True)
user_id = fields.Many2one("res.users", "User", required=True)
hours = fields.Float(required=True)
51 changes: 51 additions & 0 deletions project_workload/models/project_user_capacity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from collections import defaultdict
from datetime import datetime, timedelta

from odoo import api, fields, models

from .project_capacity_unit import week_name


class ProjectUserCapacity(models.Model):
_name = "project.user.capacity"
_description = "Project User Capacity"

name = fields.Char(required=True)
user_id = fields.Many2one("res.users", "User", required=True)
filter_id = fields.Many2one("ir.filters", "Domain")
line_ids = fields.One2many("project.user.capacity.line", "capacity_id", "Line")
unit_ids = fields.One2many("project.capacity.unit", "capacity_id", "Capacity Unit")

@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
records._generate_capacity_unit()
return records

def _cron_generate_week_report(self, nbr_week=52):
self.env["project.capacity"].search([])._generate_capacity_unit()

Check warning on line 30 in project_workload/models/project_user_capacity.py

View check run for this annotation

Codecov / codecov/patch

project_workload/models/project_user_capacity.py#L30

Added line #L30 was not covered by tests

def _generate_capacity_unit(self, nbr_week=52):
now = datetime.now()
items = self.env["project.capacity.unit"].search(
[("week", ">", week_name(now))]
)
weeks_per_capacity = defaultdict(set)
for item in items:
weeks_per_capacity[item.capacity_id.id].add(item.week)

Check warning on line 39 in project_workload/models/project_user_capacity.py

View check run for this annotation

Codecov / codecov/patch

project_workload/models/project_user_capacity.py#L39

Added line #L39 was not covered by tests
weeks = {week_name(now + timedelta(days=7 * x)) for x in range(nbr_week)}
vals_list = []
for capacity in self:
missing_weeks = weeks - weeks_per_capacity[capacity.id]
vals_list += [
{
"week": week,
"capacity_id": capacity.id,
}
for week in missing_weeks
]
return self.env["project.capacity.unit"].create(vals_list)
17 changes: 17 additions & 0 deletions project_workload/models/project_user_capacity_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).


from odoo import fields, models


class ProjectUserCapacityLine(models.Model):
_name = "project.user.capacity.line"
_description = "Project User Capacity Line"

capacity_id = fields.Many2one("project.user.capacity")
date_start = fields.Date(required=True, default=fields.Date.today())
date_end = fields.Date()
hours = fields.Float()
modulo = fields.Integer(default=1, string="Repeat", help="Repeat every X week")
1 change: 1 addition & 0 deletions project_workload/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* BEAU Sébastien <[email protected]>
2 changes: 2 additions & 0 deletions project_workload/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
This module allow to manage load and capacity by project and cross project
Load is managed by week
2 changes: 2 additions & 0 deletions project_workload/readme/ROADMAP.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
TODO
- in case of manual workload assignation add a check on date
47 changes: 47 additions & 0 deletions project_workload/reports/project_load_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import fields, models

# TODO


class ProjectLoadReport(models.Model):
_name = "project.load.report"
_description = "Project Load Report"

week = fields.Char()
user_id = fields.Many2one("res.users", "User")
total_planned_hours = fields.Float()
project_planned_hours = fields.Float()
project_capacity_hours = fields.Float()
total_capacity_hours = fields.Float()
reserved_capacity_hours = fields.Float()
available_capacity_hours = fields.Float(
help="min(total_capacity-reserved_capacity, project_capacity_hours)"
)


#
# """SELECT sum(hours)
# FROM project_task_load_unit
# GROUP BY week
# WHERE project_id in %s"""
#
# """SELECT sum(hours)
# FROM project_user_capacity_unit
# GROUP BY week
# WHERE capacity_id in %s"""
#
# # capacité réservé par autre
# """SELECT sum(hours)
# FROM project_user_capacity_unit
# GROUP BY week
# WHERE capacity_id not in %s"""
#
# # capacité total
# """SELECT sum(hours)
# FROM project_user_capacity_unit
# GROUP BY week
# """
5 changes: 5 additions & 0 deletions project_workload/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_edit_project_user_capacity,edit project user capacity,model_project_user_capacity,project.group_project_user,1,1,1,1
access_edit_project_user_capacity_line,edit project user capacity line,model_project_user_capacity_line,project.group_project_user,1,1,1,1
access_edit_project_capacity_unit,edit project capacity unit,model_project_capacity_unit,project.group_project_user,1,1,1,1
access_edit_project_task_workload,edit project task workload,model_project_task_workload,project.group_project_user,1,1,1,1
1 change: 1 addition & 0 deletions project_workload/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_workload
Loading

0 comments on commit cc829bd

Please sign in to comment.