Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: configurable loan accrual frequency #225

Open
wants to merge 7 commits into
base: poc-staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lending/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@

scheduler_events = {
"daily_long": [
"lending.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_loans",
"lending.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.schedule_accrual",
"lending.loan_management.doctype.process_loan_demand.process_loan_demand.process_daily_loan_demands",
"lending.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
"lending.loan_management.doctype.process_loan_classification.process_loan_classification.create_process_loan_classification",
Expand Down
7 changes: 7 additions & 0 deletions lending/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@
"insert_after": "interest_day_count_convention",
"non_negative": 1,
},
{
"fieldname": "loan_accrual_frequency",
"label": "Loan Accrual Frequency",
"fieldtype": "Select",
"options": "Daily\nWeekly\nFortnightly\nMonthly",
"insert_after": "min_days_bw_disbursement_first_repayment",
},
{
"fieldname": "loan_column_break",
"fieldtype": "Column Break",
Expand Down
61 changes: 59 additions & 2 deletions lending/loan_management/doctype/loan/test_loan.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
)
from lending.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import (
process_loan_interest_accrual_for_loans,
schedule_accrual,
is_posting_date_accrual_day
)
from lending.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall import (
create_process_loan_security_shortfall,
Expand Down Expand Up @@ -119,7 +121,7 @@ def setUp(self):
25,
1,
5,
"Cash",
"Cash - _TC",
"Disbursement Account - _TC",
"Payment Account - _TC",
"Loan Account - _TC",
Expand All @@ -136,7 +138,7 @@ def setUp(self):
25,
0,
5,
"Cash",
"Cash - _TC",
"Disbursement Account - _TC",
"Payment Account - _TC",
"Loan Account - _TC",
Expand Down Expand Up @@ -1464,6 +1466,61 @@ def test_backdated_pre_payment(self):
)
repayment_entry.submit()

def test_accrual_background_job(self):
loan = create_loan(
"_Test Customer 1",
"Term Loan Product 2",
100000,
"Repay Over Number of Periods",
22,
repayment_start_date="2024-08-16",
posting_date="2024-08-16",
rate_of_interest=8.5,
applicant_type="Customer",
)
# Daily accrual
frappe.db.set_value(
"Company",
"_Test Company",
"loan_accrual_frequency",
"Daily",
)
self.assertTrue(is_posting_date_accrual_day("_Test Company", "2024-12-12"))
self.assertTrue(is_posting_date_accrual_day("_Test Company", "2024-01-01"))

# Weekly accrual
frappe.db.set_value(
"Company",
"_Test Company",
"loan_accrual_frequency",
"Weekly",
)
self.assertFalse(is_posting_date_accrual_day("_Test Company", "2024-05-01"))
self.assertTrue(is_posting_date_accrual_day("_Test Company", "2024-05-06"))

# Fortnightly
frappe.db.set_value(
"Company",
"_Test Company",
"loan_accrual_frequency",
"Fortnightly",
)
self.assertFalse(is_posting_date_accrual_day("_Test Company", "2024-05-06"))
self.assertTrue(is_posting_date_accrual_day("_Test Company", "2024-05-13"))
self.assertFalse(is_posting_date_accrual_day("_Test Company", "2024-05-20"))
self.assertTrue(is_posting_date_accrual_day("_Test Company", "2024-05-27"))

# Monthly accrual
frappe.db.set_value(
"Company",
"_Test Company",
"loan_accrual_frequency",
"Monthly",
)
self.assertFalse(is_posting_date_accrual_day("_Test Company", "2024-12-02"))
self.assertTrue(is_posting_date_accrual_day("_Test Company", "2024-05-01"))
self.assertFalse(is_posting_date_accrual_day("_Test Company", "2024-12-02"))
self.assertTrue(is_posting_date_accrual_day("_Test Company", "2024-12-01"))

def create_secured_demand_loan(applicant, disbursement_amount=None):
frappe.db.set_value(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


import frappe
import erpnext
from frappe.model.document import Document
from frappe.utils import add_days, nowdate

Expand All @@ -22,6 +23,47 @@ def on_submit(self):
accrual_date=self.posting_date,
)

def get_loan_accrual_frequency(company):
company_doc = frappe.qb.DocType("Company")
query = (
frappe.qb.from_(company_doc)
.select(company_doc.loan_accrual_frequency)
.where(company_doc.name == company)
)
loan_accrual_frequency = query.run(as_dict=True)[0]['loan_accrual_frequency']
return loan_accrual_frequency

def is_posting_date_accrual_day(company, posting_date):
loan_accrual_frequency = get_loan_accrual_frequency(company)
day_of_the_month = frappe.utils.getdate(posting_date).day
weekday = frappe.utils.getdate(posting_date).weekday()
match loan_accrual_frequency:
case "Daily":
return True
case "Weekly":
if weekday == 0:
return True
case "Fortnightly":
# More thinking required
# May or may not work
# The logic for week_of_the_month assumes it's Monday, so should only be used
# in this specific circumstance
week_of_the_month = ((day_of_the_month - 1) // 7) % 2
if weekday == 0 and (week_of_the_month == 1 or week_of_the_month == 3):
return True
pass
case "Monthly":
if day_of_the_month == 1:
return True
return False



def schedule_accrual():
company = erpnext.get_default_company()
posting_date = add_days(nowdate(), -1)
if is_posting_date_accrual_day(company=company, post_date=posting_date):
process_loan_interest_accrual_for_loans(posting_date=posting_date)

def process_loan_interest_accrual_for_loans(
posting_date=None, loan_product=None, loan=None, accrual_type="Regular"
Expand Down
1 change: 1 addition & 0 deletions lending/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ lending.patches.v15_0.create_accounting_dimensions_for_loan_doctypes
lending.patches.v15_0.update_maturity_date
lending.patches.v15_0.loan_repayment_schedule_status_patch
lending.patches.v15_0.loan_disbursement_status_patch
lending.patches.v15_0.create_custom_field_loan_accrual_rate_for_company
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from lending.install import after_install
def execute():
after_install()
Loading