Merge branch 'develop'

This commit is contained in:
Nabin Hait 2015-11-16 16:42:56 +05:30
commit a975fea3ac
38 changed files with 1043 additions and 920 deletions

View File

@ -1,2 +1,2 @@
from __future__ import unicode_literals from __future__ import unicode_literals
__version__ = '6.8.4' __version__ = '6.9.0'

View File

@ -21,11 +21,13 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
if(doc.docstatus == 1 && !in_list(["Stopped", "Closed", "Delivered"], doc.status)) { if(doc.docstatus == 1 && !in_list(["Stopped", "Closed", "Delivered"], doc.status)) {
if (this.frm.has_perm("submit")) {
if(flt(doc.per_billed, 2) < 100 || doc.per_received < 100) { if(flt(doc.per_billed, 2) < 100 || doc.per_received < 100) {
cur_frm.add_custom_button(__('Stop'), this.stop_purchase_order); cur_frm.add_custom_button(__('Stop'), this.stop_purchase_order);
} }
cur_frm.add_custom_button(__('Close'), this.close_purchase_order); cur_frm.add_custom_button(__('Close'), this.close_purchase_order);
}
if(doc.delivered_by_supplier && doc.status!="Delivered"){ if(doc.delivered_by_supplier && doc.status!="Delivered"){
cur_frm.add_custom_button(__('Mark as Delivered'), this.delivered_by_supplier); cur_frm.add_custom_button(__('Mark as Delivered'), this.delivered_by_supplier);
@ -35,7 +37,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry); cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry);
} }
if(flt(doc.per_received, 2) < 100) { if(flt(doc.per_received, 2) < 100 && this.frm.doc.__onload.has_stock_item) {
cur_frm.add_custom_button(__('Receive'), this.make_purchase_receipt).addClass("btn-primary"); cur_frm.add_custom_button(__('Receive'), this.make_purchase_receipt).addClass("btn-primary");
if(doc.is_subcontracted==="Yes") { if(doc.is_subcontracted==="Yes") {
@ -53,8 +55,10 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
} }
if(doc.docstatus == 1 && in_list(["Stopped", "Closed", "Delivered"], doc.status)) { if(doc.docstatus == 1 && in_list(["Stopped", "Closed", "Delivered"], doc.status)) {
if (this.frm.has_perm("submit")) {
cur_frm.add_custom_button(__('Re-open'), this.unstop_purchase_order); cur_frm.add_custom_button(__('Re-open'), this.unstop_purchase_order);
} }
}
}, },
make_stock_entry: function() { make_stock_entry: function() {

View File

@ -33,6 +33,9 @@ class PurchaseOrder(BuyingController):
'overflow_type': 'order' 'overflow_type': 'order'
}] }]
def onload(self):
self.set_onload("has_stock_item", len(self.get_stock_items()) > 0)
def validate(self): def validate(self):
super(PurchaseOrder, self).validate() super(PurchaseOrder, self).validate()

View File

@ -398,17 +398,18 @@ class calculate_taxes_and_totals(object):
return return
self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"]) self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
total_amount_to_pay = flt(self.doc.grand_total - self.doc.total_advance - self.doc.write_off_amount, if self.doc.party_account_currency == self.doc.currency:
self.doc.precision("grand_total")) total_amount_to_pay = flt(self.doc.grand_total - self.doc.total_advance
- flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
else:
total_amount_to_pay = flt(self.doc.base_grand_total - self.doc.total_advance
- flt(self.doc.base_write_off_amount), self.doc.precision("grand_total"))
if self.doc.doctype == "Sales Invoice": if self.doc.doctype == "Sales Invoice":
self.doc.round_floats_in(self.doc, ["paid_amount"]) self.doc.round_floats_in(self.doc, ["paid_amount"])
outstanding_amount = flt(total_amount_to_pay - self.doc.paid_amount, self.doc.precision("outstanding_amount")) paid_amount = self.doc.paid_amount \
elif self.doc.doctype == "Purchase Invoice": if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
outstanding_amount = flt(total_amount_to_pay, self.doc.precision("outstanding_amount")) self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount),
if self.doc.party_account_currency == self.doc.currency:
self.doc.outstanding_amount = outstanding_amount
else:
self.doc.outstanding_amount = flt(outstanding_amount * self.doc.conversion_rate,
self.doc.precision("outstanding_amount")) self.doc.precision("outstanding_amount"))
elif self.doc.doctype == "Purchase Invoice":
self.doc.outstanding_amount = flt(total_amount_to_pay, self.doc.precision("outstanding_amount"))

View File

