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

TA#65338 [14.0][ADD] project_milestone_spent_hours #1

Merged
merged 5 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 34 additions & 0 deletions project_milestone_spent_hours/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
Project Milestone Spent Hours
=================================

.. contents:: Table of Contents

Context
-------
The module `project_milestone <https://github.com/OCA/project/tree/12.0/project_milestone>`_ allows to define milestones for a project.

Multiple tasks in the project can be linked to a given milestone.

Field total hours is displayed in form and list view of a project milestone and in tab of milestones of a project

Description
-----------
Field Total Hours is the sum of timesheets of active tasks associated to the milestone

Overview
--------

I create timesheets and set duration for 2 tasks associated to the same milestone

.. image:: project_milestone_spent_hours/static/description/task1.png

.. image:: project_milestone_spent_hours/static/description/task2.png

I open the milestone, the field Total hours is set with sum of timesheets spent hours of associated tasks

.. image:: project_milestone_spent_hours/static/description/milestone.png


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

from . import models
20 changes: 20 additions & 0 deletions project_milestone_spent_hours/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
"name": "Project Milestone Spent Hours",
"version": "14.0.1.0.0",
"author": "Numigi, Odoo Community Association (OCA)",
"maintainer": "Numigi",
"website": "https://github.com/OCA/project",
"license": "AGPL-3",
"category": "Project",
"summary": """Add field Total Hours in milestones which display the sum
of all hours of tasks associated to the milestone""",
"depends": ["hr_timesheet", "project_milestone"],
"data": [
"views/project_milestone.xml",
"views/project.xml",
],
"installable": True,
}
37 changes: 37 additions & 0 deletions project_milestone_spent_hours/i18n/fr.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * project_milestone_spent_hours
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-03-28 15:25+0000\n"
"PO-Revision-Date: 2022-03-28 15:25+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"

#. module: project_milestone_spent_hours
#: model:ir.model,name:project_milestone_spent_hours.model_account_analytic_line
msgid "Analytic Line"
msgstr "Ligne analytique"

#. module: project_milestone_spent_hours
#: model:ir.model.fields,field_description:project_milestone_spent_hours.field_account_analytic_line__milestone_id
msgid "Milestone"
msgstr "Jalon"

#. module: project_milestone_spent_hours
#: model:ir.model,name:project_milestone_spent_hours.model_project_milestone
msgid "Project Milestone"
msgstr "Jalon du projet"

#. module: project_milestone_spent_hours
#: model:ir.model.fields,field_description:project_milestone_spent_hours.field_project_milestone__total_hours
msgid "Total Hours"
msgstr "Heures passées"

4 changes: 4 additions & 0 deletions project_milestone_spent_hours/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import project_milestone, account_analytic_line
17 changes: 17 additions & 0 deletions project_milestone_spent_hours/models/account_analytic_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models


class AccountAnalyticLine(models.Model):
_inherit = "account.analytic.line"

milestone_id = fields.Many2one(
"project.milestone",
related="task_id.milestone_id",
string="Milestone",
index=True,
compute_sudo=True,
store=True,
)
46 changes: 46 additions & 0 deletions project_milestone_spent_hours/models/project_milestone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class ProjectMilestone(models.Model):
_inherit = "project.milestone"

active = fields.Boolean("Active", default=True)
total_hours = fields.Float(
compute="_compute_total_hours",
string="Total Hours",
compute_sudo=True,
store=True,
)

def write(self, vals):
res = super(ProjectMilestone, self).write(vals)
if "project_id" in vals:
self._remove_task_milestones(vals["project_id"])
return res

def _remove_task_milestones(self, project_id):
self.with_context(active_test=False).mapped("project_task_ids").filtered(
lambda milestone: not project_id or milestone.project_id.id != project_id
).write({"milestone_id": False})

