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] new module project_task_report #225

Open
wants to merge 1 commit into
base: 16.0
Choose a base branch
from
Open
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
17 changes: 17 additions & 0 deletions project_task_report/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
===================
Project Task Report
===================

Task wizard to sum up the progress of some projects tasks between two dates

Usage
=====

To use this module, you need to:

#. Go to Project > Reporting > Tasks Report
#. Select the related Projects and the Start and End dates of the report
#. The resulting report is a list of the Tasks with new timesheets and/or with stage change between the two selected dates



1 change: 1 addition & 0 deletions project_task_report/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import wizards
17 changes: 17 additions & 0 deletions project_task_report/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2024 Akretion
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

{
"name": "Project Task Report",
"summary": """Task wizard to sum up the progress between two dates""",
"version": "16.0.1.0.0",
"license": "AGPL-3",
"author": "Akretion",
"website": "http://akretion.com",
"depends": ["project", "hr_timesheet"],
"data": [
"security/ir.model.access.csv",
"wizards/project_task_report.xml",
],
"demo": ["data/project_task_report_demo.xml"],
}
104 changes: 104 additions & 0 deletions project_task_report/data/project_task_report_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Tasks -->
<record id="task_1" model="project.task">
<field name="project_id" ref="project.project_project_1"/>
<field name="name">Task 1</field>
</record>
<record id="task_2" model="project.task">
<field name="project_id" ref="project.project_project_1"/>
<field name="name">Task 2</field>
<field name="create_date" eval="datetime(2023, 12, 1)"/>
</record>

<!-- Timesheets -->
<record id="timesheet_11" model="account.analytic.line">
<field name="name">Timesheet 1.1</field>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="date" eval="datetime(2024, 3, 2)"/>
<field name="task_id" ref="task_1"/>
<field name="unit_amount">1.00</field>
</record>
<record id="timesheet_21" model="account.analytic.line">
<field name="name">Timesheet 2.1</field>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="date" eval="datetime(2024, 2, 1)"/>
<field name="task_id" ref="task_2"/>
</record>
<record id="timesheet_22" model="account.analytic.line">
<field name="name">Timesheet 2.2</field>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="date" eval="datetime(2024, 3, 1)"/>
<field name="task_id" ref="task_2"/>
<field name="unit_amount">10.00</field>
</record>
<record id="timesheet_23" model="account.analytic.line">
<field name="name">Timesheet 2.3</field>
<field name="employee_id" ref="hr.employee_qdp"/>
<field name="date" eval="datetime(2024, 3, 2)"/>
<field name="task_id" ref="task_2"/>
<field name="unit_amount">2.00</field>
</record>

<!-- Historic stage changes on Task 2 -->
<record id="task_2_mail_message_1" model="mail.message">
<field name="model">project.task</field>
<field name="res_id" ref="task_2"/>
<field name="message_type">notification</field>
<field name="date" eval="datetime(2024, 1, 1)"/>
<field name="subtype_id" ref="project.mt_task_stage"/>
<field name="author_id" ref="base.partner_admin"/>
</record>
<record id="task_2_mail_message_1_track" model="mail.tracking.value">
<field name="field" model="ir.model.fields" eval="obj().search([('model', '=', 'project.task'), ('name', '=', 'stage_id')])"/>
<field name="field_desc">Stage</field>
<field name="old_value_char">New</field>
<field name="new_value_char">In Progress</field>
<field name="field_type">many2one</field>
<field name="old_value_integer">1</field>
<field name="new_value_integer">2</field>
<field name="mail_message_id" ref="task_2_mail_message_1"/>
</record>

<record id="task_2_mail_message_2" model="mail.message">
<field name="model">project.task</field>
<field name="res_id" ref="task_2"/>
<field name="message_type">notification</field>
<field name="date" eval="datetime(2024, 4, 1)"/>
<field name="subtype_id" ref="project.mt_task_stage"/>
<field name="author_id" ref="base.partner_admin"/>
</record>
<record id="task_2_mail_message_2_track" model="mail.tracking.value">
<field name="field" model="ir.model.fields" eval="obj().search([('model', '=', 'project.task'), ('name', '=', 'stage_id')])"/>
<field name="field_desc">Stage</field>
<field name="old_value_char">In Progress</field>
<field name="new_value_char">Done</field>
<field name="field_type">many2one</field>
<field name="old_value_integer">2</field>
<field name="new_value_integer">3</field>
<field name="mail_message_id" ref="task_2_mail_message_2"/>
</record>

