From defae116980f769d146965324722b1dcb503e250 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Mon, 18 May 2026 11:39:03 +0530 Subject: [PATCH 1/5] fix: Reverse GL Entries update in the background job to avoid timeout issues --- .../doctype/loan_demand/loan_demand.py | 13 ++++++++++++- .../loan_interest_accrual/loan_interest_accrual.py | 11 ++++++++++- .../doctype/loan_repayment/loan_repayment.py | 13 ++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lending/loan_management/doctype/loan_demand/loan_demand.py b/lending/loan_management/doctype/loan_demand/loan_demand.py index eedc64515..81e2bc75a 100644 --- a/lending/loan_management/doctype/loan_demand/loan_demand.py +++ b/lending/loan_management/doctype/loan_demand/loan_demand.py @@ -101,7 +101,18 @@ def update_repayment_schedule(self, cancel=0): def on_cancel(self): self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] - self.make_gl_entries(cancel=1) + + # Reverse GL Entries update in the background job to avoid timeout issues during demand cancellation + if not frappe.flags.in_test: + frappe.enqueue( + self.make_gl_entries, + cancel=1, + queue="long", + enqueue_after_commit=True, + ) + else: + self.make_gl_entries(cancel=1) + self.update_repayment_schedule(cancel=1) self.make_credit_note() diff --git a/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index ec4511fa5..c924fc927 100644 --- a/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -145,7 +145,16 @@ def on_submit(self): self.db_set("additional_interest_suspense_entry", additional_interest_jv) def on_cancel(self): - self.make_gl_entries(cancel=1) + # Reverse GL Entries update in the background job to avoid timeout issues during accrual cancellation + if not frappe.flags.in_test: + frappe.enqueue( + self.make_gl_entries, + cancel=1, + queue="long", + enqueue_after_commit=True, + ) + else: + self.make_gl_entries(cancel=1) if self.normal_interest_journal_entry and loan_accounting_enabled(self.company): doc = frappe.get_doc("Journal Entry", self.normal_interest_journal_entry) diff --git a/lending/loan_management/doctype/loan_repayment/loan_repayment.py b/lending/loan_management/doctype/loan_repayment/loan_repayment.py index 06d00a0aa..0361152b3 100644 --- a/lending/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/lending/loan_management/doctype/loan_repayment/loan_repayment.py @@ -723,7 +723,18 @@ def on_cancel(self): "Loan Repayment Repost", "Loan Adjustment", ] - self.make_gl_entries(cancel=1) + + # Reverse GL Entries update in the background job to avoid timeout issues during cancellation + if not frappe.flags.in_test: + frappe.enqueue( + self.make_gl_entries, + cancel=1, + queue="long", + enqueue_after_commit=True, + ) + else: + self.make_gl_entries(cancel=1) + self.post_suspense_entries(cancel=1) if not self.is_write_off_waiver: From ab6c4363209fc6bd20058dbfa89db6628dcc698d Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Thu, 21 May 2026 15:21:53 +0530 Subject: [PATCH 2/5] revert: changes --- .../doctype/loan_demand/loan_demand.py | 13 +------------ .../loan_interest_accrual/loan_interest_accrual.py | 11 +---------- .../doctype/loan_repayment/loan_repayment.py | 13 +------------ 3 files changed, 3 insertions(+), 34 deletions(-) diff --git a/lending/loan_management/doctype/loan_demand/loan_demand.py b/lending/loan_management/doctype/loan_demand/loan_demand.py index 81e2bc75a..eedc64515 100644 --- a/lending/loan_management/doctype/loan_demand/loan_demand.py +++ b/lending/loan_management/doctype/loan_demand/loan_demand.py @@ -101,18 +101,7 @@ def update_repayment_schedule(self, cancel=0): def on_cancel(self): self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] - - # Reverse GL Entries update in the background job to avoid timeout issues during demand cancellation - if not frappe.flags.in_test: - frappe.enqueue( - self.make_gl_entries, - cancel=1, - queue="long", - enqueue_after_commit=True, - ) - else: - self.make_gl_entries(cancel=1) - + self.make_gl_entries(cancel=1) self.update_repayment_schedule(cancel=1) self.make_credit_note() diff --git a/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index c924fc927..ec4511fa5 100644 --- a/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -145,16 +145,7 @@ def on_submit(self): self.db_set("additional_interest_suspense_entry", additional_interest_jv) def on_cancel(self): - # Reverse GL Entries update in the background job to avoid timeout issues during accrual cancellation - if not frappe.flags.in_test: - frappe.enqueue( - self.make_gl_entries, - cancel=1, - queue="long", - enqueue_after_commit=True, - ) - else: - self.make_gl_entries(cancel=1) + self.make_gl_entries(cancel=1) if self.normal_interest_journal_entry and loan_accounting_enabled(self.company): doc = frappe.get_doc("Journal Entry", self.normal_interest_journal_entry) diff --git a/lending/loan_management/doctype/loan_repayment/loan_repayment.py b/lending/loan_management/doctype/loan_repayment/loan_repayment.py index 0361152b3..06d00a0aa 100644 --- a/lending/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/lending/loan_management/doctype/loan_repayment/loan_repayment.py @@ -723,18 +723,7 @@ def on_cancel(self): "Loan Repayment Repost", "Loan Adjustment", ] - - # Reverse GL Entries update in the background job to avoid timeout issues during cancellation - if not frappe.flags.in_test: - frappe.enqueue( - self.make_gl_entries, - cancel=1, - queue="long", - enqueue_after_commit=True, - ) - else: - self.make_gl_entries(cancel=1) - + self.make_gl_entries(cancel=1) self.post_suspense_entries(cancel=1) if not self.is_write_off_waiver: From fc9ae1d413d95a17c3419d9fd6813a97fe5954c0 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Mon, 25 May 2026 11:05:49 +0530 Subject: [PATCH 3/5] feat: add scheduler based reverse GL creation for cancelled loan records --- lending/hooks.py | 7 ++- lending/install.py | 16 +++++- .../doctype/loan_demand/loan_demand.json | 11 +++- .../doctype/loan_demand/loan_demand.py | 12 +++- .../loan_interest_accrual.json | 11 +++- .../loan_interest_accrual.py | 12 +++- lending/loan_management/utils.py | 38 +++++++++++++ lending/patches.txt | 1 + ...add_cancel_gl_queue_settings_in_company.py | 57 +++++++++++++++++++ 9 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 lending/patches/v16_0/add_cancel_gl_queue_settings_in_company.py diff --git a/lending/hooks.py b/lending/hooks.py index f88e62403..e60cb1c96 100644 --- a/lending/hooks.py +++ b/lending/hooks.py @@ -194,7 +194,12 @@ ], "hourly_long": [ "lending.loan_management.doctype.loan_repayment.loan_repayment.process_pending_credit_notes", - ] + ], + "cron": { + "*/30 * * * *": [ + "lending.loan_management.utils.process_cancelled_gl_entries", + ] + }, } bank_reconciliation_doctypes = [ diff --git a/lending/install.py b/lending/install.py index 0de3f748c..87b00282d 100644 --- a/lending/install.py +++ b/lending/install.py @@ -95,12 +95,26 @@ "fieldtype": "Check", "insert_after": "loan_column_break", }, + { + "fieldname": "enable_demand_cancel_gl_queue", + "label": "Enable Demand Cancel GL Queue", + "fieldtype": "Check", + "default": "1", + "insert_after": "enable_loan_accounting", + }, + { + "fieldname": "enable_interest_cancel_gl_queue", + "label": "Enable Interest Cancel GL Queue", + "fieldtype": "Check", + "default": "1", + "insert_after": "enable_demand_cancel_gl_queue", + }, { "fieldname": "collection_offset_logic_based_on", "label": "Collection Offset Logic Based On", "fieldtype": "Select", "options": "NPA Flag\nDays Past Due", - "insert_after": "enable_loan_accounting", + "insert_after": "enable_interest_cancel_gl_queue", }, { "fieldname": "days_past_due_threshold", diff --git a/lending/loan_management/doctype/loan_demand/loan_demand.json b/lending/loan_management/doctype/loan_demand/loan_demand.json index ab77c455c..893a5270c 100644 --- a/lending/loan_management/doctype/loan_demand/loan_demand.json +++ b/lending/loan_management/doctype/loan_demand/loan_demand.json @@ -36,6 +36,7 @@ "accounting_dimensions_section", "cost_center", "is_imported", + "cancel_gl_pending", "column_break_hmrs", "amended_from", "process_loan_demand", @@ -294,6 +295,14 @@ "label": "Is Imported", "read_only": 1 }, + { + "default": "0", + "fieldname": "cancel_gl_pending", + "fieldtype": "Check", + "hidden": 1, + "label": "Cancel GL Pending", + "read_only": 1 + }, { "default": "0", "fieldname": "is_partial_pre_paid_interest", @@ -305,7 +314,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2026-02-16 15:13:09.765918", + "modified": "2026-05-25 10:35:43.539786", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Demand", diff --git a/lending/loan_management/doctype/loan_demand/loan_demand.py b/lending/loan_management/doctype/loan_demand/loan_demand.py index eedc64515..052042868 100644 --- a/lending/loan_management/doctype/loan_demand/loan_demand.py +++ b/lending/loan_management/doctype/loan_demand/loan_demand.py @@ -22,6 +22,7 @@ class LoanDemand(LoanController): amended_from: DF.Link | None applicant: DF.DynamicLink | None applicant_type: DF.Link | None + cancel_gl_pending: DF.Check company: DF.Link | None cost_center: DF.Link | None demand_amount: DF.Currency @@ -101,10 +102,19 @@ def update_repayment_schedule(self, cancel=0): def on_cancel(self): self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] - self.make_gl_entries(cancel=1) + + if self.queue_cancel_gl() and not frappe.flags.in_test: + self.db_set("cancel_gl_pending", 1) + else: + self.make_gl_entries(cancel=1) + self.db_set("cancel_gl_pending", 0) + self.update_repayment_schedule(cancel=1) self.make_credit_note() + def queue_cancel_gl(self): + return frappe.db.get_value("Company", self.company, "enable_demand_cancel_gl_queue") + def make_credit_note(self): if not self.demand_type == "Charges": return diff --git a/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index fe3043405..ccaed36c5 100644 --- a/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -41,6 +41,7 @@ "process_loan_interest_accrual", "amended_from", "is_imported", + "cancel_gl_pending", "column_break_svgc", "normal_interest_journal_entry", "additional_interest_suspense_entry" @@ -291,13 +292,21 @@ "fieldtype": "Check", "label": "Is Imported", "read_only": 1 + }, + { + "default": "0", + "fieldname": "cancel_gl_pending", + "fieldtype": "Check", + "hidden": 1, + "label": "Cancel GL Pending", + "read_only": 1 } ], "in_create": 1, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2026-01-28 15:20:16.650685", + "modified": "2026-05-25 10:36:03.518964", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", diff --git a/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index ec4511fa5..15992586d 100644 --- a/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -43,6 +43,7 @@ class LoanInterestAccrual(LoanController): applicant: DF.DynamicLink | None applicant_type: DF.Literal["Employee", "Member", "Customer"] base_amount: DF.Currency + cancel_gl_pending: DF.Check company: DF.Link | None cost_center: DF.Link | None interest_amount: DF.Currency @@ -145,7 +146,13 @@ def on_submit(self): self.db_set("additional_interest_suspense_entry", additional_interest_jv) def on_cancel(self): - self.make_gl_entries(cancel=1) + self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] + + if self.queue_cancel_gl() and not frappe.flags.in_test: + self.db_set("cancel_gl_pending", 1) + else: + self.make_gl_entries(cancel=1) + self.db_set("cancel_gl_pending", 0) if self.normal_interest_journal_entry and loan_accounting_enabled(self.company): doc = frappe.get_doc("Journal Entry", self.normal_interest_journal_entry) @@ -157,7 +164,8 @@ def on_cancel(self): doc.flags.ignore_links = True doc.cancel() - self.ignore_linked_doctypes = ["GL Entry", "Payment Ledger Entry"] + def queue_cancel_gl(self): + return frappe.db.get_value("Company", self.company, "enable_interest_cancel_gl_queue") def make_gl_entries(self, cancel=0, adv_adj=0): if not loan_accounting_enabled(self.company): diff --git a/lending/loan_management/utils.py b/lending/loan_management/utils.py index 2699c29fe..0a9daa13c 100644 --- a/lending/loan_management/utils.py +++ b/lending/loan_management/utils.py @@ -367,3 +367,41 @@ def create_charge_master(charge_type): "is_stock_item": 0, } ).insert() + + +def process_cancelled_gl_entries(): + process_cancelled_documents( + doctype="Loan Demand", + title="Loan Demand Cancel GL Queue Error", + ) + + process_cancelled_documents( + doctype="Loan Interest Accrual", + title="Loan Interest Accrual Cancel GL Queue Error", + ) + + +def process_cancelled_documents(doctype, title): + doc = frappe.qb.DocType(doctype) + rows = ( + frappe.qb.from_(doc) + .select(doc.name) + .where((doc.docstatus == 2) & (doc.cancel_gl_pending == 1)) + .run(as_dict=True) + ) + + for row in rows: + try: + docname = row.name + document = frappe.get_doc(doctype, docname) + document.make_gl_entries(cancel=1) + document.db_set("cancel_gl_pending", 0, update_modified=False) + frappe.db.commit() + except Exception: + frappe.db.rollback() + frappe.log_error( + title=title, + message=frappe.get_traceback(), + reference_doctype=doctype, + reference_name=row.name, + ) diff --git a/lending/patches.txt b/lending/patches.txt index 115456d2d..4de316418 100644 --- a/lending/patches.txt +++ b/lending/patches.txt @@ -48,3 +48,4 @@ lending.patches.v1_0.update_value_date_in_loan_refund lending.patches.v1_0.update_value_date_in_pending_doctypes lending.patches.v16_0.add_enable_loan_accounting_field lending.patches.v16_0.update_is_invoice_generated_field +lending.patches.v16_0.add_cancel_gl_queue_settings_in_company diff --git a/lending/patches/v16_0/add_cancel_gl_queue_settings_in_company.py b/lending/patches/v16_0/add_cancel_gl_queue_settings_in_company.py new file mode 100644 index 000000000..6e359c29c --- /dev/null +++ b/lending/patches/v16_0/add_cancel_gl_queue_settings_in_company.py @@ -0,0 +1,57 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company_fields = [] + created_demand_queue_field = False + created_interest_queue_field = False + + if not frappe.db.exists( + "Custom Field", {"dt": "Company", "fieldname": "enable_demand_cancel_gl_queue"} + ): + company_fields.append( + { + "fieldname": "enable_demand_cancel_gl_queue", + "label": "Enable Demand Cancel GL Queue", + "fieldtype": "Check", + "default": "1", + "insert_after": "enable_loan_accounting", + } + ) + created_demand_queue_field = True + + if not frappe.db.exists( + "Custom Field", {"dt": "Company", "fieldname": "enable_interest_cancel_gl_queue"} + ): + company_fields.append( + { + "fieldname": "enable_interest_cancel_gl_queue", + "label": "Enable Interest Cancel GL Queue", + "fieldtype": "Check", + "default": "1", + "insert_after": "enable_demand_cancel_gl_queue", + } + ) + created_interest_queue_field = True + + if company_fields: + create_custom_fields({"Company": company_fields}, update=True) + + if created_demand_queue_field: + frappe.db.sql( + """ + update `tabCompany` + set enable_demand_cancel_gl_queue = 1 + where ifnull(enable_demand_cancel_gl_queue, 0) = 0 + """ + ) + + if created_interest_queue_field: + frappe.db.sql( + """ + update `tabCompany` + set enable_interest_cancel_gl_queue = 1 + where ifnull(enable_interest_cancel_gl_queue, 0) = 0 + """ + ) From 3f8dd5c9fb376da107cfdab3a59147008fbdbcbd Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Mon, 25 May 2026 11:33:32 +0530 Subject: [PATCH 4/5] feat: add scheduler based reverse GL creation for cancelled loan records --- lending/loan_management/doctype/loan_demand/loan_demand.py | 1 - .../doctype/loan_interest_accrual/loan_interest_accrual.py | 1 - 2 files changed, 2 deletions(-) diff --git a/lending/loan_management/doctype/loan_demand/loan_demand.py b/lending/loan_management/doctype/loan_demand/loan_demand.py index 052042868..098ed2922 100644 --- a/lending/loan_management/doctype/loan_demand/loan_demand.py +++ b/lending/loan_management/doctype/loan_demand/loan_demand.py @@ -107,7 +107,6 @@ def on_cancel(self): self.db_set("cancel_gl_pending", 1) else: self.make_gl_entries(cancel=1) - self.db_set("cancel_gl_pending", 0) self.update_repayment_schedule(cancel=1) self.make_credit_note() diff --git a/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 15992586d..d9521c24b 100644 --- a/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/lending/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -152,7 +152,6 @@ def on_cancel(self): self.db_set("cancel_gl_pending", 1) else: self.make_gl_entries(cancel=1) - self.db_set("cancel_gl_pending", 0) if self.normal_interest_journal_entry and loan_accounting_enabled(self.company): doc = frappe.get_doc("Journal Entry", self.normal_interest_journal_entry) From 2826f33b97ad0151d90e980a119a221759040b30 Mon Sep 17 00:00:00 2001 From: Nihantra Patel Date: Mon, 25 May 2026 12:30:09 +0530 Subject: [PATCH 5/5] fix: utils.py --- lending/loan_management/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lending/loan_management/utils.py b/lending/loan_management/utils.py index 0a9daa13c..6072b5f64 100644 --- a/lending/loan_management/utils.py +++ b/lending/loan_management/utils.py @@ -393,9 +393,12 @@ def process_cancelled_documents(doctype, title): for row in rows: try: docname = row.name - document = frappe.get_doc(doctype, docname) + document = frappe.get_doc(doctype, docname, for_update=True) + if not document.cancel_gl_pending: + frappe.db.rollback() + continue document.make_gl_entries(cancel=1) - document.db_set("cancel_gl_pending", 0, update_modified=False) + document.db_set("cancel_gl_pending", 0) frappe.db.commit() except Exception: frappe.db.rollback()