diff --git a/hr_contract_employee_calendar_planning/README.rst b/hr_contract_employee_calendar_planning/README.rst new file mode 100644 index 00000000000..aed44ae9b23 --- /dev/null +++ b/hr_contract_employee_calendar_planning/README.rst @@ -0,0 +1,116 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +====================================== +Hr Contract Employee Calendar Planning +====================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:18ed8fc47869734b2baa5068f1346d1bbc662d2b834e9a2efa4ce502117ea7fa + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fhr-lightgray.png?logo=github + :target: https://github.com/OCA/hr/tree/19.0/hr_contract_employee_calendar_planning + :alt: OCA/hr +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr-19-0/hr-19-0-hr_contract_employee_calendar_planning + :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&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module ensures a consistent working times history when using the +**hr_contract** and **hr_employee_calendar_planning** modules together. + +There are 3 different data models which can relate to working time +(resource.calendar) records: + +- Employees (hr.employee) +- Contracts (hr.contract) +- Resources (resource.resource) -> related to hr.employee through + resource.mixin + +The **hr_employee_calendar_planning** module adds the calendar_ids field +to employees, which allows a more flexible working times configuration: +Instead of selecting a single resource.calendar, multiple calendars can +be combined into one auto-generated calendar. + +However, contracts are not considered when creating auto-generated +calendars. This can lead to unexpected behaviour, because the active +contract calendar and the employee calendar can diverge (calendar +mismatch). Additionally, when configuring a new contract, or changing +the existing contract calendar, the employee calendar will be +overwritten by the contract calendar. + +To resolve this issue, this module migrates current and previous +contract calendars into the calendar_ids and thus the auto-generated +calendar. Additionally, changes to the employee calendar by contracts +are prevented. The resource_calendar_id field is hidden in contract +views, since all working times should be managed in the calendar_ids +field for each employee. However, it is still possible to keep a +meaningful contract history, e.g. for salary information. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + + + +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 +------- + +* cibex + +Contributors +------------ + +- Jonas Buchholz +- `Studio73 `__: +- Vicent Castells + +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 `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_contract_employee_calendar_planning/__init__.py b/hr_contract_employee_calendar_planning/__init__.py new file mode 100644 index 00000000000..6f4a716678c --- /dev/null +++ b/hr_contract_employee_calendar_planning/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import models +from .hooks import post_init_hook diff --git a/hr_contract_employee_calendar_planning/__manifest__.py b/hr_contract_employee_calendar_planning/__manifest__.py new file mode 100644 index 00000000000..5fc6ff33dc1 --- /dev/null +++ b/hr_contract_employee_calendar_planning/__manifest__.py @@ -0,0 +1,14 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Hr Contract Employee Calendar Planning", + "version": "19.0.1.0.0", + "category": "Human Resources", + "website": "https://github.com/OCA/hr", + "author": "cibex,Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "auto_install": True, + "depends": ["hr", "hr_employee_calendar_planning", "resource"], + "data": ["views/contract.xml"], + "post_init_hook": "post_init_hook", +} diff --git a/hr_contract_employee_calendar_planning/hooks.py b/hr_contract_employee_calendar_planning/hooks.py new file mode 100644 index 00000000000..c016de1db03 --- /dev/null +++ b/hr_contract_employee_calendar_planning/hooks.py @@ -0,0 +1,77 @@ +import logging + +from odoo.fields import Command + +_logger = logging.getLogger(__name__) + + +def post_init_hook(env, employees=None): + """Migrate calendars from versions to calendar_ids + to have consistent work schedule history""" + if not employees: + employees = env["hr.employee"].with_context(active_test=False).search([]) + + for employee in employees.filtered( + lambda e: e.version_ids.filtered("contract_date_start") + ): + version_calendar_lines = [] + versions = employee.version_ids.filtered("contract_date_start").sorted( + "date_start" + ) + for version in versions: + date_start = version.date_start + date_end = version.date_end + # filter calendar_ids to check for overlaps with versions + # with the same work schedule + cal_ids = employee.calendar_ids.filtered( + lambda x, version=version: x.calendar_id == version.resource_calendar_id + and ( + not x.date_start + or not version.date_end + or x.date_start < version.date_end + ) + and ( + not x.date_end + or not version.date_start + or x.date_end > version.date_start + ) + ) + if cal_ids: + _logger.info(f"{version} is overlapping with {cal_ids}") + for calendar in cal_ids.sorted("date_start"): + if date_start and calendar.date_start != date_start: + _logger.info( + f"changing date_start of {calendar} " + f"from {calendar.date_start} to {date_start}" + ) + calendar.date_start = date_start + if date_end and calendar.date_end != date_end: + _logger.info( + f"changing date_end of {calendar} " + f"from {calendar.date_end} to {date_end}" + ) + calendar.date_end = date_end + break + else: + _logger.info( + f"adding new calendar_id for {version.employee_id.name}: " + f"{version.resource_calendar_id.name} " + f"from {date_start} to {date_end}" + ) + version_calendar_lines.append( + Command.create( + { + "date_start": date_start, + "date_end": date_end, + "calendar_id": version.resource_calendar_id.id, + } + ) + ) + + employee.calendar_ids = version_calendar_lines + + # set correct calendar in current version + # Prevent the resource calendar of leaves to be updated by a write + employee.version_id.with_context(no_leave_resource_calendar_update=True).update( + {"resource_calendar_id": employee.resource_calendar_id.id} + ) diff --git a/hr_contract_employee_calendar_planning/i18n/hr_contract_employee_calendar_planning.pot b/hr_contract_employee_calendar_planning/i18n/hr_contract_employee_calendar_planning.pot new file mode 100644 index 00000000000..8732aa37307 --- /dev/null +++ b/hr_contract_employee_calendar_planning/i18n/hr_contract_employee_calendar_planning.pot @@ -0,0 +1,19 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_contract_employee_calendar_planning +# +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: hr_contract_employee_calendar_planning +#: model:ir.model,name:hr_contract_employee_calendar_planning.model_hr_contract +msgid "Contract" +msgstr "" diff --git a/hr_contract_employee_calendar_planning/i18n/it.po b/hr_contract_employee_calendar_planning/i18n/it.po new file mode 100644 index 00000000000..c64c71d0154 --- /dev/null +++ b/hr_contract_employee_calendar_planning/i18n/it.po @@ -0,0 +1,25 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_contract_employee_calendar_planning +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-12-04 12:06+0000\n" +"Last-Translator: mymage \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" +"X-Generator: Weblate 5.6.2\n" + +#. module: hr_contract_employee_calendar_planning +#: model:ir.model,name:hr_contract_employee_calendar_planning.model_hr_contract +msgid "Contract" +msgstr "Contratto" + +#~ msgid "Employee Contract" +#~ msgstr "Contratto dipendente" diff --git a/hr_contract_employee_calendar_planning/models/__init__.py b/hr_contract_employee_calendar_planning/models/__init__.py new file mode 100644 index 00000000000..c942b570e64 --- /dev/null +++ b/hr_contract_employee_calendar_planning/models/__init__.py @@ -0,0 +1 @@ +from . import hr_version diff --git a/hr_contract_employee_calendar_planning/models/hr_version.py b/hr_contract_employee_calendar_planning/models/hr_version.py new file mode 100644 index 00000000000..431f6975763 --- /dev/null +++ b/hr_contract_employee_calendar_planning/models/hr_version.py @@ -0,0 +1,40 @@ +from odoo import api, models + + +class HrVersion(models.Model): + _inherit = "hr.version" + + def write(self, vals): + if vals.get("resource_calendar_id") and self.filtered( + lambda version: version.employee_id + and vals.get("resource_calendar_id") + != version.employee_id.resource_calendar_id.id + ): + # in the write method of versions, when writing the resource_calendar_id + # the employee resource_calendar_id is set to the same id + # this interferes with the logic of hr_employee_calendar_planning, + # which assumes that calendar times are managed by + # resource.calendar.attendances in auto-generated calendars + # based on the employee's calendar_ids. + # since the default calendar for new versions is the employee calendar, + # and we set the correct calendar for the existing version + # in the post_init_hook, we resolve this conflict by not allowing + # calendar changes in versions. + vals.pop("resource_calendar_id") + return super().write(vals) + + @api.model_create_multi + def create(self, vals_list): + # the create method of versions syncs version + # calendars with employee calendars + # in order to not overwrite the employee calendar + # we set the version calendar to match the employee calendar + for vals in vals_list: + employee_calendar = ( + self.env["hr.employee"] + .browse([vals.get("employee_id")]) + .resource_calendar_id + ) + if employee_calendar: + vals.update({"resource_calendar_id": employee_calendar.id}) + return super().create(vals_list) diff --git a/hr_contract_employee_calendar_planning/pyproject.toml b/hr_contract_employee_calendar_planning/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/hr_contract_employee_calendar_planning/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/hr_contract_employee_calendar_planning/readme/CONTRIBUTORS.md b/hr_contract_employee_calendar_planning/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..29d9e540700 --- /dev/null +++ b/hr_contract_employee_calendar_planning/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- Jonas Buchholz \ +- [Studio73](https://www.studio73.es/): +- Vicent Castells \<\> diff --git a/hr_contract_employee_calendar_planning/readme/DESCRIPTION.md b/hr_contract_employee_calendar_planning/readme/DESCRIPTION.md new file mode 100644 index 00000000000..21f59ac3fce --- /dev/null +++ b/hr_contract_employee_calendar_planning/readme/DESCRIPTION.md @@ -0,0 +1,30 @@ +This module ensures a consistent working times history when using the +**hr_contract** and **hr_employee_calendar_planning** modules together. + +There are 3 different data models which can relate to working time +(resource.calendar) records: + +- Employees (hr.employee) +- Contracts (hr.contract) +- Resources (resource.resource) -\> related to hr.employee through + resource.mixin + +The **hr_employee_calendar_planning** module adds the calendar_ids field +to employees, which allows a more flexible working times configuration: +Instead of selecting a single resource.calendar, multiple calendars can +be combined into one auto-generated calendar. + +However, contracts are not considered when creating auto-generated +calendars. This can lead to unexpected behaviour, because the active +contract calendar and the employee calendar can diverge (calendar +mismatch). Additionally, when configuring a new contract, or changing +the existing contract calendar, the employee calendar will be +overwritten by the contract calendar. + +To resolve this issue, this module migrates current and previous +contract calendars into the calendar_ids and thus the auto-generated +calendar. Additionally, changes to the employee calendar by contracts +are prevented. The resource_calendar_id field is hidden in contract +views, since all working times should be managed in the calendar_ids +field for each employee. However, it is still possible to keep a +meaningful contract history, e.g. for salary information. diff --git a/hr_contract_employee_calendar_planning/readme/USAGE.md b/hr_contract_employee_calendar_planning/readme/USAGE.md new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/hr_contract_employee_calendar_planning/readme/USAGE.md @@ -0,0 +1 @@ + diff --git a/hr_contract_employee_calendar_planning/static/description/icon.png b/hr_contract_employee_calendar_planning/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/hr_contract_employee_calendar_planning/static/description/icon.png differ diff --git a/hr_contract_employee_calendar_planning/static/description/index.html b/hr_contract_employee_calendar_planning/static/description/index.html new file mode 100644 index 00000000000..20f1fac8145 --- /dev/null +++ b/hr_contract_employee_calendar_planning/static/description/index.html @@ -0,0 +1,461 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Hr Contract Employee Calendar Planning

