diff --git a/hr_attendance_contract_missing_days/README.rst b/hr_attendance_contract_missing_days/README.rst new file mode 100644 index 00000000..33bd33c4 --- /dev/null +++ b/hr_attendance_contract_missing_days/README.rst @@ -0,0 +1,74 @@ +============================================================== +Attendance generation for missing days with installed contract +============================================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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_attendance-lightgray.png?logo=github + :target: https://github.com/OCA/hr_attendance/tree/15.0/hr_attendance_contract_missing_days + :alt: OCA/hr_attendance +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr_attendance-15-0/hr_attendance-15-0-hr_attendance_contract_missing_days + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/hr_attendance&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This modules changes the generation of the attendances for the missing days to use the +HR contracts accordingly. + +**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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* initOS GmbH + +Contributors +~~~~~~~~~~~~ + +* initOS GmbH (initOS.com) + +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_attendance `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_attendance_contract_missing_days/__init__.py b/hr_attendance_contract_missing_days/__init__.py new file mode 100644 index 00000000..0f55bfa9 --- /dev/null +++ b/hr_attendance_contract_missing_days/__init__.py @@ -0,0 +1,4 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/hr_attendance_contract_missing_days/__manifest__.py b/hr_attendance_contract_missing_days/__manifest__.py new file mode 100644 index 00000000..d0ce0e8e --- /dev/null +++ b/hr_attendance_contract_missing_days/__manifest__.py @@ -0,0 +1,19 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Attendance generation for missing days with installed contract", + "version": "15.0.1.0.0", + "category": "Hidden", + "author": "initOS GmbH, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/hr-attendance", + "license": "AGPL-3", + "summary": "This modules combines the generation of attendances for working " + "days without attendance with HR contracts", + "depends": [ + "hr_contract", + "hr_attendance_missing_days", + ], + "auto_install": True, + "installable": True, +} diff --git a/hr_attendance_contract_missing_days/models/__init__.py b/hr_attendance_contract_missing_days/models/__init__.py new file mode 100644 index 00000000..868f64ba --- /dev/null +++ b/hr_attendance_contract_missing_days/models/__init__.py @@ -0,0 +1,4 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import hr_employee diff --git a/hr_attendance_contract_missing_days/models/hr_employee.py b/hr_attendance_contract_missing_days/models/hr_employee.py new file mode 100644 index 00000000..3e9b4882 --- /dev/null +++ b/hr_attendance_contract_missing_days/models/hr_employee.py @@ -0,0 +1,32 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import datetime, time + +import pytz + +from odoo import models + + +class Employee(models.Model): + _inherit = "hr.employee" + + def _get_work_intervals_batch(self, dt_from, dt_to): + intervals = [] + + tz = pytz.timezone(self.tz or "UTC") + for contract in self._get_contracts(dt_from, dt_to, states=["open", "close"]): + start = datetime.combine(contract.date_start, time.min) + start = max(dt_from, tz.localize(start).astimezone(pytz.UTC)) + + if contract.date_end: + end = datetime.combine(contract.date_end, time.max) + end = min(dt_to, tz.localize(end).astimezone(pytz.UTC)) + else: + end = dt_to + + intervals.extend( + contract.resource_calendar_id._work_intervals_batch(start, end)[False] + ) + + return intervals diff --git a/hr_attendance_contract_missing_days/readme/CONTRIBUTORS.rst b/hr_attendance_contract_missing_days/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..db386db6 --- /dev/null +++ b/hr_attendance_contract_missing_days/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* initOS GmbH (initOS.com) diff --git a/hr_attendance_contract_missing_days/readme/DESCRIPTION.rst b/hr_attendance_contract_missing_days/readme/DESCRIPTION.rst new file mode 100644 index 00000000..cee63803 --- /dev/null +++ b/hr_attendance_contract_missing_days/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This modules changes the generation of the attendances for the missing days to use the +HR contracts accordingly. diff --git a/hr_attendance_contract_missing_days/static/description/index.html b/hr_attendance_contract_missing_days/static/description/index.html new file mode 100644 index 00000000..fe3c368b --- /dev/null +++ b/hr_attendance_contract_missing_days/static/description/index.html @@ -0,0 +1,420 @@ + + + + + + +Attendance generation for missing days with installed contract + + + +
+

