From 9c276252ee909cd8da679e6cf652b40271043d01 Mon Sep 17 00:00:00 2001 From: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com> Date: Wed, 14 May 2025 15:46:06 +0200 Subject: [PATCH 1/8] fix(Employee): move Expected Daily Working Time to child table (DRC-148) --- time_capture/custom_fields.py | 8 ++-- time_capture/patches.txt | 3 +- ...ve_expected_working_time_to_child_table.py | 22 +++++++++ .../__init__.py | 0 .../employee_expected_working_hours.json | 45 +++++++++++++++++++ .../employee_expected_working_hours.py | 9 ++++ 6 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 time_capture/patches/move_expected_working_time_to_child_table.py create mode 100644 time_capture/time_capture/doctype/employee_expected_working_hours/__init__.py create mode 100644 time_capture/time_capture/doctype/employee_expected_working_hours/employee_expected_working_hours.json create mode 100644 time_capture/time_capture/doctype/employee_expected_working_hours/employee_expected_working_hours.py diff --git a/time_capture/custom_fields.py b/time_capture/custom_fields.py index fc5c4c5..3ab59a3 100644 --- a/time_capture/custom_fields.py +++ b/time_capture/custom_fields.py @@ -32,12 +32,12 @@ def get_custom_fields(): ], "Employee": [ { - "fieldname": "expected_daily_working_hours", - "fieldtype": "Float", + "fieldname": "expected_working_hours", + "fieldtype": "Table", "insert_after": "attendance_device_id", - "label": _("Expected Daily Working Hours"), - "translatable": 0, + "label": _("Expected Working Hours"), "reqd": 1, + "options": "Employee Expected Working Hours", }, ], "Task": [ diff --git a/time_capture/patches.txt b/time_capture/patches.txt index 30a5c90..26a86c2 100644 --- a/time_capture/patches.txt +++ b/time_capture/patches.txt @@ -3,4 +3,5 @@ # Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations [post_model_sync] -execute:from time_capture.install import after_install;after_install() # 2025-03-19 #2 \ No newline at end of file +execute:from time_capture.install import after_install;after_install() # 2025-05-14 #15:26 +time_capture.patches.move_expected_working_time_to_child_table diff --git a/time_capture/patches/move_expected_working_time_to_child_table.py b/time_capture/patches/move_expected_working_time_to_child_table.py new file mode 100644 index 0000000..ce7bb5a --- /dev/null +++ b/time_capture/patches/move_expected_working_time_to_child_table.py @@ -0,0 +1,22 @@ +import frappe + + +def execute(): + if not frappe.db.exists( + "Custom Field", {"dt": "Employee", "fieldname": "expected_daily_working_hours", "fieldtype": "Float"} + ): + return + for e in frappe.db.get_all("Employee", filters={"expected_daily_working_hours": ["is", "set"]}): + print(f"Updating {e.name}") + employee = frappe.get_doc("Employee", e.name) + employee.append( + "expected_working_hours", + { + "valid_from": employee.date_of_joining, + "expected_daily_working_hours": employee.expected_daily_working_hours, + }, + ) + employee.save() + frappe.db.delete( + "Custom Field", {"dt": "Employee", "fieldname": "expected_daily_working_hours", "fieldtype": "Float"} + ) diff --git a/time_capture/time_capture/doctype/employee_expected_working_hours/__init__.py b/time_capture/time_capture/doctype/employee_expected_working_hours/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/time_capture/time_capture/doctype/employee_expected_working_hours/employee_expected_working_hours.json b/time_capture/time_capture/doctype/employee_expected_working_hours/employee_expected_working_hours.json new file mode 100644 index 0000000..4b67278 --- /dev/null +++ b/time_capture/time_capture/doctype/employee_expected_working_hours/employee_expected_working_hours.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-05-14 14:56:54.878019", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "valid_from", + "expected_daily_working_hours" + ], + "fields": [ + { + "columns": 4, + "fieldname": "valid_from", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Valid From", + "reqd": 1 + }, + { + "columns": 6, + "fieldname": "expected_daily_working_hours", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Expected Daily Working Hours", + "non_negative": 1, + "reqd": 1 + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-05-14 15:44:30.974078", + "modified_by": "Administrator", + "module": "Time Capture", + "name": "Employee Expected Working Hours", + "owner": "Administrator", + "permissions": [], + "row_format": "Dynamic", + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/time_capture/time_capture/doctype/employee_expected_working_hours/employee_expected_working_hours.py b/time_capture/time_capture/doctype/employee_expected_working_hours/employee_expected_working_hours.py new file mode 100644 index 0000000..5753a41 --- /dev/null +++ b/time_capture/time_capture/doctype/employee_expected_working_hours/employee_expected_working_hours.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, ALYF GmbH and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class EmployeeExpectedWorkingHours(Document): + pass From 04434e07e992e9968e030e28b85b6a4b06aaffaa Mon Sep 17 00:00:00 2001 From: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com> Date: Wed, 14 May 2025 16:45:38 +0200 Subject: [PATCH 2/8] feat: validate expected_working_hours (Date Of Joining shall be min valid_from) --- time_capture/hooks.py | 3 +++ time_capture/scripts/employee.py | 11 +++++++++++ 2 files changed, 14 insertions(+) create mode 100644 time_capture/scripts/employee.py diff --git a/time_capture/hooks.py b/time_capture/hooks.py index e0849be..f425a44 100644 --- a/time_capture/hooks.py +++ b/time_capture/hooks.py @@ -143,6 +143,9 @@ "on_submit": "time_capture.scripts.attendance.on_submit", "on_cancel": "time_capture.scripts.attendance.on_cancel", }, + "Employee": { + "before_validate": "time_capture.scripts.employee.before_validate", + }, } # Scheduled Tasks diff --git a/time_capture/scripts/employee.py b/time_capture/scripts/employee.py new file mode 100644 index 0000000..54bac5e --- /dev/null +++ b/time_capture/scripts/employee.py @@ -0,0 +1,11 @@ +import frappe +from frappe import _ + + +def before_validate(doc, method): + validate_expected_working_hours(doc) + + +def validate_expected_working_hours(doc): + if not doc.date_of_joining == min(ewh.valid_from for ewh in doc.expected_working_hours): + frappe.throw(_("Date of Joining is not the same as the earliest date of Expected Working Hours.")) From 1ec0fc9efb3966d0ce5cbfe7d476ddcb6cde2d9f Mon Sep 17 00:00:00 2001 From: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com> Date: Wed, 14 May 2025 16:46:15 +0200 Subject: [PATCH 3/8] feat: add utility function get_expected_working_hours --- time_capture/scripts/employee.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/time_capture/scripts/employee.py b/time_capture/scripts/employee.py index 54bac5e..5b3fc62 100644 --- a/time_capture/scripts/employee.py +++ b/time_capture/scripts/employee.py @@ -9,3 +9,15 @@ def before_validate(doc, method): def validate_expected_working_hours(doc): if not doc.date_of_joining == min(ewh.valid_from for ewh in doc.expected_working_hours): frappe.throw(_("Date of Joining is not the same as the earliest date of Expected Working Hours.")) + + +def get_expected_working_hours(employee_id, date): + """ + Get the expected working hours for an employee on a specific date. + """ + return frappe.db.get_value( + "Employee Expected Working Hours", + filters={"parent": employee_id, "valid_from": ["<=", date]}, + fieldname="expected_daily_working_hours", + order_by="valid_from desc", + ) From 4cd564090b7caa12a89a3b5e42995918bf0a1a10 Mon Sep 17 00:00:00 2001 From: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com> Date: Tue, 1 Jul 2025 14:16:37 +0200 Subject: [PATCH 4/8] fix: use getdate for valid_from --- time_capture/scripts/employee.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/time_capture/scripts/employee.py b/time_capture/scripts/employee.py index 5b3fc62..a727e49 100644 --- a/time_capture/scripts/employee.py +++ b/time_capture/scripts/employee.py @@ -1,5 +1,6 @@ import frappe from frappe import _ +from frappe.utils import getdate def before_validate(doc, method): @@ -7,7 +8,7 @@ def before_validate(doc, method): def validate_expected_working_hours(doc): - if not doc.date_of_joining == min(ewh.valid_from for ewh in doc.expected_working_hours): + if not doc.date_of_joining == min(getdate(ewh.valid_from) for ewh in doc.expected_working_hours): frappe.throw(_("Date of Joining is not the same as the earliest date of Expected Working Hours.")) From 3cc2ad2bcb2ca2a4e1450a36eeec98aaa565aea7 Mon Sep 17 00:00:00 2001 From: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com> Date: Tue, 1 Jul 2025 14:17:14 +0200 Subject: [PATCH 5/8] chore: favor tuples over lists --- time_capture/scripts/employee.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/time_capture/scripts/employee.py b/time_capture/scripts/employee.py index a727e49..c16803d 100644 --- a/time_capture/scripts/employee.py +++ b/time_capture/scripts/employee.py @@ -18,7 +18,7 @@ def get_expected_working_hours(employee_id, date): """ return frappe.db.get_value( "Employee Expected Working Hours", - filters={"parent": employee_id, "valid_from": ["<=", date]}, + filters={"parent": employee_id, "valid_from": ("<=", date)}, fieldname="expected_daily_working_hours", order_by="valid_from desc", ) From 871618a24f5dbb57f298a5b6db2109fffc66f378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20Ei=C3=9Fler?= <77415730+PatrickDEissler@users.noreply.github.com> Date: Tue, 1 Jul 2025 14:22:29 +0200 Subject: [PATCH 6/8] refactor: general refactoring in patch Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- .../move_expected_working_time_to_child_table.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/time_capture/patches/move_expected_working_time_to_child_table.py b/time_capture/patches/move_expected_working_time_to_child_table.py index ce7bb5a..d728cc2 100644 --- a/time_capture/patches/move_expected_working_time_to_child_table.py +++ b/time_capture/patches/move_expected_working_time_to_child_table.py @@ -6,9 +6,14 @@ def execute(): "Custom Field", {"dt": "Employee", "fieldname": "expected_daily_working_hours", "fieldtype": "Float"} ): return - for e in frappe.db.get_all("Employee", filters={"expected_daily_working_hours": ["is", "set"]}): - print(f"Updating {e.name}") - employee = frappe.get_doc("Employee", e.name) + + for employee_id in frappe.get_all("Employee", filters={"expected_daily_working_hours": ("is", "set")}, pluck="name"):: + print(f"Updating {employee_id}") + + employee = frappe.get_doc("Employee", employee_id) + if employee.expected_working_hours or not employee.expected_daily_working_hours: + continue + employee.append( "expected_working_hours", { From 6383109b99c32f1d05b072bd73d1fcad947d0865 Mon Sep 17 00:00:00 2001 From: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com> Date: Tue, 1 Jul 2025 14:24:17 +0200 Subject: [PATCH 7/8] fix: remove typos and further refactor --- ...move_expected_working_time_to_child_table.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/time_capture/patches/move_expected_working_time_to_child_table.py b/time_capture/patches/move_expected_working_time_to_child_table.py index d728cc2..d80509b 100644 --- a/time_capture/patches/move_expected_working_time_to_child_table.py +++ b/time_capture/patches/move_expected_working_time_to_child_table.py @@ -2,12 +2,17 @@ def execute(): - if not frappe.db.exists( - "Custom Field", {"dt": "Employee", "fieldname": "expected_daily_working_hours", "fieldtype": "Float"} - ): + custom_field_filters = { + "dt": "Employee", + "fieldname": "expected_daily_working_hours", + "fieldtype": "Float", + } + if not frappe.db.exists("Custom Field", custom_field_filters): return - for employee_id in frappe.get_all("Employee", filters={"expected_daily_working_hours": ("is", "set")}, pluck="name"):: + for employee_id in frappe.get_all( + "Employee", filters={"expected_daily_working_hours": ("is", "set")}, pluck="name" + ): print(f"Updating {employee_id}") employee = frappe.get_doc("Employee", employee_id) @@ -22,6 +27,4 @@ def execute(): }, ) employee.save() - frappe.db.delete( - "Custom Field", {"dt": "Employee", "fieldname": "expected_daily_working_hours", "fieldtype": "Float"} - ) + frappe.db.delete("Custom Field", custom_field_filters) From a6494272faddfe2075dcf4d0e3f4958ad4837fa3 Mon Sep 17 00:00:00 2001 From: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com> Date: Tue, 1 Jul 2025 14:27:46 +0200 Subject: [PATCH 8/8] chore: run patches for Custom Fields and Property Setters independently --- time_capture/patches.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/time_capture/patches.txt b/time_capture/patches.txt index 26a86c2..6072d3f 100644 --- a/time_capture/patches.txt +++ b/time_capture/patches.txt @@ -3,5 +3,6 @@ # Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations [post_model_sync] -execute:from time_capture.install import after_install;after_install() # 2025-05-14 #15:26 +execute:from time_capture.install import make_custom_fields;make_custom_fields() # 2025-07-01 #14:26 +execute:from time_capture.install import make_property_setters;make_property_setters() # 2025-07-01 #14:26 time_capture.patches.move_expected_working_time_to_child_table