+ +

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

+

This module ensures a consistent working times history when using the +hr_contract and hr_employee_calendar_planning modules together.

+

There are 3 different data models which can relate to working time +(resource.calendar) records:

+
    +
  • Employees (hr.employee)
  • +
  • Contracts (hr.contract)
  • +
  • Resources (resource.resource) -> related to hr.employee through +resource.mixin
  • +
+

The hr_employee_calendar_planning module adds the calendar_ids field +to employees, which allows a more flexible working times configuration: +Instead of selecting a single resource.calendar, multiple calendars can +be combined into one auto-generated calendar.

+

However, contracts are not considered when creating auto-generated +calendars. This can lead to unexpected behaviour, because the active +contract calendar and the employee calendar can diverge (calendar +mismatch). Additionally, when configuring a new contract, or changing +the existing contract calendar, the employee calendar will be +overwritten by the contract calendar.

+

To resolve this issue, this module migrates current and previous +contract calendars into the calendar_ids and thus the auto-generated +calendar. Additionally, changes to the employee calendar by contracts +are prevented. The resource_calendar_id field is hidden in contract +views, since all working times should be managed in the calendar_ids +field for each employee. However, it is still possible to keep a +meaningful contract history, e.g. for salary information.

+

Table of contents

+ +
+

Usage

