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: Loan booking updates #100

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
22 changes: 21 additions & 1 deletion lending/loan_management/doctype/loan/loan.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"repayment_periods",
"monthly_repayment_amount",
"repayment_start_date",
"repayment_frequency",
"is_term_loan",
"loan_classification_details_section",
"days_past_due",
Expand All @@ -43,6 +44,8 @@
"tenure_post_restructure",
"accounting_dimensions_section",
"cost_center",
"loan_charges_section",
"loan_charges",
"account_info",
"mode_of_payment",
"disbursement_account",
Expand Down Expand Up @@ -474,12 +477,29 @@
"fieldname": "loan_classification_details_section",
"fieldtype": "Section Break",
"label": "Loan Classification Details"
},
{
"fieldname": "repayment_frequency",
"fieldtype": "Select",
"label": "Repayment Frequency",
"options": "Monthly\nDaily\nWeekly\nQuarterly\nOne Time"
},
{
"fieldname": "loan_charges",
"fieldtype": "Table",
"label": "Loan Charges",
"options": "Loan Disbursement Charge"
},
{
"fieldname": "loan_charges_section",
"fieldtype": "Section Break",
"label": "Loan Charges"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-10-11 13:26:31.406754",
"modified": "2023-10-13 19:15:47.245190",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan",
Expand Down
60 changes: 50 additions & 10 deletions lending/loan_management/doctype/loan/loan.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def validate(self):
self.validate_accounts()
self.check_sanctioned_amount_limit()
self.set_cyclic_date()
self.set_default_charge_account()

if self.is_term_loan and not self.is_new():
self.update_draft_schedule()
Expand Down Expand Up @@ -72,7 +73,10 @@ def validate_cost_center(self):
frappe.throw(_("Cost center is mandatory for loans having rate of interest greater than 0"))

def set_cyclic_date(self):
if self.repayment_schedule_type == "Monthly as per cycle date":
if (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename to “Monthly As per cycle date”?
You would check this even if frequency is quarterly or so…

self.repayment_schedule_type == "Monthly as per cycle date"
and self.repayment_frequency == "Monthly"
):
cycle_day, min_days_bw_disbursement_first_repayment = frappe.db.get_value(
"Loan Product",
self.loan_product,
Expand All @@ -89,6 +93,20 @@ def set_cyclic_date(self):

self.repayment_start_date = cyclic_date

def set_default_charge_account(self):
for charge in self.get("loan_charges"):
if not charge.account:
account = frappe.get_cached_value(
"Loan Charges", {"parent": self.loan_product, "charge_type": charge.charge}, "income_account"
)

if not account:
account = frappe.get_cached_value(
"Item Default", {"parent": charge.charge, "company": self.company}, "income_account"
)

charge.account = account

def on_submit(self):
self.link_loan_security_pledge()
# Interest accrual for backdated term loans
Expand Down Expand Up @@ -150,6 +168,7 @@ def make_draft_schedule(self):
"loan_product": self.loan_product,
"rate_of_interest": self.rate_of_interest,
"posting_date": self.posting_date,
"repayment_frequency": self.repayment_frequency,
}
).insert()

Expand All @@ -169,6 +188,7 @@ def update_draft_schedule(self):
"posting_date": self.posting_date,
"loan_amount": self.loan_amount,
"monthly_repayment_amount": self.monthly_repayment_amount,
"repayment_frequency": self.repayment_frequency,
}
)
schedule.save()
Expand Down Expand Up @@ -389,16 +409,36 @@ def close_loan(loan, total_amount_paid):


@frappe.whitelist()
def make_loan_disbursement(loan, company, applicant_type, applicant, pending_amount=0, as_dict=0):
def make_loan_disbursement(
loan,
disbursement_amount=0,
as_dict=0,
submit=0,
posting_date=None,
disbursement_date=None,
bank_account=None,
):
loan_doc = frappe.get_doc("Loan", loan)
disbursement_entry = frappe.new_doc("Loan Disbursement")
disbursement_entry.against_loan = loan
disbursement_entry.applicant_type = applicant_type
disbursement_entry.applicant = applicant
disbursement_entry.company = company
disbursement_entry.disbursement_date = nowdate()
disbursement_entry.posting_date = nowdate()

disbursement_entry.disbursed_amount = pending_amount
disbursement_entry.against_loan = loan_doc.name
disbursement_entry.applicant_type = loan_doc.applicant_type
disbursement_entry.applicant = loan_doc.applicant
disbursement_entry.company = loan_doc.company
disbursement_entry.disbursement_date = posting_date or nowdate()
disbursement_entry.posting_date = disbursement_date or nowdate()
disbursement_entry.bank_account = bank_account

disbursement_entry.disbursed_amount = disbursement_amount

for charge in loan_doc.get("loan_charges"):
disbursement_entry.append(
"loan_disbursement_charges",
{"charge": charge.charge, "amount": charge.amount, "account": charge.account},
)

if submit:
disbursement_entry.submit()

