diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 471da9e8f05..05a3c11d91b 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -337,13 +337,18 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup def validate_party_frozen_disabled(party_type, party_name): if party_type and party_name: - party = frappe.db.get_value(party_type, party_name, ["is_frozen", "disabled"], as_dict=True) - if party.disabled: - frappe.throw(_("{0} {1} is disabled").format(party_type, party_name), PartyDisabled) - elif party.is_frozen: - frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') - if not frozen_accounts_modifier in frappe.get_roles(): - frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen) + if party_type in ("Customer", "Supplier"): + party = frappe.db.get_value(party_type, party_name, ["is_frozen", "disabled"], as_dict=True) + if party.disabled: + frappe.throw(_("{0} {1} is disabled").format(party_type, party_name), PartyDisabled) + elif party.get("is_frozen"): + frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') + if not frozen_accounts_modifier in frappe.get_roles(): + frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen) + + elif party_type == "Employee": + if frappe.db.get_value("Employee", party_name, "status") == "Left": + frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), PartyDisabled, alert=True) def get_timeline_data(doctype, name): '''returns timeline data for the past one year''' diff --git a/erpnext/config/hr.py b/erpnext/config/hr.py index 3a18a274228..366d771bb05 100644 --- a/erpnext/config/hr.py +++ b/erpnext/config/hr.py @@ -265,6 +265,26 @@ def get_data(): ] }, + { + "label": _("Employee Loan Management"), + "icon": "icon-list", + "items": [ + { + "type": "doctype", + "name": "Loan Type", + "description": _("Define various loan types") + }, + { + "type": "doctype", + "name": "Employee Loan Application", + "description": _("Employee Loan Application") + }, + { + "type": "doctype", + "name": "Employee Loan" + }, + ] + }, { "label": _("Help"), "icon": "fa fa-facetime-video", diff --git a/erpnext/docs/assets/img/human-resources/employee-loan-application.png b/erpnext/docs/assets/img/human-resources/employee-loan-application.png new file mode 100644 index 00000000000..317de4f0937 Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/employee-loan-application.png differ diff --git a/erpnext/docs/assets/img/human-resources/employee-loan.png b/erpnext/docs/assets/img/human-resources/employee-loan.png new file mode 100644 index 00000000000..19f3181ec22 Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/employee-loan.png differ diff --git a/erpnext/docs/assets/img/human-resources/loan-repayment-salary-slip.png b/erpnext/docs/assets/img/human-resources/loan-repayment-salary-slip.png new file mode 100644 index 00000000000..4e5f3400836 Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/loan-repayment-salary-slip.png differ diff --git a/erpnext/docs/assets/img/human-resources/loan-type.png b/erpnext/docs/assets/img/human-resources/loan-type.png new file mode 100644 index 00000000000..9146b89daa4 Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/loan-type.png differ diff --git a/erpnext/docs/assets/img/human-resources/repayment-info.png b/erpnext/docs/assets/img/human-resources/repayment-info.png new file mode 100644 index 00000000000..5b8bdf76f63 Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/repayment-info.png differ diff --git a/erpnext/docs/assets/img/human-resources/repayment-schedule.png b/erpnext/docs/assets/img/human-resources/repayment-schedule.png new file mode 100644 index 00000000000..f2ddf02fab3 Binary files /dev/null and b/erpnext/docs/assets/img/human-resources/repayment-schedule.png differ diff --git a/erpnext/docs/user/manual/en/human-resources/employee-loan-management.md b/erpnext/docs/user/manual/en/human-resources/employee-loan-management.md new file mode 100644 index 00000000000..b76a2260c76 --- /dev/null +++ b/erpnext/docs/user/manual/en/human-resources/employee-loan-management.md @@ -0,0 +1,56 @@ +# Employee Loan Management +This module enables companies which provides employee loans to define and manage employee loans. +Employees can request loans, which are then reviewed and approved. For the approved loans, +repayment schedule for the entire loan cycle can be generated and automatic deduction from salary can also be set up. + +### Loan Type +To create a new Loan Type go to: + +> Human Resources > Employee Loan Management > Loan Type > New Loan Type + +Configure Loan limit and Rate of interest. + +Loan Type + +### Employee Loan Application + +Employee can apply for loan by going to: + +> Human Resources > Employee Loan Management > Employee Loan Application > New Employee Loan Application + +Employee Loan Application + +#### In the Employee Loan Application, + + * Enter Employee details and Loan details + * Select the repayment method, and based on your selection enter Repayment Period in Months or repayment Amount + +On save, Employee can see Repayment Information and make changes if required before submitting. + +Employee Loan Application + +### Employee Loan + +Once the Loan is approved, Manager can create Employee Loan record for the Employee. + +> Human Resources > Employee Loan Management > Employee Loan > New Employee Loan + +Employee Loan Application + +#### In the Employee Loan, + + * Enter Employee and Loan Application + * Check "Repay from Salary" if the loan repayment will be deducted from the salary + * Enter Disbursement Date and Account Info + * As soon as you hit save, the repayment schedule is generated. + +repayment Schedule + +#### Loan repayment deduction from Salary + +To auto deduct the Loan repayment from Salary, check "Repay from Salary" in Employee Loan. It will appear as Loan repayment in Salary Slip. + +Salary Slip + + + \ No newline at end of file diff --git a/erpnext/docs/user/manual/en/human-resources/index.txt b/erpnext/docs/user/manual/en/human-resources/index.txt index f91edfaa3e9..673efdb8386 100644 --- a/erpnext/docs/user/manual/en/human-resources/index.txt +++ b/erpnext/docs/user/manual/en/human-resources/index.txt @@ -15,4 +15,5 @@ holiday-list human-resource-setup daily-work-summary fleet-management +employee-loan-management articles diff --git a/erpnext/hr/doctype/employee_loan/__init__.py b/erpnext/hr/doctype/employee_loan/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/hr/doctype/employee_loan/employee_loan.js b/erpnext/hr/doctype/employee_loan/employee_loan.js new file mode 100644 index 00000000000..a03dbda4b15 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan/employee_loan.js @@ -0,0 +1,88 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Employee Loan', { + onload: function(frm) { + frm.set_query("employee_loan_application", function() { + return { + "filters": { + "employee": frm.doc.employee, + "docstatus": 1, + "status": "Approved" + } + }; + }); + + $.each(["payment_account", "employee_loan_account"], function(i, field) { + frm.set_query(field, function() { + return { + "filters": { + "company": frm.doc.company, + "root_type": "Asset", + "is_group": 0 + } + }; + }); + }) + }, + + mode_of_payment: function(frm){ + frappe.call({ + method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account", + args: { + "mode_of_payment": frm.doc.mode_of_payment, + "company": frm.doc.company + }, + callback: function(r, rt) { + if(r.message) { + frm.set_value("payment_account", r.message.account); + } + } + }); + }, + + refresh: function(frm) { + frm.trigger("toggle_fields"); + + if(frm.doc.docstatus==1) { + frm.add_custom_button(__('Ledger'), function() { + frappe.route_options = { + "voucher_no": frm.doc.name, + "from_date": frm.doc.posting_date, + "to_date": frm.doc.posting_date, + "company": frm.doc.company, + group_by_voucher: 0 + }; + frappe.set_route("query-report", "General Ledger"); + }, "fa fa-table"); + } + }, + + employee_loan_application: function(frm) { + return frm.call({ + method: "erpnext.hr.doctype.employee_loan.employee_loan.get_employee_loan_application", + args: { + "employee_loan_application": frm.doc.employee_loan_application + }, + callback: function(r){ + if(!r.exc && r.message) { + frm.set_value("loan_type", r.message.loan_type); + frm.set_value("loan_amount", r.message.loan_amount); + frm.set_value("repayment_method", r.message.repayment_method); + frm.set_value("monthly_repayment_amount", r.message.repayment_amount); + frm.set_value("repayment_periods", r.message.repayment_periods); + frm.set_value("rate_of_interest", r.message.rate_of_interest); + } + } + }) + }, + + repayment_method: function(frm) { + frm.trigger("toggle_fields") + }, + + toggle_fields: function(frm) { + frm.toggle_enable("monthly_repayment_amount", frm.doc.repayment_method=="Repay Fixed Amount per Period") + frm.toggle_enable("repayment_periods", frm.doc.repayment_method=="Repay Over Number of Periods") + } +}); diff --git a/erpnext/hr/doctype/employee_loan/employee_loan.json b/erpnext/hr/doctype/employee_loan/employee_loan.json new file mode 100644 index 00000000000..560515fde99 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan/employee_loan.json @@ -0,0 +1,892 @@ +{ + "allow_copy": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "ELN.####", + "beta": 0, + "creation": "2016-12-02 10:11:49.673604", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_name", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee Name", + "length": 0, + "no_copy": 0, + "options": "employee.employee_name", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_loan_application", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee Loan Application", + "length": 0, + "no_copy": 0, + "options": "Employee Loan Application", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "loan_type", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Loan Type", + "length": 0, + "no_copy": 0, + "options": "Loan Type", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "posting_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Posting Date", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 1, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Loan Unpaid\nLoan Paid\nEMI in progress\nLoan Repaid", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "repay_from_salary", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repay from Salary", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Details", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "loan_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Amount", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "rate_of_interest", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Rate of Interest (%) / Year", + "length": 0, + "no_copy": 0, + "options": "loan_type.rate_of_interest", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "disbursement_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Disbursement Date", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Repay Over Number of Periods", + "fieldname": "repayment_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Method", + "length": 0, + "no_copy": 0, + "options": "\nRepay Fixed Amount per Period\nRepay Over Number of Periods", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "", + "fieldname": "repayment_periods", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Period in Months", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "depends_on": "", + "fieldname": "monthly_repayment_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Amount", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "account_info", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Account Info", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Mode of Payment", + "length": 0, + "no_copy": 0, + "options": "Mode of Payment", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "payment_account", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payment Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_9", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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_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_list_view": 0, + "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": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_15", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Schedule", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "repayment_schedule", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Schedule", + "length": 0, + "no_copy": 1, + "options": "Repayment Schedule", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_17", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Totals", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "total_payment", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Payment", + "length": 0, + "no_copy": 0, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_19", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "total_interest_payable", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Interest Payable", + "length": 0, + "no_copy": 0, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Amended From", + "length": 0, + "no_copy": 1, + "options": "Employee Loan", + "permlevel": 0, + "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 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-01-17 07:13:10.704520", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Loan", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "posting_date", + "sort_field": "creation", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_loan/employee_loan.py b/erpnext/hr/doctype/employee_loan/employee_loan.py new file mode 100644 index 00000000000..4d7a9f38dfb --- /dev/null +++ b/erpnext/hr/doctype/employee_loan/employee_loan.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, math +import erpnext +from frappe import _ +from frappe.utils import flt, rounded, add_months, nowdate +from erpnext.controllers.accounts_controller import AccountsController +from erpnext.accounts.general_ledger import make_gl_entries + +class EmployeeLoan(AccountsController): + def validate(self): + check_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods) + if not self.company: + self.company = erpnext.get_default_company() + if not self.posting_date: + self.posting_date = nowdate() + if self.loan_type and not self.rate_of_interest: + self.rate_of_interest = frappe.db.get_value("Loan Type", self.loan_type, "rate_of_interest") + if self.repayment_method == "Repay Over Number of Periods": + self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods) + + self.make_repayment_schedule() + self.set_repayment_period() + self.calculate_totals() + + def on_submit(self): + self.make_gl_entries() + + def on_cancel(self): + self.make_gl_entries() + + def make_gl_entries(self): + gl_entries = [] + # Gl entries for employee loan account + gl_entries.append( + self.get_gl_dict({ + "account": self.employee_loan_account, + "party_type": "Employee", + "party": self.employee, + "debit": self.loan_amount, + "debit_in_account_currency": self.loan_amount + }) + ) + # Gl entries for payment account + gl_entries.append( + self.get_gl_dict({ + "account": self.payment_account, + "credit": self.loan_amount, + "credit_in_account_currency": self.loan_amount + }) + ) + make_gl_entries(gl_entries, cancel=(self.docstatus == 2)) + + def make_repayment_schedule(self): + self.repayment_schedule = [] + payment_date = self.disbursement_date + balance_amount = self.loan_amount + + while(balance_amount > 0): + interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100)) + principal_amount = self.monthly_repayment_amount - interest_amount + balance_amount = rounded(balance_amount + interest_amount - self.monthly_repayment_amount) + + if balance_amount < 0: + principal_amount += balance_amount + balance_amount = 0.0 + + total_payment = principal_amount + interest_amount + + self.append("repayment_schedule", { + "payment_date": payment_date, + "principal_amount": principal_amount, + "interest_amount": interest_amount, + "total_payment": total_payment, + "balance_loan_amount": balance_amount + }) + + next_payment_date = add_months(payment_date, 1) + payment_date = next_payment_date + + def set_repayment_period(self): + if self.repayment_method == "Repay Fixed Amount per Period": + repayment_periods = len(self.repayment_schedule) + + self.repayment_periods = repayment_periods + + def calculate_totals(self): + self.total_payment = 0 + self.total_interest_payable = 0 + for data in self.repayment_schedule: + self.total_payment += data.total_payment + self.total_interest_payable +=data.interest_amount + + def update_status(self): + if self.disbursement_date: + self.status = "Loan Paid" + if len(self.repayment_schedule)>0: + self.status = "repayment in progress" + +def check_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods): + if repayment_method == "Repay Over Number of Periods" and not repayment_periods: + frappe.throw(_("Please enter Repayment Periods")) + + if repayment_method == "Repay Fixed Amount per Period": + if not monthly_repayment_amount: + frappe.throw(_("Please enter repayment Amount")) + if monthly_repayment_amount > loan_amount: + frappe.throw(_("Monthly Repayment Amount cannot be greater than Loan Amount")) + +def get_monthly_repayment_amount(repayment_method, loan_amount, rate_of_interest, repayment_periods): + if repayment_method == "Repay Over Number of Periods": + if rate_of_interest: + monthly_interest_rate = flt(rate_of_interest) / (12 *100) + monthly_repayment_amount = math.ceil((loan_amount * monthly_interest_rate * + (1 + monthly_interest_rate)**repayment_periods) \ + / ((1 + monthly_interest_rate)**repayment_periods - 1)) + else: + monthly_repayment_amount = math.ceil(flt(loan_amount) / repayment_periods) + return monthly_repayment_amount + +@frappe.whitelist() +def get_employee_loan_application(employee_loan_application): + employee_loan = frappe.get_doc("Employee Loan Application", employee_loan_application) + if employee_loan: + return employee_loan \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_loan/test_employee_loan.py b/erpnext/hr/doctype/employee_loan/test_employee_loan.py new file mode 100644 index 00000000000..c1c6ba24a99 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan/test_employee_loan.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import erpnext +import unittest +from frappe.utils import nowdate +from erpnext.hr.doctype.salary_structure.test_salary_structure import make_employee + +class TestEmployeeLoan(unittest.TestCase): + def setUp(self): + create_loan_type("Personal Loan", 500000, 8.4) + self.employee = make_employee("robert_loan@loan.com") + create_employee_loan(self.employee, "Personal Loan", 280000, "Repay Over Number of Periods", 20) + + def test_employee_loan(self): + employee_loan = frappe.get_doc("Employee Loan", {"employee":self.employee}) + self.assertEquals(employee_loan.monthly_repayment_amount, 15052) + self.assertEquals(employee_loan.total_interest_payable, 21034) + self.assertEquals(employee_loan.total_payment, 301034) + + schedule = employee_loan.repayment_schedule + + self.assertEquals(len(schedule), 20) + + for idx, principal_amount, interest_amount, balance_loan_amount in [[3, 13369, 1683, 227079], [19, 14941, 105, 0], [17, 14740, 312, 29785]]: + self.assertEquals(schedule[idx].principal_amount, principal_amount) + self.assertEquals(schedule[idx].interest_amount, interest_amount) + self.assertEquals(schedule[idx].balance_loan_amount, balance_loan_amount) + + employee_loan.repayment_method = "Repay Fixed Amount per Period" + employee_loan.monthly_repayment_amount = 14000 + employee_loan.save() + + self.assertEquals(len(employee_loan.repayment_schedule), 22) + 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): + frappe.get_doc({ + "doctype": "Loan Type", + "loan_name": loan_name, + "maximum_loan_amount": maximum_loan_amount, + "rate_of_interest": rate_of_interest + }).insert() + +def create_employee_loan(employee, loan_type, loan_amount, repayment_method, repayment_periods): + if not frappe.db.get_value("Employee Loan", {"employee":employee}): + employee_loan = frappe.new_doc("Employee Loan") + employee_loan.update({ + "employee": employee, + "loan_type": loan_type, + "loan_amount": loan_amount, + "repayment_method": repayment_method, + "repayment_periods": repayment_periods, + "disbursement_date": nowdate(), + "mode_of_payment": frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'), + "payment_account": frappe.db.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name"), + "employee_loan_account": frappe.db.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") + }) + employee_loan.insert() + return employee_loan + else: + return frappe.get_doc("Employee Loan", {"employee":employee}) \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_loan_application/__init__.py b/erpnext/hr/doctype/employee_loan_application/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/hr/doctype/employee_loan_application/employee_loan_application.js b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.js new file mode 100644 index 00000000000..6958beabfa7 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.js @@ -0,0 +1,16 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Employee Loan Application', { + refresh: function(frm) { + frm.trigger("toggle_fields") + }, + repayment_method: function(frm) { + frm.trigger("toggle_fields") + }, + + toggle_fields: function(frm) { + frm.toggle_enable("repayment_amount", frm.doc.repayment_method=="Repay Fixed Amount per Period") + frm.toggle_enable("repayment_periods", frm.doc.repayment_method=="Repay Over Number of Periods") + } +}); diff --git a/erpnext/hr/doctype/employee_loan_application/employee_loan_application.json b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.json new file mode 100644 index 00000000000..e30dc5a0012 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.json @@ -0,0 +1,681 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "ELA/.#####", + "beta": 0, + "creation": "2016-12-02 12:35:56.046811", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Posting Date", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_name", + "fieldtype": "Read Only", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Employee Name", + "length": 0, + "no_copy": 0, + "options": "employee.employee_name", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "Open\nApproved\nRejected", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Info", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "loan_type", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Type", + "length": 0, + "no_copy": 0, + "options": "Loan Type", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "loan_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Amount", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "required_by_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Required by Date", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reason", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "repayment_info", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Info", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "repayment_method", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Method", + "length": 0, + "no_copy": 0, + "options": "\nRepay Fixed Amount per Period\nRepay Over Number of Periods", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "rate_of_interest", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Rate of Interest", + "length": 0, + "no_copy": 0, + "options": "loan_type.rate_of_interest", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_payable_interest", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Payable Interest", + "length": 0, + "no_copy": 0, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_11", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "repayment_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Amount", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "repayment_periods", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Repayment Period in Months", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "total_payable_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Total Payable Amount", + "length": 0, + "no_copy": 0, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Amended From", + "length": 0, + "no_copy": 1, + "options": "Employee Loan Application", + "permlevel": 0, + "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 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-01-09 12:02:36.562577", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Loan Application", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "employee, employee_name, loan_type, loan_amount", + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "employee", + "title_field": "employee_name", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py new file mode 100644 index 00000000000..15dbd4e5e40 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, math +from frappe import _ +from frappe.utils import flt +from frappe.model.document import Document + +from erpnext.hr.doctype.employee_loan.employee_loan import get_monthly_repayment_amount, check_repayment_method + +class EmployeeLoanApplication(Document): + def validate(self): + check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods) + self.validate_loan_amount() + self.get_repayment_details() + + def validate_loan_amount(self): + maximum_loan_limit = frappe.db.get_value('Loan Type', self.loan_type, 'maximum_loan_amount') + if self.loan_amount > maximum_loan_limit: + frappe.throw(_("Loan Amount cannot exceed Maximum Loan Amount of {0}").format(maximum_loan_limit)) + + def get_repayment_details(self): + if self.repayment_method == "Repay Over Number of Periods": + self.repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods) + + if self.repayment_method == "Repay Fixed Amount per Period": + monthly_interest_rate = flt(self.rate_of_interest) / (12 *100) + self.repayment_periods = math.ceil((math.log(self.repayment_amount) - math.log(self.repayment_amount - \ + (self.loan_amount*monthly_interest_rate)))/(math.log(1+monthly_interest_rate))) + + self.total_payable_amount = self.repayment_amount * self.repayment_periods + self.total_payable_interest = self.total_payable_amount - self.loan_amount \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.py b/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.py new file mode 100644 index 00000000000..1d157d6d810 --- /dev/null +++ b/erpnext/hr/doctype/employee_loan_application/test_employee_loan_application.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from erpnext.hr.doctype.salary_structure.test_salary_structure import make_employee + +class TestEmployeeLoanApplication(unittest.TestCase): + def setUp(self): + self.create_loan_type() + self.employee = make_employee("kate_loan@loan.com") + self.create_loan_application() + + def create_loan_type(self): + if not frappe.db.get_value("Loan Type", "Home Loan"): + frappe.get_doc({ + "doctype": "Loan Type", + "loan_name": "Home Loan", + "maximum_loan_amount": 500000, + "rate_of_interest": 9.2 + }).insert() + + def create_loan_application(self): + if not frappe.db.get_value("Employee Loan Application", {"employee":self.employee}, "name"): + loan_application = frappe.new_doc("Employee Loan Application") + loan_application.update({ + "employee": self.employee, + "loan_type": "Home Loan", + "rate_of_interest": 9.2, + "loan_amount": 250000, + "repayment_method": "Repay Over Number of Periods", + "repayment_periods": 24 + }) + loan_application.insert() + + + def test_loan_totals(self): + loan_application = frappe.get_doc("Employee Loan Application", {"employee":self.employee}) + self.assertEquals(loan_application.repayment_amount, 11445) + self.assertEquals(loan_application.total_payable_interest, 24680) + self.assertEquals(loan_application.total_payable_amount, 274680) + + loan_application.repayment_method = "Repay Fixed Amount per Period" + loan_application.repayment_amount = 15000 + loan_application.save() + + self.assertEquals(loan_application.repayment_periods, 18) + self.assertEquals(loan_application.total_payable_interest, 20000) + self.assertEquals(loan_application.total_payable_amount, 270000) \ No newline at end of file diff --git a/erpnext/hr/doctype/loan_type/__init__.py b/erpnext/hr/doctype/loan_type/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/hr/doctype/loan_type/loan_type.js b/erpnext/hr/doctype/loan_type/loan_type.js new file mode 100644 index 00000000000..72f5775adde --- /dev/null +++ b/erpnext/hr/doctype/loan_type/loan_type.js @@ -0,0 +1,7 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Loan Type', { + refresh: function(frm) { + } +}); diff --git a/erpnext/hr/doctype/loan_type/loan_type.json b/erpnext/hr/doctype/loan_type/loan_type.json new file mode 100644 index 00000000000..f9441ea1a28 --- /dev/null +++ b/erpnext/hr/doctype/loan_type/loan_type.json @@ -0,0 +1,230 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:loan_name", + "beta": 0, + "creation": "2016-12-02 10:41:40.732843", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "loan_name", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Name", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "maximum_loan_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Maximum Loan Amount", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "rate_of_interest", + "fieldtype": "Percent", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Rate of Interest (%) Yearly", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "disabled", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Disabled", + "length": 0, + "no_copy": 0, + "options": "", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Description", + "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 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2016-12-29 15:54:17.716285", + "modified_by": "Administrator", + "module": "HR", + "name": "Loan Type", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "is_custom": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/loan_type/loan_type.py b/erpnext/hr/doctype/loan_type/loan_type.py new file mode 100644 index 00000000000..2714e206d8d --- /dev/null +++ b/erpnext/hr/doctype/loan_type/loan_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, 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 LoanType(Document): + pass diff --git a/erpnext/hr/doctype/loan_type/test_loan_type.py b/erpnext/hr/doctype/loan_type/test_loan_type.py new file mode 100644 index 00000000000..078e11e2627 --- /dev/null +++ b/erpnext/hr/doctype/loan_type/test_loan_type.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +# test_records = frappe.get_test_records('Loan Type') + +class TestLoanType(unittest.TestCase): + pass diff --git a/erpnext/hr/doctype/repayment_schedule/__init__.py b/erpnext/hr/doctype/repayment_schedule/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json new file mode 100644 index 00000000000..3a0dfd3d55a --- /dev/null +++ b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json @@ -0,0 +1,179 @@ +{ + "allow_copy": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2016-12-20 15:32:25.078334", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "payment_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Payment Date", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "principal_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Principal Amount", + "length": 0, + "no_copy": 1, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "interest_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Interest Amount", + "length": 0, + "no_copy": 1, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "total_payment", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Total Payment", + "length": 0, + "no_copy": 0, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 2, + "fieldname": "balance_loan_amount", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Balance Loan Amount", + "length": 0, + "no_copy": 1, + "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 + } + ], + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "in_dialog": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2017-01-09 12:00:10.818772", + "modified_by": "Administrator", + "module": "HR", + "name": "Repayment Schedule", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 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/repayment_schedule/repayment_schedule.py b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.py new file mode 100644 index 00000000000..8abee5e0894 --- /dev/null +++ b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, 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 RepaymentSchedule(Document): + pass diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.js b/erpnext/hr/doctype/salary_slip/salary_slip.js index 8b0dd1655c0..02622598551 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.js +++ b/erpnext/hr/doctype/salary_slip/salary_slip.js @@ -110,6 +110,7 @@ cur_frm.cscript.depends_on_lwp = function(doc,dt,dn){ calculate_earning_total(doc, dt, dn, true); calculate_ded_total(doc, dt, dn, true); calculate_net_pay(doc, dt, dn); + refresh_many(['amount','gross_pay', 'rounded_total', 'net_pay', 'loan_repayment']); }; // Calculate earning total diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.json b/erpnext/hr/doctype/salary_slip/salary_slip.json index e558d73bcfb..7d5dbe9c9aa 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.json +++ b/erpnext/hr/doctype/salary_slip/salary_slip.json @@ -1266,7 +1266,35 @@ "bold": 0, "collapsible": 0, "columns": 0, - "description": "Gross Pay + Arrear Amount +Encashment Amount - Total Deduction", + "fieldname": "loan_repayment", + "fieldtype": "Currency", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Loan Repayment", + "length": 0, + "no_copy": 0, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Gross Pay + Arrear Amount + Encashment Amount - Total Deduction - Loan Repayment", "fieldname": "net_pay", "fieldtype": "Currency", "hidden": 0, @@ -1362,7 +1390,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-12-14 08:26:31.400930", + "modified": "2017-01-09 12:37:03.802501", "modified_by": "Administrator", "module": "HR", "name": "Salary Slip", @@ -1413,7 +1441,7 @@ }, { "amend": 0, - "apply_user_permissions": 0, + "apply_user_permissions": 1, "cancel": 0, "create": 0, "delete": 0, @@ -1430,6 +1458,7 @@ "set_user_permissions": 0, "share": 0, "submit": 0, + "user_permission_doctypes": "[\"Employee\"]", "write": 0 } ], @@ -1440,5 +1469,6 @@ "sort_order": "DESC", "timeline_field": "employee", "title_field": "employee_name", + "track_changes": 0, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index eeec6e8e769..de205e3bdd2 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -329,11 +329,21 @@ class SalarySlip(TransactionBase): self.sum_components('earnings', 'gross_pay') self.sum_components('deductions', 'total_deduction') + + self.set_loan_repayment() - self.net_pay = flt(self.gross_pay) - flt(self.total_deduction) + self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.loan_repayment)) self.rounded_total = rounded(self.net_pay, self.precision("net_pay") if disable_rounded_total else 0) + def set_loan_repayment(self): + employee_loan = frappe.db.sql("""select sum(total_payment) as 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.loan_repayment = employee_loan[0].loan_repayment + def on_submit(self): if self.net_pay < 0: frappe.throw(_("Net Pay cannot be less than 0")) diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index 3d119c3e383..9999f1ec088 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -7,7 +7,7 @@ import frappe import erpnext import calendar from erpnext.accounts.utils import get_fiscal_year -from frappe.utils import getdate, nowdate, add_days +from frappe.utils import getdate, nowdate, add_days, flt from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.hr.doctype.process_payroll.test_process_payroll import get_salary_component_account from erpnext.hr.doctype.process_payroll.process_payroll import get_month_details @@ -24,7 +24,6 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List") - def tearDown(self): frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) frappe.set_user("Administrator") @@ -40,12 +39,12 @@ class TestSalarySlip(unittest.TestCase): self.assertEquals(ss.total_working_days, no_of_days[0]) self.assertEquals(ss.payment_days, no_of_days[0]) - self.assertEquals(ss.earnings[0].amount, 5000) + self.assertEquals(ss.earnings[0].amount, 25000) self.assertEquals(ss.earnings[1].amount, 3000) self.assertEquals(ss.deductions[0].amount, 5000) - self.assertEquals(ss.deductions[1].amount, 2500) - self.assertEquals(ss.gross_pay, 10500) - self.assertEquals(ss.net_pay, 3000) + self.assertEquals(ss.deductions[1].amount, 5000) + self.assertEquals(ss.gross_pay, 40500) + self.assertEquals(ss.net_pay, 29918) def test_salary_slip_with_holidays_excluded(self): no_of_days = self.get_no_of_days() @@ -58,13 +57,13 @@ class TestSalarySlip(unittest.TestCase): self.assertEquals(ss.total_working_days, no_of_days[0] - no_of_days[1]) self.assertEquals(ss.payment_days, no_of_days[0] - no_of_days[1]) - self.assertEquals(ss.earnings[0].amount, 5000) - self.assertEquals(ss.earnings[0].default_amount, 5000) + self.assertEquals(ss.earnings[0].amount, 25000) + self.assertEquals(ss.earnings[0].default_amount, 25000) self.assertEquals(ss.earnings[1].amount, 3000) self.assertEquals(ss.deductions[0].amount, 5000) - self.assertEquals(ss.deductions[1].amount, 2500) - self.assertEquals(ss.gross_pay, 10500) - self.assertEquals(ss.net_pay, 3000) + self.assertEquals(ss.deductions[1].amount, 5000) + self.assertEquals(ss.gross_pay, 40500) + self.assertEquals(ss.net_pay, 29918) def test_payment_days(self): no_of_days = self.get_no_of_days() @@ -130,9 +129,23 @@ class TestSalarySlip(unittest.TestCase): ss = frappe.get_doc("Salary Slip", self.make_employee_salary_slip("test_employee@salary.com", "Monthly")) ss.submit() + email_queue = frappe.db.sql("""select name from `tabEmail Queue`""") self.assertTrue(email_queue) + def test_loan_repayment_salary_slip(self): + from erpnext.hr.doctype.employee_loan.test_employee_loan import create_loan_type, create_employee_loan + employee = self.make_employee("test_employee@salary.com") + create_loan_type("Car Loan", 500000, 6.4) + employee_loan = create_employee_loan(employee, "Car Loan", 11000, "Repay Over Number of Periods", 20) + employee_loan.repay_from_salary = 1 + employee_loan.submit() + ss = frappe.get_doc("Salary Slip", + self.make_employee_salary_slip("test_employee@salary.com", "Monthly")) + ss.submit() + self.assertEquals(ss.loan_repayment, 582) + self.assertEquals(ss.net_pay, (flt(ss.gross_pay) - (flt(ss.total_deduction) + flt(ss.loan_repayment)))) + def test_payroll_frequency(self): fiscal_year = get_fiscal_year(nowdate(), company="_Test Company")[0] month = "%02d" % getdate(nowdate()).month @@ -167,7 +180,7 @@ class TestSalarySlip(unittest.TestCase): }).insert() if not frappe.db.get_value("Employee", {"user_id": user}): - frappe.get_doc({ + employee = frappe.get_doc({ "doctype": "Employee", "naming_series": "EMP-", "employee_name": user, @@ -183,6 +196,9 @@ class TestSalarySlip(unittest.TestCase): "status": "Active", "employment_type": "Intern" }).insert() + return employee.name + else: + return frappe.get_value("Employee", {"employee_name":user}, "name") def make_holiday_list(self): fiscal_year = get_fiscal_year(nowdate(), company="_Test Company") @@ -278,7 +294,7 @@ def make_salary_structure(sal_struct, payroll_frequency, employee): def get_employee_details(employee): return [{"employee": employee, - "base": 25000, + "base": 50000, "variable": 5000 } ] @@ -289,14 +305,14 @@ def get_earnings_component(): "salary_component": 'Basic Salary', "abbr":'BS', "condition": 'base > 10000', - "formula": 'base*.2', + "formula": 'base*.5', "idx": 1 }, { "salary_component": 'Basic Salary', "abbr":'BS', "condition": 'base < 10000', - "formula": 'base*.1', + "formula": 'base*.2', "idx": 2 }, { @@ -320,13 +336,13 @@ def get_deductions_component(): "salary_component": 'Professional Tax', "abbr":'PT', "condition": 'base > 10000', - "formula": 'base*.2', + "formula": 'base*.1', "idx": 1 }, { "salary_component": 'TDS', "abbr":'T', - "formula": 'base*.5', + "formula": 'base*.1', "idx": 2 }, { diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py index fe88d9afcfc..36e50d319af 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py @@ -19,8 +19,8 @@ class TestSalaryStructure(unittest.TestCase): frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Structure Test Holiday List") make_earning_salary_component(["Basic Salary", "Allowance", "HRA"]) make_deduction_salary_component(["Professional Tax", "TDS"]) - self.make_employee("test_employee@salary.com") - self.make_employee("test_employee_2@salary.com") + 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"): @@ -33,37 +33,6 @@ class TestSalaryStructure(unittest.TestCase): }).insert() holiday_list.get_weekly_off_dates() holiday_list.save() - - def make_employee(self, user): - if not frappe.db.get_value("User", user): - frappe.get_doc({ - "doctype": "User", - "email": user, - "first_name": user, - "new_password": "password", - "user_roles": [{"doctype": "UserRole", "role": "Employee"}] - }).insert() - - - if not frappe.db.get_value("Employee", {"user_id": user}): - emp = frappe.get_doc({ - "doctype": "Employee", - "naming_series": "EMP-", - "employee_name": user, - "company": erpnext.get_default_company(), - "user_id": user, - "date_of_birth": "1990-05-08", - "date_of_joining": "2013-01-01", - "relieving_date": "", - "department": frappe.get_all("Department", fields="name")[0].name, - "gender": "Female", - "company_email": user, - "status": "Active", - "employment_type": "Intern" - }).insert() - return emp.name - else: - return frappe.get_value("Employee", {"employee_name":user}, "name") def test_amount_totals(self): sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee@salary.com"}) @@ -76,6 +45,36 @@ class TestSalaryStructure(unittest.TestCase): self.assertEquals(sal_slip.get("total_deduction"), 7500) self.assertEquals(sal_slip.get("net_pay"), 7500) +def make_employee(user): + if not frappe.db.get_value("User", user): + frappe.get_doc({ + "doctype": "User", + "email": user, + "first_name": user, + "new_password": "password", + "user_roles": [{"doctype": "UserRole", "role": "Employee"}] + }).insert() + + + if not frappe.db.get_value("Employee", {"user_id": user}): + emp = frappe.get_doc({ + "doctype": "Employee", + "naming_series": "EMP-", + "employee_name": user, + "company": erpnext.get_default_company(), + "user_id": user, + "date_of_birth": "1990-05-08", + "date_of_joining": "2013-01-01", + "relieving_date": "", + "department": frappe.get_all("Department", fields="name")[0].name, + "gender": "Female", + "company_email": user, + "status": "Active", + "employment_type": "Intern" + }).insert() + 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') diff --git a/erpnext/schools/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py b/erpnext/schools/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py index cd2c82c37b7..01cee583501 100644 --- a/erpnext/schools/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py +++ b/erpnext/schools/report/student_monthly_attendance_sheet/student_monthly_attendance_sheet.py @@ -61,7 +61,7 @@ def get_attendance_list(from_date, to_date, student_batch, students_list): students_with_leave_application = get_students_with_leave_application(from_date, to_date, students_list) for d in attendance_list: att_map.setdefault(d.student, frappe._dict()).setdefault(d.date, "") - if students_with_leave_application and d.student in students_with_leave_application.get(d.date,[]): + if students_with_leave_application and d.student in students_with_leave_application.get(d.date): att_map[d.student][d.date] = "Present" else: att_map[d.student][d.date] = d.status