From 10b3911673061555830efc009ebeaacec2602c0a Mon Sep 17 00:00:00 2001 From: AaronHForgeFlow Date: Fri, 20 Oct 2023 13:28:36 +0200 Subject: [PATCH] [ADD] hr_period_create_timesheet in 15.0 [IMP] hr_period_create_timesheet: add a cron to automatically create the timesheets for future periods --- hr_period_create_timesheet/README.rst | 92 ++++ hr_period_create_timesheet/__init__.py | 4 + hr_period_create_timesheet/__manifest__.py | 20 + hr_period_create_timesheet/data/ir_cron.xml | 15 + .../readme/CONTRIBUTORS.rst | 3 + .../readme/DESCRIPTION.rst | 3 + hr_period_create_timesheet/readme/USAGE.rst | 8 + .../security/ir.model.access.csv | 2 + .../static/description/index.html | 436 ++++++++++++++++++ hr_period_create_timesheet/tests/__init__.py | 4 + .../tests/test_hr_period_create_timesheet.py | 169 +++++++ .../wizards/__init__.py | 4 + .../wizards/hr_period_create_timesheet.py | 116 +++++ .../hr_period_create_timesheet_view.xml | 53 +++ .../odoo/addons/hr_period_create_timesheet | 1 + setup/hr_period_create_timesheet/setup.py | 6 + 16 files changed, 936 insertions(+) create mode 100644 hr_period_create_timesheet/README.rst create mode 100644 hr_period_create_timesheet/__init__.py create mode 100644 hr_period_create_timesheet/__manifest__.py create mode 100644 hr_period_create_timesheet/data/ir_cron.xml create mode 100644 hr_period_create_timesheet/readme/CONTRIBUTORS.rst create mode 100644 hr_period_create_timesheet/readme/DESCRIPTION.rst create mode 100644 hr_period_create_timesheet/readme/USAGE.rst create mode 100644 hr_period_create_timesheet/security/ir.model.access.csv create mode 100644 hr_period_create_timesheet/static/description/index.html create mode 100644 hr_period_create_timesheet/tests/__init__.py create mode 100644 hr_period_create_timesheet/tests/test_hr_period_create_timesheet.py create mode 100644 hr_period_create_timesheet/wizards/__init__.py create mode 100644 hr_period_create_timesheet/wizards/hr_period_create_timesheet.py create mode 100644 hr_period_create_timesheet/wizards/hr_period_create_timesheet_view.xml create mode 120000 setup/hr_period_create_timesheet/odoo/addons/hr_period_create_timesheet create mode 100644 setup/hr_period_create_timesheet/setup.py diff --git a/hr_period_create_timesheet/README.rst b/hr_period_create_timesheet/README.rst new file mode 100644 index 0000000000..c63a676501 --- /dev/null +++ b/hr_period_create_timesheet/README.rst @@ -0,0 +1,92 @@ +========================== +HR period create timesheet +========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:81e2a29a70c3c038721b45c78c57dfdfcf651eebd84c26efe2d52c5ecf5527c7 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Ftimesheet-lightgray.png?logo=github + :target: https://github.com/OCA/timesheet/tree/15.0/hr_period_create_timesheet + :alt: OCA/timesheet +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/timesheet-15-0/timesheet-15-0-hr_period_create_timesheet + :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/timesheet&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + + +This module prepare the timesheets for all the periods created for all +employees. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + + +To use this module, you need to: + +* Go to Payroll > Payroll Periods and select the periods you want to create the + timesheets for. +* Go to Action > Generate Timesheets +* Add the employees you are generating timesheets for +* Click on Generate + +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 +~~~~~~~ + +* Eficent + +Contributors +~~~~~~~~~~~~ + +* Serpent Consulting Services Pvt. Ltd. +* Aaron Henriquez +* Jasmin Solanki + +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/timesheet `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_period_create_timesheet/__init__.py b/hr_period_create_timesheet/__init__.py new file mode 100644 index 0000000000..aebfda8086 --- /dev/null +++ b/hr_period_create_timesheet/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2023 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import wizards diff --git a/hr_period_create_timesheet/__manifest__.py b/hr_period_create_timesheet/__manifest__.py new file mode 100644 index 0000000000..36c9897700 --- /dev/null +++ b/hr_period_create_timesheet/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2023 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "HR period create timesheet", + "version": "15.0.1.0.0", + "author": "Eficent, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/timesheet", + "category": "Human Resources", + "depends": ["hr_timesheet_sheet_period"], + "summary": "This module prepare the timesheets for all the periods selected" + " for all employees", + "license": "AGPL-3", + "data": [ + "security/ir.model.access.csv", + "data/ir_cron.xml", + "wizards/hr_period_create_timesheet_view.xml", + ], + "installable": True, +} diff --git a/hr_period_create_timesheet/data/ir_cron.xml b/hr_period_create_timesheet/data/ir_cron.xml new file mode 100644 index 0000000000..bf938460f2 --- /dev/null +++ b/hr_period_create_timesheet/data/ir_cron.xml @@ -0,0 +1,15 @@ + + + + Create Timesheets for Future Periods + 1 + weeks + -1 + + + model.create_timesheets_on_future_periods() + code + + + + diff --git a/hr_period_create_timesheet/readme/CONTRIBUTORS.rst b/hr_period_create_timesheet/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..5afa26dc81 --- /dev/null +++ b/hr_period_create_timesheet/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Serpent Consulting Services Pvt. Ltd. +* Aaron Henriquez +* Jasmin Solanki diff --git a/hr_period_create_timesheet/readme/DESCRIPTION.rst b/hr_period_create_timesheet/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..5ae1927833 --- /dev/null +++ b/hr_period_create_timesheet/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ + +This module prepare the timesheets for all the periods created for all +employees. diff --git a/hr_period_create_timesheet/readme/USAGE.rst b/hr_period_create_timesheet/readme/USAGE.rst new file mode 100644 index 0000000000..0f5338b832 --- /dev/null +++ b/hr_period_create_timesheet/readme/USAGE.rst @@ -0,0 +1,8 @@ + +To use this module, you need to: + +* Go to Payroll > Payroll Periods and select the periods you want to create the + timesheets for. +* Go to Action > Generate Timesheets +* Add the employees you are generating timesheets for +* Click on Generate diff --git a/hr_period_create_timesheet/security/ir.model.access.csv b/hr_period_create_timesheet/security/ir.model.access.csv new file mode 100644 index 0000000000..17a2a93cd9 --- /dev/null +++ b/hr_period_create_timesheet/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +hr_period_create_timesheet.access_hr_period_create_timesheet,access_hr_period_create_timesheet,hr_period_create_timesheet.model_hr_period_create_timesheet,base.group_user,1,1,1,1 diff --git a/hr_period_create_timesheet/static/description/index.html b/hr_period_create_timesheet/static/description/index.html new file mode 100644 index 0000000000..07589a1361 --- /dev/null +++ b/hr_period_create_timesheet/static/description/index.html @@ -0,0 +1,436 @@ + + + + + + +HR period create timesheet + + + +
+

