Merge pull request #6662 from PawanMeh/fixes_6363
[fix] #6363
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index d352003..a51e2b5 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -193,7 +193,7 @@
def validate_max_days(self):
max_days = frappe.db.get_value("Leave Type", self.leave_type, "max_days_allowed")
- if max_days and self.total_leave_days > max_days:
+ if max_days and self.total_leave_days > cint(max_days):
frappe.throw(_("Leave of type {0} cannot be longer than {1}").format(self.leave_type, max_days))
def validate_leave_approver(self):
diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py
index 84a20d9..e101c3d 100644
--- a/erpnext/hr/doctype/salary_slip/salary_slip.py
+++ b/erpnext/hr/doctype/salary_slip/salary_slip.py
@@ -154,21 +154,18 @@
def check_sal_struct(self, joining_date, relieving_date):
st_name = frappe.db.sql("""select parent from `tabSalary Structure Employee`
- where employee=%s order by modified desc limit 1""",self.employee)
+ where employee=%s
+ and parent in (select name from `tabSalary Structure`
+ where is_active = 'Yes'
+ and (from_date <= %s or from_date <= %s)
+ and (to_date is null or to_date >= %s or to_date >= %s))
+ """,(self.employee, self.start_date, joining_date, self.end_date, relieving_date))
if st_name:
- struct = frappe.db.sql("""select name from `tabSalary Structure`
- where name=%s and is_active = 'Yes'
- and (from_date <= %s or from_date <= %s)
- and (to_date is null or to_date >= %s or to_date >= %s) order by from_date desc limit 1""",
- (st_name, self.start_date, joining_date, self.end_date, relieving_date))
-
- if not struct:
- self.salary_structure = None
- frappe.throw(_("No active or default Salary Structure found for employee {0} for the given dates")
- .format(self.employee), title=_('Salary Structure Missing'))
-
- return struct and struct[0][0] or ''
+ if len(st_name) > 1:
+ frappe.msgprint(_("Multiple active Salary Structures found for employee {0} for the given dates")
+ .format(self.employee), title=_('Warning'))
+ return st_name and st_name[0][0] or ''
else:
self.salary_structure = None
frappe.throw(_("No active or default Salary Structure found for employee {0} for the given dates")
@@ -280,24 +277,26 @@
holidays = [cstr(i) for i in holidays]
return holidays
-
+
def calculate_lwp(self, holidays, working_days):
lwp = 0
+ holidays = "','".join(holidays)
for d in range(working_days):
dt = add_days(cstr(getdate(self.start_date)), d)
- if dt not in holidays:
- leave = frappe.db.sql("""
- select t1.name, t1.half_day
- from `tabLeave Application` t1, `tabLeave Type` t2
- where t2.name = t1.leave_type
- and t2.is_lwp = 1
- and t1.docstatus = 1
- and t1.employee = %s
- and %s between from_date and to_date
- """, (self.employee, dt))
- if leave:
- lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1)
- return lwp
+ leave = frappe.db.sql("""
+ select t1.name, t1.half_day
+ from `tabLeave Application` t1, `tabLeave Type` t2
+ where t2.name = t1.leave_type
+ and t2.is_lwp = 1
+ and t1.docstatus = 1
+ and t1.employee = %(employee)s
+ and CASE WHEN t2.include_holiday != 1 THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date
+ WHEN t2.include_holiday THEN %(dt)s between from_date and to_date
+ END
+ """.format(holidays), {"employee": self.employee, "dt": dt})
+ if leave:
+ lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1)
+ return lwp
def check_existing(self):
if not self.salary_slip_based_on_timesheet:
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js
index d3f362e..334e8a5 100755
--- a/erpnext/hr/doctype/salary_structure/salary_structure.js
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.js
@@ -21,7 +21,7 @@
type: "earning"
}
}
- })
+ });
frm.set_query("salary_component", "deductions", function() {
return {
filters: {
@@ -32,14 +32,68 @@
},
refresh: function(frm) {
- frm.trigger("toggle_fields")
+ frm.trigger("toggle_fields");
frm.fields_dict['earnings'].grid.set_column_disp("default_amount", false);
frm.fields_dict['deductions'].grid.set_column_disp("default_amount", false);
frm.add_custom_button(__("Preview Salary Slip"),
function() { frm.trigger('preview_salary_slip'); }, "icon-sitemap", "btn-default");
+
+ frm.add_custom_button(__("Add Employees"),function () {
+ frm.trigger('add_employees')
+ })
- },
+ },
+
+ add_employees:function (frm) {
+ frm.$emp_dialog = new frappe.ui.Dialog({
+ title: __("Add Employees"),
+ fields: [
+ {fieldname:'company', fieldtype:'Link', options: 'Company', label: __('Company')},
+ {fieldname:'branch', fieldtype:'Link', options: 'Branch', label: __('Branch')},
+ {fieldname:'department', fieldtype:'Link', options: 'Department', label: __('Department')},
+ {fieldname:'designation', fieldtype:'Link', options: 'Designation', label: __('Designation')},
+ {fieldname:'base_variable', fieldtype:'Section Break'},
+ {fieldname:'base', fieldtype:'Currency', label: __('Base')},
+ {fieldname:'base_col_br', fieldtype:'Column Break'},
+ {fieldname:'variable', fieldtype:'Currency', label: __('Variable')}
+ ]
+ });
+ frm.$emp_dialog.set_primary_action(__("Add"), function() {
+ frm.trigger('get_employees');
+ });
+ frm.$emp_dialog.show();
+ },
+
+ get_employees:function (frm) {
+ var filters = frm.$emp_dialog.get_values();
+ if ('variable' in filters) {
+ delete filters.variable
+ }
+ if ('base' in filters) {
+ delete filters.base
+ }
+ frappe.call({
+ method:'erpnext.hr.doctype.salary_structure.salary_structure.get_employees',
+ args:{
+ filters: filters
+ },
+ callback:function (r) {
+ var employees = $.map(frm.doc.employees, function(d) { return d.employee });
+ for (var i=0; i< r.message.length; i++) {
+ if (employees.indexOf(r.message[i].name) === -1) {
+ var row = frappe.model.add_child(frm.doc, frm.fields_dict.employees.df.options, frm.fields_dict.employees.df.fieldname);
+ row.employee = r.message[i].name;
+ row.employee_name = r.message[i].employee_name;
+ row.base = frm.$emp_dialog.get_value('base');
+ row.variable = frm.$emp_dialog.get_value('variable');
+ }
+ }
+ frm.refresh_field('employees');
+ frm.$emp_dialog.hide()
+ }
+ })
+ },
salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields")
@@ -81,12 +135,12 @@
frm.toggle_display(['salary_component', 'hour_rate'], frm.doc.salary_slip_based_on_timesheet);
frm.toggle_reqd(['salary_component', 'hour_rate'], frm.doc.salary_slip_based_on_timesheet);
}
-})
+});
cur_frm.cscript.amount = function(doc, cdt, cdn){
calculate_totals(doc, cdt, cdn);
-}
+};
var calculate_totals = function(doc) {
var tbl1 = doc.earnings || [];
diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.py b/erpnext/hr/doctype/salary_structure/salary_structure.py
index d4bc6e3..13622c3 100644
--- a/erpnext/hr/doctype/salary_structure/salary_structure.py
+++ b/erpnext/hr/doctype/salary_structure/salary_structure.py
@@ -58,4 +58,9 @@
doc.name = 'Preview for {0}'.format(employee)
return frappe.get_print(doc.doctype, doc.name, doc = doc, print_format = print_format)
else:
- return doc
\ No newline at end of file
+ return doc
+
+
+@frappe.whitelist()
+def get_employees(**args):
+ return frappe.get_list('Employee',filters=args['filters'], fields=['name', 'employee_name'])
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index e291696..4ddeae9 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -69,6 +69,7 @@
self.flags.old_lead = self.lead_name
validate_party_accounts(self)
self.status = get_party_status(self)
+ self.validate_credit_limit_on_change()
def on_update(self):
self.validate_name_with_customer_group()
@@ -125,6 +126,15 @@
if frappe.db.exists("Customer Group", self.name):
frappe.throw(_("A Customer Group exists with same name please change the Customer name or rename the Customer Group"), frappe.NameError)
+ def validate_credit_limit_on_change(self):
+ if self.get("__islocal") or self.credit_limit == frappe.db.get_value("Customer", self.name, "credit_limit"):
+ return
+
+ for company in frappe.get_all("Company"):
+ outstanding_amt = get_customer_outstanding(self.name, company.name)
+ if flt(self.credit_limit) < outstanding_amt:
+ frappe.throw(_("""New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}""").format(outstanding_amt))
+
def delete_customer_address(self):
addresses = frappe.db.sql("""select name, lead from `tabAddress`
where customer=%s""", (self.name,))
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 9e53845..36e4819 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -8,6 +8,8 @@
from frappe.test_runner import make_test_records
from erpnext.exceptions import PartyFrozen, PartyDisabled
+from frappe.utils import flt
+from erpnext.selling.doctype.customer.customer import get_credit_limit, get_customer_outstanding
test_ignore = ["Price List"]
@@ -111,6 +113,64 @@
self.assertEquals("_Test Customer 1 - 1", duplicate_customer.name)
self.assertEquals(test_customer_1.customer_name, duplicate_customer.customer_name)
+ def get_customer_outstanding_amount(self):
+ outstanding_amt = get_customer_outstanding('_Test Customer', '_Test Company')
+
+ # If outstanding is negative make a transaction to get positive outstanding amount
+ if outstanding_amt > 0.0:
+ return outstanding_amt
+
+ item_qty = int((abs(outstanding_amt) + 200)/100)
+ make_sales_order({'qty':item_qty})
+ return get_customer_outstanding('_Test Customer', '_Test Company')
+
+ def test_customer_credit_limit(self):
+ from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+ from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
+
+ outstanding_amt = self.get_customer_outstanding_amount()
+ credit_limit = get_credit_limit('_Test Customer', '_Test Company')
+
+ if outstanding_amt <= 0.0:
+ item_qty = int((abs(outstanding_amt) + 200)/100)
+ make_sales_order({'qty':item_qty})
+
+ if credit_limit == 0.0:
+ frappe.db.set_value("Customer", '_Test Customer', 'credit_limit', outstanding_amt - 50.0)
+
+ # Sales Order
+ so = make_sales_order(do_not_submit=True)
+ self.assertRaises(frappe.ValidationError, so.submit)
+
+ # Delivery Note
+ dn = create_delivery_note(do_not_submit=True)
+ self.assertRaises(frappe.ValidationError, dn.submit)
+
+ # Sales Invoice
+ si = create_sales_invoice(do_not_submit=True)
+ self.assertRaises(frappe.ValidationError, si.submit)
+
+ if credit_limit > outstanding_amt:
+ frappe.db.set_value("Customer", '_Test Customer', 'credit_limit', credit_limit)
+
+ # Makes Sales invoice from Sales Order
+ so.save(ignore_permissions=True)
+ si = make_sales_invoice(so.name)
+ si.save(ignore_permissions=True)
+ self.assertRaises(frappe.ValidationError, make_sales_order)
+
+ def test_customer_credit_limit_on_change(self):
+ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+
+ outstanding_amt = self.get_customer_outstanding_amount()
+ credit_limit = get_credit_limit('_Test Customer', '_Test Company')
+
+ customer = frappe.get_doc("Customer", '_Test Customer')
+ customer.credit_limit = flt(outstanding_amt - 100)
+ self.assertRaises(frappe.ValidationError, customer.save)
+
def get_customer_dict(customer_name):
return {
"customer_group": "_Test Customer Group",
@@ -119,4 +179,3 @@
"doctype": "Customer",
"territory": "_Test Territory"
}
-
diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py
index 8f058e8..eb5c043 100644
--- a/erpnext/setup/doctype/company/delete_company_transactions.py
+++ b/erpnext/setup/doctype/company/delete_company_transactions.py
@@ -14,7 +14,7 @@
doc = frappe.get_doc("Company", company_name)
if frappe.session.user != doc.owner:
- frappe.throw(_("Transactions can only be deleted by the creator of the Company"),
+ frappe.throw(_("Transactions can only be deleted by the creator of the Company"),
frappe.PermissionError)
delete_bins(company_name)
@@ -37,6 +37,9 @@
if not meta.issingle:
if not meta.istable:
+ # delete communication
+ delete_communications(doctype, company_name, company_fieldname)
+
# delete children
for df in meta.get_table_fields():
frappe.db.sql("""delete from `tab{0}` where parent in
@@ -64,7 +67,6 @@
frappe.db.sql("""update tabSeries set current = %s
where name=%s""", (last, prefix))
-
def delete_bins(company_name):
frappe.db.sql("""delete from tabBin where warehouse in
(select name from tabWarehouse where company=%s)""", company_name)
@@ -76,3 +78,9 @@
where lead=%s and (customer='' or customer is null) and (supplier='' or supplier is null)""", lead.name)
frappe.db.sql("""update `tabAddress` set lead=null, lead_name=null where lead=%s""", lead.name)
+
+def delete_communications(doctype, company_name, company_fieldname):
+ frappe.db.sql("""
+ DELETE FROM `tabCommunication` WHERE reference_doctype = %s AND
+ EXISTS (SELECT name FROM `tab{0}` WHERE {1} = %s AND `tabCommunication`.reference_name = name)
+ """.format(doctype, company_fieldname), (doctype, company_name))