@ -29,7 +29,7 @@ blogs.
""" """
app_icon = "icon-th" app_icon = "icon-th"
app_color = "#e74c3c" app_color = "#e74c3c"
app_version = "6.8.4" app_version = "6.9.0"
app_email = "info@erpnext.com" app_email = "info@erpnext.com"
app_license = "GNU General Public License (v3)" app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext" source_link = "https://github.com/frappe/erpnext"
@ -96,7 +96,8 @@ has_website_permission = {
"Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission", "Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission",
"Sales Invoice": "erpnext.controllers.website_list_for_contact.has_website_permission", "Sales Invoice": "erpnext.controllers.website_list_for_contact.has_website_permission",
"Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission", "Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission",
"Issue": "erpnext.support.doctype.issue.issue.has_website_permission" "Issue": "erpnext.support.doctype.issue.issue.has_website_permission",
"Address": "erpnext.utilities.doctype.address.address.has_website_permission"
} }
permission_query_conditions = { permission_query_conditions = {

View File

@ -536,7 +536,7 @@
"is_submittable": 1, "is_submittable": 1,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"modified": "2015-10-02 07:38:50.191920", "modified": "2015-11-14 12:11:13.213073",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Expense Claim", "name": "Expense Claim",
@ -564,7 +564,7 @@
}, },
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0, "apply_user_permissions": 1,
"cancel": 0, "cancel": 0,
"create": 1, "create": 1,
"delete": 0, "delete": 0,
@ -580,6 +580,7 @@
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"user_permission_doctypes": "[\"Employee\"]",
"write": 1 "write": 1
}, },
{ {

View File

@ -1,48 +1,62 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // 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.add_fetch('employee','employee_name','employee_name');
cur_frm.cscript.employee = function(doc, dt, dn) { frappe.ui.form.on("Leave Allocation", {
calculate_total_leaves_allocated(doc, dt, dn); onload: function(frm) {
} if(!frm.doc.from_date) frm.set_value("from_date", get_today());
cur_frm.cscript.leave_type = function(doc, dt, dn) { frm.set_query("employee", function() {
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);
}
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 { return {
query: "erpnext.controllers.queries.employee_query" 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));
}
}
})

View File

@ -3,93 +3,112 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cint, flt, date_diff from frappe.utils import flt, date_diff, formatdate
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.utils import set_employee_name 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): class LeaveAllocation(Document):
def validate(self): def validate(self):
self.validate_period() self.validate_period()
self.validate_new_leaves_allocated_value() self.validate_new_leaves_allocated_value()
self.check_existing_leave_allocation() self.validate_allocation_overlap()
if not self.total_leaves_allocated: self.validate_back_dated_allocation()
self.total_leaves_allocated = self.new_leaves_allocated self.set_total_leaves_allocated()
self.validate_total_leaves_allocated()
set_employee_name(self) set_employee_name(self)
def on_update_after_submit(self): def on_update_after_submit(self):
self.validate_new_leaves_allocated_value() self.validate_new_leaves_allocated_value()
self.set_total_leaves_allocated()
def on_update(self): frappe.db.set(self,'carry_forwarded_leaves', flt(self.carry_forwarded_leaves))
self.get_total_allocated_leaves() frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated))
self.validate_against_leave_applications()
def validate_period(self): def validate_period(self):
if date_diff(self.to_date, self.from_date) <= 0: 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): def validate_new_leaves_allocated_value(self):
"""validate that leave allocation is in multiples of 0.5""" """validate that leave allocation is in multiples of 0.5"""
if flt(self.new_leaves_allocated) % 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): def validate_allocation_overlap(self):
"""check whether leave for same type is already allocated or not""" leave_allocation = frappe.db.sql("""
leave_allocation = frappe.db.sql("""select name from `tabLeave Allocation` select name from `tabLeave Allocation`
where employee='%s' and leave_type='%s' and to_date >= '%s' and from_date <= '%s' and docstatus=1 where employee=%s and leave_type=%s and docstatus=1
"""%(self.employee, self.leave_type, self.from_date, self.to_date)) and to_date >= %s and from_date <= %s""",
(self.employee, self.leave_type, self.from_date, self.to_date))
if leave_allocation: if leave_allocation:
frappe.msgprint(_("Leaves for type {0} already allocated for Employee {1} for period {2} - {3}").format(self.leave_type, frappe.msgprint(_("{0} already allocated for Employee {1} for period {2} - {3}")
self.employee, self.from_date, self.to_date)) .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]))
def get_leave_bal(self): frappe.throw(_('Reference') + ': <a href="#Form/Leave Allocation/{0}">{0}</a>'
return self.get_leaves_allocated() - self.get_leaves_applied() .format(leave_allocation[0][0]), OverlapError)
def get_leaves_applied(self): def validate_back_dated_allocation(self):
leaves_applied = frappe.db.sql("""select SUM(ifnull(total_leave_days, 0)) future_allocation = frappe.db.sql("""select name, from_date from `tabLeave Allocation`
from `tabLeave Application` where employee=%s and leave_type=%s where employee=%s and leave_type=%s and docstatus=1 and from_date > %s
and to_date<=%s and docstatus=1""", and carry_forward=1""", (self.employee, self.leave_type, self.to_date), as_dict=1)
(self.employee, self.leave_type, self.from_date))
return leaves_applied and flt(leaves_applied[0][0]) or 0
def get_leaves_allocated(self): if future_allocation:
leaves_allocated = frappe.db.sql("""select SUM(ifnull(total_leaves_allocated, 0)) frappe.throw(_("Leave cannot be allocated before {0}, as leave balance has already been carry-forwarded in the future leave allocation record {1}")
from `tabLeave Allocation` where employee=%s and leave_type=%s .format(formatdate(future_allocation[0].from_date), future_allocation[0].name),
and to_date<=%s and docstatus=1 and name!=%s""", BackDatedAllocationError)
(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): def set_total_leaves_allocated(self):
"""check whether carry forward is allowed or not for this leave type""" self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee,
cf = frappe.db.sql("""select is_carry_forward from `tabLeave Type` where name = %s""", self.leave_type, self.from_date, self.carry_forward)
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): self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + flt(self.new_leaves_allocated)
if self.carry_forward:
self.allow_carry_forward()
prev_bal = 0 if not self.total_leaves_allocated:
if cint(self.carry_forward) == 1: frappe.throw(_("Total leaves allocated is mandatory"))
prev_bal = self.get_leave_bal()
ret = { def validate_total_leaves_allocated(self):
'carry_forwarded_leaves': prev_bal, if date_diff(self.to_date, self.from_date) <= flt(self.total_leaves_allocated):
'total_leaves_allocated': flt(prev_bal) + flt(self.new_leaves_allocated) frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError)
}
return ret
def get_total_allocated_leaves(self): def validate_against_leave_applications(self):
leave_det = self.get_carry_forwarded_leaves() leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type,
self.validate_total_leaves_allocated(leave_det) self.from_date, self.to_date)
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'])) 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)
@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))
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"))

