From fd53c64d5d6aa77e6dbd88c8c65732b2effd9486 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 29 Mar 2019 20:50:06 +0530 Subject: [PATCH 001/117] feat: create carried forward leave expiry option --- erpnext/hr/doctype/leave_type/leave_type.json | 85 +++++++++++++++++-- erpnext/hr/doctype/leave_type/leave_type.py | 9 +- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 6a7a80a784..06f18c8a5e 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, @@ -14,10 +15,12 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "leave_type_name", "fieldtype": "Data", "hidden": 0, @@ -42,15 +45,17 @@ "search_index": 0, "set_only_once": 0, "translatable": 0, - "unique": 0 + "unique": 1 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, "depends_on": "", + "fetch_if_empty": 0, "fieldname": "max_leaves_allowed", "fieldtype": "Int", "hidden": 0, @@ -78,10 +83,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "applicable_after", "fieldtype": "Int", "hidden": 0, @@ -109,10 +116,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "max_continuous_days_allowed", "fieldtype": "Int", "hidden": 0, @@ -141,10 +150,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_3", "fieldtype": "Column Break", "hidden": 0, @@ -171,10 +182,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "is_carry_forward", "fieldtype": "Check", "hidden": 0, @@ -203,10 +216,46 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "eval: doc.is_carry_forward == 1", + "fetch_if_empty": 0, + "fieldname": "carry_forward_leave_expiry", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Carry Forward Leave Expiry", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "is_lwp", "fieldtype": "Check", "hidden": 0, @@ -233,10 +282,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "is_optional_leave", "fieldtype": "Check", "hidden": 0, @@ -264,10 +315,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "allow_negative", "fieldtype": "Check", "hidden": 0, @@ -294,10 +347,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "include_holiday", "fieldtype": "Check", "hidden": 0, @@ -324,10 +379,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "is_compensatory", "fieldtype": "Check", "hidden": 0, @@ -355,10 +412,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "encashment", "fieldtype": "Section Break", "hidden": 0, @@ -386,10 +445,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "allow_encashment", "fieldtype": "Check", "hidden": 0, @@ -417,11 +478,13 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, "depends_on": "allow_encashment", + "fetch_if_empty": 0, "fieldname": "encashment_threshold_days", "fieldtype": "Int", "hidden": 0, @@ -449,11 +512,13 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, "depends_on": "allow_encashment", + "fetch_if_empty": 0, "fieldname": "earning_component", "fieldtype": "Link", "hidden": 0, @@ -482,10 +547,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "earned_leave", "fieldtype": "Section Break", "hidden": 0, @@ -513,10 +580,12 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "is_earned_leave", "fieldtype": "Check", "hidden": 0, @@ -544,11 +613,13 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, "depends_on": "is_earned_leave", + "fetch_if_empty": 0, "fieldname": "earned_leave_frequency", "fieldtype": "Select", "hidden": 0, @@ -577,12 +648,14 @@ }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, "default": "0.5", "depends_on": "is_earned_leave", + "fetch_if_empty": 0, "fieldname": "rounding", "fieldtype": "Select", "hidden": 0, @@ -611,17 +684,15 @@ } ], "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 0, "icon": "fa fa-flag", "idx": 1, - "image_view": 0, "in_create": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-06-03 18:32:51.803472", + "modified": "2019-03-29 16:06:03.456035", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", @@ -687,8 +758,8 @@ ], "quick_entry": 0, "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 0, "track_changes": 0, - "track_seen": 0 -} + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index e0127e5e97..d7891a4f62 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -3,8 +3,15 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document class LeaveType(Document): - pass \ No newline at end of file + def validate(self): + if self.is_carry_forward: + self.validate_carry_forward() + + def validate_carry_forward(self): + if (self.carry_forward_leave_expiry <1 or self.carry_forward_leave_expiry > 365): + frappe.throw(_('Invalid entry!! Carried forward days need to expire within a year')) From c182a5687eae94994b0df8f8692d70195a594670 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 5 Apr 2019 11:17:40 +0530 Subject: [PATCH 002/117] feat: add validation for carry forward leave expiry --- erpnext/hr/doctype/leave_type/leave_type.json | 107 ++++++++++++------ erpnext/hr/doctype/leave_type/leave_type.py | 4 +- 2 files changed, 75 insertions(+), 36 deletions(-) diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 06f18c8a5e..ee9b04fc00 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -221,40 +221,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval: doc.is_carry_forward == 1", - "fetch_if_empty": 0, - "fieldname": "carry_forward_leave_expiry", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Carry Forward Leave Expiry", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "depends_on": "", "fetch_if_empty": 0, "fieldname": "is_lwp", "fieldtype": "Check", @@ -410,6 +377,76 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "depends_on": "eval: doc.is_carry_forward == 1", + "fetch_if_empty": 0, + "fieldname": "carry_forward_section", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Carry Forward", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "depends_on": "", + "description": "calculated in days", + "fetch_if_empty": 0, + "fieldname": "carry_forward__leave_expiry", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Carry Forward Leave Expiry", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -692,7 +729,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-03-29 16:06:03.456035", + "modified": "2019-04-04 14:27:47.742997", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index d7891a4f62..9cd574a598 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals +import calendar import frappe from frappe import _ @@ -13,5 +14,6 @@ class LeaveType(Document): self.validate_carry_forward() def validate_carry_forward(self): - if (self.carry_forward_leave_expiry <1 or self.carry_forward_leave_expiry > 365): + max_days = 366 if calendar.isleap() else 365 + if not (1 < self.carry_forward_leave_expiry < max_days): frappe.throw(_('Invalid entry!! Carried forward days need to expire within a year')) From 1208ca6a36069cdd7b5f394839960826e3cdadba Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 5 Apr 2019 11:18:28 +0530 Subject: [PATCH 003/117] feat: add old leaves to track carry forward leave allocation --- .../leave_allocation/leave_allocation.json | 94 ++++++++++++++++++- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 6d61fe3d5c..431adfb16b 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -22,6 +22,7 @@ "collapsible": 0, "columns": 0, "default": "", + "fetch_if_empty": 0, "fieldname": "naming_series", "fieldtype": "Select", "hidden": 0, @@ -55,6 +56,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "employee", "fieldtype": "Link", "hidden": 0, @@ -90,6 +92,7 @@ "collapsible": 0, "columns": 0, "fetch_from": "employee.employee_name", + "fetch_if_empty": 0, "fieldname": "employee_name", "fieldtype": "Data", "hidden": 0, @@ -122,6 +125,7 @@ "collapsible": 0, "columns": 0, "fetch_from": "employee.department", + "fetch_if_empty": 0, "fieldname": "department", "fieldtype": "Link", "hidden": 0, @@ -155,6 +159,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break1", "fieldtype": "Column Break", "hidden": 0, @@ -186,6 +191,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "leave_type", "fieldtype": "Link", "hidden": 0, @@ -220,6 +226,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "from_date", "fieldtype": "Date", "hidden": 0, @@ -252,6 +259,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "to_date", "fieldtype": "Date", "hidden": 0, @@ -284,6 +292,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_6", "fieldtype": "Section Break", "hidden": 0, @@ -316,6 +325,8 @@ "bold": 1, "collapsible": 0, "columns": 0, + "depends_on": "eval: doc.is_carry_forward != 1", + "fetch_if_empty": 0, "fieldname": "new_leaves_allocated", "fieldtype": "Float", "hidden": 0, @@ -347,7 +358,76 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "eval: doc.is_carry_forward == 1", + "fetch_if_empty": 0, + "fieldname": "old_leaves_allocated", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Old Leaves Allocated", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "is_carry_forward", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "is carry forward", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", "description": "", + "fetch_if_empty": 0, "fieldname": "carry_forward", "fieldtype": "Check", "hidden": 0, @@ -380,6 +460,7 @@ "collapsible": 0, "columns": 0, "depends_on": "carry_forward", + "fetch_if_empty": 0, "fieldname": "carry_forwarded_leaves", "fieldtype": "Float", "hidden": 0, @@ -411,6 +492,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "total_leaves_allocated", "fieldtype": "Float", "hidden": 0, @@ -443,6 +525,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:doc.total_leaves_encashed>0", + "fetch_if_empty": 0, "fieldname": "total_leaves_encashed", "fieldtype": "Float", "hidden": 0, @@ -475,6 +558,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_10", "fieldtype": "Column Break", "hidden": 0, @@ -506,6 +590,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "compensatory_request", "fieldtype": "Link", "hidden": 0, @@ -539,6 +624,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "leave_period", "fieldtype": "Link", "hidden": 0, @@ -572,6 +658,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "amended_from", "fieldtype": "Link", "hidden": 0, @@ -606,6 +693,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "notes", "fieldtype": "Section Break", "hidden": 0, @@ -638,6 +726,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "description", "fieldtype": "Small Text", "hidden": 0, @@ -667,17 +756,15 @@ } ], "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 0, "icon": "fa fa-ok", "idx": 1, - "image_view": 0, "in_create": 0, "is_submittable": 1, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-01-30 11:28:09.360525", + "modified": "2019-04-04 15:09:33.421008", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", @@ -724,7 +811,6 @@ ], "quick_entry": 0, "read_only": 0, - "read_only_onload": 0, "search_fields": "employee,employee_name,leave_type,total_leaves_allocated", "show_name_in_global_search": 1, "sort_field": "modified", From 7c6b6eae5b55651d6401c57b6f8d2a1a4cd4a1b4 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 5 Apr 2019 11:58:35 +0530 Subject: [PATCH 004/117] feat: set leave allocation on carry forward check --- .../leave_allocation/leave_allocation.py | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index dc270dba41..e755da5f54 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -62,11 +62,18 @@ class LeaveAllocation(Document): if flt(self.new_leaves_allocated) % 0.5: frappe.throw(_("Leaves must be allocated in multiples of 0.5"), ValueMultiplierError) - def validate_allocation_overlap(self): + def validate_allocation_overlap(self, carry_forward=0): leave_allocation = frappe.db.sql(""" - select name from `tabLeave Allocation` - where employee=%s and leave_type=%s and docstatus=1 - and to_date >= %s and from_date <= %s""", + SELECT + name + FROM `tabLeave Allocation` + WHERE + employee=%s + AND leave_type=%s + AND docstatus=1 + AND is_carry_forward={0} + AND to_date >= %s + AND from_date <= %s""".format(carry_forward), (self.employee, self.leave_type, self.from_date, self.to_date)) if leave_allocation: @@ -111,6 +118,11 @@ class LeaveAllocation(Document): else: frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError) + def set_carry_forward_leaves(self): + self.validate_allocation_overlap(carry_forward=1) + self.old_leaves_allocated = get_carry_forwarded_leaves(self.employee, self.leave_type, + self.from_date, self.is_carry_forward) + def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 leave_allocations = frappe.db.sql(""" @@ -135,17 +147,25 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): return leave_allocated @frappe.whitelist() -def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): +def get_carry_forwarded_leaves(employee, leave_type, date, is_carry_forward=None): carry_forwarded_leaves = 0 - if carry_forward: + if is_carry_forward: validate_carry_forward(leave_type) previous_allocation = frappe.db.sql(""" - select name, from_date, to_date, total_leaves_allocated - from `tabLeave Allocation` - where employee=%s and leave_type=%s and docstatus=1 and to_date < %s - order by to_date desc limit 1 + SELECT + name, + from_date, + to_date, + total_leaves_allocated + FROM `tabLeave Allocation` + WHERE + employee=%s + AND leave_type=%s + AND docstatus=1 + AND to_date < %s + ORDER BY to_date desc limit 1 """, (employee, leave_type, date), as_dict=1) if previous_allocation: leaves_taken = get_approved_leaves_for_period(employee, leave_type, @@ -157,4 +177,4 @@ def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): def validate_carry_forward(leave_type): if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"): - frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type)) + frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type)) \ No newline at end of file From bd3b3ea12c45c2d5cda37fa984fcbbd607dde56e Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 11 Apr 2019 00:20:32 +0530 Subject: [PATCH 005/117] test: create leave type --- .../hr/doctype/leave_type/test_leave_type.py | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py index b844e49e7c..671865b5a0 100644 --- a/erpnext/hr/doctype/leave_type/test_leave_type.py +++ b/erpnext/hr/doctype/leave_type/test_leave_type.py @@ -2,6 +2,24 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals - import frappe -test_records = frappe.get_test_records('Leave Type') \ No newline at end of file +from frappe import _ + +test_records = frappe.get_test_records('Leave Type') + +def create_leave_type(**args): + if frappe.db.exists("Leave Type", args.leave_type_name): + return frappe.get_doc("Leave Type", args.leave_type_name) + leave_type = frappe.get_doc({ + "doctype": "Leave Type", + "leave_type_name": args.leave_type_name or "_Test Leave Type", + "include_holiday": args.include_holidays or 1, + "allow_encashment": args.allow_encashment or 0, + "is_earned_leave": args.is_earned_leave or 0, + "is_lwp": args.is_lwp or 0, + "is_carry_forward": args.is_carry_forward or 0, + "carry_forward_leave_expiry": args.is_carry_forward or 0, + "encashment_threshold_days": args.encashment_threshold_days or 5, + "earning_component": "Leave Encashment" + }) + return leave_type \ No newline at end of file From c28d2e4b2aeae44daa08ca247f4fa5142fdcf4f8 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 11 Apr 2019 00:21:11 +0530 Subject: [PATCH 006/117] test: create leave allocation check --- .../leave_allocation/test_leave_allocation.py | 83 +++++++++++++++---- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 3b22eb2e44..3bc8dc458d 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -1,12 +1,13 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import getdate +from frappe.utils import nowdate, add_months, getdate +from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type class TestLeaveAllocation(unittest.TestCase): def test_overlapping_allocation(self): frappe.db.sql("delete from `tabLeave Allocation`") - + employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) leaves = [ { @@ -18,7 +19,7 @@ class TestLeaveAllocation(unittest.TestCase): "from_date": getdate("2015-10-01"), "to_date": getdate("2015-10-31"), "new_leaves_allocated": 5, - "docstatus": 1 + "docstatus": 1 }, { "doctype": "Leave Allocation", @@ -28,17 +29,17 @@ class TestLeaveAllocation(unittest.TestCase): "leave_type": "_Test Leave Type", "from_date": getdate("2015-09-01"), "to_date": getdate("2015-11-30"), - "new_leaves_allocated": 5 + "new_leaves_allocated": 5 } ] frappe.get_doc(leaves[0]).save() self.assertRaises(frappe.ValidationError, frappe.get_doc(leaves[1]).save) - - def test_invalid_period(self): + + def test_invalid_period(self): employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) - - d = frappe.get_doc({ + + doc = frappe.get_doc({ "doctype": "Leave Allocation", "__islocal": 1, "employee": employee.name, @@ -46,15 +47,15 @@ class TestLeaveAllocation(unittest.TestCase): "leave_type": "_Test Leave Type", "from_date": getdate("2015-09-30"), "to_date": getdate("2015-09-1"), - "new_leaves_allocated": 5 + "new_leaves_allocated": 5 }) - + #invalid period - self.assertRaises(frappe.ValidationError, d.save) - + self.assertRaises(frappe.ValidationError, doc.save) + def test_allocated_leave_days_over_period(self): employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) - d = frappe.get_doc({ + doc = frappe.get_doc({ "doctype": "Leave Allocation", "__islocal": 1, "employee": employee.name, @@ -62,10 +63,56 @@ class TestLeaveAllocation(unittest.TestCase): "leave_type": "_Test Leave Type", "from_date": getdate("2015-09-1"), "to_date": getdate("2015-09-30"), - "new_leaves_allocated": 35 + "new_leaves_allocated": 35 }) - - #allocated leave more than period - self.assertRaises(frappe.ValidationError, d.save) - + #allocated leave more than period + self.assertRaises(frappe.ValidationError, doc.save) + + def test_carry_forward_allocation(self): + frappe.db.sql("delete from `tabLeave Allocation`") + + employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) + doc = frappe.get_doc({ + "doctype": "Leave Allocation", + "__islocal": 1, + "employee": employee.name, + "employee_name": employee.employee_name, + "leave_type": "_Test Leave Type Carry Forward", + "from_date": nowdate(), + "to_date": add_months(nowdate(),-12), + "new_leaves_allocated": 10 + }) + doc.save() + doc = frappe.get_doc({ + "doctype": "Leave Allocation", + "__islocal": 1, + "employee": employee.name, + "employee_name": employee.employee_name, + "leave_type": "_Test Leave Type Carry Forward", + "from_date": nowdate(), + "to_date": add_months(now_date(),12), + "carry_forward": 1 + }) + doc.save() + self.assertEquals(doc.total_leaves_allocated, 10) + +def create_leave_allocation(**args): + args = frappe._dict(args) + if not frappe.db.exists("Leave Type", "_Test Leave Type"): + leave_type = create_leave_type(args.leave_type) + leave_type.insert() + + employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) + leave_allocation = frappe.get_doc({ + "doctype": "Leave Allocation", + "__islocal": 1, + "employee": employee.name, + "employee_name": employee.employee_name, + "leave_type": args.leave_type or "_Test Leave Type", + "from_date": args.from_date or nowdate(), + "to_date": args.to_date or add_months(nowdate(), 12), + "new_leaves_allocated": args.new_leaves_allocated or 20 + }) + return leave_allocation + test_dependencies = ["Employee", "Leave Type"] \ No newline at end of file From e46d3a87ea7fc9850f4619ef02d6850ba3abe876 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 11 Apr 2019 00:22:11 +0530 Subject: [PATCH 007/117] feat: set carry forwarded leave allocation --- .../leave_allocation/leave_allocation.json | 107 +--------------- .../leave_allocation/leave_allocation.py | 114 +++++++++++------- 2 files changed, 75 insertions(+), 146 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 431adfb16b..aa203384e8 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -325,7 +325,7 @@ "bold": 1, "collapsible": 0, "columns": 0, - "depends_on": "eval: doc.is_carry_forward != 1", + "depends_on": "eval: doc.carry_forward != 1", "fetch_if_empty": 0, "fieldname": "new_leaves_allocated", "fieldtype": "Float", @@ -351,73 +351,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval: doc.is_carry_forward == 1", - "fetch_if_empty": 0, - "fieldname": "old_leaves_allocated", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Old Leaves Allocated", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "is_carry_forward", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "is carry forward", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -437,7 +370,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Add unused leaves from previous allocations", + "label": "Allocate unused leaves from previous allocations", "length": 0, "no_copy": 0, "permlevel": 0, @@ -452,39 +385,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "carry_forward", - "fetch_if_empty": 0, - "fieldname": "carry_forwarded_leaves", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Unused leaves", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -492,6 +392,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "", "fetch_if_empty": 0, "fieldname": "total_leaves_allocated", "fieldtype": "Float", @@ -764,7 +665,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-04-04 15:09:33.421008", + "modified": "2019-04-05 15:31:04.627015", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index e755da5f54..7ce3143901 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -18,14 +18,19 @@ class ValueMultiplierError(frappe.ValidationError): pass class LeaveAllocation(Document): def validate(self): self.validate_period() - self.validate_new_leaves_allocated_value() + self.validate_lwp() self.validate_allocation_overlap() self.validate_back_dated_allocation() + if not self.carry_forward: + self.validate_new_leaves_allocated_value() + self.validate_leave_allocation_days() self.set_total_leaves_allocated() self.validate_total_leaves_allocated() - self.validate_lwp() set_employee_name(self) - self.validate_leave_allocation_days() + + def on_update(self): + if self.carry_forward: + self.set_carry_forward_leaves() def validate_leave_allocation_days(self): company = frappe.db.get_value("Employee", self.employee, "company") @@ -44,7 +49,6 @@ class LeaveAllocation(Document): self.validate_new_leaves_allocated_value() self.set_total_leaves_allocated() - frappe.db.set(self,'carry_forwarded_leaves', flt(self.carry_forwarded_leaves)) frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated)) self.validate_against_leave_applications() @@ -62,7 +66,7 @@ class LeaveAllocation(Document): if flt(self.new_leaves_allocated) % 0.5: frappe.throw(_("Leaves must be allocated in multiples of 0.5"), ValueMultiplierError) - def validate_allocation_overlap(self, carry_forward=0): + def validate_allocation_overlap(self): leave_allocation = frappe.db.sql(""" SELECT name @@ -71,10 +75,10 @@ class LeaveAllocation(Document): employee=%s AND leave_type=%s AND docstatus=1 - AND is_carry_forward={0} + AND carry_forward={0} AND to_date >= %s - AND from_date <= %s""".format(carry_forward), - (self.employee, self.leave_type, self.from_date, self.to_date)) + AND from_date <= %s""" #nosec + .format(self.carry_forward), (self.employee, self.leave_type, self.from_date, self.to_date)) if leave_allocation: frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} to {3}") @@ -94,12 +98,11 @@ class LeaveAllocation(Document): BackDatedAllocationError) def set_total_leaves_allocated(self): - self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, - self.leave_type, self.from_date, self.carry_forward) - self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + flt(self.new_leaves_allocated) + self.total_leaves_allocated = flt(self.new_leaves_allocated) - if not self.total_leaves_allocated and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave") and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"): + if not self.total_leaves_allocated and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave")\ + and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"): frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}".format(self.leave_type))) def validate_total_leaves_allocated(self): @@ -119,20 +122,44 @@ class LeaveAllocation(Document): frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError) def set_carry_forward_leaves(self): - self.validate_allocation_overlap(carry_forward=1) - self.old_leaves_allocated = get_carry_forwarded_leaves(self.employee, self.leave_type, - self.from_date, self.is_carry_forward) + + leaves_allocated + # check number of days to expire, ignore expiry for default value + expiry_days = frappe.db.get_value("Leave Type", + filters={"leave_type": leave_type, "is_carry_forward": 1}, + fieldname="carry_forward_leave_expiry") + + max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") + leave_period = get_leave_period(self.from_date, self.to_date, company) + if leave_period: + leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date) + + carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, self.leave_type, + self.from_date, expiry_days) + leaves_allocated += carry_forwarded_leaves + + if leaves_allocated > max_leaves_allowed: + self.total_leaves_allocated = max_leaves_allowed - leaves_allocated + else: + self.total_leaves_allocated = carry_forwarded_leaves def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 leave_allocations = frappe.db.sql(""" - select employee, leave_type, from_date, to_date, total_leaves_allocated - from `tabLeave Allocation` - where employee=%(employee)s and leave_type=%(leave_type)s - and docstatus=1 - and (from_date between %(from_date)s and %(to_date)s - or to_date between %(from_date)s and %(to_date)s - or (from_date < %(from_date)s and to_date > %(to_date)s)) + SELECT + employee, + leave_type, + from_date, + to_date, + total_leaves_allocated + FROM `tabLeave Allocation` + WHERE + employee=%(employee)s + AND leave_type=%(leave_type)s + AND docstatus=1 + AND (from_date BETWEEN %(from_date)s AND %(to_date)s + OR to_date BETWEEN %(from_date)s AND %(to_date)s + OR (from_date < %(from_date)s AND to_date > %(to_date)s)) """, { "from_date": from_date, "to_date": to_date, @@ -147,31 +174,32 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): return leave_allocated @frappe.whitelist() -def get_carry_forwarded_leaves(employee, leave_type, date, is_carry_forward=None): +def get_carry_forwarded_leaves(employee, leave_type, date, expiry_days): carry_forwarded_leaves = 0 - if is_carry_forward: - validate_carry_forward(leave_type) + validate_carry_forward(leave_type) + filters = { + "employee": employee, + "leave_type": leave_type, + "docstatus": 1, + "to_date": ("<", date) + } + limit = 1 + if expiry_days: + filters.update(carry_forward=0) + limit = 2 - previous_allocation = frappe.db.sql(""" - SELECT - name, - from_date, - to_date, - total_leaves_allocated - FROM `tabLeave Allocation` - WHERE - employee=%s - AND leave_type=%s - AND docstatus=1 - AND to_date < %s - ORDER BY to_date desc limit 1 - """, (employee, leave_type, date), as_dict=1) - if previous_allocation: - leaves_taken = get_approved_leaves_for_period(employee, leave_type, - previous_allocation[0].from_date, previous_allocation[0].to_date) + previous_allocation = frappe.get_all("Leave Allocation", + filters=filters, + fields=["name","from_date","to_date","total_leaves_allocated"], + order_by="to_date desc", + limit=limit) - carry_forwarded_leaves = flt(previous_allocation[0].total_leaves_allocated) - flt(leaves_taken) + if previous_allocation: + leaves_taken = get_approved_leaves_for_period(employee, leave_type, + previous_allocation[0].from_date, previous_allocation[0].to_date) + + carry_forwarded_leaves = flt(previous_allocation[0].total_leaves_allocated) - flt(leaves_taken) return carry_forwarded_leaves From d6c5b6320ffc0327dabad107688bc71ffccbcbf5 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 23 Apr 2019 17:47:25 +0530 Subject: [PATCH 008/117] feat: add a field for conditionally displaying carry forwarded leave --- .../leave_allocation/leave_allocation.json | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index aa203384e8..0903398d1f 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -351,6 +351,40 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval: doc.carry_forward == 1", + "fetch_if_empty": 0, + "fieldname": "carry_forwarded_leaves", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Unused Leaves", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -665,7 +699,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-04-05 15:31:04.627015", + "modified": "2019-04-22 18:33:15.858006", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", From 0c0bfb1ef02d5ab68c97f4ec17c05c783abfe47c Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 23 Apr 2019 18:25:51 +0530 Subject: [PATCH 009/117] feat: calculate carry forward leaves allocation --- .../leave_allocation/leave_allocation.py | 72 +++++++++---------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 7ce3143901..7f8f806f3b 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -21,35 +21,25 @@ class LeaveAllocation(Document): self.validate_lwp() self.validate_allocation_overlap() self.validate_back_dated_allocation() - if not self.carry_forward: - self.validate_new_leaves_allocated_value() - self.validate_leave_allocation_days() - self.set_total_leaves_allocated() self.validate_total_leaves_allocated() set_employee_name(self) - - def on_update(self): - if self.carry_forward: - self.set_carry_forward_leaves() + self.validate_leaves_allocated_value() + self.validate_leave_allocation_days() + self.set_total_leaves_allocated() def validate_leave_allocation_days(self): - company = frappe.db.get_value("Employee", self.employee, "company") - leave_period = get_leave_period(self.from_date, self.to_date, company) - max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") - if max_leaves_allowed > 0: - leave_allocated = 0 - if leave_period: - leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date) - leave_allocated += self.new_leaves_allocated - if leave_allocated > max_leaves_allowed: - frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")\ - .format(self.leave_type, self.employee)) + new_leaves = self.new_leaves_allocate if not self.carry_forward else self.carry_forwarded_leaves + max_leaves, leaves_allocated = self.get_max_leaves_with_leaves_allocated_for_leave_type(flt(new_leaves)) + + if leave_allocated > max_leaves: + frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")\ + .format(self.leave_type, self.employee)) def on_update_after_submit(self): self.validate_new_leaves_allocated_value() self.set_total_leaves_allocated() - frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated)) + frappe.db.set(self,'total_leaves_allocated', flt(self.total_leaves_allocated)) self.validate_against_leave_applications() @@ -61,7 +51,7 @@ class LeaveAllocation(Document): if frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"): frappe.throw(_("Leave Type {0} cannot be allocated since it is leave without pay").format(self.leave_type)) - def validate_new_leaves_allocated_value(self): + def validate_leaves_allocated_value(self): """validate that leave allocation is in multiples of 0.5""" if flt(self.new_leaves_allocated) % 0.5: frappe.throw(_("Leaves must be allocated in multiples of 0.5"), ValueMultiplierError) @@ -98,8 +88,11 @@ class LeaveAllocation(Document): BackDatedAllocationError) def set_total_leaves_allocated(self): - - self.total_leaves_allocated = flt(self.new_leaves_allocated) + if self.carry_forward: + self.set_carry_forwarded_leaves() + self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + else: + self.total_leaves_allocated = flt(self.new_leaves_allocated) if not self.total_leaves_allocated and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave")\ and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"): @@ -121,27 +114,28 @@ class LeaveAllocation(Document): else: frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError) - def set_carry_forward_leaves(self): - - leaves_allocated - # check number of days to expire, ignore expiry for default value + def set_carry_forwarded_leaves(self): + # check number of days to expire, ignore expiry for default value 0 expiry_days = frappe.db.get_value("Leave Type", filters={"leave_type": leave_type, "is_carry_forward": 1}, fieldname="carry_forward_leave_expiry") - max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") + self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, self.leave_type, self.from_date, expiry_days) + max_leaves, leaves_allocated = self.get_max_leaves_with_leaves_allocated_for_leave_type(self.carry_forwarded_leaves) + if leaves_allocated > max_leaves: + self.carry_forwarded_leaves = max_leaves - (leaves_allocated - self.carry_forwarded_leaves) + + def get_max_leaves_with_leaves_allocated_for_leave_type(self, new_leaves): + ''' compare new leaves allocated with max leaves ''' + company = frappe.db.get_value("Employee", self.employee, "company") leave_period = get_leave_period(self.from_date, self.to_date, company) - if leave_period: - leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date) - - carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, self.leave_type, - self.from_date, expiry_days) - leaves_allocated += carry_forwarded_leaves - - if leaves_allocated > max_leaves_allowed: - self.total_leaves_allocated = max_leaves_allowed - leaves_allocated - else: - self.total_leaves_allocated = carry_forwarded_leaves + max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") + if max_leaves_allowed > 0: + leave_allocated = 0 + if leave_period: + leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date) + leave_allocated += new_leaves + return max_leaves_allowed, leaves_allocated def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 From 4badca54af4b06d127836e60af0dd5f7d9091f1d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 23 Apr 2019 18:26:24 +0530 Subject: [PATCH 010/117] feat: validate leave expiry days --- erpnext/hr/doctype/leave_type/leave_type.json | 6 +++--- erpnext/hr/doctype/leave_type/leave_type.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index ee9b04fc00..0b8e38ea73 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -422,7 +422,7 @@ "depends_on": "", "description": "calculated in days", "fetch_if_empty": 0, - "fieldname": "carry_forward__leave_expiry", + "fieldname": "carry_forward_leave_expiry", "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, @@ -431,7 +431,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Carry Forward Leave Expiry", + "label": "Carry Forward Leave Expiry", "length": 0, "no_copy": 0, "permlevel": 0, @@ -729,7 +729,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-04-04 14:27:47.742997", + "modified": "2019-04-11 15:38:39.334283", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index 9cd574a598..da21f7827b 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import calendar import frappe +from datetime import datetime from frappe import _ from frappe.model.document import Document @@ -14,6 +15,6 @@ class LeaveType(Document): self.validate_carry_forward() def validate_carry_forward(self): - max_days = 366 if calendar.isleap() else 365 - if not (1 < self.carry_forward_leave_expiry < max_days): + max_days = 366 if calendar.isleap(datetime.now().year) else 365 + if not (0 <= self.carry_forward_leave_expiry <= max_days): frappe.throw(_('Invalid entry!! Carried forward days need to expire within a year')) From 99c9cfaaedd069c4053a190a37a681ca2f79eea9 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 23 Apr 2019 18:28:28 +0530 Subject: [PATCH 011/117] feat: generate leave allocation for carry forwarded leaves --- .../hr/doctype/leave_period/leave_period.py | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py index 15fa8d6f8c..1e2884d202 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.py +++ b/erpnext/hr/doctype/leave_period/leave_period.py @@ -5,9 +5,10 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import getdate, cstr +from frappe.utils import getdate, cstr, add_days from frappe.model.document import Document from erpnext.hr.utils import validate_overlap, get_employee_leave_policy +from erpnext.hr.doctype.leave_allocation.leave_allocation import get_carry_forwarded_leaves from frappe.utils.background_jobs import enqueue from six import iteritems @@ -39,8 +40,8 @@ class LeavePeriod(Document): employee=None, carry_forward_leaves=0): employees = self.get_employees({ "grade": grade, - "department": department, - "designation": designation, + "department": department, + "designation": designation, "name": employee }) @@ -57,7 +58,7 @@ def grant_leave_alloc_for_employees(employees, leave_period, carry_forward_leave leave_allocations = [] existing_allocations_for = get_existing_allocations(employees, leave_period.name) leave_type_details = get_leave_type_details() - count=0 + count = 0 for employee in employees: if employee in existing_allocations_for: continue @@ -77,8 +78,14 @@ def grant_leave_alloc_for_employees(employees, leave_period, carry_forward_leave def get_existing_allocations(employees, leave_period): leave_allocations = frappe.db.sql_list(""" - select distinct employee from `tabLeave Allocation` - where leave_period=%s and employee in (%s) and docstatus=1 + SELECT DISTINCT + employee + FROM `tabLeave Allocation` + WHERE + leave_period=%s + AND employee in (%s) + AND carry_forward=0 + AND docstatus=1 """ % ('%s', ', '.join(['%s']*len(employees))), [leave_period] + employees) if leave_allocations: frappe.msgprint(_("Skipping Leave Allocation for the following employees, as Leave Allocation records already exists against them. {0}") @@ -87,7 +94,8 @@ def get_existing_allocations(employees, leave_period): def get_leave_type_details(): leave_type_details = frappe._dict() - leave_types = frappe.get_all("Leave Type", fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward"]) + leave_types = frappe.get_all("Leave Type", + fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "carry_forward_leave_expiry"]) for d in leave_types: leave_type_details.setdefault(d.name, d) return leave_type_details @@ -106,9 +114,19 @@ def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_ty allocation.leave_period = leave_period.name if carry_forward_leaves: if leave_type_details.get(leave_type).is_carry_forward: - allocation.carry_forward = carry_forward_leaves - allocation.save(ignore_permissions = True) + create_carry_forward_leaves_allocation(employee, leave_type, leave_type_details, leave_period, carry_forward_leaves) + allocation.save(ignore_permissions=True) allocation.submit() return allocation.name - +def create_carry_forward_leaves_allocation(employee, leave_type, leave_type_details, leave_period, carry_forward_leaves): + allocation = frappe.new_doc("Leave Allocation") + allocation.employee = employee + allocation.leave_type = leave_type + allocation.from_date = leave_period.from_date + allocation.to_date = add_days(leave_period.from_date, leave_type_details.carry_forward_leave_expiry) if not leave_type_details.carry_forward_leave_expiry else leave_period.to_date + allocation.leave_period = leave_period.name + allocation.carry_forward = carry_forward_leaves + allocation.carry_forwarded_leaves = get_carry_forwarded_leaves(employee, leave_type, leave_period.from_date, leave_type_details.carry_forward_leave_expiry) + allocation.save(ignore_permissions=True) + allocation.submit() \ No newline at end of file From 70cf4a67967ceb1525cc1f98340ae81c4180f1d8 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 24 Apr 2019 13:17:16 +0530 Subject: [PATCH 012/117] feat: validate leave allocation period to be within expiry limits --- .../leave_allocation/leave_allocation.py | 32 ++++++++++++++----- .../hr/doctype/leave_period/leave_period.py | 2 +- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 7f8f806f3b..0c00afff66 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -44,9 +44,17 @@ class LeaveAllocation(Document): self.validate_against_leave_applications() def validate_period(self): - if date_diff(self.to_date, self.from_date) <= 0: + allocation_period = date_diff(self.to_date, self.from_date) + + if allocation_period <= 0: frappe.throw(_("To date cannot be before from date")) + # check if the allocation period is more than the expiry allows for carry forwarded allocation + if self.carry_forward: + expiry_days = get_days_to_expiry_for_leave_type(self.leave_type) + if allocation_period > expiry_days: + frappe.throw(_("Leave allocation period cannot be more than the expiry days allocated")) + def validate_lwp(self): if frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"): frappe.throw(_("Leave Type {0} cannot be allocated since it is leave without pay").format(self.leave_type)) @@ -115,13 +123,10 @@ class LeaveAllocation(Document): frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError) def set_carry_forwarded_leaves(self): - # check number of days to expire, ignore expiry for default value 0 - expiry_days = frappe.db.get_value("Leave Type", - filters={"leave_type": leave_type, "is_carry_forward": 1}, - fieldname="carry_forward_leave_expiry") + self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, self.leave_type, self.from_date) - self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, self.leave_type, self.from_date, expiry_days) max_leaves, leaves_allocated = self.get_max_leaves_with_leaves_allocated_for_leave_type(self.carry_forwarded_leaves) + if leaves_allocated > max_leaves: self.carry_forwarded_leaves = max_leaves - (leaves_allocated - self.carry_forwarded_leaves) @@ -168,9 +173,10 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): return leave_allocated @frappe.whitelist() -def get_carry_forwarded_leaves(employee, leave_type, date, expiry_days): +def get_carry_forwarded_leaves(employee, leave_type, date): + ''' Calculates carry forwarded days based on previous unused leave allocations ''' carry_forwarded_leaves = 0 - + expiry_days = get_days_to_expiry_for_leave_type(leave_type) validate_carry_forward(leave_type) filters = { "employee": employee, @@ -179,6 +185,8 @@ def get_carry_forwarded_leaves(employee, leave_type, date, expiry_days): "to_date": ("<", date) } limit = 1 + + # check number of days to expire, ignore expiry for default value 0 if expiry_days: filters.update(carry_forward=0) limit = 2 @@ -197,6 +205,14 @@ def get_carry_forwarded_leaves(employee, leave_type, date, expiry_days): return carry_forwarded_leaves +def get_days_to_expiry_for_leave_type(leave_type): + ''' returns days to expiry for a provided leave type ''' + expiry_days = frappe.db.get_value("Leave Type", + filters={"leave_type": leave_type, "is_carry_forward": 1}, + fieldname="carry_forward_leave_expiry") + + + def validate_carry_forward(leave_type): if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"): frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type)) \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py index 1e2884d202..0c83a1e548 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.py +++ b/erpnext/hr/doctype/leave_period/leave_period.py @@ -127,6 +127,6 @@ def create_carry_forward_leaves_allocation(employee, leave_type, leave_type_deta allocation.to_date = add_days(leave_period.from_date, leave_type_details.carry_forward_leave_expiry) if not leave_type_details.carry_forward_leave_expiry else leave_period.to_date allocation.leave_period = leave_period.name allocation.carry_forward = carry_forward_leaves - allocation.carry_forwarded_leaves = get_carry_forwarded_leaves(employee, leave_type, leave_period.from_date, leave_type_details.carry_forward_leave_expiry) + allocation.carry_forwarded_leaves = get_carry_forwarded_leaves(employee, leave_type, leave_period.from_date) allocation.save(ignore_permissions=True) allocation.submit() \ No newline at end of file From d01863707c891a83305f05cc6da0457a163a28e0 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 25 Apr 2019 19:40:37 +0530 Subject: [PATCH 013/117] test: pass leave type as params --- .../leave_allocation/test_leave_allocation.py | 52 ++++++++----------- .../hr/doctype/leave_type/test_leave_type.py | 3 +- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 3bc8dc458d..325f3a7299 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import nowdate, add_months, getdate +from frappe.utils import nowdate, add_months, getdate, add_days from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type class TestLeaveAllocation(unittest.TestCase): @@ -71,36 +71,27 @@ class TestLeaveAllocation(unittest.TestCase): def test_carry_forward_allocation(self): frappe.db.sql("delete from `tabLeave Allocation`") - employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) - doc = frappe.get_doc({ - "doctype": "Leave Allocation", - "__islocal": 1, - "employee": employee.name, - "employee_name": employee.employee_name, - "leave_type": "_Test Leave Type Carry Forward", - "from_date": nowdate(), - "to_date": add_months(nowdate(),-12), - "new_leaves_allocated": 10 - }) - doc.save() - doc = frappe.get_doc({ - "doctype": "Leave Allocation", - "__islocal": 1, - "employee": employee.name, - "employee_name": employee.employee_name, - "leave_type": "_Test Leave Type Carry Forward", - "from_date": nowdate(), - "to_date": add_months(now_date(),12), - "carry_forward": 1 - }) - doc.save() - self.assertEquals(doc.total_leaves_allocated, 10) + leave_type = create_leave_type( + leave_type_name="_Test Carry Forward", + is_carry_forward=1, + carry_forward_leave_expiry=366) + leave_type.submit() + + leave_allocation = create_leave_allocation( + from_date=add_months(nowdate(), -12), + to_date=add_days(nowdate(), -1), + leave_type=leave_type + ) + leave_allocation.new_leaves_allocated = 10 + leave_allocation.submit() + + carry_forward_alloc = create_leave_allocation(leave_type=leave_type) + carry_forward_alloc.carry_forward = 1 + carry_forward_alloc.save() + self.assertEquals(carry_forward_alloc.total_leaves_allocated, 10) def create_leave_allocation(**args): args = frappe._dict(args) - if not frappe.db.exists("Leave Type", "_Test Leave Type"): - leave_type = create_leave_type(args.leave_type) - leave_type.insert() employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) leave_allocation = frappe.get_doc({ @@ -108,10 +99,9 @@ def create_leave_allocation(**args): "__islocal": 1, "employee": employee.name, "employee_name": employee.employee_name, - "leave_type": args.leave_type or "_Test Leave Type", + "leave_type": args.leave_type.leave_type_name or "_Test Leave Type", "from_date": args.from_date or nowdate(), - "to_date": args.to_date or add_months(nowdate(), 12), - "new_leaves_allocated": args.new_leaves_allocated or 20 + "to_date": args.to_date or add_months(nowdate(), 12) }) return leave_allocation diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py index 671865b5a0..1006550de4 100644 --- a/erpnext/hr/doctype/leave_type/test_leave_type.py +++ b/erpnext/hr/doctype/leave_type/test_leave_type.py @@ -8,6 +8,7 @@ from frappe import _ test_records = frappe.get_test_records('Leave Type') def create_leave_type(**args): + args = frappe._dict(args) if frappe.db.exists("Leave Type", args.leave_type_name): return frappe.get_doc("Leave Type", args.leave_type_name) leave_type = frappe.get_doc({ @@ -18,7 +19,7 @@ def create_leave_type(**args): "is_earned_leave": args.is_earned_leave or 0, "is_lwp": args.is_lwp or 0, "is_carry_forward": args.is_carry_forward or 0, - "carry_forward_leave_expiry": args.is_carry_forward or 0, + "carry_forward_leave_expiry": args.carry_forward_leave_expiry or 0, "encashment_threshold_days": args.encashment_threshold_days or 5, "earning_component": "Leave Encashment" }) From 0abf5d340cbbfcc9d05cb51243ce9a47dca2d1e0 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 25 Apr 2019 19:52:45 +0530 Subject: [PATCH 014/117] fix: carry forwarded allocation period validation --- .../leave_allocation/leave_allocation.py | 31 +++++++++---------- .../hr/doctype/leave_period/leave_period.py | 5 ++- erpnext/hr/doctype/leave_type/leave_type.py | 4 +-- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 0c00afff66..f7db7dee86 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -19,19 +19,19 @@ class LeaveAllocation(Document): def validate(self): self.validate_period() self.validate_lwp() + set_employee_name(self) + self.set_total_leaves_allocated() self.validate_allocation_overlap() self.validate_back_dated_allocation() self.validate_total_leaves_allocated() - set_employee_name(self) self.validate_leaves_allocated_value() self.validate_leave_allocation_days() - self.set_total_leaves_allocated() def validate_leave_allocation_days(self): - new_leaves = self.new_leaves_allocate if not self.carry_forward else self.carry_forwarded_leaves + new_leaves = self.new_leaves_allocated if not self.carry_forward else self.carry_forwarded_leaves max_leaves, leaves_allocated = self.get_max_leaves_with_leaves_allocated_for_leave_type(flt(new_leaves)) - if leave_allocated > max_leaves: + if leaves_allocated > max_leaves: frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")\ .format(self.leave_type, self.employee)) @@ -52,8 +52,9 @@ class LeaveAllocation(Document): # check if the allocation period is more than the expiry allows for carry forwarded allocation if self.carry_forward: expiry_days = get_days_to_expiry_for_leave_type(self.leave_type) - if allocation_period > expiry_days: - frappe.throw(_("Leave allocation period cannot be more than the expiry days allocated")) + + if allocation_period > flt(expiry_days) and expiry_days: + frappe.throw(_("Leave allocation period cannot exceed carry forward expiry limit")) def validate_lwp(self): if frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"): @@ -109,7 +110,7 @@ class LeaveAllocation(Document): def validate_total_leaves_allocated(self): # Adding a day to include To Date in the difference date_difference = date_diff(self.to_date, self.from_date) + 1 - if date_difference < self.total_leaves_allocated: + if date_difference < flt(self.total_leaves_allocated): frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError) def validate_against_leave_applications(self): @@ -133,13 +134,13 @@ class LeaveAllocation(Document): def get_max_leaves_with_leaves_allocated_for_leave_type(self, new_leaves): ''' compare new leaves allocated with max leaves ''' company = frappe.db.get_value("Employee", self.employee, "company") + leaves_allocated = 0 leave_period = get_leave_period(self.from_date, self.to_date, company) max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") if max_leaves_allowed > 0: - leave_allocated = 0 if leave_period: - leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date) - leave_allocated += new_leaves + leaves_allocated = get_leave_allocation_for_period(self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date) + leaves_allocated += new_leaves return max_leaves_allowed, leaves_allocated def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): @@ -184,12 +185,12 @@ def get_carry_forwarded_leaves(employee, leave_type, date): "docstatus": 1, "to_date": ("<", date) } - limit = 1 + limit = 2 # check number of days to expire, ignore expiry for default value 0 if expiry_days: filters.update(carry_forward=0) - limit = 2 + limit = 1 previous_allocation = frappe.get_all("Leave Allocation", filters=filters, @@ -207,12 +208,10 @@ def get_carry_forwarded_leaves(employee, leave_type, date): def get_days_to_expiry_for_leave_type(leave_type): ''' returns days to expiry for a provided leave type ''' - expiry_days = frappe.db.get_value("Leave Type", - filters={"leave_type": leave_type, "is_carry_forward": 1}, + return frappe.db.get_value("Leave Type", + filters={"leave_type_name": leave_type, "is_carry_forward": 1}, fieldname="carry_forward_leave_expiry") - - def validate_carry_forward(leave_type): if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"): frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type)) \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py index 0c83a1e548..a6198a8918 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.py +++ b/erpnext/hr/doctype/leave_period/leave_period.py @@ -120,6 +120,9 @@ def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_ty return allocation.name def create_carry_forward_leaves_allocation(employee, leave_type, leave_type_details, leave_period, carry_forward_leaves): + carry_forwarded_leaves = get_carry_forwarded_leaves(employee, leave_type, leave_period.from_date) + if not carry_forwarded_leaves: + return allocation = frappe.new_doc("Leave Allocation") allocation.employee = employee allocation.leave_type = leave_type @@ -127,6 +130,6 @@ def create_carry_forward_leaves_allocation(employee, leave_type, leave_type_deta allocation.to_date = add_days(leave_period.from_date, leave_type_details.carry_forward_leave_expiry) if not leave_type_details.carry_forward_leave_expiry else leave_period.to_date allocation.leave_period = leave_period.name allocation.carry_forward = carry_forward_leaves - allocation.carry_forwarded_leaves = get_carry_forwarded_leaves(employee, leave_type, leave_period.from_date) + allocation.carry_forwarded_leaves = carry_forwarded_leaves allocation.save(ignore_permissions=True) allocation.submit() \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index da21f7827b..dcae5fe085 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -15,6 +15,6 @@ class LeaveType(Document): self.validate_carry_forward() def validate_carry_forward(self): - max_days = 366 if calendar.isleap(datetime.now().year) else 365 + max_days = 367 if calendar.isleap(datetime.now().year) else 366 if not (0 <= self.carry_forward_leave_expiry <= max_days): - frappe.throw(_('Invalid entry!! Carried forward days need to expire within a year')) + frappe.throw(_('Invalid entry!! Carried forward days need to expire within a year')) \ No newline at end of file From 5e2b067107b5ab18d7cc1e863b5cd8d908ee23f7 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 25 Apr 2019 19:54:40 +0530 Subject: [PATCH 015/117] feat: display carry forwarded allocation days and total leaves allocated --- .../leave_allocation/leave_allocation.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 4b4bfafd16..0014c07a3c 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -34,34 +34,31 @@ frappe.ui.form.on("Leave Allocation", { }, carry_forwarded_leaves: function(frm) { - frm.set_value("total_leaves_allocated", - flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); + frm.set_value("total_leaves_allocated", flt(frm.doc.carry_forwarded_leaves)); }, new_leaves_allocated: function(frm) { - frm.set_value("total_leaves_allocated", - flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); + frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); }, calculate_total_leaves_allocated: function(frm) { - if (cint(frm.doc.carry_forward) == 1 && frm.doc.leave_type && frm.doc.employee) { + if (cint(frm.doc.carry_forward) === 1 && frm.doc.leave_type && frm.doc.employee) { return frappe.call({ method: "erpnext.hr.doctype.leave_allocation.leave_allocation.get_carry_forwarded_leaves", args: { "employee": frm.doc.employee, - "date": frm.doc.from_date, "leave_type": frm.doc.leave_type, - "carry_forward": frm.doc.carry_forward + "date": frm.doc.from_date, }, callback: function(r) { if (!r.exc && r.message) { + frm.set_value("new_leaves_allocated", 0); frm.set_value('carry_forwarded_leaves', r.message); - frm.set_value("total_leaves_allocated", - flt(r.message) + flt(frm.doc.new_leaves_allocated)); + frm.set_value("total_leaves_allocated", flt(r.message)); } } }) - } else if (cint(frm.doc.carry_forward) == 0) { + } else if (cint(frm.doc.carry_forward) === 0) { frm.set_value("carry_forwarded_leaves", 0); frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); } From 1de990b2ac71f95468285cdce119f83295906210 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 9 May 2019 16:51:02 +0530 Subject: [PATCH 016/117] feat: create leave ledger entry --- .../hr/doctype/leave_ledger_entry/__init__.py | 0 .../leave_ledger_entry/leave_ledger_entry.js | 8 ++ .../leave_ledger_entry.json | 99 +++++++++++++++++++ .../leave_ledger_entry/leave_ledger_entry.py | 39 ++++++++ .../test_leave_ledger_entry.py | 10 ++ 5 files changed, 156 insertions(+) create mode 100644 erpnext/hr/doctype/leave_ledger_entry/__init__.py create mode 100644 erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.js create mode 100644 erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json create mode 100644 erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py create mode 100644 erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py diff --git a/erpnext/hr/doctype/leave_ledger_entry/__init__.py b/erpnext/hr/doctype/leave_ledger_entry/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.js b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.js new file mode 100644 index 0000000000..c68d518f67 --- /dev/null +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.js @@ -0,0 +1,8 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Leave Ledger Entry', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json new file mode 100644 index 0000000000..8ef302452d --- /dev/null +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -0,0 +1,99 @@ +{ + "creation": "2019-05-09 15:47:39.760406", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "leave_type", + "transaction_type", + "transaction_name", + "leaves", + "from_date", + "to_date", + "is_carry_forward", + "amended_from" + ], + "fields": [ + { + "fieldname": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee" + }, + { + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name" + }, + { + "fieldname": "leave_type", + "fieldtype": "Link", + "label": "Leave Type", + "options": "Leave Type" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Leave Ledger Entry", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "transaction_type", + "fieldtype": "Link", + "label": "Transaction Type", + "options": "DocType" + }, + { + "fieldname": "transaction_name", + "fieldtype": "Dynamic Link", + "label": "Transaction Name", + "options": "transaction_type" + }, + { + "fieldname": "leaves", + "fieldtype": "Int", + "label": "Leaves" + }, + { + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date" + }, + { + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date" + }, + { + "fieldname": "is_carry_forward", + "fieldtype": "Check", + "label": "Is Carry Forward" + } + ], + "is_submittable": 1, + "modified": "2019-05-09 15:54:52.834794", + "modified_by": "Administrator", + "module": "HR", + "name": "Leave Ledger Entry", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "ASC" +} \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py new file mode 100644 index 0000000000..dd8de56717 --- /dev/null +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document +from frappe.utils import add_days + +class LeaveLedgerEntry(Document): + pass + +def create_leave_ledger_entry(ref_doc, submit=True): + ledger = dict( + doctype='Leave Ledger Entry', + employee=ref_doc.employee, + employee_name=ref_doc.employee_name, + leave_type=ref_doc.leave_type, + from_date=ref_doc.from_date, + transaction_document_type=ref_doc.doctype, + transaction_document_name=ref_doc.name + ) + + if ref_doc.carry_forwarded_leaves: + expiry_days = frappe.db.get_value("Leave Type", ref_doc.leave_type, "carry_forward_leave_expiry") + + ledger.update(dict( + leaves=ref_doc.carry_forwarded_leaves * 1 if submit else -1, + to_date=add_days(ref_doc.from_date, expiry_days) if expiry_days else ref_doc.to_date, + is_carry_forward=1 + )) + frappe.get_doc(ledger).insert() + + ledger.update(dict( + leaves=ref_doc.new_leaves_allocated * 1 if submit else -1, + to_date=ref_doc.to_date, + is_carry_forward=0 + )) + frappe.get_doc(ledger).insert() \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py new file mode 100644 index 0000000000..6f7725c254 --- /dev/null +++ b/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestLeaveLedgerEntry(unittest.TestCase): + pass From 2b421c39b5c0dbf7ea8e5a9a567bd124b527af15 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 9 May 2019 19:36:01 +0530 Subject: [PATCH 017/117] feat: add transaction details in ledger --- .../leave_allocation/leave_allocation.json | 1491 ++++++++--------- .../leave_ledger_entry.json | 2 +- .../leave_ledger_entry/leave_ledger_entry.py | 11 +- 3 files changed, 741 insertions(+), 763 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 0903398d1f..12c74de9d0 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -1,757 +1,736 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2013-02-20 19:10:38", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fetch_if_empty": 0, - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 1, - "options": "HR-LAL-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "employee", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Employee", - "length": 0, - "no_copy": 0, - "oldfieldname": "employee", - "oldfieldtype": "Link", - "options": "Employee", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.employee_name", - "fetch_if_empty": 0, - "fieldname": "employee_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Employee Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "fetch_if_empty": 0, - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "leave_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Leave Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "leave_type", - "oldfieldtype": "Link", - "options": "Leave Type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "from_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "to_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allocation", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 1, - "collapsible": 0, - "columns": 0, - "depends_on": "eval: doc.carry_forward != 1", - "fetch_if_empty": 0, - "fieldname": "new_leaves_allocated", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "New Leaves Allocated", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval: doc.carry_forward == 1", - "fetch_if_empty": 0, - "fieldname": "carry_forwarded_leaves", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Unused Leaves", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fetch_if_empty": 0, - "fieldname": "carry_forward", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allocate unused leaves from previous allocations", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, - "fieldname": "total_leaves_allocated", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Leaves Allocated", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.total_leaves_encashed>0", - "fetch_if_empty": 0, - "fieldname": "total_leaves_encashed", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Leaves Encashed", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "compensatory_request", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Compensatory Leave Request", - "length": 0, - "no_copy": 0, - "options": "Compensatory Leave Request", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "leave_period", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Leave Period", - "length": 0, - "no_copy": 0, - "options": "Leave Period", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Leave Allocation", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "notes", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "description", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "reason", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "300px" - } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "icon": "fa fa-ok", - "idx": 1, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-04-22 18:33:15.858006", - "modified_by": "Administrator", - "module": "HR", - "name": "Leave Allocation", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "search_fields": "employee,employee_name,leave_type,total_leaves_allocated", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "employee", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "naming_series:", + "beta": 0, + "creation": "2013-02-20 19:10:38", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Series", + "length": 0, + "no_copy": 1, + "options": "HR-LAL-.YYYY.-", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Employee", + "length": 0, + "no_copy": 0, + "oldfieldname": "employee", + "oldfieldtype": "Link", + "options": "Employee", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Employee Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Department", + "length": 0, + "no_copy": 0, + "options": "Department", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break1", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, + "width": "50%" + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "leave_type", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Leave Type", + "length": 0, + "no_copy": 0, + "oldfieldname": "leave_type", + "oldfieldtype": "Link", + "options": "Leave Type", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "from_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "From Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "to_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "To Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Allocation", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "new_leaves_allocated", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "New Leaves Allocated", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", + "fieldname": "carry_forward", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Add unused leaves from previous allocations", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "carry_forward", + "fieldname": "carry_forwarded_leaves", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Unused leaves", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_leaves_allocated", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Leaves Allocated", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.total_leaves_encashed>0", + "fieldname": "total_leaves_encashed", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Leaves Encashed", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_10", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "compensatory_request", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Compensatory Leave Request", + "length": 0, + "no_copy": 0, + "options": "Compensatory Leave Request", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "leave_period", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Leave Period", + "length": 0, + "no_copy": 0, + "options": "Leave Period", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Amended From", + "length": 0, + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Leave Allocation", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "notes", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notes", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Description", + "length": 0, + "no_copy": 0, + "oldfieldname": "reason", + "oldfieldtype": "Small Text", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, + "width": "300px" + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-ok", + "idx": 1, + "image_view": 0, + "in_create": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-01-30 11:28:09.360525", + "modified_by": "Administrator", + "module": "HR", + "name": "Leave Allocation", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "employee,employee_name,leave_type,total_leaves_allocated", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "employee", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 + } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json index 8ef302452d..c7e6b709bd 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -75,7 +75,7 @@ } ], "is_submittable": 1, - "modified": "2019-05-09 15:54:52.834794", + "modified": "2019-05-09 18:36:07.383714", "modified_by": "Administrator", "module": "HR", "name": "Leave Ledger Entry", diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index dd8de56717..35dce97936 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -3,7 +3,7 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe from frappe.model.document import Document from frappe.utils import add_days @@ -17,10 +17,9 @@ def create_leave_ledger_entry(ref_doc, submit=True): employee_name=ref_doc.employee_name, leave_type=ref_doc.leave_type, from_date=ref_doc.from_date, - transaction_document_type=ref_doc.doctype, - transaction_document_name=ref_doc.name + transaction_type=ref_doc.doctype, + transaction_name=ref_doc.name ) - if ref_doc.carry_forwarded_leaves: expiry_days = frappe.db.get_value("Leave Type", ref_doc.leave_type, "carry_forward_leave_expiry") @@ -29,11 +28,11 @@ def create_leave_ledger_entry(ref_doc, submit=True): to_date=add_days(ref_doc.from_date, expiry_days) if expiry_days else ref_doc.to_date, is_carry_forward=1 )) - frappe.get_doc(ledger).insert() + frappe.get_doc(ledger).submit() ledger.update(dict( leaves=ref_doc.new_leaves_allocated * 1 if submit else -1, to_date=ref_doc.to_date, is_carry_forward=0 )) - frappe.get_doc(ledger).insert() \ No newline at end of file + frappe.get_doc(ledger).submit() \ No newline at end of file From 679371e397a840267d1e29b7d19bdcccf5598f5a Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 9 May 2019 19:43:35 +0530 Subject: [PATCH 018/117] feat: get carry forwarded leaves via ledger entries --- .../leave_allocation/leave_allocation.js | 19 +- .../leave_allocation/leave_allocation.json | 965 +++++------------- .../leave_allocation/leave_allocation.py | 159 +-- .../hr/doctype/leave_period/leave_period.py | 21 +- 4 files changed, 293 insertions(+), 871 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 0014c07a3c..228b5528de 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -34,33 +34,36 @@ frappe.ui.form.on("Leave Allocation", { }, carry_forwarded_leaves: function(frm) { - frm.set_value("total_leaves_allocated", flt(frm.doc.carry_forwarded_leaves)); + frm.set_value("total_leaves_allocated", + flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); }, new_leaves_allocated: function(frm) { - frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); + frm.set_value("total_leaves_allocated", + flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); }, calculate_total_leaves_allocated: function(frm) { - if (cint(frm.doc.carry_forward) === 1 && frm.doc.leave_type && frm.doc.employee) { + if (cint(frm.doc.carry_forward) == 1 && frm.doc.leave_type && frm.doc.employee) { return frappe.call({ method: "erpnext.hr.doctype.leave_allocation.leave_allocation.get_carry_forwarded_leaves", args: { "employee": frm.doc.employee, - "leave_type": frm.doc.leave_type, "date": frm.doc.from_date, + "leave_type": frm.doc.leave_type, + "carry_forward": frm.doc.carry_forward }, callback: function(r) { if (!r.exc && r.message) { - frm.set_value("new_leaves_allocated", 0); frm.set_value('carry_forwarded_leaves', r.message); - frm.set_value("total_leaves_allocated", flt(r.message)); + frm.set_value("total_leaves_allocated", + flt(r.message) + flt(frm.doc.new_leaves_allocated)); } } }) - } else if (cint(frm.doc.carry_forward) === 0) { + } else if (cint(frm.doc.carry_forward) == 0) { frm.set_value("carry_forwarded_leaves", 0); frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); } } -}) +}) \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 12c74de9d0..bb851c6850 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -1,736 +1,231 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2013-02-20 19:10:38", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 1, - "options": "HR-LAL-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Employee", - "length": 0, - "no_copy": 0, - "oldfieldname": "employee", - "oldfieldtype": "Link", - "options": "Employee", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Employee Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "leave_type", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Leave Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "leave_type", - "oldfieldtype": "Link", - "options": "Leave Type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allocation", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "new_leaves_allocated", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "New Leaves Allocated", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "carry_forward", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Add unused leaves from previous allocations", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "carry_forward", - "fieldname": "carry_forwarded_leaves", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Unused leaves", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_leaves_allocated", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Leaves Allocated", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.total_leaves_encashed>0", - "fieldname": "total_leaves_encashed", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Leaves Encashed", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "compensatory_request", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Compensatory Leave Request", - "length": 0, - "no_copy": 0, - "options": "Compensatory Leave Request", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "leave_period", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Leave Period", - "length": 0, - "no_copy": 0, - "options": "Leave Period", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Leave Allocation", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "notes", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "reason", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "300px" - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-ok", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-30 11:28:09.360525", - "modified_by": "Administrator", - "module": "HR", - "name": "Leave Allocation", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "employee,employee_name,leave_type,total_leaves_allocated", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "employee", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 - } \ No newline at end of file + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2013-02-20 19:10:38", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "naming_series", + "employee", + "employee_name", + "department", + "column_break1", + "leave_type", + "from_date", + "to_date", + "section_break_6", + "new_leaves_allocated", + "carry_forward", + "carry_forwarded_leaves", + "total_leaves_allocated", + "total_leaves_encashed", + "column_break_10", + "compensatory_request", + "leave_period", + "amended_from", + "notes", + "description" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "HR-LAL-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "employee", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Employee", + "oldfieldname": "employee", + "oldfieldtype": "Link", + "options": "Employee", + "reqd": 1, + "search_index": 1 + }, + { + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Employee Name", + "read_only": 1, + "search_index": 1 + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fieldname": "column_break1", + "fieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "leave_type", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Leave Type", + "oldfieldname": "leave_type", + "oldfieldtype": "Link", + "options": "Leave Type", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date", + "reqd": 1 + }, + { + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date", + "reqd": 1 + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "label": "Allocation" + }, + { + "allow_on_submit": 1, + "bold": 1, + "fieldname": "new_leaves_allocated", + "fieldtype": "Float", + "label": "New Leaves Allocated" + }, + { + "fieldname": "carry_forward", + "fieldtype": "Check", + "label": "Add unused leaves from previous allocations" + }, + { + "depends_on": "carry_forward", + "fieldname": "carry_forwarded_leaves", + "fieldtype": "Float", + "label": "Unused leaves", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "total_leaves_allocated", + "fieldtype": "Float", + "label": "Total Leaves Allocated", + "read_only": 1, + "reqd": 1 + }, + { + "depends_on": "eval:doc.total_leaves_encashed>0", + "fieldname": "total_leaves_encashed", + "fieldtype": "Float", + "label": "Total Leaves Encashed", + "read_only": 1 + }, + { + "fieldname": "column_break_10", + "fieldtype": "Column Break" + }, + { + "fieldname": "compensatory_request", + "fieldtype": "Link", + "label": "Compensatory Leave Request", + "options": "Compensatory Leave Request", + "read_only": 1 + }, + { + "fieldname": "leave_period", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Leave Period", + "options": "Leave Period", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Leave Allocation", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "notes", + "fieldtype": "Section Break", + "label": "Notes" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "oldfieldname": "reason", + "oldfieldtype": "Small Text", + "width": "300px" + } + ], + "icon": "fa fa-ok", + "idx": 1, + "is_submittable": 1, + "modified": "2019-05-09 19:06:33.659196", + "modified_by": "Administrator", + "module": "HR", + "name": "Leave Allocation", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "search_fields": "employee,employee_name,leave_type,total_leaves_allocated", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "employee" +} \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index f7db7dee86..aa1cc9ecad 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -8,6 +8,7 @@ from frappe import _ from frappe.model.document import Document from erpnext.hr.utils import set_employee_name, get_leave_period from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry class OverlapError(frappe.ValidationError): pass class BackDatedAllocationError(frappe.ValidationError): pass @@ -18,66 +19,62 @@ class ValueMultiplierError(frappe.ValidationError): pass class LeaveAllocation(Document): def validate(self): self.validate_period() - self.validate_lwp() - set_employee_name(self) - self.set_total_leaves_allocated() + self.validate_new_leaves_allocated_value() self.validate_allocation_overlap() self.validate_back_dated_allocation() + self.set_total_leaves_allocated() self.validate_total_leaves_allocated() - self.validate_leaves_allocated_value() + self.validate_lwp() + set_employee_name(self) self.validate_leave_allocation_days() def validate_leave_allocation_days(self): - new_leaves = self.new_leaves_allocated if not self.carry_forward else self.carry_forwarded_leaves - max_leaves, leaves_allocated = self.get_max_leaves_with_leaves_allocated_for_leave_type(flt(new_leaves)) + company = frappe.db.get_value("Employee", self.employee, "company") + leave_period = get_leave_period(self.from_date, self.to_date, company) + max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") + if max_leaves_allowed > 0: + leave_allocated = 0 + if leave_period: + leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date) + leave_allocated += self.new_leaves_allocated + if leave_allocated > max_leaves_allowed: + frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")\ + .format(self.leave_type, self.employee)) - if leaves_allocated > max_leaves: - frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")\ - .format(self.leave_type, self.employee)) + def on_submit(self): + create_leave_ledger_entry(self) + + def on_cancel(self): + create_leave_ledger_entry(self) def on_update_after_submit(self): self.validate_new_leaves_allocated_value() self.set_total_leaves_allocated() - frappe.db.set(self,'total_leaves_allocated', flt(self.total_leaves_allocated)) + frappe.db.set(self,'carry_forwarded_leaves', flt(self.carry_forwarded_leaves)) + frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated)) self.validate_against_leave_applications() def validate_period(self): - allocation_period = date_diff(self.to_date, self.from_date) - - if allocation_period <= 0: + if date_diff(self.to_date, self.from_date) <= 0: frappe.throw(_("To date cannot be before from date")) - # check if the allocation period is more than the expiry allows for carry forwarded allocation - if self.carry_forward: - expiry_days = get_days_to_expiry_for_leave_type(self.leave_type) - - if allocation_period > flt(expiry_days) and expiry_days: - frappe.throw(_("Leave allocation period cannot exceed carry forward expiry limit")) - def validate_lwp(self): if frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"): frappe.throw(_("Leave Type {0} cannot be allocated since it is leave without pay").format(self.leave_type)) - def validate_leaves_allocated_value(self): + def validate_new_leaves_allocated_value(self): """validate that leave allocation is in multiples of 0.5""" if flt(self.new_leaves_allocated) % 0.5: frappe.throw(_("Leaves must be allocated in multiples of 0.5"), ValueMultiplierError) def validate_allocation_overlap(self): leave_allocation = frappe.db.sql(""" - SELECT - name - FROM `tabLeave Allocation` - WHERE - employee=%s - AND leave_type=%s - AND docstatus=1 - AND carry_forward={0} - AND to_date >= %s - AND from_date <= %s""" #nosec - .format(self.carry_forward), (self.employee, self.leave_type, self.from_date, self.to_date)) + select name from `tabLeave Allocation` + where employee=%s and leave_type=%s and docstatus=1 + and to_date >= %s and from_date <= %s""", + (self.employee, self.leave_type, self.from_date, self.to_date)) if leave_allocation: frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} to {3}") @@ -97,20 +94,18 @@ class LeaveAllocation(Document): BackDatedAllocationError) def set_total_leaves_allocated(self): - if self.carry_forward: - self.set_carry_forwarded_leaves() - self.total_leaves_allocated = flt(self.carry_forwarded_leaves) - else: - self.total_leaves_allocated = flt(self.new_leaves_allocated) + self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, + self.leave_type, self.from_date, self.carry_forward) - if not self.total_leaves_allocated and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave")\ - and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"): + self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + flt(self.new_leaves_allocated) + + if not self.total_leaves_allocated and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave") and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"): frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}".format(self.leave_type))) def validate_total_leaves_allocated(self): # Adding a day to include To Date in the difference date_difference = date_diff(self.to_date, self.from_date) + 1 - if date_difference < flt(self.total_leaves_allocated): + if date_difference < self.total_leaves_allocated: frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError) def validate_against_leave_applications(self): @@ -123,43 +118,16 @@ class LeaveAllocation(Document): else: frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError) - def set_carry_forwarded_leaves(self): - self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, self.leave_type, self.from_date) - - max_leaves, leaves_allocated = self.get_max_leaves_with_leaves_allocated_for_leave_type(self.carry_forwarded_leaves) - - if leaves_allocated > max_leaves: - self.carry_forwarded_leaves = max_leaves - (leaves_allocated - self.carry_forwarded_leaves) - - def get_max_leaves_with_leaves_allocated_for_leave_type(self, new_leaves): - ''' compare new leaves allocated with max leaves ''' - company = frappe.db.get_value("Employee", self.employee, "company") - leaves_allocated = 0 - leave_period = get_leave_period(self.from_date, self.to_date, company) - max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") - if max_leaves_allowed > 0: - if leave_period: - leaves_allocated = get_leave_allocation_for_period(self.employee, self.leave_type, leave_period[0].from_date, leave_period[0].to_date) - leaves_allocated += new_leaves - return max_leaves_allowed, leaves_allocated - def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 leave_allocations = frappe.db.sql(""" - SELECT - employee, - leave_type, - from_date, - to_date, - total_leaves_allocated - FROM `tabLeave Allocation` - WHERE - employee=%(employee)s - AND leave_type=%(leave_type)s - AND docstatus=1 - AND (from_date BETWEEN %(from_date)s AND %(to_date)s - OR to_date BETWEEN %(from_date)s AND %(to_date)s - OR (from_date < %(from_date)s AND to_date > %(to_date)s)) + select employee, leave_type, from_date, to_date, total_leaves_allocated + from `tabLeave Allocation` + where employee=%(employee)s and leave_type=%(leave_type)s + and docstatus=1 + and (from_date between %(from_date)s and %(to_date)s + or to_date between %(from_date)s and %(to_date)s + or (from_date < %(from_date)s and to_date > %(to_date)s)) """, { "from_date": from_date, "to_date": to_date, @@ -174,43 +142,14 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): return leave_allocated @frappe.whitelist() -def get_carry_forwarded_leaves(employee, leave_type, date): - ''' Calculates carry forwarded days based on previous unused leave allocations ''' - carry_forwarded_leaves = 0 - expiry_days = get_days_to_expiry_for_leave_type(leave_type) - validate_carry_forward(leave_type) - filters = { - "employee": employee, - "leave_type": leave_type, - "docstatus": 1, - "to_date": ("<", date) - } - limit = 2 +def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): + leave_records = frappe.get_all("Leave Ledger Entry", + filters={'Employee':employee, + 'leave_type':leave_type, + 'to_date':("<=", date)}, + fields=['leaves']) - # check number of days to expire, ignore expiry for default value 0 - if expiry_days: - filters.update(carry_forward=0) - limit = 1 - - previous_allocation = frappe.get_all("Leave Allocation", - filters=filters, - fields=["name","from_date","to_date","total_leaves_allocated"], - order_by="to_date desc", - limit=limit) - - if previous_allocation: - leaves_taken = get_approved_leaves_for_period(employee, leave_type, - previous_allocation[0].from_date, previous_allocation[0].to_date) - - carry_forwarded_leaves = flt(previous_allocation[0].total_leaves_allocated) - flt(leaves_taken) - - return carry_forwarded_leaves - -def get_days_to_expiry_for_leave_type(leave_type): - ''' returns days to expiry for a provided leave type ''' - return frappe.db.get_value("Leave Type", - filters={"leave_type_name": leave_type, "is_carry_forward": 1}, - fieldname="carry_forward_leave_expiry") + return sum(record.get("leaves") for record in leave_records) def validate_carry_forward(leave_type): if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"): diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py index a6198a8918..91cc9b8340 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.py +++ b/erpnext/hr/doctype/leave_period/leave_period.py @@ -114,22 +114,7 @@ def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_ty allocation.leave_period = leave_period.name if carry_forward_leaves: if leave_type_details.get(leave_type).is_carry_forward: - create_carry_forward_leaves_allocation(employee, leave_type, leave_type_details, leave_period, carry_forward_leaves) - allocation.save(ignore_permissions=True) + allocation.carry_forward = carry_forward_leaves + allocation.save(ignore_permissions = True) allocation.submit() - return allocation.name - -def create_carry_forward_leaves_allocation(employee, leave_type, leave_type_details, leave_period, carry_forward_leaves): - carry_forwarded_leaves = get_carry_forwarded_leaves(employee, leave_type, leave_period.from_date) - if not carry_forwarded_leaves: - return - allocation = frappe.new_doc("Leave Allocation") - allocation.employee = employee - allocation.leave_type = leave_type - allocation.from_date = leave_period.from_date - allocation.to_date = add_days(leave_period.from_date, leave_type_details.carry_forward_leave_expiry) if not leave_type_details.carry_forward_leave_expiry else leave_period.to_date - allocation.leave_period = leave_period.name - allocation.carry_forward = carry_forward_leaves - allocation.carry_forwarded_leaves = carry_forwarded_leaves - allocation.save(ignore_permissions=True) - allocation.submit() \ No newline at end of file + return allocation.name \ No newline at end of file From 5ad83c06c225f5a8a8cb47f0f5b5dc412f988fe5 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 10 May 2019 19:50:04 +0530 Subject: [PATCH 019/117] feat: add ledger entries on leave addition --- .../leave_allocation/leave_allocation.py | 24 ++++++++++++++++--- .../leave_application/leave_application.py | 21 ++++++++++++++++ .../leave_ledger_entry/leave_ledger_entry.py | 19 +++------------ 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index aa1cc9ecad..5f474a4554 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -42,10 +42,10 @@ class LeaveAllocation(Document): .format(self.leave_type, self.employee)) def on_submit(self): - create_leave_ledger_entry(self) + self.create_leave_ledger_entry() - def on_cancel(self): - create_leave_ledger_entry(self) + # def before_cancel(self): + # self.create_leave_ledger_entry(submit=False) def on_update_after_submit(self): self.validate_new_leaves_allocated_value() @@ -118,6 +118,24 @@ class LeaveAllocation(Document): else: frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError) + def create_leave_ledger_entry(self, submit=True): + if self.carry_forwarded_leaves: + expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "carry_forward_leave_expiry") + + args = dict( + leaves=self.carry_forwarded_leaves * 1 if submit else -1, + to_date=add_days(self.from_date, expiry_days) if expiry_days else self.to_date, + is_carry_forward=1 + ) + create_leave_ledger_entry(self, args) + + args = dict( + leaves=self.new_leaves_allocated * 1 if submit else -1, + to_date=self.to_date, + is_carry_forward=0 + ) + create_leave_ledger_entry(self, args) + def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 leave_allocations = frappe.db.sql(""" diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index f542fa1e5c..61f617806f 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -10,6 +10,7 @@ from erpnext.hr.utils import set_employee_name, get_leave_period from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry class LeaveDayBlockedError(frappe.ValidationError): pass class OverlapError(frappe.ValidationError): pass @@ -50,6 +51,7 @@ class LeaveApplication(Document): # notify leave applier about approval self.notify_employee() + self.create_leave_ledger_entry() self.reload() def on_cancel(self): @@ -57,6 +59,7 @@ class LeaveApplication(Document): # notify leave applier about cancellation self.notify_employee() self.cancel_attendance() + self.create_leave_ledger_entry(submit=False) def validate_applicable_after(self): if self.leave_type: @@ -346,6 +349,14 @@ class LeaveApplication(Document): except frappe.OutgoingEmailError: pass + def create_leave_ledger_entry(self, submit=True): + args = dict( + leaves=self.total_leave_days * -1 if submit else 1, + to_date=self.to_date, + is_carry_forward=0 + ) + create_leave_ledger_entry(self, args) + @frappe.whitelist() def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None): number_of_days = 0 @@ -384,6 +395,16 @@ def get_leave_details(employee, date): return ret +@frappe.whitelist() +def get_leave_balance(employee, leave_type, date): + leave_records = frappe.get_all("Leave Ledger Entry", + filters={'Employee':employee, + 'leave_type':leave_type, + 'to_date':("<=", date)}, + fields=['leaves']) + + return sum(record.get("leaves") for record in leave_records) + @frappe.whitelist() def get_leave_balance_on(employee, leave_type, date, allocation_records=None, docname=None, consider_all_leaves_in_the_allocation_period=False, consider_encashed_leaves=True): diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 35dce97936..60d35957b9 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -10,7 +10,7 @@ from frappe.utils import add_days class LeaveLedgerEntry(Document): pass -def create_leave_ledger_entry(ref_doc, submit=True): +def create_leave_ledger_entry(ref_doc, args): ledger = dict( doctype='Leave Ledger Entry', employee=ref_doc.employee, @@ -20,19 +20,6 @@ def create_leave_ledger_entry(ref_doc, submit=True): transaction_type=ref_doc.doctype, transaction_name=ref_doc.name ) - if ref_doc.carry_forwarded_leaves: - expiry_days = frappe.db.get_value("Leave Type", ref_doc.leave_type, "carry_forward_leave_expiry") - ledger.update(dict( - leaves=ref_doc.carry_forwarded_leaves * 1 if submit else -1, - to_date=add_days(ref_doc.from_date, expiry_days) if expiry_days else ref_doc.to_date, - is_carry_forward=1 - )) - frappe.get_doc(ledger).submit() - - ledger.update(dict( - leaves=ref_doc.new_leaves_allocated * 1 if submit else -1, - to_date=ref_doc.to_date, - is_carry_forward=0 - )) - frappe.get_doc(ledger).submit() \ No newline at end of file + ledger.update(args) + frappe.get_doc(ledger).insert(ignore_permissions=True) \ No newline at end of file From 01490f1560e5fcdae2e6b8923f016b5c1c83ba68 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Sun, 12 May 2019 21:01:25 +0530 Subject: [PATCH 020/117] feat: add cancellation workflow for leave allocation ledger entry --- .../hr/doctype/leave_allocation/leave_allocation.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 5f474a4554..57fe9830f6 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -44,8 +44,8 @@ class LeaveAllocation(Document): def on_submit(self): self.create_leave_ledger_entry() - # def before_cancel(self): - # self.create_leave_ledger_entry(submit=False) + def on_cancel(self): + self.create_leave_ledger_entry(submit=False) def on_update_after_submit(self): self.validate_new_leaves_allocated_value() @@ -123,14 +123,14 @@ class LeaveAllocation(Document): expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "carry_forward_leave_expiry") args = dict( - leaves=self.carry_forwarded_leaves * 1 if submit else -1, + leaves=self.carry_forwarded_leaves, to_date=add_days(self.from_date, expiry_days) if expiry_days else self.to_date, is_carry_forward=1 ) - create_leave_ledger_entry(self, args) + create_leave_ledger_entry(self, args, submit) args = dict( - leaves=self.new_leaves_allocated * 1 if submit else -1, + leaves=self.new_leaves_allocated, to_date=self.to_date, is_carry_forward=0 ) From 5448edff2c9ec4be725c96d0d9b82c19dd651261 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Sun, 12 May 2019 21:02:05 +0530 Subject: [PATCH 021/117] feat: delete cancelled allocation from ledger --- .../leave_ledger_entry/leave_ledger_entry.py | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 60d35957b9..12be4794aa 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -5,13 +5,14 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe import _ from frappe.utils import add_days class LeaveLedgerEntry(Document): pass -def create_leave_ledger_entry(ref_doc, args): - ledger = dict( +def create_leave_ledger_entry(ref_doc, args, submit): + ledger = frappe._dict( doctype='Leave Ledger Entry', employee=ref_doc.employee, employee_name=ref_doc.employee_name, @@ -20,6 +21,27 @@ def create_leave_ledger_entry(ref_doc, args): transaction_type=ref_doc.doctype, transaction_name=ref_doc.name ) - ledger.update(args) - frappe.get_doc(ledger).insert(ignore_permissions=True) \ No newline at end of file + + if submit: + frappe.get_doc(ledger).insert(ignore_permissions=True) + else: + delete_ledger_entry(ledger) + +def delete_ledger_entry(ledger): + ''' Delete ledger entry on cancel of leave application/allocation ''' + ledger_entry, creation_date = frappe.db.get_value("Leave Ledger Entry", + {'transaction_name': ledger.transaction_name}, + ['name', 'creation'] + ) + leave_application_records = frappe.get_all("Leave Ledger Entry", + filters={ + 'transaction_type': 'Leave Application', + 'creation_date': (">", creation_date) + }, + fields=['transaction_type']) + if not leave_application_records: + frappe.delete_doc("Leave Ledger Entry", ledger_entry) + else: + frappe.throw(_("Leave allocation %s is linked with leave application %s" + % (ledger_entry, ', '.join(leave_application_records)))) \ No newline at end of file From 783bd89413b2fc7fa3572ef123fddafc17f3b531 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Sun, 12 May 2019 21:59:36 +0530 Subject: [PATCH 022/117] feat: handle cancellation workflow for leave application --- erpnext/hr/doctype/leave_application/leave_application.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 61f617806f..76a3af7ae9 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -350,12 +350,12 @@ class LeaveApplication(Document): pass def create_leave_ledger_entry(self, submit=True): - args = dict( - leaves=self.total_leave_days * -1 if submit else 1, + args = frappe._dict( + leaves=self.total_leave_days, to_date=self.to_date, is_carry_forward=0 ) - create_leave_ledger_entry(self, args) + create_leave_ledger_entry(self, args, submit) @frappe.whitelist() def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None): From cf8f4bda8fc4d9521a47168cad220034a3f42b15 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Sun, 12 May 2019 22:45:14 +0530 Subject: [PATCH 023/117] fix: skip application fetch for non allocation records --- .../leave_ledger_entry/leave_ledger_entry.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 12be4794aa..23258f1c77 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -34,12 +34,15 @@ def delete_ledger_entry(ledger): {'transaction_name': ledger.transaction_name}, ['name', 'creation'] ) - leave_application_records = frappe.get_all("Leave Ledger Entry", - filters={ - 'transaction_type': 'Leave Application', - 'creation_date': (">", creation_date) - }, - fields=['transaction_type']) + + leave_application_records = [] + if ledger.transaction_type == "Leave Allocation": + leave_application_records = frappe.get_all("Leave Ledger Entry", + filters={ + 'transaction_type': 'Leave Application', + 'creation_date': (">", creation_date) + }, + fields=['transaction_type']) if not leave_application_records: frappe.delete_doc("Leave Ledger Entry", ledger_entry) else: From 9bb4b8e8b29eec81af9c6599a6772ec714a0116a Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Sun, 12 May 2019 23:21:04 +0530 Subject: [PATCH 024/117] feat: create expiry ledger entry on allocation period completion --- erpnext/hooks.py | 1 + .../leave_ledger_entry/leave_ledger_entry.py | 29 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 53da013f8d..72c538fd3c 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -264,6 +264,7 @@ scheduler_events = { "erpnext.projects.doctype.project.project.update_project_sales_billing", "erpnext.projects.doctype.project.project.send_project_status_email_to_users", "erpnext.quality_management.doctype.quality_review.quality_review.review", + "erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.check_expired_allocation" "erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status" ], "daily_long": [ diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 23258f1c77..585e1079f7 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -6,12 +6,12 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ -from frappe.utils import add_days +from frappe.utils import add_days, today class LeaveLedgerEntry(Document): pass -def create_leave_ledger_entry(ref_doc, args, submit): +def create_leave_ledger_entry(ref_doc, args, submit=True): ledger = frappe._dict( doctype='Leave Ledger Entry', employee=ref_doc.employee, @@ -47,4 +47,27 @@ def delete_ledger_entry(ledger): frappe.delete_doc("Leave Ledger Entry", ledger_entry) else: frappe.throw(_("Leave allocation %s is linked with leave application %s" - % (ledger_entry, ', '.join(leave_application_records)))) \ No newline at end of file + % (ledger_entry, ', '.join(leave_application_records)))) + +def check_expired_allocation(): + ''' Checks for expired allocation by comparing to_date with current_date and + based on that creates an expiry ledger entry ''' + expired_allocation = frappe.db.get_all("Leave Ledger Allocation", + filters={ + 'to_date': today(), + 'transaction_type': 'Leave Allocation' + }, + fields=['name', 'transaction_name']) + + if expired_allocation: + create_expiry_ledger_entry(expired_allocation) + +def create_expiry_ledger_entry(expired_allocation): + for allocation in expired_allocation: + ledger_entry = frappe.get_doc('Leave Ledger Entry', allocation.name) + args = { + 'leaves': -ledger_entry.leaves, + 'to_date': '', + 'is_carry_forward': ledger_entry.is_carry_forward + } + create_leave_ledger_entry(ledger_entry, args) \ No newline at end of file From 170b8dded8cd5c9bb7309193d1260bbcb5435b3d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 13 May 2019 19:17:44 +0530 Subject: [PATCH 025/117] fix: expiry logic for carry forwarded allocation --- .../leave_ledger_entry.json | 8 +++- .../leave_ledger_entry/leave_ledger_entry.py | 45 ++++++++++++++----- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json index c7e6b709bd..89c703db65 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -12,6 +12,7 @@ "from_date", "to_date", "is_carry_forward", + "is_expired", "amended_from" ], "fields": [ @@ -72,10 +73,15 @@ "fieldname": "is_carry_forward", "fieldtype": "Check", "label": "Is Carry Forward" + }, + { + "fieldname": "is_expired", + "fieldtype": "Check", + "label": "Is Expired" } ], "is_submittable": 1, - "modified": "2019-05-09 18:36:07.383714", + "modified": "2019-05-13 12:56:45.542495", "modified_by": "Administrator", "module": "HR", "name": "Leave Ledger Entry", diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 585e1079f7..526a91e976 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -9,7 +9,10 @@ from frappe import _ from frappe.utils import add_days, today class LeaveLedgerEntry(Document): - pass + def validate_entries(self): + leave_records = frappe.get_all('Leave Ledger Entry', ['leaves']) + if sum(record.get("leaves") for record in leave_records) <0: + frappe.throw(_("Invalid Ledger Entry")) def create_leave_ledger_entry(ref_doc, args, submit=True): ledger = frappe._dict( @@ -19,7 +22,7 @@ def create_leave_ledger_entry(ref_doc, args, submit=True): leave_type=ref_doc.leave_type, from_date=ref_doc.from_date, transaction_type=ref_doc.doctype, - transaction_name=ref_doc.name + transaction_name=ref_doc.name, ) ledger.update(args) @@ -52,22 +55,42 @@ def delete_ledger_entry(ledger): def check_expired_allocation(): ''' Checks for expired allocation by comparing to_date with current_date and based on that creates an expiry ledger entry ''' - expired_allocation = frappe.db.get_all("Leave Ledger Allocation", + expired_allocation = frappe.get_all("Leave Ledger Allocation", filters={ 'to_date': today(), 'transaction_type': 'Leave Allocation' }, - fields=['name', 'transaction_name']) + fields=['*']) if expired_allocation: create_expiry_ledger_entry(expired_allocation) def create_expiry_ledger_entry(expired_allocation): for allocation in expired_allocation: - ledger_entry = frappe.get_doc('Leave Ledger Entry', allocation.name) - args = { - 'leaves': -ledger_entry.leaves, - 'to_date': '', - 'is_carry_forward': ledger_entry.is_carry_forward - } - create_leave_ledger_entry(ledger_entry, args) \ No newline at end of file + filters = { + 'employee': allocation.employee, + 'leave_type': allocation.leave_type, + 'from_date': ('>=', allocation.from_date), + } + # get only application ledger entries in case of carry forward + if allocation.is_carry_forward: + filters.update(dict(transaction_type='Leave Application')) + + leave_records = frappe.get_all("Leave Ledger Entry", + filters=filters, + fields=['leaves']) + + leaves = sum(record.get("leaves") for record in leave_records) + + if allocation.is_carry_forward: + leaves = allocation.leaves + leaves + + if leaves > 0: + args = frappe._dict( + leaves=allocation.leaves * -1, + to_date='', + is_carry_forward=allocation.is_carry_forward, + is_expired=1, + from_date=allocation.to_date + ) + create_leave_ledger_entry(allocation, args) \ No newline at end of file From 201aeeb20d6286e9ed8eaea25500889626be8767 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 13 May 2019 19:19:02 +0530 Subject: [PATCH 026/117] test: creation of ledger entries on allocation submit --- .../leave_allocation/test_leave_allocation.py | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 325f3a7299..a0113e2d9f 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -68,27 +68,22 @@ class TestLeaveAllocation(unittest.TestCase): #allocated leave more than period self.assertRaises(frappe.ValidationError, doc.save) - def test_carry_forward_allocation(self): + def test_creation_of_leave_ledger_entry_on_submit(self): frappe.db.sql("delete from `tabLeave Allocation`") - leave_type = create_leave_type( - leave_type_name="_Test Carry Forward", - is_carry_forward=1, - carry_forward_leave_expiry=366) - leave_type.submit() - - leave_allocation = create_leave_allocation( - from_date=add_months(nowdate(), -12), - to_date=add_days(nowdate(), -1), - leave_type=leave_type - ) - leave_allocation.new_leaves_allocated = 10 + leave_allocation = create_leave_allocation() leave_allocation.submit() - carry_forward_alloc = create_leave_allocation(leave_type=leave_type) - carry_forward_alloc.carry_forward = 1 - carry_forward_alloc.save() - self.assertEquals(carry_forward_alloc.total_leaves_allocated, 10) + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name)) + + self.assertEquals(len(leave_ledger_entry), 1) + self.assertEquals(leave_ledger_entry[0].employee, leave_allocation.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, leave_allocation.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated) + + # check if leave ledger entry is deleted on cancellation + leave_allocation.cancel() + self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_allocation.name})) def create_leave_allocation(**args): args = frappe._dict(args) @@ -101,6 +96,7 @@ def create_leave_allocation(**args): "employee_name": employee.employee_name, "leave_type": args.leave_type.leave_type_name or "_Test Leave Type", "from_date": args.from_date or nowdate(), + "new_leaves_allocated": args.new_leaves_created or 15, "to_date": args.to_date or add_months(nowdate(), 12) }) return leave_allocation From 964deaca96e7a4ea5fc8f2eea03297a70720b5f6 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 13 May 2019 19:19:39 +0530 Subject: [PATCH 027/117] test: creation of ledger entries on application submit --- .../test_leave_application.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index d3dcca1da0..2581c2a659 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -457,6 +457,28 @@ class TestLeaveApplication(unittest.TestCase): leave_application.submit() self.assertEqual(leave_application.docstatus, 1) + def test_creation_of_leave_ledger_entry_on_submit(self): + + leave_application = frappe.get_doc(dict( + doctype = 'Leave Application', + employee = employee.name, + leave_type = leave_type_1.name, + from_date = nowdate(), + to_date = add_days(nowdate(), 4), + company = "_Test Company", + docstatus = 1, + status = "Approved" + )).submit() + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name)) + + self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, leave_application.new_leaves_allocated) + + # check if leave ledger entry is deleted on cancellation + leave_application.cancel() + self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_application.name})) + def make_allocation_record(employee=None, leave_type=None): frappe.db.sql("delete from `tabLeave Allocation`") @@ -513,4 +535,4 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el "docstatus": 1 }).insert() - allocate_leave.submit() + allocate_leave.submit() \ No newline at end of file From 6ba9a128e7fd95c2b2ebea29c36738550209c666 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 14 May 2019 11:23:10 +0530 Subject: [PATCH 028/117] feat: calculate leave balance using ledger entries --- .../leave_application/leave_application.py | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 76a3af7ae9..70d7aa4b3a 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -197,6 +197,7 @@ class LeaveApplication(Document): if not is_lwp(self.leave_type): self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, docname=self.name, consider_all_leaves_in_the_allocation_period=True) + if self.status != "Rejected" and self.leave_balance < self.total_leave_days: if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"): frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}") @@ -350,6 +351,13 @@ class LeaveApplication(Document): pass def create_leave_ledger_entry(self, submit=True): + # check if there is a carry forwarded allocation expiry between application + allocation = frappe.db.get_value('Leave Ledger Entry', + filters={ + 'transaction_type': "Leave Allocation", + 'is_carry_forward': 1, + 'from_date': self.to_date + }) args = frappe._dict( leaves=self.total_leave_days, to_date=self.to_date, @@ -396,31 +404,38 @@ def get_leave_details(employee, date): return ret @frappe.whitelist() -def get_leave_balance(employee, leave_type, date): +def get_leave_balance_on(employee, leave_type, date, allocation_records=None, consider_all_leaves_in_the_allocation_period=False): + + if allocation_records: + # leave_balance based on current allocation records + allocation = allocation_records.get(leave_type, frappe._dict()) + from_date = allocation.from_date + to_date = allocation.to_date + else: + # fetched the expired allocation creation date + from_date, to_date = frappe.db.get_value('Leave Ledger Entry', + filters={ + 'transaction_type': 'Leave Allocation', + 'employee': employee, + 'leave_type': leave_type, + 'is_expired': 0, + 'is_carry_forward': 0, + }, + fieldname=['from_date', 'to_date'], + order_by='to_date DESC') + + if consider_all_leaves_in_the_allocation_period: + date = to_date + leave_records = frappe.get_all("Leave Ledger Entry", - filters={'Employee':employee, - 'leave_type':leave_type, - 'to_date':("<=", date)}, - fields=['leaves']) + filters={'Employee':employee, + 'leave_type':leave_type, + 'to_date':("<=", date), + 'from_date': from_date}, + fields=['leaves']) return sum(record.get("leaves") for record in leave_records) -@frappe.whitelist() -def get_leave_balance_on(employee, leave_type, date, allocation_records=None, docname=None, - consider_all_leaves_in_the_allocation_period=False, consider_encashed_leaves=True): - - if allocation_records == None: - allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict()) - allocation = allocation_records.get(leave_type, frappe._dict()) - if consider_all_leaves_in_the_allocation_period: - date = allocation.to_date - leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, date, status="Approved", docname=docname) - leaves_encashed = 0 - if frappe.db.get_value("Leave Type", leave_type, 'allow_encashment') and consider_encashed_leaves: - leaves_encashed = flt(allocation.total_leaves_encashed) - - return flt(allocation.total_leaves_allocated) - (flt(leaves_taken) + flt(leaves_encashed)) - def get_total_allocated_leaves(employee, leave_type, date): filters= { 'from_date': ['<=', date], From 50037f86097ab5f744772041026c0ca172d10a34 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 15 May 2019 21:49:27 +0530 Subject: [PATCH 029/117] fix: consider min days remaining as remaining leaves --- .../leave_application/leave_application.py | 67 +++++++++---------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 70d7aa4b3a..9e0ed0f1b5 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -351,15 +351,9 @@ class LeaveApplication(Document): pass def create_leave_ledger_entry(self, submit=True): - # check if there is a carry forwarded allocation expiry between application - allocation = frappe.db.get_value('Leave Ledger Entry', - filters={ - 'transaction_type': "Leave Allocation", - 'is_carry_forward': 1, - 'from_date': self.to_date - }) args = frappe._dict( leaves=self.total_leave_days, + from_date=self.from_date, to_date=self.to_date, is_carry_forward=0 ) @@ -403,38 +397,37 @@ def get_leave_details(employee, date): return ret -@frappe.whitelist() -def get_leave_balance_on(employee, leave_type, date, allocation_records=None, consider_all_leaves_in_the_allocation_period=False): - - if allocation_records: - # leave_balance based on current allocation records - allocation = allocation_records.get(leave_type, frappe._dict()) - from_date = allocation.from_date - to_date = allocation.to_date - else: - # fetched the expired allocation creation date - from_date, to_date = frappe.db.get_value('Leave Ledger Entry', - filters={ - 'transaction_type': 'Leave Allocation', - 'employee': employee, - 'leave_type': leave_type, - 'is_expired': 0, - 'is_carry_forward': 0, - }, - fieldname=['from_date', 'to_date'], - order_by='to_date DESC') +def get_leave_balance_on(employee, leave_type, date, allocation_records=None, docname=None, + consider_all_leaves_in_the_allocation_period=False, consider_encashed_leaves=True): + if allocation_records == None: + allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict()) + allocation = allocation_records.get(leave_type, frappe._dict()) if consider_all_leaves_in_the_allocation_period: - date = to_date - - leave_records = frappe.get_all("Leave Ledger Entry", - filters={'Employee':employee, + date = allocation.to_date + leaves_taken = frappe.db.get_value("Leave Ledger Entry", filters={ + 'Employee':employee, 'leave_type':leave_type, + 'transaction_type': ('!=', 'Leave Allocation'), 'to_date':("<=", date), 'from_date': from_date}, - fields=['leaves']) + fields=['SUM(leaves)']) - return sum(record.get("leaves") for record in leave_records) + remaining_unused_leaves = flt(allocation.unused_leaves + leaves_taken) + if allocation.carry_forward and remaining_unused_leaves > 0: + expiry_date = frappe.db.get_value("Leave Ledger Entry", filters={ + 'Employee':employee, + 'leave_type':leave_type, + 'transaction_type': 'Leave Allocation', + 'transaction_name': allocation.name, + 'is_carry_forward': 1, + 'is_expiry': 0 + }, fieldname=['to_date']) + remaining_days = date_diff(expiry_date, date) + 1 + + remaining_unused_leaves = min(remaining_days, remaining_unused_leaves) + + return flt(allocation.new_leaves) + remaining_unused_leaves + leaves_taken def get_total_allocated_leaves(employee, leave_type, date): filters= { @@ -495,8 +488,10 @@ def get_leave_allocation_records(date, employee=None): allocated_leaves.setdefault(d.employee, frappe._dict()).setdefault(d.leave_type, frappe._dict({ "from_date": d.from_date, "to_date": d.to_date, - "total_leaves_allocated": d.total_leaves_allocated, - "total_leaves_encashed":d.total_leaves_encashed + "carry_forward": d.carry_forward, + "unused_leaves": d.carry_forwarded_leaves, + "new_leaves": d.new_leaves_allocated, + "name": d.name })) return allocated_leaves @@ -677,4 +672,4 @@ def get_leave_approver(employee, department=None): if department: return frappe.db.get_value('Department Approver', {'parent': department, - 'parentfield': 'leave_approvers', 'idx': 1}, 'approver') + 'parentfield': 'leave_approvers', 'idx': 1}, 'approver') \ No newline at end of file From f8f02c508d5122b3af0fc1e4eb829b3ba2a3e995 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 15 May 2019 21:50:06 +0530 Subject: [PATCH 030/117] feat: create leave ledger entry on leave encashment creation --- .../hr/doctype/leave_encashment/leave_encashment.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index 9944bc5380..4f4a8760cf 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -10,6 +10,7 @@ from frappe.utils import getdate, nowdate, flt from erpnext.hr.utils import set_employee_name from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry class LeaveEncashment(Document): def validate(self): @@ -40,6 +41,8 @@ class LeaveEncashment(Document): frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed", frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') + self.encashable_days) + self.create_leave_ledger_entry() + def on_cancel(self): if self.additional_salary: frappe.get_doc("Additional Salary", self.additional_salary).cancel() @@ -48,6 +51,7 @@ class LeaveEncashment(Document): if self.leave_allocation: frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed", frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') - self.encashable_days) + self.create_leave_ledger_entry(submit=False) def get_leave_details_for_encashment(self): salary_structure = get_assigned_salary_structure(self.employee, self.encashment_date or getdate(nowdate())) @@ -75,3 +79,11 @@ class LeaveEncashment(Document): and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee)) return leave_allocation[0][0] if leave_allocation else None + + def create_leave_ledger_entry(self, submit=True): + args = frappe._dict( + leaves=self.encashable_days * -1, + from_date=self.encashment_date, + is_carry_forward=0 + ) + create_leave_ledger_entry(self, args, submit) \ No newline at end of file From afb0c4aa431f55415397c6c8b6fe2c23eb0d5979 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 15 May 2019 21:50:34 +0530 Subject: [PATCH 031/117] fix: delete entry on cancellation of transaction --- .../leave_ledger_entry/leave_ledger_entry.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 526a91e976..1c8fed3d7e 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -20,34 +20,36 @@ def create_leave_ledger_entry(ref_doc, args, submit=True): employee=ref_doc.employee, employee_name=ref_doc.employee_name, leave_type=ref_doc.leave_type, - from_date=ref_doc.from_date, transaction_type=ref_doc.doctype, transaction_name=ref_doc.name, ) ledger.update(args) - if submit: - frappe.get_doc(ledger).insert(ignore_permissions=True) + frappe.get_doc(ledger).submit() else: delete_ledger_entry(ledger) def delete_ledger_entry(ledger): - ''' Delete ledger entry on cancel of leave application/allocation ''' - ledger_entry, creation_date = frappe.db.get_value("Leave Ledger Entry", - {'transaction_name': ledger.transaction_name}, - ['name', 'creation'] - ) + ''' Delete ledger entry on cancel of leave application/allocation/encashment ''' leave_application_records = [] + # prevent deletion when leave application has been created after allocation if ledger.transaction_type == "Leave Allocation": leave_application_records = frappe.get_all("Leave Ledger Entry", filters={ + 'employee': ledger.employee, + 'leave_type': ledger.leave_type, 'transaction_type': 'Leave Application', - 'creation_date': (">", creation_date) + 'from_date': (">=", ledger.from_date), + 'to_date': ('<=', ledger.to_date) }, - fields=['transaction_type']) + fields=['transaction_name']) + if not leave_application_records: - frappe.delete_doc("Leave Ledger Entry", ledger_entry) + frappe.db.sql("""DELETE + FROM `tabLeave Ledger Entry` + WHERE + `transaction_name`=%s""", (ledger.transaction_name)) else: frappe.throw(_("Leave allocation %s is linked with leave application %s" % (ledger_entry, ', '.join(leave_application_records)))) From 5ba17c87e53bf0b3a99fb25690c19ddebf5415e4 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 22 May 2019 18:29:22 +0530 Subject: [PATCH 032/117] feat: remove update allocation after submit --- .../leave_allocation/leave_allocation.json | 591 ++++++++++++++++-- .../leave_allocation/leave_allocation.py | 55 +- 2 files changed, 577 insertions(+), 69 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index bb851c6850..568182d3b7 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -1,192 +1,683 @@ { + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, "allow_import": 1, + "allow_rename": 0, "autoname": "naming_series:", + "beta": 0, "creation": "2013-02-20 19:10:38", + "custom": 0, + "docstatus": 0, "doctype": "DocType", "document_type": "Setup", + "editable_grid": 0, "engine": "InnoDB", - "field_order": [ - "naming_series", - "employee", - "employee_name", - "department", - "column_break1", - "leave_type", - "from_date", - "to_date", - "section_break_6", - "new_leaves_allocated", - "carry_forward", - "carry_forwarded_leaves", - "total_leaves_allocated", - "total_leaves_encashed", - "column_break_10", - "compensatory_request", - "leave_period", - "amended_from", - "notes", - "description" - ], "fields": [ { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", "fieldname": "naming_series", "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, "label": "Series", + "length": 0, "no_copy": 1, "options": "HR-LAL-.YYYY.-", + "permlevel": 0, + "precision": "", "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, "reqd": 1, - "set_only_once": 1 + "search_index": 0, + "set_only_once": 1, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "employee", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, "in_global_search": 1, "in_list_view": 1, "in_standard_filter": 1, "label": "Employee", + "length": 0, + "no_copy": 0, "oldfieldname": "employee", "oldfieldtype": "Link", "options": "Employee", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, "reqd": 1, - "search_index": 1 + "search_index": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fetch_from": "employee.employee_name", "fieldname": "employee_name", "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, "in_global_search": 1, "in_list_view": 1, + "in_standard_filter": 0, "label": "Employee Name", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, - "search_index": 1 + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fetch_from": "employee.department", "fieldname": "department", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, "label": "Department", + "length": 0, + "no_copy": 0, "options": "Department", - "read_only": 1 + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "column_break1", "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "50%" }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "leave_type", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Leave Type", + "length": 0, + "no_copy": 0, "oldfieldname": "leave_type", "oldfieldtype": "Link", "options": "Leave Type", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, "reqd": 1, - "search_index": 1 + "search_index": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "from_date", "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, "label": "From Date", - "reqd": 1 + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "to_date", "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, "label": "To Date", - "reqd": 1 + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "section_break_6", "fieldtype": "Section Break", - "label": "Allocation" + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Allocation", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { - "allow_on_submit": 1, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, "bold": 1, + "collapsible": 0, + "columns": 0, "fieldname": "new_leaves_allocated", "fieldtype": "Float", - "label": "New Leaves Allocated" + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "New Leaves Allocated", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "", "fieldname": "carry_forward", "fieldtype": "Check", - "label": "Add unused leaves from previous allocations" + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Add unused leaves from previous allocations", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "depends_on": "carry_forward", "fieldname": "carry_forwarded_leaves", "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, "label": "Unused leaves", - "read_only": 1 + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "total_leaves_allocated", "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, "label": "Total Leaves Allocated", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, "read_only": 1, - "reqd": 1 + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "depends_on": "eval:doc.total_leaves_encashed>0", "fieldname": "total_leaves_encashed", "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, "label": "Total Leaves Encashed", - "read_only": 1 + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "column_break_10", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "compensatory_request", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, "label": "Compensatory Leave Request", + "length": 0, + "no_copy": 0, "options": "Compensatory Leave Request", - "read_only": 1 + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "leave_period", "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, "in_standard_filter": 1, "label": "Leave Period", + "length": 0, + "no_copy": 0, "options": "Leave Period", - "read_only": 1 + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "amended_from", "fieldtype": "Link", + "hidden": 0, "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, "label": "Amended From", + "length": 0, "no_copy": 1, "oldfieldname": "amended_from", "oldfieldtype": "Data", "options": "Leave Allocation", + "permlevel": 0, "print_hide": 1, - "read_only": 1 + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, "collapsible": 1, + "columns": 0, "fieldname": "notes", "fieldtype": "Section Break", - "label": "Notes" + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Notes", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, "fieldname": "description", "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, "label": "Description", + "length": 0, + "no_copy": 0, "oldfieldname": "reason", "oldfieldtype": "Small Text", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0, "width": "300px" } ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, "icon": "fa fa-ok", "idx": 1, + "image_view": 0, + "in_create": 0, "is_submittable": 1, - "modified": "2019-05-09 19:06:33.659196", + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-05-22 11:28:09.360525", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", @@ -198,10 +689,15 @@ "create": 1, "delete": 1, "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "HR User", + "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 @@ -213,19 +709,28 @@ "delete": 1, "email": 1, "export": 1, + "if_owner": 0, "import": 1, + "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "HR Manager", + "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 } ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, "search_fields": "employee,employee_name,leave_type,total_leaves_allocated", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "timeline_field": "employee" + "timeline_field": "employee", + "track_changes": 0, + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 57fe9830f6..8429ad4308 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -47,15 +47,6 @@ class LeaveAllocation(Document): def on_cancel(self): self.create_leave_ledger_entry(submit=False) - def on_update_after_submit(self): - self.validate_new_leaves_allocated_value() - self.set_total_leaves_allocated() - - frappe.db.set(self,'carry_forwarded_leaves', flt(self.carry_forwarded_leaves)) - frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated)) - - self.validate_against_leave_applications() - def validate_period(self): if date_diff(self.to_date, self.from_date) <= 0: frappe.throw(_("To date cannot be before from date")) @@ -108,16 +99,6 @@ class LeaveAllocation(Document): if date_difference < self.total_leaves_allocated: frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError) - def validate_against_leave_applications(self): - leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type, - self.from_date, self.to_date) - - if flt(leaves_taken) > flt(self.total_leaves_allocated): - if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"): - frappe.msgprint(_("Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken)) - else: - frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError) - def create_leave_ledger_entry(self, submit=True): if self.carry_forwarded_leaves: expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "carry_forward_leave_expiry") @@ -134,7 +115,7 @@ class LeaveAllocation(Document): to_date=self.to_date, is_carry_forward=0 ) - create_leave_ledger_entry(self, args) + create_leave_ledger_entry(self, args, submit) def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 @@ -161,13 +142,35 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): @frappe.whitelist() def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): - leave_records = frappe.get_all("Leave Ledger Entry", - filters={'Employee':employee, - 'leave_type':leave_type, - 'to_date':("<=", date)}, - fields=['leaves']) + carry_forwarded_leaves = 0 - return sum(record.get("leaves") for record in leave_records) + if carry_forward: + validate_carry_forward(leave_type) + + carry_forwarded_leaves = frappe.db.sql(""" + SELECT + SUM(leaves) + FROM `tabLeave Ledger Entry` + WHERE + employee=%s + AND leave_type=%s + AND docstatus=1 + AND to_date < %s + AND name NOT IN ( + SELECT name + from `tabLeave Ledger Entry` + WHERE + employee=%s + AND leave_type=%s + AND docstatus=1 + AND to_date < %s + is_expired=1 + ORDER BY creation DESC + ) + ORDER BY creation DESC + """, (employee, leave_type, date), as_dict=1) + + return carry_forwarded_leaves def validate_carry_forward(leave_type): if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"): From 8ef81870bb1f282caecc51a2de7c2c0ba9bf204d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 23 May 2019 20:28:16 +0530 Subject: [PATCH 033/117] test: create leave ledger entry for encashment --- .../leave_encashment/test_leave_encashment.py | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index ef5c2aa6f3..cfed780eb7 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -9,41 +9,39 @@ from frappe.utils import today, add_months from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period +from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy\ test_dependencies = ["Leave Type"] class TestLeaveEncashment(unittest.TestCase): def setUp(self): frappe.db.sql('''delete from `tabLeave Period`''') - def test_leave_balance_value_and_amount(self): - employee = "test_employee_encashment@salary.com" - leave_type = "_Test Leave Type Encashment" + frappe.db.sql('''delete from `tabLeave Allocation`''') # create the leave policy - leave_policy = frappe.get_doc({ - "doctype": "Leave Policy", - "leave_policy_details": [{ - "leave_type": leave_type, - "annual_allocation": 10 - }] - }).insert() + leave_policy = create_leave_policy( + leave_type="_Test Leave Type Encashment", + annual_allocation=10) leave_policy.submit() # create employee, salary structure and assignment - employee = make_employee(employee) - frappe.db.set_value("Employee", employee, "leave_policy", leave_policy.name) - salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", employee, + self.employee = make_employee("test_employee_encashment@example.com") + frappe.db.set_value("Employee", "test_employee_encashment@example.com", "leave_policy", leave_policy.name) + salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee, other_details={"leave_encashment_amount_per_day": 50}) # create the leave period and assign the leaves - leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3)) - leave_period.grant_leave_allocation(employee=employee) + self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3)) + self.leave_period.grant_leave_allocation(employee=self.employee) + + def test_leave_balance_value_and_amount(self): + frappe.db.sql('''delete from `tabLeave Encashment`''') leave_encashment = frappe.get_doc(dict( doctype = 'Leave Encashment', - employee = employee, - leave_type = leave_type, - leave_period = leave_period.name, + employee = self.employee, + leave_type = "_Test Leave Type Encashment", + leave_period = self.leave_period.name, payroll_date = today() )).insert() @@ -53,3 +51,26 @@ class TestLeaveEncashment(unittest.TestCase): leave_encashment.submit() self.assertTrue(frappe.db.get_value("Leave Encashment", leave_encashment.name, "additional_salary")) + + def test_creation_of_leave_ledger_entry_on_submit(self): + frappe.db.sql('''delete from `tabLeave Encashment`''') + leave_encashment = frappe.get_doc(dict( + doctype = 'Leave Encashment', + employee = self.employee, + leave_type = "_Test Leave Type Encashment", + leave_period = self.leave_period.name, + payroll_date = today() + )).insert() + + leave_encashment.submit() + + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name)) + + self.assertEquals(len(leave_ledger_entry), 1) + self.assertEquals(leave_ledger_entry[0].employee, leave_encashment.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, leave_encashment.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1) + + # check if leave ledger entry is deleted on cancellation + leave_encashment.cancel() + self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_encashment.name})) From 45cf02308e8ef0effb3260d340988b95b342f5a2 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 23 May 2019 20:29:18 +0530 Subject: [PATCH 034/117] fix: prevent manual creation of ledger entries --- .../leave_ledger_entry.json | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json index 89c703db65..ae36735570 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -19,6 +19,7 @@ { "fieldname": "employee", "fieldtype": "Link", + "in_list_view": 1, "label": "Employee", "options": "Employee" }, @@ -30,6 +31,7 @@ { "fieldname": "leave_type", "fieldtype": "Link", + "in_list_view": 1, "label": "Leave Type", "options": "Leave Type" }, @@ -57,16 +59,19 @@ { "fieldname": "leaves", "fieldtype": "Int", + "in_list_view": 1, "label": "Leaves" }, { "fieldname": "from_date", "fieldtype": "Date", + "in_list_view": 1, "label": "From Date" }, { "fieldname": "to_date", "fieldtype": "Date", + "in_list_view": 1, "label": "To Date" }, { @@ -80,8 +85,9 @@ "label": "Is Expired" } ], + "in_create": 1, "is_submittable": 1, - "modified": "2019-05-13 12:56:45.542495", + "modified": "2019-05-23 19:22:09.028366", "modified_by": "Administrator", "module": "HR", "name": "Leave Ledger Entry", @@ -97,6 +103,47 @@ "report": 1, "role": "System Manager", "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, + "submit": 1, "write": 1 } ], From f3926d0fcbd57e7e3c59f33b2992a699ce3902a7 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Sun, 26 May 2019 20:17:16 +0530 Subject: [PATCH 035/117] patch: leave ledger entries --- erpnext/patches.txt | 1 + .../v12_0/generate_leave_ledger_entries.py | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 erpnext/patches/v12_0/generate_leave_ledger_entries.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index bdc1ed4f10..a7fb7a2bb5 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -602,3 +602,4 @@ erpnext.patches.v11_1.set_salary_details_submittable erpnext.patches.v11_1.rename_depends_on_lwp execute:frappe.delete_doc("Report", "Inactive Items") erpnext.patches.v11_1.delete_scheduling_tool +erpnext.patches.v12_0.generate_leave_ledger_entries \ No newline at end of file diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py new file mode 100644 index 0000000000..9223bb2ebc --- /dev/null +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -0,0 +1,95 @@ +# Copyright (c) 2018, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + """ Generates leave ledger entries for leave allocation/application/encashment + for last allocation """ + if frappe.db.exists("DocType","Leave Ledger Entry"): + return + + allocation_list = get_allocation_records() + generate_allocation_ledger_entries(allocation_list) + generate_application_leave_ledger_entries(allocation_list) + generate_encashment_leave_ledger_entries(allocation_list) + +def generate_allocation_ledger_entries(allocation_list): + ''' fix ledger entries for missing leave allocation transaction ''' + from erpnext.hr.doctype.leave_allocation.leave_allocation import LeaveAllocation + + for allocation in allocation_list: + LeaveAllocation.create_leave_ledger_entry(allocation) + +def generate_application_leave_ledger_entries(allocation_list): + ''' fix ledger entries for missing leave application transaction ''' + from erpnext.hr.doctype.leave_application.leave_application import LeaveApplication + + leave_applications = get_leaves_application_records(allocation_list) + + for record in leave_applications: + LeaveApplication.create_leave_ledger_entry(record) + +def generate_encashment_leave_ledger_entries(allocation_list): + ''' fix ledger entries for missing leave encashment transaction ''' + from erpnext.hr.doctype.leave_encashment.leave_encashment import LeaveEncashment + + leave_encashments = get_leave_encashment_records(allocation_list) + + for record in leave_encashments: + LeaveEncashment.create_leave_ledger_entry(record) + +def get_allocation_records(): + return frappe.db.sql(""" + SELECT + DISTINCT name, + employee, + leave_type, + new_leaves_allocated, + carry_forwarded_leaves, + from_date, + to_date, + carry_forward, + RANK() OVER(PARTITION BY employee, leave_type ORDER BY to_date DESC) as allocation + FROM `tabLeave Allocation` + WHERE + allocation=1 + """, as_dict=1) + +def get_leaves_application_records(allocation_list): + leave_applications = [] + for allocation in allocation_list: + leave_applications.append(frappe.db.sql(""" + SELECT + name, + employee, + leave_type, + total_leave_days, + from_date, + to_date + FROM `tabLeave Application` + WHERE + from_date >= %s + leave_type = %s + employee = %s + """, (allocation.from_date, allocation.leave_type, allocation.employee))) + return leave_applications + +def get_leave_encashment_records(allocation_list): + leave_encashments = [] + for allocation in allocation_list: + leave_encashments.append(frappe.db.sql(""" + SELECT + DISTINCT name, + employee, + leave_type, + encashable_days, + from_date, + to_date + FROM `tabLeave Encashment` + WHERE + leave_type = %s + employee = %s + """, (allocation.leave_type, allocation.employee))) + return leave_encashments \ No newline at end of file From 9e3b6883332ac26d6aca89c147e1cda233709960 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 03:10:39 +0530 Subject: [PATCH 036/117] test: create leave policy --- .../doctype/leave_policy/test_leave_policy.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.py b/erpnext/hr/doctype/leave_policy/test_leave_policy.py index 2c6f1d0a21..fc868ea15a 100644 --- a/erpnext/hr/doctype/leave_policy/test_leave_policy.py +++ b/erpnext/hr/doctype/leave_policy/test_leave_policy.py @@ -12,16 +12,20 @@ class TestLeavePolicy(unittest.TestCase): if random_leave_type: random_leave_type = random_leave_type[0] leave_type = frappe.get_doc("Leave Type", random_leave_type.name) - old_max_leaves_allowed = leave_type.max_leaves_allowed leave_type.max_leaves_allowed = 2 leave_type.save() - leave_policy_details = { - "doctype": "Leave Policy", - "leave_policy_details": [{ - "leave_type": leave_type.name, - "annual_allocation": leave_type.max_leaves_allowed + 1 - }] - } + leave_policy = create_leave_policy(leave_type=leave_type.name, annual_allocation=leave_type.max_leaves_allowed + 1) - self.assertRaises(frappe.ValidationError, frappe.get_doc(leave_policy_details).insert) + self.assertRaises(frappe.ValidationError, leave_policy.insert) + +def create_leave_policy(**args): + ''' Returns an object of leave policy ''' + args = frappe._dict(args) + return frappe.get_doc({ + "doctype": "Leave Policy", + "leave_policy_details": [{ + "leave_type": args.leave_type or "_Test Leave Type", + "annual_allocation": args.annual_allocation or 10 + }] + }) \ No newline at end of file From 7a7f4bd822f0474ba9b97d85786b66d20369644d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 03:12:57 +0530 Subject: [PATCH 037/117] fix: leave balance calculation --- .../leave_application/leave_application.js | 3 +- .../leave_application/leave_application.py | 127 +++++++++++++----- 2 files changed, 92 insertions(+), 38 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 5bce348489..0b0a69f7a1 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -143,7 +143,8 @@ frappe.ui.form.on("Leave Application", { method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on", args: { employee: frm.doc.employee, - date: frm.doc.from_date, + from_date: frm.doc.from_date, + to_date: frm.doc.to_date, leave_type: frm.doc.leave_type, consider_all_leaves_in_the_allocation_period: true }, diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 9e0ed0f1b5..2adf8fba71 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -195,9 +195,8 @@ class LeaveApplication(Document): frappe.throw(_("The day(s) on which you are applying for leave are holidays. You need not apply for leave.")) if not is_lwp(self.leave_type): - self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, docname=self.name, + self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, self.to_date, consider_all_leaves_in_the_allocation_period=True) - if self.status != "Rejected" and self.leave_balance < self.total_leave_days: if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"): frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}") @@ -351,13 +350,46 @@ class LeaveApplication(Document): pass def create_leave_ledger_entry(self, submit=True): - args = frappe._dict( - leaves=self.total_leave_days, + expiry_date = get_allocation_expiry(self.employee, self.leave_type, + self.to_date, self.from_date) + + if expiry_date: + self.create_application_entry_for_expiry(expiry_date) + else: + args = dict( + leaves=self.total_leave_days * -1, + from_date=self.from_date, + to_date=self.to_date, + ) + create_leave_ledger_entry(self, args, submit) + + def create_application_entry_for_expiry(self, expiry_date): + ''' splits leave application into two ledger entries to consider expiry ''' + args = dict( from_date=self.from_date, to_date=self.to_date, - is_carry_forward=0 + leaves=date_diff(expiry_date, self.from_date) * -1 ) create_leave_ledger_entry(self, args, submit) + start_date = add_days(expiry_date, 1) + args.update(dict( + from_date=start_date, + to_date=self.to_date, + leaves=date_diff(self.to_date, start_date) * -1 + )) + create_leave_ledger_entry(self, args, submit) + +def get_allocation_expiry(employee, leave_type, to_date, from_date): + return frappe.db.sql(""" + SELECT + to_date + FROM `tabLeave Ledger Entry` + WHERE + employee='%s' + AND leave_type='%s' + AND transaction_type='Leave Allocation' + AND (to_date BETWEEN %s AND %s) + """ %(employee, leave_type, from_date, to_date), as_dict=1) @frappe.whitelist() def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None): @@ -380,10 +412,11 @@ def get_leave_details(employee, date): leave_allocation = {} for d in allocation_records: allocation = allocation_records.get(d, frappe._dict()) + remaining_leaves = get_leave_balance_on(employee, d, date) date = allocation.to_date leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, date, status="Approved") leaves_pending = get_leaves_for_period(employee, d, allocation.from_date, date, status="Open") - remaining_leaves = allocation.total_leaves_allocated - leaves_taken - leaves_pending + leave_allocation[d] = { "total_leaves": allocation.total_leaves_allocated, "leaves_taken": leaves_taken, @@ -397,37 +430,45 @@ def get_leave_details(employee, date): return ret -def get_leave_balance_on(employee, leave_type, date, allocation_records=None, docname=None, - consider_all_leaves_in_the_allocation_period=False, consider_encashed_leaves=True): +@frappe.whitelist() +def get_leave_balance_on(employee, leave_type, from_date, to_date=nowdate(), allocation_records=None, docname=None, + consider_all_leaves_in_the_allocation_period=False): if allocation_records == None: - allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict()) + allocation_records = get_leave_allocation_records(from_date, employee).get(employee, frappe._dict()) allocation = allocation_records.get(leave_type, frappe._dict()) - if consider_all_leaves_in_the_allocation_period: - date = allocation.to_date - leaves_taken = frappe.db.get_value("Leave Ledger Entry", filters={ + + end_date = allocation.to_date if consider_all_leaves_in_the_allocation_period else from_date + expiry = get_allocation_expiry(employee, leave_type, to_date, from_date) + + leaves_taken = get_leaves_taken(employee, leave_type, allocation.from_date, end_date) + return get_remaining_leaves(allocation, leaves_taken, from_date, expiry) + +def get_remaining_leaves(allocation, leaves_taken, date, expiry): + ''' Returns leaves remaining after comparing with remaining days for allocation expiry ''' + def _get_remaining_leaves(allocated_leaves, end_date): + remaining_leaves = flt(allocated_leaves) + flt(leaves_taken) + + if remaining_leaves > 0: + remaining_days = date_diff(end_date, date) + 1 + remaining_leaves = min(remaining_days, remaining_leaves) + return remaining_leaves + + if expiry: + remaining_leaves = _get_remaining_leaves(allocation.carry_forwarded_leaves, expiry) + return flt(allocation.new_leaves_allocated) + flt(remaining_leaves) + else: + return _get_remaining_leaves(allocation.total_leaves_allocated, allocation.to_date) + +def get_leaves_taken(employee, leave_type, from_date, to_date): + ''' Returns leaves taken based on leave application/encashment ''' + return frappe.db.get_value("Leave Ledger Entry", filters={ 'Employee':employee, 'leave_type':leave_type, - 'transaction_type': ('!=', 'Leave Allocation'), - 'to_date':("<=", date), - 'from_date': from_date}, - fields=['SUM(leaves)']) - - remaining_unused_leaves = flt(allocation.unused_leaves + leaves_taken) - if allocation.carry_forward and remaining_unused_leaves > 0: - expiry_date = frappe.db.get_value("Leave Ledger Entry", filters={ - 'Employee':employee, - 'leave_type':leave_type, - 'transaction_type': 'Leave Allocation', - 'transaction_name': allocation.name, - 'is_carry_forward': 1, - 'is_expiry': 0 - }, fieldname=['to_date']) - remaining_days = date_diff(expiry_date, date) + 1 - - remaining_unused_leaves = min(remaining_days, remaining_unused_leaves) - - return flt(allocation.new_leaves) + remaining_unused_leaves + leaves_taken + 'leaves': ("<", 0), + 'to_date':("<=", to_date), + 'from_date': (">=", from_date)}, + fieldname=['SUM(leaves)']) def get_total_allocated_leaves(employee, leave_type, date): filters= { @@ -479,19 +520,31 @@ def get_leave_allocation_records(date, employee=None): conditions = (" and employee='%s'" % employee) if employee else "" leave_allocation_records = frappe.db.sql(""" - select employee, leave_type, total_leaves_allocated, total_leaves_encashed, from_date, to_date - from `tabLeave Allocation` - where %s between from_date and to_date and docstatus=1 {0}""".format(conditions), (date), as_dict=1) + SELECT + employee, + leave_type, + total_leaves_allocated, + carry_forwarded_leaves, + new_leaves_allocated, + carry_forward, + from_date, + to_date + FROM + `tabLeave Allocation` + WHERE + %s between from_date and to_date + AND docstatus=1 {0}""".format(conditions), (date), as_dict=1) #nosec allocated_leaves = frappe._dict() for d in leave_allocation_records: allocated_leaves.setdefault(d.employee, frappe._dict()).setdefault(d.leave_type, frappe._dict({ "from_date": d.from_date, "to_date": d.to_date, + "total_leaves_allocated": d.total_leaves_allocated, "carry_forward": d.carry_forward, - "unused_leaves": d.carry_forwarded_leaves, + "carry_forwarded_leaves": d.carry_forwarded_leaves, "new_leaves": d.new_leaves_allocated, - "name": d.name + "leave_type": d.leave_type })) return allocated_leaves From c99f644ffe661af081df406824dda294a7aef4c2 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 03:13:47 +0530 Subject: [PATCH 038/117] test: carry forward and expiry allocation --- .../leave_allocation/test_leave_allocation.py | 68 ++++++++++++++++++- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index a0113e2d9f..308f2245b9 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -3,6 +3,7 @@ import frappe import unittest from frappe.utils import nowdate, add_months, getdate, add_days from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation class TestLeaveAllocation(unittest.TestCase): def test_overlapping_allocation(self): @@ -68,6 +69,65 @@ class TestLeaveAllocation(unittest.TestCase): #allocated leave more than period self.assertRaises(frappe.ValidationError, doc.save) + def test_carry_forward_calculation(self): + frappe.db.sql("delete from `tabLeave Allocation`") + frappe.db.sql("delete from `tabLeave Ledger Entry`") + leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1) + leave_type.submit() + + # initial leave allocation + leave_allocation = create_leave_allocation( + leave_type="_Test_CF_leave", + from_date=add_months(nowdate(), -12), + to_date=add_months(nowdate(), -1), + carry_forward=1) + leave_allocation.submit() + + # leave allocation with carry forward from previous allocation + leave_allocation_1 = create_leave_allocation( + leave_type="_Test_CF_leave", + carry_forward=1) + leave_allocation_1.submit() + + self.assertEquals(leave_allocation.total_leaves_allocated, leave_allocation_1.carry_forwarded_leaves) + + def test_carry_forward_leaves_expiry(self): + frappe.db.sql("delete from `tabLeave Allocation`") + frappe.db.sql("delete from `tabLeave Ledger Entry`") + leave_type = create_leave_type( + leave_type_name="_Test_CF_leave_expiry", + is_carry_forward=1, + carry_forward_leave_expiry=90) + leave_type.submit() + + # initial leave allocation + leave_allocation = create_leave_allocation( + leave_type="_Test_CF_leave_expiry", + from_date=add_months(nowdate(), -24), + to_date=add_months(nowdate(), -12), + carry_forward=1) + leave_allocation.submit() + + leave_allocation = create_leave_allocation( + leave_type="_Test_CF_leave_expiry", + from_date=add_days(nowdate(), -90), + to_date=add_days(nowdate(), 100), + carry_forward=1) + leave_allocation.submit() + + # expires all the carry forwarded leaves after 90 days + process_expired_allocation() + + # leave allocation with carry forward of only new leaves allocated + leave_allocation_1 = create_leave_allocation( + leave_type="_Test_CF_leave_expiry", + carry_forward=1, + from_date=add_months(nowdate(), 6), + to_date=add_months(nowdate(), 12)) + leave_allocation_1.submit() + + self.assertEquals(leave_allocation_1.carry_forwarded_leaves, leave_allocation.new_leaves_allocated) + def test_creation_of_leave_ledger_entry_on_submit(self): frappe.db.sql("delete from `tabLeave Allocation`") @@ -92,11 +152,13 @@ def create_leave_allocation(**args): leave_allocation = frappe.get_doc({ "doctype": "Leave Allocation", "__islocal": 1, - "employee": employee.name, - "employee_name": employee.employee_name, - "leave_type": args.leave_type.leave_type_name or "_Test Leave Type", + "employee": args.employee or employee.name, + "employee_name": args.employee_name or employee.employee_name, + "leave_type": args.leave_type or "_Test Leave Type", "from_date": args.from_date or nowdate(), "new_leaves_allocated": args.new_leaves_created or 15, + "carry_forward": args.carry_forward or 0, + "carry_forwarded_leaves": args.carry_forwarded_leaves or 0, "to_date": args.to_date or add_months(nowdate(), 12) }) return leave_allocation From 2417c93d0eaff061cff7416d7f5f27ace72ecfeb Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 03:20:10 +0530 Subject: [PATCH 039/117] feat: create expiry and carry forward calculation on leave allocation creation --- .../leave_allocation/leave_allocation.js | 2 +- .../leave_allocation/leave_allocation.py | 63 +++++++++++-------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 228b5528de..1fc1d89635 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -66,4 +66,4 @@ frappe.ui.form.on("Leave Allocation", { frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); } } -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 8429ad4308..6fe8fdff24 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import flt, date_diff, formatdate +from frappe.utils import flt, date_diff, formatdate, add_days from frappe import _ from frappe.model.document import Document from erpnext.hr.utils import set_employee_name, get_leave_period @@ -42,6 +42,7 @@ class LeaveAllocation(Document): .format(self.leave_type, self.employee)) def on_submit(self): + self.expire_previous_allocation() self.create_leave_ledger_entry() def on_cancel(self): @@ -89,10 +90,20 @@ class LeaveAllocation(Document): self.leave_type, self.from_date, self.carry_forward) self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + flt(self.new_leaves_allocated) + self.maintain_carry_forwarded_leaves() if not self.total_leaves_allocated and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave") and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"): frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}".format(self.leave_type))) + def maintain_carry_forwarded_leaves(self): + ''' reduce the carry forwarded leaves to be within the maximum allowed leaves ''' + if not self.carry_forward: + return + max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") + if self.new_leaves_allocated <= max_leaves_allowed <= self.total_leaves_allocated: + self.carry_forwarded_leaves = max_leaves_allowed - flt(self.new_leaves_allocated) + self.total_leaves_allocated = flt(max_leaves_allowed) + def validate_total_leaves_allocated(self): # Adding a day to include To Date in the difference date_difference = date_diff(self.to_date, self.from_date) + 1 @@ -102,9 +113,9 @@ class LeaveAllocation(Document): def create_leave_ledger_entry(self, submit=True): if self.carry_forwarded_leaves: expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "carry_forward_leave_expiry") - args = dict( leaves=self.carry_forwarded_leaves, + from_date=self.from_date, to_date=add_days(self.from_date, expiry_days) if expiry_days else self.to_date, is_carry_forward=1 ) @@ -112,11 +123,26 @@ class LeaveAllocation(Document): args = dict( leaves=self.new_leaves_allocated, + from_date=self.from_date, to_date=self.to_date, is_carry_forward=0 ) create_leave_ledger_entry(self, args, submit) + def expire_previous_allocation(self): + ''' expire previous allocation leaves ''' + leaves = get_remaining_leaves(self.employee, self.leave_type, self.from_date) + + if flt(leaves) > 0: + args = dict( + leaves=leaves * -1, + from_date=self.to_date, + to_date=self.to_date, + is_carry_forward=0, + is_expired=1 + ) + create_leave_ledger_entry(self, args) + def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 leave_allocations = frappe.db.sql(""" @@ -143,35 +169,20 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): @frappe.whitelist() def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): carry_forwarded_leaves = 0 - if carry_forward: validate_carry_forward(leave_type) - - carry_forwarded_leaves = frappe.db.sql(""" - SELECT - SUM(leaves) - FROM `tabLeave Ledger Entry` - WHERE - employee=%s - AND leave_type=%s - AND docstatus=1 - AND to_date < %s - AND name NOT IN ( - SELECT name - from `tabLeave Ledger Entry` - WHERE - employee=%s - AND leave_type=%s - AND docstatus=1 - AND to_date < %s - is_expired=1 - ORDER BY creation DESC - ) - ORDER BY creation DESC - """, (employee, leave_type, date), as_dict=1) + carry_forwarded_leaves = get_remaining_leaves(employee, leave_type, date) return carry_forwarded_leaves +def get_remaining_leaves(employee, leave_type, date): + return frappe.db.get_value("Leave Ledger Entry", filters={ + "to_date": ("<=", date), + "employee": employee, + "docstatus": 1, + "leave_type": leave_type, + }, fieldname=['SUM(leaves)']) + def validate_carry_forward(leave_type): if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"): frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type)) \ No newline at end of file From e7d307e6bf98eceb2741bf36d848263611043b71 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 03:21:32 +0530 Subject: [PATCH 040/117] fix: only expire carry forwarded allocation via scheduler --- erpnext/hooks.py | 2 +- .../leave_ledger_entry/leave_ledger_entry.py | 115 ++++++++++-------- 2 files changed, 65 insertions(+), 52 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 72c538fd3c..28dd4e8f9c 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -264,7 +264,7 @@ scheduler_events = { "erpnext.projects.doctype.project.project.update_project_sales_billing", "erpnext.projects.doctype.project.project.send_project_status_email_to_users", "erpnext.quality_management.doctype.quality_review.quality_review.review", - "erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.check_expired_allocation" + "erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation" "erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status" ], "daily_long": [ diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 1c8fed3d7e..e85d5cef0a 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -6,14 +6,34 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ -from frappe.utils import add_days, today +from frappe.utils import add_days, today, flt class LeaveLedgerEntry(Document): def validate_entries(self): - leave_records = frappe.get_all('Leave Ledger Entry', ['leaves']) - if sum(record.get("leaves") for record in leave_records) <0: + total_leaves = frappe.get_all('Leave Ledger Entry', ['SUM(leaves)']) + if total_leaves < 0: frappe.throw(_("Invalid Ledger Entry")) + def on_cancel(self): + # allow cancellation of expiry leaves + if not self.is_expired: + frappe.throw(_("Only expired allocation can be cancelled")) + +def validate_leave_allocation_against_leave_application(ledger): + ''' Checks that leave allocation has no leave application against it ''' + leave_application_records = frappe.get_all("Leave Ledger Entry", + filters={ + 'employee': ledger.employee, + 'leave_type': ledger.leave_type, + 'transaction_type': 'Leave Application', + 'from_date': (">=", ledger.from_date), + 'to_date': ('<=', ledger.to_date) + }, fields=['transaction_name']) + + if leave_application_records: + frappe.throw(_("Leave allocation %s is linked with leave application %s" + % (ledger.transaction_name, ', '.join(leave_application_records)))) + def create_leave_ledger_entry(ref_doc, args, submit=True): ledger = frappe._dict( doctype='Leave Ledger Entry', @@ -22,6 +42,8 @@ def create_leave_ledger_entry(ref_doc, args, submit=True): leave_type=ref_doc.leave_type, transaction_type=ref_doc.doctype, transaction_name=ref_doc.name, + is_carry_forward=0, + is_expired=0 ) ledger.update(args) if submit: @@ -32,67 +54,58 @@ def create_leave_ledger_entry(ref_doc, args, submit=True): def delete_ledger_entry(ledger): ''' Delete ledger entry on cancel of leave application/allocation/encashment ''' - leave_application_records = [] - # prevent deletion when leave application has been created after allocation if ledger.transaction_type == "Leave Allocation": - leave_application_records = frappe.get_all("Leave Ledger Entry", + validate_leave_allocation_against_leave_application(ledger) + + frappe.db.sql("""DELETE + FROM `tabLeave Ledger Entry` + WHERE + `transaction_name`=%s""", (ledger.transaction_name)) + +def process_expired_allocation(): + ''' Check if a carry forwarded allocation has expired and create a expiry ledger entry ''' + + # fetch leave type records that has carry forwarded leaves expiry + leave_type_records = frappe.db.get_values("Leave Type", filters={ + 'carry_forward_leave_expiry': (">", 0) + }, fieldname=['name']) + + if leave_type_records: + leave_type = [record[0] for record in leave_type_records] + expired_allocation = frappe.get_all("Leave Ledger Entry", filters={ - 'employee': ledger.employee, - 'leave_type': ledger.leave_type, - 'transaction_type': 'Leave Application', - 'from_date': (">=", ledger.from_date), - 'to_date': ('<=', ledger.to_date) - }, - fields=['transaction_name']) - - if not leave_application_records: - frappe.db.sql("""DELETE - FROM `tabLeave Ledger Entry` - WHERE - `transaction_name`=%s""", (ledger.transaction_name)) - else: - frappe.throw(_("Leave allocation %s is linked with leave application %s" - % (ledger_entry, ', '.join(leave_application_records)))) - -def check_expired_allocation(): - ''' Checks for expired allocation by comparing to_date with current_date and - based on that creates an expiry ledger entry ''' - expired_allocation = frappe.get_all("Leave Ledger Allocation", - filters={ - 'to_date': today(), - 'transaction_type': 'Leave Allocation' - }, - fields=['*']) + 'to_date': today(), + 'transaction_type': 'Leave Allocation', + 'is_carry_forward': 1, + 'leave_type': ('in', leave_type) + }, fields=['leaves', 'to_date', 'employee', 'leave_type']) if expired_allocation: create_expiry_ledger_entry(expired_allocation) def create_expiry_ledger_entry(expired_allocation): + ''' Create expiry ledger entry for carry forwarded leaves ''' for allocation in expired_allocation: - filters = { - 'employee': allocation.employee, - 'leave_type': allocation.leave_type, - 'from_date': ('>=', allocation.from_date), - } - # get only application ledger entries in case of carry forward - if allocation.is_carry_forward: - filters.update(dict(transaction_type='Leave Application')) - leave_records = frappe.get_all("Leave Ledger Entry", - filters=filters, - fields=['leaves']) - - leaves = sum(record.get("leaves") for record in leave_records) - - if allocation.is_carry_forward: - leaves = allocation.leaves + leaves + leaves_taken = get_leaves_taken(allocation) + leaves = flt(allocation.leaves) + flt(leaves_taken) if leaves > 0: args = frappe._dict( leaves=allocation.leaves * -1, - to_date='', - is_carry_forward=allocation.is_carry_forward, + to_date=allocation.to_date, + is_carry_forward=1, is_expired=1, from_date=allocation.to_date ) - create_leave_ledger_entry(allocation, args) \ No newline at end of file + create_leave_ledger_entry(allocation, args) + +def get_leaves_taken(allocation): + return frappe.db.get_value("Leave Ledger Entry", + filters={ + 'employee': allocation.employee, + 'leave_type': allocation.leave_type, + 'from_date': ('>=', allocation.from_date), + 'to_date': ('<=', allocation.to_date), + 'transaction_type': 'Leave application' + }, fieldname=['SUM(leaves)']) \ No newline at end of file From d751281fa721c0ac75e452826e61f381c91a6186 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 03:23:58 +0530 Subject: [PATCH 041/117] fix: application and leave encashment test cases --- .../test_leave_application.py | 31 +++++++++++++------ .../leave_encashment/leave_encashment.py | 1 + 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 2581c2a659..4dea413dc3 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -8,6 +8,8 @@ import unittest from erpnext.hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError, NotAnOptionalHoliday, get_leave_balance_on from frappe.permissions import clear_user_permissions_for_doctype from frappe.utils import add_days, nowdate, now_datetime, getdate +from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type +from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation test_dependencies = ["Leave Allocation", "Leave Block List"] @@ -274,7 +276,7 @@ class TestLeaveApplication(unittest.TestCase): )) # can only apply on optional holidays - self.assertTrue(NotAnOptionalHoliday, leave_application.insert) + self.assertRaises(NotAnOptionalHoliday, leave_application.insert) leave_application.from_date = today leave_application.to_date = today @@ -287,6 +289,8 @@ class TestLeaveApplication(unittest.TestCase): def test_leaves_allowed(self): + frappe.db.sql("delete from `tabLeave Allocation`") + frappe.db.sql("delete from `tabLeave Ledger Entry`") employee = get_employee() leave_period = get_leave_period() frappe.delete_doc_if_exists("Leave Type", "Test Leave Type", force=1) @@ -301,7 +305,7 @@ class TestLeaveApplication(unittest.TestCase): allocate_leaves(employee, leave_period, leave_type.name, 5) leave_application = frappe.get_doc(dict( - doctype = 'Leave Application', + doctype = 'Leave Application', employee = employee.name, leave_type = leave_type.name, from_date = date, @@ -310,15 +314,14 @@ class TestLeaveApplication(unittest.TestCase): docstatus = 1, status = "Approved" )) - - self.assertTrue(leave_application.insert()) + leave_application.submit() leave_application = frappe.get_doc(dict( doctype = 'Leave Application', employee = employee.name, leave_type = leave_type.name, from_date = add_days(date, 4), - to_date = add_days(date, 7), + to_date = add_days(date, 8), company = "_Test Company", docstatus = 1, status = "Approved" @@ -458,22 +461,32 @@ class TestLeaveApplication(unittest.TestCase): self.assertEqual(leave_application.docstatus, 1) def test_creation_of_leave_ledger_entry_on_submit(self): + frappe.db.sql("delete from `tabLeave Allocation`") + employee = get_employee() + + leave_type = create_leave_type(leave_type_name = 'Test Leave Type 1') + leave_type.save() + + leave_allocation = create_leave_allocation(employee=employee.name, employee_name=employee.employee_name, + leave_type=leave_type.name) + leave_allocation.submit() leave_application = frappe.get_doc(dict( doctype = 'Leave Application', employee = employee.name, - leave_type = leave_type_1.name, - from_date = nowdate(), + leave_type = leave_type.name, + from_date = add_days(nowdate(), 1), to_date = add_days(nowdate(), 4), company = "_Test Company", docstatus = 1, status = "Approved" - )).submit() + )) + leave_application.submit() leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name)) self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee) self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type) - self.assertEquals(leave_ledger_entry[0].leaves, leave_application.new_leaves_allocated) + self.assertEquals(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1) # check if leave ledger entry is deleted on cancellation leave_application.cancel() diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index 4f4a8760cf..c59ce63c7c 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -84,6 +84,7 @@ class LeaveEncashment(Document): args = frappe._dict( leaves=self.encashable_days * -1, from_date=self.encashment_date, + to_date=self.encashable_days, is_carry_forward=0 ) create_leave_ledger_entry(self, args, submit) \ No newline at end of file From 45197965d7ab957ccd7b493478655cd213d72469 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 03:26:48 +0530 Subject: [PATCH 042/117] fix: give cancellation permission to hr manager --- erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json index ae36735570..ba33d75029 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -87,7 +87,7 @@ ], "in_create": 1, "is_submittable": 1, - "modified": "2019-05-23 19:22:09.028366", + "modified": "2019-05-27 03:25:47.805142", "modified_by": "Administrator", "module": "HR", "name": "Leave Ledger Entry", @@ -107,6 +107,7 @@ "write": 1 }, { + "cancel": 1, "create": 1, "delete": 1, "email": 1, From 24248f687b4067ce56bb13f94274170310683979 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 10:58:42 +0530 Subject: [PATCH 043/117] patch: create entries for only missing transactions --- .../v12_0/generate_leave_ledger_entries.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 9223bb2ebc..9c638abaff 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -7,7 +7,7 @@ import frappe def execute(): """ Generates leave ledger entries for leave allocation/application/encashment for last allocation """ - if frappe.db.exists("DocType","Leave Ledger Entry"): + if frappe.db.a_row_exists("Leave Ledger Entry"): return allocation_list = get_allocation_records() @@ -20,7 +20,9 @@ def generate_allocation_ledger_entries(allocation_list): from erpnext.hr.doctype.leave_allocation.leave_allocation import LeaveAllocation for allocation in allocation_list: - LeaveAllocation.create_leave_ledger_entry(allocation) + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', + 'transaction_name': allocation.name}): + LeaveAllocation.create_leave_ledger_entry(allocation) def generate_application_leave_ledger_entries(allocation_list): ''' fix ledger entries for missing leave application transaction ''' @@ -29,7 +31,9 @@ def generate_application_leave_ledger_entries(allocation_list): leave_applications = get_leaves_application_records(allocation_list) for record in leave_applications: - LeaveApplication.create_leave_ledger_entry(record) + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', + 'transaction_name': record.name}): + LeaveApplication.create_leave_ledger_entry(record) def generate_encashment_leave_ledger_entries(allocation_list): ''' fix ledger entries for missing leave encashment transaction ''' @@ -38,7 +42,9 @@ def generate_encashment_leave_ledger_entries(allocation_list): leave_encashments = get_leave_encashment_records(allocation_list) for record in leave_encashments: - LeaveEncashment.create_leave_ledger_entry(record) + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', + 'transaction_name': record.name}): + LeaveEncashment.create_leave_ledger_entry(record) def get_allocation_records(): return frappe.db.sql(""" @@ -62,7 +68,7 @@ def get_leaves_application_records(allocation_list): for allocation in allocation_list: leave_applications.append(frappe.db.sql(""" SELECT - name, + DISTINCT name, employee, leave_type, total_leave_days, From 71cdcb35930e37befd227fc6e0200041eb26aec1 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 11:00:04 +0530 Subject: [PATCH 044/117] style: add a more descriptive method name --- erpnext/hr/doctype/leave_application/leave_application.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 2adf8fba71..8e8e47e510 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -354,7 +354,7 @@ class LeaveApplication(Document): self.to_date, self.from_date) if expiry_date: - self.create_application_entry_for_expiry(expiry_date) + self.create_ledger_entry_for_intermediate_expiry(expiry_date, submit) else: args = dict( leaves=self.total_leave_days * -1, @@ -363,7 +363,7 @@ class LeaveApplication(Document): ) create_leave_ledger_entry(self, args, submit) - def create_application_entry_for_expiry(self, expiry_date): + def create_ledger_entry_for_intermediate_expiry(self, expiry_date, submit): ''' splits leave application into two ledger entries to consider expiry ''' args = dict( from_date=self.from_date, From 7fbaef5de34d14d4535813179f888336fd7b7076 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 15:39:48 +0530 Subject: [PATCH 045/117] fix: expiry ledger creation --- .../leave_application/leave_application.py | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 8e8e47e510..e5ee4e6a26 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -364,32 +364,31 @@ class LeaveApplication(Document): create_leave_ledger_entry(self, args, submit) def create_ledger_entry_for_intermediate_expiry(self, expiry_date, submit): - ''' splits leave application into two ledger entries to consider expiry ''' + ''' splits leave application into two ledger entries to consider expiry of allocation ''' args = dict( from_date=self.from_date, to_date=self.to_date, - leaves=date_diff(expiry_date, self.from_date) * -1 + leaves=(date_diff(expiry_date, self.from_date) + 1) * -1 ) create_leave_ledger_entry(self, args, submit) + start_date = add_days(expiry_date, 1) args.update(dict( from_date=start_date, to_date=self.to_date, - leaves=date_diff(self.to_date, start_date) * -1 + leaves=date_diff(self.to_date, expiry_date) * -1 )) create_leave_ledger_entry(self, args, submit) def get_allocation_expiry(employee, leave_type, to_date, from_date): - return frappe.db.sql(""" - SELECT - to_date - FROM `tabLeave Ledger Entry` - WHERE - employee='%s' - AND leave_type='%s' - AND transaction_type='Leave Allocation' - AND (to_date BETWEEN %s AND %s) - """ %(employee, leave_type, from_date, to_date), as_dict=1) + expiry = frappe.get_all("Leave Ledger Entry", + filters={ + 'employee': employee, + 'leave_type': leave_type, + 'transaction_type': 'Leave Allocation', + 'to_date': ['between', (from_date, to_date)] + },fields=['to_date']) + return expiry[0]['to_date'] if expiry else None @frappe.whitelist() def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None): @@ -452,10 +451,12 @@ def get_remaining_leaves(allocation, leaves_taken, date, expiry): if remaining_leaves > 0: remaining_days = date_diff(end_date, date) + 1 remaining_leaves = min(remaining_days, remaining_leaves) + return remaining_leaves if expiry: remaining_leaves = _get_remaining_leaves(allocation.carry_forwarded_leaves, expiry) + return flt(allocation.new_leaves_allocated) + flt(remaining_leaves) else: return _get_remaining_leaves(allocation.total_leaves_allocated, allocation.to_date) @@ -543,7 +544,7 @@ def get_leave_allocation_records(date, employee=None): "total_leaves_allocated": d.total_leaves_allocated, "carry_forward": d.carry_forward, "carry_forwarded_leaves": d.carry_forwarded_leaves, - "new_leaves": d.new_leaves_allocated, + "new_leaves_allocated": d.new_leaves_allocated, "leave_type": d.leave_type })) return allocated_leaves From 6f69bbe1d782bb710ddb80111458984a4ac35e55 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 15:40:36 +0530 Subject: [PATCH 046/117] test: leave ledger balance --- .../leave_allocation/test_leave_allocation.py | 1 - .../leave_application/leave_application.py | 4 +- .../test_leave_application.py | 70 ++++++++++++++++++- .../leave_encashment/test_leave_encashment.py | 15 ++-- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 308f2245b9..dfae329da1 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -158,7 +158,6 @@ def create_leave_allocation(**args): "from_date": args.from_date or nowdate(), "new_leaves_allocated": args.new_leaves_created or 15, "carry_forward": args.carry_forward or 0, - "carry_forwarded_leaves": args.carry_forwarded_leaves or 0, "to_date": args.to_date or add_months(nowdate(), 12) }) return leave_allocation diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index e5ee4e6a26..f3bb9f7eb9 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -354,7 +354,7 @@ class LeaveApplication(Document): self.to_date, self.from_date) if expiry_date: - self.create_ledger_entry_for_intermediate_expiry(expiry_date, submit) + self.create_ledger_entry_for_intermediate_allocation_expiry(expiry_date, submit) else: args = dict( leaves=self.total_leave_days * -1, @@ -363,7 +363,7 @@ class LeaveApplication(Document): ) create_leave_ledger_entry(self, args, submit) - def create_ledger_entry_for_intermediate_expiry(self, expiry_date, submit): + def create_ledger_entry_for_intermediate_allocation_expiry(self, expiry_date, submit): ''' splits leave application into two ledger entries to consider expiry of allocation ''' args = dict( from_date=self.from_date, diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 4dea413dc3..f5f4fa55f5 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -7,7 +7,7 @@ import unittest from erpnext.hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError, NotAnOptionalHoliday, get_leave_balance_on from frappe.permissions import clear_user_permissions_for_doctype -from frappe.utils import add_days, nowdate, now_datetime, getdate +from frappe.utils import add_days, nowdate, now_datetime, getdate, add_months from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation @@ -287,7 +287,6 @@ class TestLeaveApplication(unittest.TestCase): # check leave balance is reduced self.assertEqual(get_leave_balance_on(employee.name, leave_type, today), 9) - def test_leaves_allowed(self): frappe.db.sql("delete from `tabLeave Allocation`") frappe.db.sql("delete from `tabLeave Ledger Entry`") @@ -404,6 +403,20 @@ class TestLeaveApplication(unittest.TestCase): self.assertRaises(frappe.ValidationError, leave_application.insert) + def test_leave_balance_near_allocaton_expiry(self): + frappe.db.sql("delete from `tabLeave Allocation`") + frappe.db.sql("delete from `tabLeave Ledger Entry`") + employee = get_employee() + leave_type = create_leave_type( + leave_type_name="_Test_CF_leave_expiry", + is_carry_forward=1, + carry_forward_leave_expiry=90) + leave_type.submit() + + create_carry_forwarded_allocation(employee, leave_type) + + self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8)), 21) + def test_earned_leave(self): leave_period = get_leave_period() employee = get_employee() @@ -492,6 +505,59 @@ class TestLeaveApplication(unittest.TestCase): leave_application.cancel() self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_application.name})) + def test_ledger_entry_creation_on_intermediate_allocation_expiry(self): + frappe.db.sql("delete from `tabLeave Allocation`") + frappe.db.sql("delete from `tabLeave Ledger Entry`") + employee = get_employee() + leave_type = create_leave_type( + leave_type_name="_Test_CF_leave_expiry", + is_carry_forward=1, + carry_forward_leave_expiry=90) + leave_type.submit() + + create_carry_forwarded_allocation(employee, leave_type) + + leave_application = frappe.get_doc(dict( + doctype = 'Leave Application', + employee = employee.name, + leave_type = leave_type.name, + from_date = add_days(nowdate(), -3), + to_date = add_days(nowdate(), 7), + company = "_Test Company", + docstatus = 1, + status = "Approved" + )) + leave_application.submit() + + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', '*', filters=dict(transaction_name=leave_application.name)) + + self.assertEquals(len(leave_ledger_entry), 2) + self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, -9) + self.assertEquals(leave_ledger_entry[1].leaves, -2) + +def create_carry_forwarded_allocation(employee, leave_type): + + # initial leave allocation + leave_allocation = create_leave_allocation( + leave_type="_Test_CF_leave_expiry", + employee=employee.name, + employee_name=employee.employee_name, + from_date=add_months(nowdate(), -24), + to_date=add_months(nowdate(), -12), + carry_forward=1) + leave_allocation.submit() + + leave_allocation = create_leave_allocation( + leave_type="_Test_CF_leave_expiry", + employee=employee.name, + employee_name=employee.employee_name, + from_date=add_days(nowdate(), -85), + to_date=add_days(nowdate(), 100), + carry_forward=1) + leave_allocation.submit() + def make_allocation_record(employee=None, leave_type=None): frappe.db.sql("delete from `tabLeave Allocation`") diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index cfed780eb7..7d4fb285d8 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -17,6 +17,7 @@ class TestLeaveEncashment(unittest.TestCase): def setUp(self): frappe.db.sql('''delete from `tabLeave Period`''') frappe.db.sql('''delete from `tabLeave Allocation`''') + frappe.db.sql('''delete from `tabAdditional Salary`''') # create the leave policy leave_policy = create_leave_policy( @@ -26,7 +27,9 @@ class TestLeaveEncashment(unittest.TestCase): # create employee, salary structure and assignment self.employee = make_employee("test_employee_encashment@example.com") - frappe.db.set_value("Employee", "test_employee_encashment@example.com", "leave_policy", leave_policy.name) + + frappe.db.set_value("Employee", self.employee, "leave_policy", leave_policy.name) + salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee, other_details={"leave_encashment_amount_per_day": 50}) @@ -55,11 +58,11 @@ class TestLeaveEncashment(unittest.TestCase): def test_creation_of_leave_ledger_entry_on_submit(self): frappe.db.sql('''delete from `tabLeave Encashment`''') leave_encashment = frappe.get_doc(dict( - doctype = 'Leave Encashment', - employee = self.employee, - leave_type = "_Test Leave Type Encashment", - leave_period = self.leave_period.name, - payroll_date = today() + doctype='Leave Encashment', + employee=self.employee, + leave_type="_Test Leave Type Encashment", + leave_period=self.leave_period.name, + payroll_date=today() )).insert() leave_encashment.submit() From 61bb236cfaf234fadd69cce768c8e6915de0156e Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 27 May 2019 19:36:40 +0530 Subject: [PATCH 047/117] test: delete ledger entry after each test to maintain balance --- .../doctype/leave_encashment/test_leave_encashment.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index 7d4fb285d8..2daeffcffe 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -17,6 +17,7 @@ class TestLeaveEncashment(unittest.TestCase): def setUp(self): frappe.db.sql('''delete from `tabLeave Period`''') frappe.db.sql('''delete from `tabLeave Allocation`''') + frappe.db.sql('''delete from `tabLeave Ledger Entry`''') frappe.db.sql('''delete from `tabAdditional Salary`''') # create the leave policy @@ -41,11 +42,11 @@ class TestLeaveEncashment(unittest.TestCase): def test_leave_balance_value_and_amount(self): frappe.db.sql('''delete from `tabLeave Encashment`''') leave_encashment = frappe.get_doc(dict( - doctype = 'Leave Encashment', - employee = self.employee, - leave_type = "_Test Leave Type Encashment", - leave_period = self.leave_period.name, - payroll_date = today() + doctype='Leave Encashment', + employee=self.employee, + leave_type="_Test Leave Type Encashment", + leave_period=self.leave_period.name, + payroll_date=today() )).insert() self.assertEqual(leave_encashment.leave_balance, 10) From c6d6adcbf3889a642ff1ed7deb955e5fd0bda04f Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 28 May 2019 09:44:38 +0530 Subject: [PATCH 048/117] fix: ledger entry creation for encashment --- erpnext/hr/doctype/leave_encashment/leave_encashment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index c59ce63c7c..70cd5780c8 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -84,7 +84,7 @@ class LeaveEncashment(Document): args = frappe._dict( leaves=self.encashable_days * -1, from_date=self.encashment_date, - to_date=self.encashable_days, + to_date=self.encashment_date, is_carry_forward=0 ) create_leave_ledger_entry(self, args, submit) \ No newline at end of file From 9d6151d20047bfa54b6ffa96c14acedc25b74146 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 28 May 2019 11:45:51 +0530 Subject: [PATCH 049/117] patch: add reload doc for leave ledger entry --- erpnext/patches/v12_0/generate_leave_ledger_entries.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 9c638abaff..b8e82ef416 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -7,6 +7,7 @@ import frappe def execute(): """ Generates leave ledger entries for leave allocation/application/encashment for last allocation """ + frappe.reload_doc("HR","doctype", "Leave Ledger Entry") if frappe.db.a_row_exists("Leave Ledger Entry"): return From ae4228aed4d5097a35ec65d585238c8320784121 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 28 May 2019 13:24:15 +0530 Subject: [PATCH 050/117] fix: fetch queries --- .../v12_0/generate_leave_ledger_entries.py | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index b8e82ef416..8b1ca8c7ad 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -49,17 +49,26 @@ def generate_encashment_leave_ledger_entries(allocation_list): def get_allocation_records(): return frappe.db.sql(""" + WITH allocation_values AS ( + SELECT + DISTINCT name, + employee, + leave_type, + new_leaves_allocated, + carry_forwarded_leaves, + from_date, + to_date, + carry_forward, + RANK() OVER( + PARTITION BY employee, leave_type + ORDER BY to_date DESC + ) as allocation + FROM `tabLeave Allocation` + ) SELECT - DISTINCT name, - employee, - leave_type, - new_leaves_allocated, - carry_forwarded_leaves, - from_date, - to_date, - carry_forward, - RANK() OVER(PARTITION BY employee, leave_type ORDER BY to_date DESC) as allocation - FROM `tabLeave Allocation` + * + FROM + `allocation_values` WHERE allocation=1 """, as_dict=1) @@ -67,7 +76,7 @@ def get_allocation_records(): def get_leaves_application_records(allocation_list): leave_applications = [] for allocation in allocation_list: - leave_applications.append(frappe.db.sql(""" + leave_applications += frappe.db.sql(""" SELECT DISTINCT name, employee, @@ -78,15 +87,15 @@ def get_leaves_application_records(allocation_list): FROM `tabLeave Application` WHERE from_date >= %s - leave_type = %s - employee = %s - """, (allocation.from_date, allocation.leave_type, allocation.employee))) + AND leave_type = %s + AND employee = %s + """, (allocation.from_date, allocation.leave_type, allocation.employee)) return leave_applications def get_leave_encashment_records(allocation_list): leave_encashments = [] for allocation in allocation_list: - leave_encashments.append(frappe.db.sql(""" + leave_encashments += frappe.db.sql(""" SELECT DISTINCT name, employee, @@ -97,6 +106,7 @@ def get_leave_encashment_records(allocation_list): FROM `tabLeave Encashment` WHERE leave_type = %s - employee = %s - """, (allocation.leave_type, allocation.employee))) + AND employee = %s + AND encashment_date >= %s + """, (allocation.leave_type, allocation.employee, allocation.from_date)) return leave_encashments \ No newline at end of file From 8f47bffa0ef85040621b57a2a6defbfb6031e403 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 29 May 2019 15:54:14 +0530 Subject: [PATCH 051/117] fix: create an instance --- .../v12_0/generate_leave_ledger_entries.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 8b1ca8c7ad..3cdfa53f88 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -21,9 +21,9 @@ def generate_allocation_ledger_entries(allocation_list): from erpnext.hr.doctype.leave_allocation.leave_allocation import LeaveAllocation for allocation in allocation_list: - if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', - 'transaction_name': allocation.name}): - LeaveAllocation.create_leave_ledger_entry(allocation) + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name}): + leave_allocation = LeaveAllocation(allocation) + leave_allocation.create_leave_ledger_entry() def generate_application_leave_ledger_entries(allocation_list): ''' fix ledger entries for missing leave application transaction ''' @@ -32,9 +32,9 @@ def generate_application_leave_ledger_entries(allocation_list): leave_applications = get_leaves_application_records(allocation_list) for record in leave_applications: - if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', - 'transaction_name': record.name}): - LeaveApplication.create_leave_ledger_entry(record) + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', 'transaction_name': record.name}): + leave_application = LeaveApplication(record) + leave_application.create_leave_ledger_entry() def generate_encashment_leave_ledger_entries(allocation_list): ''' fix ledger entries for missing leave encashment transaction ''' @@ -43,9 +43,9 @@ def generate_encashment_leave_ledger_entries(allocation_list): leave_encashments = get_leave_encashment_records(allocation_list) for record in leave_encashments: - if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', - 'transaction_name': record.name}): - LeaveEncashment.create_leave_ledger_entry(record) + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': record.name}): + leave_encashment = LeaveEncashment(record) + leave_encashment.create_leave_ledger_entry() def get_allocation_records(): return frappe.db.sql(""" From 7cc6a67c186bd492c3af10e715acd3346f048e0b Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 29 May 2019 15:59:20 +0530 Subject: [PATCH 052/117] add department filter to employee leave balance --- .../leave_application/leave_application.py | 4 +++- .../leave_ledger_entry.json | 6 +++--- .../employee_leave_balance.js | 7 +++++++ .../employee_leave_balance.py | 21 +++++++++++++------ 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index f3bb9f7eb9..9eb0b91c7c 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -411,7 +411,8 @@ def get_leave_details(employee, date): leave_allocation = {} for d in allocation_records: allocation = allocation_records.get(d, frappe._dict()) - remaining_leaves = get_leave_balance_on(employee, d, date) + remaining_leaves = get_leave_balance_on(employee, d, date, to_date = allocation.to_date, + consider_all_leaves_in_the_allocation_period=True) date = allocation.to_date leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, date, status="Approved") leaves_pending = get_leaves_for_period(employee, d, allocation.from_date, date, status="Open") @@ -441,6 +442,7 @@ def get_leave_balance_on(employee, leave_type, from_date, to_date=nowdate(), all expiry = get_allocation_expiry(employee, leave_type, to_date, from_date) leaves_taken = get_leaves_taken(employee, leave_type, allocation.from_date, end_date) + return get_remaining_leaves(allocation, leaves_taken, from_date, expiry) def get_remaining_leaves(allocation, leaves_taken, date, expiry): diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json index ba33d75029..3af013d12b 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -65,21 +65,21 @@ { "fieldname": "from_date", "fieldtype": "Date", - "in_list_view": 1, "label": "From Date" }, { "fieldname": "to_date", "fieldtype": "Date", - "in_list_view": 1, "label": "To Date" }, { + "default": "0", "fieldname": "is_carry_forward", "fieldtype": "Check", "label": "Is Carry Forward" }, { + "default": "0", "fieldname": "is_expired", "fieldtype": "Check", "label": "Is Expired" @@ -87,7 +87,7 @@ ], "in_create": 1, "is_submittable": 1, - "modified": "2019-05-27 03:25:47.805142", + "modified": "2019-05-29 15:58:29.656351", "modified_by": "Administrator", "module": "HR", "name": "Leave Ledger Entry", diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js index 59c25608c2..ecb3e3061f 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js @@ -24,6 +24,13 @@ frappe.query_reports["Employee Leave Balance"] = { "options": "Company", "reqd": 1, "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname":"department", + "label": __("Department"), + "fieldtype": "Link", + "options": "Department", + "reqd": 1, } ] } diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 95cb30b791..02be799501 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import flt from erpnext.hr.doctype.leave_application.leave_application \ import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period, get_total_allocated_leaves @@ -30,14 +31,23 @@ def get_columns(leave_types): return columns +def get_conditions(filters): + filters = { + "status": "Active", + "company": filters.company, + } + if filters.get("Department"): + filters.update(filters.get("Department")) + + return filters + def get_data(filters, leave_types): user = frappe.session.user - allocation_records_based_on_to_date = get_leave_allocation_records(filters.to_date) - allocation_records_based_on_from_date = get_leave_allocation_records(filters.from_date) + conditions = get_conditions(filters) active_employees = frappe.get_all("Employee", - filters = { "status": "Active", "company": filters.company}, - fields = ["name", "employee_name", "department", "user_id"]) + filters=conditions, + fields=["name", "employee_name", "department", "user_id"]) data = [] for employee in active_employees: @@ -54,8 +64,7 @@ def get_data(filters, leave_types): opening = get_total_allocated_leaves(employee.name, leave_type, filters.to_date) # closing balance - closing = get_leave_balance_on(employee.name, leave_type, filters.to_date, - allocation_records_based_on_to_date.get(employee.name, frappe._dict())) + closing = flt(opening) - flt(leaves_taken) row += [opening, leaves_taken, closing] From ded33a7e2e18c393783889984aa2cfd5c7e3ee02 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 29 May 2019 19:12:19 +0530 Subject: [PATCH 053/117] fix: ledger entries creation --- .../doctype/leave_allocation/leave_allocation.py | 4 ++-- .../doctype/leave_application/leave_application.py | 14 ++++++++------ .../patches/v12_0/generate_leave_ledger_entries.py | 7 +++++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 6fe8fdff24..2a1301d2fd 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -136,8 +136,8 @@ class LeaveAllocation(Document): if flt(leaves) > 0: args = dict( leaves=leaves * -1, - from_date=self.to_date, - to_date=self.to_date, + from_date=self.from_date, + to_date=self.from_date, is_carry_forward=0, is_expired=1 ) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 9eb0b91c7c..b1c666ca83 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -431,22 +431,24 @@ def get_leave_details(employee, date): return ret @frappe.whitelist() -def get_leave_balance_on(employee, leave_type, from_date, to_date=nowdate(), allocation_records=None, docname=None, +def get_leave_balance_on(employee, leave_type, date, to_date=nowdate(), allocation_records=None, docname=None, consider_all_leaves_in_the_allocation_period=False): + ''' Returns leave balance till date and fetches expiry date based on to_date + to calculate minimum remaining leave balance ''' if allocation_records == None: - allocation_records = get_leave_allocation_records(from_date, employee).get(employee, frappe._dict()) + allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict()) allocation = allocation_records.get(leave_type, frappe._dict()) - end_date = allocation.to_date if consider_all_leaves_in_the_allocation_period else from_date - expiry = get_allocation_expiry(employee, leave_type, to_date, from_date) + end_date = allocation.to_date if consider_all_leaves_in_the_allocation_period else date + expiry = get_allocation_expiry(employee, leave_type, to_date, date) leaves_taken = get_leaves_taken(employee, leave_type, allocation.from_date, end_date) - return get_remaining_leaves(allocation, leaves_taken, from_date, expiry) + return get_remaining_leaves(allocation, leaves_taken, date, expiry) def get_remaining_leaves(allocation, leaves_taken, date, expiry): - ''' Returns leaves remaining after comparing with remaining days for allocation expiry ''' + ''' Returns minimum leaves remaining after comparing with remaining days for allocation expiry ''' def _get_remaining_leaves(allocated_leaves, end_date): remaining_leaves = flt(allocated_leaves) + flt(leaves_taken) diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 3cdfa53f88..31cbcc6181 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -22,6 +22,7 @@ def generate_allocation_ledger_entries(allocation_list): for allocation in allocation_list: if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name}): + allocation.update(dict(doctype="Leave Allocation")) leave_allocation = LeaveAllocation(allocation) leave_allocation.create_leave_ledger_entry() @@ -33,6 +34,7 @@ def generate_application_leave_ledger_entries(allocation_list): for record in leave_applications: if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', 'transaction_name': record.name}): + record.update(dict(doctype="Leave Application")) leave_application = LeaveApplication(record) leave_application.create_leave_ledger_entry() @@ -44,6 +46,7 @@ def generate_encashment_leave_ledger_entries(allocation_list): for record in leave_encashments: if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': record.name}): + record.update(dict(doctype="Leave Encashment")) leave_encashment = LeaveEncashment(record) leave_encashment.create_leave_ledger_entry() @@ -89,7 +92,7 @@ def get_leaves_application_records(allocation_list): from_date >= %s AND leave_type = %s AND employee = %s - """, (allocation.from_date, allocation.leave_type, allocation.employee)) + """, (allocation.from_date, allocation.leave_type, allocation.employee), as_dict=1) return leave_applications def get_leave_encashment_records(allocation_list): @@ -108,5 +111,5 @@ def get_leave_encashment_records(allocation_list): leave_type = %s AND employee = %s AND encashment_date >= %s - """, (allocation.leave_type, allocation.employee, allocation.from_date)) + """, (allocation.leave_type, allocation.employee, allocation.from_date), as_dict=1) return leave_encashments \ No newline at end of file From aafb5cb6f684ca28921d2a6de934a5f6fd4f3162 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 30 May 2019 13:13:14 +0530 Subject: [PATCH 054/117] fix: ignore expired non-carry forwarded allocation on calculating leaves taken --- .../hr/doctype/leave_application/leave_application.py | 10 +++++++--- .../leave_application/test_leave_application.py | 1 - erpnext/patches/v12_0/generate_leave_ledger_entries.py | 3 +-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index b1c666ca83..fd13436d8f 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -431,7 +431,7 @@ def get_leave_details(employee, date): return ret @frappe.whitelist() -def get_leave_balance_on(employee, leave_type, date, to_date=nowdate(), allocation_records=None, docname=None, +def get_leave_balance_on(employee, leave_type, date, to_date=nowdate(), allocation_records=None, consider_all_leaves_in_the_allocation_period=False): ''' Returns leave balance till date and fetches expiry date based on to_date to calculate minimum remaining leave balance ''' @@ -467,13 +467,17 @@ def get_remaining_leaves(allocation, leaves_taken, date, expiry): def get_leaves_taken(employee, leave_type, from_date, to_date): ''' Returns leaves taken based on leave application/encashment ''' - return frappe.db.get_value("Leave Ledger Entry", filters={ + leaves = frappe.db.get_all("Leave Ledger Entry", filters={ 'Employee':employee, 'leave_type':leave_type, 'leaves': ("<", 0), 'to_date':("<=", to_date), 'from_date': (">=", from_date)}, - fieldname=['SUM(leaves)']) + or_filters={ + 'is_expired': 0, + 'is_carry_forward': 1 + }, fields=['SUM(leaves) as leaves']) + return leaves[0]['leaves'] if leaves else None def get_total_allocated_leaves(employee, leave_type, date): filters= { diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index f5f4fa55f5..30dcafafc7 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -538,7 +538,6 @@ class TestLeaveApplication(unittest.TestCase): self.assertEquals(leave_ledger_entry[1].leaves, -2) def create_carry_forwarded_allocation(employee, leave_type): - # initial leave allocation leave_allocation = create_leave_allocation( leave_type="_Test_CF_leave_expiry", diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 31cbcc6181..dff749fce7 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -104,8 +104,7 @@ def get_leave_encashment_records(allocation_list): employee, leave_type, encashable_days, - from_date, - to_date + encashment_date FROM `tabLeave Encashment` WHERE leave_type = %s From 2124d9884ba6bbf22e88111a229e053001832cb3 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 30 May 2019 13:21:34 +0530 Subject: [PATCH 055/117] fix: pass positional arguments on creation of leave application --- erpnext/hr/doctype/leave_application/leave_application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 0b0a69f7a1..11146abce2 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -143,7 +143,7 @@ frappe.ui.form.on("Leave Application", { method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on", args: { employee: frm.doc.employee, - from_date: frm.doc.from_date, + date: frm.doc.from_date, to_date: frm.doc.to_date, leave_type: frm.doc.leave_type, consider_all_leaves_in_the_allocation_period: true From 91e62f575e51fe21058ea296adc32ddbb785faa0 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 30 May 2019 13:38:35 +0530 Subject: [PATCH 056/117] fix: department filters in employee leave balance --- .../employee_leave_balance/employee_leave_balance.js | 1 - .../employee_leave_balance/employee_leave_balance.py | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js index ecb3e3061f..68302f6ee4 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js @@ -30,7 +30,6 @@ frappe.query_reports["Employee Leave Balance"] = { "label": __("Department"), "fieldtype": "Link", "options": "Department", - "reqd": 1, } ] } diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 02be799501..e65220ae25 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -32,14 +32,15 @@ def get_columns(leave_types): return columns def get_conditions(filters): - filters = { + conditions = { "status": "Active", "company": filters.company, } - if filters.get("Department"): - filters.update(filters.get("Department")) - - return filters + if filters.get("department"): + conditions.update({ + "department": filters.get("department") + }) + return conditions def get_data(filters, leave_types): user = frappe.session.user From 95e5d812fef9ff2a03a40fa443f0007e95797582 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 30 May 2019 22:22:15 +0530 Subject: [PATCH 057/117] fix: UX changes --- .../leave_application/leave_application.json | 197 +++---- .../leave_ledger_entry.json | 12 +- .../hr/doctype/leave_period/leave_period.json | 538 +++++++++--------- erpnext/hr/doctype/leave_type/leave_type.json | 5 +- .../v12_0/generate_leave_ledger_entries.py | 1 + 5 files changed, 381 insertions(+), 372 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.json b/erpnext/hr/doctype/leave_application/leave_application.json index d7b2c9f875..812494c1c0 100644 --- a/erpnext/hr/doctype/leave_application/leave_application.json +++ b/erpnext/hr/doctype/leave_application/leave_application.json @@ -485,7 +485,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -507,6 +507,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Approval", "length": 0, "no_copy": 0, "permlevel": 0, @@ -652,39 +653,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "salary_slip", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Salary Slip", - "length": 0, - "no_copy": 0, - "options": "Salary Slip", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -748,6 +716,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 1, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -780,38 +780,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "color", - "fieldtype": "Color", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Color", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -843,37 +811,38 @@ "unique": 0 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "salary_slip", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Salary Slip", + "length": 0, + "no_copy": 0, + "options": "Salary Slip", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -906,6 +875,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "color", + "fieldtype": "Color", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Color", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -950,7 +951,7 @@ "issingle": 0, "istable": 0, "max_attachments": 3, - "modified": "2019-01-30 11:28:14.745572", + "modified": "2019-05-30 11:28:14.745572", "modified_by": "Administrator", "module": "HR", "name": "Leave Application", diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json index 3af013d12b..20b64f81aa 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -9,6 +9,7 @@ "transaction_type", "transaction_name", "leaves", + "column_break_7", "from_date", "to_date", "is_carry_forward", @@ -20,6 +21,7 @@ "fieldname": "employee", "fieldtype": "Link", "in_list_view": 1, + "in_standard_filter": 1, "label": "Employee", "options": "Employee" }, @@ -32,6 +34,7 @@ "fieldname": "leave_type", "fieldtype": "Link", "in_list_view": 1, + "in_standard_filter": 1, "label": "Leave Type", "options": "Leave Type" }, @@ -83,11 +86,15 @@ "fieldname": "is_expired", "fieldtype": "Check", "label": "Is Expired" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" } ], "in_create": 1, "is_submittable": 1, - "modified": "2019-05-29 15:58:29.656351", + "modified": "2019-05-30 14:45:16.577534", "modified_by": "Administrator", "module": "HR", "name": "Leave Ledger Entry", @@ -149,5 +156,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "title_field": "employee" } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_period/leave_period.json b/erpnext/hr/doctype/leave_period/leave_period.json index df28763c79..9e895c34fb 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.json +++ b/erpnext/hr/doctype/leave_period/leave_period.json @@ -1,294 +1,294 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "HR-LPR-.YYYY.-.#####", - "beta": 0, - "creation": "2018-04-13 15:20:52.864288", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "HR-LPR-.YYYY.-.#####", + "beta": 0, + "creation": "2018-04-13 15:20:52.864288", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "from_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "From Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "to_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "To Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_active", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Is Active", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_active", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Active", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "optional_holiday_list", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Holiday List for Optional Leave", - "length": 0, - "no_copy": 0, - "options": "Holiday List", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "optional_holiday_list", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Holiday List for Optional Leave", + "length": 0, + "no_copy": 0, + "options": "Holiday List", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 16:15:43.305502", - "modified_by": "Administrator", - "module": "HR", - "name": "Leave Period", - "name_case": "", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-05-30 16:15:43.305502", + "modified_by": "Administrator", + "module": "HR", + "name": "Leave Period", + "name_case": "", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0, "track_views": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 0b8e38ea73..7899c2f1e6 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -418,7 +418,6 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "1", "depends_on": "", "description": "calculated in days", "fetch_if_empty": 0, @@ -431,7 +430,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Carry Forward Leave Expiry", + "label": "Expire Carried Forward Leaves", "length": 0, "no_copy": 0, "permlevel": 0, @@ -729,7 +728,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-04-11 15:38:39.334283", + "modified": "2019-05-30 15:38:39.334283", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index dff749fce7..065a0137a5 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -8,6 +8,7 @@ def execute(): """ Generates leave ledger entries for leave allocation/application/encashment for last allocation """ frappe.reload_doc("HR","doctype", "Leave Ledger Entry") + frappe.reload_doc("HR","doctype", "Leave Encashment") if frappe.db.a_row_exists("Leave Ledger Entry"): return From c5385e141b0b9ecf192375dfc52207c880f8200c Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 31 May 2019 00:53:28 +0530 Subject: [PATCH 058/117] fix: add mandatory reason fields in leave application --- .../test_leave_application.py | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 30dcafafc7..899ce2ae8d 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -19,6 +19,7 @@ _test_records = [ "doctype": "Leave Application", "employee": "_T-Employee-00001", "from_date": "2013-05-01", + "description": "_Test Reason", "leave_type": "_Test Leave Type", "posting_date": "2013-01-02", "to_date": "2013-05-05" @@ -28,6 +29,7 @@ _test_records = [ "doctype": "Leave Application", "employee": "_T-Employee-00002", "from_date": "2013-05-01", + "description": "_Test Reason", "leave_type": "_Test Leave Type", "posting_date": "2013-01-02", "to_date": "2013-05-05" @@ -37,6 +39,7 @@ _test_records = [ "doctype": "Leave Application", "employee": "_T-Employee-00001", "from_date": "2013-01-15", + "description": "_Test Reason", "leave_type": "_Test Leave Type LWP", "posting_date": "2013-01-02", "to_date": "2013-01-15" @@ -46,8 +49,8 @@ _test_records = [ class TestLeaveApplication(unittest.TestCase): def setUp(self): - for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]: - frappe.db.sql("delete from `tab%s`" % dt) + for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Leave Ledger Entry"]: + frappe.db.sql("DELETE FROM `tab%s`" % dt) @classmethod def setUpClass(cls): @@ -270,6 +273,7 @@ class TestLeaveApplication(unittest.TestCase): doctype = 'Leave Application', employee = employee.name, company = '_Test Company', + description = "_Test Reason", leave_type = leave_type, from_date = date, to_date = date, @@ -288,8 +292,6 @@ class TestLeaveApplication(unittest.TestCase): self.assertEqual(get_leave_balance_on(employee.name, leave_type, today), 9) def test_leaves_allowed(self): - frappe.db.sql("delete from `tabLeave Allocation`") - frappe.db.sql("delete from `tabLeave Ledger Entry`") employee = get_employee() leave_period = get_leave_period() frappe.delete_doc_if_exists("Leave Type", "Test Leave Type", force=1) @@ -307,6 +309,7 @@ class TestLeaveApplication(unittest.TestCase): doctype = 'Leave Application', employee = employee.name, leave_type = leave_type.name, + description = "_Test Reason", from_date = date, to_date = add_days(date, 2), company = "_Test Company", @@ -319,6 +322,7 @@ class TestLeaveApplication(unittest.TestCase): doctype = 'Leave Application', employee = employee.name, leave_type = leave_type.name, + description = "_Test Reason", from_date = add_days(date, 4), to_date = add_days(date, 8), company = "_Test Company", @@ -344,6 +348,7 @@ class TestLeaveApplication(unittest.TestCase): doctype = 'Leave Application', employee = employee.name, leave_type = leave_type.name, + description = "_Test Reason", from_date = date, to_date = add_days(date, 4), company = "_Test Company", @@ -365,6 +370,7 @@ class TestLeaveApplication(unittest.TestCase): doctype = 'Leave Application', employee = employee.name, leave_type = leave_type_1.name, + description = "_Test Reason", from_date = date, to_date = add_days(date, 4), company = "_Test Company", @@ -394,6 +400,7 @@ class TestLeaveApplication(unittest.TestCase): doctype = 'Leave Application', employee = employee.name, leave_type = leave_type.name, + description = "_Test Reason", from_date = date, to_date = add_days(date, 4), company = "_Test Company", @@ -404,8 +411,6 @@ class TestLeaveApplication(unittest.TestCase): self.assertRaises(frappe.ValidationError, leave_application.insert) def test_leave_balance_near_allocaton_expiry(self): - frappe.db.sql("delete from `tabLeave Allocation`") - frappe.db.sql("delete from `tabLeave Ledger Entry`") employee = get_employee() leave_type = create_leave_type( leave_type_name="_Test_CF_leave_expiry", @@ -460,9 +465,10 @@ class TestLeaveApplication(unittest.TestCase): allocation.insert(ignore_permissions=True) allocation.submit() leave_application = frappe.get_doc(dict( - doctype = 'Leave Application', + doctype = 'Leave Application', employee = employee.name, leave_type = leave_type, + description = "_Test Reason", from_date = '2018-10-02', to_date = '2018-10-02', company = '_Test Company', @@ -474,7 +480,6 @@ class TestLeaveApplication(unittest.TestCase): self.assertEqual(leave_application.docstatus, 1) def test_creation_of_leave_ledger_entry_on_submit(self): - frappe.db.sql("delete from `tabLeave Allocation`") employee = get_employee() leave_type = create_leave_type(leave_type_name = 'Test Leave Type 1') @@ -490,6 +495,7 @@ class TestLeaveApplication(unittest.TestCase): leave_type = leave_type.name, from_date = add_days(nowdate(), 1), to_date = add_days(nowdate(), 4), + description = "_Test Reason", company = "_Test Company", docstatus = 1, status = "Approved" @@ -506,8 +512,6 @@ class TestLeaveApplication(unittest.TestCase): self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_application.name})) def test_ledger_entry_creation_on_intermediate_allocation_expiry(self): - frappe.db.sql("delete from `tabLeave Allocation`") - frappe.db.sql("delete from `tabLeave Ledger Entry`") employee = get_employee() leave_type = create_leave_type( leave_type_name="_Test_CF_leave_expiry", @@ -523,6 +527,7 @@ class TestLeaveApplication(unittest.TestCase): leave_type = leave_type.name, from_date = add_days(nowdate(), -3), to_date = add_days(nowdate(), 7), + description = "_Test Reason", company = "_Test Company", docstatus = 1, status = "Approved" @@ -558,8 +563,6 @@ def create_carry_forwarded_allocation(employee, leave_type): leave_allocation.submit() def make_allocation_record(employee=None, leave_type=None): - frappe.db.sql("delete from `tabLeave Allocation`") - allocation = frappe.get_doc({ "doctype": "Leave Allocation", "employee": employee or "_T-Employee-00001", From afa1dc4ffa1e7decadf5937452143e426cf34fec Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 3 Jun 2019 20:09:22 +0530 Subject: [PATCH 059/117] feat: expire current allocation --- .../leave_allocation/leave_allocation.js | 23 ++++++++++++ .../leave_allocation/leave_allocation.json | 36 ++++++++++++++++++- .../leave_allocation/leave_allocation.py | 21 +++++++++-- 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 1fc1d89635..489db67109 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -21,6 +21,29 @@ frappe.ui.form.on("Leave Allocation", { }) }, + refresh: function(frm) { + if(frm.doc.docstatus == 1){ + frm.add_custom_button('Expire Allocation', function() { + frm.trigger("expire_allocation"); + }); + } + }, + + expire_allocation: function(frm) { + frappe.call({ + method: 'erpnext.hr.doctype.leave_application.leave_application.expire_previous_allocation', + args: { + ref_doc: frm.doc + }, + freeze: true, + callback: function(r){ + if(!r.exc){ + frappe.msgprint(__("Allocation Expired!")); + } + } + }); + }, + employee: function(frm) { frm.trigger("calculate_total_leaves_allocated"); }, diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 568182d3b7..1d6307cbe1 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -565,6 +565,40 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "employee.leave_policy", + "fieldname": "leave_policy", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Leave Policy", + "length": 0, + "no_copy": 0, + "options": "Leave Policy", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -677,7 +711,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-05-22 11:28:09.360525", + "modified": "2019-05-30 11:28:09.360525", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 2a1301d2fd..0b2972e565 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -131,7 +131,7 @@ class LeaveAllocation(Document): def expire_previous_allocation(self): ''' expire previous allocation leaves ''' - leaves = get_remaining_leaves(self.employee, self.leave_type, self.from_date) + leaves = get_unused_leaves(self.employee, self.leave_type, self.from_date) if flt(leaves) > 0: args = dict( @@ -171,11 +171,26 @@ def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): carry_forwarded_leaves = 0 if carry_forward: validate_carry_forward(leave_type) - carry_forwarded_leaves = get_remaining_leaves(employee, leave_type, date) + carry_forwarded_leaves = get_unused_leaves(employee, leave_type, date) return carry_forwarded_leaves -def get_remaining_leaves(employee, leave_type, date): +@frappe.whitelist() +def expire_current_allocation(ref_doc): + ''' expire previous allocation leaves ''' + leaves = get_unused_leaves(ref_doc.employee, ref_doc.leave_type, ref_doc.to_date) + + if flt(leaves) > 0: + args = dict( + leaves=leaves * -1, + from_date=ref_doc.from_date, + to_date=ref_doc.from_date, + is_carry_forward=0, + is_expired=1 + ) + create_leave_ledger_entry(ref_doc, args) + +def get_unused_leaves(employee, leave_type, date): return frappe.db.get_value("Leave Ledger Entry", filters={ "to_date": ("<=", date), "employee": employee, From 62011c9dc40f46cde0b3395452168e5077ba44c9 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 5 Jun 2019 21:16:27 +0530 Subject: [PATCH 060/117] feat: add link to policy --- .../leave_allocation/leave_allocation.js | 13 +++++-- .../leave_allocation/leave_allocation.json | 35 ++++++++++++++++++- .../leave_allocation/leave_allocation.py | 6 ++-- .../leave_policy/leave_policy_dashboard.py | 5 ++- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 489db67109..50dbaadec7 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -22,10 +22,19 @@ frappe.ui.form.on("Leave Allocation", { }, refresh: function(frm) { - if(frm.doc.docstatus == 1){ - frm.add_custom_button('Expire Allocation', function() { + if(frm.doc.docstatus === 1 && frm.doc.status === "Active") { + // expire current allocation + frm.add_custom_button(__('Expire Allocation'), function() { frm.trigger("expire_allocation"); }); + + // opens leave balance report for employee + frm.add_custom_button(__('Check Leave Balance'), function() { + frappe.route_options = { + employee: frm.doc.employee, + }; + frappe.set_route("query-report", "Employee Leave Balance"); + }); } }, diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 1d6307cbe1..125fb3150b 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -599,6 +599,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Active\nExpired", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -711,7 +744,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-05-30 11:28:09.360525", + "modified": "2019-05-31 11:28:09.360525", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 0b2972e565..b402e908b0 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -183,13 +183,15 @@ def expire_current_allocation(ref_doc): if flt(leaves) > 0: args = dict( leaves=leaves * -1, - from_date=ref_doc.from_date, - to_date=ref_doc.from_date, + from_date=ref_doc.to_date, + to_date=ref_doc.to_date, is_carry_forward=0, is_expired=1 ) create_leave_ledger_entry(ref_doc, args) + frappe.db.set_value("Leave Allocation", ref_doc.name, "status", "Expired") + def get_unused_leaves(employee, leave_type, date): return frappe.db.get_value("Leave Ledger Entry", filters={ "to_date": ("<=", date), diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py index f97d2855a4..48a204596c 100644 --- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py +++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py @@ -12,6 +12,9 @@ def get_data(): }, { 'items': ['Employee Grade'] - } + }, + { + 'items': ['Leave Allocation'] + }, ] } \ No newline at end of file From 351f4d53a05d2513fc7b389590b2f9d6d4906058 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 5 Jun 2019 21:22:07 +0530 Subject: [PATCH 061/117] feat: add employee filter to employee leave balance --- .../hr/doctype/leave_allocation/leave_allocation.js | 2 +- .../hr/doctype/leave_application/leave_application.js | 10 ++++++++++ .../leave_application/test_leave_application.py | 2 +- .../doctype/leave_ledger_entry/leave_ledger_entry.json | 9 ++++++++- .../employee_leave_balance/employee_leave_balance.js | 6 ++++++ .../employee_leave_balance/employee_leave_balance.py | 7 ++++--- 6 files changed, 30 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 50dbaadec7..005f45a657 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -29,7 +29,7 @@ frappe.ui.form.on("Leave Allocation", { }); // opens leave balance report for employee - frm.add_custom_button(__('Check Leave Balance'), function() { + frm.add_custom_button(__('Leave Balance'), function() { frappe.route_options = { employee: frm.doc.employee, }; diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 11146abce2..cb1042c5ef 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -86,6 +86,16 @@ frappe.ui.form.on("Leave Application", { frm.set_value('employee', perm['Employee'].map(perm_doc => perm_doc.doc)[0]); } } + + if (frm.doc.docstatus === 1) { + frm.add_custom_button(__('Leave Balance'), function() { + frappe.route_options = { + employee: frm.doc.employee, + group_by: "" + }; + frappe.set_route("query-report", "Employee Leave Balance"); + }); + } }, employee: function(frm) { diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 899ce2ae8d..54216ee3c7 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -50,7 +50,7 @@ _test_records = [ class TestLeaveApplication(unittest.TestCase): def setUp(self): for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Leave Ledger Entry"]: - frappe.db.sql("DELETE FROM `tab%s`" % dt) + frappe.db.sql("DELETE FROM `tab%s`" % dt) #nosec @classmethod def setUpClass(cls): diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json index 20b64f81aa..4234fc4b40 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -14,6 +14,7 @@ "to_date", "is_carry_forward", "is_expired", + "is_lwp", "amended_from" ], "fields": [ @@ -90,11 +91,17 @@ { "fieldname": "column_break_7", "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "is_lwp", + "fieldtype": "Check", + "label": "Is Leave Without Pay" } ], "in_create": 1, "is_submittable": 1, - "modified": "2019-05-30 14:45:16.577534", + "modified": "2019-06-05 12:56:04.980160", "modified_by": "Administrator", "module": "HR", "name": "Leave Ledger Entry", diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js index 68302f6ee4..05728a297b 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js @@ -30,6 +30,12 @@ frappe.query_reports["Employee Leave Balance"] = { "label": __("Department"), "fieldtype": "Link", "options": "Department", + }, + { + "fieldname":"employee", + "label": __("Employee"), + "fieldtype": "Link", + "options": "Employee", } ] } diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index e65220ae25..48e9bf5e5d 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -37,9 +37,10 @@ def get_conditions(filters): "company": filters.company, } if filters.get("department"): - conditions.update({ - "department": filters.get("department") - }) + conditions.update({"department": filters.get("department")}) + if filters.get("employee"): + conditions.update({"employee": filters.get("employee")}) + return conditions def get_data(filters, leave_types): From 050f65beb44bfed1cabef5dbd51fe543002027df Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 6 Jun 2019 20:32:01 +0530 Subject: [PATCH 062/117] feat: fetch leave allocation from ledger entry --- .../employee_leave_balance.py | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 48e9bf5e5d..3c3e6af25c 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -6,7 +6,7 @@ import frappe from frappe import _ from frappe.utils import flt from erpnext.hr.doctype.leave_application.leave_application \ - import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period, get_total_allocated_leaves + import get_leaves_for_period def execute(filters=None): @@ -59,11 +59,11 @@ def get_data(filters, leave_types): for leave_type in leave_types: # leaves taken - leaves_taken = get_approved_leaves_for_period(employee.name, leave_type, - filters.from_date, filters.to_date) + leaves_taken = get_leaves_for_period(employee.name, leave_type, + filters.from_date, filters.to_date) * -1 # opening balance - opening = get_total_allocated_leaves(employee.name, leave_type, filters.to_date) + opening = get_total_allocated_leaves(employee.name, leave_type, filters.from_date, filters.to_date) # closing balance closing = flt(opening) - flt(leaves_taken) @@ -91,3 +91,19 @@ def get_approvers(department): where parent = %s and parentfield = 'leave_approvers'""", (d), as_dict=True)]) return approvers + +def get_total_allocated_leaves(employee, leave_type, from_date, to_date): + ''' Returns leave allocation between from date and to date ''' + filters= { + 'from_date': ['between', (from_date, to_date)], + 'to_date': ['between', (from_date, to_date)], + 'docstatus': 1, + 'is_expired': 0, + 'leave_type': leave_type, + 'employee': employee, + 'transaction_type': 'Leave Allocation' + } + + leave_allocation_records = frappe.db.get_all('Leave Ledger Entry', filters=filters, fields=['SUM(leaves) as leaves']) + + return flt(leave_allocation_records[0].get('leaves')) if leave_allocation_records else flt(0) \ No newline at end of file From 3863fc5fb24a76b1f5820cb1da4e1257f537f668 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 6 Jun 2019 20:34:10 +0530 Subject: [PATCH 063/117] feat: create ledger entry for each earned leave --- erpnext/hr/utils.py | 60 +++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 7357fd0cc1..e8c9dd0c45 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -270,31 +270,43 @@ def allocate_earned_leaves(): filters={'is_earned_leave' : 1}) today = getdate() divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12} - if e_leave_types: - for e_leave_type in e_leave_types: - leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where '{0}' - between from_date and to_date and docstatus=1 and leave_type='{1}'""" - .format(today, e_leave_type.name), as_dict=1) - for allocation in leave_allocations: - leave_policy = get_employee_leave_policy(allocation.employee) - if not leave_policy: - continue - if not e_leave_type.earned_leave_frequency == "Monthly": - if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency): - continue - annual_allocation = frappe.db.sql("""select annual_allocation from `tabLeave Policy Detail` - where parent=%s and leave_type=%s""", (leave_policy.name, e_leave_type.name)) - if annual_allocation and annual_allocation[0]: - earned_leaves = flt(annual_allocation[0][0]) / divide_by_frequency[e_leave_type.earned_leave_frequency] - if e_leave_type.rounding == "0.5": - earned_leaves = round(earned_leaves * 2) / 2 - else: - earned_leaves = round(earned_leaves) - allocated_leaves = frappe.db.get_value('Leave Allocation', allocation.name, 'total_leaves_allocated') - new_allocation = flt(allocated_leaves) + flt(earned_leaves) - new_allocation = new_allocation if new_allocation <= e_leave_type.max_leaves_allowed else e_leave_type.max_leaves_allowed - frappe.db.set_value('Leave Allocation', allocation.name, 'total_leaves_allocated', new_allocation) + for e_leave_type in e_leave_types: + leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where '{0}' + between from_date and to_date and docstatus=1 and leave_type='{1}'""" + .format(today, e_leave_type.name), as_dict=1) + for allocation in leave_allocations: + leave_policy = get_employee_leave_policy(allocation.employee) + if not leave_policy: + continue + if not e_leave_type.earned_leave_frequency == "Monthly": + if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency): + continue + annual_allocation = frappe.db.sql("""select annual_allocation from `tabLeave Policy Detail` + where parent=%s and leave_type=%s""", (leave_policy.name, e_leave_type.name)) + if annual_allocation and annual_allocation[0]: + earned_leaves = flt(annual_allocation[0][0]) / divide_by_frequency[e_leave_type.earned_leave_frequency] + if e_leave_type.rounding == "0.5": + earned_leaves = round(earned_leaves * 2) / 2 + else: + earned_leaves = round(earned_leaves) + + allocation = frappe.get_doc('Leave Allocation', allocation.name) + new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves) + new_allocation = new_allocation if new_allocation <= e_leave_type.max_leaves_allowed else e_leave_type.max_leaves_allowed + + if new_allocation == allocation.total_leaves_allocated: + continue + allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False) + create_earned_leave_ledger_entry(allocation, earned_leaves, today) + + +def create_earned_leave_ledger_entry(allocation, earned_leaves, date): + ''' Create leave ledger entry based on the earned leave frequency ''' + allocation.new_leaves_allocated = earned_leaves + allocation.from_date = date + allocation.carry_forwarded_leaves = 0 + allocation.create_leave_ledger_entry() def check_frequency_hit(from_date, to_date, frequency): '''Return True if current date matches frequency''' From f13243a92eba52883aefe645d346ef4a67685958 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 6 Jun 2019 20:35:10 +0530 Subject: [PATCH 064/117] feat: fetch annual allocation based on leave policy --- .../hr/doctype/leave_allocation/leave_allocation.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 005f45a657..7ecd3d1c91 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -58,6 +58,7 @@ frappe.ui.form.on("Leave Allocation", { }, leave_type: function(frm) { + frm.trigger("leave_policy") frm.trigger("calculate_total_leaves_allocated"); }, @@ -75,6 +76,17 @@ frappe.ui.form.on("Leave Allocation", { flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); }, + leave_policy: function(frm) { + if(frm.doc.leave_policy && frm.doc.leave_type) { + frappe.db.get_value("Leave Policy Detail", + {'parent': frm.doc.leave_policy, 'leave_type': frm.doc.leave_type}, + 'annual_allocation', (r) => { + if (!r.exc) { + frm.set_value("new_leaves_allocated", flt(r.annual_allocation)); + } + }, "Leave Policy") + } + }, calculate_total_leaves_allocated: function(frm) { if (cint(frm.doc.carry_forward) == 1 && frm.doc.leave_type && frm.doc.employee) { return frappe.call({ From 00c607116bbf8627028290c30e6d1494c4530236 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 6 Jun 2019 20:37:34 +0530 Subject: [PATCH 065/117] fix: handle negative leaves without allocation --- erpnext/hr/doctype/leave_allocation/leave_allocation.py | 6 +++--- .../doctype/leave_ledger_entry/leave_ledger_entry.json | 4 ++-- .../hr/doctype/leave_ledger_entry/leave_ledger_entry.py | 8 ++------ erpnext/hr/doctype/leave_type/leave_type.py | 9 +-------- 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index b402e908b0..843d3055d5 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -7,7 +7,6 @@ from frappe.utils import flt, date_diff, formatdate, add_days from frappe import _ from frappe.model.document import Document from erpnext.hr.utils import set_employee_name, get_leave_period -from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry class OverlapError(frappe.ValidationError): pass @@ -133,9 +132,9 @@ class LeaveAllocation(Document): ''' expire previous allocation leaves ''' leaves = get_unused_leaves(self.employee, self.leave_type, self.from_date) - if flt(leaves) > 0: + if leaves: args = dict( - leaves=leaves * -1, + leaves=flt(leaves) * -1, from_date=self.from_date, to_date=self.from_date, is_carry_forward=0, @@ -198,6 +197,7 @@ def get_unused_leaves(employee, leave_type, date): "employee": employee, "docstatus": 1, "leave_type": leave_type, + "is_lwp": 0 }, fieldname=['SUM(leaves)']) def validate_carry_forward(leave_type): diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json index 4234fc4b40..0166e43c0f 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -62,7 +62,7 @@ }, { "fieldname": "leaves", - "fieldtype": "Int", + "fieldtype": "Float", "in_list_view": 1, "label": "Leaves" }, @@ -101,7 +101,7 @@ ], "in_create": 1, "is_submittable": 1, - "modified": "2019-06-05 12:56:04.980160", + "modified": "2019-06-06 20:33:37.531161", "modified_by": "Administrator", "module": "HR", "name": "Leave Ledger Entry", diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index e85d5cef0a..6142dcf648 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -9,11 +9,6 @@ from frappe import _ from frappe.utils import add_days, today, flt class LeaveLedgerEntry(Document): - def validate_entries(self): - total_leaves = frappe.get_all('Leave Ledger Entry', ['SUM(leaves)']) - if total_leaves < 0: - frappe.throw(_("Invalid Ledger Entry")) - def on_cancel(self): # allow cancellation of expiry leaves if not self.is_expired: @@ -43,7 +38,8 @@ def create_leave_ledger_entry(ref_doc, args, submit=True): transaction_type=ref_doc.doctype, transaction_name=ref_doc.name, is_carry_forward=0, - is_expired=0 + is_expired=0, + is_lwp=0 ) ledger.update(args) if submit: diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index dcae5fe085..5b13edb684 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -10,11 +10,4 @@ from frappe import _ from frappe.model.document import Document class LeaveType(Document): - def validate(self): - if self.is_carry_forward: - self.validate_carry_forward() - - def validate_carry_forward(self): - max_days = 367 if calendar.isleap(datetime.now().year) else 366 - if not (0 <= self.carry_forward_leave_expiry <= max_days): - frappe.throw(_('Invalid entry!! Carried forward days need to expire within a year')) \ No newline at end of file + pass \ No newline at end of file From b840ba4407c3b0cf124b8bb9f3b32267f04ed821 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 6 Jun 2019 20:38:23 +0530 Subject: [PATCH 066/117] feat: show dashboard on submit --- .../leave_application/leave_application.js | 24 ++++++++------ .../leave_application/leave_application.json | 33 ++++++++++++++++++- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index cb1042c5ef..2076ccd22a 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -53,24 +53,27 @@ frappe.ui.form.on("Leave Application", { }, callback: function(r) { if (!r.exc && r.message['leave_allocation']) { - leave_details = r.message['leave_allocation']; + frm.set_value('leave_details', JSON.stringify(r.message['leave_allocation'])); } if (!r.exc && r.message['leave_approver']) { frm.set_value('leave_approver', r.message['leave_approver']); } } }); - - $("div").remove(".form-dashboard-section"); - let section = frm.dashboard.add_section( - frappe.render_template('leave_application_dashboard', { - data: leave_details - }) - ); - frm.dashboard.show(); + frm.trigger("create_dashboard") } }, + create_dashboard: function(frm) { + $("div").remove(".form-dashboard-section"); + let section = frm.dashboard.add_section( + frappe.render_template('leave_application_dashboard', { + data: JSON.parse(frm.doc.leave_details) + }) + ); + frm.dashboard.show(); + }, + refresh: function(frm) { if (frm.is_new()) { frm.trigger("calculate_total_days"); @@ -95,6 +98,7 @@ frappe.ui.form.on("Leave Application", { }; frappe.set_route("query-report", "Employee Leave Balance"); }); + frm.trigger("create_dashboard"); } }, @@ -148,7 +152,7 @@ frappe.ui.form.on("Leave Application", { }, get_leave_balance: function(frm) { - if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date) { + if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date && frm.doc.to_date) { return frappe.call({ method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on", args: { diff --git a/erpnext/hr/doctype/leave_application/leave_application.json b/erpnext/hr/doctype/leave_application/leave_application.json index 812494c1c0..c59449f850 100644 --- a/erpnext/hr/doctype/leave_application/leave_application.json +++ b/erpnext/hr/doctype/leave_application/leave_application.json @@ -653,6 +653,37 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "leave_details", + "fieldtype": "Small Text", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Leave Details", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -951,7 +982,7 @@ "issingle": 0, "istable": 0, "max_attachments": 3, - "modified": "2019-05-30 11:28:14.745572", + "modified": "2019-05-31 11:30:14.745572", "modified_by": "Administrator", "module": "HR", "name": "Leave Application", From c7b9ae9c5e6e0f08004bb7f997eb8909fb9919fb Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 6 Jun 2019 20:38:59 +0530 Subject: [PATCH 067/117] fix: get leave balance based on the ledger entries --- .../leave_application/leave_application.py | 157 +++++++++--------- 1 file changed, 75 insertions(+), 82 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index fd13436d8f..fec7cede2f 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \ - comma_or, get_fullname, add_days, nowdate + comma_or, get_fullname, add_days, nowdate, get_datetime_str from erpnext.hr.utils import set_employee_name, get_leave_period from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee @@ -353,6 +353,8 @@ class LeaveApplication(Document): expiry_date = get_allocation_expiry(self.employee, self.leave_type, self.to_date, self.from_date) + lwp = frappe.db.get_value("Leave Type", self.leave_type, "is_lwp") + if expiry_date: self.create_ledger_entry_for_intermediate_allocation_expiry(expiry_date, submit) else: @@ -360,6 +362,7 @@ class LeaveApplication(Document): leaves=self.total_leave_days * -1, from_date=self.from_date, to_date=self.to_date, + is_lwp=lwp ) create_leave_ledger_entry(self, args, submit) @@ -407,21 +410,22 @@ def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day @frappe.whitelist() def get_leave_details(employee, date): - allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict()) + allocation_records = get_leave_allocation_records(employee, date) leave_allocation = {} for d in allocation_records: allocation = allocation_records.get(d, frappe._dict()) remaining_leaves = get_leave_balance_on(employee, d, date, to_date = allocation.to_date, consider_all_leaves_in_the_allocation_period=True) date = allocation.to_date - leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, date, status="Approved") - leaves_pending = get_leaves_for_period(employee, d, allocation.from_date, date, status="Open") + leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, date) * -1 + leaves_pending = get_pending_leaves_for_period(employee, d, allocation.from_date, date) leave_allocation[d] = { "total_leaves": allocation.total_leaves_allocated, "leaves_taken": leaves_taken, "pending_leaves": leaves_pending, "remaining_leaves": remaining_leaves} + leave_details = leave_allocation ret = { 'leave_allocation': leave_allocation, @@ -436,17 +440,64 @@ def get_leave_balance_on(employee, leave_type, date, to_date=nowdate(), allocati ''' Returns leave balance till date and fetches expiry date based on to_date to calculate minimum remaining leave balance ''' - if allocation_records == None: - allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict()) + if not allocation_records: + allocation_records = get_leave_allocation_records(employee, date, leave_type) + allocation = allocation_records.get(leave_type, frappe._dict()) end_date = allocation.to_date if consider_all_leaves_in_the_allocation_period else date expiry = get_allocation_expiry(employee, leave_type, to_date, date) - leaves_taken = get_leaves_taken(employee, leave_type, allocation.from_date, end_date) + leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, end_date) return get_remaining_leaves(allocation, leaves_taken, date, expiry) +def get_leave_allocation_records(employee, date, leave_type=None): + ''' returns the total allocated leaves and carry forwarded leaves based on ledger entries ''' + + conditions = ("and leave_type='%s'" % leave_type) if leave_type else "" + allocation_details = frappe.db.sql(""" + SELECT + SUM(CASE WHEN is_carry_forward = 1 THEN leaves ELSE 0 END) as cf_leaves, + SUM(CASE WHEN is_carry_forward = 0 THEN leaves ELSE 0 END) as new_leaves, + MIN(from_date) as from_date, + MAX(to_date) as to_date, + leave_type + FROM `tabLeave Ledger Entry` + WHERE + from_date <= %(date)s + AND to_date >= %(date)s + AND docstatus=1 + AND transaction_type="Leave Allocation" + AND employee=%(employee)s + AND is_expired=0 + AND is_lwp=0 + {0} + GROUP BY employee, leave_type + """.format(conditions), dict(date=date, employee=employee), as_dict=1) + + allocated_leaves = frappe._dict() + for d in allocation_details: + allocated_leaves.setdefault(d.leave_type, frappe._dict({ + "from_date": d.from_date, + "to_date": d.to_date, + "total_leaves_allocated": flt(d.cf_leaves) + flt(d.new_leaves), + "carry_forwarded_leaves": d.cf_leaves, + "new_leaves_allocated": d.new_leaves, + "leave_type": d.leave_type + })) + return allocated_leaves + +def get_pending_leaves_for_period(employee, leave_type, from_date, to_date): + ''' Returns leaves that are pending approval ''' + return frappe.db.get_value("Leave Application", + filters={ + "employee": employee, + "leave_type": leave_type, + "from_date": ("<=", from_date), + "to_date": (">=", to_date), + }, fieldname=['SUM(total_leave_days)']) or flt(0) + def get_remaining_leaves(allocation, leaves_taken, date, expiry): ''' Returns minimum leaves remaining after comparing with remaining days for allocation expiry ''' def _get_remaining_leaves(allocated_leaves, end_date): @@ -465,39 +516,14 @@ def get_remaining_leaves(allocation, leaves_taken, date, expiry): else: return _get_remaining_leaves(allocation.total_leaves_allocated, allocation.to_date) -def get_leaves_taken(employee, leave_type, from_date, to_date): - ''' Returns leaves taken based on leave application/encashment ''' - leaves = frappe.db.get_all("Leave Ledger Entry", filters={ - 'Employee':employee, - 'leave_type':leave_type, - 'leaves': ("<", 0), - 'to_date':("<=", to_date), - 'from_date': (">=", from_date)}, - or_filters={ - 'is_expired': 0, - 'is_carry_forward': 1 - }, fields=['SUM(leaves) as leaves']) - return leaves[0]['leaves'] if leaves else None - -def get_total_allocated_leaves(employee, leave_type, date): - filters= { - 'from_date': ['<=', date], - 'to_date': ['>=', date], - 'docstatus': 1, - 'leave_type': leave_type, - 'employee': employee - } - - leave_allocation_records = frappe.db.get_all('Leave Allocation', filters=filters, fields=['total_leaves_allocated']) - - return flt(leave_allocation_records[0]['total_leaves_allocated']) if leave_allocation_records else flt(0) - -def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docname=None): - leave_applications = frappe.db.sql(""" - select name, employee, leave_type, from_date, to_date, total_leave_days - from `tabLeave Application` +def get_leaves_for_period(employee, leave_type, from_date, to_date): + leave_entries = frappe.db.sql(""" + select employee, leave_type, from_date, to_date, leaves, transaction_type + from `tabLeave Ledger Entry` where employee=%(employee)s and leave_type=%(leave_type)s - and status = %(status)s and docstatus != 2 + and docstatus=1 + and leaves<0 + and (is_expired=0 or is_carry_forward=1) and (from_date between %(from_date)s and %(to_date)s or to_date between %(from_date)s and %(to_date)s or (from_date < %(from_date)s and to_date > %(to_date)s)) @@ -505,58 +531,25 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docn "from_date": from_date, "to_date": to_date, "employee": employee, - "status": status, "leave_type": leave_type }, as_dict=1) leave_days = 0 - for leave_app in leave_applications: - if docname and leave_app.name == docname: - continue - if leave_app.from_date >= getdate(from_date) and leave_app.to_date <= getdate(to_date): - leave_days += leave_app.total_leave_days + + for leave_entry in leave_entries: + if leave_entry.from_date >= getdate(from_date) and \ + leave_entry.to_date <= getdate(to_date) and leave_entry.transaction_type=='Leave Encashment': + leave_days += leave_entry.leaves else: - if leave_app.from_date < getdate(from_date): - leave_app.from_date = from_date - if leave_app.to_date > getdate(to_date): - leave_app.to_date = to_date + if leave_entry.from_date < getdate(from_date): + leave_entry.from_date = from_date + if leave_entry.to_date > getdate(to_date): + leave_entry.to_date = to_date leave_days += get_number_of_leave_days(employee, leave_type, - leave_app.from_date, leave_app.to_date) + leave_entry.from_date, leave_entry.to_date) * -1 return leave_days -def get_leave_allocation_records(date, employee=None): - conditions = (" and employee='%s'" % employee) if employee else "" - - leave_allocation_records = frappe.db.sql(""" - SELECT - employee, - leave_type, - total_leaves_allocated, - carry_forwarded_leaves, - new_leaves_allocated, - carry_forward, - from_date, - to_date - FROM - `tabLeave Allocation` - WHERE - %s between from_date and to_date - AND docstatus=1 {0}""".format(conditions), (date), as_dict=1) #nosec - - allocated_leaves = frappe._dict() - for d in leave_allocation_records: - allocated_leaves.setdefault(d.employee, frappe._dict()).setdefault(d.leave_type, frappe._dict({ - "from_date": d.from_date, - "to_date": d.to_date, - "total_leaves_allocated": d.total_leaves_allocated, - "carry_forward": d.carry_forward, - "carry_forwarded_leaves": d.carry_forwarded_leaves, - "new_leaves_allocated": d.new_leaves_allocated, - "leave_type": d.leave_type - })) - return allocated_leaves - @frappe.whitelist() def get_holidays(employee, from_date, to_date): '''get holidays between two dates for the given employee''' From 368a97436820a35148f8f470b683bed0ed47a381 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 6 Jun 2019 20:43:32 +0530 Subject: [PATCH 068/117] style: change formatting --- erpnext/hr/doctype/leave_application/leave_application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index fec7cede2f..02539a4c3b 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -474,7 +474,7 @@ def get_leave_allocation_records(employee, date, leave_type=None): AND is_lwp=0 {0} GROUP BY employee, leave_type - """.format(conditions), dict(date=date, employee=employee), as_dict=1) + """.format(conditions), dict(date=date, employee=employee), as_dict=1) #nosec allocated_leaves = frappe._dict() for d in allocation_details: From e4d03bf0d0d9d4a3ca8fb548630ebb7ce30018c1 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 7 Jun 2019 11:30:27 +0530 Subject: [PATCH 069/117] fix: consider expiry in leaves --- .../leave_allocation/leave_allocation.js | 19 ++++++---- .../leave_allocation/leave_allocation.py | 36 +++++++------------ .../leave_application/leave_application.py | 5 ++- 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 7ecd3d1c91..1b3349a1ce 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -23,10 +23,13 @@ frappe.ui.form.on("Leave Allocation", { refresh: function(frm) { if(frm.doc.docstatus === 1 && frm.doc.status === "Active") { - // expire current allocation - frm.add_custom_button(__('Expire Allocation'), function() { - frm.trigger("expire_allocation"); - }); + var valid_expiry = moment(frappe.datetime.get_today()).isBetween(frm.doc.from_date, frm.doc.to_date); + if(valid_expiry) { + // expire current allocation + frm.add_custom_button(__('Expire Allocation'), function() { + frm.trigger("expire_allocation"); + }); + } // opens leave balance report for employee frm.add_custom_button(__('Leave Balance'), function() { @@ -40,15 +43,17 @@ frappe.ui.form.on("Leave Allocation", { expire_allocation: function(frm) { frappe.call({ - method: 'erpnext.hr.doctype.leave_application.leave_application.expire_previous_allocation', + method: 'expire_allocation', + doc: frm.doc, args: { - ref_doc: frm.doc + current: true }, freeze: true, callback: function(r){ if(!r.exc){ frappe.msgprint(__("Allocation Expired!")); } + frm.refresh(); } }); }, @@ -81,7 +86,7 @@ frappe.ui.form.on("Leave Allocation", { frappe.db.get_value("Leave Policy Detail", {'parent': frm.doc.leave_policy, 'leave_type': frm.doc.leave_type}, 'annual_allocation', (r) => { - if (!r.exc) { + if (r && !r.exc) { frm.set_value("new_leaves_allocated", flt(r.annual_allocation)); } }, "Leave Policy") diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 843d3055d5..72ea2733b0 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import flt, date_diff, formatdate, add_days +from frappe.utils import flt, date_diff, formatdate, add_days, today from frappe import _ from frappe.model.document import Document from erpnext.hr.utils import set_employee_name, get_leave_period @@ -41,8 +41,8 @@ class LeaveAllocation(Document): .format(self.leave_type, self.employee)) def on_submit(self): - self.expire_previous_allocation() self.create_leave_ledger_entry() + self.expire_allocation() def on_cancel(self): self.create_leave_ledger_entry(submit=False) @@ -128,20 +128,25 @@ class LeaveAllocation(Document): ) create_leave_ledger_entry(self, args, submit) - def expire_previous_allocation(self): - ''' expire previous allocation leaves ''' - leaves = get_unused_leaves(self.employee, self.leave_type, self.from_date) + def expire_allocation(self, current=False): + ''' expires allocation ''' + date = self.to_date if current else self.from_date + leaves = get_unused_leaves(self.employee, self.leave_type, date) if leaves: + expiry_date = today() if current else add_days(self.from_date, -1) args = dict( leaves=flt(leaves) * -1, - from_date=self.from_date, - to_date=self.from_date, + from_date=expiry_date, + to_date=expiry_date, is_carry_forward=0, is_expired=1 ) create_leave_ledger_entry(self, args) + if current: + frappe.db.set_value("Leave Allocation", self.name, "status", "Expired") + def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 leave_allocations = frappe.db.sql(""" @@ -174,23 +179,6 @@ def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): return carry_forwarded_leaves -@frappe.whitelist() -def expire_current_allocation(ref_doc): - ''' expire previous allocation leaves ''' - leaves = get_unused_leaves(ref_doc.employee, ref_doc.leave_type, ref_doc.to_date) - - if flt(leaves) > 0: - args = dict( - leaves=leaves * -1, - from_date=ref_doc.to_date, - to_date=ref_doc.to_date, - is_carry_forward=0, - is_expired=1 - ) - create_leave_ledger_entry(ref_doc, args) - - frappe.db.set_value("Leave Allocation", ref_doc.name, "status", "Expired") - def get_unused_leaves(employee, leave_type, date): return frappe.db.get_value("Leave Ledger Entry", filters={ "to_date": ("<=", date), diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 02539a4c3b..a9e4f05ca2 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -523,7 +523,6 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date): where employee=%(employee)s and leave_type=%(leave_type)s and docstatus=1 and leaves<0 - and (is_expired=0 or is_carry_forward=1) and (from_date between %(from_date)s and %(to_date)s or to_date between %(from_date)s and %(to_date)s or (from_date < %(from_date)s and to_date > %(to_date)s)) @@ -536,8 +535,8 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date): leave_days = 0 for leave_entry in leave_entries: - if leave_entry.from_date >= getdate(from_date) and \ - leave_entry.to_date <= getdate(to_date) and leave_entry.transaction_type=='Leave Encashment': + if leave_entry.from_date >= getdate(from_date) and leave_entry.to_date <= getdate(to_date) \ + and leave_entry.transaction_type in ('Leave Encashment', 'Leave Allocation'): leave_days += leave_entry.leaves else: if leave_entry.from_date < getdate(from_date): From 283d2c5ca24926e5737bc58df12c8164744417d3 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 11 Jun 2019 16:35:29 +0530 Subject: [PATCH 070/117] fix: only create leave application ledger on intermediate allocation expiry --- .../leave_application/leave_application.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index a9e4f05ca2..f6550f0dbe 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -370,18 +370,19 @@ class LeaveApplication(Document): ''' splits leave application into two ledger entries to consider expiry of allocation ''' args = dict( from_date=self.from_date, - to_date=self.to_date, + to_date=expiry_date, leaves=(date_diff(expiry_date, self.from_date) + 1) * -1 ) create_leave_ledger_entry(self, args, submit) - start_date = add_days(expiry_date, 1) - args.update(dict( - from_date=start_date, - to_date=self.to_date, - leaves=date_diff(self.to_date, expiry_date) * -1 - )) - create_leave_ledger_entry(self, args, submit) + if expiry_date != self.to_date: + start_date = add_days(expiry_date, 1) + args.update(dict( + from_date=start_date, + to_date=self.to_date, + leaves=date_diff(self.to_date, expiry_date) * -1 + )) + create_leave_ledger_entry(self, args, submit) def get_allocation_expiry(employee, leave_type, to_date, from_date): expiry = frappe.get_all("Leave Ledger Entry", From 4e1b60d401e9ce5f6f59aa8847985b421dc1aa29 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 14 Jun 2019 11:15:54 +0530 Subject: [PATCH 071/117] style: change formatting --- .../doctype/leave_allocation/leave_allocation.js | 15 +++++++-------- .../leave_application/leave_application.js | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 1b3349a1ce..a620d9e081 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -63,7 +63,7 @@ frappe.ui.form.on("Leave Allocation", { }, leave_type: function(frm) { - frm.trigger("leave_policy") + frm.trigger("leave_policy"); frm.trigger("calculate_total_leaves_allocated"); }, @@ -83,13 +83,12 @@ frappe.ui.form.on("Leave Allocation", { leave_policy: function(frm) { if(frm.doc.leave_policy && frm.doc.leave_type) { - frappe.db.get_value("Leave Policy Detail", - {'parent': frm.doc.leave_policy, 'leave_type': frm.doc.leave_type}, - 'annual_allocation', (r) => { - if (r && !r.exc) { - frm.set_value("new_leaves_allocated", flt(r.annual_allocation)); - } - }, "Leave Policy") + frappe.db.get_value("Leave Policy Detail",{ + 'parent': frm.doc.leave_policy, + 'leave_type': frm.doc.leave_type + }, 'annual_allocation', (r) => { + if (r && !r.exc) frm.set_value("new_leaves_allocated", flt(r.annual_allocation)); + }, "Leave Policy"); } }, calculate_total_leaves_allocated: function(frm) { diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 2076ccd22a..5534cec061 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -60,7 +60,7 @@ frappe.ui.form.on("Leave Application", { } } }); - frm.trigger("create_dashboard") + frm.trigger("create_dashboard"); } }, From 87adaed9336dd2692b185f9ece5df93877990775 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 14 Jun 2019 15:13:53 +0530 Subject: [PATCH 072/117] fix: generate ledger entries for all leave transactions --- .../v12_0/generate_leave_ledger_entries.py | 106 +++++++----------- 1 file changed, 42 insertions(+), 64 deletions(-) diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 065a0137a5..07e53c7450 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -3,84 +3,70 @@ from __future__ import unicode_literals import frappe +from frappe.utils import getdate def execute(): """ Generates leave ledger entries for leave allocation/application/encashment for last allocation """ - frappe.reload_doc("HR","doctype", "Leave Ledger Entry") - frappe.reload_doc("HR","doctype", "Leave Encashment") + frappe.reload_doc("HR", "doctype", "Leave Ledger Entry") + frappe.reload_doc("HR", "doctype", "Leave Encashment") if frappe.db.a_row_exists("Leave Ledger Entry"): return - allocation_list = get_allocation_records() - generate_allocation_ledger_entries(allocation_list) - generate_application_leave_ledger_entries(allocation_list) - generate_encashment_leave_ledger_entries(allocation_list) + generate_allocation_ledger_entries() + generate_application_leave_ledger_entries() + generate_encashment_leave_ledger_entries() -def generate_allocation_ledger_entries(allocation_list): +def generate_allocation_ledger_entries(): ''' fix ledger entries for missing leave allocation transaction ''' - from erpnext.hr.doctype.leave_allocation.leave_allocation import LeaveAllocation + allocation_list = get_allocation_records() for allocation in allocation_list: if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name}): allocation.update(dict(doctype="Leave Allocation")) - leave_allocation = LeaveAllocation(allocation) - leave_allocation.create_leave_ledger_entry() + allocation_obj = frappe.get_doc(allocation) + allocation_obj.create_leave_ledger_entry() + if allocation.to_date <= getdate(): + allocation_obj.expire_allocation() -def generate_application_leave_ledger_entries(allocation_list): + +def generate_application_leave_ledger_entries(): ''' fix ledger entries for missing leave application transaction ''' - from erpnext.hr.doctype.leave_application.leave_application import LeaveApplication + leave_applications = get_leaves_application_records() - leave_applications = get_leaves_application_records(allocation_list) - - for record in leave_applications: + for application in leave_applications: if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', 'transaction_name': record.name}): - record.update(dict(doctype="Leave Application")) - leave_application = LeaveApplication(record) - leave_application.create_leave_ledger_entry() + application.update(dict(doctype="Leave Application")) + frappe.get_doc(application).create_leave_ledger_entry() -def generate_encashment_leave_ledger_entries(allocation_list): +def generate_encashment_leave_ledger_entries(): ''' fix ledger entries for missing leave encashment transaction ''' - from erpnext.hr.doctype.leave_encashment.leave_encashment import LeaveEncashment + leave_encashments = get_leave_encashment_records() - leave_encashments = get_leave_encashment_records(allocation_list) - - for record in leave_encashments: + for encashment in leave_encashments: if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': record.name}): - record.update(dict(doctype="Leave Encashment")) - leave_encashment = LeaveEncashment(record) - leave_encashment.create_leave_ledger_entry() + encashment.update(dict(doctype="Leave Encashment")) + frappe.get_doc(encashment).create_leave_ledger_entry() def get_allocation_records(): return frappe.db.sql(""" - WITH allocation_values AS ( - SELECT - DISTINCT name, - employee, - leave_type, - new_leaves_allocated, - carry_forwarded_leaves, - from_date, - to_date, - carry_forward, - RANK() OVER( - PARTITION BY employee, leave_type - ORDER BY to_date DESC - ) as allocation - FROM `tabLeave Allocation` - ) SELECT - * - FROM - `allocation_values` + DISTINCT name, + employee, + leave_type, + new_leaves_allocated, + carry_forwarded_leaves, + from_date, + to_date, + carry_forward + FROM `tabLeave Allocation` WHERE - allocation=1 + docstatus=1 + ORDER BY to_date ASC """, as_dict=1) -def get_leaves_application_records(allocation_list): - leave_applications = [] - for allocation in allocation_list: - leave_applications += frappe.db.sql(""" +def get_leaves_application_records(): + return frappe.db.sql(""" SELECT DISTINCT name, employee, @@ -90,16 +76,11 @@ def get_leaves_application_records(allocation_list): to_date FROM `tabLeave Application` WHERE - from_date >= %s - AND leave_type = %s - AND employee = %s - """, (allocation.from_date, allocation.leave_type, allocation.employee), as_dict=1) - return leave_applications + docstatus=1 + """, as_dict=1) -def get_leave_encashment_records(allocation_list): - leave_encashments = [] - for allocation in allocation_list: - leave_encashments += frappe.db.sql(""" +def get_leave_encashment_records(): + return frappe.db.sql(""" SELECT DISTINCT name, employee, @@ -108,8 +89,5 @@ def get_leave_encashment_records(allocation_list): encashment_date FROM `tabLeave Encashment` WHERE - leave_type = %s - AND employee = %s - AND encashment_date >= %s - """, (allocation.leave_type, allocation.employee, allocation.from_date), as_dict=1) - return leave_encashments \ No newline at end of file + AND docstatus=1 + """, as_dict=1) \ No newline at end of file From 24fbe23c8d4c1a9553f2bc3721f98d25600c5785 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 14 Jun 2019 15:36:26 +0530 Subject: [PATCH 073/117] fix: add indicators for expire in the leave allocation list --- .../leave_allocation/leave_allocation_list.js | 12 +++++++ .../leave_application/leave_application.js | 21 +++++------- .../leave_application/leave_application.json | 33 +------------------ .../leave_application/leave_application.py | 1 - 4 files changed, 21 insertions(+), 46 deletions(-) create mode 100644 erpnext/hr/doctype/leave_allocation/leave_allocation_list.js diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js b/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js new file mode 100644 index 0000000000..946b4f8492 --- /dev/null +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js @@ -0,0 +1,12 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +// render +frappe.listview_settings['Leave Allocation'] = { + get_indicator: function(doc) { + if(doc.status==="Expired") { + return [__("Expired"), "darkgrey", "status, =, Expired"]; + } + }, + right_column: "grand_total" +}; diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 5534cec061..b81e615b72 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -53,27 +53,23 @@ frappe.ui.form.on("Leave Application", { }, callback: function(r) { if (!r.exc && r.message['leave_allocation']) { - frm.set_value('leave_details', JSON.stringify(r.message['leave_allocation'])); + leave_details = r.message['leave_allocation']; } if (!r.exc && r.message['leave_approver']) { frm.set_value('leave_approver', r.message['leave_approver']); } } }); - frm.trigger("create_dashboard"); + $("div").remove(".form-dashboard-section"); + frm.dashboard.add_section( + frappe.render_template('leave_application_dashboard', { + data: leave_details + }) + ); + frm.dashboard.show(); } }, - create_dashboard: function(frm) { - $("div").remove(".form-dashboard-section"); - let section = frm.dashboard.add_section( - frappe.render_template('leave_application_dashboard', { - data: JSON.parse(frm.doc.leave_details) - }) - ); - frm.dashboard.show(); - }, - refresh: function(frm) { if (frm.is_new()) { frm.trigger("calculate_total_days"); @@ -98,7 +94,6 @@ frappe.ui.form.on("Leave Application", { }; frappe.set_route("query-report", "Employee Leave Balance"); }); - frm.trigger("create_dashboard"); } }, diff --git a/erpnext/hr/doctype/leave_application/leave_application.json b/erpnext/hr/doctype/leave_application/leave_application.json index c59449f850..60efb33ca4 100644 --- a/erpnext/hr/doctype/leave_application/leave_application.json +++ b/erpnext/hr/doctype/leave_application/leave_application.json @@ -653,37 +653,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "leave_details", - "fieldtype": "Small Text", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Leave Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -982,7 +951,7 @@ "issingle": 0, "istable": 0, "max_attachments": 3, - "modified": "2019-05-31 11:30:14.745572", + "modified": "2019-06-01 11:30:14.745572", "modified_by": "Administrator", "module": "HR", "name": "Leave Application", diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index f6550f0dbe..f87f7d53ef 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -426,7 +426,6 @@ def get_leave_details(employee, date): "leaves_taken": leaves_taken, "pending_leaves": leaves_pending, "remaining_leaves": remaining_leaves} - leave_details = leave_allocation ret = { 'leave_allocation': leave_allocation, From fefdac432ce02d07b24fe3d757ce97db2a8c483a Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 14 Jun 2019 15:39:50 +0530 Subject: [PATCH 074/117] refactor: add json changes --- .../leave_allocation/leave_allocation.json | 646 +------------ .../leave_application/leave_application.json | 915 ++---------------- 2 files changed, 123 insertions(+), 1438 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 125fb3150b..41d864d912 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -1,750 +1,212 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, "allow_import": 1, - "allow_rename": 0, "autoname": "naming_series:", - "beta": 0, "creation": "2013-02-20 19:10:38", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "naming_series", + "employee", + "employee_name", + "department", + "column_break1", + "leave_type", + "from_date", + "to_date", + "section_break_6", + "new_leaves_allocated", + "carry_forward", + "carry_forwarded_leaves", + "total_leaves_allocated", + "total_leaves_encashed", + "column_break_10", + "compensatory_request", + "leave_period", + "leave_policy", + "status", + "amended_from", + "notes", + "description" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", "fieldname": "naming_series", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Series", - "length": 0, "no_copy": 1, "options": "HR-LAL-.YYYY.-", - "permlevel": 0, - "precision": "", "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "employee", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, "in_list_view": 1, "in_standard_filter": 1, "label": "Employee", - "length": 0, - "no_copy": 0, "oldfieldname": "employee", "oldfieldtype": "Link", "options": "Employee", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "employee.employee_name", "fieldname": "employee_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, "in_list_view": 1, - "in_standard_filter": 0, "label": "Employee Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "employee.department", "fieldname": "department", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Department", - "length": 0, - "no_copy": 0, "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break1", "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "leave_type", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Leave Type", - "length": 0, - "no_copy": 0, "oldfieldname": "leave_type", "oldfieldtype": "Link", "options": "Leave Type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "from_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "to_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_6", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allocation", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Allocation" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, "fieldname": "new_leaves_allocated", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "New Leaves Allocated", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "New Leaves Allocated" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", + "default": "0", "fieldname": "carry_forward", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Add unused leaves from previous allocations", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Add unused leaves from previous allocations" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "carry_forward", "fieldname": "carry_forwarded_leaves", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Unused leaves", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "total_leaves_allocated", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Total Leaves Allocated", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.total_leaves_encashed>0", "fieldname": "total_leaves_encashed", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Total Leaves Encashed", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "compensatory_request", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Compensatory Leave Request", - "length": 0, - "no_copy": 0, "options": "Compensatory Leave Request", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "leave_period", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Leave Period", - "length": 0, - "no_copy": 0, "options": "Leave Period", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "employee.leave_policy", "fieldname": "leave_policy", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Leave Policy", - "length": 0, - "no_copy": 0, "options": "Leave Policy", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "status", "fieldtype": "Select", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Status", - "length": 0, - "no_copy": 0, "options": "Active\nExpired", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "amended_from", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Amended From", - "length": 0, "no_copy": 1, "oldfieldname": "amended_from", "oldfieldtype": "Data", "options": "Leave Allocation", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "notes", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Notes", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Notes" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "description", "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Description", - "length": 0, - "no_copy": 0, "oldfieldname": "reason", "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "300px" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-ok", "idx": 1, - "image_view": 0, - "in_create": 0, "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-05-31 11:28:09.360525", + "modified": "2019-06-14 15:39:02.898695", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", @@ -756,15 +218,10 @@ "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "HR User", - "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 @@ -776,28 +233,19 @@ "delete": 1, "email": 1, "export": 1, - "if_owner": 0, "import": 1, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "HR Manager", - "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "search_fields": "employee,employee_name,leave_type,total_leaves_allocated", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "timeline_field": "employee", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "timeline_field": "employee" } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_application/leave_application.json b/erpnext/hr/doctype/leave_application/leave_application.json index 60efb33ca4..f8344b53e0 100644 --- a/erpnext/hr/doctype/leave_application/leave_application.json +++ b/erpnext/hr/doctype/leave_application/leave_application.json @@ -1,979 +1,274 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, "allow_import": 1, - "allow_rename": 0, "autoname": "naming_series:", - "beta": 0, "creation": "2013-02-20 11:18:11", - "custom": 0, "description": "Apply / Approve Leaves", - "docstatus": 0, "doctype": "DocType", "document_type": "Document", - "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "employee", + "employee_name", + "column_break_4", + "leave_type", + "department", + "leave_balance", + "section_break_5", + "from_date", + "to_date", + "half_day", + "half_day_date", + "total_leave_days", + "column_break1", + "description", + "section_break_7", + "leave_approver", + "leave_approver_name", + "column_break_18", + "status", + "leave_details", + "sb10", + "posting_date", + "company", + "follow_via_email", + "column_break_17", + "salary_slip", + "letter_head", + "color", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", "fieldname": "naming_series", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Series", - "length": 0, "no_copy": 1, "options": "HR-LAP-.YYYY.-", - "permlevel": 0, - "precision": "", "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "employee", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, "in_standard_filter": 1, "label": "Employee", - "length": 0, - "no_copy": 0, "options": "Employee", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "employee_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Employee Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "leave_type", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Leave Type", - "length": 0, - "no_copy": 0, "options": "Leave Type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "employee.department", "fieldname": "department", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Department", - "length": 0, - "no_copy": 0, "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "leave_balance", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Leave Balance Before Application", - "length": 0, "no_copy": 1, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "from_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "to_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "half_day", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Half Day", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Half Day" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.half_day && (doc.from_date != doc.to_date)", "fieldname": "half_day_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Half Day Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Half Day Date" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "total_leave_days", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Total Leave Days", - "length": 0, "no_copy": 1, - "permlevel": 0, "precision": "1", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break1", "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "50%", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "description", "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Reason", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_7", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Approval", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Approval" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "leave_approver", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Leave Approver", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "User" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "leave_approver_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Leave Approver Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_18", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Open", "fieldname": "status", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Status", - "length": 0, "no_copy": 1, - "options": "Open\nApproved\nRejected\nCancelled", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Open\nApproved\nRejected\nCancelled" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "sb10", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Today", "fieldname": "posting_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Posting Date", - "length": 0, "no_copy": 1, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "company", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Company", - "length": 0, - "no_copy": 0, "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "reqd": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", "fieldname": "follow_via_email", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Follow via Email", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_17", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "salary_slip", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Salary Slip", - "length": 0, - "no_copy": 0, - "options": "Salary Slip", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "salary_slip", + "fieldtype": "Link", + "label": "Salary Slip", + "options": "Salary Slip", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "letter_head", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Letter Head", - "length": 0, - "no_copy": 0, "options": "Letter Head", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "color", "fieldtype": "Color", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Color", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "print_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "amended_from", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Amended From", - "length": 0, "no_copy": 1, "options": "Leave Application", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 + }, + { + "fieldname": "leave_details", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Leave Details" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-calendar", "idx": 1, - "image_view": 0, - "in_create": 0, "is_submittable": 1, - "issingle": 0, - "istable": 0, "max_attachments": 3, - "modified": "2019-06-01 11:30:14.745572", + "modified": "2019-06-14 15:37:45.988552", "modified_by": "Administrator", "module": "HR", "name": "Leave Application", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Employee", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { @@ -983,9 +278,6 @@ "delete": 1, "email": 1, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, @@ -996,23 +288,9 @@ "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, - "report": 0, - "role": "All", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "All" }, { "amend": 1, @@ -1020,10 +298,6 @@ "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, @@ -1036,71 +310,34 @@ { "amend": 1, "cancel": 1, - "create": 0, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Leave Approver", - "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, "report": 1, "role": "HR User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, "report": 1, "role": "Leave Approver", - "set_user_permissions": 0, - "share": 0, - "submit": 0, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "search_fields": "employee,employee_name,leave_type,from_date,to_date,total_leave_days", - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", "timeline_field": "employee", - "title_field": "employee_name", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "title_field": "employee_name" } \ No newline at end of file From 1db0fc91a5d6b3d07ddff51877dc80297753327f Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 14 Jun 2019 15:48:31 +0530 Subject: [PATCH 075/117] fix: check generated ledger entries to avoid overlap --- erpnext/patches/v12_0/generate_leave_ledger_entries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 07e53c7450..5b71c167b6 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -35,7 +35,7 @@ def generate_application_leave_ledger_entries(): leave_applications = get_leaves_application_records() for application in leave_applications: - if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', 'transaction_name': record.name}): + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', 'transaction_name': application.name}): application.update(dict(doctype="Leave Application")) frappe.get_doc(application).create_leave_ledger_entry() @@ -44,7 +44,7 @@ def generate_encashment_leave_ledger_entries(): leave_encashments = get_leave_encashment_records() for encashment in leave_encashments: - if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': record.name}): + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': encashment.name}): encashment.update(dict(doctype="Leave Encashment")) frappe.get_doc(encashment).create_leave_ledger_entry() From 12a2b21465303b51110cdae94fe1c8c72d1e041a Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 19 Jun 2019 13:00:52 +0530 Subject: [PATCH 076/117] fix: track status for pending leaves --- .../hr/doctype/leave_allocation/leave_allocation_list.js | 7 +++---- erpnext/hr/doctype/leave_application/leave_application.py | 1 + erpnext/patches/v12_0/generate_leave_ledger_entries.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js b/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js index 946b4f8492..3ea0e2403f 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js @@ -4,9 +4,8 @@ // render frappe.listview_settings['Leave Allocation'] = { get_indicator: function(doc) { - if(doc.status==="Expired") { - return [__("Expired"), "darkgrey", "status, =, Expired"]; - } + if(doc.status==="Expired") { + return [__("Expired"), "darkgrey", "status, =, Expired"]; + } }, - right_column: "grand_total" }; diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index f87f7d53ef..d08c9edb1f 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -496,6 +496,7 @@ def get_pending_leaves_for_period(employee, leave_type, from_date, to_date): "leave_type": leave_type, "from_date": ("<=", from_date), "to_date": (">=", to_date), + "status": "Open" }, fieldname=['SUM(total_leave_days)']) or flt(0) def get_remaining_leaves(allocation, leaves_taken, date, expiry): diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 5b71c167b6..ffb2e3fba5 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -89,5 +89,5 @@ def get_leave_encashment_records(): encashment_date FROM `tabLeave Encashment` WHERE - AND docstatus=1 + docstatus=1 """, as_dict=1) \ No newline at end of file From bd999b09089c9f5cea8307cfe18a12d8b0f1952a Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 21 Jun 2019 00:49:48 +0530 Subject: [PATCH 077/117] fix: minor changes --- .../leave_allocation/leave_allocation.js | 15 +++-------- .../leave_allocation/leave_allocation.py | 15 ++++++++++- .../leave_application/leave_application.js | 3 ++- .../leave_application/leave_application.py | 23 +++++++++-------- .../test_leave_application.py | 2 +- .../leave_ledger_entry.json | 2 +- .../leave_ledger_entry/leave_ledger_entry.py | 25 ++++++++++++++++--- 7 files changed, 55 insertions(+), 30 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index a620d9e081..8f734ac1bc 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -94,19 +94,10 @@ frappe.ui.form.on("Leave Allocation", { calculate_total_leaves_allocated: function(frm) { if (cint(frm.doc.carry_forward) == 1 && frm.doc.leave_type && frm.doc.employee) { return frappe.call({ - method: "erpnext.hr.doctype.leave_allocation.leave_allocation.get_carry_forwarded_leaves", - args: { - "employee": frm.doc.employee, - "date": frm.doc.from_date, - "leave_type": frm.doc.leave_type, - "carry_forward": frm.doc.carry_forward - }, + method: "set_total_leaves_allocated", + doc: frm.doc, callback: function(r) { - if (!r.exc && r.message) { - frm.set_value('carry_forwarded_leaves', r.message); - frm.set_value("total_leaves_allocated", - flt(r.message) + flt(frm.doc.new_leaves_allocated)); - } + frm.refresh_fields(); } }) } else if (cint(frm.doc.carry_forward) == 0) { diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 72ea2733b0..df479e72e9 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -115,7 +115,7 @@ class LeaveAllocation(Document): args = dict( leaves=self.carry_forwarded_leaves, from_date=self.from_date, - to_date=add_days(self.from_date, expiry_days) if expiry_days else self.to_date, + to_date=add_days(self.from_date, expiry_days - 1) if expiry_days else self.to_date, is_carry_forward=1 ) create_leave_ledger_entry(self, args, submit) @@ -132,11 +132,13 @@ class LeaveAllocation(Document): ''' expires allocation ''' date = self.to_date if current else self.from_date leaves = get_unused_leaves(self.employee, self.leave_type, date) + ref_name = self.name if current else self.get_previous_allocation() if leaves: expiry_date = today() if current else add_days(self.from_date, -1) args = dict( leaves=flt(leaves) * -1, + transaction_name=ref_name, from_date=expiry_date, to_date=expiry_date, is_carry_forward=0, @@ -147,6 +149,17 @@ class LeaveAllocation(Document): if current: frappe.db.set_value("Leave Allocation", self.name, "status", "Expired") + def get_previous_allocation(self): + return frappe.db.get_value("Leave Allocation", + filters={ + 'to_date': ("<", self.from_date), + 'leave_type': self.leave_type, + 'employee': self.employee, + 'docstatus': 1 + }, + order_by='to_date DESC', + fieldname=['name']) + def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 leave_allocations = frappe.db.sql(""" diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index b81e615b72..44a60b0259 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -49,7 +49,7 @@ frappe.ui.form.on("Leave Application", { async: false, args: { employee: frm.doc.employee, - date: frm.doc.posting_date + date: frm.doc.from_date? frm.doc.from_date:frm.doc.posting_date }, callback: function(r) { if (!r.exc && r.message['leave_allocation']) { @@ -124,6 +124,7 @@ frappe.ui.form.on("Leave Application", { }, from_date: function(frm) { + frm.trigger("make_dashboard"); frm.trigger("half_day_datepicker"); frm.trigger("calculate_total_days"); }, diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index d08c9edb1f..7aa3e95862 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -417,9 +417,9 @@ def get_leave_details(employee, date): allocation = allocation_records.get(d, frappe._dict()) remaining_leaves = get_leave_balance_on(employee, d, date, to_date = allocation.to_date, consider_all_leaves_in_the_allocation_period=True) - date = allocation.to_date - leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, date) * -1 - leaves_pending = get_pending_leaves_for_period(employee, d, allocation.from_date, date) + end_date = allocation.to_date + leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, end_date) * -1 + leaves_pending = get_pending_leaves_for_period(employee, d, allocation.from_date, end_date) leave_allocation[d] = { "total_leaves": allocation.total_leaves_allocated, @@ -435,14 +435,17 @@ def get_leave_details(employee, date): return ret @frappe.whitelist() -def get_leave_balance_on(employee, leave_type, date, to_date=nowdate(), allocation_records=None, - consider_all_leaves_in_the_allocation_period=False): - ''' Returns leave balance till date and fetches expiry date based on to_date - to calculate minimum remaining leave balance ''' - - if not allocation_records: - allocation_records = get_leave_allocation_records(employee, date, leave_type) +def get_leave_balance_on(employee, leave_type, date, to_date=nowdate(), consider_all_leaves_in_the_allocation_period=False): + ''' + Returns leave balance on date + :param employee: employee name + :param leave_type: leave type + :param date: date to check balance on + :param to_date: future date to check for allocation expiry + :param consider_all_leaves_in_the_allocation_period: consider all leaves taken till the allocation end date + ''' + allocation_records = get_leave_allocation_records(employee, date, leave_type) allocation = allocation_records.get(leave_type, frappe._dict()) end_date = allocation.to_date if consider_all_leaves_in_the_allocation_period else date diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 54216ee3c7..ca6b99ca12 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -557,7 +557,7 @@ def create_carry_forwarded_allocation(employee, leave_type): leave_type="_Test_CF_leave_expiry", employee=employee.name, employee_name=employee.employee_name, - from_date=add_days(nowdate(), -85), + from_date=add_days(nowdate(), -84), to_date=add_days(nowdate(), 100), carry_forward=1) leave_allocation.submit() diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json index 0166e43c0f..c11422211c 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -101,7 +101,7 @@ ], "in_create": 1, "is_submittable": 1, - "modified": "2019-06-06 20:33:37.531161", + "modified": "2019-06-21 00:37:07.782810", "modified_by": "Administrator", "module": "HR", "name": "Leave Ledger Entry", diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 6142dcf648..a73f10adb8 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ -from frappe.utils import add_days, today, flt +from frappe.utils import add_days, today, flt, DATE_FORMAT class LeaveLedgerEntry(Document): def on_cancel(self): @@ -49,14 +49,31 @@ def create_leave_ledger_entry(ref_doc, args, submit=True): def delete_ledger_entry(ledger): ''' Delete ledger entry on cancel of leave application/allocation/encashment ''' - if ledger.transaction_type == "Leave Allocation": validate_leave_allocation_against_leave_application(ledger) + expired_entry = get_previous_expiry_ledger_entry(ledger) frappe.db.sql("""DELETE FROM `tabLeave Ledger Entry` WHERE - `transaction_name`=%s""", (ledger.transaction_name)) + `transaction_name`=%s + OR `name`=%s""", (ledger.transaction_name, expired_entry)) + +def get_previous_expiry_ledger_entry(ledger): + ''' Returns the expiry ledger entry having same creation date as the ledger entry to be cancelled ''' + creation_date = frappe.db.get_value("Leave Ledger Entry", filters={ + 'transaction_name': ledger.transaction_name, + 'is_expired': 0 + }, fieldname=['creation']).strftime(DATE_FORMAT) + + return frappe.db.get_value("Leave Ledger Entry", filters={ + 'creation': ('like', creation_date+"%"), + 'employee': ledger.employee, + 'leave_type': ledger.leave_type, + 'is_expired': 1, + 'docstatus': 1, + 'is_carry_forward': 0 + }, fieldname=['name']) def process_expired_allocation(): ''' Check if a carry forwarded allocation has expired and create a expiry ledger entry ''' @@ -70,7 +87,7 @@ def process_expired_allocation(): leave_type = [record[0] for record in leave_type_records] expired_allocation = frappe.get_all("Leave Ledger Entry", filters={ - 'to_date': today(), + 'to_date': add_days(today(), -1), 'transaction_type': 'Leave Allocation', 'is_carry_forward': 1, 'leave_type': ('in', leave_type) From 0d4db95d9923e433bf51dfca1d6aeb3263cbe1ee Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 3 Jul 2019 11:35:51 +0530 Subject: [PATCH 078/117] fix: skip expired leaves on allocation end date --- .../leave_application/leave_application.py | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 7aa3e95862..0dde8e157e 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -521,8 +521,39 @@ def get_remaining_leaves(allocation, leaves_taken, date, expiry): return _get_remaining_leaves(allocation.total_leaves_allocated, allocation.to_date) def get_leaves_for_period(employee, leave_type, from_date, to_date): - leave_entries = frappe.db.sql(""" - select employee, leave_type, from_date, to_date, leaves, transaction_type + leave_entries = get_leave_entries(employee, leave_type, from_date, to_date) + leave_days = 0 + + for leave_entry in leave_entries: + inclusive_period = leave_entry.from_date >= getdate(from_date) and leave_entry.to_date <= getdate(to_date) + + if inclusive_period and leave_entry.transaction_type == 'Leave Encashment': + leave_days += leave_entry.leaves + + elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \ + and not skip_expiry_leaves(leave_entry, to_date): + leave_days += leave_entry.leaves + + else: + if leave_entry.from_date < getdate(from_date): + leave_entry.from_date = from_date + if leave_entry.to_date > getdate(to_date): + leave_entry.to_date = to_date + + leave_days += get_number_of_leave_days(employee, leave_type, + leave_entry.from_date, leave_entry.to_date) * -1 + + return leave_days + +def skip_expiry_leaves(leave_entry, date): + ''' Checks whether the expired leaves coincide with the to_date of leave balance check ''' + end_date = frappe.db.get_value("Leave Allocation", {'name': leave_entry.transaction_name}, ['to_date']) + return True if end_date == date and not leave_entry.is_carry_forward else False + +def get_leave_entries(employee, leave_type, from_date, to_date): + ''' Returns leave entries between from_date and to_date ''' + return frappe.db.sql(""" + select employee, leave_type, from_date, to_date, leaves, transaction_type, is_carry_forward from `tabLeave Ledger Entry` where employee=%(employee)s and leave_type=%(leave_type)s and docstatus=1 @@ -536,22 +567,6 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date): "employee": employee, "leave_type": leave_type }, as_dict=1) - leave_days = 0 - - for leave_entry in leave_entries: - if leave_entry.from_date >= getdate(from_date) and leave_entry.to_date <= getdate(to_date) \ - and leave_entry.transaction_type in ('Leave Encashment', 'Leave Allocation'): - leave_days += leave_entry.leaves - else: - if leave_entry.from_date < getdate(from_date): - leave_entry.from_date = from_date - if leave_entry.to_date > getdate(to_date): - leave_entry.to_date = to_date - - leave_days += get_number_of_leave_days(employee, leave_type, - leave_entry.from_date, leave_entry.to_date) * -1 - - return leave_days @frappe.whitelist() def get_holidays(employee, from_date, to_date): From 80fb0bf520ea43fa1000601ae27a299e05b31db2 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 3 Jul 2019 13:06:46 +0530 Subject: [PATCH 079/117] fix: add employee leave balance report to the dashboard --- .../doctype/leave_allocation/leave_allocation.js | 8 -------- .../leave_allocation/leave_allocation_dashboard.py | 5 +++++ .../doctype/leave_application/leave_application.js | 10 ---------- .../doctype/leave_application/leave_application.py | 2 +- .../leave_application_dashboard.py | 14 ++++++++++++++ erpnext/patches.txt | 2 +- 6 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 erpnext/hr/doctype/leave_application/leave_application_dashboard.py diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 8f734ac1bc..2a26a85fa9 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -30,14 +30,6 @@ frappe.ui.form.on("Leave Allocation", { frm.trigger("expire_allocation"); }); } - - // opens leave balance report for employee - frm.add_custom_button(__('Leave Balance'), function() { - frappe.route_options = { - employee: frm.doc.employee, - }; - frappe.set_route("query-report", "Employee Leave Balance"); - }); } }, diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py index 72a1b7c194..7456aebb45 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py @@ -12,4 +12,9 @@ def get_data(): 'items': ['Leave Encashment'] } ], + 'reports': [ + { + 'items': ['Employee Leave Balance'] + } + ] } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 44a60b0259..a755b57608 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -85,16 +85,6 @@ frappe.ui.form.on("Leave Application", { frm.set_value('employee', perm['Employee'].map(perm_doc => perm_doc.doc)[0]); } } - - if (frm.doc.docstatus === 1) { - frm.add_custom_button(__('Leave Balance'), function() { - frappe.route_options = { - employee: frm.doc.employee, - group_by: "" - }; - frappe.set_route("query-report", "Employee Leave Balance"); - }); - } }, employee: function(frm) { diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 74862fb011..86d9130aab 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -438,7 +438,7 @@ def get_leave_details(employee, date): @frappe.whitelist() def get_leave_balance_on(employee, leave_type, date, to_date=nowdate(), consider_all_leaves_in_the_allocation_period=False): ''' - Returns leave balance on date + Returns leave balance till date :param employee: employee name :param leave_type: leave type :param date: date to check balance on diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py new file mode 100644 index 0000000000..8075b7b5c5 --- /dev/null +++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py @@ -0,0 +1,14 @@ +from __future__ import unicode_literals + +from frappe import _ + + +def get_data(): + return { + 'reports': [ + { + 'label': _('Reports'), + 'items': ['Employee Leave Balance'] + } + ] + } \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3b4cfd835d..b3aeab68f2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -606,7 +606,6 @@ execute:frappe.delete_doc_if_exists("Page", "support-analytics") erpnext.patches.v12_0.make_custom_fields_for_bank_remittance #14-06-2019 execute:frappe.delete_doc_if_exists("Page", "support-analytics") erpnext.patches.v12_0.make_item_manufacturer -erpnext.patches.v12_0.generate_leave_ledger_entries erpnext.patches.v11_1.move_customer_lead_to_dynamic_column erpnext.patches.v11_1.set_default_action_for_quality_inspection erpnext.patches.v11_1.delete_bom_browser @@ -617,3 +616,4 @@ erpnext.patches.v11_1.set_missing_opportunity_from erpnext.patches.v12_0.set_quotation_status erpnext.patches.v12_0.set_priority_for_support erpnext.patches.v12_0.delete_priority_property_setter +erpnext.patches.v12_0.generate_leave_ledger_entries # From 29e9f14f95e967141841c3519946d5b3b7dae7c2 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 22 Jul 2019 13:54:39 +0530 Subject: [PATCH 080/117] fix: minor changes --- .../hr/doctype/leave_application/leave_application.py | 9 +++++---- .../hr/doctype/leave_ledger_entry/leave_ledger_entry.py | 4 ++++ erpnext/hr/doctype/leave_type/leave_type.py | 6 +++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 86d9130aab..8f02ec0f92 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -357,7 +357,7 @@ class LeaveApplication(Document): lwp = frappe.db.get_value("Leave Type", self.leave_type, "is_lwp") if expiry_date: - self.create_ledger_entry_for_intermediate_allocation_expiry(expiry_date, submit) + self.create_ledger_entry_for_intermediate_allocation_expiry(expiry_date, submit, lwp) else: args = dict( leaves=self.total_leave_days * -1, @@ -367,16 +367,17 @@ class LeaveApplication(Document): ) create_leave_ledger_entry(self, args, submit) - def create_ledger_entry_for_intermediate_allocation_expiry(self, expiry_date, submit): + def create_ledger_entry_for_intermediate_allocation_expiry(self, expiry_date, submit, lwp): ''' splits leave application into two ledger entries to consider expiry of allocation ''' args = dict( from_date=self.from_date, to_date=expiry_date, - leaves=(date_diff(expiry_date, self.from_date) + 1) * -1 + leaves=(date_diff(expiry_date, self.from_date) + 1) * -1, + is_lwp=lwp ) create_leave_ledger_entry(self, args, submit) - if expiry_date != self.to_date: + if getdate(expiry_date) != getdate(self.to_date): start_date = add_days(expiry_date, 1) args.update(dict( from_date=start_date, diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index a73f10adb8..99a9d0d933 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -9,6 +9,10 @@ from frappe import _ from frappe.utils import add_days, today, flt, DATE_FORMAT class LeaveLedgerEntry(Document): + def validate(self): + if self.from_date > self.to_date: + frappe.throw(_("To date needs to be before from date")) + def on_cancel(self): # allow cancellation of expiry leaves if not self.is_expired: diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index 5b13edb684..cbc6781783 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -10,4 +10,8 @@ from frappe import _ from frappe.model.document import Document class LeaveType(Document): - pass \ No newline at end of file + def validate(self): + if self.is_lwp: + leave_allocation = frappe.get_doc("Leave Allocation", {"leave_type": self.name}, ['name']) + if leave_allocation: + frappe.throw(_("""Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay""").format(", ".join(leave_allocation))) #nosec \ No newline at end of file From f6cf58fa8c49443b47912639caaa10eb0b065601 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 22 Jul 2019 15:32:41 +0530 Subject: [PATCH 081/117] fix: change get all to sql list --- .../leave_ledger_entry/leave_ledger_entry.py | 18 ++++++++++-------- erpnext/hr/doctype/leave_type/leave_type.py | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 99a9d0d933..0520b867f2 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -20,14 +20,16 @@ class LeaveLedgerEntry(Document): def validate_leave_allocation_against_leave_application(ledger): ''' Checks that leave allocation has no leave application against it ''' - leave_application_records = frappe.get_all("Leave Ledger Entry", - filters={ - 'employee': ledger.employee, - 'leave_type': ledger.leave_type, - 'transaction_type': 'Leave Application', - 'from_date': (">=", ledger.from_date), - 'to_date': ('<=', ledger.to_date) - }, fields=['transaction_name']) + leave_application_records = frappe.db.sql_list(""" + SELECT transaction_name + FROM `tabLeave Application` + WHERE + employee=%s, + leave_type=%s, + transaction_type='Leave Application', + from_date>=%s, + to_date<=%s + """, (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date)) if leave_application_records: frappe.throw(_("Leave allocation %s is linked with leave application %s" diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index cbc6781783..75336e0c7e 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -12,6 +12,6 @@ from frappe.model.document import Document class LeaveType(Document): def validate(self): if self.is_lwp: - leave_allocation = frappe.get_doc("Leave Allocation", {"leave_type": self.name}, ['name']) + leave_allocation = frappe.db.sql_list("""select name from `tabLeave Allocation` where leave_type=%s""", (self.name)) if leave_allocation: frappe.throw(_("""Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay""").format(", ".join(leave_allocation))) #nosec \ No newline at end of file From 08c02287dd3a396d61dd3573f10efbcfe300fca1 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 22 Jul 2019 17:47:03 +0530 Subject: [PATCH 082/117] fix: expiry allocaton after creating all the transactions --- .../v12_0/generate_leave_ledger_entries.py | 146 +++++++++--------- 1 file changed, 77 insertions(+), 69 deletions(-) diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index ffb2e3fba5..8a92bb3a3d 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -6,88 +6,96 @@ import frappe from frappe.utils import getdate def execute(): - """ Generates leave ledger entries for leave allocation/application/encashment - for last allocation """ - frappe.reload_doc("HR", "doctype", "Leave Ledger Entry") - frappe.reload_doc("HR", "doctype", "Leave Encashment") - if frappe.db.a_row_exists("Leave Ledger Entry"): - return + """ Generates leave ledger entries for leave allocation/application/encashment + for last allocation """ + frappe.reload_doc("HR", "doctype", "Leave Ledger Entry") + frappe.reload_doc("HR", "doctype", "Leave Encashment") + if frappe.db.a_row_exists("Leave Ledger Entry"): + return - generate_allocation_ledger_entries() - generate_application_leave_ledger_entries() - generate_encashment_leave_ledger_entries() + generate_allocation_ledger_entries() + generate_application_leave_ledger_entries() + generate_encashment_leave_ledger_entries() + generate_expiry_allocation_ledger_entries() def generate_allocation_ledger_entries(): - ''' fix ledger entries for missing leave allocation transaction ''' - allocation_list = get_allocation_records() - - for allocation in allocation_list: - if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name}): - allocation.update(dict(doctype="Leave Allocation")) - allocation_obj = frappe.get_doc(allocation) - allocation_obj.create_leave_ledger_entry() - if allocation.to_date <= getdate(): - allocation_obj.expire_allocation() + ''' fix ledger entries for missing leave allocation transaction ''' + allocation_list = get_allocation_records() + for allocation in allocation_list: + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name}): + allocation.update(dict(doctype="Leave Allocation")) + allocation_obj = frappe.get_doc(allocation) + allocation_obj.create_leave_ledger_entry() def generate_application_leave_ledger_entries(): - ''' fix ledger entries for missing leave application transaction ''' - leave_applications = get_leaves_application_records() + ''' fix ledger entries for missing leave application transaction ''' + leave_applications = get_leaves_application_records() - for application in leave_applications: - if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', 'transaction_name': application.name}): - application.update(dict(doctype="Leave Application")) - frappe.get_doc(application).create_leave_ledger_entry() + for application in leave_applications: + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', 'transaction_name': application.name}): + application.update(dict(doctype="Leave Application")) + frappe.get_doc(application).create_leave_ledger_entry() def generate_encashment_leave_ledger_entries(): - ''' fix ledger entries for missing leave encashment transaction ''' - leave_encashments = get_leave_encashment_records() + ''' fix ledger entries for missing leave encashment transaction ''' + leave_encashments = get_leave_encashment_records() - for encashment in leave_encashments: - if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': encashment.name}): - encashment.update(dict(doctype="Leave Encashment")) - frappe.get_doc(encashment).create_leave_ledger_entry() + for encashment in leave_encashments: + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': encashment.name}): + encashment.update(dict(doctype="Leave Encashment")) + frappe.get_doc(encashment).create_leave_ledger_entry() + +def generate_expiry_allocation_ledger_entries(): + ''' fix ledger entries for missing leave allocation transaction ''' + allocation_list = get_allocation_records() + + for allocation in allocation_list: + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name, 'is_expired': 1}): + allocation.update(dict(doctype="Leave Allocation")) + allocation_obj = frappe.get_doc(allocation) + allocation_obj.expire_allocation() def get_allocation_records(): - return frappe.db.sql(""" - SELECT - DISTINCT name, - employee, - leave_type, - new_leaves_allocated, - carry_forwarded_leaves, - from_date, - to_date, - carry_forward - FROM `tabLeave Allocation` - WHERE - docstatus=1 - ORDER BY to_date ASC - """, as_dict=1) + return frappe.db.sql(""" + SELECT + name, + employee, + leave_type, + new_leaves_allocated, + carry_forwarded_leaves, + from_date, + to_date, + carry_forward + FROM `tabLeave Allocation` + WHERE + docstatus=1 + ORDER BY to_date ASC + """, as_dict=1) def get_leaves_application_records(): - return frappe.db.sql(""" - SELECT - DISTINCT name, - employee, - leave_type, - total_leave_days, - from_date, - to_date - FROM `tabLeave Application` - WHERE - docstatus=1 - """, as_dict=1) + return frappe.db.sql(""" + SELECT + name, + employee, + leave_type, + total_leave_days, + from_date, + to_date + FROM `tabLeave Application` + WHERE + docstatus=1 + """, as_dict=1) def get_leave_encashment_records(): - return frappe.db.sql(""" - SELECT - DISTINCT name, - employee, - leave_type, - encashable_days, - encashment_date - FROM `tabLeave Encashment` - WHERE - docstatus=1 - """, as_dict=1) \ No newline at end of file + return frappe.db.sql(""" + SELECT + name, + employee, + leave_type, + encashable_days, + encashment_date + FROM `tabLeave Encashment` + WHERE + docstatus=1 + """, as_dict=1) \ No newline at end of file From fd1d4c2927d6d397965ad12b9792fb609064e6ea Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 22 Jul 2019 18:05:27 +0530 Subject: [PATCH 083/117] chore: split expire allocation --- .../leave_allocation/leave_allocation.js | 7 ++-- .../leave_allocation/leave_allocation.json | 22 ++++++------- .../leave_allocation/leave_allocation.py | 32 +++++++++++++++---- .../leave_allocation/leave_allocation_list.js | 2 +- .../v12_0/generate_leave_ledger_entries.py | 2 +- 5 files changed, 40 insertions(+), 25 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 2a26a85fa9..7c3e1e44fa 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -22,7 +22,7 @@ frappe.ui.form.on("Leave Allocation", { }, refresh: function(frm) { - if(frm.doc.docstatus === 1 && frm.doc.status === "Active") { + if(frm.doc.docstatus === 1 && frm.doc.expired) { var valid_expiry = moment(frappe.datetime.get_today()).isBetween(frm.doc.from_date, frm.doc.to_date); if(valid_expiry) { // expire current allocation @@ -35,11 +35,8 @@ frappe.ui.form.on("Leave Allocation", { expire_allocation: function(frm) { frappe.call({ - method: 'expire_allocation', + method: 'expire_current_allocation', doc: frm.doc, - args: { - current: true - }, freeze: true, callback: function(r){ if(!r.exc){ diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 41d864d912..1618c67d3d 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -24,7 +24,7 @@ "compensatory_request", "leave_period", "leave_policy", - "status", + "expired", "amended_from", "notes", "description" @@ -167,15 +167,6 @@ "options": "Leave Policy", "read_only": 1 }, - { - "fieldname": "status", - "fieldtype": "Select", - "hidden": 1, - "in_standard_filter": 1, - "label": "Status", - "options": "Active\nExpired", - "read_only": 1 - }, { "fieldname": "amended_from", "fieldtype": "Link", @@ -201,12 +192,21 @@ "oldfieldname": "reason", "oldfieldtype": "Small Text", "width": "300px" + }, + { + "default": "0", + "fieldname": "expired", + "fieldtype": "Check", + "hidden": 1, + "in_standard_filter": 1, + "label": "Expired", + "read_only": 1 } ], "icon": "fa fa-ok", "idx": 1, "is_submittable": 1, - "modified": "2019-06-14 15:39:02.898695", + "modified": "2019-07-22 17:50:39.591195", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index df479e72e9..494043ff14 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -42,7 +42,7 @@ class LeaveAllocation(Document): def on_submit(self): self.create_leave_ledger_entry() - self.expire_allocation() + self.expire_previous_allocation() def on_cancel(self): self.create_leave_ledger_entry(submit=False) @@ -128,14 +128,14 @@ class LeaveAllocation(Document): ) create_leave_ledger_entry(self, args, submit) - def expire_allocation(self, current=False): + def expire_current_allocation(self): ''' expires allocation ''' - date = self.to_date if current else self.from_date + date = self.to_date leaves = get_unused_leaves(self.employee, self.leave_type, date) - ref_name = self.name if current else self.get_previous_allocation() + ref_name = self.name if leaves: - expiry_date = today() if current else add_days(self.from_date, -1) + expiry_date = today() args = dict( leaves=flt(leaves) * -1, transaction_name=ref_name, @@ -146,8 +146,26 @@ class LeaveAllocation(Document): ) create_leave_ledger_entry(self, args) - if current: - frappe.db.set_value("Leave Allocation", self.name, "status", "Expired") + frappe.db.set_value("Leave Allocation", self.name, "expired", 1) + + def expire_previous_allocation(self): + date = self.from_date + leaves = get_unused_leaves(self.employee, self.leave_type, date) + ref_name = self.get_previous_allocation() + + if leaves: + expiry_date = add_days(self.from_date, -1) + args = dict( + leaves=flt(leaves) * -1, + transaction_name=ref_name, + from_date=expiry_date, + to_date=expiry_date, + is_carry_forward=0, + is_expired=1 + ) + create_leave_ledger_entry(self, args) + + frappe.db.set_value("Leave Allocation", ref_name, "expired", 1) def get_previous_allocation(self): return frappe.db.get_value("Leave Allocation", diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js b/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js index 3ea0e2403f..93f7b8356b 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js @@ -5,7 +5,7 @@ frappe.listview_settings['Leave Allocation'] = { get_indicator: function(doc) { if(doc.status==="Expired") { - return [__("Expired"), "darkgrey", "status, =, Expired"]; + return [__("Expired"), "darkgrey", "expired, =, 1"]; } }, }; diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 8a92bb3a3d..f2a798e1f8 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -54,7 +54,7 @@ def generate_expiry_allocation_ledger_entries(): if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name, 'is_expired': 1}): allocation.update(dict(doctype="Leave Allocation")) allocation_obj = frappe.get_doc(allocation) - allocation_obj.expire_allocation() + allocation_obj.expire_previous_allocation() def get_allocation_records(): return frappe.db.sql(""" From bb1be2e2ee4ba2d2f9a688ef88ff51419177ad4d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 23 Jul 2019 13:18:40 +0530 Subject: [PATCH 084/117] chore: change fieldname for carried forward leave expiry --- erpnext/hr/doctype/leave_allocation/leave_allocation.py | 2 +- .../hr/doctype/leave_allocation/test_leave_allocation.py | 2 +- .../hr/doctype/leave_application/test_leave_application.py | 4 ++-- erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py | 2 +- erpnext/hr/doctype/leave_period/leave_period.py | 2 +- erpnext/hr/doctype/leave_type/leave_type.json | 6 +++--- erpnext/hr/doctype/leave_type/test_leave_type.py | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 494043ff14..67f30b5816 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -111,7 +111,7 @@ class LeaveAllocation(Document): def create_leave_ledger_entry(self, submit=True): if self.carry_forwarded_leaves: - expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "carry_forward_leave_expiry") + expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "expire_carried_forward_leaves") args = dict( leaves=self.carry_forwarded_leaves, from_date=self.from_date, diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index dfae329da1..dfa64db416 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -97,7 +97,7 @@ class TestLeaveAllocation(unittest.TestCase): leave_type = create_leave_type( leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1, - carry_forward_leave_expiry=90) + expire_carried_forward_leaves=90) leave_type.submit() # initial leave allocation diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index ca6b99ca12..709a2f57df 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -415,7 +415,7 @@ class TestLeaveApplication(unittest.TestCase): leave_type = create_leave_type( leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1, - carry_forward_leave_expiry=90) + expire_carried_forward_leaves=90) leave_type.submit() create_carry_forwarded_allocation(employee, leave_type) @@ -516,7 +516,7 @@ class TestLeaveApplication(unittest.TestCase): leave_type = create_leave_type( leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1, - carry_forward_leave_expiry=90) + expire_carried_forward_leaves=90) leave_type.submit() create_carry_forwarded_allocation(employee, leave_type) diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 0520b867f2..135b750bf9 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -86,7 +86,7 @@ def process_expired_allocation(): # fetch leave type records that has carry forwarded leaves expiry leave_type_records = frappe.db.get_values("Leave Type", filters={ - 'carry_forward_leave_expiry': (">", 0) + 'expire_carried_forward_leaves': (">", 0) }, fieldname=['name']) if leave_type_records: diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py index 91cc9b8340..f37efeba0b 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.py +++ b/erpnext/hr/doctype/leave_period/leave_period.py @@ -95,7 +95,7 @@ def get_existing_allocations(employees, leave_period): def get_leave_type_details(): leave_type_details = frappe._dict() leave_types = frappe.get_all("Leave Type", - fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "carry_forward_leave_expiry"]) + fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carried_forward_leaves"]) for d in leave_types: leave_type_details.setdefault(d.name, d) return leave_type_details diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 7899c2f1e6..7a279063ce 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -419,9 +419,9 @@ "collapsible": 0, "columns": 0, "depends_on": "", - "description": "calculated in days", + "description": "Calculated in days", "fetch_if_empty": 0, - "fieldname": "carry_forward_leave_expiry", + "fieldname": "expire_carried_forward_leaves", "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, @@ -728,7 +728,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-05-30 15:38:39.334283", + "modified": "2019-07-22 15:38:39.334283", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py index 1006550de4..d9852be6c9 100644 --- a/erpnext/hr/doctype/leave_type/test_leave_type.py +++ b/erpnext/hr/doctype/leave_type/test_leave_type.py @@ -19,7 +19,7 @@ def create_leave_type(**args): "is_earned_leave": args.is_earned_leave or 0, "is_lwp": args.is_lwp or 0, "is_carry_forward": args.is_carry_forward or 0, - "carry_forward_leave_expiry": args.carry_forward_leave_expiry or 0, + "expire_carried_forward_leaves": args.expire_carried_forward_leaves or 0, "encashment_threshold_days": args.encashment_threshold_days or 5, "earning_component": "Leave Encashment" }) From 5ca3e83a005827316613c57da92b402fd4ce18c4 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 25 Jul 2019 14:35:39 +0530 Subject: [PATCH 085/117] feat: add ledger link in hr config --- erpnext/config/hr.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/config/hr.py b/erpnext/config/hr.py index 0367755595..1f597f080d 100644 --- a/erpnext/config/hr.py +++ b/erpnext/config/hr.py @@ -134,6 +134,12 @@ def get_data(): "name": "Employee Leave Balance", "doctype": "Leave Application" }, + { + "type": "report", + "is_query_report": True, + "name": "Leave Ledger Entry", + "doctype": "Leave Ledger Entry" + }, ] }, { From 5eac8703da409c7c540552eeda87456ffd02bf4c Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 31 Jul 2019 19:28:21 +0530 Subject: [PATCH 086/117] fix: create reverse expiry for back dated leaves --- .../leave_encashment/leave_encashment.py | 30 ++++++++++++++----- .../leave_encashment/test_leave_encashment.py | 1 - 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index 70cd5780c8..c47ff85717 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -11,6 +11,7 @@ from erpnext.hr.utils import set_employee_name from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry +from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves class LeaveEncashment(Document): def validate(self): @@ -26,7 +27,7 @@ class LeaveEncashment(Document): def on_submit(self): if not self.leave_allocation: - self.leave_allocation = self.get_leave_allocation() + self.leave_allocation = self.get_leave_allocation().get('name') additional_salary = frappe.new_doc("Additional Salary") additional_salary.company = frappe.get_value("Employee", self.employee, "company") additional_salary.employee = self.employee @@ -61,8 +62,9 @@ class LeaveEncashment(Document): if not frappe.db.get_value("Leave Type", self.leave_type, 'allow_encashment'): frappe.throw(_("Leave Type {0} is not encashable").format(self.leave_type)) - self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, - self.encashment_date or getdate(nowdate()), consider_all_leaves_in_the_allocation_period=True) + allocation = self.get_leave_allocation() + + self.leave_balance = allocation.total_leaves_allocated - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) encashable_days = self.leave_balance - frappe.db.get_value('Leave Type', self.leave_type, 'encashment_threshold_days') self.encashable_days = encashable_days if encashable_days > 0 else 0 @@ -70,15 +72,15 @@ class LeaveEncashment(Document): per_day_encashment = frappe.db.get_value('Salary Structure', salary_structure , 'leave_encashment_amount_per_day') self.encashment_amount = self.encashable_days * per_day_encashment if per_day_encashment > 0 else 0 - self.leave_allocation = self.get_leave_allocation() + self.leave_allocation = allocation.name return True def get_leave_allocation(self): - leave_allocation = frappe.db.sql("""select name from `tabLeave Allocation` where '{0}' + leave_allocation = frappe.db.sql("""select name, to_date, total_leaves_allocated from `tabLeave Allocation` where '{0}' between from_date and to_date and docstatus=1 and leave_type='{1}' - and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee)) + and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee), as_dict=1) - return leave_allocation[0][0] if leave_allocation else None + return leave_allocation[0] if leave_allocation else None def create_leave_ledger_entry(self, submit=True): args = frappe._dict( @@ -87,4 +89,16 @@ class LeaveEncashment(Document): to_date=self.encashment_date, is_carry_forward=0 ) - create_leave_ledger_entry(self, args, submit) \ No newline at end of file + create_leave_ledger_entry(self, args, submit) + + # create reverse entry for expired leaves + to_date = self.get_leave_allocation().get('to_date') + if to_date < getdate(nowdate()): + args = frappe._dict( + leaves=self.encashable_days, + from_date=to_date, + to_date=to_date, + is_carry_forward=0 + ) + create_leave_ledger_entry(self, args, submit) + diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index 2daeffcffe..e5bd170bc4 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -38,7 +38,6 @@ class TestLeaveEncashment(unittest.TestCase): self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3)) self.leave_period.grant_leave_allocation(employee=self.employee) - def test_leave_balance_value_and_amount(self): frappe.db.sql('''delete from `tabLeave Encashment`''') leave_encashment = frappe.get_doc(dict( From 8fb600162e1f93c0adaffcbff567b4821d853635 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 31 Jul 2019 19:31:26 +0530 Subject: [PATCH 087/117] fix: remove all leaves via scheduler --- .../leave_allocation/leave_allocation.js | 7 +- .../leave_allocation/leave_allocation.py | 96 +++++++------------ .../leave_allocation/test_leave_allocation.py | 4 +- .../leave_application/leave_application.py | 2 +- .../leave_ledger_entry/leave_ledger_entry.py | 91 ++++++++++++------ .../v12_0/generate_leave_ledger_entries.py | 3 +- 6 files changed, 107 insertions(+), 96 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 7c3e1e44fa..8c910a200a 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -35,8 +35,11 @@ frappe.ui.form.on("Leave Allocation", { expire_allocation: function(frm) { frappe.call({ - method: 'expire_current_allocation', - doc: frm.doc, + method: 'erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.expire_allocation', + args: { + 'allocation': frm.doc, + 'expiry_date': frappe.datetime.get_today() + }, freeze: true, callback: function(r){ if(!r.exc){ diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 67f30b5816..2374e469a5 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -7,7 +7,7 @@ from frappe.utils import flt, date_diff, formatdate, add_days, today from frappe import _ from frappe.model.document import Document from erpnext.hr.utils import set_employee_name, get_leave_period -from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation, create_leave_ledger_entry class OverlapError(frappe.ValidationError): pass class BackDatedAllocationError(frappe.ValidationError): pass @@ -42,7 +42,11 @@ class LeaveAllocation(Document): def on_submit(self): self.create_leave_ledger_entry() - self.expire_previous_allocation() + + # expire all unused leaves in the ledger on creation of carry forward allocation + allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee) + if self.carry_forward and allocation: + expire_allocation(allocation) def on_cancel(self): self.create_leave_ledger_entry(submit=False) @@ -128,55 +132,17 @@ class LeaveAllocation(Document): ) create_leave_ledger_entry(self, args, submit) - def expire_current_allocation(self): - ''' expires allocation ''' - date = self.to_date - leaves = get_unused_leaves(self.employee, self.leave_type, date) - ref_name = self.name - - if leaves: - expiry_date = today() - args = dict( - leaves=flt(leaves) * -1, - transaction_name=ref_name, - from_date=expiry_date, - to_date=expiry_date, - is_carry_forward=0, - is_expired=1 - ) - create_leave_ledger_entry(self, args) - - frappe.db.set_value("Leave Allocation", self.name, "expired", 1) - - def expire_previous_allocation(self): - date = self.from_date - leaves = get_unused_leaves(self.employee, self.leave_type, date) - ref_name = self.get_previous_allocation() - - if leaves: - expiry_date = add_days(self.from_date, -1) - args = dict( - leaves=flt(leaves) * -1, - transaction_name=ref_name, - from_date=expiry_date, - to_date=expiry_date, - is_carry_forward=0, - is_expired=1 - ) - create_leave_ledger_entry(self, args) - - frappe.db.set_value("Leave Allocation", ref_name, "expired", 1) - - def get_previous_allocation(self): - return frappe.db.get_value("Leave Allocation", - filters={ - 'to_date': ("<", self.from_date), - 'leave_type': self.leave_type, - 'employee': self.employee, - 'docstatus': 1 - }, - order_by='to_date DESC', - fieldname=['name']) +def get_previous_allocation(from_date, leave_type, employee): + ''' Returns document properties of previous allocation ''' + return frappe.db.get_value("Leave Allocation", + filters={ + 'to_date': ("<", from_date), + 'leave_type': leave_type, + 'employee': employee, + 'docstatus': 1 + }, + order_by='to_date DESC', + fieldname=['name', 'from_date', 'to_date'], as_dict=1) def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 @@ -203,21 +169,27 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): @frappe.whitelist() def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): - carry_forwarded_leaves = 0 - if carry_forward: + ''' Returns carry forwarded leaves for the given employee ''' + carry_forwarded_leaves = 0.0 + previous_allocation = get_previous_allocation(date, leave_type, employee) + if carry_forward and previous_allocation: validate_carry_forward(leave_type) - carry_forwarded_leaves = get_unused_leaves(employee, leave_type, date) + carry_forwarded_leaves = get_unused_leaves(employee, leave_type, previous_allocation.from_date, previous_allocation.to_date) return carry_forwarded_leaves -def get_unused_leaves(employee, leave_type, date): - return frappe.db.get_value("Leave Ledger Entry", filters={ - "to_date": ("<=", date), - "employee": employee, - "docstatus": 1, - "leave_type": leave_type, - "is_lwp": 0 - }, fieldname=['SUM(leaves)']) +def get_unused_leaves(employee, leave_type, from_date, to_date): + ''' Returns unused leaves between the given period while skipping leave allocation expiry ''' + leaves = frappe.get_all("Leave Ledger Entry", filters={ + 'employee': employee, + 'leave_type': leave_type, + 'from_date': ('>=', from_date), + 'to_date': ('<=', to_date) + }, or_filters={ + 'is_expired': 0, + 'is_carry_forward': 1 + }, fields=['sum(leaves) as leaves']) + return flt(leaves[0]['leaves']) def validate_carry_forward(leave_type): if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"): diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index dfa64db416..4f4e0ab1fc 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -3,7 +3,7 @@ import frappe import unittest from frappe.utils import nowdate, add_months, getdate, add_days from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type -from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation, expire_allocation class TestLeaveAllocation(unittest.TestCase): def test_overlapping_allocation(self): @@ -108,6 +108,8 @@ class TestLeaveAllocation(unittest.TestCase): carry_forward=1) leave_allocation.submit() + expire_allocation(leave_allocation) + leave_allocation = create_leave_allocation( leave_type="_Test_CF_leave_expiry", from_date=add_days(nowdate(), -90), diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 8f02ec0f92..36a4145467 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -198,7 +198,7 @@ class LeaveApplication(Document): if not is_lwp(self.leave_type): self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, self.to_date, consider_all_leaves_in_the_allocation_period=True) - if self.status != "Rejected" and self.leave_balance < self.total_leave_days: + if self.status != "Rejected" and (self.leave_balance < self.total_leave_days or not self.leave_balance): if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"): frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}") .format(self.leave_type)) diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 135b750bf9..d19e15cf1a 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -6,29 +6,31 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ -from frappe.utils import add_days, today, flt, DATE_FORMAT +from frappe.utils import add_days, today, flt, DATE_FORMAT, getdate class LeaveLedgerEntry(Document): def validate(self): - if self.from_date > self.to_date: + if getdate(self.from_date) > getdate(self.to_date): frappe.throw(_("To date needs to be before from date")) def on_cancel(self): # allow cancellation of expiry leaves - if not self.is_expired: + if self.is_expired: + frappe.db.set_value("Leave Allocation", self.transaction_name, "expired", 0) + else: frappe.throw(_("Only expired allocation can be cancelled")) def validate_leave_allocation_against_leave_application(ledger): ''' Checks that leave allocation has no leave application against it ''' leave_application_records = frappe.db.sql_list(""" SELECT transaction_name - FROM `tabLeave Application` + FROM `tabLeave Ledger Entry` WHERE - employee=%s, - leave_type=%s, - transaction_type='Leave Application', - from_date>=%s, - to_date<=%s + employee=%s + AND leave_type=%s + AND transaction_type='Leave Application' + AND from_date>=%s + AND to_date<=%s """, (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date)) if leave_application_records: @@ -48,6 +50,7 @@ def create_leave_ledger_entry(ref_doc, args, submit=True): is_lwp=0 ) ledger.update(args) + if submit: frappe.get_doc(ledger).submit() else: @@ -91,40 +94,70 @@ def process_expired_allocation(): if leave_type_records: leave_type = [record[0] for record in leave_type_records] - expired_allocation = frappe.get_all("Leave Ledger Entry", + expired_allocation = frappe.get_all("Leave Ledger Entry", + fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'], filters={ 'to_date': add_days(today(), -1), 'transaction_type': 'Leave Allocation', - 'is_carry_forward': 1, + }, + or_filters={ + 'is_carry_forward': 0, 'leave_type': ('in', leave_type) - }, fields=['leaves', 'to_date', 'employee', 'leave_type']) + }) if expired_allocation: create_expiry_ledger_entry(expired_allocation) def create_expiry_ledger_entry(expired_allocation): - ''' Create expiry ledger entry for carry forwarded leaves ''' + ''' Create ledger entry for expired allocation ''' for allocation in expired_allocation: - leaves_taken = get_leaves_taken(allocation) - leaves = flt(allocation.leaves) + flt(leaves_taken) + if allocation.is_carry_forward: + expire_carried_forward_allocation(allocation) + else: + expire_allocation(allocation) - if leaves > 0: - args = frappe._dict( - leaves=allocation.leaves * -1, - to_date=allocation.to_date, - is_carry_forward=1, - is_expired=1, - from_date=allocation.to_date - ) - create_leave_ledger_entry(allocation, args) - -def get_leaves_taken(allocation): +def get_remaining_leaves(allocation): + ''' Returns remaining leaves from the given allocation ''' return frappe.db.get_value("Leave Ledger Entry", filters={ 'employee': allocation.employee, 'leave_type': allocation.leave_type, - 'from_date': ('>=', allocation.from_date), 'to_date': ('<=', allocation.to_date), - 'transaction_type': 'Leave application' - }, fieldname=['SUM(leaves)']) \ No newline at end of file + }, fieldname=['SUM(leaves)']) + +@frappe.whitelist() +def expire_allocation(allocation, expiry_date=None): + ''' expires allocation ''' + leaves = get_remaining_leaves(allocation) + expiry_date = expiry_date if expiry_date else allocation.to_date + + if leaves: + args = dict( + leaves=flt(leaves) * -1, + transaction_name=allocation.name, + from_date=expiry_date, + to_date=expiry_date, + is_carry_forward=0, + is_expired=1 + ) + create_leave_ledger_entry(allocation, args) + + frappe.db.set_value("Leave Allocation", allocation.name, "expired", 1) + +def expire_carried_forward_allocation(allocation): + ''' Expires remaining leaves in the on carried forward allocation ''' + from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period + leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, allocation.from_date, allocation.to_date) + leaves = flt(allocation.leaves) + flt(leaves_taken) + if leaves > 0: + args = frappe._dict( + transaction_name=allocation.name, + transaction_type="Leave Allocation", + leaves=allocation.leaves * -1, + is_carry_forward=allocation.is_carry_forward, + is_expired=1, + from_date=allocation.to_date, + to_date=allocation.to_date + ) + create_leave_ledger_entry(allocation, args) \ No newline at end of file diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index f2a798e1f8..7dfdcc162b 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -48,13 +48,14 @@ def generate_encashment_leave_ledger_entries(): def generate_expiry_allocation_ledger_entries(): ''' fix ledger entries for missing leave allocation transaction ''' + from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation allocation_list = get_allocation_records() for allocation in allocation_list: if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name, 'is_expired': 1}): allocation.update(dict(doctype="Leave Allocation")) allocation_obj = frappe.get_doc(allocation) - allocation_obj.expire_previous_allocation() + expire_allocation(allocation_obj) def get_allocation_records(): return frappe.db.sql(""" From f281f00d4393c5edff7b1e05a27c85662351dc70 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 5 Aug 2019 14:47:02 +0530 Subject: [PATCH 088/117] feat: auto leave encashment --- erpnext/hooks.py | 5 +++-- erpnext/hr/doctype/hr_settings/hr_settings.json | 9 ++++++++- .../doctype/leave_encashment/leave_encashment.py | 16 +++++++++++++++- erpnext/hr/utils.py | 15 ++++++++++++++- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 4f89533ef8..3a73e23991 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -271,11 +271,12 @@ scheduler_events = { "erpnext.projects.doctype.project.project.update_project_sales_billing", "erpnext.projects.doctype.project.project.send_project_status_email_to_users", "erpnext.quality_management.doctype.quality_review.quality_review.review", - "erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation" "erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status", ], "daily_long": [ - "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms" + "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms", + "erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation", + "erpnext.hr.utils.generate_leave_encashment" ], "monthly_long": [ "erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income", diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 8dd0acf455..a41c887852 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -24,6 +24,7 @@ "column_break_18", "leave_approver_mandatory_in_leave_application", "show_leaves_of_all_department_members_in_calendar", + "auto_leave_encashment", "hiring_settings", "check_vacancies" ], @@ -153,12 +154,18 @@ "fieldname": "check_vacancies", "fieldtype": "Check", "label": "Check Vacancies On Job Offer Creation" + }, + { + "default": "0", + "fieldname": "auto_leave_encashment", + "fieldtype": "Check", + "label": "Auto Leave Encashment" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, - "modified": "2019-07-01 18:59:55.256878", + "modified": "2019-08-05 13:07:17.993968", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index c47ff85717..d256c7f369 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -64,7 +64,7 @@ class LeaveEncashment(Document): allocation = self.get_leave_allocation() - self.leave_balance = allocation.total_leaves_allocated - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) + self.leave_balance = allocation.total_leaves_allocated - get_unused_leaves(self.employee, self.leave_type, self.encashment_date) encashable_days = self.leave_balance - frappe.db.get_value('Leave Type', self.leave_type, 'encashment_threshold_days') self.encashable_days = encashable_days if encashable_days > 0 else 0 @@ -102,3 +102,17 @@ class LeaveEncashment(Document): ) create_leave_ledger_entry(self, args, submit) + +def create_leave_encashment(leave_allocation): + ''' Creates leave encashment for the given allocations ''' + for allocation in leave_allocation: + if not get_assigned_salary_structure(allocation.employee, allocation.to_date): + continue + leave_encashment = frappe.get_doc(dict( + doctype="Leave Encashment", + leave_period=allocation.leave_period, + employee=allocation.employee, + leave_type=allocation.leave_type, + encashment_date=allocation.to_date + )) + leave_encashment.insert(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 1d9db07157..bf09de86e7 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ -from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate, flt, cstr +from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate, flt, cstr, add_days, today from frappe.model.document import Document from frappe.desk.form import assign_to from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee @@ -263,6 +263,19 @@ def get_leave_period(from_date, to_date, company): if leave_period: return leave_period +def generate_leave_encashment(): + ''' Generates a draft leave encashment on allocation expiry ''' + from erpnext.hr.doctype.leave_encashment.leave_encashment import create_leave_encashment + if frappe.db.get_single_value('HR Settings', 'auto_leave_encashment'): + leave_type = frappe.db.sql_list("SELECT name FROM `tabLeave Type` WHERE `allow_encashment`=1") + + leave_allocation = frappe.get_all("Leave Allocation", filters={ + 'to_date': add_days(today(), -1), + 'leave_type': ('in', leave_type) + }, fields=['employee', 'leave_period', 'leave_type', 'to_date', 'total_leaves_allocated', 'new_leaves_allocated']) + + create_leave_encashment(leave_allocation=leave_allocation) + def allocate_earned_leaves(): '''Allocate earned leaves to Employees''' e_leave_types = frappe.get_all("Leave Type", From 6bed870dfa368d7b96d57a3f9ddcf40ac5790d80 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 5 Aug 2019 14:47:37 +0530 Subject: [PATCH 089/117] feat: pro-rata leave allocation --- .../leave_application/leave_application.py | 2 +- .../hr/doctype/leave_period/leave_period.js | 2 +- .../hr/doctype/leave_period/leave_period.py | 59 +++++++++++-------- 3 files changed, 36 insertions(+), 27 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 36a4145467..2e43721a58 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -515,7 +515,7 @@ def get_remaining_leaves(allocation, leaves_taken, date, expiry): return remaining_leaves - if expiry: + if expiry and allocation.carry_forwarded_leaves: remaining_leaves = _get_remaining_leaves(allocation.carry_forwarded_leaves, expiry) return flt(allocation.new_leaves_allocated) + flt(remaining_leaves) diff --git a/erpnext/hr/doctype/leave_period/leave_period.js b/erpnext/hr/doctype/leave_period/leave_period.js index b8c5f716f5..bad2b8766c 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.js +++ b/erpnext/hr/doctype/leave_period/leave_period.js @@ -68,7 +68,7 @@ frappe.ui.form.on('Leave Period', { }, { "label": "Add unused leaves from previous allocations", - "fieldname": "carry_forward_leaves", + "fieldname": "carry_forward", "fieldtype": "Check" } ], diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py index f37efeba0b..7b0f882035 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.py +++ b/erpnext/hr/doctype/leave_period/leave_period.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import getdate, cstr, add_days +from frappe.utils import getdate, cstr, add_days, date_diff, getdate, ceil from frappe.model.document import Document from erpnext.hr.utils import validate_overlap, get_employee_leave_policy from erpnext.hr.doctype.leave_allocation.leave_allocation import get_carry_forwarded_leaves @@ -22,8 +22,8 @@ class LeavePeriod(Document): condition_str = " and " + " and ".join(conditions) if len(conditions) else "" - employees = frappe.db.sql_list("select name from tabEmployee where status='Active' {condition}" - .format(condition=condition_str), tuple(values)) + employees = frappe._dict(frappe.db.sql("select name, date_of_joining from tabEmployee where status='Active' {condition}" + .format(condition=condition_str), tuple(values))) return employees @@ -37,29 +37,29 @@ class LeavePeriod(Document): def grant_leave_allocation(self, grade=None, department=None, designation=None, - employee=None, carry_forward_leaves=0): - employees = self.get_employees({ + employee=None, carry_forward=0): + employee_records = self.get_employees({ "grade": grade, "department": department, "designation": designation, "name": employee }) - if employees: - if len(employees) > 20: + if employee_records: + if len(employee_records) > 20: frappe.enqueue(grant_leave_alloc_for_employees, timeout=600, - employees=employees, leave_period=self, carry_forward_leaves=carry_forward_leaves) + employee_records=employee_records, leave_period=self, carry_forward=carry_forward) else: - grant_leave_alloc_for_employees(employees, self, carry_forward_leaves) + grant_leave_alloc_for_employees(employee_records, self, carry_forward) else: frappe.msgprint(_("No Employee Found")) -def grant_leave_alloc_for_employees(employees, leave_period, carry_forward_leaves=0): +def grant_leave_alloc_for_employees(employee_records, leave_period, carry_forward=0): leave_allocations = [] - existing_allocations_for = get_existing_allocations(employees, leave_period.name) + existing_allocations_for = get_existing_allocations(list(employee_records.keys()), leave_period.name) leave_type_details = get_leave_type_details() count = 0 - for employee in employees: + for employee in employee_records.keys(): if employee in existing_allocations_for: continue count +=1 @@ -68,10 +68,10 @@ def grant_leave_alloc_for_employees(employees, leave_period, carry_forward_leave for leave_policy_detail in leave_policy.leave_policy_details: if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp: leave_allocation = create_leave_allocation(employee, leave_policy_detail.leave_type, - leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward_leaves) + leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward, employee_records.get(employee)) leave_allocations.append(leave_allocation) frappe.db.commit() - frappe.publish_progress(count*100/len(set(employees) - set(existing_allocations_for)), title = _("Allocating leaves...")) + frappe.publish_progress(count*100/len(set(employee_records.keys()) - set(existing_allocations_for)), title = _("Allocating leaves...")) if leave_allocations: frappe.msgprint(_("Leaves has been granted sucessfully")) @@ -100,21 +100,30 @@ def get_leave_type_details(): leave_type_details.setdefault(d.name, d) return leave_type_details -def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_type_details, leave_period, carry_forward_leaves): - allocation = frappe.new_doc("Leave Allocation") - allocation.employee = employee - allocation.leave_type = leave_type - allocation.from_date = leave_period.from_date - allocation.to_date = leave_period.to_date +def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_type_details, leave_period, carry_forward, date_of_joining): + ''' Creates leave allocation for the given employee in the provided leave period ''' + if carry_forward and not leave_type_details.get(leave_type).is_carry_forward: + carry_forward = 0 + + # Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period + if getdate(date_of_joining) > getdate(leave_period.from_date): + remaining_period = ((date_diff(leave_period.to_date, date_of_joining) + 1) / (date_diff(leave_period.to_date, leave_period.from_date) + 1)) + new_leaves_allocated = ceil(new_leaves_allocated * remaining_period) + # Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0 if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1: new_leaves_allocated = 0 - allocation.new_leaves_allocated = new_leaves_allocated - allocation.leave_period = leave_period.name - if carry_forward_leaves: - if leave_type_details.get(leave_type).is_carry_forward: - allocation.carry_forward = carry_forward_leaves + allocation = frappe.get_doc(dict( + doctype="Leave Allocation", + employee=employee, + leave_type=leave_type, + from_date=leave_period.from_date, + to_date=leave_period.to_date, + new_leaves_allocated=new_leaves_allocated, + leave_period=leave_period.name, + carry_forward=carry_forward + )) allocation.save(ignore_permissions = True) allocation.submit() return allocation.name \ No newline at end of file From 314647572cc0824dddc34d3057f2e321fa3d9176 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 5 Aug 2019 15:35:19 +0530 Subject: [PATCH 090/117] fix: check for old unexpired allocation --- .../leave_encashment/leave_encashment.py | 2 +- .../leave_ledger_entry/leave_ledger_entry.py | 28 +++++++++++++------ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index d256c7f369..30529b6241 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -64,7 +64,7 @@ class LeaveEncashment(Document): allocation = self.get_leave_allocation() - self.leave_balance = allocation.total_leaves_allocated - get_unused_leaves(self.employee, self.leave_type, self.encashment_date) + self.leave_balance = allocation.total_leaves_allocated - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) encashable_days = self.leave_balance - frappe.db.get_value('Leave Type', self.leave_type, 'encashment_threshold_days') self.encashable_days = encashable_days if encashable_days > 0 else 0 diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index d19e15cf1a..8fca24127b 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -72,8 +72,11 @@ def get_previous_expiry_ledger_entry(ledger): ''' Returns the expiry ledger entry having same creation date as the ledger entry to be cancelled ''' creation_date = frappe.db.get_value("Leave Ledger Entry", filters={ 'transaction_name': ledger.transaction_name, - 'is_expired': 0 - }, fieldname=['creation']).strftime(DATE_FORMAT) + 'is_expired': 0, + 'transaction_type': 'Leave Allocation' + }, fieldname=['creation']) + + creation_date = creation_date.strftime(DATE_FORMAT) if creation_date else '' return frappe.db.get_value("Leave Ledger Entry", filters={ 'creation': ('like', creation_date+"%"), @@ -94,24 +97,31 @@ def process_expired_allocation(): if leave_type_records: leave_type = [record[0] for record in leave_type_records] - expired_allocation = frappe.get_all("Leave Ledger Entry", + + expired_allocation = frappe.db.sql_list("""SELECT name + FROM `tabLeave Ledger Entry` + WHERE + `transaction_type`='Leave Allocation' + AND `is_expired`=1""") + + expire_allocation = frappe.get_all("Leave Ledger Entry", fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'], filters={ - 'to_date': add_days(today(), -1), + 'to_date': ("<", today()), 'transaction_type': 'Leave Allocation', + 'transaction_name': ('not in', expired_allocation) }, or_filters={ 'is_carry_forward': 0, 'leave_type': ('in', leave_type) }) - if expired_allocation: - create_expiry_ledger_entry(expired_allocation) + if expire_allocation: + create_expiry_ledger_entry(expire_allocation) -def create_expiry_ledger_entry(expired_allocation): +def create_expiry_ledger_entry(expire_allocation): ''' Create ledger entry for expired allocation ''' - for allocation in expired_allocation: - + for allocation in expire_allocation: if allocation.is_carry_forward: expire_carried_forward_allocation(allocation) else: From 5cbe6160cade4236894af6bcd41f1e195145042c Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 8 Aug 2019 17:06:15 +0530 Subject: [PATCH 091/117] feat: consider carry forwarded leaves on creation of encashment --- .../leave_allocation/leave_allocation.js | 8 ++-- .../leave_allocation/leave_allocation.json | 26 ++++++++----- .../leave_allocation/leave_allocation.py | 39 ++++++++++++------- .../leave_encashment/leave_encashment.py | 7 ++-- .../leave_ledger_entry/leave_ledger_entry.py | 9 +++-- .../hr/doctype/leave_period/leave_period.py | 2 +- erpnext/hr/utils.py | 2 +- .../v12_0/generate_leave_ledger_entries.py | 13 ++++++- 8 files changed, 69 insertions(+), 37 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 8c910a200a..210a73cfe5 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -63,14 +63,14 @@ frappe.ui.form.on("Leave Allocation", { frm.trigger("calculate_total_leaves_allocated"); }, - carry_forwarded_leaves: function(frm) { + unused_leaves: function(frm) { frm.set_value("total_leaves_allocated", - flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); + flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated)); }, new_leaves_allocated: function(frm) { frm.set_value("total_leaves_allocated", - flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); + flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated)); }, leave_policy: function(frm) { @@ -93,7 +93,7 @@ frappe.ui.form.on("Leave Allocation", { } }) } else if (cint(frm.doc.carry_forward) == 0) { - frm.set_value("carry_forwarded_leaves", 0); + frm.set_value("unused_leaves", 0); frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); } } diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 1618c67d3d..007497e34a 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -17,13 +17,14 @@ "section_break_6", "new_leaves_allocated", "carry_forward", - "carry_forwarded_leaves", + "unused_leaves", "total_leaves_allocated", "total_leaves_encashed", "column_break_10", "compensatory_request", "leave_period", "leave_policy", + "carry_forwarded_leaves_count", "expired", "amended_from", "notes", @@ -119,7 +120,7 @@ }, { "depends_on": "carry_forward", - "fieldname": "carry_forwarded_leaves", + "fieldname": "unused_leaves", "fieldtype": "Float", "label": "Unused leaves", "read_only": 1 @@ -167,6 +168,15 @@ "options": "Leave Policy", "read_only": 1 }, + { + "default": "0", + "fieldname": "expired", + "fieldtype": "Check", + "hidden": 1, + "in_standard_filter": 1, + "label": "Expired", + "read_only": 1 + }, { "fieldname": "amended_from", "fieldtype": "Link", @@ -194,19 +204,17 @@ "width": "300px" }, { - "default": "0", - "fieldname": "expired", - "fieldtype": "Check", - "hidden": 1, - "in_standard_filter": 1, - "label": "Expired", + "depends_on": "carry_forwarded_leaves_count", + "fieldname": "carry_forwarded_leaves_count", + "fieldtype": "Float", + "label": "Carry Forwarded Leaves", "read_only": 1 } ], "icon": "fa fa-ok", "idx": 1, "is_submittable": 1, - "modified": "2019-07-22 17:50:39.591195", + "modified": "2019-08-08 15:08:42.440909", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 2374e469a5..5e81999765 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -50,6 +50,8 @@ class LeaveAllocation(Document): def on_cancel(self): self.create_leave_ledger_entry(submit=False) + if self.carry_forward: + self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True) def validate_period(self): if date_diff(self.to_date, self.from_date) <= 0: @@ -89,24 +91,33 @@ class LeaveAllocation(Document): BackDatedAllocationError) def set_total_leaves_allocated(self): - self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, + self.unused_leaves = get_carry_forwarded_leaves(self.employee, self.leave_type, self.from_date, self.carry_forward) - self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + flt(self.new_leaves_allocated) - self.maintain_carry_forwarded_leaves() + self.total_leaves_allocated = flt(self.unused_leaves) + flt(self.new_leaves_allocated) + + if self.carry_forward: + self.maintain_carry_forwarded_leaves() + self.set_carry_forwarded_leaves_in_previous_allocation() if not self.total_leaves_allocated and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave") and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"): - frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}".format(self.leave_type))) + frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}").format(self.leave_type)) def maintain_carry_forwarded_leaves(self): - ''' reduce the carry forwarded leaves to be within the maximum allowed leaves ''' - if not self.carry_forward: - return + ''' Reduce the carry forwarded leaves to be within the maximum allowed leaves ''' + max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") if self.new_leaves_allocated <= max_leaves_allowed <= self.total_leaves_allocated: - self.carry_forwarded_leaves = max_leaves_allowed - flt(self.new_leaves_allocated) + self.unused_leaves = max_leaves_allowed - flt(self.new_leaves_allocated) self.total_leaves_allocated = flt(max_leaves_allowed) + def set_carry_forwarded_leaves_in_previous_allocation(self, on_cancel=False): + ''' Set carry forwarded leaves in previous allocation ''' + previous_allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee) + if on_cancel: + self.unused_leaves = 0.0 + frappe.db.set_value("Leave Allocation", previous_allocation.name, 'carry_forwarded_leaves_count', self.unused_leaves) + def validate_total_leaves_allocated(self): # Adding a day to include To Date in the difference date_difference = date_diff(self.to_date, self.from_date) + 1 @@ -114,10 +125,10 @@ class LeaveAllocation(Document): frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError) def create_leave_ledger_entry(self, submit=True): - if self.carry_forwarded_leaves: + if self.unused_leaves: expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "expire_carried_forward_leaves") args = dict( - leaves=self.carry_forwarded_leaves, + leaves=self.unused_leaves, from_date=self.from_date, to_date=add_days(self.from_date, expiry_days - 1) if expiry_days else self.to_date, is_carry_forward=1 @@ -142,7 +153,7 @@ def get_previous_allocation(from_date, leave_type, employee): 'docstatus': 1 }, order_by='to_date DESC', - fieldname=['name', 'from_date', 'to_date'], as_dict=1) + fieldname=['name', 'from_date', 'to_date', 'employee', 'leave_type'], as_dict=1) def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 @@ -170,13 +181,13 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): @frappe.whitelist() def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): ''' Returns carry forwarded leaves for the given employee ''' - carry_forwarded_leaves = 0.0 + unused_leaves = 0.0 previous_allocation = get_previous_allocation(date, leave_type, employee) if carry_forward and previous_allocation: validate_carry_forward(leave_type) - carry_forwarded_leaves = get_unused_leaves(employee, leave_type, previous_allocation.from_date, previous_allocation.to_date) + unused_leaves = get_unused_leaves(employee, leave_type, previous_allocation.from_date, previous_allocation.to_date) - return carry_forwarded_leaves + return unused_leaves def get_unused_leaves(employee, leave_type, from_date, to_date): ''' Returns unused leaves between the given period while skipping leave allocation expiry ''' diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index 30529b6241..42f0179baf 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -64,7 +64,8 @@ class LeaveEncashment(Document): allocation = self.get_leave_allocation() - self.leave_balance = allocation.total_leaves_allocated - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) + self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\ + - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) encashable_days = self.leave_balance - frappe.db.get_value('Leave Type', self.leave_type, 'encashment_threshold_days') self.encashable_days = encashable_days if encashable_days > 0 else 0 @@ -76,9 +77,9 @@ class LeaveEncashment(Document): return True def get_leave_allocation(self): - leave_allocation = frappe.db.sql("""select name, to_date, total_leaves_allocated from `tabLeave Allocation` where '{0}' + leave_allocation = frappe.db.sql("""select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}' between from_date and to_date and docstatus=1 and leave_type='{1}' - and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee), as_dict=1) + and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee), as_dict=1) #nosec return leave_allocation[0] if leave_allocation else None diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 8fca24127b..33e4ea0909 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -104,7 +104,7 @@ def process_expired_allocation(): `transaction_type`='Leave Allocation' AND `is_expired`=1""") - expire_allocation = frappe.get_all("Leave Ledger Entry", + expire_allocation = frappe.get_all("Leave Ledger Entry", fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'], filters={ 'to_date': ("<", today()), @@ -119,9 +119,9 @@ def process_expired_allocation(): if expire_allocation: create_expiry_ledger_entry(expire_allocation) -def create_expiry_ledger_entry(expire_allocation): +def create_expiry_ledger_entry(allocations): ''' Create ledger entry for expired allocation ''' - for allocation in expire_allocation: + for allocation in allocations: if allocation.is_carry_forward: expire_carried_forward_allocation(allocation) else: @@ -138,7 +138,7 @@ def get_remaining_leaves(allocation): @frappe.whitelist() def expire_allocation(allocation, expiry_date=None): - ''' expires allocation ''' + ''' expires non-carry forwarded allocation ''' leaves = get_remaining_leaves(allocation) expiry_date = expiry_date if expiry_date else allocation.to_date @@ -146,6 +146,7 @@ def expire_allocation(allocation, expiry_date=None): args = dict( leaves=flt(leaves) * -1, transaction_name=allocation.name, + transaction_type='Leave Allocation', from_date=expiry_date, to_date=expiry_date, is_carry_forward=0, diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py index 7b0f882035..a7de7185c9 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.py +++ b/erpnext/hr/doctype/leave_period/leave_period.py @@ -22,7 +22,7 @@ class LeavePeriod(Document): condition_str = " and " + " and ".join(conditions) if len(conditions) else "" - employees = frappe._dict(frappe.db.sql("select name, date_of_joining from tabEmployee where status='Active' {condition}" + employees = frappe._dict(frappe.db.sql("select name, date_of_joining from tabEmployee where status='Active' {condition}" #nosec .format(condition=condition_str), tuple(values))) return employees diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index bf09de86e7..4fe9f6b285 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -318,7 +318,7 @@ def create_earned_leave_ledger_entry(allocation, earned_leaves, date): ''' Create leave ledger entry based on the earned leave frequency ''' allocation.new_leaves_allocated = earned_leaves allocation.from_date = date - allocation.carry_forwarded_leaves = 0 + allocation.unused_leaves = 0 allocation.create_leave_ledger_entry() def check_frequency_hit(from_date, to_date, frequency): diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 7dfdcc162b..c1e1b4f3b2 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -13,11 +13,22 @@ def execute(): if frappe.db.a_row_exists("Leave Ledger Entry"): return + if not frappe.get_meta("Leave Allocation").has_field("unused_leaves"): + frappe.reload_doc("HR", "doctype", "Leave Allocation") + update_leave_allocation_fieldname() + generate_allocation_ledger_entries() generate_application_leave_ledger_entries() generate_encashment_leave_ledger_entries() generate_expiry_allocation_ledger_entries() +def update_leave_allocation_fieldname(): + ''' maps data from old field to the new field ''' + frappe.db.sql(""" + UPDATE `tabLeave Allocation` + SET `unused_leaves` = `carry_forwarded_leaves` + """) + def generate_allocation_ledger_entries(): ''' fix ledger entries for missing leave allocation transaction ''' allocation_list = get_allocation_records() @@ -64,7 +75,7 @@ def get_allocation_records(): employee, leave_type, new_leaves_allocated, - carry_forwarded_leaves, + unused_leaves, from_date, to_date, carry_forward From 9bc4232af619503028400ffdc446428feb81691d Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 8 Aug 2019 17:11:08 +0530 Subject: [PATCH 092/117] feat: calculate remaining leaves both multiple simultaneous allocation --- .../leave_allocation/test_leave_allocation.js | 2 +- .../leave_allocation/test_leave_allocation.py | 10 ++++------ .../leave_application/leave_application.py | 16 ++++++++++------ .../leave_application/test_leave_application.py | 15 ++++++++++++++- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js index b8f4fafa6d..0ef78f2f88 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js @@ -34,7 +34,7 @@ QUnit.test("Test: Leave allocation [HR]", function (assert) { () => assert.equal(today_date, cur_frm.doc.from_date, "from date correctly set"), // check for total leaves - () => assert.equal(cur_frm.doc.carry_forwarded_leaves + 2, cur_frm.doc.total_leaves_allocated, + () => assert.equal(cur_frm.doc.unused_leaves + 2, cur_frm.doc.total_leaves_allocated, "total leave calculation is correctly set"), () => done() ]); diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 4f4e0ab1fc..8f876ae7ce 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -80,7 +80,7 @@ class TestLeaveAllocation(unittest.TestCase): leave_type="_Test_CF_leave", from_date=add_months(nowdate(), -12), to_date=add_months(nowdate(), -1), - carry_forward=1) + carry_forward=0) leave_allocation.submit() # leave allocation with carry forward from previous allocation @@ -89,7 +89,7 @@ class TestLeaveAllocation(unittest.TestCase): carry_forward=1) leave_allocation_1.submit() - self.assertEquals(leave_allocation.total_leaves_allocated, leave_allocation_1.carry_forwarded_leaves) + self.assertEquals(leave_allocation.total_leaves_allocated, leave_allocation_1.unused_leaves) def test_carry_forward_leaves_expiry(self): frappe.db.sql("delete from `tabLeave Allocation`") @@ -105,11 +105,9 @@ class TestLeaveAllocation(unittest.TestCase): leave_type="_Test_CF_leave_expiry", from_date=add_months(nowdate(), -24), to_date=add_months(nowdate(), -12), - carry_forward=1) + carry_forward=0) leave_allocation.submit() - expire_allocation(leave_allocation) - leave_allocation = create_leave_allocation( leave_type="_Test_CF_leave_expiry", from_date=add_days(nowdate(), -90), @@ -128,7 +126,7 @@ class TestLeaveAllocation(unittest.TestCase): to_date=add_months(nowdate(), 12)) leave_allocation_1.submit() - self.assertEquals(leave_allocation_1.carry_forwarded_leaves, leave_allocation.new_leaves_allocated) + self.assertEquals(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated) def test_creation_of_leave_ledger_entry_on_submit(self): frappe.db.sql("delete from `tabLeave Allocation`") diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 2e43721a58..0aa8849e87 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -387,10 +387,12 @@ class LeaveApplication(Document): create_leave_ledger_entry(self, args, submit) def get_allocation_expiry(employee, leave_type, to_date, from_date): + ''' Returns expiry of carry forward allocation in leave ledger entry ''' expiry = frappe.get_all("Leave Ledger Entry", filters={ 'employee': employee, 'leave_type': leave_type, + 'is_carry_forward': 1, 'transaction_type': 'Leave Allocation', 'to_date': ['between', (from_date, to_date)] },fields=['to_date']) @@ -487,7 +489,7 @@ def get_leave_allocation_records(employee, date, leave_type=None): "from_date": d.from_date, "to_date": d.to_date, "total_leaves_allocated": flt(d.cf_leaves) + flt(d.new_leaves), - "carry_forwarded_leaves": d.cf_leaves, + "unused_leaves": d.cf_leaves, "new_leaves_allocated": d.new_leaves, "leave_type": d.leave_type })) @@ -515,12 +517,14 @@ def get_remaining_leaves(allocation, leaves_taken, date, expiry): return remaining_leaves - if expiry and allocation.carry_forwarded_leaves: - remaining_leaves = _get_remaining_leaves(allocation.carry_forwarded_leaves, expiry) + total_leaves = allocation.total_leaves_allocated - return flt(allocation.new_leaves_allocated) + flt(remaining_leaves) - else: - return _get_remaining_leaves(allocation.total_leaves_allocated, allocation.to_date) + if expiry and allocation.unused_leaves: + remaining_leaves = _get_remaining_leaves(allocation.unused_leaves, expiry) + + total_leaves = flt(allocation.new_leaves_allocated) + flt(remaining_leaves) + + return _get_remaining_leaves(total_leaves, allocation.to_date) def get_leaves_for_period(employee, leave_type, from_date, to_date): leave_entries = get_leave_entries(employee, leave_type, from_date, to_date) diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 709a2f57df..0f7bf86b22 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -542,6 +542,19 @@ class TestLeaveApplication(unittest.TestCase): self.assertEquals(leave_ledger_entry[0].leaves, -9) self.assertEquals(leave_ledger_entry[1].leaves, -2) + def test_leave_application_creation_after_expiry(self): + # test leave balance for carry forwarded allocation + employee = get_employee() + leave_type = create_leave_type( + leave_type_name="_Test_CF_leave_expiry", + is_carry_forward=1, + expire_carried_forward_leaves=90) + leave_type.submit() + + create_carry_forwarded_allocation(employee, leave_type) + + self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0) + def create_carry_forwarded_allocation(employee, leave_type): # initial leave allocation leave_allocation = create_leave_allocation( @@ -550,7 +563,7 @@ def create_carry_forwarded_allocation(employee, leave_type): employee_name=employee.employee_name, from_date=add_months(nowdate(), -24), to_date=add_months(nowdate(), -12), - carry_forward=1) + carry_forward=0) leave_allocation.submit() leave_allocation = create_leave_allocation( From bafc89f4399600bea1df4eecd6bc708b7ebf20c2 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 8 Aug 2019 17:43:23 +0530 Subject: [PATCH 093/117] refactor: convert raw sql to orm --- .../leave_allocation/leave_allocation.py | 2 +- .../leave_allocation/test_leave_allocation.py | 2 +- .../leave_application/leave_application.js | 2 +- .../test_leave_application.py | 6 +-- .../leave_ledger_entry/leave_ledger_entry.py | 2 +- .../hr/doctype/leave_period/leave_period.py | 2 +- erpnext/hr/doctype/leave_type/leave_type.json | 6 +-- .../hr/doctype/leave_type/test_leave_type.py | 2 +- .../v12_0/generate_leave_ledger_entries.py | 49 +++++-------------- 9 files changed, 23 insertions(+), 50 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 5e81999765..c11c125c6a 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -126,7 +126,7 @@ class LeaveAllocation(Document): def create_leave_ledger_entry(self, submit=True): if self.unused_leaves: - expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "expire_carried_forward_leaves") + expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "expire_carry_forwarded_leaves_after_days") args = dict( leaves=self.unused_leaves, from_date=self.from_date, diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 8f876ae7ce..bdba8c9f8f 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -97,7 +97,7 @@ class TestLeaveAllocation(unittest.TestCase): leave_type = create_leave_type( leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1, - expire_carried_forward_leaves=90) + expire_carry_forwarded_leaves_after_days=90) leave_type.submit() # initial leave allocation diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index a755b57608..174641048b 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -49,7 +49,7 @@ frappe.ui.form.on("Leave Application", { async: false, args: { employee: frm.doc.employee, - date: frm.doc.from_date? frm.doc.from_date:frm.doc.posting_date + date: frm.doc.from_date || frm.doc.posting_date }, callback: function(r) { if (!r.exc && r.message['leave_allocation']) { diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 0f7bf86b22..ad141a5748 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -415,7 +415,7 @@ class TestLeaveApplication(unittest.TestCase): leave_type = create_leave_type( leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1, - expire_carried_forward_leaves=90) + expire_carry_forwarded_leaves_after_days=90) leave_type.submit() create_carry_forwarded_allocation(employee, leave_type) @@ -516,7 +516,7 @@ class TestLeaveApplication(unittest.TestCase): leave_type = create_leave_type( leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1, - expire_carried_forward_leaves=90) + expire_carry_forwarded_leaves_after_days=90) leave_type.submit() create_carry_forwarded_allocation(employee, leave_type) @@ -548,7 +548,7 @@ class TestLeaveApplication(unittest.TestCase): leave_type = create_leave_type( leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1, - expire_carried_forward_leaves=90) + expire_carry_forwarded_leaves_after_days=90) leave_type.submit() create_carry_forwarded_allocation(employee, leave_type) diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py index 33e4ea0909..c82114e6d5 100644 --- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -92,7 +92,7 @@ def process_expired_allocation(): # fetch leave type records that has carry forwarded leaves expiry leave_type_records = frappe.db.get_values("Leave Type", filters={ - 'expire_carried_forward_leaves': (">", 0) + 'expire_carry_forwarded_leaves_after_days': (">", 0) }, fieldname=['name']) if leave_type_records: diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py index a7de7185c9..a8566c4ffb 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.py +++ b/erpnext/hr/doctype/leave_period/leave_period.py @@ -95,7 +95,7 @@ def get_existing_allocations(employees, leave_period): def get_leave_type_details(): leave_type_details = frappe._dict() leave_types = frappe.get_all("Leave Type", - fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carried_forward_leaves"]) + fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"]) for d in leave_types: leave_type_details.setdefault(d.name, d) return leave_type_details diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 7a279063ce..36d5f8b3d1 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -421,7 +421,7 @@ "depends_on": "", "description": "Calculated in days", "fetch_if_empty": 0, - "fieldname": "expire_carried_forward_leaves", + "fieldname": "expire_carry_forwarded_leaves_after_days", "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, @@ -430,7 +430,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Expire Carried Forward Leaves", + "label": "Expire Carry Forwarded Leaves After Days", "length": 0, "no_copy": 0, "permlevel": 0, @@ -728,7 +728,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-07-22 15:38:39.334283", + "modified": "2019-08-01 15:38:39.334283", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py index d9852be6c9..0c4f435860 100644 --- a/erpnext/hr/doctype/leave_type/test_leave_type.py +++ b/erpnext/hr/doctype/leave_type/test_leave_type.py @@ -19,7 +19,7 @@ def create_leave_type(**args): "is_earned_leave": args.is_earned_leave or 0, "is_lwp": args.is_lwp or 0, "is_carry_forward": args.is_carry_forward or 0, - "expire_carried_forward_leaves": args.expire_carried_forward_leaves or 0, + "expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0, "encashment_threshold_days": args.encashment_threshold_days or 5, "earning_component": "Leave Encashment" }) diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index c1e1b4f3b2..38f6883b9a 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -69,45 +69,18 @@ def generate_expiry_allocation_ledger_entries(): expire_allocation(allocation_obj) def get_allocation_records(): - return frappe.db.sql(""" - SELECT - name, - employee, - leave_type, - new_leaves_allocated, - unused_leaves, - from_date, - to_date, - carry_forward - FROM `tabLeave Allocation` - WHERE - docstatus=1 - ORDER BY to_date ASC - """, as_dict=1) + return frappe.get_all("Leave Allocation", filters={ + "docstatus": 1 + }, fields=['name', 'employee', 'leave_type', 'new_leaves_allocated', + 'unused_leaves', 'from_date', 'to_date', 'carry_forward' + ], order_by='to_date ASC') def get_leaves_application_records(): - return frappe.db.sql(""" - SELECT - name, - employee, - leave_type, - total_leave_days, - from_date, - to_date - FROM `tabLeave Application` - WHERE - docstatus=1 - """, as_dict=1) + return frappe.get_all("Leave Application", filters={ + "docstatus": 1 + }, fields=['name', 'employee', 'leave_type', 'total_leave_days', 'from_date', 'to_date']) def get_leave_encashment_records(): - return frappe.db.sql(""" - SELECT - name, - employee, - leave_type, - encashable_days, - encashment_date - FROM `tabLeave Encashment` - WHERE - docstatus=1 - """, as_dict=1) \ No newline at end of file + return frappe.get_all("Leave Encashment", filters={ + "docstatus": 1 + }, fields=['name', 'employee', 'leave_type', 'encashable_days', 'encashment_date']) From 3662ed50d2eb624b62c4b27d00e53b9689560223 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 8 Aug 2019 19:47:17 +0530 Subject: [PATCH 094/117] fix: multiple changes --- erpnext/hr/doctype/leave_type/leave_type.json | 4 ++-- erpnext/hr/doctype/leave_type/leave_type.py | 2 +- .../employee_leave_balance.py | 2 +- erpnext/hr/utils.py | 15 +++++++++------ erpnext/patches.txt | 1 - 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 36d5f8b3d1..2f15e3b3c1 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -430,7 +430,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Expire Carry Forwarded Leaves After Days", + "label": "Expire Carry Forwarded Leaves (Days)", "length": 0, "no_copy": 0, "permlevel": 0, @@ -728,7 +728,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-08-01 15:38:39.334283", + "modified": "2019-08-02 15:38:39.334283", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index 75336e0c7e..3a19fa98e6 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -14,4 +14,4 @@ class LeaveType(Document): if self.is_lwp: leave_allocation = frappe.db.sql_list("""select name from `tabLeave Allocation` where leave_type=%s""", (self.name)) if leave_allocation: - frappe.throw(_("""Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay""").format(", ".join(leave_allocation))) #nosec \ No newline at end of file + frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec \ No newline at end of file diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index b99708a91f..66e3614982 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -66,7 +66,7 @@ def get_data(filters, leave_types): filters.from_date, filters.to_date) * -1 # opening balance - opening = get_leave_balance_on(employee.name, leave_type, filters.from_date) + opening = get_total_allocated_leaves(employee.name, leave_type, filters.from_date, filters.to_date) # closing balance closing = flt(opening) - flt(leaves_taken) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 4fe9f6b285..3accbbb156 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -266,8 +266,10 @@ def get_leave_period(from_date, to_date, company): def generate_leave_encashment(): ''' Generates a draft leave encashment on allocation expiry ''' from erpnext.hr.doctype.leave_encashment.leave_encashment import create_leave_encashment + if frappe.db.get_single_value('HR Settings', 'auto_leave_encashment'): - leave_type = frappe.db.sql_list("SELECT name FROM `tabLeave Type` WHERE `allow_encashment`=1") + leave_type = frappe.get_all('Leave Type', filters={'allow_encashment': 1}, fields=['name']) + leave_type=[l['name'] for l in leave_type] leave_allocation = frappe.get_all("Leave Allocation", filters={ 'to_date': add_days(today(), -1), @@ -285,9 +287,8 @@ def allocate_earned_leaves(): divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12} for e_leave_type in e_leave_types: - leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where '{0}' - between from_date and to_date and docstatus=1 and leave_type='{1}'""" - .format(today, e_leave_type.name), as_dict=1) + leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where %s + between from_date and to_date and docstatus=1 and leave_type=%s""", (today, e_leave_type.name), as_dict=1) for allocation in leave_allocations: leave_policy = get_employee_leave_policy(allocation.employee) if not leave_policy: @@ -295,8 +296,10 @@ def allocate_earned_leaves(): if not e_leave_type.earned_leave_frequency == "Monthly": if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency): continue - annual_allocation = frappe.db.sql("""select annual_allocation from `tabLeave Policy Detail` - where parent=%s and leave_type=%s""", (leave_policy.name, e_leave_type.name)) + annual_allocation = frappe.get_all("Leave Policy Detail", filters={ + 'parent': leave_policy.name, + 'leave_type': e_leave_type.name + }, fields=['annual_allocation']) if annual_allocation and annual_allocation[0]: earned_leaves = flt(annual_allocation[0][0]) / divide_by_frequency[e_leave_type.earned_leave_frequency] if e_leave_type.rounding == "0.5": diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ce2a5192d9..9b8c2f0a6b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -603,7 +603,6 @@ erpnext.patches.v11_1.set_salary_details_submittable erpnext.patches.v11_1.rename_depends_on_lwp execute:frappe.delete_doc("Report", "Inactive Items") erpnext.patches.v11_1.delete_scheduling_tool -execute:frappe.delete_doc_if_exists("Page", "support-analytics") erpnext.patches.v12_0.rename_tolerance_fields erpnext.patches.v12_0.make_custom_fields_for_bank_remittance #14-06-2019 execute:frappe.delete_doc_if_exists("Page", "support-analytics") From 261d132f3a90ffc0b3391d4baf7f2bc1e50a9c77 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 9 Aug 2019 13:18:52 +0530 Subject: [PATCH 095/117] fix: calculate earned leaves based on annual allocation --- erpnext/hr/utils.py | 8 ++++---- erpnext/patches/v12_0/generate_leave_ledger_entries.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 3accbbb156..7ef61e920c 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -296,12 +296,12 @@ def allocate_earned_leaves(): if not e_leave_type.earned_leave_frequency == "Monthly": if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency): continue - annual_allocation = frappe.get_all("Leave Policy Detail", filters={ + annual_allocation = frappe.db.get_value("Leave Policy Detail", filters={ 'parent': leave_policy.name, 'leave_type': e_leave_type.name - }, fields=['annual_allocation']) - if annual_allocation and annual_allocation[0]: - earned_leaves = flt(annual_allocation[0][0]) / divide_by_frequency[e_leave_type.earned_leave_frequency] + }, fieldname=['annual_allocation']) + if annual_allocation: + earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency] if e_leave_type.rounding == "0.5": earned_leaves = round(earned_leaves * 2) / 2 else: diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py index 38f6883b9a..44b59bf09b 100644 --- a/erpnext/patches/v12_0/generate_leave_ledger_entries.py +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -83,4 +83,4 @@ def get_leaves_application_records(): def get_leave_encashment_records(): return frappe.get_all("Leave Encashment", filters={ "docstatus": 1 - }, fields=['name', 'employee', 'leave_type', 'encashable_days', 'encashment_date']) + }, fields=['name', 'employee', 'leave_type', 'encashable_days', 'encashment_date']) \ No newline at end of file From 8b9b77959d0b2b03158cfa376d3d4d74ee1b3005 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 13 Aug 2019 13:20:10 +0530 Subject: [PATCH 096/117] fix: fetch min date when carry forward expiry is greater than leave allocation expiry --- erpnext/hr/doctype/leave_allocation/leave_allocation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index c11c125c6a..296a52c2c7 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import flt, date_diff, formatdate, add_days, today +from frappe.utils import flt, date_diff, formatdate, add_days, today, getdate from frappe import _ from frappe.model.document import Document from erpnext.hr.utils import set_employee_name, get_leave_period @@ -127,10 +127,11 @@ class LeaveAllocation(Document): def create_leave_ledger_entry(self, submit=True): if self.unused_leaves: expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "expire_carry_forwarded_leaves_after_days") + end_date = add_days(self.from_date, expiry_days - 1) if expiry_days else self.to_date args = dict( leaves=self.unused_leaves, from_date=self.from_date, - to_date=add_days(self.from_date, expiry_days - 1) if expiry_days else self.to_date, + to_date= min(getdate(end_date), getdate(self.to_date)), is_carry_forward=1 ) create_leave_ledger_entry(self, args, submit) From 44546305490e99213f0310dc0bf6cb8549138d59 Mon Sep 17 00:00:00 2001 From: Vignesh S Date: Tue, 30 Jul 2019 01:16:17 +0530 Subject: [PATCH 097/117] feat(Auto Attendance): Add grace period Co-authored-by: Karthikeyan S --- erpnext/hr/doctype/attendance/attendance.json | 19 +++++++- .../employee_checkin/employee_checkin.json | 18 +------ .../employee_checkin/employee_checkin.py | 22 +++++++-- .../employee_checkin/test_employee_checkin.py | 8 ++-- erpnext/hr/doctype/shift_type/shift_type.json | 47 +------------------ erpnext/hr/doctype/shift_type/shift_type.py | 19 +++++--- .../monthly_attendance_sheet.py | 8 +++- 7 files changed, 61 insertions(+), 80 deletions(-) diff --git a/erpnext/hr/doctype/attendance/attendance.json b/erpnext/hr/doctype/attendance/attendance.json index eb38147a98..bc89b368d3 100644 --- a/erpnext/hr/doctype/attendance/attendance.json +++ b/erpnext/hr/doctype/attendance/attendance.json @@ -4,6 +4,7 @@ "creation": "2013-01-10 16:34:13", "doctype": "DocType", "document_type": "Setup", + "engine": "InnoDB", "field_order": [ "attendance_details", "naming_series", @@ -19,7 +20,9 @@ "department", "shift", "attendance_request", - "amended_from" + "amended_from", + "late_entry", + "early_exit" ], "fields": [ { @@ -153,12 +156,24 @@ "fieldtype": "Link", "label": "Shift", "options": "Shift Type" + }, + { + "default": "0", + "fieldname": "late_entry", + "fieldtype": "Check", + "label": "Late Entry" + }, + { + "default": "0", + "fieldname": "early_exit", + "fieldtype": "Check", + "label": "Early Exit" } ], "icon": "fa fa-ok", "idx": 1, "is_submittable": 1, - "modified": "2019-06-05 19:37:30.410071", + "modified": "2019-07-29 20:35:40.845422", "modified_by": "Administrator", "module": "HR", "name": "Attendance", diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.json b/erpnext/hr/doctype/employee_checkin/employee_checkin.json index 15ec7c0b1b..08fa4afa5c 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.json +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.json @@ -14,8 +14,6 @@ "device_id", "skip_auto_attendance", "attendance", - "entry_grace_period_consequence", - "exit_grace_period_consequence", "shift_start", "shift_end", "shift_actual_start", @@ -80,20 +78,6 @@ "options": "Attendance", "read_only": 1 }, - { - "default": "0", - "fieldname": "entry_grace_period_consequence", - "fieldtype": "Check", - "hidden": 1, - "label": "Entry Grace Period Consequence" - }, - { - "default": "0", - "fieldname": "exit_grace_period_consequence", - "fieldtype": "Check", - "hidden": 1, - "label": "Exit Grace Period Consequence" - }, { "fieldname": "shift_start", "fieldtype": "Datetime", @@ -119,7 +103,7 @@ "label": "Shift Actual End" } ], - "modified": "2019-06-10 15:33:22.731697", + "modified": "2019-07-23 23:47:33.975263", "modified_by": "Administrator", "module": "HR", "name": "Employee Checkin", diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py index b0e15d96ed..d7d6706140 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py @@ -72,7 +72,7 @@ def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=N return doc -def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, shift=None): +def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, shift=None): """Creates an attendance and links the attendance to the Employee Checkin. Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown. @@ -98,7 +98,9 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki 'status': attendance_status, 'working_hours': working_hours, 'company': employee_doc.company, - 'shift': shift + 'shift': shift, + 'late_entry': late_entry, + 'early_exit': early_exit } attendance = frappe.get_doc(doc_dict).insert() attendance.submit() @@ -124,11 +126,16 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type): :param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out' """ total_hours = 0 + in_time = out_time = None if check_in_out_type == 'Alternating entries as IN and OUT during the same shift': + in_time = logs[0].time + if len(logs) >= 2: + out_time = logs[-1].time if working_hours_calc_type == 'First Check-in and Last Check-out': # assumption in this case: First log always taken as IN, Last log always taken as OUT - total_hours = time_diff_in_hours(logs[0].time, logs[-1].time) + total_hours = time_diff_in_hours(in_time, logs[-1].time) elif working_hours_calc_type == 'Every Valid Check-in and Check-out': + logs = logs[:] while len(logs) >= 2: total_hours += time_diff_in_hours(logs[0].time, logs[1].time) del logs[:2] @@ -138,11 +145,15 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type): first_in_log = logs[find_index_in_dict(logs, 'log_type', 'IN')] last_out_log = logs[len(logs)-1-find_index_in_dict(reversed(logs), 'log_type', 'OUT')] if first_in_log and last_out_log: - total_hours = time_diff_in_hours(first_in_log.time, last_out_log.time) + in_time, out_time = first_in_log.time, last_out_log.time + total_hours = time_diff_in_hours(in_time, out_time) elif working_hours_calc_type == 'Every Valid Check-in and Check-out': in_log = out_log = None for log in logs: if in_log and out_log: + if not in_time: + in_time = in_log.time + out_time = out_log.time total_hours += time_diff_in_hours(in_log.time, out_log.time) in_log = out_log = None if not in_log: @@ -150,8 +161,9 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type): elif not out_log: out_log = log if log.log_type == 'OUT' else None if in_log and out_log: + out_time = out_log.time total_hours += time_diff_in_hours(in_log.time, out_log.time) - return total_hours + return total_hours, in_time, out_time def time_diff_in_hours(start, end): return round((end-start).total_seconds() / 3600, 1) diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py index 424d1a3c1b..9f12ef24e6 100644 --- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py @@ -70,16 +70,16 @@ class TestEmployeeCheckin(unittest.TestCase): logs_type_2 = [frappe._dict(x) for x in logs_type_2] working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[0]) - self.assertEqual(working_hours, 6.5) + self.assertEqual(working_hours, (6.5, logs_type_1[0].time, logs_type_1[-1].time)) working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[1]) - self.assertEqual(working_hours, 4.5) + self.assertEqual(working_hours, (4.5, logs_type_1[0].time, logs_type_1[-1].time)) working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[0]) - self.assertEqual(working_hours, 5) + self.assertEqual(working_hours, (5, logs_type_2[1].time, logs_type_2[-1].time)) working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[1]) - self.assertEqual(working_hours, 4.5) + self.assertEqual(working_hours, (4.5, logs_type_2[1].time, logs_type_2[-1].time)) def make_n_checkins(employee, n, hours_to_reverse=1): logs = [make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n+1))] diff --git a/erpnext/hr/doctype/shift_type/shift_type.json b/erpnext/hr/doctype/shift_type/shift_type.json index 86039deebd..61f3d2c279 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.json +++ b/erpnext/hr/doctype/shift_type/shift_type.json @@ -23,14 +23,9 @@ "grace_period_settings_auto_attendance_section", "enable_entry_grace_period", "late_entry_grace_period", - "consequence_after", - "consequence", "column_break_18", "enable_exit_grace_period", - "enable_different_consequence_for_early_exit", - "early_exit_grace_period", - "early_exit_consequence_after", - "early_exit_consequence" + "early_exit_grace_period" ], "fields": [ { @@ -107,21 +102,6 @@ "fieldtype": "Int", "label": "Late Entry Grace Period" }, - { - "depends_on": "enable_entry_grace_period", - "description": "The number of occurrence after which the consequence is executed.", - "fieldname": "consequence_after", - "fieldtype": "Int", - "label": "Consequence after" - }, - { - "default": "Half Day", - "depends_on": "enable_entry_grace_period", - "fieldname": "consequence", - "fieldtype": "Select", - "label": "Consequence", - "options": "Half Day\nAbsent" - }, { "fieldname": "column_break_18", "fieldtype": "Column Break" @@ -132,13 +112,6 @@ "fieldtype": "Check", "label": "Enable Exit Grace Period" }, - { - "default": "0", - "depends_on": "enable_exit_grace_period", - "fieldname": "enable_different_consequence_for_early_exit", - "fieldtype": "Check", - "label": "Enable Different Consequence for Early Exit" - }, { "depends_on": "eval:doc.enable_exit_grace_period", "description": "The time before the shift end time when check-out is considered as early (in minutes).", @@ -146,21 +119,6 @@ "fieldtype": "Int", "label": "Early Exit Grace Period" }, - { - "depends_on": "eval:doc.enable_exit_grace_period && doc.enable_different_consequence_for_early_exit", - "description": "The number of occurrence after which the consequence is executed.", - "fieldname": "early_exit_consequence_after", - "fieldtype": "Int", - "label": "Early Exit Consequence after" - }, - { - "default": "Half Day", - "depends_on": "eval:doc.enable_exit_grace_period && doc.enable_different_consequence_for_early_exit", - "fieldname": "early_exit_consequence", - "fieldtype": "Select", - "label": "Early Exit Consequence", - "options": "Half Day\nAbsent" - }, { "default": "60", "description": "Time after the end of shift during which check-out is considered for attendance.", @@ -178,7 +136,6 @@ "depends_on": "enable_auto_attendance", "fieldname": "grace_period_settings_auto_attendance_section", "fieldtype": "Section Break", - "hidden": 1, "label": "Grace Period Settings For Auto Attendance" }, { @@ -201,7 +158,7 @@ "label": "Last Sync of Checkin" } ], - "modified": "2019-06-10 06:02:44.272036", + "modified": "2019-07-30 01:05:24.660666", "modified_by": "Administrator", "module": "HR", "name": "Shift Type", diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index b98f445c0f..8de92b2761 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -28,8 +28,8 @@ class ShiftType(Document): logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time") for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])): single_shift_logs = list(group) - attendance_status, working_hours = self.get_attendance(single_shift_logs) - mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, self.name) + attendance_status, working_hours, late_entry, early_exit = self.get_attendance(single_shift_logs) + mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, self.name) for employee in self.get_assigned_employee(self.process_attendance_after, True): self.mark_absent_for_dates_with_no_attendance(employee) @@ -39,12 +39,19 @@ class ShiftType(Document): 1. These logs belongs to an single shift, single employee and is not in a holiday date. 2. Logs are in chronological order """ - total_working_hours = calculate_working_hours(logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on) + late_entry = early_exit = False + total_working_hours, in_time, out_time = calculate_working_hours(logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on) + if cint(self.enable_entry_grace_period) and in_time and in_time > logs[0].shift_start + timedelta(minutes=cint(self.late_entry_grace_period)): + late_entry = True + + if cint(self.enable_exit_grace_period) and out_time and out_time < logs[0].shift_end - timedelta(minutes=cint(self.early_exit_grace_period)): + early_exit = True + if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent: - return 'Absent', total_working_hours + return 'Absent', total_working_hours, late_entry, early_exit if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day: - return 'Half Day', total_working_hours - return 'Present', total_working_hours + return 'Half Day', total_working_hours, late_entry, early_exit + return 'Present', total_working_hours, late_entry, early_exit def mark_absent_for_dates_with_no_attendance(self, employee): """Marks Absents for the given employee on working days in this shift which have no attendance marked. diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py index e9c702944d..1e9c83bf3e 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -25,6 +25,7 @@ def execute(filters=None): leave_types = frappe.db.sql("""select name from `tabLeave Type`""", as_list=True) leave_list = [d[0] for d in leave_types] columns.extend(leave_list) + columns.extend([_("Total Late Entries") + ":Float:120", _("Total Early Exits") + ":Float:120"]) for emp in sorted(att_map): emp_det = emp_map.get(emp) @@ -65,6 +66,10 @@ def execute(filters=None): leave_details = frappe.db.sql("""select leave_type, status, count(*) as count from `tabAttendance`\ where leave_type is not NULL %s group by leave_type, status""" % conditions, filters, as_dict=1) + + time_default_counts = frappe.db.sql("""select (select count(*) from `tabAttendance` where \ + late_entry = 1 %s) as late_entry_count, (select count(*) from tabAttendance where \ + early_exit = 1 %s) as early_exit_count""" % (conditions, conditions), filters) leaves = {} for d in leave_details: @@ -80,7 +85,8 @@ def execute(filters=None): row.append(leaves[d]) else: row.append("0.0") - + + row.extend([time_default_counts[0][0],time_default_counts[0][1]]) data.append(row) return columns, data From 107b2768fd1a585f1ca83d1aad202751c426f3ca Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 14 Aug 2019 16:09:16 +0530 Subject: [PATCH 098/117] refactor: replace raw sql with orm --- erpnext/hr/doctype/leave_type/leave_type.py | 9 ++++++++- erpnext/hr/utils.py | 1 - 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index 3a19fa98e6..598bff2f13 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import calendar import frappe from datetime import datetime +from frappe.utils import today from frappe import _ from frappe.model.document import Document @@ -12,6 +13,12 @@ from frappe.model.document import Document class LeaveType(Document): def validate(self): if self.is_lwp: - leave_allocation = frappe.db.sql_list("""select name from `tabLeave Allocation` where leave_type=%s""", (self.name)) + leave_allocation = frappe.get_all("Leave Allocation", filters={ + 'leave_type': self.name, + 'from_date': ("<=", today()), + 'to_date': (">=", today()) + }, ['name']) + leave_allocation = [l['name'] for l in leave_allocation] + frappe.db("""select name from `tabLeave Allocation` where leave_type=%s""", (self.name)) if leave_allocation: frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec \ No newline at end of file diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 2183c5e678..1464a77936 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -323,7 +323,6 @@ def allocate_earned_leaves(): allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False) create_earned_leave_ledger_entry(allocation, earned_leaves, today) - def create_earned_leave_ledger_entry(allocation, earned_leaves, date): ''' Create leave ledger entry based on the earned leave frequency ''' allocation.new_leaves_allocated = earned_leaves From ae2a0fb4c7ae3704e8f37fa3820cd9511fb05bf5 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 14 Aug 2019 16:50:31 +0530 Subject: [PATCH 099/117] chore: remove unused code --- erpnext/hr/doctype/leave_type/leave_type.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index 598bff2f13..c0d1296841 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -17,8 +17,7 @@ class LeaveType(Document): 'leave_type': self.name, 'from_date': ("<=", today()), 'to_date': (">=", today()) - }, ['name']) + }, fields=['name']) leave_allocation = [l['name'] for l in leave_allocation] - frappe.db("""select name from `tabLeave Allocation` where leave_type=%s""", (self.name)) if leave_allocation: - frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec \ No newline at end of file + frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec From dbb44c8950c468a3681f6cda46b7be3a67a86245 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 14 Aug 2019 17:38:44 +0530 Subject: [PATCH 100/117] fix: restrict the payment order to non received type payment entries --- erpnext/accounts/doctype/payment_order/payment_order.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/payment_order/payment_order.js b/erpnext/accounts/doctype/payment_order/payment_order.js index f2f00cebcb..ce9cfe527c 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.js +++ b/erpnext/accounts/doctype/payment_order/payment_order.js @@ -66,6 +66,7 @@ frappe.ui.form.on('Payment Order', { get_query_filters: { bank: frm.doc.bank, docstatus: 1, + payment_type: ("!=", "Receive"), bank_account: frm.doc.company_bank_account, paid_from: frm.doc.account, payment_order_status: ["=", "Initiated"], From 2fb6bc986781d7819afd860cb046a6f060baaaf0 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 15 Aug 2019 17:06:32 +0530 Subject: [PATCH 101/117] fix: Default dimensions in child doctypes --- .../accounting_dimension.js | 7 ---- .../accounting_dimension_detail.json | 8 ++--- .../public/js/utils/dimension_tree_filter.js | 36 ++++++++++++++----- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index 88b11dd678..5cea0d15bf 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -40,16 +40,9 @@ frappe.ui.form.on('Accounting Dimension', { }, document_type: function(frm) { - frm.set_value('label', frm.doc.document_type); frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type)); - if (frm.is_new()){ - let row = frappe.model.add_child(frm.doc, "Accounting Dimension Detail", "dimension_defaults"); - row.reference_document = frm.doc.document_type; - frm.refresh_fields("dimension_defaults"); - } - frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => { if (r && r.document_type) { frm.set_df_property('document_type', 'description', "Document type is already set as dimension"); diff --git a/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json b/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json index 1ccef6cc7a..e9e1f43f99 100644 --- a/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json +++ b/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json @@ -17,8 +17,7 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Company", - "options": "Company", - "reqd": 1 + "options": "Company" }, { "fieldname": "reference_document", @@ -34,8 +33,7 @@ "fieldtype": "Dynamic Link", "in_list_view": 1, "label": "Default Dimension", - "options": "reference_document", - "reqd": 1 + "options": "reference_document" }, { "columns": 3, @@ -55,7 +53,7 @@ } ], "istable": 1, - "modified": "2019-07-17 23:34:33.026883", + "modified": "2019-08-15 11:59:09.389891", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension Detail", diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js index f1c92091a8..72a05b4221 100644 --- a/erpnext/public/js/utils/dimension_tree_filter.js +++ b/erpnext/public/js/utils/dimension_tree_filter.js @@ -1,12 +1,13 @@ frappe.provide('frappe.ui.form'); erpnext.doctypes_with_dimensions = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", - "Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item", - "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", - "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", - "Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", - "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription", - "Subscription Plan"]; + "Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Shipping Rule", "Loyalty Program", + "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", + "Subscription", "Purchase Order", "Journal Entry", "Material Request", "Purchase Receipt", "Landed Cost Item", "Asset"] + +erpnext.child_docs = ["Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", + "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", + "Landed Cost Item", "Asset Value Adjustment", "Opening Invoice Creation Tool Item", "Subscription Plan"] frappe.call({ method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimension_filters", @@ -26,8 +27,19 @@ erpnext.doctypes_with_dimensions.forEach((doctype) => { "is_group": 0 }); } - if (frm.is_new() && frappe.meta.has_field(doctype, 'company') && frm.doc.company) { - frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); + + if (frappe.meta.has_field(doctype, dimension['fieldname'])) { + if (frm.is_new() && frappe.meta.has_field(doctype, 'company') && frm.doc.company) { + frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); + } + } + + if (frm.doc.items && frm.doc.items.length) { + frm.doc.items[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']]; + } + + if (frm.doc.accounts && frm.doc.accounts.length) { + frm.doc.accounts[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']]; } }); }); @@ -36,11 +48,17 @@ erpnext.doctypes_with_dimensions.forEach((doctype) => { company: function(frm) { if(frm.doc.company) { erpnext.dimension_filters.forEach((dimension) => { - frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); + if (frappe.meta.has_field(doctype, dimension['fieldname'])) { + frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); + } }); } }, + }); +}); +erpnext.child_docs.forEach((doctype) => { + frappe.ui.form.on(doctype, { items_add: function(frm, cdt, cdn) { erpnext.dimension_filters.forEach((dimension) => { var row = frappe.get_doc(cdt, cdn); From 42d9298318657f83cd11612887280ee59c59316d Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 16 Aug 2019 12:58:24 +0530 Subject: [PATCH 102/117] fix: Remove extra space --- erpnext/accounts/doctype/account/account_tree.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 6fdd797437..efac1af551 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -123,7 +123,8 @@ frappe.treeview_settings["Account"] = { if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){ // show Dr if positive since balance is calculated as debit - credit else show Cr - let dr_or_cr = node.data.balance_in_account_currency > 0 ? "Dr": "Cr"; + let balance = node.data.balance_in_account_currency || node.data.balance; + let dr_or_cr = balance > 0 ? "Dr": "Cr"; if (node.data && node.data.balance!==undefined) { $('' From 8d0b6f9a60c606423e3fea258ccfe1b12127560d Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 16 Aug 2019 14:00:26 +0530 Subject: [PATCH 103/117] fix: Handling if no default dimension exists --- .../public/js/utils/dimension_tree_filter.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js index 72a05b4221..aada92d7da 100644 --- a/erpnext/public/js/utils/dimension_tree_filter.js +++ b/erpnext/public/js/utils/dimension_tree_filter.js @@ -28,25 +28,27 @@ erpnext.doctypes_with_dimensions.forEach((doctype) => { }); } - if (frappe.meta.has_field(doctype, dimension['fieldname'])) { - if (frm.is_new() && frappe.meta.has_field(doctype, 'company') && frm.doc.company) { - frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); + if (Object.keys(erpnext.default_dimensions).length > 0) { + if (frappe.meta.has_field(doctype, dimension['fieldname'])) { + if (frm.is_new() && frappe.meta.has_field(doctype, 'company') && frm.doc.company) { + frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); + } } - } - if (frm.doc.items && frm.doc.items.length) { - frm.doc.items[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']]; - } + if (frm.doc.items && frm.doc.items.length) { + frm.doc.items[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']]; + } - if (frm.doc.accounts && frm.doc.accounts.length) { - frm.doc.accounts[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']]; + if (frm.doc.accounts && frm.doc.accounts.length) { + frm.doc.accounts[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']]; + } } }); }); }, company: function(frm) { - if(frm.doc.company) { + if(frm.doc.company && (Object.keys(erpnext.default_dimensions).length > 0)) { erpnext.dimension_filters.forEach((dimension) => { if (frappe.meta.has_field(doctype, dimension['fieldname'])) { frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); From 0a2ed6da37848cd10315366fcc063545c2504d6b Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 16 Aug 2019 15:50:17 +0530 Subject: [PATCH 104/117] fix: Add missing semicolon --- erpnext/public/js/utils/dimension_tree_filter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js index aada92d7da..e6c18a149b 100644 --- a/erpnext/public/js/utils/dimension_tree_filter.js +++ b/erpnext/public/js/utils/dimension_tree_filter.js @@ -3,11 +3,11 @@ frappe.provide('frappe.ui.form'); erpnext.doctypes_with_dimensions = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", "Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Shipping Rule", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", - "Subscription", "Purchase Order", "Journal Entry", "Material Request", "Purchase Receipt", "Landed Cost Item", "Asset"] + "Subscription", "Purchase Order", "Journal Entry", "Material Request", "Purchase Receipt", "Landed Cost Item", "Asset"]; erpnext.child_docs = ["Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", - "Landed Cost Item", "Asset Value Adjustment", "Opening Invoice Creation Tool Item", "Subscription Plan"] + "Landed Cost Item", "Asset Value Adjustment", "Opening Invoice Creation Tool Item", "Subscription Plan"]; frappe.call({ method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimension_filters", From 0df513434edf63cb37bc21f919fffc0f779cbda0 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 19 Aug 2019 10:04:52 +0530 Subject: [PATCH 105/117] fix: valuation rate in stock ledger (#18744) * fix: valuation rate in stock ledger * test: allow zero valuation rate for items --- .../doctype/delivery_note/test_delivery_note.py | 1 + erpnext/stock/stock_ledger.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index bc95c965bc..91b6f4c606 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -701,6 +701,7 @@ def create_delivery_note(**args): "qty": args.qty or 1, "rate": args.rate or 100, "conversion_factor": 1.0, + "allow_zero_valuation_rate": args.allow_zero_valuation_rate or 1, "expense_account": "Cost of Goods Sold - _TC", "cost_center": args.cost_center or "_Test Cost Center - _TC", "serial_no": args.serial_no, diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 5fda2a4007..920fc272f7 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -464,16 +464,22 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` - where item_code = %s and warehouse = %s - and valuation_rate >= 0 - order by posting_date desc, posting_time desc, creation desc limit 1""", (item_code, warehouse)) + where + item_code = %s + AND warehouse = %s + AND valuation_rate >= 0 + AND NOT (voucher_no = %s AND voucher_type = %s) + order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type)) if not last_valuation_rate: # Get valuation rate from last sle for the item against any warehouse last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` - where item_code = %s and valuation_rate > 0 - order by posting_date desc, posting_time desc, creation desc limit 1""", item_code) + where + item_code = %s + AND valuation_rate > 0 + AND NOT(voucher_no = %s AND voucher_type = %s) + order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, voucher_no, voucher_type)) if last_valuation_rate: return flt(last_valuation_rate[0][0]) # as there is previous records, it might come with zero rate From bc5712a1b3aac62f170a2e1b37056817f50287bd Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 19 Aug 2019 10:24:44 +0530 Subject: [PATCH 106/117] fix: validated cost center in financial_statement (#18733) * fix: validated cost center in financial_statement * Update financial_statements.py --- erpnext/accounts/report/financial_statements.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 7b9c939b47..3c8de6026a 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -425,9 +425,12 @@ def get_cost_centers_with_children(cost_centers): all_cost_centers = [] for d in cost_centers: - lft, rgt = frappe.db.get_value("Cost Center", d, ["lft", "rgt"]) - children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]}) - all_cost_centers += [c.name for c in children] + if frappe.db.exists("Cost Center", d): + lft, rgt = frappe.db.get_value("Cost Center", d, ["lft", "rgt"]) + children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]}) + all_cost_centers += [c.name for c in children] + else: + frappe.throw(_("Cost Center: {0} does not exist".format(d))) return list(set(all_cost_centers)) From 19c3cb0d5b3cde60c41fc5b7100edddca717b77a Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 19 Aug 2019 10:31:02 +0530 Subject: [PATCH 107/117] fix: removed filters(not required) (#18729) --- .../controllers/sales_and_purchase_return.py | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 2fddcdf24c..7d03722b16 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -18,34 +18,29 @@ def validate_return(doc): validate_returned_items(doc) def validate_return_against(doc): - filters = {"doctype": doc.doctype, "docstatus": 1, "company": doc.company} - if doc.meta.get_field("customer") and doc.customer: - filters["customer"] = doc.customer - elif doc.meta.get_field("supplier") and doc.supplier: - filters["supplier"] = doc.supplier - - if not frappe.db.exists(filters): + if not frappe.db.exists(doc.doctype, doc.return_against): frappe.throw(_("Invalid {0}: {1}") .format(doc.meta.get_label("return_against"), doc.return_against)) else: ref_doc = frappe.get_doc(doc.doctype, doc.return_against) - # validate posting date time - return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00") - ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00") + if ref_doc.company == doc.company and ref_doc.customer = doc.customer and ref_doc.docstatus == 1: + # validate posting date time + return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00") + ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00") - if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime): - frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime))) + if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime): + frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime))) - # validate same exchange rate - if doc.conversion_rate != ref_doc.conversion_rate: - frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})") - .format(doc.doctype, doc.return_against, ref_doc.conversion_rate)) + # validate same exchange rate + if doc.conversion_rate != ref_doc.conversion_rate: + frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})") + .format(doc.doctype, doc.return_against, ref_doc.conversion_rate)) - # validate update stock - if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock: - frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}") - .format(doc.return_against)) + # validate update stock + if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock: + frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}") + .format(doc.return_against)) def validate_returned_items(doc): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos From 960a1cbd8f965374c51c5f45b2a857f24bd4d958 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 19 Aug 2019 11:51:16 +0530 Subject: [PATCH 108/117] fix: Party dashboard heatmap not capturing sales, purchase and other activities (#18753) --- erpnext/accounts/party.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index cb94722651..e42f4af2a5 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -469,7 +469,9 @@ def get_timeline_data(doctype, name): # fetch and append data from Activity Log data += frappe.db.sql("""select {fields} from `tabActivity Log` - where reference_doctype={doctype} and reference_name={name} + where (reference_doctype="{doctype}" and reference_name="{name}") + or (timeline_doctype in ("{doctype}") and timeline_name="{name}") + or (reference_doctype in ("Quotation", "Opportunity") and timeline_name="{name}") and status!='Success' and creation > {after} {group_by} order by creation desc """.format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields, From 5efedd7a605a4ef1070a7f08886d9c9178aeba7f Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 19 Aug 2019 12:56:22 +0530 Subject: [PATCH 109/117] fix: Travis (#18772) --- erpnext/controllers/sales_and_purchase_return.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 7d03722b16..28dd4ea8b6 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -24,7 +24,7 @@ def validate_return_against(doc): else: ref_doc = frappe.get_doc(doc.doctype, doc.return_against) - if ref_doc.company == doc.company and ref_doc.customer = doc.customer and ref_doc.docstatus == 1: + if ref_doc.company == doc.company and ref_doc.customer == doc.customer and ref_doc.docstatus == 1: # validate posting date time return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00") ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00") From c80e5fe7a1e7d39eb297ba73c3e1c3ebf6845be1 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 19 Aug 2019 14:38:15 +0530 Subject: [PATCH 110/117] fix: Failing sales and purchase return test cases --- erpnext/controllers/sales_and_purchase_return.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 28dd4ea8b6..6a03c519ef 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -24,7 +24,12 @@ def validate_return_against(doc): else: ref_doc = frappe.get_doc(doc.doctype, doc.return_against) - if ref_doc.company == doc.company and ref_doc.customer == doc.customer and ref_doc.docstatus == 1: + if doc.doctype == "Sales Invoice": + party_type == "customer" + else: + party_type == "supplier" + + if ref_doc.company == doc.company and ref_doc.get(pary_type) == doc.get(party_type) and ref_doc.docstatus == 1: # validate posting date time return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00") ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00") From 7d288437d8a119712dad5067f40c008648420736 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 19 Aug 2019 16:19:48 +0530 Subject: [PATCH 111/117] fix: Assignment --- erpnext/controllers/sales_and_purchase_return.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 6a03c519ef..cb68315f9b 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -25,9 +25,9 @@ def validate_return_against(doc): ref_doc = frappe.get_doc(doc.doctype, doc.return_against) if doc.doctype == "Sales Invoice": - party_type == "customer" + party_type = "customer" else: - party_type == "supplier" + party_type = "supplier" if ref_doc.company == doc.company and ref_doc.get(pary_type) == doc.get(party_type) and ref_doc.docstatus == 1: # validate posting date time From e2acc748c81b4e2d7a4e96e8f64e7e9e09acf226 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 19 Aug 2019 16:38:04 +0530 Subject: [PATCH 112/117] fix: Check for return against delivery noteas well --- erpnext/controllers/sales_and_purchase_return.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index cb68315f9b..e9633558ef 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -24,7 +24,7 @@ def validate_return_against(doc): else: ref_doc = frappe.get_doc(doc.doctype, doc.return_against) - if doc.doctype == "Sales Invoice": + if doc.doctype in ("Sales Invoice", "Delivery Note"): party_type = "customer" else: party_type = "supplier" From 0487ad5515df3fd1535bd5d1271e69e842d186e4 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Mon, 19 Aug 2019 16:40:29 +0530 Subject: [PATCH 113/117] fix: Code cleanup --- erpnext/controllers/sales_and_purchase_return.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index e9633558ef..6054b9015a 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -24,10 +24,7 @@ def validate_return_against(doc): else: ref_doc = frappe.get_doc(doc.doctype, doc.return_against) - if doc.doctype in ("Sales Invoice", "Delivery Note"): - party_type = "customer" - else: - party_type = "supplier" + party_type == "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier" if ref_doc.company == doc.company and ref_doc.get(pary_type) == doc.get(party_type) and ref_doc.docstatus == 1: # validate posting date time From 3965451c7a78d3ea78d6fa5c57204448cbc24ec9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 19 Aug 2019 17:40:29 +0530 Subject: [PATCH 114/117] Update erpnext/controllers/sales_and_purchase_return.py Co-Authored-By: Mangesh-Khairnar --- erpnext/controllers/sales_and_purchase_return.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 6054b9015a..bc2876f824 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -24,7 +24,7 @@ def validate_return_against(doc): else: ref_doc = frappe.get_doc(doc.doctype, doc.return_against) - party_type == "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier" + party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier" if ref_doc.company == doc.company and ref_doc.get(pary_type) == doc.get(party_type) and ref_doc.docstatus == 1: # validate posting date time From 33b392ac2be2075321856c6273ee4b18f1ab715e Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 19 Aug 2019 17:43:10 +0530 Subject: [PATCH 115/117] Update erpnext/controllers/sales_and_purchase_return.py Co-Authored-By: Mangesh-Khairnar --- erpnext/controllers/sales_and_purchase_return.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index bc2876f824..b713958b1b 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -26,7 +26,7 @@ def validate_return_against(doc): party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier" - if ref_doc.company == doc.company and ref_doc.get(pary_type) == doc.get(party_type) and ref_doc.docstatus == 1: + if ref_doc.company == doc.company and ref_doc.get(party_type) == doc.get(party_type) and ref_doc.docstatus == 1: # validate posting date time return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00") ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00") From 15839250804c6d62d37739533b2e898506218690 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 26 May 2019 18:18:21 +0530 Subject: [PATCH 116/117] fix: Pro rata calculation is not working for WDV depreciation method --- erpnext/assets/doctype/asset/asset.js | 15 ++- erpnext/assets/doctype/asset/asset.py | 109 +++++++----------- .../asset_settings/asset_settings.json | 71 +----------- 3 files changed, 56 insertions(+), 139 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 2d78d2693d..c5cad73801 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -303,14 +303,17 @@ frappe.ui.form.on('Asset', { }, set_depreciation_rate: function(frm, row) { - if (row.total_number_of_depreciations && row.frequency_of_depreciation) { + if (row.total_number_of_depreciations && row.frequency_of_depreciation + && row.expected_value_after_useful_life) { frappe.call({ method: "get_depreciation_rate", doc: frm.doc, args: row, callback: function(r) { if (r.message) { - frappe.model.set_value(row.doctype, row.name, "rate_of_depreciation", r.message); + frappe.flags.dont_change_rate = true; + frappe.model.set_value(row.doctype, row.name, + "rate_of_depreciation", flt(r.message, precision("rate_of_depreciation", row))); } } }); @@ -338,6 +341,14 @@ frappe.ui.form.on('Asset Finance Book', { total_number_of_depreciations: function(frm, cdt, cdn) { const row = locals[cdt][cdn]; frm.events.set_depreciation_rate(frm, row); + }, + + rate_of_depreciation: function(frm, cdt, cdn) { + if(!frappe.flags.dont_change_rate) { + frappe.model.set_value(cdt, cdn, "expected_value_after_useful_life", 0); + } + + frappe.flags.dont_change_rate = false; } }); diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index c398a7342a..6475d0c4aa 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -101,7 +101,8 @@ class Asset(AccountsController): def set_depreciation_rate(self): for d in self.get("finance_books"): - d.rate_of_depreciation = self.get_depreciation_rate(d, on_validate=True) + d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True), + d.precision("rate_of_depreciation")) def make_depreciation_schedule(self): depreciation_method = [d.depreciation_method for d in self.finance_books] @@ -110,8 +111,6 @@ class Asset(AccountsController): self.schedules = [] if not self.get("schedules") and self.available_for_use_date: - total_depreciations = sum([d.total_number_of_depreciations for d in self.get('finance_books')]) - for d in self.get('finance_books'): self.validate_asset_finance_books(d) @@ -124,74 +123,55 @@ class Asset(AccountsController): end_date = add_months(d.depreciation_start_date, no_of_depreciations * cint(d.frequency_of_depreciation)) - total_days = date_diff(end_date, self.available_for_use_date) - rate_per_day = (value_after_depreciation - d.get("expected_value_after_useful_life")) / total_days + if d.depreciation_method in ("Straight Line", "Manual"): + total_days = date_diff(end_date, self.available_for_use_date) + rate_per_day = (value_after_depreciation - d.get("expected_value_after_useful_life")) / total_days number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \ cint(self.number_of_depreciations_booked) from_date = self.available_for_use_date if number_of_pending_depreciations: - next_depr_date = getdate(add_months(self.available_for_use_date, - number_of_pending_depreciations * 12)) - if (cint(frappe.db.get_value("Asset Settings", None, "schedule_based_on_fiscal_year")) == 1 - and getdate(d.depreciation_start_date) < next_depr_date): + period_start_date = add_months(d.depreciation_start_date, + cint(d.frequency_of_depreciation) * -1) - number_of_pending_depreciations += 1 - for n in range(number_of_pending_depreciations): - if n == list(range(number_of_pending_depreciations))[-1]: - schedule_date = add_months(self.available_for_use_date, n * 12) - previous_scheduled_date = add_months(d.depreciation_start_date, (n-1) * 12) - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation, - d, previous_scheduled_date, schedule_date) + for n in range(number_of_pending_depreciations): + schedule_date = add_months(d.depreciation_start_date, + n * cint(d.frequency_of_depreciation)) - elif n == list(range(number_of_pending_depreciations))[0]: - schedule_date = d.depreciation_start_date - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation, - d, self.available_for_use_date, schedule_date) + days = date_diff(schedule_date, from_date) - else: - schedule_date = add_months(d.depreciation_start_date, n * 12) - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation, d) + if n == 0: days += 1 - if value_after_depreciation != 0: - value_after_depreciation -= flt(depreciation_amount) + if d.depreciation_method in ("Straight Line", "Manual"): + depreciation_amount = days * rate_per_day + else: + total_days = date_diff(schedule_date, period_start_date) + period_start_date = schedule_date + depreciation_amount = self.get_depreciation_amount(value_after_depreciation, + d.total_number_of_depreciations, d) - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx - }) - else: - for n in range(number_of_pending_depreciations): - schedule_date = add_months(d.depreciation_start_date, - n * cint(d.frequency_of_depreciation)) + depreciation_amount = flt((depreciation_amount * days) / total_days, + self.precision("gross_purchase_amount")) - if d.depreciation_method in ("Straight Line", "Manual"): - days = date_diff(schedule_date, from_date) - if n == 0: days += 1 + from_date = schedule_date - depreciation_amount = days * rate_per_day - from_date = schedule_date - else: - depreciation_amount = self.get_depreciation_amount(value_after_depreciation, - d.total_number_of_depreciations, d) + if depreciation_amount: + value_after_depreciation -= flt(depreciation_amount, + self.precision("gross_purchase_amount")) - if depreciation_amount: - value_after_depreciation -= flt(depreciation_amount) + if (n == cint(number_of_pending_depreciations) - 1 and + d.expected_value_after_useful_life and + value_after_depreciation > d.expected_value_after_useful_life): + depreciation_amount += (value_after_depreciation - d.expected_value_after_useful_life) - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx - }) + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) def validate_asset_finance_books(self, row): if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount): @@ -261,16 +241,8 @@ class Asset(AccountsController): return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation) def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row): - if row.depreciation_method in ["Straight Line", "Manual"]: - amt = (flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life) - - flt(self.opening_accumulated_depreciation)) - - depreciation_amount = amt * row.rate_of_depreciation - else: - depreciation_amount = flt(depreciable_value) * (flt(row.rate_of_depreciation) / 100) - value_after_depreciation = flt(depreciable_value) - depreciation_amount - if value_after_depreciation < flt(row.expected_value_after_useful_life): - depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life) + precision = self.precision("gross_purchase_amount") + depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision) return depreciation_amount @@ -301,9 +273,12 @@ class Asset(AccountsController): flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount')) - if row.expected_value_after_useful_life < asset_value_after_full_schedule: + if (row.expected_value_after_useful_life and + row.expected_value_after_useful_life < asset_value_after_full_schedule): frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}") .format(row.idx, asset_value_after_full_schedule)) + elif not row.expected_value_after_useful_life: + row.expected_value_after_useful_life = asset_value_after_full_schedule def validate_cancellation(self): if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.json b/erpnext/assets/doctype/asset_settings/asset_settings.json index a3fee96f4e..edc5ce169c 100644 --- a/erpnext/assets/doctype/asset_settings/asset_settings.json +++ b/erpnext/assets/doctype/asset_settings/asset_settings.json @@ -46,75 +46,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "schedule_based_on_fiscal_year", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Calculate Prorated Depreciation Schedule Based on Fiscal Year", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "360", - "depends_on": "eval:doc.schedule_based_on_fiscal_year", - "description": "This value is used for pro-rata temporis calculation", - "fetch_if_empty": 0, - "fieldname": "number_of_days_in_fiscal_year", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Number of Days in Fiscal Year", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -159,7 +90,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2019-03-08 10:44:41.924547", + "modified": "2019-05-26 18:31:19.930563", "modified_by": "Administrator", "module": "Assets", "name": "Asset Settings", From 5d3dee206f07e0e092c9d2bffcdda56772b961b0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 28 May 2019 10:33:56 +0530 Subject: [PATCH 117/117] fixed test cases and the logic for pro rata calculation --- erpnext/assets/doctype/asset/asset.py | 146 +++++++++++---------- erpnext/assets/doctype/asset/test_asset.py | 137 +++++++++++-------- 2 files changed, 160 insertions(+), 123 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 6475d0c4aa..45d2ec2c51 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, erpnext, math, json from frappe import _ from six import string_types -from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff +from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, add_days from frappe.model.document import Document from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset.depreciation \ @@ -105,73 +105,84 @@ class Asset(AccountsController): d.precision("rate_of_depreciation")) def make_depreciation_schedule(self): - depreciation_method = [d.depreciation_method for d in self.finance_books] - - if 'Manual' not in depreciation_method: + if 'Manual' not in [d.depreciation_method for d in self.finance_books]: self.schedules = [] - if not self.get("schedules") and self.available_for_use_date: - for d in self.get('finance_books'): - self.validate_asset_finance_books(d) + if self.get("schedules") or not self.available_for_use_date: + return - value_after_depreciation = (flt(self.gross_purchase_amount) - - flt(self.opening_accumulated_depreciation)) + for d in self.get('finance_books'): + self.validate_asset_finance_books(d) - d.value_after_depreciation = value_after_depreciation + value_after_depreciation = (flt(self.gross_purchase_amount) - + flt(self.opening_accumulated_depreciation)) - no_of_depreciations = cint(d.total_number_of_depreciations - 1) - cint(self.number_of_depreciations_booked) - end_date = add_months(d.depreciation_start_date, - no_of_depreciations * cint(d.frequency_of_depreciation)) + d.value_after_depreciation = value_after_depreciation - if d.depreciation_method in ("Straight Line", "Manual"): - total_days = date_diff(end_date, self.available_for_use_date) - rate_per_day = (value_after_depreciation - d.get("expected_value_after_useful_life")) / total_days + number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \ + cint(self.number_of_depreciations_booked) - number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \ - cint(self.number_of_depreciations_booked) + has_pro_rata = self.check_is_pro_rata(d) - from_date = self.available_for_use_date - if number_of_pending_depreciations: - period_start_date = add_months(d.depreciation_start_date, - cint(d.frequency_of_depreciation) * -1) + if has_pro_rata: + number_of_pending_depreciations += 1 - for n in range(number_of_pending_depreciations): - schedule_date = add_months(d.depreciation_start_date, - n * cint(d.frequency_of_depreciation)) + skip_row = False + for n in range(number_of_pending_depreciations): + # If depreciation is already completed (for double declining balance) + if skip_row: continue - days = date_diff(schedule_date, from_date) + depreciation_amount = self.get_depreciation_amount(value_after_depreciation, + d.total_number_of_depreciations, d) - if n == 0: days += 1 + if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: + schedule_date = add_months(d.depreciation_start_date, + n * cint(d.frequency_of_depreciation)) - if d.depreciation_method in ("Straight Line", "Manual"): - depreciation_amount = days * rate_per_day - else: - total_days = date_diff(schedule_date, period_start_date) - period_start_date = schedule_date - depreciation_amount = self.get_depreciation_amount(value_after_depreciation, - d.total_number_of_depreciations, d) + # For first row + if has_pro_rata and n==0: + depreciation_amount, days = get_pro_rata_amt(d, depreciation_amount, + self.available_for_use_date, d.depreciation_start_date) + # For last row + elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: + to_date = add_months(self.available_for_use_date, + n * cint(d.frequency_of_depreciation)) - depreciation_amount = flt((depreciation_amount * days) / total_days, - self.precision("gross_purchase_amount")) + depreciation_amount, days = get_pro_rata_amt(d, + depreciation_amount, schedule_date, to_date) - from_date = schedule_date + schedule_date = add_days(schedule_date, days) - if depreciation_amount: - value_after_depreciation -= flt(depreciation_amount, - self.precision("gross_purchase_amount")) + if not depreciation_amount: continue + value_after_depreciation -= flt(depreciation_amount, + self.precision("gross_purchase_amount")) - if (n == cint(number_of_pending_depreciations) - 1 and - d.expected_value_after_useful_life and - value_after_depreciation > d.expected_value_after_useful_life): - depreciation_amount += (value_after_depreciation - d.expected_value_after_useful_life) + # Adjust depreciation amount in the last period based on the expected value after useful life + if d.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1 + and value_after_depreciation != d.expected_value_after_useful_life) + or value_after_depreciation < d.expected_value_after_useful_life): + depreciation_amount += (value_after_depreciation - d.expected_value_after_useful_life) + skip_row = True - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx - }) + if depreciation_amount > 0: + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) + + def check_is_pro_rata(self, row): + has_pro_rata = False + + days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1 + total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation) + + if days < total_days: + has_pro_rata = True + + return has_pro_rata def validate_asset_finance_books(self, row): if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount): @@ -242,22 +253,13 @@ class Asset(AccountsController): def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row): precision = self.precision("gross_purchase_amount") - depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision) - - return depreciation_amount - - def get_depreciation_amount_prorata_temporis(self, depreciable_value, row, start_date=None, end_date=None): - if start_date and end_date: - prorata_temporis = min(abs(flt(date_diff(str(end_date), str(start_date)))) / flt(frappe.db.get_value("Asset Settings", None, "number_of_days_in_fiscal_year")), 1) - else: - prorata_temporis = 1 if row.depreciation_method in ("Straight Line", "Manual"): depreciation_amount = (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) - - cint(self.number_of_depreciations_booked)) * prorata_temporis + cint(self.number_of_depreciations_booked)) else: - depreciation_amount = self.get_depreciation_amount(depreciable_value, row.total_number_of_depreciations, row) + depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision) return depreciation_amount @@ -387,15 +389,7 @@ class Asset(AccountsController): if isinstance(args, string_types): args = json.loads(args) - number_of_depreciations_booked = 0 - if self.is_existing_asset: - number_of_depreciations_booked = self.number_of_depreciations_booked - float_precision = cint(frappe.db.get_default("float_precision")) or 2 - tot_no_of_depreciation = flt(args.get("total_number_of_depreciations")) - flt(number_of_depreciations_booked) - - if args.get("depreciation_method") in ["Straight Line", "Manual"]: - return 1.0 / tot_no_of_depreciation if args.get("depreciation_method") == 'Double Declining Balance': return 200.0 / args.get("total_number_of_depreciations") @@ -575,3 +569,15 @@ def make_journal_entry(asset_name): def is_cwip_accounting_disabled(): return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting")) + +def get_pro_rata_amt(row, depreciation_amount, from_date, to_date): + days = date_diff(to_date, from_date) + total_days = get_total_days(to_date, row.frequency_of_depreciation) + + return (depreciation_amount * flt(days)) / flt(total_days), days + +def get_total_days(date, frequency): + period_start_date = add_months(date, + cint(frequency) * -1) + + return date_diff(date, period_start_date) \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index fceccfbd1c..481ee7d9f4 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -88,23 +88,23 @@ class TestAsset(unittest.TestCase): asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' asset.append("finance_books", { "expected_value_after_useful_life": 10000, - "next_depreciation_date": "2020-12-31", "depreciation_method": "Straight Line", "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" }) asset.save() + self.assertEqual(asset.status, "Draft") expected_schedules = [ - ["2020-06-06", 147.54, 147.54], - ["2021-04-06", 44852.46, 45000.0], - ["2022-02-06", 45000.0, 90000.00] + ["2030-12-31", 30000.00, 30000.00], + ["2031-12-31", 30000.00, 60000.00], + ["2032-12-31", 30000.00, 90000.00] ] schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] @@ -118,20 +118,21 @@ class TestAsset(unittest.TestCase): asset.calculate_depreciation = 1 asset.number_of_depreciations_booked = 1 asset.opening_accumulated_depreciation = 40000 + asset.available_for_use_date = "2030-06-06" asset.append("finance_books", { "expected_value_after_useful_life": 10000, - "next_depreciation_date": "2020-12-31", "depreciation_method": "Straight Line", "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" }) asset.insert() self.assertEqual(asset.status, "Draft") asset.save() expected_schedules = [ - ["2020-06-06", 164.47, 40164.47], - ["2021-04-06", 49835.53, 90000.00] + ["2030-12-31", 14246.58, 54246.58], + ["2031-12-31", 25000.00, 79246.58], + ["2032-06-06", 10753.42, 90000.00] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] for d in asset.get("schedules")] @@ -145,24 +146,23 @@ class TestAsset(unittest.TestCase): asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' asset.append("finance_books", { "expected_value_after_useful_life": 10000, - "next_depreciation_date": "2020-12-31", "depreciation_method": "Double Declining Balance", "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" + "frequency_of_depreciation": 12, + "depreciation_start_date": '2030-12-31' }) asset.insert() self.assertEqual(asset.status, "Draft") asset.save() expected_schedules = [ - ["2020-06-06", 66666.67, 66666.67], - ["2021-04-06", 22222.22, 88888.89], - ["2022-02-06", 1111.11, 90000.0] + ['2030-12-31', 66667.00, 66667.00], + ['2031-12-31', 22222.11, 88889.11], + ['2032-12-31', 1110.89, 90000.0] ] schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] @@ -177,23 +177,21 @@ class TestAsset(unittest.TestCase): asset.is_existing_asset = 1 asset.number_of_depreciations_booked = 1 asset.opening_accumulated_depreciation = 50000 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2029-11-30' asset.append("finance_books", { "expected_value_after_useful_life": 10000, - "next_depreciation_date": "2020-12-31", "depreciation_method": "Double Declining Balance", "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" }) asset.insert() self.assertEqual(asset.status, "Draft") - asset.save() - - asset.save() expected_schedules = [ - ["2020-06-06", 33333.33, 83333.33], - ["2021-04-06", 6666.67, 90000.0] + ["2030-12-31", 33333.50, 83333.50], + ["2031-12-31", 6666.50, 90000.0] ] schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] @@ -209,25 +207,25 @@ class TestAsset(unittest.TestCase): asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.purchase_date = '2020-01-30' + asset.purchase_date = '2030-01-30' asset.is_existing_asset = 0 - asset.available_for_use_date = "2020-01-30" + asset.available_for_use_date = "2030-01-30" asset.append("finance_books", { "expected_value_after_useful_life": 10000, "depreciation_method": "Straight Line", "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" }) asset.insert() asset.save() expected_schedules = [ - ["2020-12-31", 28000.0, 28000.0], - ["2021-12-31", 30000.0, 58000.0], - ["2022-12-31", 30000.0, 88000.0], - ["2023-01-30", 2000.0, 90000.0] + ["2030-12-31", 27534.25, 27534.25], + ["2031-12-31", 30000.0, 57534.25], + ["2032-12-31", 30000.0, 87534.25], + ["2033-01-30", 2465.75, 90000.0] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -266,8 +264,8 @@ class TestAsset(unittest.TestCase): self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR") expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 0.0, 32129.24), - ("_Test Depreciations - _TC", 32129.24, 0.0) + ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0), + ("_Test Depreciations - _TC", 30000.0, 0.0) ) gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` @@ -277,15 +275,15 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) self.assertEqual(asset.get("value_after_depreciation"), 0) - def test_depreciation_entry_for_wdv(self): + def test_depreciation_entry_for_wdv_without_pro_rata(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=8000.0, location="Test Location") asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-06-06' - asset.purchase_date = '2030-06-06' + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' asset.append("finance_books", { "expected_value_after_useful_life": 1000, "depreciation_method": "Written Down Value", @@ -298,9 +296,41 @@ class TestAsset(unittest.TestCase): self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 4000.0, 4000.0], - ["2031-12-31", 2000.0, 6000.0], - ["2032-12-31", 1000.0, 7000.0], + ["2030-12-31", 4000.00, 4000.00], + ["2031-12-31", 2000.00, 6000.00], + ["2032-12-31", 1000.00, 7000.0], + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_pro_rata_depreciation_entry_for_wdv(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=8000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-06-06' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 1000, + "depreciation_method": "Written Down Value", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save(ignore_permissions=True) + + self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + expected_schedules = [ + ["2030-12-31", 2279.45, 2279.45], + ["2031-12-31", 2860.28, 5139.73], + ["2032-12-31", 1430.14, 6569.87], + ["2033-06-06", 430.13, 7000.0], ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -346,18 +376,19 @@ class TestAsset(unittest.TestCase): asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' + asset.available_for_use_date = nowdate() + asset.purchase_date = nowdate() asset.append("finance_books", { "expected_value_after_useful_life": 10000, "depreciation_method": "Straight Line", "total_number_of_depreciations": 3, "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" + "depreciation_start_date": nowdate() }) asset.insert() asset.submit() - post_depreciation_entries(date="2021-01-01") + + post_depreciation_entries(date=add_months(nowdate(), 10)) scrap_asset(asset.name) @@ -366,9 +397,9 @@ class TestAsset(unittest.TestCase): self.assertTrue(asset.journal_entry_for_scrap) expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 147.54, 0.0), + ("_Test Accumulated Depreciations - _TC", 30000.0, 0.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 99852.46, 0.0) + ("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0) ) gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` @@ -412,9 +443,9 @@ class TestAsset(unittest.TestCase): self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 23051.47, 0.0), + ("_Test Accumulated Depreciations - _TC", 20392.16, 0.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 51948.53, 0.0), + ("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 0.0), ("Debtors - _TC", 25000.0, 0.0) )