Skip to content

Commit

Permalink
TA#72205 [16.0][MIG] project_time_range
Browse files Browse the repository at this point in the history
  • Loading branch information
lanto-razafindrabe committed Dec 17, 2024
1 parent d94eab5 commit b355230
Show file tree
Hide file tree
Showing 22 changed files with 698 additions and 0 deletions.
1 change: 1 addition & 0 deletions .docker_files/main/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"project_task_deadline_from_project",
"project_task_editable_list_view",
"project_task_full_text_search",
"project_time_range",
"project_track_end_date",
"project_type_advanced",
"project_default_task_stage",
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ COPY project_task_date_planned /mnt/extra-addons/project_task_date_planned
COPY project_task_deadline_from_project /mnt/extra-addons/project_task_deadline_from_project
COPY project_task_editable_list_view /mnt/extra-addons/project_task_editable_list_view
COPY project_task_full_text_search /mnt/extra-addons/project_task_full_text_search
COPY project_time_range /mnt/extra-addons/project_time_range
COPY project_track_end_date /mnt/extra-addons/project_track_end_date
COPY project_type_advanced /mnt/extra-addons/project_type_advanced
COPY project_default_task_stage /mnt/extra-addons/project_default_task_stage
Expand Down
47 changes: 47 additions & 0 deletions project_time_range/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
Project Time Range
==================

.. contents:: Table of Contents

Tasks
-----
The module adds the fields `Min` and `Max` under the `Timesheets` tab of a task.

.. image:: static/description/task_form.png

These fields contain the minimum and maximum time initialy estimated on the task.
The field `Initially Planned Hours` is renamed `Ideal`.

The fields are also added to the list view of tasks.

.. image:: static/description/task_list.png

They are also added to the portal view of a task.

.. image:: static/description/portal_task.png

Subtasks
--------
Also, if a task has subtasks, the sum of time estimated on subtasks is also displayed.

.. image:: static/description/task_form_with_subtasks.png

Projects
--------
On projects, a ``Management`` tab is added.

This tab contains the sum of hours of all tasks under the project.

.. image:: static/description/project_form.png

The time ``Spent`` is the sum of hours on analytic lines.

The time ``Remaining`` is the ``Ideal`` time minus the time ``Spent``.

The fields are also added to the list view of projects.

.. image:: static/description/project_list.png