if as_dict:
return disbursement_entry.as_dict()
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"engine": "InnoDB",
"field_order": [
"sales_invoice",
"charge",
"pending_charge_amount",
"allocated_amount"
],
Expand All @@ -29,12 +30,19 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated Amount"
},
{
"fieldname": "charge",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Charge",
"options": "Item"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-05-16 09:14:13.972294",
"modified": "2023-10-11 16:50:15.722251",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Charge Reference",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ def set_missing_values(self):
if not self.posting_date:
self.posting_date = self.disbursement_date or nowdate()

if not self.disbursement_account and self.bank_account:
self.disbursement_account = frappe.db.get_value("Bank Account", self.bank_account, "account")

def withheld_security_deposit(self):
if self.withhold_security_deposit:
sd = frappe.get_doc(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-10-10 19:47:07.918342",
"modified": "2023-10-11 19:47:07.918342",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Disbursement Charge",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"applicant",
"loan_product",
"repayment_type",
"select_charge_manually",
"loan_restructure",
"column_break_3",
"company",
Expand Down Expand Up @@ -373,12 +374,19 @@
"label": "Loan Restructure",
"options": "Loan Restructure",
"read_only": 1
},
{
"default": "0",
"depends_on": "eval:doc.repayment_type == \"Charges Waiver\"",
"fieldname": "select_charge_manually",
"fieldtype": "Check",
"label": "Select Charge Manually"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-10-11 09:25:15.123899",
"modified": "2023-10-11 17:45:09.856637",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Repayment",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"adjusted_interest",
"column_break_n6iy",
"loan_product",
"repayment_frequency",
"repayment_schedule_type",
"repayment_method",
"repayment_periods",
Expand Down Expand Up @@ -143,12 +144,18 @@
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"fieldname": "repayment_frequency",
"fieldtype": "Select",
"label": "Repayment Frequency",
"options": "Monthly\nDaily\nWeekly\nQuarterly\nOne Time"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-10-02 22:14:24.172876",
"modified": "2023-10-13 16:55:43.615976",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Repayment Schedule",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def make_repayment_schedule(self):
interest_amount, principal_amount, balance_amount, total_payment, days = self.get_amounts(
payment_date,
balance_amount,
self.repayment_frequency,
schedule_type_details.repayment_schedule_type,
schedule_type_details.repayment_date_on,
broken_period_interest_days,
Expand Down Expand Up @@ -78,11 +79,16 @@ def make_repayment_schedule(self):
schedule_type_details.repayment_schedule_type
in ["Monthly as per repayment start date", "Monthly as per cycle date"]
or schedule_type_details.repayment_date_on == "End of the current month"
):
) and self.repayment_frequency == "Monthly":
next_payment_date = add_single_month(payment_date)
payment_date = next_payment_date
elif self.repayment_frequency == "Weekly":
payment_date = add_days(payment_date, 7)
elif self.repayment_frequency == "Daily":
payment_date = add_days(payment_date, 1)
elif self.repayment_type == "Quarterly":
payment_date = add_months(payment_date, 3)

bmi_days = 0
carry_forward_interest = 0

def validate_repayment_method(self):
Expand All @@ -99,36 +105,50 @@ def get_amounts(
self,
payment_date,
balance_amount,
repayment_frequency,
schedule_type,
repayment_date_on,
additional_days,
carry_forward_interest=0,
):
if schedule_type == "Monthly as per repayment start date":
if repayment_frequency == "Monthly":
if schedule_type == "Monthly as per repayment start date":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again: “Monthly As per repayment start date.” ?

days = 1
months = 12
else:
expected_payment_date = get_last_day(payment_date)
if repayment_date_on == "Start of the next month":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, “Start of the next month cycle.” ?

expected_payment_date = add_days(expected_payment_date, 1)

if schedule_type == "Monthly as per cycle date":
days = date_diff(payment_date, add_months(payment_date, -1))
if additional_days < 0:
days = date_diff(self.repayment_start_date, self.posting_date)
additional_days = 0

months = 365
if additional_days:
days += additional_days
additional_days = 0
elif expected_payment_date == payment_date:
# using 30 days for calculating interest for all full months
days = 30
months = 365
else:
days = date_diff(get_last_day(payment_date), payment_date)
months = 365
elif repayment_frequency == "Weekly":
days = 7
months = 52
elif repayment_frequency == "Daily":
days = 1
months = 365
elif repayment_frequency == "Quarterly":
days = 3
months = 12
else:
expected_payment_date = get_last_day(payment_date)
if repayment_date_on == "Start of the next month":
expected_payment_date = add_days(expected_payment_date, 1)

if schedule_type == "Monthly as per cycle date":
days = date_diff(payment_date, add_months(payment_date, -1))
if additional_days < 0:
days = date_diff(self.repayment_start_date, self.posting_date)
additional_days = 0

months = 365
if additional_days:
days += additional_days
additional_days = 0
elif expected_payment_date == payment_date:
# using 30 days for calculating interest for all full months
days = 30
months = 365
else:
days = date_diff(get_last_day(payment_date), payment_date)
months = 365
elif repayment_frequency == "One Time":
days = date_diff(self.repayment_start_date, self.posting_date)
months = 365

interest_amount = flt(balance_amount * flt(self.rate_of_interest) * days / (months * 100))
principal_amount = self.monthly_repayment_amount - flt(interest_amount)
Expand Down