Merge pull request #23213 from deepeshgarg007/loan_disbursement_utility
feat: Utility function to get possible loan disbursal amount
This commit is contained in:
commit
d1b88af5b6
@ -17,6 +17,7 @@ from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loa
|
||||
from erpnext.loan_management.doctype.loan.loan import create_loan_security_unpledge
|
||||
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
|
||||
from erpnext.loan_management.doctype.loan_application.loan_application import create_pledge
|
||||
from erpnext.loan_management.doctype.loan_disbursement.loan_disbursement import get_disbursal_amount
|
||||
|
||||
class TestLoan(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -323,6 +324,56 @@ class TestLoan(unittest.TestCase):
|
||||
self.assertEqual(loan.status, 'Closed')
|
||||
self.assertEquals(sum(pledged_qty.values()), 0)
|
||||
|
||||
def test_disbursal_check_with_shortfall(self):
|
||||
pledges = [{
|
||||
"loan_security": "Test Security 2",
|
||||
"qty": 8000.00,
|
||||
"haircut": 50,
|
||||
}]
|
||||
|
||||
loan_application = create_loan_application('_Test Company', self.applicant2,
|
||||
'Stock Loan', pledges, "Repay Over Number of Periods", 12)
|
||||
|
||||
create_pledge(loan_application)
|
||||
|
||||
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
|
||||
loan.submit()
|
||||
|
||||
#Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
|
||||
make_loan_disbursement_entry(loan.name, 700000)
|
||||
|
||||
frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100
|
||||
where loan_security='Test Security 2'""")
|
||||
|
||||
create_process_loan_security_shortfall()
|
||||
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
|
||||
self.assertTrue(loan_security_shortfall)
|
||||
|
||||
self.assertEqual(get_disbursal_amount(loan.name), 0)
|
||||
|
||||
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
|
||||
where loan_security='Test Security 2'""")
|
||||
|
||||
def test_disbursal_check_without_shortfall(self):
|
||||
pledges = [{
|
||||
"loan_security": "Test Security 2",
|
||||
"qty": 8000.00,
|
||||
"haircut": 50,
|
||||
}]
|
||||
|
||||
loan_application = create_loan_application('_Test Company', self.applicant2,
|
||||
'Stock Loan', pledges, "Repay Over Number of Periods", 12)
|
||||
|
||||
create_pledge(loan_application)
|
||||
|
||||
loan = create_loan_with_security(self.applicant2, "Stock Loan", "Repay Over Number of Periods", 12, loan_application)
|
||||
loan.submit()
|
||||
|
||||
#Disbursing 7,00,000 from the allowed 10,00,000 according to security pledge
|
||||
make_loan_disbursement_entry(loan.name, 700000)
|
||||
|
||||
self.assertEqual(get_disbursal_amount(loan.name), 300000)
|
||||
|
||||
|
||||
def create_loan_accounts():
|
||||
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
|
||||
|
@ -33,18 +33,18 @@ frappe.ui.form.on('Loan Application', {
|
||||
|
||||
if (frm.doc.is_secured_loan) {
|
||||
frappe.db.get_value("Loan Security Pledge", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
|
||||
if (!r) {
|
||||
if (Object.keys(r).length === 0) {
|
||||
frm.add_custom_button(__('Loan Security Pledge'), function() {
|
||||
frm.trigger('create_loan_security_pledge')
|
||||
frm.trigger('create_loan_security_pledge');
|
||||
},__('Create'))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frappe.db.get_value("Loan", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => {
|
||||
if (!r) {
|
||||
if (Object.keys(r).length === 0) {
|
||||
frm.add_custom_button(__('Loan'), function() {
|
||||
frm.trigger('create_loan')
|
||||
frm.trigger('create_loan');
|
||||
},__('Create'))
|
||||
} else {
|
||||
frm.set_df_property('status', 'read_only', 1);
|
||||
@ -54,7 +54,7 @@ frappe.ui.form.on('Loan Application', {
|
||||
},
|
||||
create_loan: function(frm) {
|
||||
if (frm.doc.status != "Approved") {
|
||||
frappe.throw(__("Cannot create loan until application is approved"))
|
||||
frappe.throw(__("Cannot create loan until application is approved"));
|
||||
}
|
||||
|
||||
frappe.model.open_mapped_doc({
|
||||
|
@ -67,28 +67,10 @@ class LoanDisbursement(AccountsController):
|
||||
disbursed_amount = self.disbursed_amount + loan_details.disbursed_amount
|
||||
total_payment = loan_details.total_payment
|
||||
|
||||
if disbursed_amount > loan_details.loan_amount and loan_details.is_term_loan:
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than loan amount"))
|
||||
possible_disbursal_amount = get_disbursal_amount(self.against_loan)
|
||||
|
||||
if loan_details.status == 'Disbursed':
|
||||
pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
|
||||
- flt(loan_details.total_principal_paid)
|
||||
else:
|
||||
pending_principal_amount = loan_details.disbursed_amount
|
||||
|
||||
security_value = 0.0
|
||||
if loan_details.is_secured_loan:
|
||||
security_value = get_total_pledged_security_value(self.against_loan)
|
||||
|
||||
if not security_value:
|
||||
security_value = loan_details.loan_amount
|
||||
|
||||
if pending_principal_amount + self.disbursed_amount > flt(security_value):
|
||||
allowed_amount = security_value - pending_principal_amount
|
||||
if allowed_amount < 0:
|
||||
allowed_amount = 0
|
||||
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(allowed_amount))
|
||||
if self.disbursed_amount > possible_disbursal_amount:
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than {0}").format(possible_disbursal_amount))
|
||||
|
||||
if loan_details.status == "Disbursed" and not loan_details.is_term_loan:
|
||||
process_loan_interest_accrual_for_demand_loans(posting_date=add_days(self.disbursement_date, -1),
|
||||
@ -176,3 +158,32 @@ def get_total_pledged_security_value(loan):
|
||||
security_value += (loan_security_price_map.get(security) * qty * hair_cut_map.get(security))/100
|
||||
|
||||
return security_value
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_disbursal_amount(loan):
|
||||
loan_details = frappe.get_all("Loan", fields = ["loan_amount", "disbursed_amount", "total_payment",
|
||||
"total_principal_paid", "total_interest_payable", "status", "is_term_loan", "is_secured_loan"],
|
||||
filters= { "name": loan })[0]
|
||||
|
||||
if loan_details.is_secured_loan and frappe.get_all('Loan Security Shortfall', filters={'loan': loan,
|
||||
'status': 'Pending'}):
|
||||
return 0
|
||||
|
||||
if loan_details.status == 'Disbursed':
|
||||
pending_principal_amount = flt(loan_details.total_payment) - flt(loan_details.total_interest_payable) \
|
||||
- flt(loan_details.total_principal_paid)
|
||||
else:
|
||||
pending_principal_amount = flt(loan_details.disbursed_amount)
|
||||
|
||||
security_value = 0.0
|
||||
if loan_details.is_secured_loan:
|
||||
security_value = get_total_pledged_security_value(loan)
|
||||
|
||||
if not security_value and not loan_details.is_secured_loan:
|
||||
security_value = flt(loan_details.loan_amount)
|
||||
|
||||
disbursal_amount = flt(security_value) - flt(pending_principal_amount)
|
||||
|
||||
return disbursal_amount
|
||||
|
||||
|
||||
|
@ -85,8 +85,11 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
||||
if no_of_days <= 0:
|
||||
return
|
||||
|
||||
pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
if loan.status == 'Disbursed':
|
||||
pending_principal_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
else:
|
||||
pending_principal_amount = loan.disbursed_amount
|
||||
|
||||
interest_per_day = (pending_principal_amount * loan.rate_of_interest) / (days_in_year(get_datetime(posting_date).year) * 100)
|
||||
payable_interest = interest_per_day * no_of_days
|
||||
@ -107,7 +110,7 @@ def calculate_accrual_amount_for_demand_loans(loan, posting_date, process_loan_i
|
||||
|
||||
def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_interest, open_loans=None, loan_type=None):
|
||||
query_filters = {
|
||||
"status": "Disbursed",
|
||||
"status": ('in', ['Disbursed', 'Partially Disbursed']),
|
||||
"docstatus": 1
|
||||
}
|
||||
|
||||
@ -118,8 +121,9 @@ def make_accrual_interest_entry_for_demand_loans(posting_date, process_loan_inte
|
||||
|
||||
if not open_loans:
|
||||
open_loans = frappe.get_all("Loan",
|
||||
fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account", "is_term_loan",
|
||||
"disbursement_date", "applicant_type", "applicant", "rate_of_interest", "total_interest_payable", "repayment_start_date"],
|
||||
fields=["name", "total_payment", "total_amount_paid", "loan_account", "interest_income_account",
|
||||
"is_term_loan", "status", "disbursement_date", "disbursed_amount", "applicant_type", "applicant",
|
||||
"rate_of_interest", "total_interest_payable", "total_principal_paid", "repayment_start_date"],
|
||||
filters=query_filters)
|
||||
|
||||
for loan in open_loans:
|
||||
|
@ -281,7 +281,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
||||
|
||||
due_date = add_days(entry.posting_date, 1)
|
||||
no_of_late_days = date_diff(posting_date,
|
||||
add_days(due_date, loan_type_details.grace_period_in_days))
|
||||
add_days(due_date, loan_type_details.grace_period_in_days))
|
||||
|
||||
if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary):
|
||||
penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)/365
|
||||
@ -297,7 +297,10 @@ def get_amounts(amounts, against_loan, posting_date, payment_type):
|
||||
if not final_due_date:
|
||||
final_due_date = add_days(due_date, loan_type_details.grace_period_in_days)
|
||||
|
||||
pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable
|
||||
if against_loan_doc.status == 'Disbursed':
|
||||
pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid - against_loan_doc.total_interest_payable
|
||||
else:
|
||||
pending_principal_amount = against_loan_doc.disbursed_amount
|
||||
|
||||
if payment_type == "Loan Closure":
|
||||
if due_date:
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import get_datetime
|
||||
from frappe.utils import get_datetime, flt
|
||||
from frappe.model.document import Document
|
||||
from six import iteritems
|
||||
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import get_pledged_security_qty
|
||||
@ -51,13 +51,19 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
|
||||
"valid_upto": (">=", update_time)
|
||||
}, as_list=1))
|
||||
|
||||
loans = frappe.get_all('Loan', fields=['name', 'loan_amount', 'total_principal_paid'],
|
||||
filters={'status': 'Disbursed', 'is_secured_loan': 1})
|
||||
loans = frappe.get_all('Loan', fields=['name', 'loan_amount', 'total_principal_paid', 'total_payment',
|
||||
'total_interest_payable', 'disbursed_amount', 'status'],
|
||||
filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1})
|
||||
|
||||
loan_security_map = {}
|
||||
|
||||
for loan in loans:
|
||||
outstanding_amount = loan.loan_amount - loan.total_principal_paid
|
||||
if loan.status == 'Disbursed':
|
||||
outstanding_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
else:
|
||||
outstanding_amount = loan.disbursed_amount
|
||||
|
||||
pledged_securities = get_pledged_security_qty(loan.name)
|
||||
ltv_ratio = ''
|
||||
security_value = 0.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user