+
+
+

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

+
    +
  • cibex
  • +
+
+
+

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

+

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

+
+
+
+
+ + diff --git a/hr_contract_employee_calendar_planning/tests/__init__.py b/hr_contract_employee_calendar_planning/tests/__init__.py new file mode 100644 index 00000000000..4c053338648 --- /dev/null +++ b/hr_contract_employee_calendar_planning/tests/__init__.py @@ -0,0 +1 @@ +from . import test_hr_contract_employee_calendar_planning diff --git a/hr_contract_employee_calendar_planning/tests/test_hr_contract_employee_calendar_planning.py b/hr_contract_employee_calendar_planning/tests/test_hr_contract_employee_calendar_planning.py new file mode 100644 index 00000000000..a6209894d96 --- /dev/null +++ b/hr_contract_employee_calendar_planning/tests/test_hr_contract_employee_calendar_planning.py @@ -0,0 +1,139 @@ +from datetime import datetime + +from odoo.addons.hr.tests.common import TestHrCommon + +from ..hooks import post_init_hook + + +class TestHrContractEmployeeCalendarPlanning(TestHrCommon): + def test_calendar_migration_from_contracts(self): + calendar_40h = self.env["resource.calendar"].browse(1) + calendar_35h = calendar_40h.copy( + { + "name": "Test 35h calendar", + "attendance_ids": [(5, 0, 0)], + } + ) + calendar_35h.write( + { + "attendance_ids": [ + ( + 0, + 0, + { + "name": "Monday", + "dayofweek": "0", + "hour_from": 8, + "hour_to": 15, + }, + ), + ], + } + ) + + self.assertTrue(calendar_40h.exists()) + self.assertTrue(calendar_35h.exists()) + self.env = self.env( + context=dict( + self.env.context, + mail_create_nolog=True, + mail_create_nosubscribe=True, + mail_notrack=True, + no_reset_password=True, + tracking_disable=True, + ) + ) + + # newly created contracts get the same calendar as the employee + self.employee.resource_calendar_id = calendar_40h + self.employee.create_version( + { + "name": "contract1", + "wage": 1, + "date_version": datetime.strptime("2018-11-30", "%Y-%m-%d").date(), + "contract_date_start": datetime.strptime( + "2018-11-30", "%Y-%m-%d" + ).date(), + "contract_date_end": datetime.strptime("2019-11-30", "%Y-%m-%d").date(), + } + ) + self.employee.resource_calendar_id = calendar_35h + self.employee.create_version( + { + "name": "contract2", + "wage": 1, + "date_version": datetime.strptime("2019-12-01", "%Y-%m-%d").date(), + "contract_date_start": datetime.strptime( + "2019-12-01", "%Y-%m-%d" + ).date(), + "contract_date_end": datetime.strptime("2020-11-30", "%Y-%m-%d").date(), + } + ) + self.employee.calendar_ids.unlink() + calendar_ids = self.env["hr.employee.calendar"].create( + [ + { + "date_start": False, + "date_end": datetime.strptime("2020-11-30", "%Y-%m-%d").date(), + "calendar_id": calendar_35h.id, + "employee_id": self.employee.id, + }, + { + "date_start": datetime.strptime("2020-12-01", "%Y-%m-%d").date(), + "date_end": False, + "calendar_id": calendar_40h.id, + "employee_id": self.employee.id, + }, + ] + ) + start_dt = datetime(2019, 1, 1, 0, 0, 0) + end_dt = datetime(2019, 1, 2, 0, 0, 0) + self.assertEqual( + 7.0, + self.employee.resource_calendar_id.get_work_hours_count( + start_dt=start_dt, + end_dt=end_dt, + ), + ) + # calendar migration from contracts + post_init_hook(self.env, self.employee) + self.assertEqual( + 8.0, + self.employee.resource_calendar_id.get_work_hours_count( + start_dt=start_dt, + end_dt=end_dt, + ), + ) + self.assertTrue(calendar_ids.ids < self.employee.calendar_ids.ids) + + def test_contract_create_write(self): + self.employee.resource_calendar_id = self.env["resource.calendar"].browse([1]) + version = self.env["hr.version"].create( + { + "name": "contract1", + "wage": 1, + "employee_id": self.employee.id, + "date_version": datetime.strptime("2018-11-30", "%Y-%m-%d").date(), + "contract_date_start": datetime.strptime( + "2018-11-30", "%Y-%m-%d" + ).date(), + "contract_date_end": datetime.strptime("2019-11-30", "%Y-%m-%d").date(), + "resource_calendar_id": self.env["resource.calendar"].browse([2]).id, + } + ) + self.assertEqual(self.employee.resource_calendar_id.id, 1) + version.write( + { + "resource_calendar_id": self.env["resource.calendar"].browse([2]).id, + } + ) + self.assertEqual(self.employee.resource_calendar_id.id, 1) + + def test_contract_write(self): + self.employee.resource_calendar_id = self.env["resource.calendar"].browse([1]) + self.env["hr.version"].write( + { + "resource_calendar_id": 2, + } + ) + self.assertEqual(self.employee.resource_calendar_id.id, 1) diff --git a/hr_contract_employee_calendar_planning/views/contract.xml b/hr_contract_employee_calendar_planning/views/contract.xml new file mode 100644 index 00000000000..512d54d1d9b --- /dev/null +++ b/hr_contract_employee_calendar_planning/views/contract.xml @@ -0,0 +1,28 @@ + + + hr.employee.calendar.planning.form + hr.employee + + 999 + + + + + + + + + hr.version.employee.calendar.planning.history.form + hr.version + + 999 + + + + + + +