View File

@ -13,7 +13,7 @@ class TestLeaveAllocation(unittest.TestCase):
"employee": employee.name, "employee": employee.name,
"employee_name": employee.employee_name, "employee_name": employee.employee_name,
"leave_type": "_Test Leave Type", "leave_type": "_Test Leave Type",
"from_date": getdate("2015-10-1"), "from_date": getdate("2015-10-01"),
"to_date": getdate("2015-10-31"), "to_date": getdate("2015-10-31"),
"new_leaves_allocated": 5, "new_leaves_allocated": 5,
"docstatus": 1 "docstatus": 1
@ -24,7 +24,7 @@ class TestLeaveAllocation(unittest.TestCase):
"employee": employee.name, "employee": employee.name,
"employee_name": employee.employee_name, "employee_name": employee.employee_name,
"leave_type": "_Test Leave Type", "leave_type": "_Test Leave Type",
"from_date": getdate("2015-09-1"), "from_date": getdate("2015-09-01"),
"to_date": getdate("2015-11-30"), "to_date": getdate("2015-11-30"),
"new_leaves_allocated": 5 "new_leaves_allocated": 5
} }

View File

@ -66,14 +66,18 @@ frappe.ui.form.on("Leave Application", {
}, },
get_leave_balance: function(frm) { 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) { if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date) {
return frm.call({ return frappe.call({
method: "get_leave_balance", method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on",
args: { args: {
employee: frm.doc.employee, employee: frm.doc.employee,
from_date: frm.doc.from_date, date: frm.doc.from_date,
to_date: frm.doc.to_date,
leave_type: frm.doc.leave_type 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 @@ frappe.ui.form.on("Leave Application", {
if(frm.doc.from_date && frm.doc.to_date) { if(frm.doc.from_date && frm.doc.to_date) {
if (cint(frm.doc.half_day)==1) { if (cint(frm.doc.half_day)==1) {
frm.set_value("total_leave_days", 0.5); 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 // server call is done to include holidays in leave days calculations
return frappe.call({ return frappe.call({
method: 'erpnext.hr.doctype.leave_application.leave_application.get_total_leave_days', method: 'erpnext.hr.doctype.leave_application.leave_application.get_number_of_leave_days',
args: { leave_app: frm.doc }, args: {
callback: function(response) { "employee": frm.doc.employee,
if (response && response.message) { "leave_type": frm.doc.leave_type,
frm.set_value('total_leave_days', response.message.total_leave_days); "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"); frm.trigger("get_leave_balance");
} }
} }

View File

@ -418,28 +418,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "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, "allow_on_submit": 1,
"bold": 0, "bold": 0,
@ -559,7 +537,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 3, "max_attachments": 3,
"modified": "2015-10-28 16:14:25.640730", "modified": "2015-11-15 19:32:32.258397",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Application", "name": "Leave Application",
@ -711,7 +689,7 @@
], ],
"read_only": 0, "read_only": 0,
"read_only_onload": 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_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "employee_name" "title_field": "employee_name"

View File

@ -2,13 +2,13 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, json import frappe
from frappe import _ from frappe import _
from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \ from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \
comma_or, get_fullname comma_or, get_fullname
from frappe import msgprint
from erpnext.hr.utils import set_employee_name 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 LeaveDayBlockedError(frappe.ValidationError): pass
class OverlapError(frappe.ValidationError): pass class OverlapError(frappe.ValidationError): pass
@ -18,8 +18,7 @@ class LeaveApproverIdentityError(frappe.ValidationError): pass
from frappe.model.document import Document from frappe.model.document import Document
class LeaveApplication(Document): class LeaveApplication(Document):
def get_feed(self): def get_feed(self):
return _("{0}: From {0} of type {1}").format(self.status, return _("{0}: From {0} of type {1}").format(self.status, self.employee_name, self.leave_type)
self.employee_name, self.leave_type)
def validate(self): def validate(self):
if not getattr(self, "__islocal", None) and frappe.db.exists(self.doctype, self.name): if not getattr(self, "__islocal", None) and frappe.db.exists(self.doctype, self.name):
@ -29,7 +28,7 @@ class LeaveApplication(Document):
set_employee_name(self) set_employee_name(self)
self.validate_to_date() self.validate_dates()
self.validate_balance_leaves() self.validate_balance_leaves()
self.validate_leave_overlap() self.validate_leave_overlap()
self.validate_max_days() self.validate_max_days()
@ -51,6 +50,8 @@ class LeaveApplication(Document):
if self.status != "Approved": if self.status != "Approved":
frappe.throw(_("Only Leave Applications with status 'Approved' can be submitted")) frappe.throw(_("Only Leave Applications with status 'Approved' can be submitted"))
self.validate_back_dated_application()
# notify leave applier about approval # notify leave applier about approval
self.notify_employee(self.status) self.notify_employee(self.status)
@ -58,9 +59,40 @@ class LeaveApplication(Document):
# notify leave applier about cancellation # notify leave applier about cancellation
self.notify_employee("cancelled") self.notify_employee("cancelled")
def show_block_day_warning(self): def validate_dates(self):
from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates 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):
block_dates = get_applicable_block_dates(self.from_date, self.to_date, block_dates = get_applicable_block_dates(self.from_date, self.to_date,
self.employee, self.company, all_lists=True) self.employee, self.company, all_lists=True)
@ -70,60 +102,39 @@ class LeaveApplication(Document):
frappe.msgprint(formatdate(d.block_date) + ": " + d.reason) frappe.msgprint(formatdate(d.block_date) + ": " + d.reason)
def validate_block_days(self): 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, block_dates = get_applicable_block_dates(self.from_date, self.to_date,
self.employee, self.company) self.employee, self.company)
if block_dates: if block_dates and self.status == "Approved":
if self.status == "Approved": frappe.throw(_("You are not authorized to approve leaves on Block Dates"), LeaveDayBlockedError)
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"))
def validate_balance_leaves(self): def validate_balance_leaves(self):
if self.from_date and self.to_date: 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: 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): if not is_lwp(self.leave_type):
self.leave_balance = get_leave_balance(self.employee, self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date)
self.leave_type, self.from_date, self.to_date)["leave_balance"]
if self.status != "Rejected" \ if self.status != "Rejected" and self.leave_balance < self.total_leave_days:
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 frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"): 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: 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): def validate_leave_overlap(self):
if not self.name: if not self.name:
self.name = "New Leave Application" self.name = "New Leave Application"
for d in frappe.db.sql("""select name, leave_type, posting_date, for d in frappe.db.sql("""select name, leave_type, posting_date, from_date, to_date
from_date, to_date
from `tabLeave Application` from `tabLeave Application`
where where employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved")
employee = %(employee)s and to_date >= %(from_date)s and from_date <= %(to_date)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""", { and name != %(name)s""", {
"employee": self.employee, "employee": self.employee,
"from_date": self.from_date, "from_date": self.from_date,
@ -131,9 +142,12 @@ class LeaveApplication(Document):
"name": self.name "name": self.name
}, as_dict = 1): }, as_dict = 1):
frappe.msgprint(_("Employee {0} has already applied for {1} between {2} and {3}").format(self.employee, frappe.msgprint(_("Employee {0} has already applied for {1} between {2} and {3}")
cstr(d['leave_type']), formatdate(d['from_date']), formatdate(d['to_date']))) .format(self.employee, cstr(d['leave_type']),
frappe.throw('<a href="#Form/Leave Application/{0}">{0}</a>'.format(d["name"]), OverlapError) 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): def validate_max_days(self):
max_days = frappe.db.get_value("Leave Type", self.leave_type, "max_days_allowed") max_days = frappe.db.get_value("Leave Type", self.leave_type, "max_days_allowed")
@ -145,7 +159,8 @@ class LeaveApplication(Document):
leave_approvers = [l.leave_approver for l in employee.get("leave_approvers")] leave_approvers = [l.leave_approver for l in employee.get("leave_approvers")]
if len(leave_approvers) and self.leave_approver not in 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` elif self.leave_approver and not frappe.db.sql("""select name from `tabUserRole`
where parent=%s and role='Leave Approver'""", self.leave_approver): where parent=%s and role='Leave Approver'""", self.leave_approver):
@ -153,8 +168,8 @@ class LeaveApplication(Document):
.format(get_fullname(self.leave_approver), self.leave_approver), InvalidLeaveApproverError) .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: 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"), frappe.throw(_("Only the selected Leave Approver can submit this Leave Application"),
raise_exception=LeaveApproverIdentityError) LeaveApproverIdentityError)
def notify_employee(self, status): def notify_employee(self, status):
employee = frappe.get_doc("Employee", self.employee) employee = frappe.get_doc("Employee", self.employee)
@ -214,59 +229,88 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
and user.name like %s and user.name like %s
and approver.leave_approver=user.name""", (filters.get("employee"), "%" + txt + "%")) and approver.leave_approver=user.name""", (filters.get("employee"), "%" + txt + "%"))
def get_holidays(leave_app): @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
@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 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 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, and h1.holiday_date between %s and %s""", (employee, from_date, to_date))[0][0]
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.
if not tot_hol: if not tot_hol:
tot_hol = frappe.db.sql("""select count(*) from `tabHoliday` h1, `tabHoliday List` h2 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 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""", and ifnull(h2.is_default,0) = 1""", (from_date, to_date))[0][0]
(leave_app.from_date, leave_app.to_date, leave_app.fiscal_year))[0][0]
return tot_hol 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): def is_lwp(leave_type):
lwp = frappe.db.sql("select is_lwp from `tabLeave Type` where name = %s", 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 return lwp and cint(lwp[0][0]) or 0

View File

@ -41,7 +41,7 @@ class ProcessPayroll(Document):
def get_joining_releiving_condition(self): def get_joining_releiving_condition(self):
m = self.get_month_details(self.fiscal_year, self.month) m = get_month_details(self.fiscal_year, self.month)
cond = """ cond = """
and ifnull(t1.date_of_joining, '0000-00-00') <= '%(month_end_date)s' and ifnull(t1.date_of_joining, '0000-00-00') <= '%(month_end_date)s'
and ifnull(t1.relieving_date, '2199-12-31') >= '%(month_start_date)s' and ifnull(t1.relieving_date, '2199-12-31') >= '%(month_start_date)s'
@ -54,24 +54,6 @@ class ProcessPayroll(Document):
if not self.get(f): if not self.get(f):
frappe.throw(_("Please set {0}").format(f)) frappe.throw(_("Please set {0}").format(f))
def get_month_details(self, year, month):
ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date")
if ysd:
from dateutil.relativedelta import relativedelta
import calendar, datetime
diff_mnt = cint(month)-cint(ysd.month)
if diff_mnt<0:
diff_mnt = 12-int(ysd.month)+cint(month)
msd = ysd + relativedelta(months=diff_mnt) # month start date
month_days = cint(calendar.monthrange(cint(msd.year) ,cint(month))[1]) # days in month
med = datetime.date(msd.year, cint(month), month_days) # month end date
return {
'year': msd.year,
'month_start_date': msd,
'month_end_date': med,
'month_days': month_days
}
def create_sal_slip(self): def create_sal_slip(self):
""" """
Creates salary slip for selected employees if already not created Creates salary slip for selected employees if already not created
@ -205,3 +187,22 @@ class ProcessPayroll(Document):
]) ])
return journal_entry.as_dict() return journal_entry.as_dict()
def get_month_details(year, month):
ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date")
if ysd:
from dateutil.relativedelta import relativedelta
import calendar, datetime
diff_mnt = cint(month)-cint(ysd.month)
if diff_mnt<0:
diff_mnt = 12-int(ysd.month)+cint(month)
msd = ysd + relativedelta(months=diff_mnt) # month start date
month_days = cint(calendar.monthrange(cint(msd.year) ,cint(month))[1]) # days in month
med = datetime.date(msd.year, cint(month), month_days) # month end date
return frappe._dict({
'year': msd.year,
'month_start_date': msd,
'month_end_date': med,
'month_days': month_days
})

View File

@ -10,6 +10,7 @@ from frappe.model.naming import make_autoname
from frappe import msgprint, _ from frappe import msgprint, _
from erpnext.setup.utils import get_company_currency from erpnext.setup.utils import get_company_currency
from erpnext.hr.utils import set_employee_name from erpnext.hr.utils import set_employee_name
from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
@ -25,11 +26,17 @@ class SalarySlip(TransactionBase):
self.pull_sal_struct(struct) self.pull_sal_struct(struct)
def check_sal_struct(self): def check_sal_struct(self):
m = get_month_details(self.fiscal_year, self.month)
struct = frappe.db.sql("""select name from `tabSalary Structure` struct = frappe.db.sql("""select name from `tabSalary Structure`
where employee=%s and is_active = 'Yes'""", self.employee) where employee=%s and is_active = 'Yes'
and from_date <= %s and (to_date is null or to_date >= %s)""",
(self.employee, m.month_start_date, m.month_end_date))
if not struct: if not struct:
msgprint(_("Please create Salary Structure for employee {0}").format(self.employee)) msgprint(_("No active Salary Structure found for employee {0} and the month")
.format(self.employee))
self.employee = None self.employee = None
return struct and struct[0][0] or '' return struct and struct[0][0] or ''
def pull_sal_struct(self, struct): def pull_sal_struct(self, struct):
@ -49,7 +56,7 @@ class SalarySlip(TransactionBase):
if not self.month: if not self.month:
self.month = "%02d" % getdate(nowdate()).month self.month = "%02d" % getdate(nowdate()).month
m = frappe.get_doc('Process Payroll').get_month_details(self.fiscal_year, self.month) m = get_month_details(self.fiscal_year, self.month)
holidays = self.get_holidays_for_employee(m) holidays = self.get_holidays_for_employee(m)
if not cint(frappe.db.get_value("HR Settings", "HR Settings", if not cint(frappe.db.get_value("HR Settings", "HR Settings",

View File

@ -10,8 +10,20 @@ from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_sli
class TestSalarySlip(unittest.TestCase): class TestSalarySlip(unittest.TestCase):
def setUp(self): def setUp(self):
frappe.db.sql("""delete from `tabLeave Application`""") for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]:
frappe.db.sql("""delete from `tabSalary 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) frappe.db.set_value("Holiday List", "_Test Holiday List", "is_default", 1)

View File

@ -7,12 +7,14 @@ frappe.query_reports["Employee Leave Balance"] = {
"fieldname":"from_date", "fieldname":"from_date",
"label": __("From Date"), "label": __("From Date"),
"fieldtype": "Date", "fieldtype": "Date",
"reqd": 1,
"default": frappe.datetime.year_start() "default": frappe.datetime.year_start()
}, },
{ {
"fieldname":"to_date", "fieldname":"to_date",
"label": __("To Date"), "label": __("To Date"),
"fieldtype": "Date", "fieldtype": "Date",
"reqd": 1,
"default": frappe.datetime.year_end() "default": frappe.datetime.year_end()
}, },
{ {
@ -20,6 +22,7 @@ frappe.query_reports["Employee Leave Balance"] = {
"label": __("Company"), "label": __("Company"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Company", "options": "Company",
"reqd": 1,
"default": frappe.defaults.get_user_default("company") "default": frappe.defaults.get_user_default("company")
} }
] ]

View File

@ -4,67 +4,54 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ 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): def execute(filters=None):
if not filters: filters = {} leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc")
employee_filters = { columns = get_columns(leave_types)
"status": "Active" data = get_data(filters, leave_types)
}
if filters.get("company"): return columns, data
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)
def get_columns(leave_types):
columns = [ 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: for leave_type in leave_types:
columns.append(_(leave_type) + " " + _("Opening") + ":Float") columns.append(_(leave_type) + " " + _("Taken") + ":Float:160")
columns.append(_(leave_type) + " " + _("Taken") + ":Float") columns.append(_(leave_type) + " " + _("Balance") + ":Float:160")
columns.append(_(leave_type) + " " + _("Balance") + ":Float")
data = {} return columns
for d in allocations:
data.setdefault((d.employee,d.leave_type), frappe._dict()).allocation = d.leaves_allocated
for d in applications: def get_data(filters, leave_types):
data.setdefault((d.employee, d.leave_type), frappe._dict()).leaves = d.leaves
result = [] allocation_records_based_on_to_date = get_leave_allocation_records(filters.to_date)
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] 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

View File

@ -232,3 +232,4 @@ execute:frappe.delete_doc_if_exists("Report", "Item-wise Last Purchase Rate")
erpnext.patches.v6_6.fix_website_image erpnext.patches.v6_6.fix_website_image
erpnext.patches.v6_6.remove_fiscal_year_from_leave_allocation erpnext.patches.v6_6.remove_fiscal_year_from_leave_allocation
execute:frappe.delete_doc_if_exists("DocType", "Stock UOM Replace Utility") execute:frappe.delete_doc_if_exists("DocType", "Stock UOM Replace Utility")
erpnext.patches.v6_8.make_webform_standard

View File

View File

@ -0,0 +1,9 @@
import frappe
def execute():
frappe.reload_doctype("Web Form")
frappe.delete_doc("Web Form", "Issues")
frappe.delete_doc("Web Form", "Addresses")
from erpnext.setup.install import add_web_forms
add_web_forms()

View File

@ -4,12 +4,14 @@
frappe.provide("erpnext.projects"); frappe.provide("erpnext.projects");
frappe.ui.form.on("Time Log", "onload", function(frm) { frappe.ui.form.on("Time Log", "onload", function(frm) {
if (frm.doc.__islocal) {
if (frm.doc.for_manufacturing) { if (frm.doc.for_manufacturing) {
frappe.ui.form.trigger("Time Log", "production_order"); frappe.ui.form.trigger("Time Log", "production_order");
} }
if (frm.doc.from_time && frm.doc.to_time) { if (frm.doc.from_time && frm.doc.to_time) {
frappe.ui.form.trigger("Time Log", "to_time"); frappe.ui.form.trigger("Time Log", "to_time");
} }
}
}); });
frappe.ui.form.on("Time Log", "refresh", function(frm) { frappe.ui.form.on("Time Log", "refresh", function(frm) {

View File

@ -500,9 +500,13 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return; if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return;
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]); frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);
if(this.frm.doc.party_account_currency == this.frm.doc.currency) {
var total_amount_to_pay = flt((this.frm.doc.grand_total - this.frm.doc.total_advance var total_amount_to_pay = flt((this.frm.doc.grand_total - this.frm.doc.total_advance
- this.frm.doc.write_off_amount), precision("grand_total")); - this.frm.doc.write_off_amount), precision("grand_total"));
else {
var total_amount_to_pay = flt((this.frm.doc.base_grand_total - this.frm.doc.total_advance
- this.frm.doc.base_write_off_amount), precision("base_grand_total"));
}
if(this.frm.doc.doctype == "Sales Invoice") { if(this.frm.doc.doctype == "Sales Invoice") {
frappe.model.round_floats_in(this.frm.doc, ["paid_amount"]); frappe.model.round_floats_in(this.frm.doc, ["paid_amount"]);
@ -518,18 +522,15 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
this.frm.refresh_field("paid_amount"); this.frm.refresh_field("paid_amount");
this.frm.refresh_field("base_paid_amount"); this.frm.refresh_field("base_paid_amount");
var outstanding_amount = flt(total_amount_to_pay - this.frm.doc.paid_amount, var paid_amount = (this.frm.doc.party_account_currency == this.frm.doc.currency) ?
this.frm.doc.paid_amount : this.frm.doc.base_paid_amount;
var outstanding_amount = flt(total_amount_to_pay - flt(paid_amount),
precision("outstanding_amount")); precision("outstanding_amount"));
} else if(this.frm.doc.doctype == "Purchase Invoice") { } else if(this.frm.doc.doctype == "Purchase Invoice") {
var outstanding_amount = flt(total_amount_to_pay, precision("outstanding_amount")); var outstanding_amount = flt(total_amount_to_pay, precision("outstanding_amount"));
} }
if(this.frm.doc.party_account_currency == this.frm.doc.currency) {
this.frm.set_value("outstanding_amount", outstanding_amount); this.frm.set_value("outstanding_amount", outstanding_amount);
} else {
this.frm.set_value("outstanding_amount",
flt(outstanding_amount * this.frm.doc.conversion_rate, precision("outstanding_amount")));
}
} }
}) })

