From b737835209fdce038297cf0a3b58c1820f7d126f Mon Sep 17 00:00:00 2001 From: hugues de keyzer Date: Tue, 27 Jan 2026 14:55:37 +0100 Subject: [PATCH] [ADD] hr_work_entry_contract_constrained_attendance add new module hr_work_entry_contract_constrained_attendance. --- .../README.rst | 63 +++ .../__init__.py | 5 + .../__manifest__.py | 22 + .../i18n/fr.po | 45 ++ ..._entry_contract_constrained_attendance.pot | 41 ++ .../models/__init__.py | 7 + .../models/hr_contract.py | 75 ++++ .../models/res_company.py | 18 + .../models/res_config_settings.py | 13 + .../pyproject.toml | 3 + .../readme/CONTRIBUTORS.md | 2 + .../readme/DESCRIPTION.md | 1 + .../static/description/index.html | 420 ++++++++++++++++++ .../tests/__init__.py | 5 + ...k_entry_contract_constrained_attendance.py | 48 ++ .../views/res_config_settings_views.xml | 31 ++ 16 files changed, 799 insertions(+) create mode 100644 hr_work_entry_contract_constrained_attendance/README.rst create mode 100644 hr_work_entry_contract_constrained_attendance/__init__.py create mode 100644 hr_work_entry_contract_constrained_attendance/__manifest__.py create mode 100644 hr_work_entry_contract_constrained_attendance/i18n/fr.po create mode 100644 hr_work_entry_contract_constrained_attendance/i18n/hr_work_entry_contract_constrained_attendance.pot create mode 100644 hr_work_entry_contract_constrained_attendance/models/__init__.py create mode 100644 hr_work_entry_contract_constrained_attendance/models/hr_contract.py create mode 100644 hr_work_entry_contract_constrained_attendance/models/res_company.py create mode 100644 hr_work_entry_contract_constrained_attendance/models/res_config_settings.py create mode 100644 hr_work_entry_contract_constrained_attendance/pyproject.toml create mode 100644 hr_work_entry_contract_constrained_attendance/readme/CONTRIBUTORS.md create mode 100644 hr_work_entry_contract_constrained_attendance/readme/DESCRIPTION.md create mode 100644 hr_work_entry_contract_constrained_attendance/static/description/index.html create mode 100644 hr_work_entry_contract_constrained_attendance/tests/__init__.py create mode 100644 hr_work_entry_contract_constrained_attendance/tests/test_hr_work_entry_contract_constrained_attendance.py create mode 100644 hr_work_entry_contract_constrained_attendance/views/res_config_settings_views.xml diff --git a/hr_work_entry_contract_constrained_attendance/README.rst b/hr_work_entry_contract_constrained_attendance/README.rst new file mode 100644 index 0000000..dc2f6ff --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/README.rst @@ -0,0 +1,63 @@ +============================================= +Constrain Attendance Work Entries to Contract +============================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:26331c3fff67ce6ea8a3816e41b0174fc536ccff3f43f1de37ed1ad0f8acdc11 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-coopiteasy%2Fcie--hr-lightgray.png?logo=github + :target: https://github.com/coopiteasy/cie-hr/tree/18.0/hr_work_entry_contract_constrained_attendance + :alt: coopiteasy/cie-hr + +|badge1| |badge2| |badge3| + +Constrain work entries generated from attendances to the contract's +working schedule. + +**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 +------- + +* Coop IT Easy SC + +Contributors +------------ + +- `Coop IT Easy SC `__: + + - hugues de keyzer + +Maintainers +----------- + +This module is part of the `coopiteasy/cie-hr `_ project on GitHub. + +You are welcome to contribute. diff --git a/hr_work_entry_contract_constrained_attendance/__init__.py b/hr_work_entry_contract_constrained_attendance/__init__.py new file mode 100644 index 0000000..c581248 --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/__init__.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2026 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +from . import models diff --git a/hr_work_entry_contract_constrained_attendance/__manifest__.py b/hr_work_entry_contract_constrained_attendance/__manifest__.py new file mode 100644 index 0000000..74ad9fe --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/__manifest__.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2026 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +{ + "name": "Constrain Attendance Work Entries to Contract", + "summary": ( + "Constrain work entries generated from attendances to the contract's " + "working schedule" + ), + "version": "18.0.1.0.0", + "category": "Human Resources/Employees", + "website": "https://github.com/coopiteasy/cie-hr", + "author": "Coop IT Easy SC", + "license": "AGPL-3", + "depends": [ + "hr_work_entry_contract_attendance_oca", + ], + "data": [ + "views/res_config_settings_views.xml", + ], +} diff --git a/hr_work_entry_contract_constrained_attendance/i18n/fr.po b/hr_work_entry_contract_constrained_attendance/i18n/fr.po new file mode 100644 index 0000000..12f3635 --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/i18n/fr.po @@ -0,0 +1,45 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_work_entry_contract_constrained_attendance +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +#. module: hr_work_entry_contract_constrained_attendance +#: model:ir.model.fields,field_description:hr_work_entry_contract_constrained_attendance.field_res_company__absence_hr_work_entry_type_id +#: model:ir.model.fields,field_description:hr_work_entry_contract_constrained_attendance.field_res_config_settings__absence_hr_work_entry_type_id +#: model_terms:ir.ui.view,arch_db:hr_work_entry_contract_constrained_attendance.res_config_settings_view_form +msgid "Absence Work Entry Type" +msgstr "Type de prestation pour les absences" + +#. module: hr_work_entry_contract_constrained_attendance +#: model:ir.model,name:hr_work_entry_contract_constrained_attendance.model_res_company +msgid "Companies" +msgstr "" + +#. module: hr_work_entry_contract_constrained_attendance +#: model:ir.model,name:hr_work_entry_contract_constrained_attendance.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: hr_work_entry_contract_constrained_attendance +#: model:ir.model,name:hr_work_entry_contract_constrained_attendance.model_hr_contract +msgid "Employee Contract" +msgstr "" + +#. module: hr_work_entry_contract_constrained_attendance +#: model_terms:ir.ui.view,arch_db:hr_work_entry_contract_constrained_attendance.res_config_settings_view_form +msgid "Work entry type to use for work entries corresponding to an absence." +msgstr "" +"Type de prestation à utiliser pour les prestations correspondant à une " +"absence." diff --git a/hr_work_entry_contract_constrained_attendance/i18n/hr_work_entry_contract_constrained_attendance.pot b/hr_work_entry_contract_constrained_attendance/i18n/hr_work_entry_contract_constrained_attendance.pot new file mode 100644 index 0000000..c9c9e1f --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/i18n/hr_work_entry_contract_constrained_attendance.pot @@ -0,0 +1,41 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_work_entry_contract_constrained_attendance +# +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_work_entry_contract_constrained_attendance +#: model:ir.model.fields,field_description:hr_work_entry_contract_constrained_attendance.field_res_company__absence_hr_work_entry_type_id +#: model:ir.model.fields,field_description:hr_work_entry_contract_constrained_attendance.field_res_config_settings__absence_hr_work_entry_type_id +#: model_terms:ir.ui.view,arch_db:hr_work_entry_contract_constrained_attendance.res_config_settings_view_form +msgid "Absence Work Entry Type" +msgstr "" + +#. module: hr_work_entry_contract_constrained_attendance +#: model:ir.model,name:hr_work_entry_contract_constrained_attendance.model_res_company +msgid "Companies" +msgstr "" + +#. module: hr_work_entry_contract_constrained_attendance +#: model:ir.model,name:hr_work_entry_contract_constrained_attendance.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: hr_work_entry_contract_constrained_attendance +#: model:ir.model,name:hr_work_entry_contract_constrained_attendance.model_hr_contract +msgid "Employee Contract" +msgstr "" + +#. module: hr_work_entry_contract_constrained_attendance +#: model_terms:ir.ui.view,arch_db:hr_work_entry_contract_constrained_attendance.res_config_settings_view_form +msgid "Work entry type to use for work entries corresponding to an absence." +msgstr "" diff --git a/hr_work_entry_contract_constrained_attendance/models/__init__.py b/hr_work_entry_contract_constrained_attendance/models/__init__.py new file mode 100644 index 0000000..ea10dd5 --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/models/__init__.py @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2026 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +from . import hr_contract +from . import res_company +from . import res_config_settings diff --git a/hr_work_entry_contract_constrained_attendance/models/hr_contract.py b/hr_work_entry_contract_constrained_attendance/models/hr_contract.py new file mode 100644 index 0000000..32be388 --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/models/hr_contract.py @@ -0,0 +1,75 @@ +# SPDX-FileCopyrightText: 2026 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +from datetime import timedelta + +import pytz + +from odoo import api, fields, models + +from odoo.addons.hr_work_entry_contract.models.hr_work_intervals import WorkIntervals + + +class HrContract(models.Model): + _inherit = "hr.contract" + + def _get_open_hr_attendances(self, start_dt, end_dt): + # return attendances that are still open and that started in the + # interval + return self.env["hr.attendance"].search( + [ + ("employee_id", "=", self.employee_id.id), + ("check_out", "=", False), + ("check_in", ">=", start_dt), + ("check_in", "<=", end_dt), + ] + ) + + @api.model + def _constrain_hr_attendance_intervals_to_contract( + self, hr_attendance_intervals, contract_intervals, limit_dt + ): + """ + Constrain hr.attendance work intervals to the contract attendance + intervals, and drop all intervals that end after limit_dt. + """ + # intersection with the attendances first return the attendance + # intervals constrained to the contract intervals, but still linked to + # the hr.attendance record. + presence_intervals = hr_attendance_intervals & contract_intervals + absence_intervals = contract_intervals - hr_attendance_intervals + all_intervals = presence_intervals | absence_intervals + return WorkIntervals(filter(lambda x: x[1] < limit_dt, all_intervals)) + + def _get_attendance_intervals(self, start_dt, end_dt): + result = super()._get_attendance_intervals(start_dt, end_dt) + contracts = self.filtered(lambda c: c.work_entry_source == "attendance_oca") + contract_intervals = contracts._get_hr_attendance_contract_intervals( + start_dt, end_dt + ) + now = fields.Datetime.now() + # limit datetime for attendances that don't have a check_out date. + default_check_out_dt = now + timedelta(minutes=1) + now_with_tz = pytz.utc.localize(now) + for contract in contracts: + open_hr_attendances = contract._get_open_hr_attendances(start_dt, end_dt) + open_hr_attendance_intervals = self._hr_attendances_to_work_intervals( + open_hr_attendances, default_check_out_dt=default_check_out_dt + ) + resource_id = contract.employee_id.resource_id.id + intervals = contract._constrain_hr_attendance_intervals_to_contract( + result[resource_id] | open_hr_attendance_intervals, + contract_intervals[resource_id], + now_with_tz, + ) + result[resource_id] = intervals + return result + + def _get_interval_work_entry_type(self, interval): + if ( + self.work_entry_source == "attendance_oca" + and interval[2]._name != "hr.attendance" + ): + return self.env.company.absence_hr_work_entry_type_id + return super()._get_interval_work_entry_type(interval) diff --git a/hr_work_entry_contract_constrained_attendance/models/res_company.py b/hr_work_entry_contract_constrained_attendance/models/res_company.py new file mode 100644 index 0000000..d745062 --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/models/res_company.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2026 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + def _get_default_absence_hr_work_entry_type(self): + return self.env.ref("hr_work_entry_contract.work_entry_type_unpaid_leave") + + absence_hr_work_entry_type_id = fields.Many2one( + "hr.work.entry.type", + "Absence Work Entry Type", + default=_get_default_absence_hr_work_entry_type, + ) diff --git a/hr_work_entry_contract_constrained_attendance/models/res_config_settings.py b/hr_work_entry_contract_constrained_attendance/models/res_config_settings.py new file mode 100644 index 0000000..66d0c23 --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/models/res_config_settings.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2026 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + absence_hr_work_entry_type_id = fields.Many2one( + related="company_id.absence_hr_work_entry_type_id", readonly=False + ) diff --git a/hr_work_entry_contract_constrained_attendance/pyproject.toml b/hr_work_entry_contract_constrained_attendance/pyproject.toml new file mode 100644 index 0000000..4231d0c --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/hr_work_entry_contract_constrained_attendance/readme/CONTRIBUTORS.md b/hr_work_entry_contract_constrained_attendance/readme/CONTRIBUTORS.md new file mode 100644 index 0000000..c8cb5ac --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +* [Coop IT Easy SC](https://coopiteasy.be): + * hugues de keyzer diff --git a/hr_work_entry_contract_constrained_attendance/readme/DESCRIPTION.md b/hr_work_entry_contract_constrained_attendance/readme/DESCRIPTION.md new file mode 100644 index 0000000..48f6a4b --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/readme/DESCRIPTION.md @@ -0,0 +1 @@ +Constrain work entries generated from attendances to the contract's working schedule. diff --git a/hr_work_entry_contract_constrained_attendance/static/description/index.html b/hr_work_entry_contract_constrained_attendance/static/description/index.html new file mode 100644 index 0000000..95de7fa --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/static/description/index.html @@ -0,0 +1,420 @@ + + + + + +Constrain Attendance Work Entries to Contract + + + +
+

