Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[14.0] [ADD] project_workload #291

Open
wants to merge 18 commits into
base: 14.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2234ed4
[ADD] project_workload
paradoxxxzero Feb 7, 2024
a99040e
[IMP] project_workload: Use compute to sync tasks, workloads and units.
paradoxxxzero Feb 7, 2024
8e86a6f
[IMP] project_workload: Improve workload sync inheritance
paradoxxxzero Feb 7, 2024
cb2ec80
[ADD] project_workload_additions
paradoxxxzero Feb 7, 2024
018f0cc
[FIX] project_workload: Fix compute being emptied and fix ui
paradoxxxzero Feb 12, 2024
eb6fe4f
[FIX] project_workload_additions: Add additional workload in forms an…
paradoxxxzero Feb 12, 2024
92f7072
[IMP] project_workload_timesheet: Add workload units in timesheet she…
paradoxxxzero Feb 12, 2024
895e5b6
[ADD] projectt_workload_timesheet_additions
paradoxxxzero Feb 12, 2024
1fb98b7
[IMP] project_workload: Improve naming
paradoxxxzero Feb 15, 2024
b65ed85
[IMP] project_workload_additions: Improve naming
paradoxxxzero Feb 15, 2024
d5e6e6f
[IMP] project_workload_timesheet: Link timesheet lines with workload …
paradoxxxzero Feb 15, 2024
8fd9f2b
[IMP] project_workload_timesheet_additions: Adapt unit sheet lines re…
paradoxxxzero Feb 15, 2024
ad310c4
[ADD] project_workload_milestone
paradoxxxzero Mar 5, 2024
391d7c1
[LINT]
paradoxxxzero Mar 5, 2024
2f229bc
project_workload_milestone: fix check when start_date is empty, fix c…
sebastienbeau Jul 5, 2024
13b760c
Update copier
sebastienbeau Jul 5, 2024
7955d70
project_workload: several fix and simplify code
sebastienbeau Jul 9, 2024
1cf7476
project_workload: add view to see load, automatically set done load w…
sebastienbeau Sep 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
exclude: |
(?x)
# NOT INSTALLABLE ADDONS
^project_workload_capacity/|
# END NOT INSTALLABLE ADDONS
# Files and folders generated by bots, to avoid loops
^setup/|/static/description/index\.html$|
Expand Down
74 changes: 74 additions & 0 deletions project_workload/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
================
Project Workload
================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:a45619f62875e2d11a3144636fa1343f1edcec7ab82c4f6ee3a39111fd50a612
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |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-akretion%2Fak--odoo--incubator-lightgray.png?logo=github
:target: https://github.com/akretion/ak-odoo-incubator/tree/14.0/project_workload
:alt: akretion/ak-odoo-incubator

|badge1| |badge2| |badge3|

This module allow to manage load and capacity by project and cross project
Load is managed by week

.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_

**Table of contents**

.. contents::
:local:

Known issues / Roadmap
======================

TODO
- in case of manual workload assignation add a check on date

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/akretion/ak-odoo-incubator/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
`feedback <https://github.com/akretion/ak-odoo-incubator/issues/new?body=module:%20project_workload%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

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

Credits
=======

Authors
~~~~~~~

* Akretion

Contributors
~~~~~~~~~~~~

* `Akretion <https://www.akretion.com>`_:
* BEAU Sébastien <sebastien.beau@akretion.com>
* Florian Mounier <florian.mounier@akretion.com>

Maintainers
~~~~~~~~~~~

This module is part of the `akretion/ak-odoo-incubator <https://github.com/akretion/ak-odoo-incubator/tree/14.0/project_workload>`_ project on GitHub.

You are welcome to contribute.
4 changes: 4 additions & 0 deletions project_workload/TODO
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- propager les dates des workload depuis la tache
- avoir une vision du lien "planifié" vs "fait"
- avoir une vision d'un bilan d'un sprint
- avoir une vision de sa charge de travail
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
27 changes: 27 additions & 0 deletions project_workload/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2024 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# @author Florian Mounier <florian.mounier@akretion.com>
# 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",
"depends": [
"project_timeline",
],
"data": [
"security/ir.model.access.csv",
"security/project_workload_security.xml",
"views/project_task_workload_view.xml",
"views/project_task_view.xml",
"views/project_project_view.xml",
"views/project_task_workload_unit_view.xml",
"views/menu_view.xml",
],
}
4 changes: 4 additions & 0 deletions project_workload/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import project_task_workload
from . import project_workload_unit
from . import project_project
from . import project_task
12 changes: 12 additions & 0 deletions project_workload/models/project_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright 2024 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# @author Florian Mounier <florian.mounier@akretion.com>
# 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()
102 changes: 102 additions & 0 deletions project_workload/models/project_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Copyright 2024 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# @author Florian Mounier <florian.mounier@akretion.com>
# 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"

