Merge branch 'master' into develop

This commit is contained in:
Nabin Hait 2017-11-15 14:08:17 +05:30
commit 6fd163bc55
21 changed files with 617 additions and 100 deletions

View File

@ -4,7 +4,7 @@ import inspect
import frappe import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
__version__ = '9.2.12' __version__ = '9.2.13'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -200,9 +200,6 @@ def get_party_account(party_type, party, company):
if (account and account_currency != existing_gle_currency) or not account: if (account and account_currency != existing_gle_currency) or not account:
account = get_party_gle_account(party_type, party, company) account = get_party_gle_account(party_type, party, company)
if not account:
frappe.throw(_("Party account not specified, please setup default party account in company"))
return account return account
def get_party_account_currency(party_type, party, company): def get_party_account_currency(party_type, party, company):

View File

@ -8,12 +8,12 @@ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import getdate from frappe.utils import getdate
import json import json
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
class Consultation(Document): class Consultation(Document):
def on_update(self): def on_update(self):
if(self.appointment): if(self.appointment):
frappe.db.set_value("Patient Appointment",self.appointment,"status","Closed") frappe.db.set_value("Patient Appointment", self.appointment, "status", "Closed")
update_consultation_to_medical_record(self) update_consultation_to_medical_record(self)
def after_insert(self): def after_insert(self):
@ -23,9 +23,10 @@ class Consultation(Document):
if not self.diagnosis or not self.symptoms: if not self.diagnosis or not self.symptoms:
frappe.throw("Diagnosis and Complaints cannot be left blank") frappe.throw("Diagnosis and Complaints cannot be left blank")
physician = frappe.get_doc("Physician",self.physician) def on_cancel(self):
if(frappe.session.user != physician.user_id): if(self.appointment):
frappe.throw(_("You don't have permission to submit")) frappe.db.set_value("Patient Appointment", self.appointment, "status", "Open")
delete_medical_record(self)
def set_sales_invoice_fields(company, patient): def set_sales_invoice_fields(company, patient):
sales_invoice = frappe.new_doc("Sales Invoice") sales_invoice = frappe.new_doc("Sales Invoice")
@ -91,8 +92,8 @@ def create_invoice_items(physician, invoice, company):
item_line.qty = 1 item_line.qty = 1
item_line.uom = "Nos" item_line.uom = "Nos"
item_line.conversion_factor = 1 item_line.conversion_factor = 1
item_line.income_account = get_income_account(physician,company) item_line.income_account = get_income_account(physician, company)
op_consulting_charge = frappe.get_value("Physician",physician,"op_consulting_charge") op_consulting_charge = frappe.get_value("Physician", physician, "op_consulting_charge")
if op_consulting_charge: if op_consulting_charge:
item_line.rate = op_consulting_charge item_line.rate = op_consulting_charge
item_line.amount = op_consulting_charge item_line.amount = op_consulting_charge
@ -111,10 +112,13 @@ def insert_consultation_to_medical_record(doc):
medical_record.save(ignore_permissions=True) medical_record.save(ignore_permissions=True)
def update_consultation_to_medical_record(consultation): def update_consultation_to_medical_record(consultation):
medical_record_id = frappe.db.sql("select name from `tabPatient Medical Record` where reference_name=%s",(consultation.name)) medical_record_id = frappe.db.sql("select name from `tabPatient Medical Record` where reference_name=%s", (consultation.name))
if(medical_record_id[0][0]): if(medical_record_id[0][0]):
subject = set_subject_field(consultation) subject = set_subject_field(consultation)
frappe.db.set_value("Patient Medical Record",medical_record_id[0][0],"subject",subject) frappe.db.set_value("Patient Medical Record", medical_record_id[0][0], "subject", subject)
def delete_medical_record(consultation):
frappe.db.sql("""delete from `tabPatient Medical Record` where reference_name = %s""", (consultation.name))
def set_subject_field(consultation): def set_subject_field(consultation):
subject = "No Diagnosis " subject = "No Diagnosis "

