diff --git a/hr_holidays_ux/README.rst b/hr_holidays_ux/README.rst index b1cf433c..65771ba1 100644 --- a/hr_holidays_ux/README.rst +++ b/hr_holidays_ux/README.rst @@ -16,10 +16,9 @@ Holidays UX This module: -#. Splits time off records by month. -#. Adds a pre-approved stage for time off requests that require attachments. This allows managers to fully approve the time off once the employee uploads the required document. - - +#. Splits time off records by month: a request that spans several months is saved as one time off per month, both when it is created and when its dates are changed. +#. Adds a Pre-Approved stage for time off types that expect a supporting document. When a manager approves a request that has no document yet, it goes to Pre-Approved; once the employee uploads the document, the manager approves it for good. +#. Adds a Pre-Approved filter to the time off search view. Installation ============ @@ -33,18 +32,20 @@ Configuration To configure this module, you need to: -#. Nothing to configure +#. Go to Time Off > Configuration > Time Types and open a type. +#. Check Supporting Document Expected and then Pre Approved Instance. Usage ===== To use this module, you need to: -#. Go to ... +#. An employee requests time off of a type configured with Pre Approved Instance, without a document. +#. A Time Off Officer or the employee's time off manager approves it: the request goes to Pre-Approved. +#. The employee attaches the supporting document in the chatter of the request. +#. The officer or manager clicks Approve: the request is approved. -.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas - :alt: Try me on Runbot - :target: http://runbot.adhoc.com.ar/ +If the document is already attached when the request is approved, it skips Pre-Approved. Credits ======= diff --git a/hr_holidays_ux/__manifest__.py b/hr_holidays_ux/__manifest__.py index 962b603d..8b88f3fd 100644 --- a/hr_holidays_ux/__manifest__.py +++ b/hr_holidays_ux/__manifest__.py @@ -19,7 +19,7 @@ ############################################################################## { "name": "Holidays UX", - "version": "19.0.1.1.0", + "version": "20.0.1.0.0", "category": "Human Resources", "sequence": 14, "summary": "Split time off records by month", @@ -32,11 +32,11 @@ ], "data": [ "data/ir_actions_server.xml", - "views/hr_leave_type_views.xml", + "views/hr_work_entry_type_views.xml", "views/hr_leave_views.xml", ], "demo": [], - "installable": False, + "installable": True, "auto_install": False, "application": False, } diff --git a/hr_holidays_ux/i18n/es.po b/hr_holidays_ux/i18n/es.po index 54ad3b3a..1c28cc6c 100644 --- a/hr_holidays_ux/i18n/es.po +++ b/hr_holidays_ux/i18n/es.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 19.0+e\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-25 16:07+0000\n" -"PO-Revision-Date: 2026-06-30 18:54+0000\n" +"POT-Creation-Date: 2026-10-15 18:52+0000\n" +"PO-Revision-Date: 2026-10-15 18:52+0000\n" "Last-Translator: BAF \n" "Language-Team: Spanish \n" @@ -22,6 +22,79 @@ msgstr "" " ? 1 : 2);\n" "X-Generator: Weblate 5.10.4\n" +#. module: hr_holidays_ux +#: model:ir.module.module,description:base.module_hr_holidays_ux +msgid "" +".. |company| replace:: ADHOC SA\n" +"\n" +".. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png\n" +" :alt: ADHOC SA\n" +" :target: https://www.adhoc.com.ar\n" +"\n" +".. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png\n" +"\n" +".. image:: https://img.shields.io/badge/license-AGPL--3-blue.png\n" +" :target: https://www.gnu.org/licenses/agpl\n" +" :alt: License: AGPL-3\n" +"\n" +"============\n" +"Holidays UX\n" +"============\n" +"\n" +"This module:\n" +"\n" +"#. Splits time off records by month: a request that spans several months is saved as one time off per month, both when it is created and when its dates are changed.\n" +"#. Adds a Pre-Approved stage for time off types that expect a supporting document. When a manager approves a request that has no document yet, it goes to Pre-Approved; once the employee uploads the document, the manager approves it for good.\n" +"#. Adds a Pre-Approved filter to the time off search view.\n" +"\n" +"Installation\n" +"============\n" +"\n" +"To install this module, you need to:\n" +"\n" +"#. Only need to install the module\n" +"\n" +"Configuration\n" +"=============\n" +"\n" +"To configure this module, you need to:\n" +"\n" +"#. Go to Time Off > Configuration > Time Types and open a type.\n" +"#. Check Supporting Document Expected and then Pre Approved Instance.\n" +"\n" +"Usage\n" +"=====\n" +"\n" +"To use this module, you need to:\n" +"\n" +"#. An employee requests time off of a type configured with Pre Approved Instance, without a document.\n" +"#. A Time Off Officer or the employee's time off manager approves it: the request goes to Pre-Approved.\n" +"#. The employee uploads the supporting document on the request.\n" +"#. The officer or manager clicks Approve: the request is approved.\n" +"\n" +"If the document is already attached when the request is approved, it skips Pre-Approved.\n" +"\n" +"Credits\n" +"=======\n" +"\n" +"Images\n" +"------\n" +"\n" +"* |company| |icon|\n" +"\n" +"Contributors\n" +"------------\n" +"\n" +"Maintainer\n" +"----------\n" +"\n" +"|company_logo|\n" +"\n" +"This module is maintained by the |company|.\n" +"\n" +"To contribute to this module, please visit https://www.adhoc.com.ar.\n" +msgstr "" + #. module: hr_holidays_ux #: model_terms:ir.ui.view,arch_db:hr_holidays_ux.hr_leave_view_form_ux msgid "Approve" @@ -31,7 +104,7 @@ msgstr "Aprobar" #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave__display_name #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_allocation__display_name #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_allocation_generate_multi_wizard__display_name -#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_type__display_name +#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_work_entry_type__display_name msgid "Display Name" msgstr "Nombre para mostrar" @@ -46,11 +119,16 @@ msgstr "Generar asignaciones de vacaciones para varios empleados" msgid "Generated Allocations" msgstr "Vacaciones Generadas" +#. module: hr_holidays_ux +#: model:ir.module.module,shortdesc:base.module_hr_holidays_ux +msgid "Holidays UX" +msgstr "Ausencias UX" + #. module: hr_holidays_ux #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave__id #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_allocation__id #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_allocation_generate_multi_wizard__id -#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_type__id +#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_work_entry_type__id msgid "ID" msgstr "ID" @@ -63,8 +141,8 @@ msgstr "" "validada." #. module: hr_holidays_ux -#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_type__pre_approved_instance -#: model_terms:ir.ui.view,arch_db:hr_holidays_ux.edit_holiday_status_form_ux +#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_work_entry_type__pre_approved_instance +#: model_terms:ir.ui.view,arch_db:hr_holidays_ux.hr_work_entry_type_view_form_ux msgid "Pre Approved Instance" msgstr "Instancia Preaprobada" @@ -77,7 +155,12 @@ msgstr "Preaprobado" #. module: hr_holidays_ux #: model:ir.actions.server,name:hr_holidays_ux.action_reset_allocation_to_confirm msgid "Reset to \"To Approve\"" -msgstr "" +msgstr "Volver a \"Por aprobar\"" + +#. module: hr_holidays_ux +#: model:ir.module.module,summary:base.module_hr_holidays_ux +msgid "Split time off records by month" +msgstr "Divide las ausencias por mes" #. module: hr_holidays_ux #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave__state @@ -91,9 +174,12 @@ msgid "" "The following allocations already have leaves taken and cannot be reset:\n" "%s" msgstr "" +"Las siguientes asignaciones ya tienen ausencias tomadas y no se pueden " +"restablecer:\n" +"%s" #. module: hr_holidays_ux -#: model:ir.model.fields,help:hr_holidays_ux.field_hr_leave_type__pre_approved_instance +#: model:ir.model.fields,help:hr_holidays_ux.field_hr_work_entry_type__pre_approved_instance msgid "" "This instance is necessary when the supported document is added after the " "leave" @@ -104,14 +190,14 @@ msgstr "" #. module: hr_holidays_ux #: model:ir.model,name:hr_holidays_ux.model_hr_leave msgid "Time Off" -msgstr "Ausencias" +msgstr "Vacaciones" #. module: hr_holidays_ux #: model:ir.model,name:hr_holidays_ux.model_hr_leave_allocation msgid "Time Off Allocation" -msgstr "" +msgstr "Asignación de tiempo personal" #. module: hr_holidays_ux -#: model:ir.model,name:hr_holidays_ux.model_hr_leave_type -msgid "Time Off Type" -msgstr "Tipo de ausencia" +#: model:ir.model,name:hr_holidays_ux.model_hr_work_entry_type +msgid "Time Type" +msgstr "Tipo de tiempo" diff --git a/hr_holidays_ux/i18n/hr_holidays_ux.pot b/hr_holidays_ux/i18n/hr_holidays_ux.pot index a213fe79..5b997fe3 100644 --- a/hr_holidays_ux/i18n/hr_holidays_ux.pot +++ b/hr_holidays_ux/i18n/hr_holidays_ux.pot @@ -4,10 +4,10 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 19.0+e\n" +"Project-Id-Version: Odoo Server 20.0+e\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-25 16:07+0000\n" -"PO-Revision-Date: 2026-06-25 16:07+0000\n" +"POT-Creation-Date: 2026-10-15 18:51+0000\n" +"PO-Revision-Date: 2026-10-15 18:51+0000\n" "Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -15,6 +15,79 @@ msgstr "" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" +#. module: hr_holidays_ux +#: model:ir.module.module,description:base.module_hr_holidays_ux +msgid "" +".. |company| replace:: ADHOC SA\n" +"\n" +".. |company_logo| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-logo.png\n" +" :alt: ADHOC SA\n" +" :target: https://www.adhoc.com.ar\n" +"\n" +".. |icon| image:: https://raw.githubusercontent.com/ingadhoc/maintainer-tools/master/resources/adhoc-icon.png\n" +"\n" +".. image:: https://img.shields.io/badge/license-AGPL--3-blue.png\n" +" :target: https://www.gnu.org/licenses/agpl\n" +" :alt: License: AGPL-3\n" +"\n" +"============\n" +"Holidays UX\n" +"============\n" +"\n" +"This module:\n" +"\n" +"#. Splits time off records by month: a request that spans several months is saved as one time off per month, both when it is created and when its dates are changed.\n" +"#. Adds a Pre-Approved stage for time off types that expect a supporting document. When a manager approves a request that has no document yet, it goes to Pre-Approved; once the employee uploads the document, the manager approves it for good.\n" +"#. Adds a Pre-Approved filter to the time off search view.\n" +"\n" +"Installation\n" +"============\n" +"\n" +"To install this module, you need to:\n" +"\n" +"#. Only need to install the module\n" +"\n" +"Configuration\n" +"=============\n" +"\n" +"To configure this module, you need to:\n" +"\n" +"#. Go to Time Off > Configuration > Time Types and open a type.\n" +"#. Check Supporting Document Expected and then Pre Approved Instance.\n" +"\n" +"Usage\n" +"=====\n" +"\n" +"To use this module, you need to:\n" +"\n" +"#. An employee requests time off of a type configured with Pre Approved Instance, without a document.\n" +"#. A Time Off Officer or the employee's time off manager approves it: the request goes to Pre-Approved.\n" +"#. The employee uploads the supporting document on the request.\n" +"#. The officer or manager clicks Approve: the request is approved.\n" +"\n" +"If the document is already attached when the request is approved, it skips Pre-Approved.\n" +"\n" +"Credits\n" +"=======\n" +"\n" +"Images\n" +"------\n" +"\n" +"* |company| |icon|\n" +"\n" +"Contributors\n" +"------------\n" +"\n" +"Maintainer\n" +"----------\n" +"\n" +"|company_logo|\n" +"\n" +"This module is maintained by the |company|.\n" +"\n" +"To contribute to this module, please visit https://www.adhoc.com.ar.\n" +msgstr "" + #. module: hr_holidays_ux #: model_terms:ir.ui.view,arch_db:hr_holidays_ux.hr_leave_view_form_ux msgid "Approve" @@ -24,7 +97,7 @@ msgstr "" #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave__display_name #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_allocation__display_name #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_allocation_generate_multi_wizard__display_name -#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_type__display_name +#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_work_entry_type__display_name msgid "Display Name" msgstr "" @@ -39,11 +112,16 @@ msgstr "" msgid "Generated Allocations" msgstr "" +#. module: hr_holidays_ux +#: model:ir.module.module,shortdesc:base.module_hr_holidays_ux +msgid "Holidays UX" +msgstr "" + #. module: hr_holidays_ux #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave__id #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_allocation__id #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_allocation_generate_multi_wizard__id -#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_type__id +#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_work_entry_type__id msgid "ID" msgstr "" @@ -54,8 +132,8 @@ msgid "Only a Time Off Officer/Manager can approve a pre-validated leave." msgstr "" #. module: hr_holidays_ux -#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave_type__pre_approved_instance -#: model_terms:ir.ui.view,arch_db:hr_holidays_ux.edit_holiday_status_form_ux +#: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_work_entry_type__pre_approved_instance +#: model_terms:ir.ui.view,arch_db:hr_holidays_ux.hr_work_entry_type_view_form_ux msgid "Pre Approved Instance" msgstr "" @@ -70,6 +148,11 @@ msgstr "" msgid "Reset to \"To Approve\"" msgstr "" +#. module: hr_holidays_ux +#: model:ir.module.module,summary:base.module_hr_holidays_ux +msgid "Split time off records by month" +msgstr "" + #. module: hr_holidays_ux #: model:ir.model.fields,field_description:hr_holidays_ux.field_hr_leave__state msgid "Status" @@ -84,7 +167,7 @@ msgid "" msgstr "" #. module: hr_holidays_ux -#: model:ir.model.fields,help:hr_holidays_ux.field_hr_leave_type__pre_approved_instance +#: model:ir.model.fields,help:hr_holidays_ux.field_hr_work_entry_type__pre_approved_instance msgid "" "This instance is necessary when the supported document is added after the " "leave" @@ -101,6 +184,6 @@ msgid "Time Off Allocation" msgstr "" #. module: hr_holidays_ux -#: model:ir.model,name:hr_holidays_ux.model_hr_leave_type -msgid "Time Off Type" +#: model:ir.model,name:hr_holidays_ux.model_hr_work_entry_type +msgid "Time Type" msgstr "" diff --git a/hr_holidays_ux/models/__init__.py b/hr_holidays_ux/models/__init__.py index 78ad8977..ce518145 100644 --- a/hr_holidays_ux/models/__init__.py +++ b/hr_holidays_ux/models/__init__.py @@ -1,3 +1,3 @@ from . import hr_leave from . import hr_leave_allocation -from . import hr_leave_type +from . import hr_work_entry_type diff --git a/hr_holidays_ux/models/hr_leave.py b/hr_holidays_ux/models/hr_leave.py index d9a44ac8..719af25b 100644 --- a/hr_holidays_ux/models/hr_leave.py +++ b/hr_holidays_ux/models/hr_leave.py @@ -174,7 +174,7 @@ def write(self, vals): # no overlapping records, safe to create base_vals = { "employee_id": record.employee_id.id, - "holiday_status_id": record.holiday_status_id.id, + "work_entry_type_id": record.work_entry_type_id.id, } # add fields that exist in vals or record, safely @@ -186,8 +186,6 @@ def write(self, vals): "date_to", "name", "description", - "request_unit_half", - "request_unit_hours", ] for field in safe_fields: @@ -221,28 +219,28 @@ def copy_data(self, default=None): """Override copy_data to handle leave splitting properly.""" return super().copy_data(default=default) - def action_approve(self, check_state=True): - res = super().action_approve() - if ( - self.state == "validate" - and not self.supported_attachment_ids - and self.holiday_status_id.pre_approved_instance - ): - self.state = "pre-validate" - return res - - def action_validate(self, check_state=True): - res = super().action_validate() - if not self.supported_attachment_ids and self.holiday_status_id.pre_approved_instance: - self.state = "pre-validate" + def _get_pre_approved_pending(self): + return self.filtered( + lambda leave: not leave.supported_attachment_ids and leave.work_entry_type_id.pre_approved_instance + ) + + def _action_validate(self, check_state=True): + to_pre_validate = self._get_pre_approved_pending() + res = super(HrLeave, self - to_pre_validate)._action_validate(check_state) + if to_pre_validate: + # time rules run when the leave is fully approved, in action_post_approve + to_pre_validate = to_pre_validate.with_context(skip_time_rules=True) + super(HrLeave, to_pre_validate)._action_validate(check_state) + to_pre_validate.write({"state": "pre-validate"}) return res def action_post_approve(self): """Approve a pre-validated leave once documents are uploaded.""" - self.write({"state": "validate"}) + self.with_context(skip_time_rules=True).write({"state": "validate"}) self._validate_leave_request() if not self.env.context.get("leave_fast_create"): self.activity_update() + self._trigger_time_rules() return True def _get_next_states_by_state(self): diff --git a/hr_holidays_ux/models/hr_leave_type.py b/hr_holidays_ux/models/hr_work_entry_type.py similarity index 71% rename from hr_holidays_ux/models/hr_leave_type.py rename to hr_holidays_ux/models/hr_work_entry_type.py index 5f9aecbe..8c123b94 100644 --- a/hr_holidays_ux/models/hr_leave_type.py +++ b/hr_holidays_ux/models/hr_work_entry_type.py @@ -1,8 +1,8 @@ from odoo import fields, models -class HrLeaveType(models.Model): - _inherit = "hr.leave.type" +class HrWorkEntryType(models.Model): + _inherit = "hr.work.entry.type" pre_approved_instance = fields.Boolean( help="This instance is necessary when the supported document is added after the leave" diff --git a/hr_holidays_ux/tests/__init__.py b/hr_holidays_ux/tests/__init__.py new file mode 100644 index 00000000..2f28082a --- /dev/null +++ b/hr_holidays_ux/tests/__init__.py @@ -0,0 +1 @@ +from . import test_hr_holidays_ux diff --git a/hr_holidays_ux/tests/test_hr_holidays_ux.py b/hr_holidays_ux/tests/test_hr_holidays_ux.py new file mode 100644 index 00000000..a4814435 --- /dev/null +++ b/hr_holidays_ux/tests/test_hr_holidays_ux.py @@ -0,0 +1,188 @@ +from datetime import date +from unittest.mock import patch + +from odoo.addons.hr_holidays.tests.common import TestHrHolidaysCommon +from odoo.exceptions import UserError +from odoo.tests import tagged + + +@tagged("post_install", "-at_install") +class TestHrHolidaysUx(TestHrHolidaysCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.no_alloc_type = cls.env["hr.work.entry.type"].create( + { + "name": "UX No Allocation", + "code": "UXNA", + "requires_allocation": False, + "leave_validation_type": "hr", + "request_unit": "day", + "unit_of_measure": "day", + } + ) + cls.pre_approved_type = cls.env["hr.work.entry.type"].create( + { + "name": "UX Pre Approved", + "code": "UXPA", + "requires_allocation": False, + "leave_validation_type": "hr", + "support_document": True, + "pre_approved_instance": True, + "request_unit": "day", + "unit_of_measure": "day", + } + ) + cls.alloc_type = cls.env["hr.work.entry.type"].create( + { + "name": "UX With Allocation", + "code": "UXWA", + "requires_allocation": True, + "leave_validation_type": "hr", + "allocation_validation_type": "manager", + "request_unit": "day", + "unit_of_measure": "day", + } + ) + + def _create_leave(self, date_from, date_to, work_entry_type=None): + # Created by the employee so it is not auto-approved on create + leaves = ( + self.env["hr.leave"] + .with_user(self.user_employee) + .create( + { + "employee_id": self.employee_emp.id, + "work_entry_type_id": (work_entry_type or self.no_alloc_type).id, + "request_date_from": date_from, + "request_date_to": date_to, + } + ) + ) + return leaves.with_env(self.env) + + def _employee_leaves(self): + return self.env["hr.leave"].search([("employee_id", "=", self.employee_emp.id)], order="request_date_from") + + def test_create_splits_by_month(self): + self._create_leave(date(2027, 1, 25), date(2027, 2, 3)) + self.assertEqual( + [(leave.request_date_from, leave.request_date_to) for leave in self._employee_leaves()], + [(date(2027, 1, 25), date(2027, 1, 31)), (date(2027, 2, 1), date(2027, 2, 3))], + ) + + def test_create_same_month_not_split(self): + self._create_leave(date(2027, 1, 18), date(2027, 1, 22)) + self.assertEqual(len(self._employee_leaves()), 1) + + def test_write_splits_by_month(self): + leave = self._create_leave(date(2027, 3, 1), date(2027, 3, 5)) + leave.write({"request_date_to": date(2027, 4, 2)}) + self.assertEqual( + [(leave.request_date_from, leave.request_date_to) for leave in self._employee_leaves()], + [(date(2027, 3, 1), date(2027, 3, 31)), (date(2027, 4, 1), date(2027, 4, 2))], + ) + + def test_write_split_skips_overlapping_period(self): + leave = self._create_leave(date(2027, 3, 1), date(2027, 3, 5)) + existing = self._create_leave(date(2027, 4, 1), date(2027, 4, 2)) + leave.write({"request_date_to": date(2027, 4, 2)}) + self.assertEqual(self._employee_leaves(), leave | existing) + self.assertEqual(leave.request_date_to, date(2027, 3, 31)) + + def test_pre_approved_flow(self): + leave = self._create_leave(date(2027, 3, 8), date(2027, 3, 9), self.pre_approved_type) + leave.action_approve() + self.assertEqual(leave.state, "pre-validate") + self.env["ir.attachment"].create( + {"name": "certificate.txt", "raw": b"doc", "res_model": "hr.leave", "res_id": leave.id} + ) + leave.invalidate_recordset() + self.assertTrue(leave.supported_attachment_ids) + leave.action_post_approve() + self.assertEqual(leave.state, "validate") + + def _attach_document(self, leave): + self.env["ir.attachment"].create( + {"name": "certificate.txt", "raw": b"doc", "res_model": "hr.leave", "res_id": leave.id} + ) + leave.invalidate_recordset() + + def test_pre_approved_time_rules_run_on_post_approve(self): + leave = self._create_leave(date(2027, 3, 8), date(2027, 3, 9), self.pre_approved_type) + leave_class = type(self.env["hr.leave"]) + with patch.object(leave_class, "_trigger_time_rules", autospec=True) as trigger: + leave.action_approve() + self.assertEqual(leave.state, "pre-validate") + # empty recordsets also reach the mixin and are a no-op there + self.assertFalse([call.args[0] for call in trigger.call_args_list if call.args[0]]) + self._attach_document(leave) + leave.action_post_approve() + self.assertEqual(leave.state, "validate") + self.assertEqual([call.args[0] for call in trigger.call_args_list if call.args[0]], [leave]) + + def test_approve_in_batch(self): + pre_approved = self._create_leave(date(2027, 3, 8), date(2027, 3, 9), self.pre_approved_type) + regular = self._create_leave(date(2027, 3, 15), date(2027, 3, 16)) + (pre_approved | regular).action_approve() + self.assertEqual(pre_approved.state, "pre-validate") + self.assertEqual(regular.state, "validate") + + def test_approve_with_document_skips_pre_validate(self): + leave = self._create_leave(date(2027, 3, 8), date(2027, 3, 9), self.pre_approved_type) + self._attach_document(leave) + leave.action_approve() + self.assertEqual(leave.state, "validate") + + def test_approve_without_pre_approved_instance(self): + leave = self._create_leave(date(2027, 3, 8), date(2027, 3, 9)) + leave.action_approve() + self.assertEqual(leave.state, "validate") + + def test_pre_validate_back_to_confirm_requires_officer(self): + leave = self._create_leave(date(2027, 3, 8), date(2027, 3, 9), self.pre_approved_type) + leave.action_approve() + self.assertEqual(leave.state, "pre-validate") + with self.assertRaisesRegex(UserError, "pre-validated"): + leave.with_user(self.user_employee)._check_approval_update("confirm") + + def _create_allocation(self): + allocation = self.env["hr.leave.allocation"].create( + { + "employee_id": self.employee_emp.id, + "work_entry_type_id": self.alloc_type.id, + "number_of_days": 10, + "date_from": date(2027, 1, 1), + } + ) + allocation.action_approve() + self.assertEqual(allocation.state, "validate") + return allocation + + def test_reset_allocation_to_confirm(self): + allocation = self._create_allocation() + allocation.action_reset_to_confirm() + self.assertEqual(allocation.state, "confirm") + + def test_reset_allocation_with_leaves_taken(self): + allocation = self._create_allocation() + self._create_leave(date(2027, 3, 8), date(2027, 3, 9), self.alloc_type).action_approve() + allocation.invalidate_recordset() + self.assertGreater(allocation.leaves_taken, 0) + with self.assertRaisesRegex(UserError, "already have leaves taken"): + allocation.action_reset_to_confirm() + self.assertEqual(allocation.state, "validate") + + def test_generate_multi_allocations_not_approved(self): + wizard = self.env["hr.leave.allocation.generate.multi.wizard"].create( + { + "work_entry_type_id": self.alloc_type.id, + "employee_ids": [(6, 0, (self.employee_emp | self.employee_hruser).ids)], + "duration": 5, + "date_from": date(2027, 1, 1), + } + ) + action = wizard.action_generate_allocations() + allocations = self.env["hr.leave.allocation"].search(action["domain"]) + self.assertEqual(len(allocations), 2) + self.assertEqual(set(allocations.mapped("state")), {"confirm"}) diff --git a/hr_holidays_ux/views/hr_leave_type_views.xml b/hr_holidays_ux/views/hr_leave_type_views.xml deleted file mode 100644 index 6d57f708..00000000 --- a/hr_holidays_ux/views/hr_leave_type_views.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - hr.leave.type.form - hr.leave.type - - - - - - - - - - diff --git a/hr_holidays_ux/views/hr_leave_views.xml b/hr_holidays_ux/views/hr_leave_views.xml index e2f3a7c7..276fa791 100644 --- a/hr_holidays_ux/views/hr_leave_views.xml +++ b/hr_holidays_ux/views/hr_leave_views.xml @@ -5,11 +5,6 @@ hr.leave - - - not leave_type_support_document or state not in ('confirm', 'validate1', 'pre-validate') - -