Contributors
------------
* Numigi (tm) and all its contributors (https://bit.ly/numigiens)
4 changes: 4 additions & 0 deletions project_time_range/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import models

Check notice on line 4 in project_time_range/__init__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_time_range/__init__.py#L4

'.models' imported but unused (F401)
16 changes: 16 additions & 0 deletions project_time_range/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

{

Check warning on line 4 in project_time_range/__manifest__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_time_range/__manifest__.py#L4

Statement seems to have no effect
"name": "Project Time Range",
"version": "16.0.1.0.0",
"author": "Numigi",
"maintainer": "Numigi",
"website": "https://bit.ly/numigi-com",
"license": "LGPL-3",
"category": "Project",
"summary": "Add fields Min and Max on project tasks.",
"depends": ["hr_timesheet"],
"data": ["views/portal.xml", "views/project_task.xml", "views/project_project.xml"],
"installable": True,
}
165 changes: 165 additions & 0 deletions project_time_range/i18n/fr.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_task_time_range
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-02-24 14:59+0000\n"
"PO-Revision-Date: 2021-02-24 10:06-0500\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: \n"
"X-Generator: Poedit 2.0.6\n"

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.portal_my_task_with_time_range
msgid "<strong>Ideal:</strong>"
msgstr "<strong>Idéal:</strong>"

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.portal_my_task_with_time_range
msgid "<strong>Max:</strong>"
msgstr "<strong>Max:</strong>"

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.portal_my_task_with_time_range
msgid "<strong>Min:</strong>"
msgstr "<strong>Min:</strong>"

#. module: project_time_range
#: model:ir.model.fields,field_description:project_time_range.field_project_project__consumed_hours
msgid "Consumed Hours"
msgstr "Heures consommées"

#. module: project_time_range
#: code:addons/project_time_range/models/project_task.py:51
#, python-format
msgid "Hours must be positive numbers."
msgstr "Les heures doivent être des nombres positives."

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.project_form
#: model_terms:ir.ui.view,arch_db:project_time_range.project_list
#: model_terms:ir.ui.view,arch_db:project_time_range.project_task_form
#: model_terms:ir.ui.view,arch_db:project_time_range.project_task_list
msgid "Ideal"
msgstr "Idéal"

#. module: project_time_range
#: model:ir.model.fields,field_description:project_time_range.field_project_project__planned_hours
#: model:ir.model.fields,field_description:project_time_range.field_project_task__planned_hours
msgid "Ideal Planned Hours"
msgstr "Heures planifiées idéal"

#. module: project_time_range
#: model:ir.model.fields,help:project_time_range.field_project_task__planned_hours
msgid ""
"It is the time planned to achieve the task. If this document has sub-tasks, "
"it means the time needed to achieve this tasks and its childs."
msgstr ""

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.project_form
msgid "Management"
msgstr "Pilotage"

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.project_form
#: model_terms:ir.ui.view,arch_db:project_time_range.project_list
#: model_terms:ir.ui.view,arch_db:project_time_range.project_task_form
#: model_terms:ir.ui.view,arch_db:project_time_range.project_task_list
msgid "Max"
msgstr "Max"

#. module: project_time_range
#: code:addons/project_time_range/models/project_task.py:38
#, python-format
msgid "Max Hours must be greater than the planned hours."
msgstr "Les heures Max doivent être supérieures aux heures idéales."

#. module: project_time_range
#: model:ir.model.fields,field_description:project_time_range.field_project_project__max_hours
#: model:ir.model.fields,field_description:project_time_range.field_project_task__max_hours
msgid "Maximum Planned Hours"
msgstr "Heures maximales plannifiées"

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.project_form
#: model_terms:ir.ui.view,arch_db:project_time_range.project_list
#: model_terms:ir.ui.view,arch_db:project_time_range.project_task_form
#: model_terms:ir.ui.view,arch_db:project_time_range.project_task_list
msgid "Min"
msgstr "Min"

#. module: project_time_range
#: code:addons/project_time_range/models/project_task.py:45
#, python-format
msgid "Min Hours must be lesser than the planned hours."
msgstr "Les heures Min doivent être inférieures aux heures idéales."

#. module: project_time_range
#: model:ir.model.fields,field_description:project_time_range.field_project_project__min_hours
#: model:ir.model.fields,field_description:project_time_range.field_project_task__min_hours
msgid "Minimum Planned Hours"
msgstr "Heures minimales plannifiées"

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.project_task_form
msgid "Planned Hours"
msgstr "Heures planifiées idéales"

#. module: project_time_range
#: model:ir.model,name:project_time_range.model_project_project
msgid "Project"
msgstr "Projet"

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.project_form
#: model_terms:ir.ui.view,arch_db:project_time_range.project_list
msgid "Remaining"
msgstr "Restant"

#. module: project_time_range
#: model:ir.model.fields,field_description:project_time_range.field_project_project__remaining_hours
msgid "Remaining Hours"
msgstr "Heures restantes"

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.project_form
#: model_terms:ir.ui.view,arch_db:project_time_range.project_list
msgid "Spent"
msgstr "Consommé"

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.project_task_form
msgid "Sub-tasks Hours"
msgstr "Heures planifiées des sous-tâches"

#. module: project_time_range
#: model:ir.model.fields,field_description:project_time_range.field_project_task__subtask_max_hours
msgid "Sub-tasks Max"
msgstr "Heures maximales des sous-tâches"

#. module: project_time_range
#: model:ir.model.fields,field_description:project_time_range.field_project_task__subtask_min_hours
msgid "Sub-tasks Min"
msgstr "Heures minimales des sous-tâches"

#. module: project_time_range
#: model:ir.model,name:project_time_range.model_project_task
msgid "Task"
msgstr "Tâche"

#. module: project_time_range
#: model_terms:ir.ui.view,arch_db:project_time_range.project_form
msgid "Time Range"
msgstr "Intervalle de temps"

#~ msgid "Initially Planned Hours"
#~ msgstr "Heures planifiées initialement"
4 changes: 4 additions & 0 deletions project_time_range/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import project_task, project_project

Check notice on line 4 in project_time_range/models/__init__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_time_range/models/__init__.py#L4

'.project_task' imported but unused (F401)
63 changes: 63 additions & 0 deletions project_time_range/models/project_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo import fields, models, api


class ProjectProject(models.Model):

_inherit = "project.project"

min_hours = fields.Float(
"Minimum Planned Hours", compute="_compute_time_range", store=True
)
max_hours = fields.Float(
"Maximum Planned Hours", compute="_compute_time_range", store=True
)
planned_hours = fields.Float(
string="Ideal Planned Hours", compute="_compute_time_range", store=True
)
consumed_hours = fields.Float(
string="Consumed Hours", compute="_compute_consumed_remaining_hours"
)
remaining_hours = fields.Float(
string="Remaining Hours", compute="_compute_consumed_remaining_hours"
)

@api.depends(
"task_ids",
"task_ids.min_hours",
"task_ids.max_hours",
"task_ids.planned_hours",
"task_ids.active",
"task_ids.parent_id",
)
def _compute_time_range(self):
for project in self:
tasks = project._get_parent_tasks()
project.min_hours = sum(tasks.mapped("min_hours"))
project.max_hours = sum(tasks.mapped("max_hours"))
project.planned_hours = sum(tasks.mapped("planned_hours"))

def _get_parent_tasks(self):
return (
self.env["project.task"]
.with_context({})
.search([("project_id", "=", self.id), ("parent_id", "=", False)])
)

def _compute_consumed_remaining_hours(self):
consumed_hours = self._get_consumed_hours_per_project()
for project in self:
project.consumed_hours = consumed_hours.get(project.id, 0)
project.remaining_hours = project.planned_hours - project.consumed_hours

def _get_consumed_hours_per_project(self):
self._cr.execute(
"SELECT project_id, sum(unit_amount) "
"FROM account_analytic_line "
"WHERE project_id in %s "
"GROUP BY project_id",
(tuple(self.ids),),
)
return dict(self._cr.fetchall())
57 changes: 57 additions & 0 deletions project_time_range/models/project_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).


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