<record id="task_2_mail_message_3" model="mail.message">
<field name="model">project.task</field>
<field name="res_id" ref="task_2"/>
<field name="message_type">notification</field>
<field name="date" eval="datetime(2024, 5, 1)"/>
<field name="subtype_id" ref="project.mt_task_stage"/>
<field name="author_id" ref="base.partner_admin"/>
</record>
<record id="task_2_mail_message_3_track" model="mail.tracking.value">
<field name="field" model="ir.model.fields" eval="obj().search([('model', '=', 'project.task'), ('name', '=', 'stage_id')])"/>
<field name="field_desc">Stage</field>
<field name="old_value_char">Done</field>
<field name="new_value_char">Canceled</field>
<field name="field_type">many2one</field>
<field name="old_value_integer">3</field>
<field name="new_value_integer">4</field>
<field name="mail_message_id" ref="task_2_mail_message_3"/>
</record>


</data>
</odoo>
3 changes: 3 additions & 0 deletions project_task_report/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
project_task_report.access_project_task_report,access_project_task_report,project_task_report.model_project_task_report,base.group_user,1,1,1,1
project_task_report.access_project_task_report_line,access_project_task_report_line,project_task_report.model_project_task_report_line,base.group_user,1,1,1,1
1 change: 1 addition & 0 deletions project_task_report/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import test_project_task_report
38 changes: 38 additions & 0 deletions project_task_report/tests/test_project_task_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2024 Akretion
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from datetime import datetime
from odoo.tests.common import TransactionCase


class TestProjectTaskReport(TransactionCase):

def setUp(self):
super().setUp()
self.task_report_id = self.env["project.task.report"].create(
{
"start_date": datetime(2024, 2, 2),
"end_date": datetime(2024, 6, 1),
"project_ids": self.env.ref("project.project_project_1").ids,
}
)
line_ids = self.task_report_id._create_line_ids()
self.line_ids = line_ids.sorted(lambda l: l.task_id.name)

self.stage_new = self.env.ref("project.project_stage_0")
self.stage_in_progress = self.env.ref("project.project_stage_1")
self.stage_canceled = self.env.ref("project.project_stage_3")

def test_task_report_no_stage_change(self):
task_line_id = self.line_ids[0]
self.assertEqual(task_line_id.task_id.name, "Task 1")
self.assertEqual(task_line_id.hours_spent, 1)
self.assertEqual(task_line_id.start_stage_id, self.stage_new)
self.assertEqual(task_line_id.end_stage_id, self.stage_new)

def test_task_report_with_stage_change(self):
task_line_id = self.line_ids[1]
self.assertEqual(task_line_id.task_id.name, "Task 2")
self.assertEqual(task_line_id.hours_spent, 12)
self.assertEqual(task_line_id.start_stage_id, self.stage_in_progress)
self.assertEqual(task_line_id.end_stage_id, self.stage_canceled)
1 change: 1 addition & 0 deletions project_task_report/wizards/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import project_task_report
84 changes: 84 additions & 0 deletions project_task_report/wizards/project_task_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright 2024 Akretion
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models


class ProjectTaskReport(models.TransientModel):
_name = "project.task.report"
_description = "Task Report"

start_date = fields.Date()
end_date = fields.Date()
project_ids = fields.Many2many("project.project", string="Projects")

def _create_line_ids(self):
line_vals = []
task_ids = self.env["project.task"].search(
[("project_id", "in", self.project_ids.ids)]
)
field_stage_id = self.env["ir.model.fields"].search(
[("model", "=", "project.task"), ("name", "=", "stage_id")]
)

for task_id in task_ids:
line_val = {"task_id": task_id.id, "timesheet_ids": []}

# Catch the timesheets between the start and end dates
for timesheet_id in task_id.timesheet_ids:
if self.start_date < timesheet_id.date < self.end_date:
line_val["timesheet_ids"].append(timesheet_id.id)

