From c1b8625cd02111ec6bb41bc08d1625e4d87f4794 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 18 Sep 2024 13:59:19 +0200 Subject: [PATCH] [ADD] hr_holidays_security: New module TT50622 --- hr_holidays_security/README.rst | 98 ++++ hr_holidays_security/__init__.py | 1 + hr_holidays_security/__manifest__.py | 18 + hr_holidays_security/models/__init__.py | 3 + hr_holidays_security/models/hr_leave.py | 71 +++ .../models/hr_leave_allocation.py | 82 ++++ .../models/ir_actions_act_window.py | 46 ++ hr_holidays_security/readme/CONTEXT.md | 5 + hr_holidays_security/readme/CONTRIBUTORS.md | 2 + hr_holidays_security/readme/DESCRIPTION.md | 2 + hr_holidays_security/readme/USAGE.md | 2 + hr_holidays_security/security/security.xml | 9 + .../static/description/index.html | 445 ++++++++++++++++++ hr_holidays_security/tests/__init__.py | 1 + .../tests/test_hr_holidays_security.py | 55 +++ .../views/hr_leave_allocation_views.xml | 38 ++ hr_holidays_security/views/hr_leave_views.xml | 26 + .../odoo/addons/hr_holidays_security | 1 + setup/hr_holidays_security/setup.py | 6 + 19 files changed, 911 insertions(+) create mode 100644 hr_holidays_security/README.rst create mode 100644 hr_holidays_security/__init__.py create mode 100644 hr_holidays_security/__manifest__.py create mode 100644 hr_holidays_security/models/__init__.py create mode 100644 hr_holidays_security/models/hr_leave.py create mode 100644 hr_holidays_security/models/hr_leave_allocation.py create mode 100644 hr_holidays_security/models/ir_actions_act_window.py create mode 100644 hr_holidays_security/readme/CONTEXT.md create mode 100644 hr_holidays_security/readme/CONTRIBUTORS.md create mode 100644 hr_holidays_security/readme/DESCRIPTION.md create mode 100644 hr_holidays_security/readme/USAGE.md create mode 100644 hr_holidays_security/security/security.xml create mode 100644 hr_holidays_security/static/description/index.html create mode 100644 hr_holidays_security/tests/__init__.py create mode 100644 hr_holidays_security/tests/test_hr_holidays_security.py create mode 100644 hr_holidays_security/views/hr_leave_allocation_views.xml create mode 100644 hr_holidays_security/views/hr_leave_views.xml create mode 120000 setup/hr_holidays_security/odoo/addons/hr_holidays_security create mode 100644 setup/hr_holidays_security/setup.py diff --git a/hr_holidays_security/README.rst b/hr_holidays_security/README.rst new file mode 100644 index 00000000..0a2c5ec9 --- /dev/null +++ b/hr_holidays_security/README.rst @@ -0,0 +1,98 @@ +==================== +HR Holidays Security +==================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:1c52c1427940b2c6755bb73a4807a92cf62c513359e1f21dfd3d0b46495ea1c0 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fhr--holidays-lightgray.png?logo=github + :target: https://github.com/OCA/hr-holidays/tree/14.0/hr_holidays_security + :alt: OCA/hr-holidays +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr-holidays-14-0/hr-holidays-14-0-hr_holidays_security + :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/hr-holidays&target_branch=14.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the permissions of Time Off responsibles and +approvers to flex their team assignments. + +**Table of contents** + +.. contents:: + :local: + +Use Cases / Context +=================== + +In a large organization Time Off Responsible users powers can be so +rigid, as they aren't allowed to fully approve their team's requests. +Time Off All Approvers have a too much approval scope while Time Off +Manager would be too powerful. + +This module aims to adjust those powers for each group, respecting the +core workflow. + +Usage +===== + +Now a Time Off Responsible and All Approvals users can view, creat, +edit, reject, set as draft leaves and allocation from their team +employees. + +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 +------- + +* Tecnativa +* + +Contributors +------------ + +- `Tecnativa `__ + + - David Vidal + +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. + +This module is part of the `OCA/hr-holidays `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_holidays_security/__init__.py b/hr_holidays_security/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/hr_holidays_security/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/hr_holidays_security/__manifest__.py b/hr_holidays_security/__manifest__.py new file mode 100644 index 00000000..6aae0127 --- /dev/null +++ b/hr_holidays_security/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "HR Holidays Security", + "summary": "Allow time-off responsibles to fully manage their team requests", + "version": "14.0.1.0.0", + "license": "AGPL-3", + "category": "Human Resources", + "author": "Tecnativa, Odoo Community Association (OCA),", + "website": "https://github.com/OCA/hr-holidays", + "depends": ["hr_holidays"], + "data": [ + "security/security.xml", + "views/hr_leave_views.xml", + "views/hr_leave_allocation_views.xml", + ], + "installable": True, +} diff --git a/hr_holidays_security/models/__init__.py b/hr_holidays_security/models/__init__.py new file mode 100644 index 00000000..d17b27f1 --- /dev/null +++ b/hr_holidays_security/models/__init__.py @@ -0,0 +1,3 @@ +from . import hr_leave +from . import hr_leave_allocation +from . import ir_actions_act_window diff --git a/hr_holidays_security/models/hr_leave.py b/hr_holidays_security/models/hr_leave.py new file mode 100644 index 00000000..bdd0c67b --- /dev/null +++ b/hr_holidays_security/models/hr_leave.py @@ -0,0 +1,71 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import _, models +from odoo.exceptions import UserError + + +class HrLeave(models.Model): + _inherit = "hr.leave" + + def _check_approval_update(self, state): + """Check if target state is achievable""" + if ( + not self.env.user.has_group("hr_holidays.group_hr_holidays_responsible") + or self.env.user.has_group("hr_holidays.group_hr_holidays_manager") + or self.env.is_superuser() + ): + return super()._check_approval_update(state) + # Do nothing + if state == "confirm": + return + current_employee = self.env.user.employee_id + is_officer = self.env.user.has_group("hr_holidays.group_hr_holidays_user") + is_responsible = self.env.user.has_group( + "hr_holidays.group_hr_holidays_responsible" + ) + + for holiday in self: + val_type = holiday.validation_type + if state != "draft": + if ( + val_type == "no_validation" + and current_employee == holiday.employee_id + ): + continue + # use ir.rule based first access check: department, members, ... + # (see security.xml) + holiday.check_access_rule("write") + # This handles states validate1 validate and refuse + if holiday.employee_id == current_employee: + raise UserError( + _( + "Only a Time Off Manager can approve/refuse its own requests." + ) + ) + if ( + (state == "validate1" and val_type == "both") + or (state == "validate" and val_type == "manager") + and holiday.holiday_type == "employee" + ): + if ( + not is_officer + and self.env.user != holiday.employee_id.leave_manager_id + ): + raise UserError( + _( + "You must be either %s's manager or Time off Manager " + "to approve this leave" + ) + % (holiday.employee_id.name) + ) + if ( + not is_responsible + and (state == "validate" and val_type == "hr") + and holiday.holiday_type == "employee" + ): + raise UserError( + _( + "You must either be a Time off Officer or Time off Manager " + "to approve this leave" + ) + ) diff --git a/hr_holidays_security/models/hr_leave_allocation.py b/hr_holidays_security/models/hr_leave_allocation.py new file mode 100644 index 00000000..840851fa --- /dev/null +++ b/hr_holidays_security/models/hr_leave_allocation.py @@ -0,0 +1,82 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, fields, models + + +class HrLeaveAllocation(models.Model): + _inherit = "hr.leave.allocation" + + private_name = fields.Char(groups="hr_holidays.group_hr_holidays_responsible") + allowed_holyday_status_ids = fields.Many2many( + comodel_name="hr.leave.type", compute="_compute_allowed_holyday_status_ids" + ) + + def _get_allowed_holyday_status_domain(self): + if self.user_has_groups("hr_holidays.group_hr_holidays_user") or ( + self._user_is_bare_responsible() + and self.employee_id.user_id != self.env.user + ): + return [("valid", "=", True), ("allocation_type", "!=", "no")] + else: + return [("valid", "=", True), ("allocation_type", "=", "fixed_allocation")] + + @api.depends("employee_id") + def _compute_allowed_holyday_status_ids(self): + """Responsibles can only do allocations on their team members but on + themselves""" + for allocation in self: + allocation.allowed_holyday_status_ids = self.env["hr.leave.type"].search( + allocation._get_allowed_holyday_status_domain() + ) + + @api.model + def _user_is_bare_responsible(self): + return self.env.user.has_group( + "hr_holidays.group_hr_holidays_responsible" + ) and not self.env.user.has_group("hr_holidays.group_hr_holidays_user") + + def _compute_description(self): + self.check_access_rights("read") + self.check_access_rule("read") + if not self._user_is_bare_responsible(): + return super()._compute_description() + for allocation in self: + if ( + allocation.employee_id.user_id == self.env.user + or allocation.manager_id == self.env.user + ): + allocation.name = allocation.sudo().private_name + else: + allocation.name = "*****" + + def _inverse_description(self): + if not self._user_is_bare_responsible(): + return super()._inverse_description() + for allocation in self: + if ( + allocation.employee_id.user_id == self.env.user + or allocation.manager_id == self.env.user + ): + allocation.sudo().private_name = allocation.name + + def _search_description(self, operator, value): + if not self._user_is_bare_responsible(): + return super()._search_description(operator, value) + domain = [("private_name", operator, value)] + allocations = self.sudo().search(domain) + return [("id", "in", allocations.ids)] + + def _check_approval_update(self, state): + # Lift restrictions + if not self.env.user.has_group("hr_holidays.group_hr_holidays_responsible"): + return super()._check_approval_update(state) + current_employee = self.env.user.employee_id + if not current_employee: + return + for holiday in self: + if state == "confirm": + continue + if self.env.user == holiday.employee_id.leave_manager_id: + # use ir.rule based first access check: department, members, ... + # (see security.xml) + holiday.check_access_rule("write") diff --git a/hr_holidays_security/models/ir_actions_act_window.py b/hr_holidays_security/models/ir_actions_act_window.py new file mode 100644 index 00000000..2a1356f0 --- /dev/null +++ b/hr_holidays_security/models/ir_actions_act_window.py @@ -0,0 +1,46 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import models + + +class IrActionsActWindow(models.Model): + _inherit = "ir.actions.act_window" + + def _compute_views(self): + # HACK: Not a very nice override but negative groups don't work right in this + # case as they don't trigger the necessary compute changes in the views. So we + # want to use a different view for this + res = super()._compute_views() + leave_action = self.filtered( + lambda x: x + == self.env.ref("hr_holidays.hr_leave_action_action_approve_department") + ) + allocation_action = self.filtered( + lambda x: x + == self.env.ref("hr_holidays.hr_leave_allocation_action_approve_department") + ) + if leave_action or allocation_action: + bare_responsible = self.env.user.has_group( + "hr_holidays.group_hr_holidays_responsible" + ) and not self.env.user.has_group("hr_holidays.group_hr_holidays_user") + if bare_responsible and leave_action: + bare_responsible_form = self.env.ref( + "hr_holidays_security.hr_leave_view_form_responsible" + ) + leave_action.views = [ + (view, view_type) + if view_type != "form" + else (bare_responsible_form.id, view_type) + for view, view_type in leave_action.views + ] + if bare_responsible and allocation_action: + bare_responsible_form = self.env.ref( + "hr_holidays_security.hr_leave_allocation_view_form_manager" + ) + allocation_action.views = [ + (view, view_type) + if view_type != "form" + else (bare_responsible_form.id, view_type) + for view, view_type in allocation_action.views + ] + return res diff --git a/hr_holidays_security/readme/CONTEXT.md b/hr_holidays_security/readme/CONTEXT.md new file mode 100644 index 00000000..b3fd39d6 --- /dev/null +++ b/hr_holidays_security/readme/CONTEXT.md @@ -0,0 +1,5 @@ +In a large organization Time Off Responsible users powers can be so rigid, as they +aren't allowed to fully approve their team's requests. Time Off All Approvers have a +too much approval scope while Time Off Manager would be too powerful. + +This module aims to adjust those powers for each group, respecting the core workflow. diff --git a/hr_holidays_security/readme/CONTRIBUTORS.md b/hr_holidays_security/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..3a16ddf0 --- /dev/null +++ b/hr_holidays_security/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Tecnativa](https://tecnativa.com) + - David Vidal diff --git a/hr_holidays_security/readme/DESCRIPTION.md b/hr_holidays_security/readme/DESCRIPTION.md new file mode 100644 index 00000000..a2e9029c --- /dev/null +++ b/hr_holidays_security/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module extends the permissions of Time Off responsibles and approvers to flex +their team assignments. diff --git a/hr_holidays_security/readme/USAGE.md b/hr_holidays_security/readme/USAGE.md new file mode 100644 index 00000000..07c86c8a --- /dev/null +++ b/hr_holidays_security/readme/USAGE.md @@ -0,0 +1,2 @@ +Now a Time Off Responsible and All Approvals users can view, creat, edit, reject, set as +draft leaves and allocation from their team employees. diff --git a/hr_holidays_security/security/security.xml b/hr_holidays_security/security/security.xml new file mode 100644 index 00000000..b7885b55 --- /dev/null +++ b/hr_holidays_security/security/security.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/hr_holidays_security/static/description/index.html b/hr_holidays_security/static/description/index.html new file mode 100644 index 00000000..cd535076 --- /dev/null +++ b/hr_holidays_security/static/description/index.html @@ -0,0 +1,445 @@ + + + + + +HR Holidays Security + + + +
+

