diff --git a/erpnext/__init__.py b/erpnext/__init__.py index f7761ac851..a55d0e7562 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -4,7 +4,7 @@ import inspect import frappe from erpnext.hooks import regional_overrides -__version__ = '9.2.12' +__version__ = '9.2.13' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index f655830864..e6887baacc 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -200,9 +200,6 @@ def get_party_account(party_type, party, company): if (account and account_currency != existing_gle_currency) or not account: 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 def get_party_account_currency(party_type, party, company): diff --git a/erpnext/healthcare/doctype/consultation/consultation.py b/erpnext/healthcare/doctype/consultation/consultation.py index e16c22176c..69d7ecbd01 100755 --- a/erpnext/healthcare/doctype/consultation/consultation.py +++ b/erpnext/healthcare/doctype/consultation/consultation.py @@ -8,12 +8,12 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import getdate 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): def on_update(self): 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) def after_insert(self): @@ -23,9 +23,10 @@ class Consultation(Document): if not self.diagnosis or not self.symptoms: frappe.throw("Diagnosis and Complaints cannot be left blank") - physician = frappe.get_doc("Physician",self.physician) - if(frappe.session.user != physician.user_id): - frappe.throw(_("You don't have permission to submit")) + def on_cancel(self): + if(self.appointment): + frappe.db.set_value("Patient Appointment", self.appointment, "status", "Open") + delete_medical_record(self) def set_sales_invoice_fields(company, patient): sales_invoice = frappe.new_doc("Sales Invoice") @@ -91,8 +92,8 @@ def create_invoice_items(physician, invoice, company): item_line.qty = 1 item_line.uom = "Nos" item_line.conversion_factor = 1 - item_line.income_account = get_income_account(physician,company) - op_consulting_charge = frappe.get_value("Physician",physician,"op_consulting_charge") + item_line.income_account = get_income_account(physician, company) + op_consulting_charge = frappe.get_value("Physician", physician, "op_consulting_charge") if op_consulting_charge: item_line.rate = 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) 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]): 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): subject = "No Diagnosis " diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js index 75b0584f1f..8e98fee87e 100644 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js +++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js @@ -9,6 +9,7 @@ frappe.ui.form.on('Healthcare Settings', { filters: { 'account_type': 'Receivable', 'company': d.company, + 'is_group': 0 } }; }); @@ -18,6 +19,7 @@ frappe.ui.form.on('Healthcare Settings', { filters: { 'root_type': 'Income', 'company': d.company, + 'is_group': 0 } }; }); diff --git a/erpnext/healthcare/doctype/patient/patient_dashboard.py b/erpnext/healthcare/doctype/patient/patient_dashboard.py index cb98f0dcf4..f015b83038 100644 --- a/erpnext/healthcare/doctype/patient/patient_dashboard.py +++ b/erpnext/healthcare/doctype/patient/patient_dashboard.py @@ -11,8 +11,8 @@ def get_data(): 'items': ['Patient Appointment', 'Consultation'] }, { - 'label': _('Lab Tests'), - 'items': ['Lab Test'] + 'label': _('Lab Tests and Vital Signs'), + 'items': ['Lab Test', 'Vital Signs'] } ] } diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 1942b66f7b..2237ff5d1b 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -25,6 +25,14 @@ frappe.ui.form.on('Patient Appointment', { frm.add_custom_button(__('Cancel'), function() { 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){ frm.add_custom_button(__('Cancel'), function() { diff --git a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json b/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json index 9cbcf7b9b7..6edc0cc085 100644 --- a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json +++ b/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json @@ -56,7 +56,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Patient", "length": 0, @@ -174,7 +174,7 @@ "ignore_xss_filter": 1, "in_filter": 0, "in_global_search": 0, - "in_list_view": 1, + "in_list_view": 0, "in_standard_filter": 0, "label": "Subject", "length": 0, @@ -236,7 +236,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Datetime", "length": 0, @@ -266,7 +266,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Reference DocType", "length": 0, @@ -297,7 +297,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Reference Name", "length": 0, @@ -389,7 +389,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-10-04 16:09:55.597866", + "modified": "2017-11-15 12:48:59.945615", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Medical Record", @@ -425,6 +425,7 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", + "title_field": "patient", "track_changes": 1, "track_seen": 1 } \ No newline at end of file diff --git a/erpnext/healthcare/doctype/physician/physician.js b/erpnext/healthcare/doctype/physician/physician.js index 37389fe36a..c607f232fd 100755 --- a/erpnext/healthcare/doctype/physician/physician.js +++ b/erpnext/healthcare/doctype/physician/physician.js @@ -9,6 +9,7 @@ frappe.ui.form.on('Physician', { filters: { 'root_type': 'Income', 'company': d.company, + 'is_group': 0 } }; }); diff --git a/erpnext/healthcare/doctype/physician_schedule/physician_schedule.js b/erpnext/healthcare/doctype/physician_schedule/physician_schedule.js index e198d35fd3..74ba66f81a 100644 --- a/erpnext/healthcare/doctype/physician_schedule/physician_schedule.js +++ b/erpnext/healthcare/doctype/physician_schedule/physician_schedule.js @@ -33,7 +33,7 @@ frappe.ui.form.on('Physician Schedule', { while(cur_time < end_time) { let to_time = cur_time.clone().add(values.duration, 'minutes'); - if(to_time < end_time) { + if(to_time <= end_time) { // add a new timeslot frm.add_child('time_slots', { diff --git a/erpnext/hr/doctype/employee_loan/test_employee_loan.py b/erpnext/hr/doctype/employee_loan/test_employee_loan.py index 8671baae4f..c32e85ea1b 100644 --- a/erpnext/hr/doctype/employee_loan/test_employee_loan.py +++ b/erpnext/hr/doctype/employee_loan/test_employee_loan.py @@ -38,9 +38,8 @@ class TestEmployeeLoan(unittest.TestCase): self.assertEquals(employee_loan.total_interest_payable, 22712) self.assertEquals(employee_loan.total_payment, 302712) - 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({ "doctype": "Loan Type", "loan_name": loan_name, @@ -49,6 +48,7 @@ def create_loan_type(loan_name, maximum_loan_amount, rate_of_interest): }).insert() 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}): employee_loan = frappe.new_doc("Employee Loan") employee_loan.update({ diff --git a/erpnext/hr/doctype/process_payroll/process_payroll.py b/erpnext/hr/doctype/process_payroll/process_payroll.py index 0e329a7c19..f8ac044c80 100644 --- a/erpnext/hr/doctype/process_payroll/process_payroll.py +++ b/erpnext/hr/doctype/process_payroll/process_payroll.py @@ -190,23 +190,28 @@ class ProcessPayroll(Document): def format_as_links(self, salary_slip): return ['{0}'.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() - totals = frappe.db.sql(""" - select sum(principal_amount) as total_principal_amount, sum(interest_amount) as total_interest_amount, - sum(total_loan_repayment) as total_loan_repayment, sum(rounded_total) as rounded_total from `tabSalary Slip` t1 + return frappe.db.sql(""" select eld.employee_loan_account, + eld.interest_income_account, eld.principal_amount, eld.interest_amount, eld.total_payment + 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 """ % ('%s', '%s', cond), (self.start_date, self.end_date), as_dict=True) - return totals[0] - - 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] + return totals and totals[0] or None def get_salary_component_account(self, salary_component): 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 {} deductions = self.get_salary_component_total(component_type = "deductions") or {} default_payroll_payable_account = self.get_default_payroll_payable_account() - loan_amounts = self.get_total_salary_and_loan_amounts() - loan_accounts = self.get_loan_accounts() + loan_details = self.get_loan_details() jv_name = "" precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency") @@ -294,18 +298,18 @@ class ProcessPayroll(Document): }) # Employee loan - if loan_amounts.total_loan_repayment: + for data in loan_details: accounts.append({ - "account": loan_accounts.employee_loan_account, - "credit_in_account_currency": loan_amounts.total_principal_amount + "account": data.employee_loan_account, + "credit_in_account_currency": data.principal_amount }) accounts.append({ - "account": loan_accounts.interest_income_account, - "credit_in_account_currency": loan_amounts.total_interest_amount, + "account": data.interest_income_account, + "credit_in_account_currency": data.interest_amount, "cost_center": self.cost_center, "project": self.project }) - payable_amount -= flt(loan_amounts.total_loan_repayment, precision) + payable_amount -= flt(data.total_payment, precision) # Payable amount accounts.append({ @@ -327,11 +331,11 @@ class ProcessPayroll(Document): def make_payment_entry(self): 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() 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.voucher_type = 'Bank Entry' journal_entry.user_remark = _('Payment of salary from {0} to {1}')\ diff --git a/erpnext/hr/doctype/process_payroll/test_process_payroll.py b/erpnext/hr/doctype/process_payroll/test_process_payroll.py index cac43c4d47..91b60b4e68 100644 --- a/erpnext/hr/doctype/process_payroll/test_process_payroll.py +++ b/erpnext/hr/doctype/process_payroll/test_process_payroll.py @@ -6,9 +6,9 @@ import unittest import erpnext import frappe -from frappe.utils import nowdate -from erpnext.hr.doctype.process_payroll.process_payroll import get_end_date - +from dateutil.relativedelta import relativedelta +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): def test_process_payroll(self): @@ -19,22 +19,9 @@ class TestProcessPayroll(unittest.TestCase): if not frappe.db.get_value('Salary Component Account', {'parent': data.name, 'company': erpnext.get_default_company()}, '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"}): - process_payroll = frappe.get_doc("Process Payroll", "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() + make_process_payroll() def test_get_end_date(self): 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('2017-02-15', 'monthly'), {'end_date': '2017-03-14'}) 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): company = erpnext.get_default_company() @@ -63,4 +142,54 @@ def create_account(company): "parent_account": "Indirect Expenses - " + frappe.db.get_value('Company', company, 'abbr'), "company": company }).insert() - return salary_account \ No newline at end of file + 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 diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json index 6a52f2d7f6..6cc62eb058 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.json +++ b/erpnext/hr/doctype/salary_slip/salary_slip.json @@ -1293,7 +1293,68 @@ "bold": 0, "collapsible": 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", "hidden": 0, "ignore_user_permissions": 0, @@ -1302,7 +1363,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Principal Amount", + "label": "Total Principal Amount", "length": 0, "no_copy": 0, "options": "Company:company:default_currency", @@ -1324,7 +1385,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "interest_amount", + "default": "0", + "fieldname": "total_interest_amount", "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, @@ -1333,7 +1395,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Interest Amount", + "label": "Total Interest Amount", "length": 0, "no_copy": 0, "options": "Company:company:default_currency", @@ -1355,7 +1417,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "column_break_48", + "fieldname": "column_break_45", "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, @@ -1384,6 +1446,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "default": "0", "fieldname": "total_loan_repayment", "fieldtype": "Currency", "hidden": 0, @@ -1604,7 +1667,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-11-10 18:40:33.817074", + "modified": "2017-11-13 23:55:37.504856", "modified_by": "Administrator", "module": "HR", "name": "Salary Slip", diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 1f9c192e8d..ea5f35b04a 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -375,15 +375,34 @@ class SalarySlip(TransactionBase): self.precision("net_pay") if disable_rounded_total else 0) def set_loan_repayment(self): - employee_loan = frappe.db.sql("""select sum(principal_amount) as principal_amount, sum(interest_amount) as interest_amount, - sum(total_payment) as total_loan_repayment from `tabRepayment Schedule` - where payment_date between %s and %s and parent in (select name from `tabEmployee Loan` - where employee = %s and repay_from_salary = 1 and docstatus = 1)""", - (self.start_date, self.end_date, self.employee), as_dict=True) - if employee_loan: - self.principal_amount = employee_loan[0].principal_amount - self.interest_amount = employee_loan[0].interest_amount - self.total_loan_repayment = employee_loan[0].total_loan_repayment + self.set('loans', []) + self.total_loan_repayment = 0 + self.total_interest_amount = 0 + self.total_principal_amount = 0 + + for loan in self.get_employee_loan_details(): + self.append('loans', { + 'employee_loan': loan.name, + '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): if self.net_pay < 0: diff --git a/erpnext/hr/doctype/salary_slip_loan/__init__.py b/erpnext/hr/doctype/salary_slip_loan/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/salary_slip_loan/salary_slip_loan.json b/erpnext/hr/doctype/salary_slip_loan/salary_slip_loan.json new file mode 100644 index 0000000000..445c2f430e --- /dev/null +++ b/erpnext/hr/doctype/salary_slip_loan/salary_slip_loan.json @@ -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 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/salary_slip_loan/salary_slip_loan.py b/erpnext/hr/doctype/salary_slip_loan/salary_slip_loan.py new file mode 100644 index 0000000000..83908ce627 --- /dev/null +++ b/erpnext/hr/doctype/salary_slip_loan/salary_slip_loan.py @@ -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 diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py index 6b1404c81c..2382a81aaf 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py @@ -17,11 +17,9 @@ class TestSalaryStructure(unittest.TestCase): def setUp(self): self.make_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_2@salary.com") - + def make_holiday_list(self): if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"): holiday_list = frappe.get_doc({ @@ -33,7 +31,7 @@ class TestSalaryStructure(unittest.TestCase): }).insert() holiday_list.get_weekly_off_dates() holiday_list.save() - + def test_amount_totals(self): sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee@salary.com"}) if not sal_slip: @@ -64,7 +62,7 @@ class TestSalaryStructure(unittest.TestCase): for row in salary_structure.deductions: self.assertFalse(("\n" in row.formula) or ("\n" in row.condition)) - + def make_employee(user): if not frappe.db.get_value("User", user): frappe.get_doc({ @@ -74,7 +72,6 @@ def make_employee(user): "new_password": "password", "roles": [{"doctype": "Has Role", "role": "Employee"}] }).insert() - if not frappe.db.get_value("Employee", {"user_id": user}): emp = frappe.get_doc({ @@ -95,7 +92,7 @@ def make_employee(user): return emp.name else: return frappe.get_value("Employee", {"employee_name":user}, "name") - + def make_salary_slip_from_salary_structure(employee): sal_struct = make_salary_structure('Salary Structure Sample') 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.submit() 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): frappe.get_doc({ "doctype": "Salary Structure", "name": sal_struct, "company": erpnext.get_default_company(), - "employees": get_employee_details(), + "employees": employees or get_employee_details(), "earnings": get_earnings_component(), "deductions": get_deductions_component(), "payroll_frequency": "Monthly", "payment_account": frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") }).insert() - return sal_struct - - + return sal_struct + def get_employee_details(): return [{"employee": frappe.get_value("Employee", {"employee_name":"test_employee@salary.com"}, "name"), "base": 25000, @@ -136,8 +132,11 @@ def get_employee_details(): "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 [ { "salary_component": 'Basic Salary', @@ -167,7 +166,7 @@ def get_earnings_component(): "idx": 4 }, ] - + def get_deductions_component(): return [ { @@ -191,5 +190,4 @@ def get_deductions_component(): "formula": 'base*.1', "idx": 3 } - ] - \ No newline at end of file + ] diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ca8b0ecb54..1ae99a3d3a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -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.remove_non_existing_warehouse_from_stock_settings execute:frappe.delete_doc_if_exists("DocType", "Program Fee") -erpnext.patches.v9_2.delete_healthcare_domain_default_items \ No newline at end of file +erpnext.patches.v9_0.update_employee_loan_details +erpnext.patches.v9_2.delete_healthcare_domain_default_items diff --git a/erpnext/patches/v9_0/update_employee_loan_details.py b/erpnext/patches/v9_0/update_employee_loan_details.py new file mode 100644 index 0000000000..86690fc1f8 --- /dev/null +++ b/erpnext/patches/v9_0/update_employee_loan_details.py @@ -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() diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 6a6af3d583..6790176ede 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -157,7 +157,7 @@ class Item(WebsiteGenerator): def make_route(self): if not self.route: 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): """Validate if the website image is a public file"""