diff --git a/hr_contract_employee_calendar_planning/README.rst b/hr_contract_employee_calendar_planning/README.rst new file mode 100644 index 00000000000..89e6ae3bec6 --- /dev/null +++ b/hr_contract_employee_calendar_planning/README.rst @@ -0,0 +1,103 @@ +====================================== +Hr Contract Employee Calendar Planning +====================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:945d7b6f8371eb43e7b75d570ebe24e46153a4556691486880cec967241c37cb + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-lightgray.png?logo=github + :target: https://github.com/OCA/hr/tree/16.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-16-0/hr-16-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=16.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: + +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 + +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..5eef8416827 --- /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": "16.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_contract", "hr_employee_calendar_planning"], + "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..88c19ae5ad6 --- /dev/null +++ b/hr_contract_employee_calendar_planning/hooks.py @@ -0,0 +1,63 @@ +import logging + +from odoo import SUPERUSER_ID, api + +_logger = logging.getLogger(__name__) + + +def post_init_hook(cr, registry, employees=None): + """Migrate calendars from contracts to calendar_ids + to have consistent work schedule history""" + env = api.Environment(cr, SUPERUSER_ID, {}) + if not employees: + employees = env["hr.employee"].with_context(active_test=False).search([]) + + for employee in employees.filtered("contract_ids"): + contract_calendar_lines = [] + for contract in employee.contract_ids.sorted("date_start"): + date_start = contract.date_start + date_end = contract.date_end + # filter calendar_ids to check for overlaps with contracts + # with the same work schedule + cal_ids = employee.calendar_ids.filtered( + lambda x: x.calendar_id == contract.resource_calendar_id + and (not x.date_start or not date_end or x.date_start < date_end) + and (not x.date_end or not date_start or x.date_end > date_start) + ) + if cal_ids: + _logger.info(f"{contract} 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 {contract.employee_id.name}: " + f"{contract.resource_calendar_id.name} from {date_start} to {date_end}" + ) + contract_calendar_lines.append( + ( + 0, + 0, + { + "date_start": date_start, + "date_end": date_end, + "calendar_id": contract.resource_calendar_id.id, + }, + ) + ) + + employee.calendar_ids = contract_calendar_lines + + # set correct calendar in contract + employee.contract_id.resource_calendar_id = employee.resource_calendar_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..9c98af3df67 --- /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 15.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 "Employee 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..e165ef8950b --- /dev/null +++ b/hr_contract_employee_calendar_planning/i18n/it.po @@ -0,0 +1,22 @@ +# 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: 2023-05-18 20:32+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 4.16.4\n" + +#. module: hr_contract_employee_calendar_planning +#: model:ir.model,name:hr_contract_employee_calendar_planning.model_hr_contract +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..99a5468ac8a --- /dev/null +++ b/hr_contract_employee_calendar_planning/models/__init__.py @@ -0,0 +1 @@ +from . import contract diff --git a/hr_contract_employee_calendar_planning/models/contract.py b/hr_contract_employee_calendar_planning/models/contract.py new file mode 100644 index 00000000000..2b21d7956f3 --- /dev/null +++ b/hr_contract_employee_calendar_planning/models/contract.py @@ -0,0 +1,38 @@ +from odoo import api, models + + +class HrContract(models.Model): + _inherit = "hr.contract" + + def write(self, vals): + if ( + vals.get("resource_calendar_id") + and self.employee_id + and vals.get("resource_calendar_id") + != self.employee_id.resource_calendar_id.id + ): + # in the write method of contracts, 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 contracts is the employee calendar + # and we set the correct calendar for the existing contract in the post_init_hook + # we resolve this conflict by not allowing calendar changes in contracts + vals.pop("resource_calendar_id") + return super().write(vals) + + @api.model + def create(self, vals): + # the create method of contracts syncs contract calendars with employee calendars + # in order to not overwrite the employee calendar + # we set the contract calendar to match the employee calendar + employee_contract = ( + self.env["hr.employee"] + .browse([vals.get("employee_id")]) + .resource_calendar_id + ) + if employee_contract: + vals.update({"resource_calendar_id": employee_contract.id}) + contracts = super(HrContract, self).create(vals) + return contracts diff --git a/hr_contract_employee_calendar_planning/readme/CONTRIBUTORS.rst b/hr_contract_employee_calendar_planning/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..a310dc5d781 --- /dev/null +++ b/hr_contract_employee_calendar_planning/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Jonas Buchholz diff --git a/hr_contract_employee_calendar_planning/readme/DESCRIPTION.rst b/hr_contract_employee_calendar_planning/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..33f40ed3aeb --- /dev/null +++ b/hr_contract_employee_calendar_planning/readme/DESCRIPTION.rst @@ -0,0 +1,28 @@ +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.rst b/hr_contract_employee_calendar_planning/readme/USAGE.rst new file mode 100644 index 00000000000..e69de29bb2d 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..fae04aaa6c8 --- /dev/null +++ b/hr_contract_employee_calendar_planning/static/description/index.html @@ -0,0 +1,445 @@ + + + + + + +Hr Contract Employee Calendar Planning + + + +
+

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