View File

@ -5,8 +5,9 @@ frappe.pages['setup-wizard'].on_page_load = function(wrapper) {
frappe.set_route("desk"); frappe.set_route("desk");
return; return;
} }
} };
function load_erpnext_slides() {
$.extend(erpnext.wiz, { $.extend(erpnext.wiz, {
region: { region: {
title: __("Region"), title: __("Region"),
@ -400,9 +401,11 @@ erpnext.wiz.fiscal_years = {
"South Africa": ["03-01", "02-28"], "South Africa": ["03-01", "02-28"],
"Thailand": ["10-01", "09-30"], "Thailand": ["10-01", "09-30"],
"United Kingdom": ["04-01", "03-31"], "United Kingdom": ["04-01", "03-31"],
} };
};
frappe.wiz.on("before_load", function() { frappe.wiz.on("before_load", function() {
load_erpnext_slides();
frappe.wiz.add_slide(erpnext.wiz.user); frappe.wiz.add_slide(erpnext.wiz.user);
frappe.wiz.add_slide(erpnext.wiz.org); frappe.wiz.add_slide(erpnext.wiz.org);
frappe.wiz.add_slide(erpnext.wiz.branding); frappe.wiz.add_slide(erpnext.wiz.branding);

View File

@ -56,6 +56,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry); cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_bank_entry);
} }
if (this.frm.has_perm("submit")) {
// stop // stop
if(flt(doc.per_delivered, 2) < 100 || flt(doc.per_billed) < 100) { if(flt(doc.per_delivered, 2) < 100 || flt(doc.per_billed) < 100) {
cur_frm.add_custom_button(__('Stop'), this.stop_sales_order) cur_frm.add_custom_button(__('Stop'), this.stop_sales_order)
@ -63,6 +64,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
cur_frm.add_custom_button(__('Close'), this.close_sales_order) cur_frm.add_custom_button(__('Close'), this.close_sales_order)
}
// maintenance // maintenance
if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)===-1) { if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)===-1) {
@ -82,10 +84,12 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
} else { } else {
if (this.frm.has_perm("submit")) {
// un-stop // un-stop
cur_frm.add_custom_button(__('Re-open'), cur_frm.cscript['Unstop Sales Order']); cur_frm.add_custom_button(__('Re-open'), cur_frm.cscript['Unstop Sales Order']);
} }
} }
}
if (this.frm.doc.docstatus===0) { if (this.frm.doc.docstatus===0) {
cur_frm.add_custom_button(__('From Quotation'), cur_frm.add_custom_button(__('From Quotation'),

View File

@ -27,6 +27,7 @@ def get_random_quote():
("There is more to life than increasing its speed.", "Mahatma Gandhi"), ("There is more to life than increasing its speed.", "Mahatma Gandhi"),
("A small body of determined spirits fired by an unquenchable faith in their mission can alter the course of history.", "Mahatma Gandhi"), ("A small body of determined spirits fired by an unquenchable faith in their mission can alter the course of history.", "Mahatma Gandhi"),
("If two wrongs don't make a right, try three.", "Laurence J. Peter"), ("If two wrongs don't make a right, try three.", "Laurence J. Peter"),
("Inspiration exists, but it has to find you working.", "Pablo Picasso"),
] ]
return random.choice(quotes) return random.choice(quotes)

View File

@ -9,6 +9,7 @@
"docstatus": 0, "docstatus": 0,
"doctype": "Web Form", "doctype": "Web Form",
"introduction_text": null, "introduction_text": null,
"is_standard": 1,
"login_required": 1, "login_required": 1,
"modified": "2015-06-01 06:53:43.699336", "modified": "2015-06-01 06:53:43.699336",
"name": "addresses", "name": "addresses",

View File

@ -8,6 +8,7 @@
"doc_type": "Issue", "doc_type": "Issue",
"docstatus": 0, "docstatus": 0,
"doctype": "Web Form", "doctype": "Web Form",
"is_standard": 1,
"introduction_text": null, "introduction_text": null,
"login_required": 1, "login_required": 1,
"modified": "2015-06-01 08:14:26.350792", "modified": "2015-06-01 08:14:26.350792",

View File

@ -18,7 +18,7 @@
<br> <br>
<hr> <hr>
<h3>Next Steps</h3> <h3>{%= __("Next Steps") %}</h3>
<ul class="list-unstyled"> <ul class="list-unstyled">
<li><a class="text-muted" href="#">{%= __("Go to the Desktop and start using ERPNext") %}</a></li> <li><a class="text-muted" href="#">{%= __("Go to the Desktop and start using ERPNext") %}</a></li>
<li><a class="text-muted" href="#Module/Learn">{%= __("View a list of all the help videos") %}</a></li> <li><a class="text-muted" href="#Module/Learn">{%= __("View a list of all the help videos") %}</a></li>

View File

@ -46,13 +46,14 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
if (cint(frappe.defaults.get_default("auto_accounting_for_stock"))) { if (cint(frappe.defaults.get_default("auto_accounting_for_stock"))) {
this.show_general_ledger(); this.show_general_ledger();
} }
if (this.frm.has_perm("submit") && (doc.status !== "Closed")
if(doc.status !== "Closed") { && this.frm.doc.__onload && this.frm.doc.__onload.has_return_entry) {
cur_frm.add_custom_button(__("Close"), this.close_delivery_note) cur_frm.add_custom_button(__("Close"), this.close_delivery_note)
} }
} }
if(doc.__onload && !doc.__onload.billing_complete && doc.docstatus==1 && !doc.is_return && doc.status!="Closed") { if(doc.__onload && !doc.__onload.billing_complete && doc.docstatus==1
&& !doc.is_return && doc.status!="Closed") {
// show Make Invoice button only if Delivery Note is not created from Sales Invoice // show Make Invoice button only if Delivery Note is not created from Sales Invoice
var from_sales_invoice = false; var from_sales_invoice = false;
from_sales_invoice = cur_frm.doc.items.some(function(item) { from_sales_invoice = cur_frm.doc.items.some(function(item) {
@ -63,7 +64,7 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
cur_frm.add_custom_button(__('Invoice'), this.make_sales_invoice).addClass("btn-primary"); cur_frm.add_custom_button(__('Invoice'), this.make_sales_invoice).addClass("btn-primary");
} }
if(doc.docstatus==1 && doc.status === "Closed") { if(doc.docstatus==1 && doc.status === "Closed" && this.frm.has_perm("submit")) {
cur_frm.add_custom_button(__('Re-open'), this.reopen_delivery_note) cur_frm.add_custom_button(__('Re-open'), this.reopen_delivery_note)
} }
erpnext.stock.delivery_note.set_print_hide(doc, dt, dn); erpnext.stock.delivery_note.set_print_hide(doc, dt, dn);

View File

@ -69,7 +69,10 @@ class DeliveryNote(SellingController):
where docstatus=1 and delivery_note=%s""", self.name) where docstatus=1 and delivery_note=%s""", self.name)
if billed_qty: if billed_qty:
total_qty = sum((item.qty for item in self.get("items"))) total_qty = sum((item.qty for item in self.get("items")))
self.get("__onload").billing_complete = (billed_qty[0][0] == total_qty) self.set_onload("billing_complete", (billed_qty[0][0] == total_qty))
self.set_onload("has_return_entry", len(frappe.db.exists({"doctype": "Delivery Note",
"is_return": 1, "return_against": self.name, "docstatus": 1})))
def before_print(self): def before_print(self):
def toggle_print_hide(meta, fieldname): def toggle_print_hide(meta, fieldname):

View File

@ -105,7 +105,11 @@ class Item(WebsiteGenerator):
frappe.local.message_log.pop() frappe.local.message_log.pop()
except requests.exceptions.HTTPError: except requests.exceptions.HTTPError:
frappe.msgprint(_("Warning: Invalid Attachment {0}").format(self.website_image)) frappe.msgprint(_("Warning: Invalid attachment {0}").format(self.website_image))
self.website_image = None
except requests.exceptions.SSLError:
frappe.msgprint(_("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
self.website_image = None self.website_image = None
# for CSV import # for CSV import

View File

@ -57,14 +57,18 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
if(this.frm.doc.docstatus == 1 && this.frm.doc.status!="Closed") { if(this.frm.doc.docstatus == 1 && this.frm.doc.status!="Closed") {
cur_frm.add_custom_button(__('Return'), this.make_purchase_return); cur_frm.add_custom_button(__('Return'), this.make_purchase_return);
if(this.frm.doc.__onload && !this.frm.doc.__onload.billing_complete) { if(this.frm.doc.__onload && !this.frm.doc.__onload.billing_complete) {
cur_frm.add_custom_button(__('Invoice'), this.make_purchase_invoice).addClass("btn-primary"); cur_frm.add_custom_button(__('Invoice'),
this.make_purchase_invoice).addClass("btn-primary");
} }
if (this.frm.has_perm("submit") &&
this.frm.doc.__onload && this.frm.doc.__onload.has_return_entry) {
cur_frm.add_custom_button(__("Close"), this.close_purchase_receipt) cur_frm.add_custom_button(__("Close"), this.close_purchase_receipt)
} }
} }
}
if(this.frm.doc.docstatus==1 && this.frm.doc.status === "Closed") { if(this.frm.doc.docstatus==1 && this.frm.doc.status === "Closed" && this.frm.has_perm("submit")) {
cur_frm.add_custom_button(__('Re-open'), this.reopen_purchase_receipt) cur_frm.add_custom_button(__('Re-open'), this.reopen_purchase_receipt)
} }

View File

@ -51,7 +51,10 @@ class PurchaseReceipt(BuyingController):
where purchase_receipt=%s and docstatus=1""", self.name) where purchase_receipt=%s and docstatus=1""", self.name)
if billed_qty: if billed_qty:
total_qty = sum((item.qty for item in self.get("items"))) total_qty = sum((item.qty for item in self.get("items")))
self.get("__onload").billing_complete = (billed_qty[0][0] == total_qty) self.set_onload("billing_complete", (billed_qty[0][0] == total_qty))
self.set_onload("has_return_entry", len(frappe.db.exists({"doctype": "Purchase Receipt",
"is_return": 1, "return_against": self.name, "docstatus": 1})))
def validate(self): def validate(self):
super(PurchaseReceipt, self).validate() super(PurchaseReceipt, self).validate()

View File

@ -1,5 +1,5 @@
<div class="web-list-item"> <div class="web-list-item">
<a href="/addresses?name={{ doc.name }}" no-pjax class="no-decoration"> <a href="/addresses?name={{ doc.name | urlencode }}" no-pjax class="no-decoration">
<h4 class="strong">{{ doc.address_title }}</h4> <h4 class="strong">{{ doc.address_title }}</h4>
<p class="text-muted small"> <p class="text-muted small">
{{ frappe.get_doc(doc).get_display() }} {{ frappe.get_doc(doc).get_display() }}

View File

@ -1,6 +1,6 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
version = "6.8.4" version = "6.9.0"
with open("requirements.txt", "r") as f: with open("requirements.txt", "r") as f:
install_requires = f.readlines() install_requires = f.readlines()