From a9f6ef68bb2cbc0e75d6fa78dce8db2bb6ff9770 Mon Sep 17 00:00:00 2001 From: Emilio Pascual Date: Fri, 22 May 2026 09:27:31 +0200 Subject: [PATCH] [ADD] hr_timesheet_manager_approver: Allow approvers to manage subordinate timesheets Extend the `timesheet_line_rule_approver` record rule so that users with group_hr_timesheet_approver can read, write, create and delete timesheets of employees whose parent_id.user_id matches them. Add unit tests to verify CRUD access on subordinate timesheets while restricting access to non-subordinates. @moduon MT-14395 --- hr_timesheet_manager_approver/README.rst | 117 +++++ hr_timesheet_manager_approver/__init__.py | 2 + hr_timesheet_manager_approver/__manifest__.py | 19 + hr_timesheet_manager_approver/pyproject.toml | 3 + .../readme/CONTRIBUTORS.md | 1 + .../readme/DESCRIPTION.md | 6 + hr_timesheet_manager_approver/readme/USAGE.md | 10 + ...hr_timesheet_manager_approver_security.xml | 17 + .../static/description/index.html | 454 ++++++++++++++++++ .../tests/__init__.py | 4 + .../test_hr_timesheet_manager_approver.py | 145 ++++++ 11 files changed, 778 insertions(+) create mode 100644 hr_timesheet_manager_approver/README.rst create mode 100644 hr_timesheet_manager_approver/__init__.py create mode 100644 hr_timesheet_manager_approver/__manifest__.py create mode 100644 hr_timesheet_manager_approver/pyproject.toml create mode 100644 hr_timesheet_manager_approver/readme/CONTRIBUTORS.md create mode 100644 hr_timesheet_manager_approver/readme/DESCRIPTION.md create mode 100644 hr_timesheet_manager_approver/readme/USAGE.md create mode 100644 hr_timesheet_manager_approver/security/hr_timesheet_manager_approver_security.xml create mode 100644 hr_timesheet_manager_approver/static/description/index.html create mode 100644 hr_timesheet_manager_approver/tests/__init__.py create mode 100644 hr_timesheet_manager_approver/tests/test_hr_timesheet_manager_approver.py diff --git a/hr_timesheet_manager_approver/README.rst b/hr_timesheet_manager_approver/README.rst new file mode 100644 index 0000000000..adbe57057f --- /dev/null +++ b/hr_timesheet_manager_approver/README.rst @@ -0,0 +1,117 @@ +========================== +Timesheet Manager Approver +========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:a835f0b5c73ced76ef46f252021aa25d284d12f1de59606a45930c03c15f95d3 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |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-OCA%2Ftimesheet-lightgray.png?logo=github + :target: https://github.com/OCA/timesheet/tree/18.0/hr_timesheet_manager_approver + :alt: OCA/timesheet +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/timesheet-18-0/timesheet-18-0-hr_timesheet_manager_approver + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/timesheet&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the standard timesheet approver rules so that a user +with the group *User: all timesheets* can also manage the timesheets of +their subordinate employees (i.e. employees whose *Manager* field points +to the approver). + +It updates the ``timesheet_line_rule_approver`` record rule to include +the clause +``('employee_id.parent_id.user_id', 'in', (False, user.id))``. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module: + +1. Go to **Employees** and open the employee you want to set as a + manager. +2. In the *Work Information* tab, under *Employee*, set the **Timesheet + Responsible** or ensure the **Manager** field points to the approver + user. +3. Assign the *Timesheets / User: all timesheets* group to the manager + user (*Settings → Users & Companies → Users* → *Timesheets* section). +4. Log in as the manager user. +5. Go to **Timesheets**. +6. You will now see, edit and delete timesheets for employees who report + to you, even on projects with *Invited employees* privacy. Also you + can create but only in the projects where the user has access. + +**Important:** Without this module, a timesheet approver can only manage +timesheets on projects where they are a follower. With this module, the +approver also gains access to their subordinates' timesheets regardless +of project visibility. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub 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 `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Moduon + +Contributors +------------ + +- Emilio Pascual (`Moduon `__) + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-EmilioPascual| image:: https://github.com/EmilioPascual.png?size=40px + :target: https://github.com/EmilioPascual + :alt: EmilioPascual +.. |maintainer-rafaelbn| image:: https://github.com/rafaelbn.png?size=40px + :target: https://github.com/rafaelbn + :alt: rafaelbn + +Current `maintainers `__: + +|maintainer-EmilioPascual| |maintainer-rafaelbn| + +This module is part of the `OCA/timesheet `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_timesheet_manager_approver/__init__.py b/hr_timesheet_manager_approver/__init__.py new file mode 100644 index 0000000000..ad95df3dab --- /dev/null +++ b/hr_timesheet_manager_approver/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2025 Moduon Team SL +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). diff --git a/hr_timesheet_manager_approver/__manifest__.py b/hr_timesheet_manager_approver/__manifest__.py new file mode 100644 index 0000000000..18ff4514e9 --- /dev/null +++ b/hr_timesheet_manager_approver/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2025 Moduon Team SL +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Timesheet Manager Approver", + "version": "18.0.1.0.0", + "category": "Human Resources", + "summary": "Allow timesheet approvers to manage subordinate timesheets", + "license": "AGPL-3", + "author": "Moduon, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/timesheet", + "depends": ["hr_timesheet"], + "maintainers": ["EmilioPascual", "rafaelbn"], + "data": [ + "security/hr_timesheet_manager_approver_security.xml", + ], + "installable": True, + "auto_install": False, +} diff --git a/hr_timesheet_manager_approver/pyproject.toml b/hr_timesheet_manager_approver/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/hr_timesheet_manager_approver/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/hr_timesheet_manager_approver/readme/CONTRIBUTORS.md b/hr_timesheet_manager_approver/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..ee7b9a6c8b --- /dev/null +++ b/hr_timesheet_manager_approver/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Emilio Pascual ([Moduon](https://www.moduon.team/)) diff --git a/hr_timesheet_manager_approver/readme/DESCRIPTION.md b/hr_timesheet_manager_approver/readme/DESCRIPTION.md new file mode 100644 index 0000000000..90d09356c5 --- /dev/null +++ b/hr_timesheet_manager_approver/readme/DESCRIPTION.md @@ -0,0 +1,6 @@ +This module extends the standard timesheet approver rules so that a user with the +group *User: all timesheets* can also manage the timesheets of their subordinate +employees (i.e. employees whose *Manager* field points to the approver). + +It updates the ``timesheet_line_rule_approver`` record rule to include the clause +``('employee_id.parent_id.user_id', 'in', (False, user.id))``. diff --git a/hr_timesheet_manager_approver/readme/USAGE.md b/hr_timesheet_manager_approver/readme/USAGE.md new file mode 100644 index 0000000000..de99bedf0f --- /dev/null +++ b/hr_timesheet_manager_approver/readme/USAGE.md @@ -0,0 +1,10 @@ +To use this module: + +1. Go to **Employees** and open the employee you want to set as a manager. +2. In the *Work Information* tab, under *Employee*, set the **Timesheet Responsible** or ensure the **Manager** field points to the approver user. +3. Assign the *Timesheets / User: all timesheets* group to the manager user (*Settings → Users & Companies → Users* → *Timesheets* section). +4. Log in as the manager user. +5. Go to **Timesheets**. +6. You will now see, edit and delete timesheets for employees who report to you, even on projects with *Invited employees* privacy. Also you can create but only in the projects where the user has access. + +**Important:** Without this module, a timesheet approver can only manage timesheets on projects where they are a follower. With this module, the approver also gains access to their subordinates' timesheets regardless of project visibility. diff --git a/hr_timesheet_manager_approver/security/hr_timesheet_manager_approver_security.xml b/hr_timesheet_manager_approver/security/hr_timesheet_manager_approver_security.xml new file mode 100644 index 0000000000..da66c1540f --- /dev/null +++ b/hr_timesheet_manager_approver/security/hr_timesheet_manager_approver_security.xml @@ -0,0 +1,17 @@ + + + + + [ + ('project_id', '!=', False), + '|', '|', '|', + ('project_id.privacy_visibility', '!=', 'followers'), + ('project_id.message_partner_ids', 'in', [user.partner_id.id]), + ('task_id.message_partner_ids', 'in', [user.partner_id.id]), + ('employee_id.parent_id.user_id', '=', user.id) + ] + + diff --git a/hr_timesheet_manager_approver/static/description/index.html b/hr_timesheet_manager_approver/static/description/index.html new file mode 100644 index 0000000000..2663c1d0dc --- /dev/null +++ b/hr_timesheet_manager_approver/static/description/index.html @@ -0,0 +1,454 @@ + + + + + +Timesheet Manager Approver + + + +
+