+ +
+

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..d7d05565504 --- /dev/null +++ b/hr_contract_employee_calendar_planning/tests/test_hr_contract_employee_calendar_planning.py @@ -0,0 +1,103 @@ +from datetime import datetime + +from odoo.addons.hr_contract.tests.common import TestContractCommon + +from ..hooks import post_init_hook + + +class TestHrContractEmployeeCalendarPlanning(TestContractCommon): + def test_calendar_migration_from_contracts(self): + 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 = self.env["resource.calendar"].browse([1]) + self.env["hr.contract"].create( + { + "name": "contract1", + "wage": 1, + "state": "close", + "employee_id": self.employee.id, + "date_start": datetime.strptime("2018-11-30", "%Y-%m-%d").date(), + "date_end": datetime.strptime("2019-11-30", "%Y-%m-%d").date(), + } + ) + self.employee.resource_calendar_id = self.env["resource.calendar"].browse([2]) + self.env["hr.contract"].create( + { + "name": "contract2", + "wage": 1, + "state": "open", + "employee_id": self.employee.id, + "date_start": datetime.strptime("2019-12-01", "%Y-%m-%d").date(), + "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": 2, + "employee_id": self.employee.id, + }, + { + "date_start": datetime.strptime("2020-12-01", "%Y-%m-%d").date(), + "date_end": False, + "calendar_id": 1, + "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.cr, self.env.registry, 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(self): + self.employee.resource_calendar_id = self.env["resource.calendar"].browse([1]) + self.env["hr.contract"].create( + { + "name": "contract1", + "wage": 1, + "state": "close", + "employee_id": self.employee.id, + "date_start": datetime.strptime("2018-11-30", "%Y-%m-%d").date(), + "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) + + def test_contract_write(self): + self.employee.resource_calendar_id = self.env["resource.calendar"].browse([1]) + self.env["hr.contract"].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..d32da9cee1d --- /dev/null +++ b/hr_contract_employee_calendar_planning/views/contract.xml @@ -0,0 +1,29 @@ + + + hr.contract.employee.calendar.planning.form + hr.contract + + 999 + + + + + + + + + + hr.contract.employee.calendar.planning.history.form + hr.contract.history + + 999 + + + + + + + + + + diff --git a/setup/hr_contract_employee_calendar_planning/odoo/addons/hr_contract_employee_calendar_planning b/setup/hr_contract_employee_calendar_planning/odoo/addons/hr_contract_employee_calendar_planning new file mode 120000 index 00000000000..3a0e5a276f0 --- /dev/null +++ b/setup/hr_contract_employee_calendar_planning/odoo/addons/hr_contract_employee_calendar_planning @@ -0,0 +1 @@ +../../../../hr_contract_employee_calendar_planning \ No newline at end of file diff --git a/setup/hr_contract_employee_calendar_planning/setup.py b/setup/hr_contract_employee_calendar_planning/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/hr_contract_employee_calendar_planning/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)