Employee loan fixes (#11495)

* Employee loan fixes

* Update update_employee_loan_details.py
This commit is contained in:
rohitwaghchaure 2017-11-14 17:59:20 +05:30 committed by Nabin Hait
parent 3f83afe4e1
commit 43edd5d03c
11 changed files with 581 additions and 77 deletions

View File

@ -38,9 +38,8 @@ class TestEmployeeLoan(unittest.TestCase):
self.assertEquals(employee_loan.total_interest_payable, 22712) self.assertEquals(employee_loan.total_interest_payable, 22712)
self.assertEquals(employee_loan.total_payment, 302712) self.assertEquals(employee_loan.total_payment, 302712)
def create_loan_type(loan_name, maximum_loan_amount, rate_of_interest): def create_loan_type(loan_name, maximum_loan_amount, rate_of_interest):
if not frappe.db.get_value("Loan Type", loan_name): if not frappe.db.exists("Loan Type", loan_name):
frappe.get_doc({ frappe.get_doc({
"doctype": "Loan Type", "doctype": "Loan Type",
"loan_name": loan_name, "loan_name": loan_name,
@ -49,6 +48,7 @@ def create_loan_type(loan_name, maximum_loan_amount, rate_of_interest):
}).insert() }).insert()
def create_employee_loan(employee, loan_type, loan_amount, repayment_method, repayment_periods): def create_employee_loan(employee, loan_type, loan_amount, repayment_method, repayment_periods):
create_loan_type(loan_type, 500000, 8.4)
if not frappe.db.get_value("Employee Loan", {"employee":employee}): if not frappe.db.get_value("Employee Loan", {"employee":employee}):
employee_loan = frappe.new_doc("Employee Loan") employee_loan = frappe.new_doc("Employee Loan")
employee_loan.update({ employee_loan.update({

View File

@ -190,23 +190,28 @@ 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_loan_details(self):
""" """
Get total loan principal, loan interest and salary amount from submitted salary slip based on selected criteria Get loan details from submitted salary slip based on selected criteria
""" """
cond = self.get_filter_condition() cond = self.get_filter_condition()
totals = frappe.db.sql(""" return frappe.db.sql(""" select eld.employee_loan_account,
select sum(principal_amount) as total_principal_amount, sum(interest_amount) as total_interest_amount, eld.interest_income_account, eld.principal_amount, eld.interest_amount, eld.total_payment
sum(total_loan_repayment) as total_loan_repayment, sum(rounded_total) as rounded_total from `tabSalary Slip` t1 from
`tabSalary Slip` t1, `tabSalary Slip Loan` eld
where
t1.docstatus = 1 and t1.name = eld.parent and start_date >= %s and end_date <= %s %s
""" % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True) or []
def get_total_salary_amount(self):
"""
Get total salary amount from submitted salary slip based on selected criteria
"""
cond = self.get_filter_condition()
totals = frappe.db.sql(""" select sum(rounded_total) as rounded_total from `tabSalary Slip` t1
where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s
""" % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True) """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True)
return totals[0] return totals and totals[0] or None
def get_loan_accounts(self):
loan_accounts = frappe.get_all("Employee Loan", fields=["employee_loan_account", "interest_income_account"],
filters = {"company": self.company, "docstatus":1})
if loan_accounts:
return loan_accounts[0]
def get_salary_component_account(self, salary_component): def get_salary_component_account(self, salary_component):
account = frappe.db.get_value("Salary Component Account", account = frappe.db.get_value("Salary Component Account",
@ -257,8 +262,7 @@ class ProcessPayroll(Document):
earnings = self.get_salary_component_total(component_type = "earnings") or {} earnings = self.get_salary_component_total(component_type = "earnings") or {}
deductions = self.get_salary_component_total(component_type = "deductions") or {} deductions = self.get_salary_component_total(component_type = "deductions") or {}
default_payroll_payable_account = self.get_default_payroll_payable_account() default_payroll_payable_account = self.get_default_payroll_payable_account()
loan_amounts = self.get_total_salary_and_loan_amounts() loan_details = self.get_loan_details()
loan_accounts = self.get_loan_accounts()
jv_name = "" jv_name = ""
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
@ -294,18 +298,18 @@ class ProcessPayroll(Document):
}) })
# Employee loan # Employee loan
if loan_amounts.total_loan_repayment: for data in loan_details:
accounts.append({ accounts.append({
"account": loan_accounts.employee_loan_account, "account": data.employee_loan_account,
"credit_in_account_currency": loan_amounts.total_principal_amount "credit_in_account_currency": data.principal_amount
}) })
accounts.append({ accounts.append({
"account": loan_accounts.interest_income_account, "account": data.interest_income_account,
"credit_in_account_currency": loan_amounts.total_interest_amount, "credit_in_account_currency": data.interest_amount,
"cost_center": self.cost_center, "cost_center": self.cost_center,
"project": self.project "project": self.project
}) })
payable_amount -= flt(loan_amounts.total_loan_repayment, precision) payable_amount -= flt(data.total_payment, precision)
# Payable amount # Payable amount
accounts.append({ accounts.append({
@ -327,11 +331,11 @@ class ProcessPayroll(Document):
def make_payment_entry(self): def make_payment_entry(self):
self.check_permission('write') self.check_permission('write')
total_salary_amount = self.get_total_salary_and_loan_amounts() total_salary_amount = self.get_total_salary_amount()
default_payroll_payable_account = self.get_default_payroll_payable_account() default_payroll_payable_account = self.get_default_payroll_payable_account()
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
if total_salary_amount.rounded_total: if total_salary_amount and total_salary_amount.rounded_total:
journal_entry = frappe.new_doc('Journal Entry') journal_entry = frappe.new_doc('Journal Entry')
journal_entry.voucher_type = 'Bank Entry' journal_entry.voucher_type = 'Bank Entry'
journal_entry.user_remark = _('Payment of salary from {0} to {1}')\ journal_entry.user_remark = _('Payment of salary from {0} to {1}')\

View File

@ -6,9 +6,9 @@ import unittest
import erpnext import erpnext
import frappe import frappe
from frappe.utils import nowdate from dateutil.relativedelta import relativedelta
from erpnext.hr.doctype.process_payroll.process_payroll import get_end_date from erpnext.accounts.utils import get_fiscal_year, getdate, nowdate
from erpnext.hr.doctype.process_payroll.process_payroll import get_start_end_dates, get_end_date
class TestProcessPayroll(unittest.TestCase): class TestProcessPayroll(unittest.TestCase):
def test_process_payroll(self): def test_process_payroll(self):
@ -19,22 +19,9 @@ class TestProcessPayroll(unittest.TestCase):
if not frappe.db.get_value('Salary Component Account', if not frappe.db.get_value('Salary Component Account',
{'parent': data.name, 'company': erpnext.get_default_company()}, 'name'): {'parent': data.name, 'company': erpnext.get_default_company()}, 'name'):
get_salary_component_account(data.name) get_salary_component_account(data.name)
payment_account = frappe.get_value('Account',
{'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
if not frappe.db.get_value("Salary Slip", {"start_date": "2016-11-01", "end_date": "2016-11-30"}): if not frappe.db.get_value("Salary Slip", {"start_date": "2016-11-01", "end_date": "2016-11-30"}):
process_payroll = frappe.get_doc("Process Payroll", "Process Payroll") make_process_payroll()
process_payroll.company = erpnext.get_default_company()
process_payroll.start_date = "2016-11-01"
process_payroll.end_date = "2016-11-30"
process_payroll.payment_account = payment_account
process_payroll.posting_date = nowdate()
process_payroll.payroll_frequency = "Monthly"
process_payroll.create_salary_slips()
process_payroll.submit_salary_slips()
if process_payroll.get_sal_slip_list(ss_status = 1):
r = process_payroll.make_payment_entry()
def test_get_end_date(self): 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-01-01', 'monthly'), {'end_date': '2017-01-31'})
@ -45,7 +32,99 @@ class TestProcessPayroll(unittest.TestCase):
self.assertEqual(get_end_date('2020-02-15', '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', 'monthly'), {'end_date': '2017-03-14'})
self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'}) self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'})
def test_employee_loan(self):
from erpnext.hr.doctype.salary_structure.test_salary_structure import (make_employee,
make_salary_structure)
from erpnext.hr.doctype.employee_loan.test_employee_loan import create_employee_loan
branch = "Test Employee Branch"
employee = make_employee("test_employee@loan.com")
company = erpnext.get_default_company()
holiday_list = make_holiday("test holiday for loan")
if not frappe.db.exists('Salary Component', 'Basic Salary'):
frappe.get_doc({
'doctype': 'Salary Component',
'salary_component': 'Basic Salary',
'salary_component_abbr': 'BS',
'type': 'Earning',
'accounts': [{
'company': company,
'default_account': frappe.db.get_value('Account',
{'company': company, 'root_type': 'Expense', 'account_type': ''}, 'name')
}]
}).insert()
if not frappe.db.get_value('Salary Component Account',
{'parent': 'Basic Salary', 'company': company}):
salary_component = frappe.get_doc('Salary Component', 'Basic Salary')
salary_component.append('accounts', {
'company': company,
'default_account': 'Salary - WP'
})
company_doc = frappe.get_doc('Company', company)
if not company_doc.default_payroll_payable_account:
company_doc.default_payroll_payable_account = frappe.db.get_value('Account',
{'company': company, 'root_type': 'Liability', 'account_type': ''}, 'name')
company_doc.save()
if not frappe.db.exists('Branch', branch):
frappe.get_doc({
'doctype': 'Branch',
'branch': branch
}).insert()
employee_doc = frappe.get_doc('Employee', employee)
employee_doc.branch = branch
employee_doc.holiday_list = holiday_list
employee_doc.save()
employee_loan = create_employee_loan(employee,
"Personal Loan", 280000, "Repay Over Number of Periods", 20)
employee_loan.repay_from_salary = 1
employee_loan.submit()
salary_strcture = "Test Salary Structure for Loan"
if not frappe.db.exists('Salary Structure', salary_strcture):
salary_strcture = make_salary_structure(salary_strcture, [{
'employee': employee,
'from_date': '2017-01-01',
'base': 30000
}])
salary_strcture = frappe.get_doc('Salary Structure', salary_strcture)
salary_strcture.set('earnings', [{
'salary_component': 'Basic Salary',
'abbr': 'BS',
'amount_based_on_formula':1,
'formula': 'base*.5'
}])
salary_strcture.save()
dates = get_start_end_dates('Monthly', nowdate())
make_process_payroll(start_date=dates.start_date,
end_date=dates.end_date, branch=branch)
name = frappe.db.get_value('Salary Slip',
{'posting_date': nowdate(), 'employee': employee}, 'name')
salary_slip = frappe.get_doc('Salary Slip', name)
for row in salary_slip.loans:
if row.employee_loan == employee_loan.name:
interest_amount = (280000 * 8.4)/(12*100)
principal_amount = employee_loan.monthly_repayment_amount - interest_amount
self.assertEqual(row.interest_amount, interest_amount)
self.assertEqual(row.principal_amount, principal_amount)
self.assertEqual(row.total_payment,
interest_amount + principal_amount)
if salary_slip.docstatus == 0:
frappe.delete_doc('Salary Slip', name)
employee_loan.cancel()
frappe.delete_doc('Employee Loan', employee_loan.name)
def get_salary_component_account(sal_comp): def get_salary_component_account(sal_comp):
company = erpnext.get_default_company() company = erpnext.get_default_company()
@ -63,4 +142,54 @@ def create_account(company):
"parent_account": "Indirect Expenses - " + frappe.db.get_value('Company', company, 'abbr'), "parent_account": "Indirect Expenses - " + frappe.db.get_value('Company', company, 'abbr'),
"company": company "company": company
}).insert() }).insert()
return salary_account return salary_account
def make_process_payroll(**args):
args = frappe._dict(args)
process_payroll = frappe.get_doc("Process Payroll", "Process Payroll")
process_payroll.company = erpnext.get_default_company()
process_payroll.start_date = args.start_date or "2016-11-01"
process_payroll.end_date = args.end_date or "2016-11-30"
process_payroll.payment_account = get_payment_account()
process_payroll.posting_date = nowdate()
process_payroll.payroll_frequency = "Monthly"
process_payroll.branch = args.branch or None
process_payroll.create_salary_slips()
process_payroll.submit_salary_slips()
if process_payroll.get_sal_slip_list(ss_status = 1):
r = process_payroll.make_payment_entry()
return process_payroll
def get_payment_account():
return frappe.get_value('Account',
{'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
def make_holiday(holiday_list_name):
if not frappe.db.exists('Holiday List', holiday_list_name):
current_fiscal_year = get_fiscal_year(nowdate(), as_dict=True)
dt = getdate(nowdate())
new_year = dt + relativedelta(month=01, day=01, year=dt.year)
republic_day = dt + relativedelta(month=01, day=26, year=dt.year)
test_holiday = dt + relativedelta(month=02, day=02, year=dt.year)
frappe.get_doc({
'doctype': 'Holiday List',
'from_date': current_fiscal_year.year_start_date,
'to_date': current_fiscal_year.year_end_date,
'holiday_list_name': holiday_list_name,
'holidays': [{
'holiday_date': new_year,
'description': 'New Year'
}, {
'holiday_date': republic_day,
'description': 'Republic Day'
}, {
'holiday_date': test_holiday,
'description': 'Test Holiday'
}]
}).insert()
return holiday_list_name

View File

@ -1293,7 +1293,68 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "principal_amount", "fieldname": "loans",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employee Loan",
"length": 0,
"no_copy": 0,
"options": "Salary Slip Loan",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_43",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "total_principal_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -1302,7 +1363,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Principal Amount", "label": "Total Principal Amount",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
@ -1324,7 +1385,8 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "interest_amount", "default": "0",
"fieldname": "total_interest_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -1333,7 +1395,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Interest Amount", "label": "Total Interest Amount",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
@ -1355,7 +1417,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "column_break_48", "fieldname": "column_break_45",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -1384,6 +1446,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "0",
"fieldname": "total_loan_repayment", "fieldname": "total_loan_repayment",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0, "hidden": 0,
@ -1604,7 +1667,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-11-10 18:40:33.817074", "modified": "2017-11-13 23:55:37.504856",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Slip", "name": "Salary Slip",

View File

@ -375,15 +375,34 @@ class SalarySlip(TransactionBase):
self.precision("net_pay") if disable_rounded_total else 0) self.precision("net_pay") if disable_rounded_total else 0)
def set_loan_repayment(self): def set_loan_repayment(self):
employee_loan = frappe.db.sql("""select sum(principal_amount) as principal_amount, sum(interest_amount) as interest_amount, self.set('loans', [])
sum(total_payment) as total_loan_repayment from `tabRepayment Schedule` self.total_loan_repayment = 0
where payment_date between %s and %s and parent in (select name from `tabEmployee Loan` self.total_interest_amount = 0
where employee = %s and repay_from_salary = 1 and docstatus = 1)""", self.total_principal_amount = 0
(self.start_date, self.end_date, self.employee), as_dict=True)
if employee_loan: for loan in self.get_employee_loan_details():
self.principal_amount = employee_loan[0].principal_amount self.append('loans', {
self.interest_amount = employee_loan[0].interest_amount 'employee_loan': loan.name,
self.total_loan_repayment = employee_loan[0].total_loan_repayment 'total_payment': loan.total_payment,
'interest_amount': loan.interest_amount,
'principal_amount': loan.principal_amount,
'employee_loan_account': loan.employee_loan_account,
'interest_income_account': loan.interest_income_account
})
self.total_loan_repayment += loan.total_payment
self.total_interest_amount += loan.interest_amount
self.total_principal_amount += loan.principal_amount
def get_employee_loan_details(self):
return frappe.db.sql("""select rps.principal_amount, rps.interest_amount, el.name,
rps.total_payment, el.employee_loan_account, el.interest_income_account
from
`tabRepayment Schedule` as rps, `tabEmployee Loan` as el
where
el.name = rps.parent and rps.payment_date between %s and %s and
el.repay_from_salary = 1 and el.docstatus = 1 and el.employee = %s""",
(self.start_date, self.end_date, self.employee), as_dict=True) or []
def on_submit(self): def on_submit(self):
if self.net_pay < 0: if self.net_pay < 0:

View File

@ -0,0 +1,256 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-11-08 12:51:12.834479",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee_loan",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee Loan",
"length": 0,
"no_copy": 0,
"options": "Employee Loan",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee_loan_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee Loan Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "interest_income_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Interest Income Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "principal_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Principal Amount",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "interest_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Interest Amount",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_payment",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Total Payment",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-11-13 23:59:47.237689",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Slip Loan",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class SalarySlipLoan(Document):
pass

View File

@ -17,11 +17,9 @@ class TestSalaryStructure(unittest.TestCase):
def setUp(self): def setUp(self):
self.make_holiday_list() self.make_holiday_list()
frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Structure Test Holiday List") frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Structure Test Holiday List")
make_earning_salary_component(["Basic Salary", "Special Allowance", "HRA"])
make_deduction_salary_component(["Professional Tax", "TDS"])
make_employee("test_employee@salary.com") make_employee("test_employee@salary.com")
make_employee("test_employee_2@salary.com") make_employee("test_employee_2@salary.com")
def make_holiday_list(self): def make_holiday_list(self):
if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"): if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"):
holiday_list = frappe.get_doc({ holiday_list = frappe.get_doc({
@ -33,7 +31,7 @@ class TestSalaryStructure(unittest.TestCase):
}).insert() }).insert()
holiday_list.get_weekly_off_dates() holiday_list.get_weekly_off_dates()
holiday_list.save() holiday_list.save()
def test_amount_totals(self): def test_amount_totals(self):
sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee@salary.com"}) sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee@salary.com"})
if not sal_slip: if not sal_slip:
@ -64,7 +62,7 @@ class TestSalaryStructure(unittest.TestCase):
for row in salary_structure.deductions: for row in salary_structure.deductions:
self.assertFalse(("\n" in row.formula) or ("\n" in row.condition)) self.assertFalse(("\n" in row.formula) or ("\n" in row.condition))
def make_employee(user): def make_employee(user):
if not frappe.db.get_value("User", user): if not frappe.db.get_value("User", user):
frappe.get_doc({ frappe.get_doc({
@ -74,7 +72,6 @@ def make_employee(user):
"new_password": "password", "new_password": "password",
"roles": [{"doctype": "Has Role", "role": "Employee"}] "roles": [{"doctype": "Has Role", "role": "Employee"}]
}).insert() }).insert()
if not frappe.db.get_value("Employee", {"user_id": user}): if not frappe.db.get_value("Employee", {"user_id": user}):
emp = frappe.get_doc({ emp = frappe.get_doc({
@ -95,7 +92,7 @@ def make_employee(user):
return emp.name return emp.name
else: else:
return frappe.get_value("Employee", {"employee_name":user}, "name") return frappe.get_value("Employee", {"employee_name":user}, "name")
def make_salary_slip_from_salary_structure(employee): def make_salary_slip_from_salary_structure(employee):
sal_struct = make_salary_structure('Salary Structure Sample') sal_struct = make_salary_structure('Salary Structure Sample')
sal_slip = make_salary_slip(sal_struct, employee = employee) sal_slip = make_salary_slip(sal_struct, employee = employee)
@ -106,22 +103,21 @@ def make_salary_slip_from_salary_structure(employee):
sal_slip.insert() sal_slip.insert()
sal_slip.submit() sal_slip.submit()
return sal_slip return sal_slip
def make_salary_structure(sal_struct): def make_salary_structure(sal_struct, employees=None):
if not frappe.db.exists('Salary Structure', sal_struct): if not frappe.db.exists('Salary Structure', sal_struct):
frappe.get_doc({ frappe.get_doc({
"doctype": "Salary Structure", "doctype": "Salary Structure",
"name": sal_struct, "name": sal_struct,
"company": erpnext.get_default_company(), "company": erpnext.get_default_company(),
"employees": get_employee_details(), "employees": employees or get_employee_details(),
"earnings": get_earnings_component(), "earnings": get_earnings_component(),
"deductions": get_deductions_component(), "deductions": get_deductions_component(),
"payroll_frequency": "Monthly", "payroll_frequency": "Monthly",
"payment_account": frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") "payment_account": frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
}).insert() }).insert()
return sal_struct return sal_struct
def get_employee_details(): def get_employee_details():
return [{"employee": frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), return [{"employee": frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"),
"base": 25000, "base": 25000,
@ -136,8 +132,11 @@ def get_employee_details():
"idx": 2 "idx": 2
} }
] ]
def get_earnings_component(): def get_earnings_component():
make_earning_salary_component(["Basic Salary", "Special Allowance", "HRA"])
make_deduction_salary_component(["Professional Tax", "TDS"])
return [ return [
{ {
"salary_component": 'Basic Salary', "salary_component": 'Basic Salary',
@ -167,7 +166,7 @@ def get_earnings_component():
"idx": 4 "idx": 4
}, },
] ]
def get_deductions_component(): def get_deductions_component():
return [ return [
{ {
@ -191,5 +190,4 @@ def get_deductions_component():
"formula": 'base*.1', "formula": 'base*.1',
"idx": 3 "idx": 3
} }
] ]

View File

@ -458,4 +458,5 @@ erpnext.patches.v9_0.copy_old_fees_field_data
erpnext.patches.v9_0.set_pos_profile_name erpnext.patches.v9_0.set_pos_profile_name
erpnext.patches.v9_0.remove_non_existing_warehouse_from_stock_settings erpnext.patches.v9_0.remove_non_existing_warehouse_from_stock_settings
execute:frappe.delete_doc_if_exists("DocType", "Program Fee") execute:frappe.delete_doc_if_exists("DocType", "Program Fee")
erpnext.patches.v9_2.delete_healthcare_domain_default_items erpnext.patches.v9_0.update_employee_loan_details
erpnext.patches.v9_2.delete_healthcare_domain_default_items

View File

@ -0,0 +1,24 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc('hr', 'doctype', 'salary_slip_loan')
frappe.reload_doc('hr', 'doctype', 'salary_slip')
for data in frappe.db.sql(""" select name,
start_date, end_date, total_loan_repayment
from
`tabSalary Slip`
where
docstatus < 2 and ifnull(total_loan_repayment, 0) > 0""", as_dict=1):
salary_slip = frappe.get_doc('Salary Slip', data.name)
salary_slip.set_loan_repayment()
if salary_slip.total_loan_repayment == data.total_loan_repayment:
for row in salary_slip.loans:
row.db_update()
salary_slip.db_update()