View File

@ -9,6 +9,7 @@ frappe.ui.form.on('Healthcare Settings', {
filters: { filters: {
'account_type': 'Receivable', 'account_type': 'Receivable',
'company': d.company, 'company': d.company,
'is_group': 0
} }
}; };
}); });
@ -18,6 +19,7 @@ frappe.ui.form.on('Healthcare Settings', {
filters: { filters: {
'root_type': 'Income', 'root_type': 'Income',
'company': d.company, 'company': d.company,
'is_group': 0
} }
}; };
}); });

View File

@ -11,8 +11,8 @@ def get_data():
'items': ['Patient Appointment', 'Consultation'] 'items': ['Patient Appointment', 'Consultation']
}, },
{ {
'label': _('Lab Tests'), 'label': _('Lab Tests and Vital Signs'),
'items': ['Lab Test'] 'items': ['Lab Test', 'Vital Signs']
} }
] ]
} }

View File

@ -25,6 +25,14 @@ frappe.ui.form.on('Patient Appointment', {
frm.add_custom_button(__('Cancel'), function() { frm.add_custom_button(__('Cancel'), function() {
btn_update_status(frm, "Cancelled"); btn_update_status(frm, "Cancelled");
}); });
frm.add_custom_button(__("Consultation"),function(){
btn_create_consultation(frm);
},"Create");
frm.add_custom_button(__('Vital Signs'), function() {
btn_create_vital_signs(frm);
},"Create");
} }
if(frm.doc.status == "Scheduled" && !frm.doc.__islocal){ if(frm.doc.status == "Scheduled" && !frm.doc.__islocal){
frm.add_custom_button(__('Cancel'), function() { frm.add_custom_button(__('Cancel'), function() {

View File

@ -56,7 +56,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Patient", "label": "Patient",
"length": 0, "length": 0,
@ -174,7 +174,7 @@
"ignore_xss_filter": 1, "ignore_xss_filter": 1,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 1, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Subject", "label": "Subject",
"length": 0, "length": 0,
@ -236,7 +236,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Datetime", "label": "Datetime",
"length": 0, "length": 0,
@ -266,7 +266,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Reference DocType", "label": "Reference DocType",
"length": 0, "length": 0,
@ -297,7 +297,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Reference Name", "label": "Reference Name",
"length": 0, "length": 0,
@ -389,7 +389,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-10-04 16:09:55.597866", "modified": "2017-11-15 12:48:59.945615",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Patient Medical Record", "name": "Patient Medical Record",
@ -425,6 +425,7 @@
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "patient",
"track_changes": 1, "track_changes": 1,
"track_seen": 1 "track_seen": 1
} }

View File

@ -9,6 +9,7 @@ frappe.ui.form.on('Physician', {
filters: { filters: {
'root_type': 'Income', 'root_type': 'Income',
'company': d.company, 'company': d.company,
'is_group': 0
} }
}; };
}); });

View File

@ -33,7 +33,7 @@ frappe.ui.form.on('Physician Schedule', {
while(cur_time < end_time) { while(cur_time < end_time) {
let to_time = cur_time.clone().add(values.duration, 'minutes'); let to_time = cur_time.clone().add(values.duration, 'minutes');
if(to_time < end_time) { if(to_time <= end_time) {
// add a new timeslot // add a new timeslot
frm.add_child('time_slots', { frm.add_child('time_slots', {

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

@ -460,4 +460,5 @@ execute:frappe.delete_doc_if_exists("DocType", "Program Fee")
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()

View File

@ -158,7 +158,7 @@ class Item(WebsiteGenerator):
def make_route(self): def make_route(self):
if not self.route: if not self.route:
return cstr(frappe.db.get_value('Item Group', self.item_group, return cstr(frappe.db.get_value('Item Group', self.item_group,
'route')) + '/' + self.scrub(self.item_name + '-' + random_string(5)) 'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
def validate_website_image(self): def validate_website_image(self):
"""Validate if the website image is a public file""" """Validate if the website image is a public file"""