Timesheet Manager Approver

+ + +

Beta License: AGPL-3 OCA/timesheet Translate me on Weblate Try me on Runboat

+

This module extends the standard timesheet approver rules so that a user +with the group User: all timesheets can also manage the timesheets of +their subordinate employees (i.e. employees whose Manager field points +to the approver).

+

It updates the timesheet_line_rule_approver record rule to include +the clause +('employee_id.parent_id.user_id', 'in', (False, user.id)).

+

Table of contents

+ +
+

Usage

+

To use this module:

+
    +
  1. Go to Employees and open the employee you want to set as a +manager.
  2. +
  3. In the Work Information tab, under Employee, set the Timesheet +Responsible or ensure the Manager field points to the approver +user.
  4. +
  5. Assign the Timesheets / User: all timesheets group to the manager +user (Settings → Users & Companies → UsersTimesheets section).
  6. +
  7. Log in as the manager user.
  8. +
  9. Go to Timesheets.
  10. +
  11. You will now see, edit and delete timesheets for employees who report +to you, even on projects with Invited employees privacy. Also you +can create but only in the projects where the user has access.
  12. +
+

Important: Without this module, a timesheet approver can only manage +timesheets on projects where they are a follower. With this module, the +approver also gains access to their subordinates’ timesheets regardless +of project visibility.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub 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.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Moduon
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainers:

