mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-25 07:54:46 +00:00
Merge branch 'develop' of https://github.com/frappe/erpnext into loan_fixes_phase_2
This commit is contained in:
@@ -275,6 +275,11 @@ class TestLoan(unittest.TestCase):
|
||||
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
|
||||
where loan_security='Test Security 2'""")
|
||||
|
||||
create_process_loan_security_shortfall()
|
||||
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
|
||||
self.assertEquals(loan_security_shortfall.status, "Completed")
|
||||
self.assertEquals(loan_security_shortfall.shortfall_amount, 0)
|
||||
|
||||
def test_loan_security_unpledge(self):
|
||||
pledge = [{
|
||||
"loan_security": "Test Security 1",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"interest_payable",
|
||||
"payable_amount",
|
||||
"column_break_9",
|
||||
"shortfall_amount",
|
||||
"payable_principal_amount",
|
||||
"penalty_amount",
|
||||
"amount_paid",
|
||||
@@ -31,6 +32,7 @@
|
||||
"column_break_21",
|
||||
"reference_date",
|
||||
"principal_amount_paid",
|
||||
"total_penalty_paid",
|
||||
"total_interest_paid",
|
||||
"repayment_details",
|
||||
"amended_from"
|
||||
@@ -226,12 +228,25 @@
|
||||
"fieldtype": "Percent",
|
||||
"label": "Rate Of Interest",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "shortfall_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Shortfall Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_penalty_paid",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Penalty Paid",
|
||||
"options": "Company:company:default_currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-05 10:06:58.792841",
|
||||
"modified": "2021-04-05 13:45:19.137896",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Repayment",
|
||||
|
||||
@@ -62,6 +62,12 @@ class LoanRepayment(AccountsController):
|
||||
if not self.payable_amount:
|
||||
self.payable_amount = flt(amounts['payable_amount'], precision)
|
||||
|
||||
shortfall_amount = flt(frappe.db.get_value('Loan Security Shortfall', {'loan': self.against_loan, 'status': 'Pending'},
|
||||
'shortfall_amount'))
|
||||
|
||||
if shortfall_amount:
|
||||
self.shortfall_amount = shortfall_amount
|
||||
|
||||
if amounts.get('due_date'):
|
||||
self.due_date = amounts.get('due_date')
|
||||
|
||||
@@ -78,7 +84,7 @@ class LoanRepayment(AccountsController):
|
||||
if not self.amount_paid:
|
||||
frappe.throw(_("Amount paid cannot be zero"))
|
||||
|
||||
if self.amount_paid < self.penalty_amount:
|
||||
if not self.shortfall_amount and self.amount_paid < self.penalty_amount:
|
||||
msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount)
|
||||
frappe.throw(msg)
|
||||
|
||||
@@ -157,11 +163,28 @@ class LoanRepayment(AccountsController):
|
||||
def allocate_amounts(self, repayment_details):
|
||||
self.set('repayment_details', [])
|
||||
self.principal_amount_paid = 0
|
||||
total_interest_paid = 0
|
||||
interest_paid = self.amount_paid - self.penalty_amount
|
||||
self.total_penalty_paid = 0
|
||||
interest_paid = self.amount_paid
|
||||
|
||||
if self.amount_paid - self.penalty_amount > 0:
|
||||
interest_paid = self.amount_paid - self.penalty_amount
|
||||
if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
|
||||
self.principal_amount_paid = self.shortfall_amount
|
||||
elif self.shortfall_amount:
|
||||
self.principal_amount_paid = self.amount_paid
|
||||
|
||||
interest_paid -= self.principal_amount_paid
|
||||
|
||||
if interest_paid > 0:
|
||||
if self.penalty_amount and interest_paid > self.penalty_amount:
|
||||
self.total_penalty_paid = self.penalty_amount
|
||||
elif self.penalty_amount:
|
||||
self.total_penalty_paid = interest_paid
|
||||
|
||||
interest_paid -= self.total_penalty_paid
|
||||
|
||||
total_interest_paid = 0
|
||||
# interest_paid = self.amount_paid - self.principal_amount_paid - self.penalty_amount
|
||||
|
||||
if interest_paid > 0:
|
||||
for lia, amounts in iteritems(repayment_details.get('pending_accrual_entries', [])):
|
||||
if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid:
|
||||
interest_amount = amounts['interest_amount']
|
||||
@@ -186,7 +209,7 @@ class LoanRepayment(AccountsController):
|
||||
'paid_principal_amount': paid_principal
|
||||
})
|
||||
|
||||
if repayment_details['unaccrued_interest'] and interest_paid:
|
||||
if repayment_details['unaccrued_interest'] and interest_paid > 0:
|
||||
# no of days for which to accrue interest
|
||||
# Interest can only be accrued for an entire day and not partial
|
||||
if interest_paid > repayment_details['unaccrued_interest']:
|
||||
@@ -202,20 +225,20 @@ class LoanRepayment(AccountsController):
|
||||
interest_paid -= no_of_days * per_day_interest
|
||||
|
||||
self.total_interest_paid = total_interest_paid
|
||||
if interest_paid:
|
||||
if interest_paid > 0:
|
||||
self.principal_amount_paid += interest_paid
|
||||
|
||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||
gle_map = []
|
||||
loan_details = frappe.get_doc("Loan", self.against_loan)
|
||||
|
||||
if self.penalty_amount:
|
||||
if self.total_penalty_paid:
|
||||
gle_map.append(
|
||||
self.get_gl_dict({
|
||||
"account": loan_details.loan_account,
|
||||
"against": loan_details.payment_account,
|
||||
"debit": self.penalty_amount,
|
||||
"debit_in_account_currency": self.penalty_amount,
|
||||
"debit": self.total_penalty_paid,
|
||||
"debit_in_account_currency": self.total_penalty_paid,
|
||||
"against_voucher_type": "Loan",
|
||||
"against_voucher": self.against_loan,
|
||||
"remarks": _("Penalty against loan:") + self.against_loan,
|
||||
@@ -230,8 +253,8 @@ class LoanRepayment(AccountsController):
|
||||
self.get_gl_dict({
|
||||
"account": loan_details.penalty_income_account,
|
||||
"against": loan_details.payment_account,
|
||||
"credit": self.penalty_amount,
|
||||
"credit_in_account_currency": self.penalty_amount,
|
||||
"credit": self.total_penalty_paid,
|
||||
"credit_in_account_currency": self.total_penalty_paid,
|
||||
"against_voucher_type": "Loan",
|
||||
"against_voucher": self.against_loan,
|
||||
"remarks": _("Penalty against loan:") + self.against_loan,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "LM-LSS-.#####",
|
||||
"creation": "2019-09-06 11:33:34.709540",
|
||||
"doctype": "DocType",
|
||||
@@ -14,6 +15,7 @@
|
||||
"shortfall_amount",
|
||||
"column_break_8",
|
||||
"security_value",
|
||||
"shortfall_percentage",
|
||||
"section_break_8",
|
||||
"process_loan_security_shortfall"
|
||||
],
|
||||
@@ -85,10 +87,18 @@
|
||||
{
|
||||
"fieldname": "column_break_8",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "shortfall_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Shortfall Percentage",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"modified": "2019-10-24 06:24:26.128997",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-01 08:13:43.263772",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Security Shortfall",
|
||||
|
||||
@@ -12,7 +12,7 @@ from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpled
|
||||
class LoanSecurityShortfall(Document):
|
||||
pass
|
||||
|
||||
def update_shortfall_status(loan, security_value):
|
||||
def update_shortfall_status(loan, security_value, on_cancel=0):
|
||||
loan_security_shortfall = frappe.db.get_value("Loan Security Shortfall",
|
||||
{"loan": loan, "status": "Pending"}, ['name', 'shortfall_amount'], as_dict=1)
|
||||
|
||||
@@ -22,7 +22,9 @@ def update_shortfall_status(loan, security_value):
|
||||
if security_value >= loan_security_shortfall.shortfall_amount:
|
||||
frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, {
|
||||
"status": "Completed",
|
||||
"shortfall_amount": loan_security_shortfall.shortfall_amount})
|
||||
"shortfall_amount": loan_security_shortfall.shortfall_amount,
|
||||
"shortfall_percentage": 0
|
||||
})
|
||||
else:
|
||||
frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name,
|
||||
"shortfall_amount", loan_security_shortfall.shortfall_amount - security_value)
|
||||
@@ -55,6 +57,9 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
|
||||
'total_interest_payable', 'disbursed_amount', 'status'],
|
||||
filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1})
|
||||
|
||||
loan_shortfall_map = frappe._dict(frappe.get_all("Loan Security Shortfall",
|
||||
fields=["loan", "name"], filters={"status": "Pending"}, as_list=1))
|
||||
|
||||
loan_security_map = {}
|
||||
|
||||
for loan in loans:
|
||||
@@ -62,7 +67,8 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
|
||||
outstanding_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
else:
|
||||
outstanding_amount = loan.disbursed_amount
|
||||
outstanding_amount = flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \
|
||||
- flt(loan.total_principal_paid)
|
||||
|
||||
pledged_securities = get_pledged_security_qty(loan.name)
|
||||
ltv_ratio = ''
|
||||
@@ -71,16 +77,22 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
|
||||
for security, qty in pledged_securities.items():
|
||||
if not ltv_ratio:
|
||||
ltv_ratio = get_ltv_ratio(security)
|
||||
security_value += loan_security_price_map.get(security) * qty
|
||||
security_value += flt(loan_security_price_map.get(security)) * flt(qty)
|
||||
|
||||
current_ratio = (outstanding_amount/security_value) * 100
|
||||
current_ratio = (outstanding_amount/security_value) * 100 if security_value else 0
|
||||
|
||||
if current_ratio > ltv_ratio:
|
||||
shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
|
||||
create_loan_security_shortfall(loan.name, outstanding_amount, security_value, shortfall_amount,
|
||||
process_loan_security_shortfall)
|
||||
current_ratio, process_loan_security_shortfall)
|
||||
elif loan_shortfall_map.get(loan.name):
|
||||
shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
|
||||
if shortfall_amount <= 0:
|
||||
shortfall = loan_shortfall_map.get(loan.name)
|
||||
update_pending_shortfall(shortfall)
|
||||
|
||||
def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall):
|
||||
def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, shortfall_ratio,
|
||||
process_loan_security_shortfall):
|
||||
existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name")
|
||||
|
||||
if existing_shortfall:
|
||||
@@ -93,6 +105,7 @@ def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_
|
||||
ltv_shortfall.loan_amount = loan_amount
|
||||
ltv_shortfall.security_value = security_value
|
||||
ltv_shortfall.shortfall_amount = shortfall_amount
|
||||
ltv_shortfall.shortfall_percentage = shortfall_ratio
|
||||
ltv_shortfall.process_loan_security_shortfall = process_loan_security_shortfall
|
||||
ltv_shortfall.save()
|
||||
|
||||
@@ -101,3 +114,12 @@ def get_ltv_ratio(loan_security):
|
||||
ltv_ratio = frappe.db.get_value('Loan Security Type', loan_security_type, 'loan_to_value_ratio')
|
||||
return ltv_ratio
|
||||
|
||||
def update_pending_shortfall(shortfall):
|
||||
# Get all pending loan security shortfall
|
||||
frappe.db.set_value("Loan Security Shortfall", shortfall,
|
||||
{
|
||||
"status": "Completed",
|
||||
"shortfall_amount": 0,
|
||||
"shortfall_percentage": 0
|
||||
})
|
||||
|
||||
|
||||
@@ -63,9 +63,11 @@ def get_active_loan_details(filters):
|
||||
currency = erpnext.get_company_currency(filters.get('company'))
|
||||
|
||||
for loan in loan_details:
|
||||
total_payment = loan.total_payment if loan.status == 'Disbursed' else loan.disbursed_amount
|
||||
|
||||
loan.update({
|
||||
"sanctioned_amount": flt(sanctioned_amount_map.get(loan.applicant_name)),
|
||||
"principal_outstanding": flt(loan.total_payment) - flt(loan.total_principal_paid) \
|
||||
"principal_outstanding": flt(total_payment) - flt(loan.total_principal_paid) \
|
||||
- flt(loan.total_interest_payable) - flt(loan.written_off_amount),
|
||||
"total_repayment": flt(payments.get(loan.loan)),
|
||||
"accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
|
||||
|
||||
Reference in New Issue
Block a user