class ProjectTask(models.Model):

_inherit = "project.task"

min_hours = fields.Float("Minimum Planned Hours")
max_hours = fields.Float("Maximum Planned Hours")
planned_hours = fields.Float(string="Ideal Planned Hours")

subtask_min_hours = fields.Float(
"Sub-tasks Min", compute="_compute_subtask_min_hours"
)
subtask_max_hours = fields.Float(
"Sub-tasks Max", compute="_compute_subtask_max_hours"
)

def _compute_subtask_min_hours(self):
for task in self:
task.subtask_min_hours = sum(task.child_ids.mapped("min_hours"))

def _compute_subtask_max_hours(self):
for task in self:
task.subtask_max_hours = sum(task.child_ids.mapped("max_hours"))

@api.constrains("planned_hours", "max_hours")
@time_range_constraint
def _check_max_hours(self):
if self.max_hours < self.planned_hours:
raise ValidationError(
_("Max Hours must be greater than the planned hours.")
)

@api.constrains("planned_hours", "min_hours")
@time_range_constraint
def _check_min_hours(self):
if self.min_hours > self.planned_hours:
raise ValidationError(_("Min Hours must be lesser than the planned hours."))

@api.constrains("planned_hours", "min_hours", "max_hours")
@time_range_constraint
def _check_positive_hours(self):
if any(h < 0 for h in [self.min_hours, self.planned_hours, self.max_hours]):
raise ValidationError(_("Hours must be positive numbers."))

@api.model
def create(self, vals):
if "max_hours" not in vals:
vals["max_hours"] = vals.get("planned_hours")
return super().create(vals)
16 changes: 16 additions & 0 deletions project_time_range/models/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2023 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import threading
import functools


def time_range_constraint(method):
@functools.wraps(method)
def wrapper(self):
is_testing = getattr(threading.currentThread(), "testing", False)
if self._context.get("enable_task_max_hours_constraint") or not is_testing:
for task in self:
method(task)

return wrapper
Binary file added project_time_range/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
Loading

0 comments on commit b355230

Please sign in to comment.