+

EmilioPascual rafaelbn

+

This module is part of the OCA/timesheet project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/hr_timesheet_manager_approver/tests/__init__.py b/hr_timesheet_manager_approver/tests/__init__.py new file mode 100644 index 0000000000..cb73f364ba --- /dev/null +++ b/hr_timesheet_manager_approver/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2025 Moduon Team SL +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_hr_timesheet_manager_approver diff --git a/hr_timesheet_manager_approver/tests/test_hr_timesheet_manager_approver.py b/hr_timesheet_manager_approver/tests/test_hr_timesheet_manager_approver.py new file mode 100644 index 0000000000..0f947490a8 --- /dev/null +++ b/hr_timesheet_manager_approver/tests/test_hr_timesheet_manager_approver.py @@ -0,0 +1,145 @@ +# Copyright 2025 Moduon Team SL +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests import new_test_user + +from odoo.addons.base.tests.common import BaseCommon + + +class TestHrTimesheetManagerApprover(BaseCommon): + """Test that timesheet approvers can manage subordinate timesheets.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.manager_user = new_test_user( + cls.env, + login="manager_user", + groups="hr_timesheet.group_hr_timesheet_approver,project.group_project_user", + ) + cls.subordinate_user = new_test_user( + cls.env, + login="subordinate_user", + groups="hr_timesheet.group_hr_timesheet_user,project.group_project_user", + ) + cls.other_user = new_test_user( + cls.env, + login="other_user", + groups="hr_timesheet.group_hr_timesheet_user,project.group_project_user", + ) + cls.manager_employee = cls.env["hr.employee"].create( + { + "name": "Manager Employee", + "user_id": cls.manager_user.id, + } + ) + cls.subordinate_employee = cls.env["hr.employee"].create( + { + "name": "Subordinate Employee", + "user_id": cls.subordinate_user.id, + "parent_id": cls.manager_employee.id, + } + ) + cls.other_manager_user = new_test_user( + cls.env, + login="other_manager_user", + groups="hr_timesheet.group_hr_timesheet_approver,project.group_project_user", + ) + cls.other_manager_employee = cls.env["hr.employee"].create( + { + "name": "Other Manager Employee", + "user_id": cls.other_manager_user.id, + } + ) + cls.other_employee = cls.env["hr.employee"].create( + { + "name": "Other Employee", + "user_id": cls.other_user.id, + "parent_id": cls.other_manager_employee.id, + } + ) + cls.analytic_plan = cls.env["account.analytic.plan"].create( + {"name": "Test Plan"} + ) + cls.analytic_account = cls.env["account.analytic.account"].create( + {"name": "AA Test", "plan_id": cls.analytic_plan.id} + ) + cls.project = cls.env["project.project"].create( + { + "name": "Test Project", + "privacy_visibility": "followers", + "account_id": cls.analytic_account.id, + } + ) + cls.manager_env = cls.env(user=cls.manager_user) + + def test_manager_can_read_subordinate_timesheet(self): + """Manager with approver group can read subordinate timesheet.""" + timesheet = self.env["account.analytic.line"].create( + { + "name": "Test Timesheet", + "project_id": self.project.id, + "employee_id": self.subordinate_employee.id, + "unit_amount": 1, + } + ) + record = self.manager_env["account.analytic.line"].browse(timesheet.id) + self.assertTrue(record.exists()) + self.assertEqual(record.name, "Test Timesheet") + + def test_manager_can_write_subordinate_timesheet(self): + """Manager with approver group can write subordinate timesheet.""" + timesheet = self.env["account.analytic.line"].create( + { + "name": "Test Timesheet", + "project_id": self.project.id, + "employee_id": self.subordinate_employee.id, + "unit_amount": 1, + } + ) + record = self.manager_env["account.analytic.line"].browse(timesheet.id) + record.write({"name": "Updated Timesheet"}) + self.assertEqual(record.name, "Updated Timesheet") + + def test_manager_can_create_timesheet_for_subordinate(self): + """Manager with approver group can create timesheet for subordinate.""" + record = self.manager_env["account.analytic.line"].create( + { + "name": "Manager Created Timesheet", + "project_id": self.project.id, + "employee_id": self.subordinate_employee.id, + "unit_amount": 2, + } + ) + self.assertTrue(record.exists()) + + def test_manager_can_unlink_subordinate_timesheet(self): + """Manager with approver group can delete subordinate timesheet.""" + timesheet = self.env["account.analytic.line"].create( + { + "name": "Test Timesheet", + "project_id": self.project.id, + "employee_id": self.subordinate_employee.id, + "unit_amount": 1, + } + ) + record = self.manager_env["account.analytic.line"].browse(timesheet.id) + record.unlink() + self.assertFalse( + self.env["account.analytic.line"].browse(timesheet.id).exists() + ) + + def test_manager_cannot_access_non_subordinate_timesheet(self): + """Manager with approver group cannot access timesheet of non-subordinate.""" + timesheet = self.env["account.analytic.line"].create( + { + "name": "Other Timesheet", + "project_id": self.project.id, + "employee_id": self.other_employee.id, + "unit_amount": 1, + } + ) + found = self.manager_env["account.analytic.line"].search( + [("id", "=", timesheet.id)] + ) + self.assertFalse(found, "Manager should not see non-subordinate timesheet")