HR Holidays Security

+ + +

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

+

This module extends the permissions of Time Off responsibles and +approvers to flex their team assignments.

+

Table of contents

+ +
+

Use Cases / Context

+

In a large organization Time Off Responsible users powers can be so +rigid, as they aren’t allowed to fully approve their team’s requests. +Time Off All Approvers have a too much approval scope while Time Off +Manager would be too powerful.

+

This module aims to adjust those powers for each group, respecting the +core workflow.

+
+
+

Usage

+

Now a Time Off Responsible and All Approvals users can view, creat, +edit, reject, set as draft leaves and allocation from their team +employees.

+
+
+

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

+
    +
  • Tecnativa
  • +
  • +
+
+
+

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.

+

This module is part of the OCA/hr-holidays project on GitHub.

+

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

+
+
+
+ + diff --git a/hr_holidays_security/tests/__init__.py b/hr_holidays_security/tests/__init__.py new file mode 100644 index 00000000..8943b06d --- /dev/null +++ b/hr_holidays_security/tests/__init__.py @@ -0,0 +1 @@ +from . import test_hr_holidays_security diff --git a/hr_holidays_security/tests/test_hr_holidays_security.py b/hr_holidays_security/tests/test_hr_holidays_security.py new file mode 100644 index 00000000..2eb32b3a --- /dev/null +++ b/hr_holidays_security/tests/test_hr_holidays_security.py @@ -0,0 +1,55 @@ +# Copyright 2024 Tecnativa - David Vidal +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields +from odoo.tests import Form, SavepointCase + +from odoo.addons.mail.tests.common import mail_new_test_user + + +class HrHolidaysSecurityCase(SavepointCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + # Define some users and their employees + # responsible 1 + # |- r1 team member A + # responsible 2 + # |- r2 team member B + cls.responsible_1 = cls.create_user_and_employee( + cls, "responsible_1", groups="hr_holidays.group_hr_holidays_responsible" + ) + cls.responsible_2 = cls.create_user_and_employee( + cls, "responsible_2", groups="hr_holidays.group_hr_holidays_responsible" + ) + cls.r1_team_member_a = cls.create_user_and_employee( + cls, "r1_team_member_a", groups="base.group_user" + ) + cls.r2_team_member_b = cls.create_user_and_employee( + cls, "r2_team_member_b", groups="base.group_user" + ) + cls.r1_team_member_a.employee_id.leave_manager_id = cls.responsible_1 + cls.r2_team_member_b.employee_id.leave_manager_id = cls.responsible_2 + cls.sick_time_off = cls.env.ref("hr_holidays.holiday_status_sl") + + def create_user_and_employee(self, login, groups): + user = mail_new_test_user(self.env, login=login, groups=groups) + self.env["hr.employee"].create( + { + "name": "Employee %s" % login, + "user_id": user.id, + } + ) + return user + + def new_leave(self, user): + return Form(self.env["hr.leave"].with_user(user.id)) + + def test_leave_approvals(self): + """Different users workflows""" + leave_request = self.new_leave(self.responsible_2) + leave_request.employee_id = self.r2_team_member_b.employee_id + leave_request.holiday_status_id = self.sick_time_off + leave_request.request_date_from = fields.Date.today() + leave_request.request_date_to = fields.Date.today() + leave_request.save() diff --git a/hr_holidays_security/views/hr_leave_allocation_views.xml b/hr_holidays_security/views/hr_leave_allocation_views.xml new file mode 100644 index 00000000..fa8dfc4d --- /dev/null +++ b/hr_holidays_security/views/hr_leave_allocation_views.xml @@ -0,0 +1,38 @@ + + + + hr.leave.allocation + + + + + + + [('id', 'in', allowed_holyday_status_ids)] + + + + + hr.leave.allocation + + primary + + + hr_holidays.group_hr_holidays_responsible + {'required': [('holiday_type', '=', 'employee')], 'invisible': [('holiday_type', '!=', 'employee')]} + ['|', ('leave_manager_id', '=', uid), ('user_id', '=', uid)] + + + + diff --git a/hr_holidays_security/views/hr_leave_views.xml b/hr_holidays_security/views/hr_leave_views.xml new file mode 100644 index 00000000..f7c8a5f9 --- /dev/null +++ b/hr_holidays_security/views/hr_leave_views.xml @@ -0,0 +1,26 @@ + + + + hr.leave + primary + + + + hr_holidays.group_hr_holidays_responsible + + + hr_holidays.group_hr_holidays_responsible + {'required': [('holiday_type', '=', 'employee')], 'invisible': [('holiday_type', '!=', 'employee')]} + ['|', ('leave_manager_id', '=', uid), ('user_id', '=', uid)] + + + + diff --git a/setup/hr_holidays_security/odoo/addons/hr_holidays_security b/setup/hr_holidays_security/odoo/addons/hr_holidays_security new file mode 120000 index 00000000..1d80b906 --- /dev/null +++ b/setup/hr_holidays_security/odoo/addons/hr_holidays_security @@ -0,0 +1 @@ +../../../../hr_holidays_security \ No newline at end of file diff --git a/setup/hr_holidays_security/setup.py b/setup/hr_holidays_security/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/hr_holidays_security/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)