Can't set Start and End Dates in Salary Slip (#9513) (#9944)

* remove trigger from end_date

* adds new function `get_end_date`:
- it tries to calculate the appropriate end date for a given frequency
- returns an empty string if frequency is 'biweekly'
- adds test cases

* changes logic in `set_start_end_dates`:
- if start_date is empty in form, call process_payroll.get_start_end_dates
- else, call process_payroll.get_end_date

* `get_end_date` should return a dict

* changed "biweekly" to "bimonthly"

* change the behaviour of process payroll start and end date:
- when payroll frequency is changed, change start/end date as usual
- if start date is manually changed, use the frequency to calculate the end date

* clean up

* further cleanup

* in `get_end_date`, if `frequency` isn"t given, "monthly"

* remove end_date from cscript and introduce `set_end_date`

* fix tests

* removed whitespaces
This commit is contained in:
tundebabzy 2017-07-20 06:03:03 +01:00 committed by Makarand Bauskar
parent 8e0f23efc7
commit 11d23f84d7
4 changed files with 104 additions and 29 deletions

View File

@ -1,6 +1,8 @@
// 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
var in_progress = false;
frappe.ui.form.on("Process Payroll", { frappe.ui.form.on("Process Payroll", {
onload: function (frm) { onload: function (frm) {
frm.doc.posting_date = frappe.datetime.nowdate(); frm.doc.posting_date = frappe.datetime.nowdate();
@ -47,11 +49,12 @@ frappe.ui.form.on("Process Payroll", {
}, },
start_date: function (frm) { start_date: function (frm) {
frm.trigger("set_start_end_dates"); if(!in_progress && frm.doc.start_date){
}, frm.trigger("set_end_date");
}else{
end_date: function (frm) { // reset flag
frm.trigger("set_start_end_dates"); in_progress = false
}
}, },
salary_slip_based_on_timesheet: function (frm) { salary_slip_based_on_timesheet: function (frm) {
@ -68,10 +71,11 @@ frappe.ui.form.on("Process Payroll", {
method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_start_end_dates', method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_start_end_dates',
args: { args: {
payroll_frequency: frm.doc.payroll_frequency, payroll_frequency: frm.doc.payroll_frequency,
start_date: frm.doc.start_date || frm.doc.posting_date start_date: frm.doc.posting_date
}, },
callback: function (r) { callback: function (r) {
if (r.message) { if (r.message) {
in_progress = true;
frm.set_value('start_date', r.message.start_date); frm.set_value('start_date', r.message.start_date);
frm.set_value('end_date', r.message.end_date); frm.set_value('end_date', r.message.end_date);
} }
@ -79,6 +83,21 @@ frappe.ui.form.on("Process Payroll", {
}) })
} }
}, },
set_end_date: function(frm){
frappe.call({
method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_end_date',
args: {
frequency: frm.doc.payroll_frequency,
start_date: frm.doc.start_date
},
callback: function (r) {
if (r.message) {
frm.set_value('end_date', r.message.end_date);
}
}
})
}
}) })
cur_frm.cscript.display_activity_log = function (msg) { cur_frm.cscript.display_activity_log = function (msg) {

View File

@ -3,7 +3,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money from dateutil.relativedelta import relativedelta
from frappe.utils import cint, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT
from frappe import _ from frappe import _
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
@ -47,7 +48,6 @@ class ProcessPayroll(Document):
%s """% cond, {"sal_struct": sal_struct}) %s """% cond, {"sal_struct": sal_struct})
return emp_list return emp_list
def get_filter_condition(self): def get_filter_condition(self):
self.check_mandatory() self.check_mandatory()
@ -58,7 +58,6 @@ class ProcessPayroll(Document):
return cond return cond
def get_joining_releiving_condition(self): def get_joining_releiving_condition(self):
cond = """ cond = """
and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s' and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
@ -66,7 +65,6 @@ class ProcessPayroll(Document):
""" % {"start_date": self.start_date, "end_date": self.end_date} """ % {"start_date": self.start_date, "end_date": self.end_date}
return cond return cond
def check_mandatory(self): def check_mandatory(self):
for fieldname in ['company', 'start_date', 'end_date']: for fieldname in ['company', 'start_date', 'end_date']:
if not self.get(fieldname): if not self.get(fieldname):
@ -110,7 +108,6 @@ class ProcessPayroll(Document):
ss_list.append(ss_dict) ss_list.append(ss_dict)
return self.create_log(ss_list) return self.create_log(ss_list)
def create_log(self, ss_list): def create_log(self, ss_list):
if not ss_list or len(ss_list) < 1: if not ss_list or len(ss_list) < 1:
log = "<p>" + _("No employee for the above selected criteria OR salary slip already created") + "</p>" log = "<p>" + _("No employee for the above selected criteria OR salary slip already created") + "</p>"
@ -134,7 +131,6 @@ class ProcessPayroll(Document):
""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict) """ % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
return ss_list return ss_list
def submit_salary_slips(self): def submit_salary_slips(self):
""" """
Submit all salary slips based on selected criteria Submit all salary slips based on selected criteria
@ -194,7 +190,6 @@ class ProcessPayroll(Document):
def format_as_links(self, salary_slip): def format_as_links(self, salary_slip):
return ['<a href="#Form/Salary Slip/{0}">{0}</a>'.format(salary_slip)] return ['<a href="#Form/Salary Slip/{0}">{0}</a>'.format(salary_slip)]
def get_total_salary_and_loan_amounts(self): def get_total_salary_and_loan_amounts(self):
""" """
Get total loan principal, loan interest and salary amount from submitted salary slip based on selected criteria Get total loan principal, loan interest and salary amount from submitted salary slip based on selected criteria
@ -257,7 +252,6 @@ class ProcessPayroll(Document):
return payroll_payable_account return payroll_payable_account
def make_accural_jv_entry(self): def make_accural_jv_entry(self):
self.check_permission('write') self.check_permission('write')
earnings = self.get_salary_component_total(component_type = "earnings") or {} earnings = self.get_salary_component_total(component_type = "earnings") or {}
@ -358,7 +352,6 @@ class ProcessPayroll(Document):
self.update(get_start_end_dates(self.payroll_frequency, self.update(get_start_end_dates(self.payroll_frequency,
self.start_date or self.posting_date, self.company)) self.start_date or self.posting_date, self.company))
@frappe.whitelist() @frappe.whitelist()
def get_start_end_dates(payroll_frequency, start_date=None, company=None): def get_start_end_dates(payroll_frequency, start_date=None, company=None):
'''Returns dict of start and end dates for given payroll frequency based on start_date''' '''Returns dict of start and end dates for given payroll frequency based on start_date'''
@ -391,6 +384,29 @@ def get_start_end_dates(payroll_frequency, start_date=None, company=None):
'start_date': start_date, 'end_date': end_date 'start_date': start_date, 'end_date': end_date
}) })
def get_frequency_kwargs(frequency_name):
frequency_dict = {
'monthly': {'months': 1},
'fortnightly': {'days': 14},
'weekly': {'days': 7},
'daily': {'days': 1}
}
return frequency_dict.get(frequency_name)
@frappe.whitelist()
def get_end_date(start_date, frequency):
start_date = getdate(start_date)
frequency = frequency.lower() if frequency else 'monthly'
kwargs = get_frequency_kwargs(frequency) if frequency != 'bimonthly' else get_frequency_kwargs('monthly')
# weekly, fortnightly and daily intervals have fixed days so no problems
end_date = add_to_date(start_date, **kwargs) - relativedelta(days=1)
if frequency != 'bimonthly':
return dict(end_date=end_date.strftime(DATE_FORMAT))
else:
return dict(end_date='')
def get_month_details(year, month): def get_month_details(year, month):
ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date") ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date")
if ysd: if ysd:

View File

@ -3,10 +3,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import frappe
import erpnext import erpnext
from frappe.utils import flt, add_months, cint, nowdate, getdate, add_days, random_string import frappe
from frappe.utils.make_random import get_random from frappe.utils import nowdate
from erpnext.hr.doctype.process_payroll.process_payroll import get_end_date
class TestProcessPayroll(unittest.TestCase): class TestProcessPayroll(unittest.TestCase):
def test_process_payroll(self): def test_process_payroll(self):
@ -31,6 +33,16 @@ class TestProcessPayroll(unittest.TestCase):
if process_payroll.get_sal_slip_list(ss_status = 1): if process_payroll.get_sal_slip_list(ss_status = 1):
r = process_payroll.make_payment_entry() r = process_payroll.make_payment_entry()
def test_get_end_date(self):
self.assertEqual(get_end_date('2017-01-01', 'monthly'), {'end_date': '2017-01-31'})
self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'})
self.assertEqual(get_end_date('2017-02-01', 'fortnightly'), {'end_date': '2017-02-14'})
self.assertEqual(get_end_date('2017-02-01', 'bimonthly'), {'end_date': ''})
self.assertEqual(get_end_date('2017-01-01', 'bimonthly'), {'end_date': ''})
self.assertEqual(get_end_date('2020-02-15', 'bimonthly'), {'end_date': ''})
self.assertEqual(get_end_date('2017-02-15', 'monthly'), {'end_date': '2017-03-14'})
self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'})
def get_salary_component_account(sal_comp): def get_salary_component_account(sal_comp):
company = erpnext.get_default_company() company = erpnext.get_default_company()

View File

@ -29,6 +29,27 @@ frappe.ui.form.on("Salary Slip", {
}) })
}, },
start_date: function(frm){
if(frm.doc.start_date){
frm.trigger("set_end_date");
}
},
set_end_date: function(frm){
frappe.call({
method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_end_date',
args: {
frequency: frm.doc.payroll_frequency,
start_date: frm.doc.start_date
},
callback: function (r) {
if (r.message) {
frm.set_value('end_date', r.message.end_date);
}
}
})
},
company: function(frm) { company: function(frm) {
var company = locals[':Company'][frm.doc.company]; var company = locals[':Company'][frm.doc.company];
if(!frm.doc.letter_head && company.default_letter_head) { if(!frm.doc.letter_head && company.default_letter_head) {
@ -45,11 +66,17 @@ frappe.ui.form.on("Salary Slip", {
}, },
salary_slip_based_on_timesheet: function(frm) { salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields") frm.trigger("toggle_fields");
frm.set_value('start_date', '');
}, },
payroll_frequency: function(frm) { payroll_frequency: function(frm) {
frm.trigger("toggle_fields") frm.trigger("toggle_fields");
frm.set_value('start_date', '');
},
employee: function(frm){
frm.set_value('start_date', '');
}, },
toggle_fields: function(frm) { toggle_fields: function(frm) {
@ -74,6 +101,7 @@ frappe.ui.form.on('Salary Detail', {
// Get leave details // Get leave details
//--------------------------------------------------------------------- //---------------------------------------------------------------------
cur_frm.cscript.start_date = function(doc, dt, dn){ cur_frm.cscript.start_date = function(doc, dt, dn){
if(!doc.start_date){
return frappe.call({ return frappe.call({
method: 'get_emp_and_leave_details', method: 'get_emp_and_leave_details',
doc: locals[dt][dn], doc: locals[dt][dn],
@ -82,10 +110,10 @@ cur_frm.cscript.start_date = function(doc, dt, dn){
calculate_all(doc, dt, dn); calculate_all(doc, dt, dn);
} }
}); });
}
} }
cur_frm.cscript.payroll_frequency = cur_frm.cscript.salary_slip_based_on_timesheet = cur_frm.cscript.start_date; cur_frm.cscript.payroll_frequency = cur_frm.cscript.salary_slip_based_on_timesheet = cur_frm.cscript.start_date;
cur_frm.cscript.end_date = cur_frm.cscript.start_date;
cur_frm.cscript.employee = function(doc,dt,dn){ cur_frm.cscript.employee = function(doc,dt,dn){
doc.salary_structure = '' doc.salary_structure = ''