2
0
mirror of https://github.com/frappe/erpnext.git synced 2024-06-12 07:04:00 +00:00
erpnext/erpnext/loan_management/doctype/loan/test_loan.py
2022-10-23 18:51:51 +05:30

1434 lines
42 KiB
Python

# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.utils import (
add_days,
add_months,
add_to_date,
date_diff,
flt,
format_date,
get_datetime,
nowdate,
)
from erpnext.loan_management.doctype.loan.loan import (
make_loan_write_off,
request_loan_closure,
unpledge_security,
)
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,
)
from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import (
days_in_year,
)
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts
from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import (
get_pledged_security_qty,
)
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import (
process_loan_interest_accrual_for_demand_loans,
process_loan_interest_accrual_for_term_loans,
)
from erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import (
create_process_loan_security_shortfall,
)
from erpnext.selling.doctype.customer.test_customer import get_customer_dict
from erpnext.setup.doctype.employee.test_employee import make_employee
class TestLoan(unittest.TestCase):
def setUp(self):
create_loan_accounts()
create_loan_type(
"Personal Loan",
500000,
8.4,
is_term_loan=1,
mode_of_payment="Cash",
disbursement_account="Disbursement Account - _TC",
payment_account="Payment Account - _TC",
loan_account="Loan Account - _TC",
interest_income_account="Interest Income Account - _TC",
penalty_income_account="Penalty Income Account - _TC",
repayment_schedule_type="Monthly as per repayment start date",
)
create_loan_type(
"Term Loan Type 1",
12000,
7.5,
is_term_loan=1,
mode_of_payment="Cash",
disbursement_account="Disbursement Account - _TC",
payment_account="Payment Account - _TC",
loan_account="Loan Account - _TC",
interest_income_account="Interest Income Account - _TC",
penalty_income_account="Penalty Income Account - _TC",
repayment_schedule_type="Monthly as per repayment start date",
)
create_loan_type(
"Term Loan Type 2",
12000,
7.5,
is_term_loan=1,
mode_of_payment="Cash",
disbursement_account="Disbursement Account - _TC",
payment_account="Payment Account - _TC",
loan_account="Loan Account - _TC",
interest_income_account="Interest Income Account - _TC",
penalty_income_account="Penalty Income Account - _TC",
repayment_schedule_type="Pro-rated calendar months",
repayment_date_on="Start of the next month",
)
create_loan_type(
"Term Loan Type 3",
12000,
7.5,
is_term_loan=1,
mode_of_payment="Cash",
disbursement_account="Disbursement Account - _TC",
payment_account="Payment Account - _TC",
loan_account="Loan Account - _TC",
interest_income_account="Interest Income Account - _TC",
penalty_income_account="Penalty Income Account - _TC",
repayment_schedule_type="Pro-rated calendar months",
repayment_date_on="End of the current month",
)
create_loan_type(
"Stock Loan",
2000000,
13.5,
25,
1,
5,
"Cash",
"Disbursement Account - _TC",
"Payment Account - _TC",
"Loan Account - _TC",
"Interest Income Account - _TC",
"Penalty Income Account - _TC",
repayment_schedule_type="Monthly as per repayment start date",
)
create_loan_type(
"Demand Loan",
2000000,
13.5,
25,
0,
5,
"Cash",
"Disbursement Account - _TC",
"Payment Account - _TC",
"Loan Account - _TC",
"Interest Income Account - _TC",
"Penalty Income Account - _TC",
)
create_loan_security_type()
create_loan_security()
create_loan_security_price(
"Test Security 1", 500, "Nos", get_datetime(), get_datetime(add_to_date(nowdate(), hours=24))
)
create_loan_security_price(
"Test Security 2", 250, "Nos", get_datetime(), get_datetime(add_to_date(nowdate(), hours=24))
)
self.applicant1 = make_employee("robert_loan@loan.com")
if not frappe.db.exists("Customer", "_Test Loan Customer"):
frappe.get_doc(get_customer_dict("_Test Loan Customer")).insert(ignore_permissions=True)
if not frappe.db.exists("Customer", "_Test Loan Customer 1"):
frappe.get_doc(get_customer_dict("_Test Loan Customer 1")).insert(ignore_permissions=True)
self.applicant2 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer"}, "name")
self.applicant3 = frappe.db.get_value("Customer", {"name": "_Test Loan Customer 1"}, "name")
create_loan(self.applicant1, "Personal Loan", 280000, "Repay Over Number of Periods", 20)
def test_loan(self):
loan = frappe.get_doc("Loan", {"applicant": self.applicant1})
self.assertEqual(loan.monthly_repayment_amount, 15052)
self.assertEqual(flt(loan.total_interest_payable, 0), 21034)
self.assertEqual(flt(loan.total_payment, 0), 301034)
schedule = loan.repayment_schedule
self.assertEqual(len(schedule), 20)
for idx, principal_amount, interest_amount, balance_loan_amount in [
[3, 13369, 1683, 227080],
[19, 14941, 105, 0],
[17, 14740, 312, 29785],
]:
self.assertEqual(flt(schedule[idx].principal_amount, 0), principal_amount)
self.assertEqual(flt(schedule[idx].interest_amount, 0), interest_amount)
self.assertEqual(flt(schedule[idx].balance_loan_amount, 0), balance_loan_amount)
loan.repayment_method = "Repay Fixed Amount per Period"
loan.monthly_repayment_amount = 14000
loan.save()
self.assertEqual(len(loan.repayment_schedule), 22)
self.assertEqual(flt(loan.total_interest_payable, 0), 22712)
self.assertEqual(flt(loan.total_payment, 0), 302712)
def test_loan_with_security(self):
pledge = [
{
"loan_security": "Test Security 1",
"qty": 4000.00,
}
]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Stock Loan", pledge, "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
)
self.assertEqual(loan.loan_amount, 1000000)
def test_loan_disbursement(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Stock Loan", pledge, "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
)
self.assertEqual(loan.loan_amount, 1000000)
loan.submit()
loan_disbursement_entry1 = make_loan_disbursement_entry(loan.name, 500000)
loan_disbursement_entry2 = make_loan_disbursement_entry(loan.name, 500000)
loan = frappe.get_doc("Loan", loan.name)
gl_entries1 = frappe.db.get_all(
"GL Entry",
fields=["name"],
filters={"voucher_type": "Loan Disbursement", "voucher_no": loan_disbursement_entry1.name},
)
gl_entries2 = frappe.db.get_all(
"GL Entry",
fields=["name"],
filters={"voucher_type": "Loan Disbursement", "voucher_no": loan_disbursement_entry2.name},
)
self.assertEqual(loan.status, "Disbursed")
self.assertEqual(loan.disbursed_amount, 1000000)
self.assertTrue(gl_entries1)
self.assertTrue(gl_entries2)
def test_sanctioned_amount_limit(self):
# Clear loan docs before checking
frappe.db.sql("DELETE FROM `tabLoan` where applicant = '_Test Loan Customer 1'")
frappe.db.sql("DELETE FROM `tabLoan Application` where applicant = '_Test Loan Customer 1'")
frappe.db.sql("DELETE FROM `tabLoan Security Pledge` where applicant = '_Test Loan Customer 1'")
if not frappe.db.get_value(
"Sanctioned Loan Amount",
filters={
"applicant_type": "Customer",
"applicant": "_Test Loan Customer 1",
"company": "_Test Company",
},
):
frappe.get_doc(
{
"doctype": "Sanctioned Loan Amount",
"applicant_type": "Customer",
"applicant": "_Test Loan Customer 1",
"sanctioned_amount_limit": 1500000,
"company": "_Test Company",
}
).insert(ignore_permissions=True)
# Make First Loan
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
"_Test Company", self.applicant3, "Demand Loan", pledge
)
create_pledge(loan_application)
loan = create_demand_loan(
self.applicant3, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
# Make second loan greater than the sanctioned amount
loan_application = create_loan_application(
"_Test Company", self.applicant3, "Demand Loan", pledge, do_not_save=True
)
self.assertRaises(frappe.ValidationError, loan_application.save)
def test_regular_loan_repayment(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Demand Loan", pledge
)
create_pledge(loan_application)
loan = create_demand_loan(
self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
first_date = "2019-10-01"
last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
accrued_interest_amount = flt(
(loan.loan_amount * loan.rate_of_interest * no_of_days)
/ (days_in_year(get_datetime(first_date).year) * 100),
2,
)
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
repayment_entry = create_repayment_entry(
loan.name, self.applicant2, add_days(last_date, 10), 111119
)
repayment_entry.save()
repayment_entry.submit()
penalty_amount = (accrued_interest_amount * 5 * 25) / 100
self.assertEqual(flt(repayment_entry.penalty_amount, 0), flt(penalty_amount, 0))
amounts = frappe.db.get_all(
"Loan Interest Accrual", {"loan": loan.name}, ["paid_interest_amount"]
)
loan.load_from_db()
total_interest_paid = amounts[0]["paid_interest_amount"] + amounts[1]["paid_interest_amount"]
self.assertEqual(amounts[1]["paid_interest_amount"], repayment_entry.interest_payable)
self.assertEqual(
flt(loan.total_principal_paid, 0),
flt(repayment_entry.amount_paid - penalty_amount - total_interest_paid, 0),
)
# Check Repayment Entry cancel
repayment_entry.load_from_db()
repayment_entry.cancel()
loan.load_from_db()
self.assertEqual(loan.total_principal_paid, 0)
self.assertEqual(loan.total_principal_paid, 0)
def test_loan_closure(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Demand Loan", pledge
)
create_pledge(loan_application)
loan = create_demand_loan(
self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
first_date = "2019-10-01"
last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
# Adding 5 since repayment is made 5 days late after due date
# and since payment type is loan closure so interest should be considered for those
# 5 days as well though in grace period
no_of_days += 5
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
days_in_year(get_datetime(first_date).year) * 100
)
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
repayment_entry = create_repayment_entry(
loan.name,
self.applicant2,
add_days(last_date, 5),
flt(loan.loan_amount + accrued_interest_amount),
)
repayment_entry.submit()
amount = frappe.db.get_value(
"Loan Interest Accrual", {"loan": loan.name}, ["sum(paid_interest_amount)"]
)
self.assertEqual(flt(amount, 0), flt(accrued_interest_amount, 0))
self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
request_loan_closure(loan.name)
loan.load_from_db()
self.assertEqual(loan.status, "Loan Closure Requested")
def test_loan_repayment_for_term_loan(self):
pledges = [
{"loan_security": "Test Security 2", "qty": 4000.00},
{"loan_security": "Test Security 1", "qty": 2000.00},
]
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,
posting_date=add_months(nowdate(), -1),
)
loan.submit()
make_loan_disbursement_entry(
loan.name, loan.loan_amount, disbursement_date=add_months(nowdate(), -1)
)
process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
repayment_entry = create_repayment_entry(
loan.name, self.applicant2, add_days(nowdate(), 5), 89768.75
)
repayment_entry.submit()
amounts = frappe.db.get_value(
"Loan Interest Accrual", {"loan": loan.name}, ["paid_interest_amount", "paid_principal_amount"]
)
self.assertEqual(amounts[0], 11250.00)
self.assertEqual(amounts[1], 78303.00)
def test_repayment_schedule_update(self):
loan = create_loan(
self.applicant2,
"Personal Loan",
200000,
"Repay Over Number of Periods",
4,
applicant_type="Customer",
repayment_start_date="2021-04-30",
posting_date="2021-04-01",
)
loan.submit()
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date="2021-04-01")
process_loan_interest_accrual_for_term_loans(posting_date="2021-05-01")
process_loan_interest_accrual_for_term_loans(posting_date="2021-06-01")
repayment_entry = create_repayment_entry(loan.name, self.applicant2, "2021-06-05", 120000)
repayment_entry.submit()
loan.load_from_db()
self.assertEqual(flt(loan.get("repayment_schedule")[3].principal_amount, 2), 41369.83)
self.assertEqual(flt(loan.get("repayment_schedule")[3].interest_amount, 2), 289.59)
self.assertEqual(flt(loan.get("repayment_schedule")[3].total_payment, 2), 41659.41)
self.assertEqual(flt(loan.get("repayment_schedule")[3].balance_loan_amount, 2), 0)
def test_security_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()
make_loan_disbursement_entry(loan.name, loan.loan_amount)
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(loan_security_shortfall.loan_amount, 1000000.00)
self.assertEqual(loan_security_shortfall.security_value, 800000.00)
self.assertEqual(loan_security_shortfall.shortfall_amount, 600000.00)
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.assertEqual(loan_security_shortfall.status, "Completed")
self.assertEqual(loan_security_shortfall.shortfall_amount, 0)
def test_loan_security_unpledge(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Demand Loan", pledge
)
create_pledge(loan_application)
loan = create_demand_loan(
self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
first_date = "2019-10-01"
last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
no_of_days += 5
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
days_in_year(get_datetime(first_date).year) * 100
)
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
repayment_entry = create_repayment_entry(
loan.name,
self.applicant2,
add_days(last_date, 5),
flt(loan.loan_amount + accrued_interest_amount),
)
repayment_entry.submit()
request_loan_closure(loan.name)
loan.load_from_db()
self.assertEqual(loan.status, "Loan Closure Requested")
unpledge_request = unpledge_security(loan=loan.name, save=1)
unpledge_request.submit()
unpledge_request.status = "Approved"
unpledge_request.save()
loan.load_from_db()
pledged_qty = get_pledged_security_qty(loan.name)
self.assertEqual(loan.status, "Closed")
self.assertEqual(sum(pledged_qty.values()), 0)
amounts = amounts = calculate_amounts(loan.name, add_days(last_date, 5))
self.assertEqual(amounts["pending_principal_amount"], 0)
self.assertEqual(amounts["payable_principal_amount"], 0.0)
self.assertEqual(amounts["interest_amount"], 0)
def test_partial_loan_security_unpledge(self):
pledge = [
{"loan_security": "Test Security 1", "qty": 2000.00},
{"loan_security": "Test Security 2", "qty": 4000.00},
]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Demand Loan", pledge
)
create_pledge(loan_application)
loan = create_demand_loan(
self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
first_date = "2019-10-01"
last_date = "2019-10-30"
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
repayment_entry = create_repayment_entry(
loan.name, self.applicant2, add_days(last_date, 5), 600000
)
repayment_entry.submit()
unpledge_map = {"Test Security 2": 2000}
unpledge_request = unpledge_security(loan=loan.name, security_map=unpledge_map, save=1)
unpledge_request.submit()
unpledge_request.status = "Approved"
unpledge_request.save()
unpledge_request.submit()
unpledge_request.load_from_db()
self.assertEqual(unpledge_request.docstatus, 1)
def test_sanctioned_loan_security_unpledge(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Demand Loan", pledge
)
create_pledge(loan_application)
loan = create_demand_loan(
self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
unpledge_map = {"Test Security 1": 4000}
unpledge_request = unpledge_security(loan=loan.name, security_map=unpledge_map, save=1)
unpledge_request.submit()
unpledge_request.status = "Approved"
unpledge_request.save()
unpledge_request.submit()
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 test_pending_loan_amount_after_closure_request(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Demand Loan", pledge
)
create_pledge(loan_application)
loan = create_demand_loan(
self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
first_date = "2019-10-01"
last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
no_of_days += 5
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
days_in_year(get_datetime(first_date).year) * 100
)
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
repayment_entry = create_repayment_entry(
loan.name,
self.applicant2,
add_days(last_date, 5),
flt(loan.loan_amount + accrued_interest_amount),
)
repayment_entry.submit()
amounts = frappe.db.get_value(
"Loan Interest Accrual", {"loan": loan.name}, ["paid_interest_amount", "paid_principal_amount"]
)
request_loan_closure(loan.name)
loan.load_from_db()
self.assertEqual(loan.status, "Loan Closure Requested")
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
self.assertEqual(amounts["pending_principal_amount"], 0.0)
def test_partial_unaccrued_interest_payment(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Demand Loan", pledge
)
create_pledge(loan_application)
loan = create_demand_loan(
self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
first_date = "2019-10-01"
last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
no_of_days += 5.5
# get partial unaccrued interest amount
paid_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
days_in_year(get_datetime(first_date).year) * 100
)
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
repayment_entry = create_repayment_entry(
loan.name, self.applicant2, add_days(last_date, 5), paid_amount
)
repayment_entry.submit()
repayment_entry.load_from_db()
partial_accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 5) / (
days_in_year(get_datetime(first_date).year) * 100
)
interest_amount = flt(amounts["interest_amount"] + partial_accrued_interest_amount, 2)
self.assertEqual(flt(repayment_entry.total_interest_paid, 0), flt(interest_amount, 0))
def test_penalty(self):
loan, amounts = create_loan_scenario_for_penalty(self)
# 30 days - grace period
penalty_days = 30 - 4
penalty_applicable_amount = flt(amounts["interest_amount"] / 2)
penalty_amount = flt((((penalty_applicable_amount * 25) / 100) * penalty_days), 2)
process = process_loan_interest_accrual_for_demand_loans(posting_date="2019-11-30")
calculated_penalty_amount = frappe.db.get_value(
"Loan Interest Accrual",
{"process_loan_interest_accrual": process, "loan": loan.name},
"penalty_amount",
)
self.assertEqual(loan.loan_amount, 1000000)
self.assertEqual(calculated_penalty_amount, penalty_amount)
def test_penalty_repayment(self):
loan, dummy = create_loan_scenario_for_penalty(self)
amounts = calculate_amounts(loan.name, "2019-11-30 00:00:00")
first_penalty = 10000
second_penalty = amounts["penalty_amount"] - 10000
repayment_entry = create_repayment_entry(
loan.name, self.applicant2, "2019-11-30 00:00:00", 10000
)
repayment_entry.submit()
amounts = calculate_amounts(loan.name, "2019-11-30 00:00:01")
self.assertEqual(amounts["penalty_amount"], second_penalty)
repayment_entry = create_repayment_entry(
loan.name, self.applicant2, "2019-11-30 00:00:01", second_penalty
)
repayment_entry.submit()
amounts = calculate_amounts(loan.name, "2019-11-30 00:00:02")
self.assertEqual(amounts["penalty_amount"], 0)
def test_loan_write_off_limit(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Demand Loan", pledge
)
create_pledge(loan_application)
loan = create_demand_loan(
self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
first_date = "2019-10-01"
last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
no_of_days += 5
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
days_in_year(get_datetime(first_date).year) * 100
)
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
# repay 50 less so that it can be automatically written off
repayment_entry = create_repayment_entry(
loan.name,
self.applicant2,
add_days(last_date, 5),
flt(loan.loan_amount + accrued_interest_amount - 50),
)
repayment_entry.submit()
amount = frappe.db.get_value(
"Loan Interest Accrual", {"loan": loan.name}, ["sum(paid_interest_amount)"]
)
self.assertEqual(flt(amount, 0), flt(accrued_interest_amount, 0))
self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
self.assertEqual(flt(amounts["pending_principal_amount"], 0), 50)
request_loan_closure(loan.name)
loan.load_from_db()
self.assertEqual(loan.status, "Loan Closure Requested")
def test_loan_repayment_against_partially_disbursed_loan(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Demand Loan", pledge
)
create_pledge(loan_application)
loan = create_demand_loan(
self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
first_date = "2019-10-01"
last_date = "2019-10-30"
make_loan_disbursement_entry(loan.name, loan.loan_amount / 2, disbursement_date=first_date)
loan.load_from_db()
self.assertEqual(loan.status, "Partially Disbursed")
create_repayment_entry(
loan.name, self.applicant2, add_days(last_date, 5), flt(loan.loan_amount / 3)
)
def test_loan_amount_write_off(self):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application(
"_Test Company", self.applicant2, "Demand Loan", pledge
)
create_pledge(loan_application)
loan = create_demand_loan(
self.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
self.assertEqual(loan.loan_amount, 1000000)
first_date = "2019-10-01"
last_date = "2019-10-30"
no_of_days = date_diff(last_date, first_date) + 1
no_of_days += 5
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) / (
days_in_year(get_datetime(first_date).year) * 100
)
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
# repay 100 less so that it can be automatically written off
repayment_entry = create_repayment_entry(
loan.name,
self.applicant2,
add_days(last_date, 5),
flt(loan.loan_amount + accrued_interest_amount - 100),
)
repayment_entry.submit()
amount = frappe.db.get_value(
"Loan Interest Accrual", {"loan": loan.name}, ["sum(paid_interest_amount)"]
)
self.assertEqual(flt(amount, 0), flt(accrued_interest_amount, 0))
self.assertEqual(flt(repayment_entry.penalty_amount, 5), 0)
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
self.assertEqual(flt(amounts["pending_principal_amount"], 0), 100)
we = make_loan_write_off(loan.name, amount=amounts["pending_principal_amount"])
we.submit()
amounts = calculate_amounts(loan.name, add_days(last_date, 5))
self.assertEqual(flt(amounts["pending_principal_amount"], 0), 0)
def test_term_loan_schedule_types(self):
loan = create_loan(
self.applicant1,
"Term Loan Type 1",
12000,
"Repay Over Number of Periods",
12,
repayment_start_date="2022-10-17",
)
# Check for first, second and last installment date
self.assertEqual(
format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "17-10-2022"
)
self.assertEqual(
format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "17-11-2022"
)
self.assertEqual(
format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "17-09-2023"
)
loan.loan_type = "Term Loan Type 2"
loan.save()
# Check for first, second and last installment date
self.assertEqual(
format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "01-11-2022"
)
self.assertEqual(
format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "01-12-2022"
)
self.assertEqual(
format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "01-10-2023"
)
loan.loan_type = "Term Loan Type 3"
loan.save()
# Check for first, second and last installment date
self.assertEqual(
format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "31-10-2022"
)
self.assertEqual(
format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "30-11-2022"
)
self.assertEqual(
format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "30-09-2023"
)
loan.repayment_method = "Repay Fixed Amount per Period"
loan.monthly_repayment_amount = 1042
loan.save()
self.assertEqual(
format_date(loan.get("repayment_schedule")[0].payment_date, "dd-MM-yyyy"), "31-10-2022"
)
self.assertEqual(
format_date(loan.get("repayment_schedule")[1].payment_date, "dd-MM-yyyy"), "30-11-2022"
)
self.assertEqual(
format_date(loan.get("repayment_schedule")[-1].payment_date, "dd-MM-yyyy"), "30-09-2023"
)
def create_loan_scenario_for_penalty(doc):
pledge = [{"loan_security": "Test Security 1", "qty": 4000.00}]
loan_application = create_loan_application("_Test Company", doc.applicant2, "Demand Loan", pledge)
create_pledge(loan_application)
loan = create_demand_loan(
doc.applicant2, "Demand Loan", loan_application, posting_date="2019-10-01"
)
loan.submit()
first_date = "2019-10-01"
last_date = "2019-10-30"
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date=last_date)
amounts = calculate_amounts(loan.name, add_days(last_date, 1))
paid_amount = amounts["interest_amount"] / 2
repayment_entry = create_repayment_entry(
loan.name, doc.applicant2, add_days(last_date, 5), paid_amount
)
repayment_entry.submit()
return loan, amounts
def create_loan_accounts():
if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
frappe.get_doc(
{
"doctype": "Account",
"account_name": "Loans and Advances (Assets)",
"company": "_Test Company",
"root_type": "Asset",
"report_type": "Balance Sheet",
"currency": "INR",
"parent_account": "Current Assets - _TC",
"account_type": "Bank",
"is_group": 1,
}
).insert(ignore_permissions=True)
if not frappe.db.exists("Account", "Loan Account - _TC"):
frappe.get_doc(
{
"doctype": "Account",
"company": "_Test Company",
"account_name": "Loan Account",
"root_type": "Asset",
"report_type": "Balance Sheet",
"currency": "INR",
"parent_account": "Loans and Advances (Assets) - _TC",
"account_type": "Bank",
}
).insert(ignore_permissions=True)
if not frappe.db.exists("Account", "Payment Account - _TC"):
frappe.get_doc(
{
"doctype": "Account",
"company": "_Test Company",
"account_name": "Payment Account",
"root_type": "Asset",
"report_type": "Balance Sheet",
"currency": "INR",
"parent_account": "Bank Accounts - _TC",
"account_type": "Bank",
}
).insert(ignore_permissions=True)
if not frappe.db.exists("Account", "Disbursement Account - _TC"):
frappe.get_doc(
{
"doctype": "Account",
"company": "_Test Company",
"account_name": "Disbursement Account",
"root_type": "Asset",
"report_type": "Balance Sheet",
"currency": "INR",
"parent_account": "Bank Accounts - _TC",
"account_type": "Bank",
}
).insert(ignore_permissions=True)
if not frappe.db.exists("Account", "Interest Income Account - _TC"):
frappe.get_doc(
{
"doctype": "Account",
"company": "_Test Company",
"root_type": "Income",
"account_name": "Interest Income Account",
"report_type": "Profit and Loss",
"currency": "INR",
"parent_account": "Direct Income - _TC",
"account_type": "Income Account",
}
).insert(ignore_permissions=True)
if not frappe.db.exists("Account", "Penalty Income Account - _TC"):
frappe.get_doc(
{
"doctype": "Account",
"company": "_Test Company",
"account_name": "Penalty Income Account",
"root_type": "Income",
"report_type": "Profit and Loss",
"currency": "INR",
"parent_account": "Direct Income - _TC",
"account_type": "Income Account",
}
).insert(ignore_permissions=True)
def create_loan_type(
loan_name,
maximum_loan_amount,
rate_of_interest,
penalty_interest_rate=None,
is_term_loan=None,
grace_period_in_days=None,
mode_of_payment=None,
disbursement_account=None,
payment_account=None,
loan_account=None,
interest_income_account=None,
penalty_income_account=None,
repayment_method=None,
repayment_periods=None,
repayment_schedule_type=None,
repayment_date_on=None,
):
if not frappe.db.exists("Loan Type", loan_name):
loan_type = frappe.get_doc(
{
"doctype": "Loan Type",
"company": "_Test Company",
"loan_name": loan_name,
"is_term_loan": is_term_loan,
"repayment_schedule_type": "Monthly as per repayment start date",
"maximum_loan_amount": maximum_loan_amount,
"rate_of_interest": rate_of_interest,
"penalty_interest_rate": penalty_interest_rate,
"grace_period_in_days": grace_period_in_days,
"mode_of_payment": mode_of_payment,
"disbursement_account": disbursement_account,
"payment_account": payment_account,
"loan_account": loan_account,
"interest_income_account": interest_income_account,
"penalty_income_account": penalty_income_account,
"repayment_method": repayment_method,
"repayment_periods": repayment_periods,
"write_off_amount": 100,
}
)
if loan_type.is_term_loan:
loan_type.repayment_schedule_type = repayment_schedule_type
if loan_type.repayment_schedule_type != "Monthly as per repayment start date":
loan_type.repayment_date_on = repayment_date_on
loan_type.insert()
loan_type.submit()
def create_loan_security_type():
if not frappe.db.exists("Loan Security Type", "Stock"):
frappe.get_doc(
{
"doctype": "Loan Security Type",
"loan_security_type": "Stock",
"unit_of_measure": "Nos",
"haircut": 50.00,
"loan_to_value_ratio": 50,
}
).insert(ignore_permissions=True)
def create_loan_security():
if not frappe.db.exists("Loan Security", "Test Security 1"):
frappe.get_doc(
{
"doctype": "Loan Security",
"loan_security_type": "Stock",
"loan_security_code": "532779",
"loan_security_name": "Test Security 1",
"unit_of_measure": "Nos",
"haircut": 50.00,
}
).insert(ignore_permissions=True)
if not frappe.db.exists("Loan Security", "Test Security 2"):
frappe.get_doc(
{
"doctype": "Loan Security",
"loan_security_type": "Stock",
"loan_security_code": "531335",
"loan_security_name": "Test Security 2",
"unit_of_measure": "Nos",
"haircut": 50.00,
}
).insert(ignore_permissions=True)
def create_loan_security_pledge(applicant, pledges, loan_application=None, loan=None):
lsp = frappe.new_doc("Loan Security Pledge")
lsp.applicant_type = "Customer"
lsp.applicant = applicant
lsp.company = "_Test Company"
lsp.loan_application = loan_application
if loan:
lsp.loan = loan
for pledge in pledges:
lsp.append("securities", {"loan_security": pledge["loan_security"], "qty": pledge["qty"]})
lsp.save()
lsp.submit()
return lsp
def make_loan_disbursement_entry(loan, amount, disbursement_date=None):
loan_disbursement_entry = frappe.get_doc(
{
"doctype": "Loan Disbursement",
"against_loan": loan,
"disbursement_date": disbursement_date,
"company": "_Test Company",
"disbursed_amount": amount,
"cost_center": "Main - _TC",
}
).insert(ignore_permissions=True)
loan_disbursement_entry.save()
loan_disbursement_entry.submit()
return loan_disbursement_entry
def create_loan_security_price(loan_security, loan_security_price, uom, from_date, to_date):
if not frappe.db.get_value(
"Loan Security Price",
{"loan_security": loan_security, "valid_from": ("<=", from_date), "valid_upto": (">=", to_date)},
"name",
):
lsp = frappe.get_doc(
{
"doctype": "Loan Security Price",
"loan_security": loan_security,
"loan_security_price": loan_security_price,
"uom": uom,
"valid_from": from_date,
"valid_upto": to_date,
}
).insert(ignore_permissions=True)
def create_repayment_entry(loan, applicant, posting_date, paid_amount):
lr = frappe.get_doc(
{
"doctype": "Loan Repayment",
"against_loan": loan,
"company": "_Test Company",
"posting_date": posting_date or nowdate(),
"applicant": applicant,
"amount_paid": paid_amount,
"loan_type": "Stock Loan",
}
).insert(ignore_permissions=True)
return lr
def create_loan_application(
company,
applicant,
loan_type,
proposed_pledges,
repayment_method=None,
repayment_periods=None,
posting_date=None,
do_not_save=False,
):
loan_application = frappe.new_doc("Loan Application")
loan_application.applicant_type = "Customer"
loan_application.company = company
loan_application.applicant = applicant
loan_application.loan_type = loan_type
loan_application.posting_date = posting_date or nowdate()
loan_application.is_secured_loan = 1
if repayment_method:
loan_application.repayment_method = repayment_method
loan_application.repayment_periods = repayment_periods
for pledge in proposed_pledges:
loan_application.append("proposed_pledges", pledge)
if do_not_save:
return loan_application
loan_application.save()
loan_application.submit()
loan_application.status = "Approved"
loan_application.save()
return loan_application.name
def create_loan(
applicant,
loan_type,
loan_amount,
repayment_method,
repayment_periods,
applicant_type=None,
repayment_start_date=None,
posting_date=None,
):
loan = frappe.get_doc(
{
"doctype": "Loan",
"applicant_type": applicant_type or "Employee",
"company": "_Test Company",
"applicant": applicant,
"loan_type": loan_type,
"loan_amount": loan_amount,
"repayment_method": repayment_method,
"repayment_periods": repayment_periods,
"repayment_start_date": repayment_start_date or nowdate(),
"is_term_loan": 1,
"posting_date": posting_date or nowdate(),
}
)
loan.save()
return loan
def create_loan_with_security(
applicant,
loan_type,
repayment_method,
repayment_periods,
loan_application,
posting_date=None,
repayment_start_date=None,
):
loan = frappe.get_doc(
{
"doctype": "Loan",
"company": "_Test Company",
"applicant_type": "Customer",
"posting_date": posting_date or nowdate(),
"loan_application": loan_application,
"applicant": applicant,
"loan_type": loan_type,
"is_term_loan": 1,
"is_secured_loan": 1,
"repayment_method": repayment_method,
"repayment_periods": repayment_periods,
"repayment_start_date": repayment_start_date or nowdate(),
"mode_of_payment": frappe.db.get_value("Mode of Payment", {"type": "Cash"}, "name"),
"payment_account": "Payment Account - _TC",
"loan_account": "Loan Account - _TC",
"interest_income_account": "Interest Income Account - _TC",
"penalty_income_account": "Penalty Income Account - _TC",
}
)
loan.save()
return loan
def create_demand_loan(applicant, loan_type, loan_application, posting_date=None):
loan = frappe.get_doc(
{
"doctype": "Loan",
"company": "_Test Company",
"applicant_type": "Customer",
"posting_date": posting_date or nowdate(),
"loan_application": loan_application,
"applicant": applicant,
"loan_type": loan_type,
"is_term_loan": 0,
"is_secured_loan": 1,
"mode_of_payment": frappe.db.get_value("Mode of Payment", {"type": "Cash"}, "name"),
"payment_account": "Payment Account - _TC",
"loan_account": "Loan Account - _TC",
"interest_income_account": "Interest Income Account - _TC",
"penalty_income_account": "Penalty Income Account - _TC",
}
)
loan.save()
return loan