From 43615ab0517af58e304d7f1972fff6127003a2a2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 13 Oct 2023 23:09:45 +0530 Subject: [PATCH 1/2] feat: Loan booking updates (cherry picked from commit 106d2e8f91f0f3f93a50cab0a262d3bb04ff5ed2) # Conflicts: # lending/loan_management/doctype/loan/loan.json --- .../loan_management/doctype/loan/loan.json | 23 +++++- lending/loan_management/doctype/loan/loan.py | 60 +++++++++++++--- .../loan_charge_reference.json | 10 ++- .../loan_disbursement/loan_disbursement.py | 3 + .../loan_disbursement_charge.json | 2 +- .../loan_repayment/loan_repayment.json | 10 ++- .../loan_repayment_schedule.json | 9 ++- .../loan_repayment_schedule.py | 70 ++++++++++++------- 8 files changed, 147 insertions(+), 40 deletions(-) diff --git a/lending/loan_management/doctype/loan/loan.json b/lending/loan_management/doctype/loan/loan.json index 9ae68095..b2e021e0 100644 --- a/lending/loan_management/doctype/loan/loan.json +++ b/lending/loan_management/doctype/loan/loan.json @@ -33,6 +33,7 @@ "repayment_periods", "monthly_repayment_amount", "repayment_start_date", + "repayment_frequency", "is_term_loan", "loan_classification_details_section", "days_past_due", @@ -44,6 +45,8 @@ "tenure_post_restructure", "accounting_dimensions_section", "cost_center", + "loan_charges_section", + "loan_charges", "account_info", "mode_of_payment", "disbursement_account", @@ -477,18 +480,36 @@ "label": "Loan Classification Details" }, { +<<<<<<< HEAD "fetch_from": "loan_type.loan_category", "fieldname": "loan_category", "fieldtype": "Link", "label": "Loan Category", "options": "Loan Category", "read_only": 1 +======= + "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" +>>>>>>> 106d2e8 (feat: Loan booking updates) } ], "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", diff --git a/lending/loan_management/doctype/loan/loan.py b/lending/loan_management/doctype/loan/loan.py index 2411c43c..d3db994d 100644 --- a/lending/loan_management/doctype/loan/loan.py +++ b/lending/loan_management/doctype/loan/loan.py @@ -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() @@ -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 ( + 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, @@ -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 @@ -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() @@ -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() @@ -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: diff --git a/lending/loan_management/doctype/loan_charge_reference/loan_charge_reference.json b/lending/loan_management/doctype/loan_charge_reference/loan_charge_reference.json index 86d27d79..07a8e644 100644 --- a/lending/loan_management/doctype/loan_charge_reference/loan_charge_reference.json +++ b/lending/loan_management/doctype/loan_charge_reference/loan_charge_reference.json @@ -7,6 +7,7 @@ "engine": "InnoDB", "field_order": [ "sales_invoice", + "charge", "pending_charge_amount", "allocated_amount" ], @@ -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", diff --git a/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py b/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py index bcf178d4..9b5cc6c2 100644 --- a/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/lending/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -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( diff --git a/lending/loan_management/doctype/loan_disbursement_charge/loan_disbursement_charge.json b/lending/loan_management/doctype/loan_disbursement_charge/loan_disbursement_charge.json index 094e71bc..fea8318b 100644 --- a/lending/loan_management/doctype/loan_disbursement_charge/loan_disbursement_charge.json +++ b/lending/loan_management/doctype/loan_disbursement_charge/loan_disbursement_charge.json @@ -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", diff --git a/lending/loan_management/doctype/loan_repayment/loan_repayment.json b/lending/loan_management/doctype/loan_repayment/loan_repayment.json index 6b05fd82..8970fccd 100644 --- a/lending/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/lending/loan_management/doctype/loan_repayment/loan_repayment.json @@ -11,6 +11,7 @@ "applicant", "loan_product", "repayment_type", + "select_charge_manually", "loan_restructure", "column_break_3", "company", @@ -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", diff --git a/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.json b/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.json index f308e706..e6873452 100644 --- a/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.json +++ b/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.json @@ -16,6 +16,7 @@ "adjusted_interest", "column_break_n6iy", "loan_product", + "repayment_frequency", "repayment_schedule_type", "repayment_method", "repayment_periods", @@ -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", diff --git a/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.py b/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.py index 0e5a2aea..ca56501c 100644 --- a/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.py +++ b/lending/loan_management/doctype/loan_repayment_schedule/loan_repayment_schedule.py @@ -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, @@ -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): @@ -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": + days = 1 + 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 == "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) From 51d272b597ee7f84af213d3717cd42659c360f9a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 18 Oct 2023 11:35:13 +0530 Subject: [PATCH 2/2] chore: resolve conflicts --- lending/loan_management/doctype/loan/loan.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lending/loan_management/doctype/loan/loan.json b/lending/loan_management/doctype/loan/loan.json index b2e021e0..7a4c95d3 100644 --- a/lending/loan_management/doctype/loan/loan.json +++ b/lending/loan_management/doctype/loan/loan.json @@ -480,14 +480,14 @@ "label": "Loan Classification Details" }, { -<<<<<<< HEAD "fetch_from": "loan_type.loan_category", "fieldname": "loan_category", "fieldtype": "Link", "label": "Loan Category", "options": "Loan Category", "read_only": 1 -======= + }, + { "fieldname": "repayment_frequency", "fieldtype": "Select", "label": "Repayment Frequency", @@ -503,7 +503,6 @@ "fieldname": "loan_charges_section", "fieldtype": "Section Break", "label": "Loan Charges" ->>>>>>> 106d2e8 (feat: Loan booking updates) } ], "index_web_pages_for_search": 1,