diff --git a/project_timesheet_holidays_editable/README.rst b/project_timesheet_holidays_editable/README.rst new file mode 100644 index 0000000000..4f447bc2ea --- /dev/null +++ b/project_timesheet_holidays_editable/README.rst @@ -0,0 +1,134 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +=================================================================== +Project Timesheet Holidays - re-enable generated timesheets edition +=================================================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:729ca2ced074913d51d934ec8a6604ddca29291c6dbf52c758185403365c1650 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/license-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/19.0/project_timesheet_holidays_editable + :alt: OCA/timesheet +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/timesheet-19-0/timesheet-19-0-project_timesheet_holidays_editable + :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=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +By default, every timesheet generated from a leave becomes read-only, so +nobody can directly edit it and e.g. change its hour count. This +shouldn't occur in Odoo previous versions. + +This could be annoying, because for certain leaves (like medical +leaves), final spent time should be different than initial leave filled. +This forces to reject, modify and then approve again leave. Exact time +could not be filled sometimes anyway, due to leave form hour selection. + +This addon re-enables timesheet edition when they're generated from +leaves (initially name and quant fields), letting us make it editable +depending on leave type. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Once installed, it adds a configuration option to define the editability +level of timesheets generated from time off requests at the company +level. + +Configuration + +After installing the module, follow these steps: + +1. Go to Settings (or navigate to Configuration → Settings within the + Timesheets or Time Off apps). +2. Locate the Time Off section (where timesheet generation for leaves is + configured). +3. Make sure an Internal Project (``internal_project_id``) is set. (The + editability configuration menu is only visible if a global internal + project is defined for the company). +4. Configure the Timesheet Edit Level. + +Timesheet Restriction Levels + +You can define how restrictive the generated timesheets are for the +company: + +1. None + + - The generated timesheet entries cannot be edited by anyone + +2. Officer + + - Timesheet entries can only be edited by users belonging to the + group: hr_holidays.group_hr_holidays_user + - Prevents regular employees from modifying generated timesheets + +3. All + + - Timesheet entries can be edited by any user with access to + timesheets + +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 +------- + +* Solvos Consultoría Informática + +Contributors +------------ + +- `Solvos `__: + + - David ALonso + - Carlos García + +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/timesheet `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/project_timesheet_holidays_editable/__init__.py b/project_timesheet_holidays_editable/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/project_timesheet_holidays_editable/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/project_timesheet_holidays_editable/__manifest__.py b/project_timesheet_holidays_editable/__manifest__.py new file mode 100644 index 0000000000..f35e8d3926 --- /dev/null +++ b/project_timesheet_holidays_editable/__manifest__.py @@ -0,0 +1,15 @@ +# © 2026 Solvos Consultoría Informática () +# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0.html + +{ + "name": "Project Timesheet Holidays - re-enable generated timesheets edition", + "summary": "Re-enables timesheet edition when they're generated from leaves", + "category": "Human Resources", + "version": "19.0.1.0.0", + "depends": ["project_timesheet_holidays"], + "data": ["views/res_config_settings_views.xml"], + "author": "Solvos Consultoría Informática, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/timesheet", + "license": "AGPL-3", + "installable": True, +} diff --git a/project_timesheet_holidays_editable/i18n/it.po b/project_timesheet_holidays_editable/i18n/it.po new file mode 100644 index 0000000000..e512cfecef --- /dev/null +++ b/project_timesheet_holidays_editable/i18n/it.po @@ -0,0 +1,53 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * project_timesheet_holidays_editable +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: project_timesheet_holidays_editable +#: model:ir.model.fields,help:project_timesheet_holidays_editable.field_hr_leave_type__timesheet_edit_level +msgid "" +"\n" +" For this leave type, indicates who can edit generated timesheets\n" +" " +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model.fields.selection,name:project_timesheet_holidays_editable.selection__hr_leave_type__timesheet_edit_level__all +msgid "All" +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model,name:project_timesheet_holidays_editable.model_account_analytic_line +msgid "Analytic Line" +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model.fields.selection,name:project_timesheet_holidays_editable.selection__hr_leave_type__timesheet_edit_level__none +msgid "None" +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model.fields.selection,name:project_timesheet_holidays_editable.selection__hr_leave_type__timesheet_edit_level__approver +msgid "Officer" +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model,name:project_timesheet_holidays_editable.model_hr_leave_type +msgid "Time Off Type" +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model.fields,field_description:project_timesheet_holidays_editable.field_hr_leave_type__timesheet_edit_level +msgid "Timesheet Edit Level" +msgstr "" diff --git a/project_timesheet_holidays_editable/i18n/project_timesheet_holidays_editable.pot b/project_timesheet_holidays_editable/i18n/project_timesheet_holidays_editable.pot new file mode 100644 index 0000000000..87a6eabede --- /dev/null +++ b/project_timesheet_holidays_editable/i18n/project_timesheet_holidays_editable.pot @@ -0,0 +1,52 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * project_timesheet_holidays_editable +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \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_timesheet_holidays_editable +#: model:ir.model.fields,help:project_timesheet_holidays_editable.field_hr_leave_type__timesheet_edit_level +msgid "" +"\n" +" For this leave type, indicates who can edit generated timesheets\n" +" " +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model.fields.selection,name:project_timesheet_holidays_editable.selection__hr_leave_type__timesheet_edit_level__all +msgid "All" +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model,name:project_timesheet_holidays_editable.model_account_analytic_line +msgid "Analytic Line" +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model.fields.selection,name:project_timesheet_holidays_editable.selection__hr_leave_type__timesheet_edit_level__none +msgid "None" +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model.fields.selection,name:project_timesheet_holidays_editable.selection__hr_leave_type__timesheet_edit_level__approver +msgid "Officer" +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model,name:project_timesheet_holidays_editable.model_hr_leave_type +msgid "Time Off Type" +msgstr "" + +#. module: project_timesheet_holidays_editable +#: model:ir.model.fields,field_description:project_timesheet_holidays_editable.field_hr_leave_type__timesheet_edit_level +msgid "Timesheet Edit Level" +msgstr "" diff --git a/project_timesheet_holidays_editable/models/__init__.py b/project_timesheet_holidays_editable/models/__init__.py new file mode 100644 index 0000000000..191fc4ddc4 --- /dev/null +++ b/project_timesheet_holidays_editable/models/__init__.py @@ -0,0 +1,3 @@ +from . import res_company +from . import res_config_settings +from . import account_analytic_line diff --git a/project_timesheet_holidays_editable/models/account_analytic_line.py b/project_timesheet_holidays_editable/models/account_analytic_line.py new file mode 100644 index 0000000000..2feed34ff6 --- /dev/null +++ b/project_timesheet_holidays_editable/models/account_analytic_line.py @@ -0,0 +1,31 @@ +# © 2026 Solvos Consultoría Informática () +# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0.html + +from odoo import api, models + + +class AccountAnalyticLine(models.Model): + _inherit = "account.analytic.line" + + def _check_can_write(self, values): + # A holiday generated timesheet can only be modified if holiday type + # allows it for everybody, or if the user is an approver + obj = self + holiday = self.sudo().holiday_id + if ( + holiday + and ( + holiday.employee_id.company_id.timesheet_edit_level == "all" + or ( + holiday.employee_id.company_id.timesheet_edit_level == "approver" + and self.env.user.has_group("hr_holidays.group_hr_holidays_user") + ) + ) + and (not set(values.keys()) - set(self._get_ts_from_holiday_edit_fields())) + ): + obj = self.sudo() + return super(AccountAnalyticLine, obj)._check_can_write(values) + + @api.model + def _get_ts_from_holiday_edit_fields(self): + return ["name", "unit_amount"] diff --git a/project_timesheet_holidays_editable/models/res_company.py b/project_timesheet_holidays_editable/models/res_company.py new file mode 100644 index 0000000000..ac60c08297 --- /dev/null +++ b/project_timesheet_holidays_editable/models/res_company.py @@ -0,0 +1,14 @@ +# © 2026 Solvos Consultoría Informática () +# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + timesheet_edit_level = fields.Selection( + selection=[("none", "None"), ("approver", "Officer"), ("all", "All")], + default="none", + string="Timesheet Edit Level for Time Off", + ) diff --git a/project_timesheet_holidays_editable/models/res_config_settings.py b/project_timesheet_holidays_editable/models/res_config_settings.py new file mode 100644 index 0000000000..bb031278fd --- /dev/null +++ b/project_timesheet_holidays_editable/models/res_config_settings.py @@ -0,0 +1,17 @@ +# © 2026 Solvos Consultoría Informática () +# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + timesheet_edit_level = fields.Selection( + related="company_id.timesheet_edit_level", + readonly=False, + string="Timesheet Edit Level for Time Off", + help=""" + Indicates who can edit generated timesheets + """, + ) diff --git a/project_timesheet_holidays_editable/pyproject.toml b/project_timesheet_holidays_editable/pyproject.toml new file mode 100644 index 0000000000..4231d0cccb --- /dev/null +++ b/project_timesheet_holidays_editable/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/project_timesheet_holidays_editable/readme/CONTRIBUTORS.md b/project_timesheet_holidays_editable/readme/CONTRIBUTORS.md new file mode 100644 index 0000000000..ca166dffe3 --- /dev/null +++ b/project_timesheet_holidays_editable/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- [Solvos](https://www.solvos.es): + - David ALonso \ + - Carlos García \ diff --git a/project_timesheet_holidays_editable/readme/DESCRIPTION.md b/project_timesheet_holidays_editable/readme/DESCRIPTION.md new file mode 100644 index 0000000000..acb5001a48 --- /dev/null +++ b/project_timesheet_holidays_editable/readme/DESCRIPTION.md @@ -0,0 +1,5 @@ +By default, every timesheet generated from a leave becomes read-only, so nobody can directly edit it and e.g. change its hour count. This shouldn't occur in Odoo previous versions. + +This could be annoying, because for certain leaves (like medical leaves), final spent time should be different than initial leave filled. This forces to reject, modify and then approve again leave. Exact time could not be filled sometimes anyway, due to leave form hour selection. + +This addon re-enables timesheet edition when they're generated from leaves (initially name and quant fields), letting us make it editable depending on leave type. diff --git a/project_timesheet_holidays_editable/readme/USAGE.md b/project_timesheet_holidays_editable/readme/USAGE.md new file mode 100644 index 0000000000..93c443d170 --- /dev/null +++ b/project_timesheet_holidays_editable/readme/USAGE.md @@ -0,0 +1,24 @@ +Once installed, it adds a configuration option to define the editability level of timesheets generated from time off requests at the company level. + +Configuration + +After installing the module, follow these steps: + +1. Go to Settings (or navigate to Configuration → Settings within the Timesheets or Time Off apps). +2. Locate the Time Off section (where timesheet generation for leaves is configured). +3. Make sure an Internal Project (`internal_project_id`) is set. (The editability configuration menu is only visible if a global internal project is defined for the company). +4. Configure the Timesheet Edit Level. + +Timesheet Restriction Levels + +You can define how restrictive the generated timesheets are for the company: + +1. None + - The generated timesheet entries cannot be edited by anyone + +2. Officer + - Timesheet entries can only be edited by users belonging to the group: hr_holidays.group_hr_holidays_user + - Prevents regular employees from modifying generated timesheets + +3. All + - Timesheet entries can be edited by any user with access to timesheets diff --git a/project_timesheet_holidays_editable/static/description/icon.png b/project_timesheet_holidays_editable/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/project_timesheet_holidays_editable/static/description/icon.png differ diff --git a/project_timesheet_holidays_editable/static/description/index.html b/project_timesheet_holidays_editable/static/description/index.html new file mode 100644 index 0000000000..dc38aad87f --- /dev/null +++ b/project_timesheet_holidays_editable/static/description/index.html @@ -0,0 +1,481 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Project Timesheet Holidays - re-enable generated timesheets edition

+ +

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

+

By default, every timesheet generated from a leave becomes read-only, so +nobody can directly edit it and e.g. change its hour count. This +shouldn’t occur in Odoo previous versions.

+

This could be annoying, because for certain leaves (like medical +leaves), final spent time should be different than initial leave filled. +This forces to reject, modify and then approve again leave. Exact time +could not be filled sometimes anyway, due to leave form hour selection.

+

This addon re-enables timesheet edition when they’re generated from +leaves (initially name and quant fields), letting us make it editable +depending on leave type.

+

Table of contents

+ +
+

Usage

+

Once installed, it adds a configuration option to define the editability +level of timesheets generated from time off requests at the company +level.

+

Configuration

+

After installing the module, follow these steps:

+
    +
  1. Go to Settings (or navigate to Configuration → Settings within the +Timesheets or Time Off apps).
  2. +
  3. Locate the Time Off section (where timesheet generation for leaves is +configured).
  4. +
  5. Make sure an Internal Project (internal_project_id) is set. (The +editability configuration menu is only visible if a global internal +project is defined for the company).
  6. +
  7. Configure the Timesheet Edit Level.
  8. +
+

Timesheet Restriction Levels

+

You can define how restrictive the generated timesheets are for the +company:

+
    +
  1. None
      +
    • The generated timesheet entries cannot be edited by anyone
    • +
    +
  2. +
  3. Officer
      +
    • Timesheet entries can only be edited by users belonging to the +group: hr_holidays.group_hr_holidays_user
    • +
    • Prevents regular employees from modifying generated timesheets
    • +
    +
  4. +
  5. All
      +
    • Timesheet entries can be edited by any user with access to +timesheets
    • +
    +
  6. +
+
+
+

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

+
    +
  • Solvos Consultoría Informática
  • +
+
+
+

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/timesheet project on GitHub.

+

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

+
+
+
+
+ + diff --git a/project_timesheet_holidays_editable/tests/__init__.py b/project_timesheet_holidays_editable/tests/__init__.py new file mode 100644 index 0000000000..661cbbdff0 --- /dev/null +++ b/project_timesheet_holidays_editable/tests/__init__.py @@ -0,0 +1 @@ +from . import test_project_timesheet_holidays_editable diff --git a/project_timesheet_holidays_editable/tests/test_project_timesheet_holidays_editable.py b/project_timesheet_holidays_editable/tests/test_project_timesheet_holidays_editable.py new file mode 100644 index 0000000000..050d0932b9 --- /dev/null +++ b/project_timesheet_holidays_editable/tests/test_project_timesheet_holidays_editable.py @@ -0,0 +1,172 @@ +# © 2026 Solvos Consultoría Informática () +# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0.html + +from datetime import date + +from odoo import Command +from odoo.exceptions import UserError +from odoo.tests.common import tagged + +from odoo.addons.base.tests.common import BaseCommon + + +@tagged("post_install", "-at_install", "pruebilla") +class TestProjectTimesheetHolidays(BaseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.Employee = cls.env["hr.employee"] + cls.LeaveType = cls.env["hr.leave.type"] + cls.Leave = cls.env["hr.leave"] + cls.AnalyticLine = cls.env["account.analytic.line"] + cls.Project = cls.env["project.project"] + cls.Task = cls.env["project.task"] + + cls.user_employee = cls.env["res.users"].create( + { + "name": "HR Employee", + "login": "hr_employee", + "group_ids": [ + Command.set( + [ + cls.env.ref("hr_timesheet.group_hr_timesheet_user").id, + ] + ) + ], + } + ) + + cls.user_approver = cls.env["res.users"].create( + { + "name": "HR Approver", + "login": "hr_approver", + "group_ids": [ + Command.set( + [ + cls.env.ref("hr_holidays.group_hr_holidays_user").id, + cls.env.ref("project.group_project_manager").id, + ] + ) + ], + } + ) + + cls.project = cls.Project.create( + { + "name": "Project Vacations", + "company_id": cls.env.company.id, + } + ) + + cls.task = cls.Task.create( + { + "name": "Vacations Task", + "project_id": cls.project.id, + "company_id": cls.env.company.id, + } + ) + + cls.env.company.write( + { + "internal_project_id": cls.project.id, + "leave_timesheet_task_id": cls.task.id, + } + ) + + cls.employee = cls.Employee.create( + { + "name": "Test Employee", + "user_id": cls.user_employee.id, + "company_id": cls.env.company.id, + } + ) + + def _create_leave_with_timesheet(self, edit_level): + self.employee.company_id.write( + { + "timesheet_edit_level": edit_level, + } + ) + + leave_type = self.LeaveType.create( + { + "name": f"Leave {edit_level}", + } + ) + + allocation = self.env["hr.leave.allocation"].create( + { + "name": "Test Allocation", + "employee_id": self.employee.id, + "holiday_status_id": leave_type.id, + "number_of_days": 10, + } + ) + allocation.action_approve() + + leave = self.Leave.create( + { + "name": "Vacations", + "employee_id": self.employee.id, + "holiday_status_id": leave_type.id, + "request_date_from": date.today(), + "request_date_to": date.today(), + } + ) + + leave.action_approve() + + ts = self.AnalyticLine.search([("holiday_id", "=", leave.id)], limit=1) + + self.assertTrue(ts) + return ts + + def test_edit_timesheet_allowed_edit_level_all(self): + ts = self._create_leave_with_timesheet("all") + + ts.with_user(self.user_employee).write( + { + "name": "New name", + "unit_amount": 4, + } + ) + + def test_edit_timesheet_not_allowed_edit_level_all(self): + ts = self._create_leave_with_timesheet("all") + + with self.assertRaises(UserError): + ts.with_user(self.user_employee).write( + { + "date": date.today(), + } + ) + + def test_edit_timesheet_not_allowed_edit_level_none(self): + ts = self._create_leave_with_timesheet("none") + + with self.assertRaises(UserError): + ts.with_user(self.user_employee).write( + { + "name": "shouldnt be able", + } + ) + + def test_edit_timesheet_by_employee_without_group_edit_level_approver(self): + ts = self._create_leave_with_timesheet("approver") + + with self.assertRaises(UserError): + ts.with_user(self.user_employee).write( + { + "unit_amount": 3, + } + ) + + def test_edit_timesheet_by_user_approver_without_group_edit_level_approver(self): + ts = self._create_leave_with_timesheet("approver") + + ts.with_user(self.user_approver).write( + { + "unit_amount": 3, + } + ) diff --git a/project_timesheet_holidays_editable/views/res_config_settings_views.xml b/project_timesheet_holidays_editable/views/res_config_settings_views.xml new file mode 100644 index 0000000000..4d443b75ee --- /dev/null +++ b/project_timesheet_holidays_editable/views/res_config_settings_views.xml @@ -0,0 +1,27 @@ + + + res.config.settings.view.form.inherit.edit.ts + res.config.settings + + + +
+
+
+
+
+