diff --git a/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py b/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py deleted file mode 100644 index b6c650207f0..00000000000 --- a/erpnext/hr/doctype/employee_loan_application/employee_loan_application.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- 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, rounded -from frappe.model.mapper import get_mapped_doc -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 maximum_loan_limit and 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) - if monthly_interest_rate: - monthly_interest_amount = self.loan_amount * monthly_interest_rate - if monthly_interest_amount >= self.repayment_amount: - frappe.throw(_("Repayment amount {} should be greater than monthly interest amount {}"). - format(self.repayment_amount, monthly_interest_amount)) - - self.repayment_periods = math.ceil((math.log(self.repayment_amount) - - math.log(self.repayment_amount - (monthly_interest_amount))) / - (math.log(1 + monthly_interest_rate))) - else: - self.repayment_periods = self.loan_amount / self.repayment_amount - - self.calculate_payable_amount() - - def calculate_payable_amount(self): - balance_amount = self.loan_amount - self.total_payable_amount = 0 - self.total_payable_interest = 0 - - while(balance_amount > 0): - interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100)) - balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount) - - self.total_payable_interest += interest_amount - - self.total_payable_amount = self.loan_amount + self.total_payable_interest - -@frappe.whitelist() -def make_employee_loan(source_name, target_doc = None): - doclist = get_mapped_doc("Employee Loan Application", source_name, { - "Employee Loan Application": { - "doctype": "Employee Loan", - "validation": { - "docstatus": ["=", 1] - } - } - }, target_doc) - - return doclist \ No newline at end of file diff --git a/erpnext/hr/doctype/loan/loan.js b/erpnext/hr/doctype/loan/loan.js index e1b41786f47..3f5c30c4758 100644 --- a/erpnext/hr/doctype/loan/loan.js +++ b/erpnext/hr/doctype/loan/loan.js @@ -39,31 +39,19 @@ frappe.ui.form.on('Loan', { }, refresh: function (frm) { - if (frm.doc.docstatus == 1 && frm.doc.status == "Sanctioned") { - frm.add_custom_button(__('Create Disbursement Entry'), function() { - frm.trigger("make_jv"); - }) - } - if (frm.doc.repayment_schedule) { - let total_amount_paid = 0; - $.each(frm.doc.repayment_schedule || [], function(i, row) { - if (row.paid) { - total_amount_paid += row.total_payment; - } - }); - frm.set_value("total_amount_paid", total_amount_paid); -; } - if (frm.doc.docstatus == 1 && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) { - frm.add_custom_button(__('Create Repayment Entry'), function() { - frm.trigger("make_repayment_entry"); - }) + if (frm.doc.docstatus == 1) { + if (frm.doc.status == "Sanctioned") { + frm.add_custom_button(__('Create Disbursement Entry'), function() { + frm.trigger("make_jv"); + }).addClass("btn-primary"); + } else if (frm.doc.status == "Disbursed" && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) { + frm.add_custom_button(__('Create Repayment Entry'), function() { + frm.trigger("make_repayment_entry"); + }).addClass("btn-primary"); + } } frm.trigger("toggle_fields"); }, - status: function (frm) { - frm.toggle_reqd("disbursement_date", frm.doc.status == 'Disbursed') - frm.toggle_reqd("repayment_start_date", frm.doc.status == 'Disbursed') - }, make_jv: function (frm) { frappe.call({ diff --git a/erpnext/hr/doctype/loan/loan.json b/erpnext/hr/doctype/loan/loan.json index 587b3010ca0..505b601edd5 100644 --- a/erpnext/hr/doctype/loan/loan.json +++ b/erpnext/hr/doctype/loan/loan.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, @@ -20,6 +21,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "applicant_type", "fieldtype": "Select", "hidden": 0, @@ -53,6 +55,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "applicant", "fieldtype": "Dynamic Link", "hidden": 0, @@ -86,6 +89,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "applicant_name", "fieldtype": "Data", "hidden": 0, @@ -118,6 +122,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "loan_application", "fieldtype": "Link", "hidden": 0, @@ -151,6 +156,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "loan_type", "fieldtype": "Link", "hidden": 0, @@ -184,6 +190,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_3", "fieldtype": "Column Break", "hidden": 0, @@ -215,7 +222,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "", + "default": "Today", + "fetch_if_empty": 0, "fieldname": "posting_date", "fieldtype": "Date", "hidden": 0, @@ -248,6 +256,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "company", "fieldtype": "Link", "hidden": 0, @@ -282,6 +291,7 @@ "collapsible": 0, "columns": 0, "default": "Sanctioned", + "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", "hidden": 0, @@ -299,7 +309,7 @@ "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, @@ -316,6 +326,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:doc.applicant_type==\"Employee\"", + "fetch_if_empty": 0, "fieldname": "repay_from_salary", "fieldtype": "Check", "hidden": 0, @@ -348,6 +359,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_8", "fieldtype": "Section Break", "hidden": 0, @@ -380,6 +392,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "loan_amount", "fieldtype": "Currency", "hidden": 0, @@ -415,6 +428,7 @@ "columns": 0, "default": "", "fetch_from": "loan_type.rate_of_interest", + "fetch_if_empty": 0, "fieldname": "rate_of_interest", "fieldtype": "Percent", "hidden": 0, @@ -448,6 +462,8 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "eval:doc.status==\"Disbursed\"", + "fetch_if_empty": 0, "fieldname": "disbursement_date", "fieldtype": "Date", "hidden": 0, @@ -480,6 +496,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "repayment_start_date", "fieldtype": "Date", "hidden": 0, @@ -499,7 +516,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -512,6 +529,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_11", "fieldtype": "Column Break", "hidden": 0, @@ -544,6 +562,7 @@ "collapsible": 0, "columns": 0, "default": "Repay Over Number of Periods", + "fetch_if_empty": 0, "fieldname": "repayment_method", "fieldtype": "Select", "hidden": 0, @@ -579,6 +598,7 @@ "columns": 0, "default": "", "depends_on": "", + "fetch_if_empty": 0, "fieldname": "repayment_periods", "fieldtype": "Int", "hidden": 0, @@ -613,6 +633,7 @@ "columns": 0, "default": "", "depends_on": "", + "fetch_if_empty": 0, "fieldname": "monthly_repayment_amount", "fieldtype": "Currency", "hidden": 0, @@ -646,6 +667,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "account_info", "fieldtype": "Section Break", "hidden": 0, @@ -678,6 +700,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "mode_of_payment", "fieldtype": "Link", "hidden": 0, @@ -711,6 +734,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "payment_account", "fieldtype": "Link", "hidden": 0, @@ -744,6 +768,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_9", "fieldtype": "Column Break", "hidden": 0, @@ -775,6 +800,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "loan_account", "fieldtype": "Link", "hidden": 0, @@ -808,6 +834,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "interest_income_account", "fieldtype": "Link", "hidden": 0, @@ -841,6 +868,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_15", "fieldtype": "Section Break", "hidden": 0, @@ -873,6 +901,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "repayment_schedule", "fieldtype": "Table", "hidden": 0, @@ -906,6 +935,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_17", "fieldtype": "Section Break", "hidden": 0, @@ -939,6 +969,7 @@ "collapsible": 0, "columns": 0, "default": "0", + "fetch_if_empty": 0, "fieldname": "total_payment", "fieldtype": "Currency", "hidden": 0, @@ -972,6 +1003,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_19", "fieldtype": "Column Break", "hidden": 0, @@ -1004,6 +1036,7 @@ "collapsible": 0, "columns": 0, "default": "0", + "fetch_if_empty": 0, "fieldname": "total_interest_payable", "fieldtype": "Currency", "hidden": 0, @@ -1037,6 +1070,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "total_amount_paid", "fieldtype": "Currency", "hidden": 0, @@ -1070,6 +1104,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "amended_from", "fieldtype": "Link", "hidden": 0, @@ -1106,7 +1141,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 16:15:53.267145", + "modified": "2019-07-10 13:04:20.953694", "modified_by": "Administrator", "module": "HR", "name": "Loan", @@ -1149,7 +1184,6 @@ "set_user_permissions": 0, "share": 0, "submit": 0, - "user_permission_doctypes": "[\"Employee\"]", "write": 0 } ], diff --git a/erpnext/hr/doctype/loan/loan.py b/erpnext/hr/doctype/loan/loan.py index 58c9b8f6676..a803863124d 100644 --- a/erpnext/hr/doctype/loan/loan.py +++ b/erpnext/hr/doctype/loan/loan.py @@ -6,29 +6,33 @@ from __future__ import unicode_literals import frappe, math, json import erpnext from frappe import _ -from frappe.utils import flt, rounded, add_months, nowdate +from frappe.utils import flt, rounded, add_months, nowdate, getdate from erpnext.controllers.accounts_controller import AccountsController class Loan(AccountsController): def validate(self): - check_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods) + validate_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods) + self.set_missing_fields() + self.make_repayment_schedule() + self.set_repayment_period() + self.calculate_totals() + + def set_missing_fields(self): 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) + if self.status == "Repaid/Closed": self.total_amount_paid = self.total_payment - if self.status == 'Disbursed' and self.repayment_start_date < self.disbursement_date: - frappe.throw(_("Repayment Start Date cannot be before Disbursement Date.")) - if self.status == "Disbursed": - self.make_repayment_schedule() - self.set_repayment_period() - self.calculate_totals() def make_jv_entry(self): self.check_permission('write') @@ -105,20 +109,31 @@ def update_total_amount_paid(doc): frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid) def update_disbursement_status(doc): - disbursement = frappe.db.sql("""select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount - from `tabGL Entry` where account = %s and against_voucher_type = 'Loan' and against_voucher = %s""", - (doc.payment_account, doc.name), as_dict=1)[0] - if disbursement.disbursed_amount == doc.loan_amount: - frappe.db.set_value("Loan", doc.name , "status", "Disbursed") - if disbursement.disbursed_amount == 0: - frappe.db.set_value("Loan", doc.name , "status", "Sanctioned") - if disbursement.disbursed_amount > doc.loan_amount: - frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount)) - if disbursement.disbursed_amount > 0: - frappe.db.set_value("Loan", doc.name , "disbursement_date", disbursement.posting_date) - frappe.db.set_value("Loan", doc.name , "repayment_start_date", disbursement.posting_date) + disbursement = frappe.db.sql(""" + select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount + from `tabGL Entry` + where account = %s and against_voucher_type = 'Loan' and against_voucher = %s + """, (doc.payment_account, doc.name), as_dict=1)[0] -def check_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods): + disbursement_date = None + if not disbursement or disbursement.disbursed_amount == 0: + status = "Sanctioned" + elif disbursement.disbursed_amount == doc.loan_amount: + disbursement_date = disbursement.posting_date + status = "Disbursed" + elif disbursement.disbursed_amount > doc.loan_amount: + frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount)) + + if status == 'Disbursed' and getdate(disbursement_date) > getdate(frappe.db.get_value("Loan", doc.name, "repayment_start_date")): + frappe.throw(_("Disbursement Date cannot be after Loan Repayment Start Date")) + + frappe.db.sql(""" + update `tabLoan` + set status = %s, disbursement_date = %s + where name = %s + """, (status, disbursement_date, doc.name)) + +def validate_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")) @@ -222,4 +237,4 @@ def make_jv_entry(loan, company, loan_account, applicant_type, applicant, loan_a "reference_name": loan, }) journal_entry.set("accounts", account_amt_list) - return journal_entry.as_dict() \ No newline at end of file + return journal_entry.as_dict() diff --git a/erpnext/hr/doctype/loan_application/loan_application.js b/erpnext/hr/doctype/loan_application/loan_application.js index febcbd88e76..a73b62a894e 100644 --- a/erpnext/hr/doctype/loan_application/loan_application.js +++ b/erpnext/hr/doctype/loan_application/loan_application.js @@ -23,9 +23,8 @@ frappe.ui.form.on('Loan Application', { }, add_toolbar_buttons: function(frm) { if (frm.doc.status == "Approved") { - frm.add_custom_button(__('Loan'), function() { + frm.add_custom_button(__('Create Loan'), function() { frappe.call({ - type: "GET", method: "erpnext.hr.doctype.loan_application.loan_application.make_loan", args: { "source_name": frm.doc.name @@ -37,7 +36,7 @@ frappe.ui.form.on('Loan Application', { } } }); - }) + }).addClass("btn-primary"); } } }); diff --git a/erpnext/hr/doctype/loan_application/loan_application.py b/erpnext/hr/doctype/loan_application/loan_application.py index 706c9646c73..5dbcf15eac1 100644 --- a/erpnext/hr/doctype/loan_application/loan_application.py +++ b/erpnext/hr/doctype/loan_application/loan_application.py @@ -9,11 +9,11 @@ from frappe.utils import flt, rounded from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document -from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, check_repayment_method +from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, validate_repayment_method class LoanApplication(Document): def validate(self): - check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods) + validate_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods) self.validate_loan_amount() self.get_repayment_details() @@ -29,14 +29,14 @@ class LoanApplication(Document): if self.repayment_method == "Repay Fixed Amount per Period": monthly_interest_rate = flt(self.rate_of_interest) / (12 *100) if monthly_interest_rate: - self.repayment_periods = math.ceil((math.log(self.repayment_amount) - + 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))) else: self.repayment_periods = self.loan_amount / self.repayment_amount self.calculate_payable_amount() - + def calculate_payable_amount(self): balance_amount = self.loan_amount self.total_payable_amount = 0 @@ -47,9 +47,9 @@ class LoanApplication(Document): balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount) self.total_payable_interest += interest_amount - + self.total_payable_amount = self.loan_amount + self.total_payable_interest - + @frappe.whitelist() def make_loan(source_name, target_doc = None): doclist = get_mapped_doc("Loan Application", source_name, {