Attendance generation for missing days with installed contract

+ + +

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

+

This modules changes the generation of the attendances for the missing days to use the +HR contracts accordingly.

+

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 smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • initOS GmbH
  • +
+
+
+

Contributors

+
    +
  • initOS GmbH (initOS.com)
  • +
+
+
+

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

+

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

+
+
+
+ + diff --git a/hr_attendance_contract_missing_days/tests/__init__.py b/hr_attendance_contract_missing_days/tests/__init__.py new file mode 100644 index 00000000..1b6a5842 --- /dev/null +++ b/hr_attendance_contract_missing_days/tests/__init__.py @@ -0,0 +1 @@ +from . import test_hr_attendance_contract_missing_days diff --git a/hr_attendance_contract_missing_days/tests/test_hr_attendance_contract_missing_days.py b/hr_attendance_contract_missing_days/tests/test_hr_attendance_contract_missing_days.py new file mode 100644 index 00000000..9b9fc81a --- /dev/null +++ b/hr_attendance_contract_missing_days/tests/test_hr_attendance_contract_missing_days.py @@ -0,0 +1,23 @@ +from odoo.addons.hr_attendance_missing_days.tests import test_attendance + + +class TestAttendance(test_attendance.TestAttendance): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.contract = cls.env["hr.contract"].create( + { + "name": "2023", + "date_start": "2023-01-01", + "date_end": "2023-12-31", + "state": "open", + "wage": 42, + "employee_id": cls.employee.id, + } + ) + + def _clone_employee(self, employee, defaults): + result = super()._clone_employee(employee, defaults) + for contract in employee.contract_ids: + contract.copy({"employee_id": result.id, "state": "open"}) + return result diff --git a/hr_attendance_missing_days/README.rst b/hr_attendance_missing_days/README.rst new file mode 100644 index 00000000..45ed4cf4 --- /dev/null +++ b/hr_attendance_missing_days/README.rst @@ -0,0 +1,79 @@ +====================================== +Attendance generation for missing days +====================================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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_attendance-lightgray.png?logo=github + :target: https://github.com/OCA/hr_attendance/tree/15.0/hr_attendance_missing_days + :alt: OCA/hr_attendance +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr_attendance-15-0/hr_attendance-15-0-hr_attendance_missing_days + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/hr_attendance&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This modules generates attendances for employee with 0 minutes for working days with missing +attendances. The configured reason is set to make it easier for filtering. This can be used +to generate pseudo attendances for working days otherwise Odoo wouldn't reduce the overtime +of the employee. + +#. Go to *Attendances > Configuration > Missing Days* +#. Select a reason to set for the created attendances + +**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 smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* initOS GmbH + +Contributors +~~~~~~~~~~~~ + +* initOS GmbH (initOS.com) + +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_attendance `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_attendance_missing_days/__init__.py b/hr_attendance_missing_days/__init__.py new file mode 100644 index 00000000..0f55bfa9 --- /dev/null +++ b/hr_attendance_missing_days/__init__.py @@ -0,0 +1,4 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/hr_attendance_missing_days/__manifest__.py b/hr_attendance_missing_days/__manifest__.py new file mode 100644 index 00000000..3daf6be0 --- /dev/null +++ b/hr_attendance_missing_days/__manifest__.py @@ -0,0 +1,21 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Attendance generation for missing days", + "version": "15.0.1.0.0", + "category": "Hidden", + "author": "initOS GmbH, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/hr-attendance", + "license": "AGPL-3", + "summary": "This modules generates attendances for working days without attendance", + "depends": [ + "hr_attendance_reason", + ], + "data": [ + "data/hr_attendance_reason.xml", + "data/ir_cron.xml", + "views/res_config_settings_views.xml", + ], + "installable": True, +} diff --git a/hr_attendance_missing_days/data/hr_attendance_reason.xml b/hr_attendance_missing_days/data/hr_attendance_reason.xml new file mode 100644 index 00000000..74ac587a --- /dev/null +++ b/hr_attendance_missing_days/data/hr_attendance_reason.xml @@ -0,0 +1,7 @@ + + + + System generated attendances for missing days + S-GMD + + diff --git a/hr_attendance_missing_days/data/ir_cron.xml b/hr_attendance_missing_days/data/ir_cron.xml new file mode 100644 index 00000000..c616cf97 --- /dev/null +++ b/hr_attendance_missing_days/data/ir_cron.xml @@ -0,0 +1,20 @@ + + + + Missing Attendance + 1 + days + -1 + + + + + code + + model.create_missing_attendances(datetime.date.today() - datetime.timedelta(days=31)) + + + diff --git a/hr_attendance_missing_days/models/__init__.py b/hr_attendance_missing_days/models/__init__.py new file mode 100644 index 00000000..e5a43d85 --- /dev/null +++ b/hr_attendance_missing_days/models/__init__.py @@ -0,0 +1,4 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import hr_employee, res_company, res_config_settings diff --git a/hr_attendance_missing_days/models/hr_employee.py b/hr_attendance_missing_days/models/hr_employee.py new file mode 100644 index 00000000..e39ffb50 --- /dev/null +++ b/hr_attendance_missing_days/models/hr_employee.py @@ -0,0 +1,92 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import date, datetime, time, timedelta + +import pytz + +from odoo import models + +_logger = logging.getLogger(__name__) + + +def ensure_tz(dt, tz=None): + if not dt.tzinfo: + dt = pytz.utc.localize(dt) + return dt.astimezone(tz) if tz else dt + + +class Employee(models.Model): + _inherit = "hr.employee" + + def _prepare_missing_attendance_values(self, dt, reasons): + self.ensure_one() + return { + "employee_id": self.id, + "check_in": dt, + "check_out": dt, + "attendance_reason_ids": [(6, 0, reasons.ids)], + } + + def _get_work_intervals_batch(self, dt_from, dt_to): + self.ensure_one() + return self.resource_calendar_id._work_intervals_batch(dt_from, dt_to)[False] + + def create_missing_attendances(self, date_from=None, date_to=None): + for emp in self.search([]): + emp._create_missing_attendances(date_from, date_to) + + def _create_missing_attendances(self, date_from=None, date_to=None): + self.ensure_one() + + reason = self.env.company.sudo().attendance_missing_days_reason + if not reason: + return + + if not date_from: + date_from = self.env.company.sudo().overtime_start_date + + if not date_to: + date_to = date.today() + + # Determine the start and end of the day and convert to UTC + dt_from = datetime.combine(date_from, time.min) + dt_to = datetime.combine(date_to, time.max) + + tz = pytz.timezone(self.tz or "UTC") + dt_from, dt_to = map(tz.localize, (dt_from, dt_to)) + dt_from, dt_to = ensure_tz(dt_from, pytz.utc), ensure_tz(dt_to, pytz.utc) + + # Skip the active day + if dt_to.replace(tzinfo=None) > datetime.now(): + dt_to -= timedelta(days=1) + + if dt_from > dt_to: + return + + intervals = self._get_work_intervals_batch(dt_from, dt_to) + work_dates = {} + for start, _stop, _attendance in sorted(intervals): + start_date = start.date() + if start_date not in work_dates: + work_dates[start_date] = ensure_tz(start, pytz.utc).replace(tzinfo=None) + + domain = [ + ("check_in", ">=", dt_from.replace(tzinfo=None)), + ("check_in", "<=", dt_to.replace(tzinfo=None)), + ] + attendance_records = self.attendance_ids.filtered_domain(domain) + attendances = { + ensure_tz(attendance_date, tz).date() + for attendance_date in attendance_records.mapped("check_in") + + attendance_records.mapped("check_out") + } + + vals = [] + for missing in set(work_dates) - attendances: + vals.append( + self._prepare_missing_attendance_values(work_dates[missing], reason) + ) + + self.env["hr.attendance"].create(vals) diff --git a/hr_attendance_missing_days/models/res_company.py b/hr_attendance_missing_days/models/res_company.py new file mode 100644 index 00000000..8adc05d5 --- /dev/null +++ b/hr_attendance_missing_days/models/res_company.py @@ -0,0 +1,16 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + attendance_missing_days_reason = fields.Many2one( + "hr.attendance.reason", + default=lambda self: self.env.ref( + "hr_attendance_missing_days.attendance_reason_missing_days", + raise_if_not_found=False, + ), + ) diff --git a/hr_attendance_missing_days/models/res_config_settings.py b/hr_attendance_missing_days/models/res_config_settings.py new file mode 100644 index 00000000..968403a2 --- /dev/null +++ b/hr_attendance_missing_days/models/res_config_settings.py @@ -0,0 +1,13 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + attendance_missing_days_reason = fields.Many2one( + related="company_id.attendance_missing_days_reason", + readonly=False, + ) diff --git a/hr_attendance_missing_days/readme/CONTRIBUTORS.rst b/hr_attendance_missing_days/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..db386db6 --- /dev/null +++ b/hr_attendance_missing_days/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* initOS GmbH (initOS.com) diff --git a/hr_attendance_missing_days/readme/DESCRIPTION.rst b/hr_attendance_missing_days/readme/DESCRIPTION.rst new file mode 100644 index 00000000..2b6da7e0 --- /dev/null +++ b/hr_attendance_missing_days/readme/DESCRIPTION.rst @@ -0,0 +1,7 @@ +This modules generates attendances for employee with 0 minutes for working days with missing +attendances. The configured reason is set to make it easier for filtering. This can be used +to generate pseudo attendances for working days otherwise Odoo wouldn't reduce the overtime +of the employee. + +#. Go to *Attendances > Configuration > Missing Days* +#. Select a reason to set for the created attendances diff --git a/hr_attendance_missing_days/static/description/index.html b/hr_attendance_missing_days/static/description/index.html new file mode 100644 index 00000000..293ff594 --- /dev/null +++ b/hr_attendance_missing_days/static/description/index.html @@ -0,0 +1,426 @@ + + + + + + +Attendance generation for missing days + + + +
+