Constrain Attendance Work Entries to Contract

+ + +

Beta License: AGPL-3 coopiteasy/cie-hr

+

Constrain work entries generated from attendances to the contract’s +working schedule.

+

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

+
    +
  • Coop IT Easy SC
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is part of the coopiteasy/cie-hr project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/hr_work_entry_contract_constrained_attendance/tests/__init__.py b/hr_work_entry_contract_constrained_attendance/tests/__init__.py new file mode 100644 index 0000000..b755ad4 --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/tests/__init__.py @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2026 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +from . import test_hr_work_entry_contract_constrained_attendance diff --git a/hr_work_entry_contract_constrained_attendance/tests/test_hr_work_entry_contract_constrained_attendance.py b/hr_work_entry_contract_constrained_attendance/tests/test_hr_work_entry_contract_constrained_attendance.py new file mode 100644 index 0000000..9f05c8a --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/tests/test_hr_work_entry_contract_constrained_attendance.py @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2026 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +from odoo.tests.common import TransactionCase + + +class TestHRWorkEntryContractAttendance(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + # TODO: test that an attendance that begins before the working schedule + # slot generates a work entry that is trimmed to the beginning of the + # working schedule slot + + # TODO: test that an attendance that ends after the working schedule slot + # generates a work entry that is trimmed to the end of the working + # schedule slot + + # TODO: test that an attendance that begins before and ends after the work + # schedule slot generates a work entry that is trimmed to the beginning + # and end of the working schedule slot + + # TODO: test that an attendance that begins after the working schedule + # slot generates two work entries: one from the beginning of the working + # schedule slot to the beginning of the attendance and one corresponding + # to the attendance + + # TODO: test that an attendance that ends before the working schedule slot + # generates two work entries: one corresponding to the attendance and one + # from the end on the attendance to the end of the working schedule slot + + # TODO: test that the work entry at the beginning of a working schedule + # slot not corresponding to an attendance is always linked to the first + # attendance of the slot (if any) (should it?) + + # TODO: test that the work entry at the end of a working schedule slot not + # corresponding to an attendance is always linked to the last attendance + # of the slot (if any) (should it?) + + # TODO: test that modifying an attendance modifies or deletes the + # corresponding absence work entries + + # TODO: test that work entries are generated for working schedule slots + # that are not covered by attendances (but when? when a new (later) + # attendance is created: fill the gaps, or when the work entries are + # (re)generated manually, or each night) diff --git a/hr_work_entry_contract_constrained_attendance/views/res_config_settings_views.xml b/hr_work_entry_contract_constrained_attendance/views/res_config_settings_views.xml new file mode 100644 index 0000000..473afb9 --- /dev/null +++ b/hr_work_entry_contract_constrained_attendance/views/res_config_settings_views.xml @@ -0,0 +1,31 @@ + + + + + res.config.settings.view.form.inherit + res.config.settings + + + + + + + + + +