# Catch the historic stage changes between the start and end dates
track_ids = task_id.message_ids.tracking_value_ids.filtered(
lambda t: t.field == field_stage_id
and self.start_date < t.mail_message_id.date.date() < self.end_date
).sorted(lambda t: t.mail_message_id.date)

if track_ids:
line_val.update(
{
"start_stage_id": track_ids[0].old_value_integer,
"end_stage_id": track_ids[-1].new_value_integer,
}
)

if line_val["timesheet_ids"] or line_val.get("start_stage_id"):
# Fill stage fields in case there have been timesheets without stage change
if not line_val.get("start_stage_id"):
line_val.update(
{
"start_stage_id": task_id.stage_id.id,
"end_stage_id": task_id.stage_id.id,
}
)
line_vals.append(line_val)

return self.env["project.task.report.line"].create(line_vals)

def action_view_task_report(self):
self.ensure_one()

line_ids = self._create_line_ids()

action_xml_id = "project_task_report.project_task_report_line_act_window"
action = self.env["ir.actions.act_window"]._for_xml_id(action_xml_id)
action.update({"domain": [("id", "in", line_ids.ids)]})
return action


class ProjectTaskReportLine(models.TransientModel):
_name = "project.task.report.line"
_description = "Task Report Line"

task_id = fields.Many2one("project.task", readonly=True)
start_stage_id = fields.Many2one("project.task.type", readonly=True)
end_stage_id = fields.Many2one("project.task.type", readonly=True)

timesheet_ids = fields.Many2many("account.analytic.line", readonly=True)
hours_spent = fields.Float(compute="_compute_hours_spent", store=True)

@api.depends("timesheet_ids.unit_amount")
def _compute_hours_spent(self):
for rec in self:
rec.hours_spent = sum(rec.timesheet_ids.mapped("unit_amount"))
82 changes: 82 additions & 0 deletions project_task_report/wizards/project_task_report.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 Akretion
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->

<odoo>

<record id="project_task_report_form_view" model="ir.ui.view">
<field name="model">project.task.report</field>
<field name="arch" type="xml">
<form string="Project Task Report">
<group>
<field name="start_date"/>
<field name="end_date"/>
<field name="project_ids" widget="many2many_tags"/>
</group>
<footer>
<button name="action_view_task_report"
string="Create Task Report"
class="btn-primary"
type="object"/>
<button string="Cancel"
class="btn-default"
special="cancel"/>
</footer>
</form>
</field>
</record>

<record model="ir.actions.act_window" id="project_task_report_act_window">
<field name="name">Tasks Report</field>
<field name="res_model">project.task.report</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>

<record id="project_task_report_line_tree_view" model="ir.ui.view">
<field name="name">project.task.report.line.tree</field>
<field name="model">project.task.report.line</field>
<field name="arch" type="xml">
<tree string="Task Report" create="false" delete="false" edit="false">
<field name="task_id"/>
<field name="start_stage_id"/>
<field name="end_stage_id"/>
<field name="hours_spent" widget="timesheet_uom"/>
</tree>
</field>
</record>

<record id="project_task_report_line_form_view" model="ir.ui.view">
<field name="model">project.task.report.line</field>
<field name="arch" type="xml">
<form string="Project Task Report Line">
<sheet>
<div class="oe_title">
<label for="task_id"/>
<h1><field name="task_id"/></h1>
</div>
<group class="col-12 col-lg-6">
<field name="start_stage_id"/>
<field name="end_stage_id"/>
</group>
<group class="col-12 col-lg-6">
<field name="timesheet_ids"/>
</group>
</sheet>
</form>
</field>
</record>

<record model="ir.actions.act_window" id="project_task_report_line_act_window">
<field name="name">Tasks Report</field>
<field name="res_model">project.task.report.line</field>
<field name="view_mode">tree,form</field>
</record>

<record model="ir.ui.menu" id="project_task_report_menu">
<field name="name">Tasks Report</field>
<field name="parent_id" ref="project.menu_project_report"/>
<field name="action" ref="project_task_report_act_window"/>
<field name="sequence" eval="10"/>
</record>
</odoo>