Merge pull request #4323 from nabinhait/leaves
[fix][cleanup] Leave allocation, application and balance report cleanup and fixes
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
index 994dc3c..06c7693 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js
@@ -1,48 +1,62 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
-cur_frm.cscript.onload = function(doc, dt, dn) {
- if(!doc.posting_date) set_multiple(dt,dn,{posting_date:get_today()});
-}
-
-
cur_frm.add_fetch('employee','employee_name','employee_name');
-cur_frm.cscript.employee = function(doc, dt, dn) {
- calculate_total_leaves_allocated(doc, dt, dn);
-}
-
-cur_frm.cscript.leave_type = function(doc, dt, dn) {
- calculate_total_leaves_allocated(doc, dt, dn);
-}
-
-cur_frm.cscript.fiscal_year = function(doc, dt, dn) {
- calculate_total_leaves_allocated(doc, dt, dn);
-}
-
-cur_frm.cscript.carry_forward = function(doc, dt, dn) {
- calculate_total_leaves_allocated(doc, dt, dn);
-}
-
-cur_frm.cscript.carry_forwarded_leaves = function(doc, dt, dn) {
- set_multiple(dt,dn,{total_leaves_allocated : flt(doc.carry_forwarded_leaves)+flt(doc.new_leaves_allocated)});
-}
-
-cur_frm.cscript.new_leaves_allocated = function(doc, dt, dn) {
- set_multiple(dt,dn,{total_leaves_allocated : flt(doc.carry_forwarded_leaves)+flt(doc.new_leaves_allocated)});
-}
-
-calculate_total_leaves_allocated = function(doc, dt, dn) {
- if(cint(doc.carry_forward) == 1 && doc.leave_type && doc.fiscal_year && doc.employee){
- return get_server_fields('get_carry_forwarded_leaves','','', doc, dt, dn, 1);
+frappe.ui.form.on("Leave Allocation", {
+ onload: function(frm) {
+ if(!frm.doc.from_date) frm.set_value("from_date", get_today());
+
+ frm.set_query("employee", function() {
+ return {
+ query: "erpnext.controllers.queries.employee_query"
+ }
+ })
+ },
+
+ employee: function(frm) {
+ frm.trigger("calculate_total_leaves_allocated");
+ },
+
+ leave_type: function(frm) {
+ frm.trigger("calculate_total_leaves_allocated");
+ },
+
+ carry_forward: function(frm) {
+ frm.trigger("calculate_total_leaves_allocated");
+ },
+
+ carry_forwarded_leaves: function(frm) {
+ frm.set_value("total_leaves_allocated",
+ flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated));
+ },
+
+ new_leaves_allocated: function(frm) {
+ frm.set_value("total_leaves_allocated",
+ flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated));
+ },
+
+ calculate_total_leaves_allocated: function(frm) {
+ if (cint(frm.doc.carry_forward) == 1 && frm.doc.leave_type && frm.doc.employee) {
+ 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
+ },
+ 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));
+ }
+ }
+ })
+ } else if (cint(frm.doc.carry_forward) == 0) {
+ frm.set_value("carry_forwarded_leaves", 0);
+ frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated));
+ }
}
- else if(cint(doc.carry_forward) == 0){
- set_multiple(dt,dn,{carry_forwarded_leaves : 0,total_leaves_allocated : flt(doc.new_leaves_allocated)});
- }
-}
-
-cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) {
- return{
- query: "erpnext.controllers.queries.employee_query"
- }
-}
+})
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index f846124..57eb146 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -3,93 +3,112 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import cint, flt, date_diff
+from frappe.utils import flt, date_diff, formatdate
from frappe import _
from frappe.model.document import Document
from erpnext.hr.utils import set_employee_name
+from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period
+
+class OverlapError(frappe.ValidationError): pass
+class BackDatedAllocationError(frappe.ValidationError): pass
+class OverAllocationError(frappe.ValidationError): pass
+class LessAllocationError(frappe.ValidationError): pass
+class ValueMultiplierError(frappe.ValidationError): pass
class LeaveAllocation(Document):
def validate(self):
self.validate_period()
self.validate_new_leaves_allocated_value()
- self.check_existing_leave_allocation()
- if not self.total_leaves_allocated:
- self.total_leaves_allocated = self.new_leaves_allocated
-
+ self.validate_allocation_overlap()
+ self.validate_back_dated_allocation()
+ self.set_total_leaves_allocated()
+ self.validate_total_leaves_allocated()
set_employee_name(self)
def on_update_after_submit(self):
self.validate_new_leaves_allocated_value()
-
- def on_update(self):
- self.get_total_allocated_leaves()
+ self.set_total_leaves_allocated()
+
+ frappe.db.set(self,'carry_forwarded_leaves', flt(self.carry_forwarded_leaves))
+ frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated))
+
+ self.validate_against_leave_applications()
def validate_period(self):
if date_diff(self.to_date, self.from_date) <= 0:
- frappe.throw(_("Invalid period"))
+ frappe.throw(_("To date cannot be before from date"))
def validate_new_leaves_allocated_value(self):
"""validate that leave allocation is in multiples of 0.5"""
if flt(self.new_leaves_allocated) % 0.5:
- frappe.throw(_("Leaves must be allocated in multiples of 0.5"))
+ frappe.throw(_("Leaves must be allocated in multiples of 0.5"), ValueMultiplierError)
- def check_existing_leave_allocation(self):
- """check whether leave for same type is already allocated or not"""
- leave_allocation = frappe.db.sql("""select name from `tabLeave Allocation`
- where employee='%s' and leave_type='%s' and to_date >= '%s' and from_date <= '%s' and docstatus=1
- """%(self.employee, self.leave_type, self.from_date, self.to_date))
+ def validate_allocation_overlap(self):
+ leave_allocation = frappe.db.sql("""
+ select name from `tabLeave Allocation`
+ where employee=%s and leave_type=%s and docstatus=1
+ and to_date >= %s and from_date <= %s""",
+ (self.employee, self.leave_type, self.from_date, self.to_date))
if leave_allocation:
- frappe.msgprint(_("Leaves for type {0} already allocated for Employee {1} for period {2} - {3}").format(self.leave_type,
- self.employee, self.from_date, self.to_date))
- frappe.throw(_('Reference') + ': <a href="#Form/Leave Allocation/{0}">{0}</a>'.format(leave_allocation[0][0]))
+ frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} - {3}")
+ .format(self.leave_type, self.employee, formatdate(self.from_date), formatdate(self.to_date)))
+
+ frappe.throw(_('Reference') + ': <a href="#Form/Leave Allocation/{0}">{0}</a>'
+ .format(leave_allocation[0][0]), OverlapError)
+
+ def validate_back_dated_allocation(self):
+ future_allocation = frappe.db.sql("""select name, from_date from `tabLeave Allocation`
+ where employee=%s and leave_type=%s and docstatus=1 and from_date > %s
+ and carry_forward=1""", (self.employee, self.leave_type, self.to_date), as_dict=1)
+
+ if future_allocation:
+ frappe.throw(_("Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}")
+ .format(formatdate(future_allocation[0].from_date), future_allocation[0].name),
+ BackDatedAllocationError)
- def get_leave_bal(self):
- return self.get_leaves_allocated() - self.get_leaves_applied()
+ def set_total_leaves_allocated(self):
+ self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee,
+ self.leave_type, self.from_date, self.carry_forward)
+
+ self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + flt(self.new_leaves_allocated)
+
+ if not self.total_leaves_allocated:
+ frappe.throw(_("Total leaves allocated is mandatory"))
- def get_leaves_applied(self):
- leaves_applied = frappe.db.sql("""select SUM(ifnull(total_leave_days, 0))
- from `tabLeave Application` where employee=%s and leave_type=%s
- and to_date<=%s and docstatus=1""",
- (self.employee, self.leave_type, self.from_date))
- return leaves_applied and flt(leaves_applied[0][0]) or 0
+ def validate_total_leaves_allocated(self):
+ if date_diff(self.to_date, self.from_date) <= flt(self.total_leaves_allocated):
+ frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError)
+
+ def validate_against_leave_applications(self):
+ leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type,
+ self.from_date, self.to_date)
+
+ if flt(leaves_taken) > flt(self.total_leaves_allocated):
+ frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError)
- def get_leaves_allocated(self):
- leaves_allocated = frappe.db.sql("""select SUM(ifnull(total_leaves_allocated, 0))
- from `tabLeave Allocation` where employee=%s and leave_type=%s
- and to_date<=%s and docstatus=1 and name!=%s""",
- (self.employee, self.leave_type, self.from_date, self.name))
- return leaves_allocated and flt(leaves_allocated[0][0]) or 0
-
- def allow_carry_forward(self):
- """check whether carry forward is allowed or not for this leave type"""
- cf = frappe.db.sql("""select is_carry_forward from `tabLeave Type` where name = %s""",
- self.leave_type)
- cf = cf and cint(cf[0][0]) or 0
- if not cf:
- frappe.db.set(self,'carry_forward',0)
- frappe.throw(_("Cannot carry forward {0}").format(self.leave_type))
-
- def get_carry_forwarded_leaves(self):
- if self.carry_forward:
- self.allow_carry_forward()
-
- prev_bal = 0
- if cint(self.carry_forward) == 1:
- prev_bal = self.get_leave_bal()
-
- ret = {
- 'carry_forwarded_leaves': prev_bal,
- 'total_leaves_allocated': flt(prev_bal) + flt(self.new_leaves_allocated)
- }
- return ret
-
- def get_total_allocated_leaves(self):
- leave_det = self.get_carry_forwarded_leaves()
- self.validate_total_leaves_allocated(leave_det)
- frappe.db.set(self,'carry_forwarded_leaves',flt(leave_det['carry_forwarded_leaves']))
- frappe.db.set(self,'total_leaves_allocated',flt(leave_det['total_leaves_allocated']))
-
- def validate_total_leaves_allocated(self, leave_det):
- if date_diff(self.to_date, self.from_date) <= leave_det['total_leaves_allocated']:
- frappe.throw(_("Total allocated leaves are more than period"))
+@frappe.whitelist()
+def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None):
+ carry_forwarded_leaves = 0
+
+ if carry_forward:
+ validate_carry_forward(leave_type)
+
+ 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)
+
+ carry_forwarded_leaves = flt(previous_allocation[0].total_leaves_allocated) - flt(leaves_taken)
+
+ return carry_forwarded_leaves
+
+def validate_carry_forward(leave_type):
+ if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"):
+ frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type))
+
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index d36fb2c..b3eee31 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -13,7 +13,7 @@
"employee": employee.name,
"employee_name": employee.employee_name,
"leave_type": "_Test Leave Type",
- "from_date": getdate("2015-10-1"),
+ "from_date": getdate("2015-10-01"),
"to_date": getdate("2015-10-31"),
"new_leaves_allocated": 5,
"docstatus": 1
@@ -24,7 +24,7 @@
"employee": employee.name,
"employee_name": employee.employee_name,
"leave_type": "_Test Leave Type",
- "from_date": getdate("2015-09-1"),
+ "from_date": getdate("2015-09-01"),
"to_date": getdate("2015-11-30"),
"new_leaves_allocated": 5
}
diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js
index 835170a..e708b77 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.js
+++ b/erpnext/hr/doctype/leave_application/leave_application.js
@@ -66,14 +66,18 @@
},
get_leave_balance: function(frm) {
- if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date && frm.doc.to_date) {
- return frm.call({
- method: "get_leave_balance",
+ if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date) {
+ return frappe.call({
+ method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on",
args: {
employee: frm.doc.employee,
- from_date: frm.doc.from_date,
- to_date: frm.doc.to_date,
+ date: frm.doc.from_date,
leave_type: frm.doc.leave_type
+ },
+ callback: function(r) {
+ if (!r.exc && r.message) {
+ frm.set_value('leave_balance', r.message);
+ }
}
});
}
@@ -83,14 +87,20 @@
if(frm.doc.from_date && frm.doc.to_date) {
if (cint(frm.doc.half_day)==1) {
frm.set_value("total_leave_days", 0.5);
- } else {
+ } else if (frm.doc.employee && frm.doc.leave_type){
// server call is done to include holidays in leave days calculations
return frappe.call({
- method: 'erpnext.hr.doctype.leave_application.leave_application.get_total_leave_days',
- args: { leave_app: frm.doc },
- callback: function(response) {
- if (response && response.message) {
- frm.set_value('total_leave_days', response.message.total_leave_days);
+ method: 'erpnext.hr.doctype.leave_application.leave_application.get_number_of_leave_days',
+ args: {
+ "employee": frm.doc.employee,
+ "leave_type": frm.doc.leave_type,
+ "from_date": frm.doc.from_date,
+ "to_date": frm.doc.to_date,
+ "half_day": frm.doc.half_day
+ },
+ callback: function(r) {
+ if (r && r.message) {
+ frm.set_value('total_leave_days', r.message);
frm.trigger("get_leave_balance");
}
}
diff --git a/erpnext/hr/doctype/leave_application/leave_application.json b/erpnext/hr/doctype/leave_application/leave_application.json
index 62e4cd8..10f6594 100644
--- a/erpnext/hr/doctype/leave_application/leave_application.json
+++ b/erpnext/hr/doctype/leave_application/leave_application.json
@@ -419,28 +419,6 @@
"unique": 0
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "fiscal_year",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "label": "Fiscal Year",
- "no_copy": 0,
- "options": "Fiscal Year",
- "permlevel": 0,
- "print_hide": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
- {
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -559,7 +537,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 3,
- "modified": "2015-10-28 16:14:25.640730",
+ "modified": "2015-11-15 19:32:32.258397",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Application",
@@ -711,7 +689,7 @@
],
"read_only": 0,
"read_only_onload": 0,
- "search_fields": "employee,employee_name,leave_type,from_date,to_date,total_leave_days,fiscal_year",
+ "search_fields": "employee,employee_name,leave_type,from_date,to_date,total_leave_days",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "employee_name"
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 8c91173..1d84a40 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -2,13 +2,13 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe, json
+import frappe
from frappe import _
-
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \
comma_or, get_fullname
-from frappe import msgprint
from erpnext.hr.utils import set_employee_name
+from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
+
class LeaveDayBlockedError(frappe.ValidationError): pass
class OverlapError(frappe.ValidationError): pass
@@ -18,8 +18,7 @@
from frappe.model.document import Document
class LeaveApplication(Document):
def get_feed(self):
- return _("{0}: From {0} of type {1}").format(self.status,
- self.employee_name, self.leave_type)
+ return _("{0}: From {0} of type {1}").format(self.status, self.employee_name, self.leave_type)
def validate(self):
if not getattr(self, "__islocal", None) and frappe.db.exists(self.doctype, self.name):
@@ -29,7 +28,7 @@
set_employee_name(self)
- self.validate_to_date()
+ self.validate_dates()
self.validate_balance_leaves()
self.validate_leave_overlap()
self.validate_max_days()
@@ -50,6 +49,8 @@
def on_submit(self):
if self.status != "Approved":
frappe.throw(_("Only Leave Applications with status 'Approved' can be submitted"))
+
+ self.validate_back_dated_application()
# notify leave applier about approval
self.notify_employee(self.status)
@@ -57,10 +58,41 @@
def on_cancel(self):
# notify leave applier about cancellation
self.notify_employee("cancelled")
+
+ def validate_dates(self):
+ if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)):
+ frappe.throw(_("To date cannot be before from date"))
+
+ self.validate_dates_acorss_allocation()
+ self.validate_back_dated_application()
+
+ def validate_dates_acorss_allocation(self):
+ def _get_leave_alloction_record(date):
+ allocation = frappe.db.sql("""select name from `tabLeave Allocation`
+ where employee=%s and leave_type=%s and docstatus=1
+ and %s between from_date and to_date""", (self.employee, self.leave_type, date))
+
+ return allocation and allocation[0][0]
+
+ allocation_based_on_from_date = _get_leave_alloction_record(self.from_date)
+ allocation_based_on_to_date = _get_leave_alloction_record(self.to_date)
+
+ if not (allocation_based_on_from_date or allocation_based_on_to_date):
+ frappe.throw(_("Application period cannot be outside leave allocation period"))
+
+ elif allocation_based_on_from_date != allocation_based_on_to_date:
+ frappe.throw(_("Application period cannot be across two alocation records"))
+
+ def validate_back_dated_application(self):
+ future_allocation = frappe.db.sql("""select name, from_date from `tabLeave Allocation`
+ where employee=%s and leave_type=%s and docstatus=1 and from_date > %s
+ and carry_forward=1""", (self.employee, self.leave_type, self.to_date), as_dict=1)
+
+ if future_allocation:
+ frappe.throw(_("Leave cannot be applied/cancelled before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}")
+ .format(formatdate(future_allocation[0].from_date), future_allocation[0].name))
def show_block_day_warning(self):
- from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
-
block_dates = get_applicable_block_dates(self.from_date, self.to_date,
self.employee, self.company, all_lists=True)
@@ -70,60 +102,39 @@
frappe.msgprint(formatdate(d.block_date) + ": " + d.reason)
def validate_block_days(self):
- from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
-
block_dates = get_applicable_block_dates(self.from_date, self.to_date,
self.employee, self.company)
- if block_dates:
- if self.status == "Approved":
- frappe.throw(_("Cannot approve leave as you are not authorized to approve leaves on Block Dates"),
- LeaveDayBlockedError)
-
- def get_holidays(self):
- return get_holidays(self)
-
- def get_total_leave_days(self):
- return get_total_leave_days(self)
-
- def validate_to_date(self):
- if self.from_date and self.to_date and \
- (getdate(self.to_date) < getdate(self.from_date)):
- frappe.throw(_("To date cannot be before from date"))
+ if block_dates and self.status == "Approved":
+ frappe.throw(_("You are not authorized to approve leaves on Block Dates"), LeaveDayBlockedError)
def validate_balance_leaves(self):
if self.from_date and self.to_date:
- self.total_leave_days = self.get_total_leave_days()["total_leave_days"]
+ self.total_leave_days = get_number_of_leave_days(self.employee, self.leave_type,
+ self.from_date, self.to_date, self.half_day)
if self.total_leave_days == 0:
- frappe.throw(_("The day(s) on which you are applying for leave are holiday. You need not apply for leave."))
+ 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(self.employee,
- self.leave_type, self.from_date, self.to_date)["leave_balance"]
+ self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date)
- if self.status != "Rejected" \
- and self.leave_balance - self.total_leave_days < 0:
- #check if this leave type allow the remaining balance to be in negative. If yes then warn the user and continue to save else warn the user and don't save.
+ if self.status != "Rejected" and self.leave_balance < self.total_leave_days:
if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"):
- frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}").format(self.leave_type))
+ frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}")
+ .format(self.leave_type))
else:
- frappe.throw(_("There is not enough leave balance for Leave Type {0}").format(self.leave_type))
-
+ frappe.throw(_("There is not enough leave balance for Leave Type {0}")
+ .format(self.leave_type))
def validate_leave_overlap(self):
if not self.name:
self.name = "New Leave Application"
- for d in frappe.db.sql("""select name, leave_type, posting_date,
- from_date, to_date
+ for d in frappe.db.sql("""select name, leave_type, posting_date, from_date, to_date
from `tabLeave Application`
- where
- employee = %(employee)s
- and docstatus < 2
- and status in ("Open", "Approved")
- and to_date >= %(from_date)s
- and from_date <= %(to_date)s
+ where employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved")
+ and to_date >= %(from_date)s and from_date <= %(to_date)s
and name != %(name)s""", {
"employee": self.employee,
"from_date": self.from_date,
@@ -131,9 +142,12 @@
"name": self.name
}, as_dict = 1):
- frappe.msgprint(_("Employee {0} has already applied for {1} between {2} and {3}").format(self.employee,
- cstr(d['leave_type']), formatdate(d['from_date']), formatdate(d['to_date'])))
- frappe.throw('<a href="#Form/Leave Application/{0}">{0}</a>'.format(d["name"]), OverlapError)
+ frappe.msgprint(_("Employee {0} has already applied for {1} between {2} and {3}")
+ .format(self.employee, cstr(d['leave_type']),
+ formatdate(d['from_date']), formatdate(d['to_date'])))
+
+ frappe.throw("""Exising Application: <a href="#Form/Leave Application/{0}">{0}</a>"""
+ .format(d["name"]), OverlapError)
def validate_max_days(self):
max_days = frappe.db.get_value("Leave Type", self.leave_type, "max_days_allowed")
@@ -145,7 +159,8 @@
leave_approvers = [l.leave_approver for l in employee.get("leave_approvers")]
if len(leave_approvers) and self.leave_approver not in leave_approvers:
- frappe.throw(_("Leave approver must be one of {0}").format(comma_or(leave_approvers)), InvalidLeaveApproverError)
+ frappe.throw(_("Leave approver must be one of {0}")
+ .format(comma_or(leave_approvers)), InvalidLeaveApproverError)
elif self.leave_approver and not frappe.db.sql("""select name from `tabUserRole`
where parent=%s and role='Leave Approver'""", self.leave_approver):
@@ -153,8 +168,8 @@
.format(get_fullname(self.leave_approver), self.leave_approver), InvalidLeaveApproverError)
elif self.docstatus==1 and len(leave_approvers) and self.leave_approver != frappe.session.user:
- msgprint(_("Only the selected Leave Approver can submit this Leave Application"),
- raise_exception=LeaveApproverIdentityError)
+ frappe.throw(_("Only the selected Leave Approver can submit this Leave Application"),
+ LeaveApproverIdentityError)
def notify_employee(self, status):
employee = frappe.get_doc("Employee", self.employee)
@@ -213,60 +228,89 @@
approver.parent = %s
and user.name like %s
and approver.leave_approver=user.name""", (filters.get("employee"), "%" + txt + "%"))
+
+@frappe.whitelist()
+def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day=None):
+ if half_day:
+ return 0.5
+ number_of_days = date_diff(to_date, from_date) + 1
+ if not frappe.db.get_value("Leave Type", leave_type, "include_holiday"):
+ number_of_days = flt(number_of_days) - flt(get_holidays(employee, from_date, to_date))
+
+ return number_of_days
-def get_holidays(leave_app):
+@frappe.whitelist()
+def get_leave_balance_on(employee, leave_type, date, allocation_records=None):
+ if allocation_records == None:
+ allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict())
+
+ allocation = allocation_records.get(leave_type, frappe._dict())
+
+ leaves_taken = get_approved_leaves_for_period(employee, leave_type, allocation.from_date, date)
+
+ return flt(allocation.total_leaves_allocated) - flt(leaves_taken)
+
+def get_approved_leaves_for_period(employee, leave_type, from_date, to_date):
+ leave_applications = frappe.db.sql("""
+ select employee, leave_type, from_date, to_date, total_leave_days
+ from `tabLeave Application`
+ where employee=%(employee)s and leave_type=%(leave_type)s
+ and status="Approved" and docstatus=1
+ and (from_date between %(from_date)s and %(to_date)s
+ or to_date between %(from_date)s and %(to_date)s
+ or (from_date < %(from_date)s and to_date > %(to_date)s))
+ """, {
+ "from_date": from_date,
+ "to_date": to_date,
+ "employee": employee,
+ "leave_type": leave_type
+ }, as_dict=1)
+
+ leave_days = 0
+ for leave_app in leave_applications:
+ 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, from_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,
+ "total_leaves_allocated": d.total_leaves_allocated
+ }))
+
+ return allocated_leaves
+
+
+def get_holidays(employee, from_date, to_date):
tot_hol = frappe.db.sql("""select count(*) from `tabHoliday` h1, `tabHoliday List` h2, `tabEmployee` e1
where e1.name = %s and h1.parent = h2.name and e1.holiday_list = h2.name
- and h1.holiday_date between %s and %s""", (leave_app.employee, leave_app.from_date,
- leave_app.to_date))[0][0]
- # below line is needed. If an employee hasn't been assigned with any holiday list then above will return 0 rows.
+ and h1.holiday_date between %s and %s""", (employee, from_date, to_date))[0][0]
+
if not tot_hol:
tot_hol = frappe.db.sql("""select count(*) from `tabHoliday` h1, `tabHoliday List` h2
where h1.parent = h2.name and h1.holiday_date between %s and %s
- and ifnull(h2.is_default,0) = 1 and h2.fiscal_year = %s""",
- (leave_app.from_date, leave_app.to_date, leave_app.fiscal_year))[0][0]
+ and ifnull(h2.is_default,0) = 1""", (from_date, to_date))[0][0]
+
return tot_hol
-@frappe.whitelist()
-def get_total_leave_days(leave_app):
- # Parse Leave Application if neccessary
- if isinstance(leave_app, str) or isinstance(leave_app, unicode):
- leave_app = frappe.get_doc(json.loads(leave_app))
-
- """Calculates total leave days based on input and holidays"""
- ret = {'total_leave_days' : 0.5}
- if not leave_app.half_day:
- tot_days = date_diff(leave_app.to_date, leave_app.from_date) + 1
- if frappe.db.get_value("Leave Type", leave_app.leave_type, "include_holiday"):
- ret = {
- 'total_leave_days' : flt(tot_days)
- }
- else:
- holidays = leave_app.get_holidays()
- ret = {
- 'total_leave_days' : flt(tot_days)-flt(holidays)
- }
-
- return ret
-
-@frappe.whitelist()
-def get_leave_balance(employee, leave_type, from_date, to_date):
- leave_all = frappe.db.sql("""select total_leaves_allocated
- from `tabLeave Allocation` where employee = %s and leave_type = %s
- and from_date<=%s and to_date>=%s and docstatus = 1""", (employee,
- leave_type, from_date, to_date))
-
- leave_all = leave_all and flt(leave_all[0][0]) or 0
-
- leave_app = frappe.db.sql("""select SUM(total_leave_days)
- from `tabLeave Application`
- where employee = %s and leave_type = %s and to_date>=%s and from_date<=%s
- and status="Approved" and docstatus = 1""", (employee, leave_type, from_date, to_date))
- leave_app = leave_app and flt(leave_app[0][0]) or 0
-
- ret = {'leave_balance': leave_all - leave_app}
- return ret
-
def is_lwp(leave_type):
lwp = frappe.db.sql("select is_lwp from `tabLeave Type` where name = %s", leave_type)
return lwp and cint(lwp[0][0]) or 0
diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
index 2bb7f52..fb69440 100644
--- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py
@@ -10,8 +10,20 @@
class TestSalarySlip(unittest.TestCase):
def setUp(self):
- frappe.db.sql("""delete from `tabLeave Application`""")
- frappe.db.sql("""delete from `tabSalary Slip`""")
+ for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]:
+ frappe.db.sql("delete from `tab%s`" % dt)
+
+ allocation = frappe.get_doc({
+ "doctype": "Leave Allocation",
+ "employee": "_T-Employee-0001",
+ "leave_type": "_Test Leave Type LWP",
+ "from_date": "2013-01-01",
+ "to_date": "2015-12-31",
+ "new_leaves_allocated": 5
+ })
+
+ allocation.insert()
+ allocation.submit()
frappe.db.set_value("Holiday List", "_Test Holiday List", "is_default", 1)
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js
index 41b1421..5df4554 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js
@@ -7,12 +7,14 @@
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
+ "reqd": 1,
"default": frappe.datetime.year_start()
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
+ "reqd": 1,
"default": frappe.datetime.year_end()
},
{
@@ -20,6 +22,7 @@
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
+ "reqd": 1,
"default": frappe.defaults.get_user_default("company")
}
]
diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
index 0aa88a8..7f1c442 100644
--- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
+++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py
@@ -4,67 +4,54 @@
from __future__ import unicode_literals
import frappe
from frappe import _
-from frappe.desk.reportview import execute as runreport
+from erpnext.hr.doctype.leave_application.leave_application \
+ import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period
+
def execute(filters=None):
- if not filters: filters = {}
-
- employee_filters = {
- "status": "Active"
- }
+ leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc")
- if filters.get("company"):
- filters["company"] = filters.company
-
- employees = runreport(doctype="Employee", fields=["name", "employee_name", "department"],
- filters=employee_filters, limit_page_length=None)
-
- if not employees:
- frappe.throw(_("No employee found!"))
-
- leave_types = frappe.db.sql_list("select name from `tabLeave Type`")
-
- employee_names = [d.name for d in employees]
-
- allocations = frappe.db.sql("""select employee, leave_type, sum(new_leaves_allocated) as leaves_allocated
- from `tabLeave Allocation`
- where docstatus=1 and employee in (%s) and from_date >= '%s' and to_date <= '%s'""" %
- (','.join(['%s']*len(employee_names)), filters.get("from_date"),
- filters.get("to_date")), employee_names, as_dict=True)
-
- applications = frappe.db.sql("""select employee, leave_type,
- SUM(total_leave_days) as leaves
- from `tabLeave Application`
- where status="Approved" and docstatus = 1 and employee in (%s)
- and from_date >= '%s' and to_date <= '%s'
- group by employee, leave_type""" %
- (','.join(['%s']*len(employee_names)), filters.get("from_date"),
- filters.get("to_date")), employee_names, as_dict=True)
-
+ columns = get_columns(leave_types)
+ data = get_data(filters, leave_types)
+
+ return columns, data
+
+def get_columns(leave_types):
columns = [
- _("Employee") + ":Link/Employee:150", _("Employee Name") + "::200", _("Department") +"::150"
+ _("Employee") + ":Link/Employee:150",
+ _("Employee Name") + "::200",
+ _("Department") +"::150"
]
for leave_type in leave_types:
- columns.append(_(leave_type) + " " + _("Opening") + ":Float")
- columns.append(_(leave_type) + " " + _("Taken") + ":Float")
- columns.append(_(leave_type) + " " + _("Balance") + ":Float")
+ columns.append(_(leave_type) + " " + _("Taken") + ":Float:160")
+ columns.append(_(leave_type) + " " + _("Balance") + ":Float:160")
+
+ return columns
+
+def get_data(filters, leave_types):
- data = {}
- for d in allocations:
- data.setdefault((d.employee,d.leave_type), frappe._dict()).allocation = d.leaves_allocated
+ allocation_records_based_on_to_date = get_leave_allocation_records(filters.to_date)
- for d in applications:
- data.setdefault((d.employee, d.leave_type), frappe._dict()).leaves = d.leaves
-
- result = []
- for employee in employees:
+ active_employees = frappe.get_all("Employee",
+ filters = { "status": "Active", "company": filters.company},
+ fields = ["name", "employee_name", "department"])
+
+ data = []
+ for employee in active_employees:
row = [employee.name, employee.employee_name, employee.department]
- result.append(row)
- for leave_type in leave_types:
- tmp = data.get((employee.name, leave_type), frappe._dict())
- row.append(tmp.allocation or 0)
- row.append(tmp.leaves or 0)
- row.append((tmp.allocation or 0) - (tmp.leaves or 0))
- return columns, result
+ 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)
+
+ # 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()))
+
+ row += [leaves_taken, closing]
+
+ data.append(row)
+
+ return data
\ No newline at end of file
diff --git a/erpnext/projects/doctype/time_log/time_log.js b/erpnext/projects/doctype/time_log/time_log.js
index 5fce970..7b2faf9 100644
--- a/erpnext/projects/doctype/time_log/time_log.js
+++ b/erpnext/projects/doctype/time_log/time_log.js
@@ -4,11 +4,13 @@
frappe.provide("erpnext.projects");
frappe.ui.form.on("Time Log", "onload", function(frm) {
- if (frm.doc.for_manufacturing) {
- frappe.ui.form.trigger("Time Log", "production_order");
- }
- if (frm.doc.from_time && frm.doc.to_time) {
- frappe.ui.form.trigger("Time Log", "to_time");
+ if (frm.doc.__islocal) {
+ if (frm.doc.for_manufacturing) {
+ frappe.ui.form.trigger("Time Log", "production_order");
+ }
+ if (frm.doc.from_time && frm.doc.to_time) {
+ frappe.ui.form.trigger("Time Log", "to_time");
+ }
}
});
diff --git a/test_sites/apps.txt b/test_sites/apps.txt
index 3796729..ee64549 100644
--- a/test_sites/apps.txt
+++ b/test_sites/apps.txt
@@ -1 +1 @@
-erpnext
+erpnext
\ No newline at end of file
diff --git a/test_sites/languages.txt b/test_sites/languages.txt
index 0163b22..cf2b150 100644
--- a/test_sites/languages.txt
+++ b/test_sites/languages.txt
@@ -1 +1 @@
-en english
+en english
\ No newline at end of file
diff --git a/test_sites/test_site/site_config.json b/test_sites/test_site/site_config.json
index 7d1194a..48b330b 100644
--- a/test_sites/test_site/site_config.json
+++ b/test_sites/test_site/site_config.json
@@ -9,4 +9,4 @@
"run_selenium_tests": 1,
"host_name": "http://localhost:8000",
"install_apps": ["erpnext"]
-}
+}
\ No newline at end of file