HR period create timesheet

+ + +

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

+

This module prepare the timesheets for all the periods created for all +employees.

+

Table of contents

+ +
+

Usage

+

To use this module, you need to:

+
    +
  • Go to Payroll > Payroll Periods and select the periods you want to create the +timesheets for.
  • +
  • Go to Action > Generate Timesheets
  • +
  • Add the employees you are generating timesheets for
  • +
  • Click on Generate
  • +
+
+
+

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

+
    +
  • Eficent
  • +
+
+
+

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

+

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

+
+
+
+ + diff --git a/hr_period_create_timesheet/tests/__init__.py b/hr_period_create_timesheet/tests/__init__.py new file mode 100644 index 0000000000..d288990c1f --- /dev/null +++ b/hr_period_create_timesheet/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2023 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import test_hr_period_create_timesheet diff --git a/hr_period_create_timesheet/tests/test_hr_period_create_timesheet.py b/hr_period_create_timesheet/tests/test_hr_period_create_timesheet.py new file mode 100644 index 0000000000..001c60b8e2 --- /dev/null +++ b/hr_period_create_timesheet/tests/test_hr_period_create_timesheet.py @@ -0,0 +1,169 @@ +# Copyright 2023 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +import calendar +import time +from datetime import datetime + +from odoo.tests import common +from odoo.tools.safe_eval import safe_eval + + +class TestHRPeriodCreateTimesheet(common.TransactionCase): + def setUp(self): + super(TestHRPeriodCreateTimesheet, self).setUp() + self.user_model = self.env["res.users"] + self.company_model = self.env["res.company"] + self.payslip_model = self.env["hr.payslip"] + self.run_model = self.env["hr.payslip.run"] + self.fy_model = self.env["hr.fiscalyear"] + self.period_model = self.env["hr.period"] + self.data_range_type_model = self.env["date.range.type"] + self.timesheet_sheet = self.env["hr_timesheet.sheet"] + self.hr_employee = self.env["hr.employee"] + self.create_timesheet = self.env["hr.period.create.timesheet"] + self.project_2 = self.env.ref("project.project_project_2") + self.root = self.env.ref("hr.employee_admin") + self.dept = self.env.ref("hr.employee_vad") + self.dept_1 = self.env.ref("hr.dep_rd") + self.dept.write({"parent_id": self.root.id}) + self.company_id = self.env.user.company_id + self.type = self.create_data_range_type("test_hr_period") + + self.vals = { + "company_id": self.company_id.id, + "date_start": time.strftime("%Y-01-01"), + "date_end": time.strftime("%Y-12-31"), + "schedule_pay": "monthly", + "type_id": self.type.id, + "payment_day": "2", + "payment_weekday": "0", + "payment_week": "1", + "name": "Test", + } + # create user + self.user_test = self.user_model.create( + { + "name": "User 1", + "login": "tua@example.com", + "password": "base-test-passwd", + } + ) + # create Employee + self.employee = self.hr_employee.create( + { + "name": "Employee 1", + "user_id": self.user_test.id, + "address_id": self.user_test.partner_id.id, + "parent_id": self.root.id, + "company_id": self.company_id.id, + } + ) + + def create_data_range_type(self, name): + # create Data Range Type + return self.data_range_type_model.create({"name": name, "active": True}) + + def create_fiscal_year(self, vals=None): + if vals is None: + vals = {} + + self.vals.update(vals) + # create Fiscal Year + return self.fy_model.create(self.vals) + + def get_periods(self, fiscal_year): + return fiscal_year.period_ids.sorted(key=lambda p: p.date_start) + + def check_period(self, period, date_start, date_end, date_payment): + if date_start: + self.assertEqual(period.date_start.strftime("%Y-%m-%d"), date_start) + if date_end: + self.assertEqual(period.date_end.strftime("%Y-%m-%d"), date_end) + if date_payment: + self.assertEqual(period.date_payment.strftime("%Y-%m-%d"), date_payment) + + def test_create_periods_monthly(self): + fy = self.create_fiscal_year() + fy.create_periods() + periods = self.get_periods(fy) + self.assertEqual(len(periods), 12) + + self.check_period( + periods[0], + time.strftime("%Y-01-01"), + time.strftime("%Y-01-31"), + time.strftime("%Y-02-02"), + ) + current_year = datetime.now().year + if calendar.isleap(current_year): + self.check_period( + periods[1], + time.strftime("%Y-02-01"), + time.strftime("%Y-02-29"), + time.strftime("%Y-03-02"), + ) + else: + self.check_period( + periods[1], + time.strftime("%Y-02-01"), + time.strftime("%Y-02-28"), + time.strftime("%Y-03-02"), + ) + self.check_period( + periods[2], + time.strftime("%Y-03-01"), + time.strftime("%Y-03-31"), + time.strftime("%Y-04-02"), + ) + # the payment is in next year + self.check_period( + periods[11], + time.strftime("%Y-12-01"), + time.strftime("%Y-12-31"), + "2025-01-02", + ) + + period_id = self.period_model.search( + [ + ("date_start", "<=", time.strftime("%Y-%m-11")), + ("date_end", ">=", time.strftime("%Y-%m-11")), + ] + ) + # create HR Period Timesheet + wizard = self.create_timesheet.with_context( + active_ids=[period_id.id], active_model="hr.period", active_id=period_id.id + ).create({}) + wizard.employee_ids = self.employee + wizard_timesheet = wizard.compute() + self.assertIn(self.employee.id, wizard.employee_ids.ids) + + timesheet = self.timesheet_sheet.search( + [ + ("date_start", "=", period_id.date_start), + ("date_end", "=", period_id.date_end), + ("company_id", "=", period_id.company_id.id), + ] + ) + self.assertEqual(timesheet.employee_id, wizard.employee_ids) + self.assertEqual(timesheet.id, safe_eval(wizard_timesheet["domain"])[0][2][0]) + + def test_create_periods_future(self): + fy = self.create_fiscal_year() + fy.create_periods() + periods = self.get_periods(fy) + datetime.now().year + datetime.now().month + self.env["hr.period.create.timesheet"].create_timesheets_on_future_periods() + timesheets = self.timesheet_sheet.search( + [ + ("employee_id", "=", self.employee.id), + ("date_end", ">", datetime.now()), + ("company_id", "=", periods[0].company_id.id), + ] + ) + periods = self.get_periods(fy) + last_period = periods[11] + periods_left = last_period.date_end.month - datetime.now().month + 1 + # timesheets for the rest of the year + self.assertEqual(len(timesheets), periods_left) diff --git a/hr_period_create_timesheet/wizards/__init__.py b/hr_period_create_timesheet/wizards/__init__.py new file mode 100644 index 0000000000..27a212ef29 --- /dev/null +++ b/hr_period_create_timesheet/wizards/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2023 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import hr_period_create_timesheet diff --git a/hr_period_create_timesheet/wizards/hr_period_create_timesheet.py b/hr_period_create_timesheet/wizards/hr_period_create_timesheet.py new file mode 100644 index 0000000000..6e9fdd7f48 --- /dev/null +++ b/hr_period_create_timesheet/wizards/hr_period_create_timesheet.py @@ -0,0 +1,116 @@ +# Copyright 2023 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class HrPeriodCreateTimesheet(models.TransientModel): + _name = "hr.period.create.timesheet" + _description = "Hr Period Create Timesheet" + + employee_ids = fields.Many2many( + comodel_name="hr.employee", + relation="hr_employee_hr_period_create_timesheet_rel", + column1="wiz_id", + column2="employee_id", + string="Employees", + ) + + @api.model + def default_get(self, fields): + res = super(HrPeriodCreateTimesheet, self).default_get(fields) + period_obj = self.env["hr.period"] + period_ids = self.env.context.get("active_ids", []) + active_model = self.env.context.get("active_model") + + if not period_ids: + return res + assert active_model == "hr.period", "Bad context propagation" + + company_id = False + for period in period_obj.browse(period_ids): + if company_id and company_id != period.company_id.id: + raise ValidationError(_("All periods must belong to the same company.")) + company_id = period.company_id.id + res["employee_ids"] = [] + return res + + @api.model + def _prepare_timesheet(self, employee, hr_period): + return { + "employee_id": employee.id, + "date_start": hr_period.date_start, + "date_end": hr_period.date_end, + "company_id": hr_period.company_id.id, + "department_id": employee.department_id.id, + "hr_period_id": hr_period.id, + } + + def compute(self): + res = [] + hr_period_obj = self.env["hr.period"] + timesheet_obj = self.env["hr_timesheet.sheet"] + for wiz_data in self: + hr_period_ids = self.env.context.get("active_ids", []) + periods = hr_period_obj.browse(hr_period_ids) + for employee in wiz_data.employee_ids: + for hr_period in periods: + timesheet_ids = timesheet_obj.search( + [ + ("employee_id", "=", employee.id), + ("date_start", "<=", hr_period.date_end), + ("date_end", ">=", hr_period.date_start), + ] + ) + if timesheet_ids: + raise ValidationError( + _( + "Employee %(emp_name)s already has a Timesheet within the " + "date range of HR Period %(period_name)s." + ) + % ( + { + "emp_name": employee.name, + "period_name": hr_period.name, + } + ) + ) + ts_data = self._prepare_timesheet(employee, hr_period) + ts_id = timesheet_obj.create(ts_data) + res.append(ts_id.id) + + return { + "domain": "[('id','in', [" + ",".join(map(str, res)) + "])]", + "name": _("Employee Timesheets"), + "view_mode": "tree,form", + "res_model": "hr_timesheet.sheet", + "view_id": False, + "context": False, + "type": "ir.actions.act_window", + } + + def create_timesheets_on_future_periods(self): + timesheet_obj = self.env["hr_timesheet.sheet"] + today = fields.Date.today() + periods = self.env["hr.period"].search([("date_end", ">=", today)]) + employees = self.env["hr.employee"].search([("user_id", "!=", False)]) + if not periods or not employees: + return + # Create a dictionary to store existing timesheets per employee + existing_timesheets = {} + timesheet_domain = [ + ("employee_id", "in", employees.ids), + ("date_start", "<=", max(periods.mapped("date_end"))), + ("date_end", ">=", min(periods.mapped("date_start"))), + ] + for timesheet in timesheet_obj.search(timesheet_domain): + key = (timesheet.employee_id.id, timesheet.date_start, timesheet.date_end) + existing_timesheets[key] = timesheet + + for hr_period in periods: + for employee in employees: + key = (employee.id, hr_period.date_start, hr_period.date_end) + if key not in existing_timesheets: + ts_data = self._prepare_timesheet(employee, hr_period) + timesheet_obj.create(ts_data) diff --git a/hr_period_create_timesheet/wizards/hr_period_create_timesheet_view.xml b/hr_period_create_timesheet/wizards/hr_period_create_timesheet_view.xml new file mode 100644 index 0000000000..5a531037c0 --- /dev/null +++ b/hr_period_create_timesheet/wizards/hr_period_create_timesheet_view.xml @@ -0,0 +1,53 @@ + + + + + hr.period.create.timesheet.form + hr.period.create.timesheet + +
+
+
+ + + + + + + + + + + + +
+
+
+
+
+
+ + + Generate Timesheets + ir.actions.act_window + hr.period.create.timesheet + form + + new + + + + +
diff --git a/setup/hr_period_create_timesheet/odoo/addons/hr_period_create_timesheet b/setup/hr_period_create_timesheet/odoo/addons/hr_period_create_timesheet new file mode 120000 index 0000000000..63a05823fe --- /dev/null +++ b/setup/hr_period_create_timesheet/odoo/addons/hr_period_create_timesheet @@ -0,0 +1 @@ +../../../../hr_period_create_timesheet \ No newline at end of file diff --git a/setup/hr_period_create_timesheet/setup.py b/setup/hr_period_create_timesheet/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/hr_period_create_timesheet/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)