@api.depends(
"project_task_ids",
"project_task_ids.active",
"project_task_ids.milestone_id",
"project_task_ids.timesheet_ids",
"project_task_ids.timesheet_ids.unit_amount",
"active",
)
def _compute_total_hours(self):
for record in self:
total_hours = 0.0
if record.active:
total_hours = sum(
record.project_task_ids.filtered(lambda milestone: milestone.active)
.mapped("timesheet_ids")
.mapped("unit_amount")
)
record.total_hours = total_hours
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.
4 changes: 4 additions & 0 deletions project_milestone_spent_hours/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from . import test_spent_hours
74 changes: 74 additions & 0 deletions project_milestone_spent_hours/tests/test_spent_hours.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Copyright 2024-today Numigi and all its contributors (https://bit.ly/numigiens)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo.tests.common import SavepointCase


class TestMilestoneTotalHours(SavepointCase):
@classmethod
def setUpClass(cls):
super().setUpClass()

cls.project = cls.env["project.project"].create({"name": "My Project"})

cls.milestone_1 = cls.env["project.milestone"].create(
{"name": "My Milestone 1", "project_id": cls.project.id}
)

cls.milestone_2 = cls.env["project.milestone"].create(
{"name": "My Milestone 2", "project_id": cls.project.id}
)

cls.task = cls.env["project.task"].create(
{
"name": "My Task",
"project_id": cls.project.id,
"milestone_id": cls.milestone_1.id,
}
)

cls.analytic_line_1 = cls.env["account.analytic.line"].create(
{
"name": "My Timesheet 1",
"task_id": cls.task.id,
"unit_amount": 10,
"project_id": cls.project.id,
}
)

cls.analytic_line_2 = cls.env["account.analytic.line"].create(
{
"name": "My Timesheet 2",
"task_id": cls.task.id,
"unit_amount": 20,
"project_id": cls.project.id,
}
)

def test_propagate_milestone_on_analytic_line(self):
assert self.task.milestone_id & self.analytic_line_1.milestone_id

def test_update_milestone_total_hours_when_creating_analytic_line(self):
assert self.milestone_1.total_hours == 30

def test_update_milestone_total_hours_when_updating_analytic_line(self):
self.analytic_line_1.unit_amount = 20
assert self.milestone_1.total_hours == 40

def test_update_milestone_total_hours_when_removing_analytic_line(self):
self.analytic_line_1.unlink()
assert self.milestone_1.total_hours == 20

def test_update_milestone_total_hours_when_modifying_milestone_on_task(self):
self.task.milestone_id = self.milestone_2
assert self.milestone_1.total_hours == 0
assert self.milestone_2.total_hours == 30

def test_update_milestone_total_hours_when_task_inactive(self):
self.task.active = 0
assert self.milestone_1.total_hours == 0
assert self.milestone_2.total_hours == 0

def test_update_milestone_total_hours_when_remove_project(self):
self.milestone_1.project_id = False
assert self.milestone_1.total_hours == 0
21 changes: 21 additions & 0 deletions project_milestone_spent_hours/views/project.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- Form View -->
<record id="project_milestone_spent_hours_view_form" model="ir.ui.view">
<field name="name">Project Milestone Spent Hours Form</field>
<field name="model">project.project</field>
<field
name="inherit_id"
ref="project_milestone.project_enhancement_milestone_view_inherit_form"
/>
<field name="type">form</field>
<field name="arch" type="xml">
<xpath
expr="//page[@name='milestone_ids']/group/field[@name='milestone_ids']/tree/field[@name='project_task_ids']"
position="after"
>
<field name="total_hours" />
</xpath>
</field>
</record>
</odoo>
26 changes: 26 additions & 0 deletions project_milestone_spent_hours/views/project_milestone.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- List View-->
<record id="project_milestone_spent_hours_view_list" model="ir.ui.view">
<field name="name">Project Milestone Spent Hours List</field>
<field name="model">project.milestone</field>
<field name="inherit_id" ref="project_milestone.project_milestone_view_list" />
<field name="arch" type="xml">
<field name="project_task_ids" position="after">
<field name="total_hours" widget="float_time" />
</field>
</field>
</record>

<!-- Form View -->
<record id="project_milestone_spent_hours_form" model="ir.ui.view">
<field name="name">Project Milestone Spent Hours Form</field>
<field name="model">project.milestone</field>
<field name="inherit_id" ref="project_milestone.project_milestone_view_form" />
<field name="arch" type="xml">
<field name="target_date" position="after">
<field name="total_hours" widget="float_time" />
</field>
</field>
</record>
</odoo>
6 changes: 6 additions & 0 deletions setup/project_milestone_spent_hours/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import setuptools

setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)
Loading