workload_ids = fields.One2many(
"project.task.workload",
"task_id",
"Task",
compute="_compute_workload_ids",
store=True,
)
workload_unit_ids = fields.One2many(
"project.workload.unit",
compute="_compute_workload_unit_ids",
string="Workload Units",
)
use_workload = fields.Boolean(related="project_id.use_workload")
config_workload_manually = fields.Boolean()

@api.depends(
"date_start",
"date_end",
"planned_hours",
"user_id",
"config_workload_manually",
"use_workload",
)
def _compute_workload_ids(self):
for record in self:
if not record.use_workload:
continue

# Handle only automatic config in planned task
if record.config_workload_manually or not (
record.date_start and record.date_end and record.planned_hours
):
continue
record.workload_ids = record._get_workload_sync()

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

def _get_workload_sync(self):
self.ensure_one()
return [
*[(0, 0, vals) for vals in self._get_new_workloads()],
*[
(1, workload_id.id, vals)
for workload_id, vals in self._get_updated_workloads()
],
*[(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()
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.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(workloads) > 1:
return workloads[1:]
return []

@api.depends("workload_ids.unit_ids")
def _compute_workload_unit_ids(self):
# related doesn't retrieve all the data so we need to compute it
for record in self:
record.workload_unit_ids = record.workload_ids.unit_ids
121 changes: 121 additions & 0 deletions project_workload/models/project_task_workload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Copyright 2024 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <sebastien.beau@akretion.com>
# @author Florian Mounier <florian.mounier@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import re
from datetime import timedelta

from odoo import _, api, fields, models
from odoo.exceptions import ValidationError

WEEK_FORMAT = "%Y-%W"


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


week_merge_re = re.compile(r"(\d{4})-(\d{2}) - (\1)-(\d{2})")


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

project_id = fields.Many2one(
"project.project", "Project", related="task_id.project_id", store=True
)
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)
unit_ids = fields.One2many(
"project.workload.unit",
"workload_id",
string="Units",
compute="_compute_unit_ids",
store=True,
)

@api.constrains("date_start", "date_end")
def _check_end_date(self):
for task in self:
if task.date_end < task.date_start:
raise ValidationError(
_("The end date cannot be earlier than the start date.")
)

@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)
if not record.date_start or not record.date_end or not record.hours:
continue
record._check_end_date()
hours_per_week = record._get_hours_per_week()
unit_per_week = {wl.week: wl for wl in record.unit_ids}
commands = []
for week, hours in hours_per_week.items():
unit = unit_per_week.get(week)
if unit:
if unit.hours != hours:
# Update unit
commands.append((1, unit.id, {"hours": hours}))
else:
# Create unit
commands.append(
(
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,
},
)
)
for week, unit in unit_per_week.items():
if week not in hours_per_week:
# Remove not in week anymore unit
commands.append((2, unit.id))

record.unit_ids = commands

def _get_hours_per_week(self):
weeks = set()
date = self.date_start
while True:
weeks.add(week_name(date))
date += timedelta(days=7)
if date > self.date_end:
break
# For now a simple stupid split is done by week
# we do not care of the exact start / stop date
hours = self.hours / len(weeks)
return {week: hours for week in weeks}

def name_get(self):
result = []
for load in self:
if not load.date_start or not load.date_end:
continue
week_start = week_name(load.date_start)
end = load.date_end
if load.date_start.weekday() > load.date_end.weekday():
end -= timedelta(days=7)
week_end = week_name(end)
name = f"{load.task_id.name}: {week_start}"
if week_end > week_start:
name += f" - {week_end}"
name = week_merge_re.sub(r"\1-\2->\4", name)
result.append((load.id, name))
return result
Loading
Loading