Skip to content

Commit

Permalink
[FIX] project_consumable: take care timesheet attendance report
Browse files Browse the repository at this point in the history
  • Loading branch information
petrus-v committed Jan 21, 2025
1 parent 63535e0 commit 293b4f9
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 29 deletions.
2 changes: 1 addition & 1 deletion project_consumable/models/account_analytic_line.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2021 - Pierre Verkest
# Copyright 2021-2025 - Pierre Verkest
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo import models
Expand Down
2 changes: 1 addition & 1 deletion project_consumable/models/product_template.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2021 - Pierre Verkest
# Copyright 2021-2025 - Pierre Verkest
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models

Expand Down
2 changes: 1 addition & 1 deletion project_consumable/models/project_task.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2021 - Pierre Verkest
# Copyright 2021-2025 - Pierre Verkest
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import fields, models

Expand Down
1 change: 1 addition & 0 deletions project_consumable/report/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from . import hr_timesheet_attendance_report
from . import timesheets_analysis_report
64 changes: 64 additions & 0 deletions project_consumable/report/hr_timesheet_attendance_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright 2025 - Pierre Verkest
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from odoo import models, tools


class TimesheetAttendance(models.Model):
_inherit = "hr.timesheet.attendance.report"

def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
self._cr.execute(
"""CREATE OR REPLACE VIEW %s AS (
SELECT
max(id) AS id,
t.employee_id,
t.date,
t.company_id,
coalesce(sum(t.attendance), 0) AS total_attendance,
coalesce(sum(t.timesheet), 0) AS total_timesheet,
coalesce(
sum(t.attendance), 0) - coalesce(sum(t.timesheet),
0
) as total_difference,
NULLIF(sum(t.timesheet) * t.emp_cost, 0) as timesheets_cost,
NULLIF(sum(t.attendance) * t.emp_cost, 0) as attendance_cost,
NULLIF(
(coalesce(sum(t.attendance), 0) - coalesce(sum(t.timesheet), 0))
* t.emp_cost,
0
) as cost_difference
FROM (
SELECT
-hr_attendance.id AS id,
hr_employee.hourly_cost AS emp_cost,
hr_attendance.employee_id AS employee_id,
hr_attendance.worked_hours AS attendance,
NULL AS timesheet,
hr_attendance.check_in::date AS date,
hr_employee.company_id as company_id
FROM hr_attendance
LEFT JOIN hr_employee ON hr_employee.id = hr_attendance.employee_id
UNION ALL
SELECT
ts.id AS id,
hr_employee.hourly_cost AS emp_cost,
ts.employee_id AS employee_id,
NULL AS attendance,
ts.unit_amount AS timesheet,
ts.date AS date,
ts.company_id AS company_id
FROM account_analytic_line AS ts
LEFT JOIN hr_employee ON hr_employee.id = ts.employee_id
WHERE ts.project_id IS NOT NULL
-- change start
AND ts.product_id IS NULL
-- change end
) AS t
GROUP BY t.employee_id, t.date, t.company_id, t.emp_cost
ORDER BY t.date
)
"""
% self._table
)
1 change: 1 addition & 0 deletions project_consumable/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2021 - Pierre Verkest
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

from . import test_project_consumable_report
from . import test_project_consumable
26 changes: 0 additions & 26 deletions project_consumable/tests/test_project_consumable.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,29 +147,3 @@ def test_consumable_count(self):
)
)
self.assertEqual(self.project.consumable_count, 1)

def test_timesheet_analysis_report_exclude_consumable(self):
self.env["account.analytic.line"].create(
{
"name": "test timesheet",
"project_id": self.project.id,
"unit_amount": 3,
"employee_id": self.employee.id,
}
)
self.env["account.analytic.line"].create(
self._prepare_consumable_line_data(
unit_amount=7,
product_uom_id=self.env.ref(
"project_consumable.uom_cat_coffee_capsule_box_10"
).id,
)
)
analysis = self.env["timesheets.analysis.report"].search(
[
("project_id", "=", self.project.id),
("employee_id", "=", self.employee.id),
]
)
self.assertEqual(len(analysis), 1)
self.assertEqual(analysis.unit_amount, 3)
117 changes: 117 additions & 0 deletions project_consumable/tests/test_project_consumable_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Copyright 2021 - Pierre Verkest
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import datetime

from odoo.tests import TransactionCase


class TestProjectConsumableReporting(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.product = cls.env.ref("project_consumable.product_coffee_capsule")
default_plan_id = cls.env["account.analytic.plan"].search([], limit=1)
cls.analytic_account = cls.env["account.analytic.account"].create(
{
"name": "Test",
"plan_id": default_plan_id.id,
"company_id": cls.env.company.id,
}
)
cls.project = cls.env["project.project"].create(
{
"name": "Test",
"analytic_account_id": cls.analytic_account.id,
"company_id": cls.env.company.id,
}
)
cls.user_demo = cls.env.ref("base.user_demo")
cls.employee = cls.user_demo.employee_id

cls.env["hr.attendance"].create(
{
"employee_id": cls.employee.id,
"check_in": datetime(2022, 2, 9, 8, 0), # Wednesday
"check_out": datetime(2022, 2, 9, 16, 0),
}
)

def _prepare_consumable_line_data(self, **kwargs):
data = {
"name": "collect test material",
"project_id": self.project.id,
"account_id": None,
"product_id": self.product.id,
"unit_amount": 6,
"employee_id": self.employee.id,
"product_uom_id": self.product.uom_id.id,
"task_id": None,
"amount": None,
"date": None,
"partner_id": None,
}
data.update(**kwargs)
return {k: v for k, v in data.items() if v is not None}

def test_timesheet_analysis_report_exclude_consumable(self):
self.env["account.analytic.line"].create(
{
"name": "test timesheet",
"project_id": self.project.id,
"unit_amount": 3,
"employee_id": self.employee.id,
}
)
self.env["account.analytic.line"].create(
self._prepare_consumable_line_data(
unit_amount=7,
product_uom_id=self.env.ref(
"project_consumable.uom_cat_coffee_capsule_box_10"
).id,
)
)
analysis = self.env["timesheets.analysis.report"].search(
[
("project_id", "=", self.project.id),
("employee_id", "=", self.employee.id),
]
)
self.assertEqual(len(analysis), 1)
self.assertEqual(analysis.unit_amount, 3)

def test_timesheet_attendance_report_with_consumable(self):
self.env["account.analytic.line"].with_user(self.user_demo).create(
{
"name": "Test timesheet 1",
"project_id": self.project.id,
"unit_amount": 6.0,
"date": datetime(2022, 2, 9),
}
)
self.env["account.analytic.line"].create(
self._prepare_consumable_line_data(
unit_amount=7,
product_uom_id=self.env.ref(
"project_consumable.uom_cat_coffee_capsule_box_10"
).id,
employee_id=self.employee.id,
date=datetime(2022, 2, 9),
)
)
total_timesheet, total_attendance = self.env[
"hr.timesheet.attendance.report"
]._read_group(
[
("employee_id", "=", self.employee.id),
("date", ">=", datetime(2022, 2, 9, 8, 0)),
("date", "<=", datetime(2022, 2, 9, 16, 0)),
],
aggregates=["total_timesheet:sum", "total_attendance:sum"],
)[0]
self.assertEqual(
total_timesheet, 6.0, "Total timesheet in report should be 4.0"
)
self.assertEqual(
total_attendance, 7.0, "Total attendance in report should be 8.0"
)
self.assertEqual(total_attendance - total_timesheet, 1)

0 comments on commit 293b4f9

Please sign in to comment.