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..ff304a4e --- /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.datetime.now() - 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..893ce2a7 --- /dev/null +++ b/hr_attendance_missing_days/models/hr_employee.py @@ -0,0 +1,72 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from datetime import datetime, time + +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 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 = datetime.combine( + self.env.company.sudo().overtime_start_date, time.min + ) + + if not date_to: + date_to = datetime.now() + + date_from, date_to = map(ensure_tz, (date_from, date_to)) + + intervals = self.resource_calendar_id._work_intervals_batch(date_from, date_to) + work_dates = {} + for start, _stop, _attendance in sorted(intervals[False]): + 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", ">=", date_from.replace(tzinfo=None)), + ("check_in", "<=", date_to.replace(tzinfo=None)), + ] + tz = pytz.timezone(self.tz) + attendances = { + ensure_tz(attendance.check_in, tz).date() + for attendance in self.attendance_ids.filtered_domain(domain) + } + + vals = [] + for missing in set(work_dates) - attendances: + vals.append( + { + "employee_id": self.id, + "check_in": work_dates[missing], + "check_out": work_dates[missing], + "attendance_reason_ids": [(4, reason.id)], + } + ) + + 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..e81e6e7e --- /dev/null +++ b/hr_attendance_missing_days/tests/test_attendance.py @@ -0,0 +1,43 @@ +# © 2023 initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests import TransactionCase + + +class TestAttendance(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + 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"))) 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..dc7253c7 --- /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_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, +)