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

[16.0] [ADD] project_workload #315

Open
wants to merge 28 commits into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
18806e3
[ADD] project_workload
paradoxxxzero Feb 7, 2024
181c5e2
[IMP] project_workload: Use compute to sync tasks, workloads and units.
paradoxxxzero Feb 7, 2024
1246e40
[IMP] project_workload: Improve workload sync inheritance
paradoxxxzero Feb 7, 2024
ea6eb37
[ADD] project_workload_additions
paradoxxxzero Feb 7, 2024
562a5cc
[FIX] project_workload: Fix compute being emptied and fix ui
paradoxxxzero Feb 12, 2024
e0408f3
[FIX] project_workload_additions: Add additional workload in forms an…
paradoxxxzero Feb 12, 2024
c71a117
[IMP] project_workload_timesheet: Add workload units in timesheet she…
paradoxxxzero Feb 12, 2024
63e9024
[ADD] projectt_workload_timesheet_additions
paradoxxxzero Feb 12, 2024
cb68390
[IMP] project_workload: Improve naming
paradoxxxzero Feb 15, 2024
961abcf
[IMP] project_workload_additions: Improve naming
paradoxxxzero Feb 15, 2024
3a8cec0
[IMP] project_workload_timesheet: Link timesheet lines with workload …
paradoxxxzero Feb 15, 2024
ff9dd40
[IMP] project_workload_timesheet_additions: Adapt unit sheet lines re…
paradoxxxzero Feb 15, 2024
341f1c8
[ADD] project_workload_milestone
paradoxxxzero Mar 5, 2024
552b59a
[LINT]
paradoxxxzero Mar 5, 2024
e3077db
[MIG] project_workload: Migration to 16.0
paradoxxxzero Sep 30, 2024
987442b
[FIX] project_workload: project.task date_start, date_end renames
paradoxxxzero Oct 7, 2024
f28badf
[FIX] project_workload_milestone: project.task date_start, date_end r…
paradoxxxzero Oct 7, 2024
910d18d
project_workload: several fix and simplify code
sebastienbeau Jul 9, 2024
5b5623e
[IMP] project_workload: Make tests coherent with additions tests
paradoxxxzero Oct 7, 2024
38b7412
[IMP] project_workload_additions: Add multi users tests
paradoxxxzero Oct 7, 2024
ad2abec
[FIX] project_workload_milestone: project.task date_start, date_end r…
paradoxxxzero Oct 7, 2024
beba60e
[LINT]
paradoxxxzero Oct 7, 2024
f07fc3b
project_workload: add view to see load, automatically set done load w…
sebastienbeau Sep 23, 2024
dd4e9df
[FIX] project_workrload:Use project.task.is_closed
paradoxxxzero Oct 7, 2024
e495ce0
[FIX] project_workrload_additions: Fix obsolete additional workloads
paradoxxxzero Oct 7, 2024
3b3ccca
[FIX] project_workrload_additions: Use precompute on percentage
paradoxxxzero Oct 7, 2024
48982c2
[LINT] and documentation
paradoxxxzero Oct 7, 2024
4ef865e
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:4a176ece35168ce42257a5e9a7415205d47c52fdf0e5c5ebcd5986afc7b180c7
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |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/16.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:%2016.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 <[email protected]>
* Florian Mounier <[email protected]>

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

This module is part of the `akretion/ak-odoo-incubator <https://github.com/akretion/ak-odoo-incubator/tree/16.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 <[email protected]>
# @author Florian Mounier <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Project Workload",
"summary": "Ressource Workload Management",
"version": "16.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 <[email protected]>
# @author Florian Mounier <[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()
104 changes: 104 additions & 0 deletions project_workload/models/project_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# 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 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(
"planned_date_start",
"planned_date_end",
"planned_hours",
"user_ids",
"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.planned_date_start
and record.planned_date_end
and record.planned_hours
):
continue
record.workload_ids = record._get_workload_sync()

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

def _get_workload_sync(self):
self.ensure_one()
return [
*[(0, 0, vals) for vals in self._get_new_workloads()],
*[
(1, workload.id, vals)
for workload, vals in self._get_updated_workloads()
],
*[(2, workload.id) for workload 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()
# Handle only one workload per user in automatic
new_vals = []
for user in self.user_ids:
user_workload = workloads.filtered(lambda w: w.user_id == user)
if not user_workload:
new_vals.append(self._prepare_workload(user))

return new_vals

def _get_updated_workloads(self):
self.ensure_one()
workloads = self._get_main_workloads()
vals = []
# Update the users workload values
for workload in workloads:
if workload.user_id in self.user_ids:
vals.append((workload, self._prepare_workload(workload.user_id)))
return vals

def _get_obsolete_workloads(self):
self.ensure_one()
workloads = self._get_main_workloads()
# Remove other workloads, dedup for users?
return workloads.filtered(lambda w: w.user_id not in self.user_ids)

@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 <[email protected]>
# @author Florian Mounier <[email protected]>
# 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 load in self:
if load.date_end < load.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