Merge pull request #17624 from Mangesh-Khairnar/leave-management
feat: Carry Forward Leave Expiry
This commit is contained in:
commit
ac3579d2c6
@ -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"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -283,7 +283,9 @@ scheduler_events = {
|
||||
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_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",
|
||||
|
@ -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",
|
||||
|
@ -21,11 +21,41 @@ frappe.ui.form.on("Leave Allocation", {
|
||||
})
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
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
|
||||
frm.add_custom_button(__('Expire Allocation'), function() {
|
||||
frm.trigger("expire_allocation");
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
expire_allocation: function(frm) {
|
||||
frappe.call({
|
||||
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){
|
||||
frappe.msgprint(__("Allocation Expired!"));
|
||||
}
|
||||
frm.refresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
frm.trigger("calculate_total_leaves_allocated");
|
||||
},
|
||||
|
||||
leave_type: function(frm) {
|
||||
frm.trigger("leave_policy");
|
||||
frm.trigger("calculate_total_leaves_allocated");
|
||||
},
|
||||
|
||||
@ -33,37 +63,38 @@ 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) {
|
||||
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");
|
||||
}
|
||||
},
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
@ -1,683 +1,220 @@
|
||||
{
|
||||
"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",
|
||||
"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",
|
||||
"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": 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
|
||||
"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",
|
||||
"fieldname": "unused_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
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.leave_policy",
|
||||
"fieldname": "leave_policy",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Leave Policy",
|
||||
"options": "Leave Policy",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "expired",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Expired",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"depends_on": "carry_forwarded_leaves_count",
|
||||
"fieldname": "carry_forwarded_leaves_count",
|
||||
"fieldtype": "Float",
|
||||
"label": "Carry Forwarded Leaves",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"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-08-08 15:08:42.440909",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Allocation",
|
||||
@ -689,15 +226,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
|
||||
@ -709,28 +241,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"
|
||||
}
|
@ -3,11 +3,11 @@
|
||||
|
||||
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, today, getdate
|
||||
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 expire_allocation, create_leave_ledger_entry
|
||||
|
||||
class OverlapError(frappe.ValidationError): pass
|
||||
class BackDatedAllocationError(frappe.ValidationError): pass
|
||||
@ -40,14 +40,18 @@ class LeaveAllocation(Document):
|
||||
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()
|
||||
def on_submit(self):
|
||||
self.create_leave_ledger_entry()
|
||||
|
||||
frappe.db.set(self,'carry_forwarded_leaves', flt(self.carry_forwarded_leaves))
|
||||
frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated))
|
||||
# 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)
|
||||
|
||||
self.validate_against_leave_applications()
|
||||
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:
|
||||
@ -87,13 +91,32 @@ 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.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 '''
|
||||
|
||||
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.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
|
||||
@ -101,15 +124,37 @@ 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)
|
||||
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= min(getdate(end_date), getdate(self.to_date)),
|
||||
is_carry_forward=1
|
||||
)
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
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)
|
||||
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 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', 'employee', 'leave_type'], as_dict=1)
|
||||
|
||||
def get_leave_allocation_for_period(employee, leave_type, from_date, to_date):
|
||||
leave_allocated = 0
|
||||
@ -136,24 +181,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 '''
|
||||
unused_leaves = 0.0
|
||||
previous_allocation = get_previous_allocation(date, leave_type, employee)
|
||||
if carry_forward and previous_allocation:
|
||||
validate_carry_forward(leave_type)
|
||||
unused_leaves = get_unused_leaves(employee, leave_type, previous_allocation.from_date, previous_allocation.to_date)
|
||||
|
||||
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)
|
||||
return unused_leaves
|
||||
|
||||
carry_forwarded_leaves = flt(previous_allocation[0].total_leaves_allocated) - flt(leaves_taken)
|
||||
|
||||
return carry_forwarded_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"):
|
||||
|
@ -12,4 +12,9 @@ def get_data():
|
||||
'items': ['Leave Encashment']
|
||||
}
|
||||
],
|
||||
'reports': [
|
||||
{
|
||||
'items': ['Employee Leave Balance']
|
||||
}
|
||||
]
|
||||
}
|
11
erpnext/hr/doctype/leave_allocation/leave_allocation_list.js
Normal file
11
erpnext/hr/doctype/leave_allocation/leave_allocation_list.js
Normal file
@ -0,0 +1,11 @@
|
||||
// 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", "expired, =, 1"];
|
||||
}
|
||||
},
|
||||
};
|
@ -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()
|
||||
]);
|
||||
|
@ -1,7 +1,9 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import getdate
|
||||
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, expire_allocation
|
||||
|
||||
class TestLeaveAllocation(unittest.TestCase):
|
||||
def test_overlapping_allocation(self):
|
||||
@ -38,7 +40,7 @@ class TestLeaveAllocation(unittest.TestCase):
|
||||
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,
|
||||
@ -50,11 +52,11 @@ class TestLeaveAllocation(unittest.TestCase):
|
||||
})
|
||||
|
||||
#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,
|
||||
@ -64,8 +66,100 @@ class TestLeaveAllocation(unittest.TestCase):
|
||||
"to_date": getdate("2015-09-30"),
|
||||
"new_leaves_allocated": 35
|
||||
})
|
||||
|
||||
#allocated leave more than period
|
||||
self.assertRaises(frappe.ValidationError, d.save)
|
||||
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=0)
|
||||
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.unused_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,
|
||||
expire_carry_forwarded_leaves_after_days=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=0)
|
||||
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.unused_leaves, leave_allocation.new_leaves_allocated)
|
||||
|
||||
def test_creation_of_leave_ledger_entry_on_submit(self):
|
||||
frappe.db.sql("delete from `tabLeave Allocation`")
|
||||
|
||||
leave_allocation = create_leave_allocation()
|
||||
leave_allocation.submit()
|
||||
|
||||
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)
|
||||
|
||||
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": 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,
|
||||
"to_date": args.to_date or add_months(nowdate(), 12)
|
||||
})
|
||||
return leave_allocation
|
||||
|
||||
test_dependencies = ["Employee", "Leave Type"]
|
@ -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.posting_date
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc && r.message['leave_allocation']) {
|
||||
@ -60,9 +60,8 @@ frappe.ui.form.on("Leave Application", {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$("div").remove(".form-dashboard-section");
|
||||
let section = frm.dashboard.add_section(
|
||||
frm.dashboard.add_section(
|
||||
frappe.render_template('leave_application_dashboard', {
|
||||
data: leave_details
|
||||
})
|
||||
@ -115,6 +114,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");
|
||||
},
|
||||
@ -138,12 +138,13 @@ 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: {
|
||||
employee: frm.doc.employee,
|
||||
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
|
||||
},
|
||||
|
@ -174,13 +174,6 @@
|
||||
"no_copy": 1,
|
||||
"options": "Open\nApproved\nRejected\nCancelled"
|
||||
},
|
||||
{
|
||||
"fieldname": "salary_slip",
|
||||
"fieldtype": "Link",
|
||||
"label": "Salary Slip",
|
||||
"options": "Salary Slip",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sb10",
|
||||
"fieldtype": "Section Break"
|
||||
@ -193,25 +186,6 @@
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "1",
|
||||
"fieldname": "follow_via_email",
|
||||
"fieldtype": "Check",
|
||||
"label": "Follow via Email",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Color",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_17",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
@ -220,6 +194,25 @@
|
||||
"remember_last_selected_value": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "1",
|
||||
"fieldname": "follow_via_email",
|
||||
"fieldtype": "Check",
|
||||
"label": "Follow via Email",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_17",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "salary_slip",
|
||||
"fieldtype": "Link",
|
||||
"label": "Salary Slip",
|
||||
"options": "Salary Slip",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "letter_head",
|
||||
@ -229,6 +222,13 @@
|
||||
"options": "Letter Head",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "color",
|
||||
"fieldtype": "Color",
|
||||
"label": "Color",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
@ -244,7 +244,7 @@
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"max_attachments": 3,
|
||||
"modified": "2019-08-11 19:13:53.603011",
|
||||
"modified": "2019-08-13 13:32:04.860848",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Application",
|
||||
|
@ -5,11 +5,12 @@ 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
|
||||
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:
|
||||
@ -193,9 +196,9 @@ 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 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))
|
||||
@ -347,6 +350,54 @@ class LeaveApplication(Document):
|
||||
except frappe.OutgoingEmailError:
|
||||
pass
|
||||
|
||||
def create_leave_ledger_entry(self, submit=True):
|
||||
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, lwp)
|
||||
else:
|
||||
args = dict(
|
||||
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)
|
||||
|
||||
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,
|
||||
is_lwp=lwp
|
||||
)
|
||||
create_leave_ledger_entry(self, args, submit)
|
||||
|
||||
if getdate(expiry_date) != getdate(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):
|
||||
''' 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'])
|
||||
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):
|
||||
number_of_days = 0
|
||||
@ -364,14 +415,16 @@ 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())
|
||||
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
|
||||
remaining_leaves = get_leave_balance_on(employee, d, date, to_date = allocation.to_date,
|
||||
consider_all_leaves_in_the_allocation_period=True)
|
||||
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,
|
||||
"leaves_taken": leaves_taken,
|
||||
@ -386,27 +439,131 @@ def get_leave_details(employee, date):
|
||||
return ret
|
||||
|
||||
@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):
|
||||
def get_leave_balance_on(employee, leave_type, date, to_date=nowdate(), consider_all_leaves_in_the_allocation_period=False):
|
||||
'''
|
||||
Returns leave balance till 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
|
||||
'''
|
||||
|
||||
if allocation_records == None:
|
||||
allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict())
|
||||
allocation_records = get_leave_allocation_records(employee, date, leave_type)
|
||||
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))
|
||||
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)
|
||||
|
||||
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`
|
||||
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) #nosec
|
||||
|
||||
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),
|
||||
"unused_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),
|
||||
"status": "Open"
|
||||
}, 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):
|
||||
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
|
||||
|
||||
total_leaves = allocation.total_leaves_allocated
|
||||
|
||||
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)
|
||||
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 status = %(status)s and docstatus != 2
|
||||
and docstatus=1
|
||||
and leaves<0
|
||||
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))
|
||||
@ -414,43 +571,8 @@ 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
|
||||
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
|
||||
|
||||
leave_days += get_number_of_leave_days(employee, leave_type,
|
||||
leave_app.from_date, leave_app.to_date)
|
||||
|
||||
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, 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)
|
||||
|
||||
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,
|
||||
"total_leaves_encashed":d.total_leaves_encashed
|
||||
}))
|
||||
return allocated_leaves
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_holidays(employee, from_date, to_date):
|
||||
|
@ -0,0 +1,14 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from frappe import _
|
||||
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'reports': [
|
||||
{
|
||||
'label': _('Reports'),
|
||||
'items': ['Employee Leave Balance']
|
||||
}
|
||||
]
|
||||
}
|
@ -7,7 +7,9 @@ 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
|
||||
|
||||
test_dependencies = ["Leave Allocation", "Leave Block List"]
|
||||
|
||||
@ -17,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"
|
||||
@ -26,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"
|
||||
@ -35,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"
|
||||
@ -44,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) #nosec
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@ -268,13 +273,14 @@ 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,
|
||||
))
|
||||
|
||||
# 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
|
||||
@ -285,7 +291,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):
|
||||
employee = get_employee()
|
||||
leave_period = get_leave_period()
|
||||
@ -304,21 +309,22 @@ 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",
|
||||
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,
|
||||
description = "_Test Reason",
|
||||
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"
|
||||
@ -342,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",
|
||||
@ -363,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",
|
||||
@ -392,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",
|
||||
@ -401,6 +410,18 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, leave_application.insert)
|
||||
|
||||
def test_leave_balance_near_allocaton_expiry(self):
|
||||
employee = get_employee()
|
||||
leave_type = create_leave_type(
|
||||
leave_type_name="_Test_CF_leave_expiry",
|
||||
is_carry_forward=1,
|
||||
expire_carry_forwarded_leaves_after_days=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()
|
||||
@ -447,6 +468,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
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',
|
||||
@ -457,9 +479,103 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
leave_application.submit()
|
||||
self.assertEqual(leave_application.docstatus, 1)
|
||||
|
||||
def make_allocation_record(employee=None, leave_type=None):
|
||||
frappe.db.sql("delete from `tabLeave Allocation`")
|
||||
def test_creation_of_leave_ledger_entry_on_submit(self):
|
||||
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.name,
|
||||
from_date = add_days(nowdate(), 1),
|
||||
to_date = add_days(nowdate(), 4),
|
||||
description = "_Test Reason",
|
||||
company = "_Test Company",
|
||||
docstatus = 1,
|
||||
status = "Approved"
|
||||
))
|
||||
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.total_leave_days * -1)
|
||||
|
||||
# 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 test_ledger_entry_creation_on_intermediate_allocation_expiry(self):
|
||||
employee = get_employee()
|
||||
leave_type = create_leave_type(
|
||||
leave_type_name="_Test_CF_leave_expiry",
|
||||
is_carry_forward=1,
|
||||
expire_carry_forwarded_leaves_after_days=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),
|
||||
description = "_Test Reason",
|
||||
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 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_carry_forwarded_leaves_after_days=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(
|
||||
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=0)
|
||||
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(), -84),
|
||||
to_date=add_days(nowdate(), 100),
|
||||
carry_forward=1)
|
||||
leave_allocation.submit()
|
||||
|
||||
def make_allocation_record(employee=None, leave_type=None):
|
||||
allocation = frappe.get_doc({
|
||||
"doctype": "Leave Allocation",
|
||||
"employee": employee or "_T-Employee-00001",
|
||||
|
@ -10,6 +10,8 @@ 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
|
||||
from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves
|
||||
|
||||
class LeaveEncashment(Document):
|
||||
def validate(self):
|
||||
@ -25,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
|
||||
@ -40,6 +42,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 +52,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()))
|
||||
@ -57,8 +62,10 @@ 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 - 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
|
||||
@ -66,12 +73,47 @@ 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, 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))
|
||||
and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee), as_dict=1) #nosec
|
||||
|
||||
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(
|
||||
leaves=self.encashable_days * -1,
|
||||
from_date=self.encashment_date,
|
||||
to_date=self.encashment_date,
|
||||
is_carry_forward=0
|
||||
)
|
||||
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)
|
||||
|
||||
|
||||
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)
|
@ -9,41 +9,42 @@ 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`''')
|
||||
frappe.db.sql('''delete from `tabLeave Ledger Entry`''')
|
||||
frappe.db.sql('''delete from `tabAdditional Salary`''')
|
||||
|
||||
# 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", 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})
|
||||
|
||||
# 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 +54,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}))
|
||||
|
0
erpnext/hr/doctype/leave_ledger_entry/__init__.py
Normal file
0
erpnext/hr/doctype/leave_ledger_entry/__init__.py
Normal file
@ -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) {
|
||||
|
||||
// }
|
||||
});
|
168
erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
Normal file
168
erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json
Normal file
@ -0,0 +1,168 @@
|
||||
{
|
||||
"creation": "2019-05-09 15:47:39.760406",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"employee",
|
||||
"employee_name",
|
||||
"leave_type",
|
||||
"transaction_type",
|
||||
"transaction_name",
|
||||
"leaves",
|
||||
"column_break_7",
|
||||
"from_date",
|
||||
"to_date",
|
||||
"is_carry_forward",
|
||||
"is_expired",
|
||||
"is_lwp",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Employee",
|
||||
"options": "Employee"
|
||||
},
|
||||
{
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Employee Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "leave_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"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": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Leaves"
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "From Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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-06-21 00:37:07.782810",
|
||||
"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,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"cancel": 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
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"title_field": "employee"
|
||||
}
|
174
erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
Normal file
174
erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
Normal file
@ -0,0 +1,174 @@
|
||||
# -*- 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 import _
|
||||
from frappe.utils import add_days, today, flt, DATE_FORMAT, getdate
|
||||
|
||||
class LeaveLedgerEntry(Document):
|
||||
def validate(self):
|
||||
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 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 Ledger Entry`
|
||||
WHERE
|
||||
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:
|
||||
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',
|
||||
employee=ref_doc.employee,
|
||||
employee_name=ref_doc.employee_name,
|
||||
leave_type=ref_doc.leave_type,
|
||||
transaction_type=ref_doc.doctype,
|
||||
transaction_name=ref_doc.name,
|
||||
is_carry_forward=0,
|
||||
is_expired=0,
|
||||
is_lwp=0
|
||||
)
|
||||
ledger.update(args)
|
||||
|
||||
if submit:
|
||||
frappe.get_doc(ledger).submit()
|
||||
else:
|
||||
delete_ledger_entry(ledger)
|
||||
|
||||
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
|
||||
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,
|
||||
'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+"%"),
|
||||
'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 '''
|
||||
|
||||
# fetch leave type records that has carry forwarded leaves expiry
|
||||
leave_type_records = frappe.db.get_values("Leave Type", filters={
|
||||
'expire_carry_forwarded_leaves_after_days': (">", 0)
|
||||
}, fieldname=['name'])
|
||||
|
||||
if leave_type_records:
|
||||
leave_type = [record[0] for record in leave_type_records]
|
||||
|
||||
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': ("<", today()),
|
||||
'transaction_type': 'Leave Allocation',
|
||||
'transaction_name': ('not in', expired_allocation)
|
||||
},
|
||||
or_filters={
|
||||
'is_carry_forward': 0,
|
||||
'leave_type': ('in', leave_type)
|
||||
})
|
||||
|
||||
if expire_allocation:
|
||||
create_expiry_ledger_entry(expire_allocation)
|
||||
|
||||
def create_expiry_ledger_entry(allocations):
|
||||
''' Create ledger entry for expired allocation '''
|
||||
for allocation in allocations:
|
||||
if allocation.is_carry_forward:
|
||||
expire_carried_forward_allocation(allocation)
|
||||
else:
|
||||
expire_allocation(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,
|
||||
'to_date': ('<=', allocation.to_date),
|
||||
}, fieldname=['SUM(leaves)'])
|
||||
|
||||
@frappe.whitelist()
|
||||
def expire_allocation(allocation, expiry_date=None):
|
||||
''' expires non-carry forwarded 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,
|
||||
transaction_type='Leave Allocation',
|
||||
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)
|
@ -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
|
@ -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"
|
||||
}
|
||||
],
|
||||
|
@ -77,6 +77,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": "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,
|
||||
@ -141,38 +173,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": "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,
|
||||
@ -217,7 +217,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-21 16:15:43.305502",
|
||||
"modified": "2019-05-30 16:15:43.305502",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Period",
|
||||
|
@ -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, 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
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from six import iteritems
|
||||
|
||||
@ -21,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}" #nosec
|
||||
.format(condition=condition_str), tuple(values)))
|
||||
|
||||
return employees
|
||||
|
||||
@ -36,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
|
||||
@ -67,18 +68,24 @@ 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"))
|
||||
|
||||
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,28 +94,36 @@ 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", "expire_carry_forwarded_leaves_after_days"])
|
||||
for d in leave_types:
|
||||
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
|
||||
|
||||
|
||||
|
@ -12,6 +12,9 @@ def get_data():
|
||||
},
|
||||
{
|
||||
'items': ['Employee Grade']
|
||||
}
|
||||
},
|
||||
{
|
||||
'items': ['Leave Allocation']
|
||||
},
|
||||
]
|
||||
}
|
@ -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 = {
|
||||
leave_policy = create_leave_policy(leave_type=leave_type.name, annual_allocation=leave_type.max_leaves_allowed + 1)
|
||||
|
||||
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": leave_type.name,
|
||||
"annual_allocation": leave_type.max_leaves_allowed + 1
|
||||
"leave_type": args.leave_type or "_Test Leave Type",
|
||||
"annual_allocation": args.annual_allocation or 10
|
||||
}]
|
||||
}
|
||||
|
||||
self.assertRaises(frappe.ValidationError, frappe.get_doc(leave_policy_details).insert)
|
||||
})
|
@ -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,13 @@
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"hidden": 0,
|
||||
@ -233,10 +249,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 +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": "allow_negative",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@ -294,10 +314,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 +346,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 +379,81 @@
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"depends_on": "",
|
||||
"description": "Calculated in days",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "expire_carry_forwarded_leaves_after_days",
|
||||
"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": "Expire Carry Forwarded Leaves (Days)",
|
||||
"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": 1,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "encashment",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
@ -386,10 +481,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 +514,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 +548,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 +583,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 +616,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 +649,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 +684,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 +720,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-08-02 15:38:39.334283",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Leave Type",
|
||||
@ -687,8 +794,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
|
||||
}
|
@ -2,9 +2,22 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
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
|
||||
|
||||
class LeaveType(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
if self.is_lwp:
|
||||
leave_allocation = frappe.get_all("Leave Allocation", filters={
|
||||
'leave_type': self.name,
|
||||
'from_date': ("<=", today()),
|
||||
'to_date': (">=", today())
|
||||
}, fields=['name'])
|
||||
leave_allocation = [l['name'] for l in leave_allocation]
|
||||
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
|
||||
|
@ -2,6 +2,25 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
import frappe
|
||||
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({
|
||||
"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,
|
||||
"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"
|
||||
})
|
||||
return leave_type
|
@ -24,6 +24,18 @@ 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",
|
||||
},
|
||||
{
|
||||
"fieldname":"employee",
|
||||
"label": __("Employee"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Employee",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -4,8 +4,9 @@
|
||||
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
|
||||
import get_leave_balance_on, get_leaves_for_period
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@ -30,16 +31,27 @@ def get_columns(leave_types):
|
||||
|
||||
return columns
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = {
|
||||
"status": "Active",
|
||||
"company": filters.company,
|
||||
}
|
||||
if 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):
|
||||
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)
|
||||
|
||||
if filters.to_date <= filters.from_date:
|
||||
frappe.throw(_("From date can not be greater than than To date"))
|
||||
|
||||
active_employees = frappe.get_all("Employee",
|
||||
filters = { "status": "Active", "company": filters.company},
|
||||
filters=conditions,
|
||||
fields=["name", "employee_name", "department", "user_id"])
|
||||
|
||||
data = []
|
||||
@ -50,16 +62,14 @@ 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_leave_balance_on(employee.name, leave_type, filters.from_date,
|
||||
allocation_records_based_on_to_date.get(employee.name, frappe._dict()))
|
||||
opening = get_total_allocated_leaves(employee.name, leave_type, filters.from_date, 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]
|
||||
|
||||
@ -84,3 +94,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)
|
@ -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
|
||||
@ -270,6 +270,21 @@ 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.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),
|
||||
'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",
|
||||
@ -277,11 +292,10 @@ 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)
|
||||
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:
|
||||
@ -289,19 +303,32 @@ 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))
|
||||
if annual_allocation and annual_allocation[0]:
|
||||
earned_leaves = flt(annual_allocation[0][0]) / divide_by_frequency[e_leave_type.earned_leave_frequency]
|
||||
annual_allocation = frappe.db.get_value("Leave Policy Detail", filters={
|
||||
'parent': leave_policy.name,
|
||||
'leave_type': e_leave_type.name
|
||||
}, 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:
|
||||
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)
|
||||
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
|
||||
frappe.db.set_value('Leave Allocation', allocation.name, 'total_leaves_allocated', new_allocation)
|
||||
|
||||
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.unused_leaves = 0
|
||||
allocation.create_leave_ledger_entry()
|
||||
|
||||
def check_frequency_hit(from_date, to_date, frequency):
|
||||
'''Return True if current date matches frequency'''
|
||||
|
@ -626,3 +626,4 @@ erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
|
||||
erpnext.patches.v12_0.update_ewaybill_field_position
|
||||
erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes
|
||||
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
|
||||
erpnext.patches.v12_0.generate_leave_ledger_entries
|
86
erpnext/patches/v12_0/generate_leave_ledger_entries.py
Normal file
86
erpnext/patches/v12_0/generate_leave_ledger_entries.py
Normal file
@ -0,0 +1,86 @@
|
||||
# Copyright (c) 2018, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
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")
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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 '''
|
||||
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)
|
||||
expire_allocation(allocation_obj)
|
||||
|
||||
def get_allocation_records():
|
||||
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.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.get_all("Leave Encashment", filters={
|
||||
"docstatus": 1
|
||||
}, fields=['name', 'employee', 'leave_type', 'encashable_days', 'encashment_date'])
|
Loading…
Reference in New Issue
Block a user