Attendance generation for missing days

+ + +

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

+

This modules generates attendances for employee with 0 minutes for working days with missing +attendances. The configured reason is set to make it easier for filtering. This can be used +to generate pseudo attendances for working days otherwise Odoo wouldn’t reduce the overtime +of the employee.

+
    +
  1. Go to Attendances > Configuration > Missing Days
  2. +
  3. Select a reason to set for the created attendances
  4. +
+

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 smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • initOS GmbH
  • +
+
+
+

Contributors

+
    +
  • initOS GmbH (initOS.com)
  • +
+
+
+

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

+

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

+
+
+
+ + diff --git a/hr_attendance_missing_days/tests/__init__.py b/hr_attendance_missing_days/tests/__init__.py new file mode 100644 index 00000000..59d8989a --- /dev/null +++ b/hr_attendance_missing_days/tests/__init__.py @@ -0,0 +1,4 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_attendance diff --git a/hr_attendance_missing_days/tests/test_attendance.py b/hr_attendance_missing_days/tests/test_attendance.py new file mode 100644 index 00000000..a2383159 --- /dev/null +++ b/hr_attendance_missing_days/tests/test_attendance.py @@ -0,0 +1,136 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import date, datetime, timedelta + +import pytz + +from odoo.tests import TransactionCase + + +def convert_tz(dt, *, from_tz=None, to_tz=None): + return ( + pytz.timezone(from_tz or "UTC") + .localize(dt) + .astimezone(pytz.timezone(to_tz or "UTC")) + .replace(tzinfo=None) + ) + + +class TestAttendance(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + cls.env.company.overtime_start_date = datetime(2020, 1, 1) + + cls.employee = cls.env["hr.employee"].create( + { + "name": "Test Employee", + "user_id": cls.env.user.id, + "company_id": cls.env.company.id, + "tz": "UTC", + } + ) + cls.reason = cls.env["hr.attendance.reason"].create( + { + "name": "Missing Attendance", + "code": "MA", + } + ) + + def test_attendance_creation_no_reason(self): + self.env.company.attendance_missing_days_reason = False + attendances_before = self.employee.attendance_ids + self.employee._create_missing_attendances() + attendances_after = self.employee.attendance_ids + + self.assertEqual(attendances_before, attendances_after) + + def test_attendance_creation_with_reason(self): + self.env.company.attendance_missing_days_reason = self.reason + attendances_before = self.employee.attendance_ids + self.employee._create_missing_attendances() + attendances_after = self.employee.attendance_ids + + attendances_new = attendances_after - attendances_before + self.assertTrue(attendances_new) + self.assertFalse(any(attendances_new.mapped("worked_hours"))) + + def test_attendance_creation(self): + self.env.company.attendance_missing_days_reason = self.reason + + attended = {date(2023, 7, 3 + offset) for offset in range(4)} + for tz in ["Europe/Amsterdam", "Pacific/Auckland", "America/New_York"]: + employee = self._clone_employee( + self.employee, {"tz": tz, "name": f"Employee {tz}"} + ) + for offset, times in enumerate(((0, 30), (23, 30), (11, 30), (12, 30))): + # Convert the times from the employee TZ zo UTC. 3rd is monday + start = convert_tz( + datetime(2023, 7, 3 + offset, *times), + from_tz=tz, + to_tz="UTC", + ) + + # Generate a 30min attendance blocking the date + self.env["hr.attendance"].create( + { + "employee_id": employee.id, + "check_in": start, + "check_out": start + timedelta(minutes=30), + } + ) + + # Cover a huge time span + employee._create_missing_attendances(date(2023, 6, 1), date(2023, 8, 1)) + + domain = [ + ("employee_id", "=", employee.id), + ("attendance_reason_ids", "=", self.reason.id), + ] + attendances = self.env["hr.attendance"].search(domain) + self.assertTrue(attendances) + for attendance in attendances: + checkin = convert_tz(attendance.check_in, to_tz=tz) + self.assertNotIn(checkin.date(), attended) + + def test_attendance_creation_during_day(self): + self.env.company.attendance_missing_days_reason = self.reason + + now = datetime.now() + self.env["hr.attendance"].create( + { + "employee_id": self.employee.id, + "check_in": now - timedelta(minutes=30), + "check_out": now + timedelta(minutes=30), + } + ) + + attendances_before = self.employee.attendance_ids + self.employee._create_missing_attendances(now, now) + attendances_after = self.employee.attendance_ids + + attendances_new = attendances_after - attendances_before + self.assertFalse(attendances_new) + + def test_multi_day_attendance(self): + """Test that having an attendance crossing a day border doesn't break""" + self.employee.tz = "Europe/Amsterdam" + attendance = self.env["hr.attendance"].create( + { + "employee_id": self.employee.id, + "check_in": "2023-12-18 21:00:00", + "check_out": "2023-12-19 13:00:00", + } + ) + self.employee._create_missing_attendances( + date(2023, 12, 18), date(2023, 12, 19) + ) + self.assertEqual( + self.env["hr.attendance"].search([("employee_id", "=", self.employee.id)]), + attendance, + ) + + def _clone_employee(self, employee, defaults): + return employee.copy(defaults) diff --git a/hr_attendance_missing_days/views/res_config_settings_views.xml b/hr_attendance_missing_days/views/res_config_settings_views.xml new file mode 100644 index 00000000..bd5d28a7 --- /dev/null +++ b/hr_attendance_missing_days/views/res_config_settings_views.xml @@ -0,0 +1,44 @@ + + + + res.config.settings + + + +

Missing Attendance

+
+
+
+
+
+
+
+ + + + diff --git a/setup/hr_attendance_contract_missing_days/odoo/addons/hr_attendance_contract_missing_days b/setup/hr_attendance_contract_missing_days/odoo/addons/hr_attendance_contract_missing_days new file mode 120000 index 00000000..73d95a96 --- /dev/null +++ b/setup/hr_attendance_contract_missing_days/odoo/addons/hr_attendance_contract_missing_days @@ -0,0 +1 @@ +../../../../hr_attendance_contract_missing_days \ No newline at end of file diff --git a/setup/hr_attendance_contract_missing_days/setup.py b/setup/hr_attendance_contract_missing_days/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/hr_attendance_contract_missing_days/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/setup/hr_attendance_missing_days/odoo/addons/hr_attendance_missing_days b/setup/hr_attendance_missing_days/odoo/addons/hr_attendance_missing_days new file mode 120000 index 00000000..e1d52213 --- /dev/null +++ b/setup/hr_attendance_missing_days/odoo/addons/hr_attendance_missing_days @@ -0,0 +1 @@ +../../../../hr_attendance_missing_days \ No newline at end of file diff --git a/setup/hr_attendance_missing_days/setup.py b/setup/hr_attendance_missing_days/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/hr_attendance_missing_days/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)