From 7a93630629518fcdc6a12545a2155277e12ac637 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 25 Sep 2025 16:35:54 +0530 Subject: [PATCH 01/32] feat: process period closing voucher --- .../__init__.py | 0 .../process_period_closing_voucher.js | 8 ++++ .../process_period_closing_voucher.json | 42 +++++++++++++++++++ .../process_period_closing_voucher.py | 19 +++++++++ .../test_process_period_closing_voucher.py | 20 +++++++++ 5 files changed, 89 insertions(+) create mode 100644 erpnext/accounts/doctype/process_period_closing_voucher/__init__.py create mode 100644 erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js create mode 100644 erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json create mode 100644 erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py create mode 100644 erpnext/accounts/doctype/process_period_closing_voucher/test_process_period_closing_voucher.py diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/__init__.py b/erpnext/accounts/doctype/process_period_closing_voucher/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js new file mode 100644 index 00000000000..8633cb78f8d --- /dev/null +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Process Period Closing Voucher", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json new file mode 100644 index 00000000000..0c1c7a996cf --- /dev/null +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -0,0 +1,42 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-09-25 15:44:03.534699", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "section_break_zqqu" + ], + "fields": [ + { + "fieldname": "section_break_zqqu", + "fieldtype": "Section Break" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-09-25 15:44:03.534699", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Process Period Closing Voucher", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "row_format": "Dynamic", + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py new file mode 100644 index 00000000000..da38291b16c --- /dev/null +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -0,0 +1,19 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ProcessPeriodClosingVoucher(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + # end: auto-generated types + + pass diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/test_process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/test_process_period_closing_voucher.py new file mode 100644 index 00000000000..17ce2c13b09 --- /dev/null +++ b/erpnext/accounts/doctype/process_period_closing_voucher/test_process_period_closing_voucher.py @@ -0,0 +1,20 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase + +# On IntegrationTestCase, the doctype test records and all +# link-field test record dependencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + +class IntegrationTestProcessPeriodClosingVoucher(IntegrationTestCase): + """ + Integration tests for ProcessPeriodClosingVoucher. + Use this class for testing interactions between multiple components. + """ + + pass From 4888461be215e9aadf11e6bfa6e7279768ee7e41 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 1 Oct 2025 15:20:07 +0530 Subject: [PATCH 02/32] refactor: checkbox for pcv controller --- .../doctype/accounts_settings/accounts_settings.json | 9 ++++++++- .../doctype/accounts_settings/accounts_settings.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index e4f659b48be..f810a2073a9 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -77,6 +77,7 @@ "period_closing_settings_section", "acc_frozen_upto", "ignore_account_closing_balance", + "use_legacy_controller_for_pcv", "column_break_25", "frozen_accounts_modifier", "tab_break_dpet", @@ -651,6 +652,12 @@ "fieldname": "use_legacy_budget_controller", "fieldtype": "Check", "label": "Use Legacy Budget Controller" + }, + { + "default": "0", + "fieldname": "use_legacy_controller_for_pcv", + "fieldtype": "Check", + "label": "Use Legacy Controller For Period Closing Voucher" } ], "grid_page_length": 50, @@ -659,7 +666,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-09-24 16:08:08.515254", + "modified": "2025-10-01 15:17:47.168354", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 420e2fa3870..bef862d85e0 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -75,6 +75,7 @@ class AccountsSettings(Document): unlink_advance_payment_on_cancelation_of_order: DF.Check unlink_payment_on_cancellation_of_invoice: DF.Check use_legacy_budget_controller: DF.Check + use_legacy_controller_for_pcv: DF.Check # end: auto-generated types def validate(self): From a15578f8f47134c3efec451647934d92a80fce4c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 3 Oct 2025 11:17:29 +0530 Subject: [PATCH 03/32] refactor: more data structure changes --- .../period_closing_voucher.py | 16 +++++++++++----- .../process_period_closing_voucher.json | 18 ++++++++++++++---- .../process_period_closing_voucher.py | 2 ++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index fba261a1484..97c503f5885 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -131,8 +131,11 @@ class PeriodClosingVoucher(AccountsController): frappe.throw(_("Currency of the Closing Account must be {0}").format(company_currency)) def on_submit(self): - self.db_set("gle_processing_status", "In Progress") - self.make_gl_entries() + if frappe.get_single_value("Accounts Settings", "use_legacy_controller_for_pcv"): + self.db_set("gle_processing_status", "In Progress") + self.make_gl_entries() + else: + print("submit") def on_cancel(self): self.ignore_linked_doctypes = ( @@ -141,9 +144,12 @@ class PeriodClosingVoucher(AccountsController): "Payment Ledger Entry", "Account Closing Balance", ) - self.block_if_future_closing_voucher_exists() - self.db_set("gle_processing_status", "In Progress") - self.cancel_gl_entries() + if frappe.get_single_value("Accounts Settings", "use_legacy_controller_for_pcv"): + self.block_if_future_closing_voucher_exists() + self.db_set("gle_processing_status", "In Progress") + self.cancel_gl_entries() + else: + print("cancel") def make_gl_entries(self): if frappe.db.estimate_count("GL Entry") > 100_000: diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index 0c1c7a996cf..5368138a52b 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -5,18 +5,28 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ - "section_break_zqqu" + "parent_pcv", + "status" ], "fields": [ { - "fieldname": "section_break_zqqu", - "fieldtype": "Section Break" + "fieldname": "parent_pcv", + "fieldtype": "Link", + "label": "PCV", + "options": "Period Closing Voucher" + }, + { + "default": "Queued", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Queued\nRunning\nCompleted" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-09-25 15:44:03.534699", + "modified": "2025-10-01 15:57:36.832943", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index da38291b16c..784ae36ae2f 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -14,6 +14,8 @@ class ProcessPeriodClosingVoucher(Document): if TYPE_CHECKING: from frappe.types import DF + parent_pcv: DF.Link | None + status: DF.Literal["Queued", "Running", "Completed"] # end: auto-generated types pass From 0d09d21d2e12a5cc7cecf4c18389fa45051e79b9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 3 Oct 2025 14:24:35 +0530 Subject: [PATCH 04/32] refactor: child table in process pcv --- .../process_period_closing_voucher.json | 11 ++++- .../process_period_closing_voucher.py | 5 +++ .../__init__.py | 0 ...process_period_closing_voucher_detail.json | 40 +++++++++++++++++++ .../process_period_closing_voucher_detail.py | 24 +++++++++++ 5 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 erpnext/accounts/doctype/process_period_closing_voucher_detail/__init__.py create mode 100644 erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json create mode 100644 erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index 5368138a52b..cc85b6fb908 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -6,7 +6,8 @@ "engine": "InnoDB", "field_order": [ "parent_pcv", - "status" + "status", + "dates_to_process" ], "fields": [ { @@ -21,12 +22,18 @@ "fieldtype": "Select", "label": "Status", "options": "Queued\nRunning\nCompleted" + }, + { + "fieldname": "dates_to_process", + "fieldtype": "Table", + "label": "Dates to Process", + "options": "Process Period Closing Voucher Detail" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-10-01 15:57:36.832943", + "modified": "2025-10-03 14:29:55.584225", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 784ae36ae2f..62410b52efb 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -14,6 +14,11 @@ class ProcessPeriodClosingVoucher(Document): if TYPE_CHECKING: from frappe.types import DF + from erpnext.accounts.doctype.process_period_closing_voucher_detail.process_period_closing_voucher_detail import ( + ProcessPeriodClosingVoucherDetail, + ) + + dates_to_process: DF.Table[ProcessPeriodClosingVoucherDetail] parent_pcv: DF.Link | None status: DF.Literal["Queued", "Running", "Completed"] # end: auto-generated types diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/__init__.py b/erpnext/accounts/doctype/process_period_closing_voucher_detail/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json new file mode 100644 index 00000000000..32aa85702e0 --- /dev/null +++ b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json @@ -0,0 +1,40 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-10-01 15:58:17.544153", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "processing_date", + "status" + ], + "fields": [ + { + "fieldname": "processing_date", + "fieldtype": "Date", + "label": "Processing Date" + }, + { + "default": "Queued", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Queued\nRunning\nCompleted" + } + ], + "grid_page_length": 50, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-10-01 16:00:02.221411", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Process Period Closing Voucher Detail", + "owner": "Administrator", + "permissions": [], + "row_format": "Dynamic", + "rows_threshold_for_grid_search": 20, + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py new file mode 100644 index 00000000000..445451ee7c2 --- /dev/null +++ b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py @@ -0,0 +1,24 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ProcessPeriodClosingVoucherDetail(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + processing_date: DF.Date | None + status: DF.Literal["Queued", "Running", "Completed"] + # end: auto-generated types + + pass From f44c908a8de6aad79dceccf89c014a77a920d42f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 8 Oct 2025 10:23:24 +0530 Subject: [PATCH 05/32] refactor: temporarily save balances in JSON --- .../process_period_closing_voucher_detail.json | 10 ++++++++-- .../process_period_closing_voucher_detail.py | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json index 32aa85702e0..780a1d5da15 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json @@ -6,7 +6,8 @@ "engine": "InnoDB", "field_order": [ "processing_date", - "status" + "status", + "closing_balance" ], "fields": [ { @@ -20,13 +21,18 @@ "fieldtype": "Select", "label": "Status", "options": "Queued\nRunning\nCompleted" + }, + { + "fieldname": "closing_balance", + "fieldtype": "JSON", + "label": "Closing Balance" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-10-01 16:00:02.221411", + "modified": "2025-10-08 10:19:29.928526", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher Detail", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py index 445451ee7c2..3ec46f1b181 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py @@ -14,6 +14,7 @@ class ProcessPeriodClosingVoucherDetail(Document): if TYPE_CHECKING: from frappe.types import DF + closing_balance: DF.JSON | None parent: DF.Data parentfield: DF.Data parenttype: DF.Data From 1c92b015423fd89b609bd653739610eea9bae8bf Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 8 Oct 2025 11:02:52 +0530 Subject: [PATCH 06/32] refactor: barebones functions --- .../process_period_closing_voucher.js | 24 +++++++++++--- .../process_period_closing_voucher.json | 20 +++++++++-- .../process_period_closing_voucher.py | 33 +++++++++++++++++-- ...process_period_closing_voucher_detail.json | 5 ++- 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js index 8633cb78f8d..fcd6ee11b80 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js @@ -1,8 +1,24 @@ // Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -// frappe.ui.form.on("Process Period Closing Voucher", { -// refresh(frm) { +frappe.ui.form.on("Process Period Closing Voucher", { + refresh(frm) { + if (frm.doc.docstatus == 1 && ["Queued"].find((x) => x == frm.doc.status)) { + let execute_btn = __("Start"); -// }, -// }); + frm.add_custom_button(execute_btn, () => { + frm.call({ + method: "erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.start_pcv_processing", + args: { + docname: frm.doc.name, + }, + }).then((r) => { + if (!r.exc) { + frappe.show_alert(__("Job Started")); + frm.reload_doc(); + } + }); + }); + } + }, +}); diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index cc85b6fb908..131d7b7a53a 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -7,14 +7,17 @@ "field_order": [ "parent_pcv", "status", - "dates_to_process" + "dates_to_process", + "amended_from" ], "fields": [ { "fieldname": "parent_pcv", "fieldtype": "Link", + "in_list_view": 1, "label": "PCV", - "options": "Period Closing Voucher" + "options": "Period Closing Voucher", + "reqd": 1 }, { "default": "Queued", @@ -28,12 +31,23 @@ "fieldtype": "Table", "label": "Dates to Process", "options": "Process Period Closing Voucher Detail" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Process Period Closing Voucher", + "print_hide": 1, + "read_only": 1, + "search_index": 1 } ], "grid_page_length": 50, "index_web_pages_for_search": 1, + "is_submittable": 1, "links": [], - "modified": "2025-10-03 14:29:55.584225", + "modified": "2025-10-08 10:33:43.742974", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 62410b52efb..39d6b1b0874 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -1,8 +1,11 @@ # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -# import frappe +from datetime import timedelta + +import frappe from frappe.model.document import Document +from frappe.utils import add_days, get_datetime class ProcessPeriodClosingVoucher(Document): @@ -18,9 +21,33 @@ class ProcessPeriodClosingVoucher(Document): ProcessPeriodClosingVoucherDetail, ) + amended_from: DF.Link | None dates_to_process: DF.Table[ProcessPeriodClosingVoucherDetail] - parent_pcv: DF.Link | None + parent_pcv: DF.Link status: DF.Literal["Queued", "Running", "Completed"] # end: auto-generated types - pass + def validate(self): + self.status = "Queued" + self.populate_processing_table() + + def populate_processing_table(self): + self.dates_to_process = [] + pcv = frappe.get_doc("Period Closing Voucher", self.parent_pcv) + start = get_datetime(pcv.period_start_date) + end = get_datetime(pcv.period_end_date) + dates = [start + timedelta(days=x) for x in range((end - start).days + 1)] + for x in dates: + self.append("dates_to_process", {"processing_date": x, "status": "Queued"}) + + +@frappe.whitelist() +def start_pcv_processing(docname: str): + if frappe.db.get_value("Process Period Closing Voucher", docname, "status") == "Queued": + dates_to_process = frappe.db.get_all( + "Process Period Closing Voucher Detail", + filters={"parent": docname, "status": "Queued"}, + fields=["processing_date"], + order_by="processing_date", + limit=4, + ) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json index 780a1d5da15..ab0963417c0 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json @@ -13,18 +13,21 @@ { "fieldname": "processing_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Processing Date" }, { "default": "Queued", "fieldname": "status", "fieldtype": "Select", + "in_list_view": 1, "label": "Status", "options": "Queued\nRunning\nCompleted" }, { "fieldname": "closing_balance", "fieldtype": "JSON", + "in_list_view": 1, "label": "Closing Balance" } ], @@ -32,7 +35,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-10-08 10:19:29.928526", + "modified": "2025-10-08 10:47:50.050341", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher Detail", From c839ebf593082cf44820e92fa6397f633bc011a7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 10 Oct 2025 10:50:03 +0530 Subject: [PATCH 07/32] refactor: stable start, pause, resume and completion stages --- .../process_period_closing_voucher.js | 20 +++++++- .../process_period_closing_voucher.py | 48 ++++++++++++++++++- ...process_period_closing_voucher_detail.json | 4 +- .../process_period_closing_voucher_detail.py | 2 +- 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js index fcd6ee11b80..3b797476128 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js @@ -3,7 +3,7 @@ frappe.ui.form.on("Process Period Closing Voucher", { refresh(frm) { - if (frm.doc.docstatus == 1 && ["Queued"].find((x) => x == frm.doc.status)) { + if (frm.doc.docstatus == 1 && ["Queued", "Paused"].find((x) => x == frm.doc.status)) { let execute_btn = __("Start"); frm.add_custom_button(execute_btn, () => { @@ -20,5 +20,23 @@ frappe.ui.form.on("Process Period Closing Voucher", { }); }); } + + if (frm.doc.docstatus == 1 && ["Running"].find((x) => x == frm.doc.status)) { + let execute_btn = __("Pause"); + + frm.add_custom_button(execute_btn, () => { + frm.call({ + method: "erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.pause_pcv_processing", + args: { + docname: frm.doc.name, + }, + }).then((r) => { + if (!r.exc) { + frappe.show_alert(__("Job Started")); + frm.reload_doc(); + } + }); + }); + } }, }); diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 39d6b1b0874..8a72dca2245 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -6,6 +6,7 @@ from datetime import timedelta import frappe from frappe.model.document import Document from frappe.utils import add_days, get_datetime +from frappe.utils.scheduler import is_scheduler_inactive class ProcessPeriodClosingVoucher(Document): @@ -43,11 +44,54 @@ class ProcessPeriodClosingVoucher(Document): @frappe.whitelist() def start_pcv_processing(docname: str): - if frappe.db.get_value("Process Period Closing Voucher", docname, "status") == "Queued": - dates_to_process = frappe.db.get_all( + if frappe.db.get_value("Process Period Closing Voucher", docname, "status") in ["Queued", "Paused"]: + frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Running") + if dates_to_process := frappe.db.get_all( "Process Period Closing Voucher Detail", filters={"parent": docname, "status": "Queued"}, fields=["processing_date"], order_by="processing_date", limit=4, + ): + if not is_scheduler_inactive(): + for x in dates_to_process: + frappe.enqueue( + method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", + queue="long", + is_async=True, + enqueue_after_commit=True, + docname=docname, + date=x.processing_date, + ) + else: + frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") + + +@frappe.whitelist() +def pause_pcv_processing(docname: str): + frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Paused") + + +def process_individual_date(docname: str, date: str): + if frappe.db.get_value("Process Period Closing Voucher", docname, "status") == "Running": + frappe.db.set_value( + "Process Period Closing Voucher Detail", {"processing_date": date}, "status", "Completed" ) + if next_date_to_process := frappe.db.get_all( + "Process Period Closing Voucher Detail", + filters={"parent": docname, "status": "Queued"}, + fields=["processing_date"], + order_by="processing_date", + limit=1, + ): + if not is_scheduler_inactive(): + frappe.enqueue( + method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", + queue="long", + is_async=True, + enqueue_after_commit=True, + docname=docname, + date=next_date_to_process[0].processing_date, + ) + else: + frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json index ab0963417c0..6d452c18fc0 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json @@ -22,7 +22,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Status", - "options": "Queued\nRunning\nCompleted" + "options": "Queued\nRunning\nPaused\nCompleted" }, { "fieldname": "closing_balance", @@ -35,7 +35,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-10-08 10:47:50.050341", + "modified": "2025-10-09 16:46:37.778199", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher Detail", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py index 3ec46f1b181..f15d917a295 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py @@ -19,7 +19,7 @@ class ProcessPeriodClosingVoucherDetail(Document): parentfield: DF.Data parenttype: DF.Data processing_date: DF.Date | None - status: DF.Literal["Queued", "Running", "Completed"] + status: DF.Literal["Queued", "Running", "Paused", "Completed"] # end: auto-generated types pass From 1a31825409a211225e7c139fb3779b200efe15ee Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 13 Oct 2025 13:40:40 +0530 Subject: [PATCH 08/32] refactor: store closing balance as JSON --- .../process_period_closing_voucher.py | 103 ++++++++++++++---- 1 file changed, 79 insertions(+), 24 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 8a72dca2245..e68aaa6b8cb 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -4,10 +4,14 @@ from datetime import timedelta import frappe +from frappe import qb from frappe.model.document import Document +from frappe.query_builder.functions import Sum from frappe.utils import add_days, get_datetime from frappe.utils.scheduler import is_scheduler_inactive +BACKGROUND = True + class ProcessPeriodClosingVoucher(Document): # begin: auto-generated types @@ -55,43 +59,94 @@ def start_pcv_processing(docname: str): ): if not is_scheduler_inactive(): for x in dates_to_process: - frappe.enqueue( - method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", - queue="long", - is_async=True, - enqueue_after_commit=True, - docname=docname, - date=x.processing_date, - ) + if BACKGROUND: + frappe.enqueue( + method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", + queue="long", + is_async=True, + enqueue_after_commit=True, + docname=docname, + date=x.processing_date, + ) + else: + process_individual_date(docname, x.processing_date) else: frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") @frappe.whitelist() def pause_pcv_processing(docname: str): - frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Paused") + queued_dates = frappe.db.get_all( + "Process Period Closing Voucher Detail", + filters={"parent": docname, "status": "Queued"}, + fields=["name"], + ) + ppcv = qb.DocType("Process Period Closing Voucher") + qb.update(ppcv).set(ppcv.status, "Paused").where(ppcv.name.isin(queued_dates)).run() -def process_individual_date(docname: str, date: str): - if frappe.db.get_value("Process Period Closing Voucher", docname, "status") == "Running": - frappe.db.set_value( - "Process Period Closing Voucher Detail", {"processing_date": date}, "status", "Completed" - ) - if next_date_to_process := frappe.db.get_all( - "Process Period Closing Voucher Detail", - filters={"parent": docname, "status": "Queued"}, - fields=["processing_date"], - order_by="processing_date", - limit=1, - ): - if not is_scheduler_inactive(): +def call_next_date(docname: str): + if next_date_to_process := frappe.db.get_all( + "Process Period Closing Voucher Detail", + filters={"parent": docname, "status": "Queued"}, + fields=["processing_date"], + order_by="processing_date", + limit=1, + ): + next_date_to_process = next_date_to_process[0].processing_date + if not is_scheduler_inactive(): + frappe.db.set_value( + "Process Period Closing Voucher Detail", + {"processing_date": next_date_to_process, "parent": docname}, + "status", + "Running", + ) + if BACKGROUND: frappe.enqueue( method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", queue="long", is_async=True, enqueue_after_commit=True, docname=docname, - date=next_date_to_process[0].processing_date, + date=next_date_to_process, ) - else: + else: + process_individual_date(docname, next_date_to_process) + else: + running = frappe.db.get_all( + "Process Period Closing Voucher Detail", + filters={"parent": docname, "status": "Running"}, + fields=["processing_date"], + order_by="processing_date", + limit=1, + ) + if not running: frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") + + +def process_individual_date(docname: str, date: str): + if frappe.db.get_value("Process Period Closing Voucher", docname, "status") == "Running": + pcv_name = frappe.db.get_value("Process Period Closing Voucher", docname, "parent_pcv") + pcv = frappe.get_doc("Period Closing Voucher", pcv_name) + gle = qb.DocType("GL Entry") + res = ( + qb.from_(gle) + .select(gle.account, Sum(gle.debit).as_("debit"), Sum(gle.credit).as_("credit")) + .where((gle.company.eq(pcv.company)) & (gle.is_cancelled.eq(False)) & (gle.posting_date.eq(date))) + .groupby(gle.account) + .run(as_dict=True) + ) + frappe.db.set_value( + "Process Period Closing Voucher Detail", + {"processing_date": date, "parent": docname}, + "status", + "Completed", + ) + frappe.db.set_value( + "Process Period Closing Voucher Detail", + {"processing_date": date, "parent": docname}, + "closing_balance", + frappe.json.dumps(res), + ) + + call_next_date(docname) From 1846de0d493494311555b956865649ba1dd2de47 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 14 Oct 2025 16:31:46 +0530 Subject: [PATCH 09/32] refactor: store daily balances based on dimensions key dimensions key is manually converted to string --- .../process_period_closing_voucher.py | 98 ++++++++++++++++--- 1 file changed, 85 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index e68aaa6b8cb..5786c319e39 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -7,10 +7,10 @@ import frappe from frappe import qb from frappe.model.document import Document from frappe.query_builder.functions import Sum -from frappe.utils import add_days, get_datetime +from frappe.utils import add_days, flt, get_datetime from frappe.utils.scheduler import is_scheduler_inactive -BACKGROUND = True +BACKGROUND = False class ProcessPeriodClosingVoucher(Document): @@ -76,13 +76,16 @@ def start_pcv_processing(docname: str): @frappe.whitelist() def pause_pcv_processing(docname: str): + ppcv = qb.DocType("Process Period Closing Voucher") + qb.update(ppcv).set(ppcv.status, "Paused").where(ppcv.name.eq(docname)).run() + queued_dates = frappe.db.get_all( "Process Period Closing Voucher Detail", filters={"parent": docname, "status": "Queued"}, - fields=["name"], + pluck="name", ) - ppcv = qb.DocType("Process Period Closing Voucher") - qb.update(ppcv).set(ppcv.status, "Paused").where(ppcv.name.isin(queued_dates)).run() + ppcvd = qb.DocType("Process Period Closing Voucher Detail") + qb.update(ppcvd).set(ppcvd.status, "Paused").where(ppcvd.name.isin(queued_dates)).run() def call_next_date(docname: str): @@ -121,32 +124,101 @@ def call_next_date(docname: str): limit=1, ) if not running: + # TODO: Generate GL and Account Closing Balance frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") +def get_dimensions(): + from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( + get_accounting_dimensions, + ) + + default_dimensions = ["cost_center", "finance_book", "project"] + dimensions = default_dimensions + get_accounting_dimensions() + return dimensions + + +def get_key(res): + return tuple([res.get(dimension) for dimension in get_dimensions()]) + + def process_individual_date(docname: str, date: str): if frappe.db.get_value("Process Period Closing Voucher", docname, "status") == "Running": pcv_name = frappe.db.get_value("Process Period Closing Voucher", docname, "parent_pcv") pcv = frappe.get_doc("Period Closing Voucher", pcv_name) - gle = qb.DocType("GL Entry") - res = ( - qb.from_(gle) - .select(gle.account, Sum(gle.debit).as_("debit"), Sum(gle.credit).as_("credit")) - .where((gle.company.eq(pcv.company)) & (gle.is_cancelled.eq(False)) & (gle.posting_date.eq(date))) - .groupby(gle.account) - .run(as_dict=True) + + dimensions = get_dimensions() + + p_l_accounts = frappe.db.get_all( + "Account", filters={"company": pcv.company, "report_type": "Profit and Loss"}, pluck="name" ) + + gle = qb.DocType("GL Entry") + query = qb.from_(gle).select(gle.account) + for dim in dimensions: + query = query.select(gle[dim]) + + query = query.select( + Sum(gle.debit).as_("debit"), + Sum(gle.credit).as_("credit"), + Sum(gle.debit_in_account_currency).as_("debit_in_account_currency"), + Sum(gle.credit_in_account_currency).as_("credit_in_account_currency"), + ).where( + (gle.company.eq(pcv.company)) + & (gle.is_cancelled.eq(0)) + & (gle.posting_date.eq(date)) + & (gle.account.isin(p_l_accounts)) + ) + + query = query.groupby(gle.account) + for dim in dimensions: + query = query.groupby(gle[dim]) + + res = query.run(as_dict=True) + + dimension_wise_acc_balances = frappe._dict() + for x in res: + dimension_key = get_key(x) + dimension_wise_acc_balances.setdefault(dimension_key, frappe._dict()).setdefault( + x.account, + frappe._dict( + { + "debit_in_account_currency": 0, + "credit_in_account_currency": 0, + "debit": 0, + "credit": 0, + "account_currency": x.account_currency, + } + ), + ) + dimension_wise_acc_balances[dimension_key][x.account].debit_in_account_currency += flt( + x.debit_in_account_currency + ) + dimension_wise_acc_balances[dimension_key][x.account].credit_in_account_currency += flt( + x.credit_in_account_currency + ) + dimension_wise_acc_balances[dimension_key][x.account].debit += flt(x.debit) + dimension_wise_acc_balances[dimension_key][x.account].credit += flt(x.credit) + frappe.db.set_value( "Process Period Closing Voucher Detail", {"processing_date": date, "parent": docname}, "status", "Completed", ) + + # convert dict keys to json compliant json dictionary keys + json_dict = {} + for k, v in dimension_wise_acc_balances.items(): + str_key = [str(x) for x in k] + str_key = ",".join(str_key) + json_dict[str_key] = v + frappe.db.set_value( "Process Period Closing Voucher Detail", {"processing_date": date, "parent": docname}, "closing_balance", - frappe.json.dumps(res), + frappe.json.dumps(json_dict), ) call_next_date(docname) From e88074ddec52cd6a99b1c8afeaf4bd387ebc9acd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 15 Oct 2025 13:09:29 +0530 Subject: [PATCH 10/32] refactor: build and post gl entries --- .../process_period_closing_voucher.js | 18 ++ .../process_period_closing_voucher.json | 11 +- .../process_period_closing_voucher.py | 166 +++++++++++++++++- 3 files changed, 191 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js index 3b797476128..8f231f241be 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js @@ -38,5 +38,23 @@ frappe.ui.form.on("Process Period Closing Voucher", { }); }); } + + if (frm.doc.docstatus == 1 && ["Completed"].find((x) => x == frm.doc.status)) { + let execute_btn = __("Call Next"); + + frm.add_custom_button(execute_btn, () => { + frm.call({ + method: "erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.call_next_date", + args: { + docname: frm.doc.name, + }, + }).then((r) => { + if (!r.exc) { + frappe.show_alert(__("Call next date for processing")); + frm.reload_doc(); + } + }); + }); + } }, }); diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index 131d7b7a53a..5db50764153 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -7,6 +7,7 @@ "field_order": [ "parent_pcv", "status", + "total", "dates_to_process", "amended_from" ], @@ -24,12 +25,14 @@ "fieldname": "status", "fieldtype": "Select", "label": "Status", + "no_copy": 1, "options": "Queued\nRunning\nCompleted" }, { "fieldname": "dates_to_process", "fieldtype": "Table", "label": "Dates to Process", + "no_copy": 1, "options": "Process Period Closing Voucher Detail" }, { @@ -41,13 +44,19 @@ "print_hide": 1, "read_only": 1, "search_index": 1 + }, + { + "fieldname": "total", + "fieldtype": "JSON", + "label": "Total", + "no_copy": 1 } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-10-08 10:33:43.742974", + "modified": "2025-10-15 12:46:03.627166", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 5786c319e39..f9c668769bb 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -30,6 +30,7 @@ class ProcessPeriodClosingVoucher(Document): dates_to_process: DF.Table[ProcessPeriodClosingVoucherDetail] parent_pcv: DF.Link status: DF.Literal["Queued", "Running", "Completed"] + total: DF.JSON | None # end: auto-generated types def validate(self): @@ -88,6 +89,69 @@ def pause_pcv_processing(docname: str): qb.update(ppcvd).set(ppcvd.status, "Paused").where(ppcvd.name.isin(queued_dates)).run() +def get_gle_for_pl_account(pcv, acc, balances, dimensions): + balance_in_account_currency = flt(balances.debit_in_account_currency) - flt( + balances.credit_in_account_currency + ) + balance_in_company_currency = flt(balances.debit) - flt(balances.credit) + gl_entry = frappe._dict( + { + "company": pcv.company, + "posting_date": pcv.period_end_date, + "account": acc, + "account_currency": balances.account_currency, + "debit_in_account_currency": abs(balance_in_account_currency) + if balance_in_account_currency < 0 + else 0, + "debit": abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0, + "credit_in_account_currency": abs(balance_in_account_currency) + if balance_in_account_currency > 0 + else 0, + "credit": abs(balance_in_company_currency) if balance_in_company_currency > 0 else 0, + "is_period_closing_voucher_entry": 1, + "voucher_type": "Period Closing Voucher", + "voucher_no": pcv.name, + "fiscal_year": pcv.fiscal_year, + "remarks": pcv.remarks, + "is_opening": "No", + } + ) + # update dimensions + for i, dimension in enumerate(dimensions): + gl_entry[dimension] = dimensions[i] + return gl_entry + + +def get_gle_for_closing_account(pcv, dimension_balance, dimensions): + balance_in_company_currency = flt(dimension_balance.balance_in_company_currency) + debit = balance_in_company_currency if balance_in_company_currency > 0 else 0 + credit = abs(balance_in_company_currency) if balance_in_company_currency < 0 else 0 + + gl_entry = frappe._dict( + { + "company": pcv.company, + "posting_date": pcv.period_end_date, + "account": pcv.closing_account_head, + "account_currency": frappe.db.get_value("Account", pcv.closing_account_head, "account_currency"), + "debit_in_account_currency": debit, + "debit": debit, + "credit_in_account_currency": credit, + "credit": credit, + "is_period_closing_voucher_entry": 1, + "voucher_type": "Period Closing Voucher", + "voucher_no": pcv.name, + "fiscal_year": pcv.fiscal_year, + "remarks": pcv.remarks, + "is_opening": "No", + } + ) + # update dimensions + for i, dimension in enumerate(dimensions): + gl_entry[dimension] = dimensions[i] + return gl_entry + + +@frappe.whitelist() def call_next_date(docname: str): if next_date_to_process := frappe.db.get_all( "Process Period Closing Voucher Detail", @@ -123,8 +187,82 @@ def call_next_date(docname: str): order_by="processing_date", limit=1, ) + # TODO: ensure all dates are processed if not running: - # TODO: Generate GL and Account Closing Balance + # Calculate total balances for PCV period + # Build dictionary back + dimension_wise_acc_balances = {} + ppcv = frappe.get_doc("Process Period Closing Voucher", docname) + for x in [x.closing_balance for x in ppcv.dates_to_process]: + bal = frappe.json.loads(x) + for dimensions, account_balances in bal.items(): + dim_key = tuple([None if x == "None" else x for x in dimensions.split(",")]) + obj = dimension_wise_acc_balances.setdefault(dim_key, frappe._dict()) + + for acc, bal in account_balances.items(): + if acc != "balances": + bal_dict = obj.setdefault( + acc, + frappe._dict( + { + "debit_in_account_currency": 0, + "credit_in_account_currency": 0, + "debit": 0, + "credit": 0, + "account_currency": bal["account_currency"], + } + ), + ) + bal_dict["debit_in_account_currency"] += bal["debit_in_account_currency"] + bal_dict["credit_in_account_currency"] += bal["credit_in_account_currency"] + bal_dict["debit"] += bal["debit"] + bal_dict["credit"] += bal["credit"] + else: + bal_dict = obj.setdefault( + "balances", + frappe._dict( + { + "balance_in_company_currency": 0, + "balance_in_account_currency": 0, + } + ), + ) + bal_dict["balance_in_company_currency"] += bal["balance_in_company_currency"] + bal_dict["balance_in_account_currency"] += bal["balance_in_account_currency"] + + # convert dict keys to json compliant json dictionary keys + json_dict = {} + for k, v in dimension_wise_acc_balances.items(): + str_key = [str(x) for x in k] + str_key = ",".join(str_key) + json_dict[str_key] = v + + frappe.db.set_value( + "Process Period Closing Voucher", docname, "total", frappe.json.dumps(json_dict) + ) + + # Build GL map + pcv = frappe.get_doc("Period Closing Voucher", ppcv.parent_pcv) + pl_accounts_reverse_gle = [] + closing_account_gle = [] + + for dimensions, account_balances in dimension_wise_acc_balances.items(): + for acc, balances in account_balances.items(): + balance_in_company_currency = flt(balances.debit) - flt(balances.credit) + if balance_in_company_currency: + pl_accounts_reverse_gle.append(get_gle_for_pl_account(pcv, acc, balances, dimensions)) + + # closing liability account + closing_account_gle.append( + get_gle_for_closing_account(pcv, account_balances["balances"], dimensions) + ) + + gl_entries = pl_accounts_reverse_gle + closing_account_gle + from erpnext.accounts.general_ledger import make_gl_entries + + if gl_entries: + make_gl_entries(gl_entries, merge_entries=False) + frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") @@ -138,7 +276,7 @@ def get_dimensions(): return dimensions -def get_key(res): +def get_dimension_key(res): return tuple([res.get(dimension) for dimension in get_dimensions()]) @@ -163,6 +301,7 @@ def process_individual_date(docname: str, date: str): Sum(gle.credit).as_("credit"), Sum(gle.debit_in_account_currency).as_("debit_in_account_currency"), Sum(gle.credit_in_account_currency).as_("credit_in_account_currency"), + gle.account_currency, ).where( (gle.company.eq(pcv.company)) & (gle.is_cancelled.eq(0)) @@ -178,7 +317,7 @@ def process_individual_date(docname: str, date: str): dimension_wise_acc_balances = frappe._dict() for x in res: - dimension_key = get_key(x) + dimension_key = get_dimension_key(x) dimension_wise_acc_balances.setdefault(dimension_key, frappe._dict()).setdefault( x.account, frappe._dict( @@ -200,6 +339,27 @@ def process_individual_date(docname: str, date: str): dimension_wise_acc_balances[dimension_key][x.account].debit += flt(x.debit) dimension_wise_acc_balances[dimension_key][x.account].credit += flt(x.credit) + # dimension-wise total balances + dimension_wise_acc_balances[dimension_key].setdefault( + "balances", + frappe._dict( + { + "balance_in_account_currency": 0, + "balance_in_company_currency": 0, + } + ), + ) + + balance_in_account_currency = flt(x.debit_in_account_currency) - flt(x.credit_in_account_currency) + balance_in_company_currency = flt(x.debit) - flt(x.credit) + + dimension_wise_acc_balances[dimension_key][ + "balances" + ].balance_in_account_currency += balance_in_account_currency + dimension_wise_acc_balances[dimension_key][ + "balances" + ].balance_in_company_currency += balance_in_company_currency + frappe.db.set_value( "Process Period Closing Voucher Detail", {"processing_date": date, "parent": docname}, From f25ee3c53f1cf8302c43970edf504da7ac1389bf Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Oct 2025 11:54:17 +0530 Subject: [PATCH 11/32] refactor: store results as is and convert at the end --- .../process_period_closing_voucher.py | 148 +++++++----------- 1 file changed, 57 insertions(+), 91 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index f9c668769bb..ce82cbb53cf 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -190,45 +190,16 @@ def call_next_date(docname: str): # TODO: ensure all dates are processed if not running: # Calculate total balances for PCV period - # Build dictionary back - dimension_wise_acc_balances = {} - ppcv = frappe.get_doc("Process Period Closing Voucher", docname) - for x in [x.closing_balance for x in ppcv.dates_to_process]: - bal = frappe.json.loads(x) - for dimensions, account_balances in bal.items(): - dim_key = tuple([None if x == "None" else x for x in dimensions.split(",")]) - obj = dimension_wise_acc_balances.setdefault(dim_key, frappe._dict()) - for acc, bal in account_balances.items(): - if acc != "balances": - bal_dict = obj.setdefault( - acc, - frappe._dict( - { - "debit_in_account_currency": 0, - "credit_in_account_currency": 0, - "debit": 0, - "credit": 0, - "account_currency": bal["account_currency"], - } - ), - ) - bal_dict["debit_in_account_currency"] += bal["debit_in_account_currency"] - bal_dict["credit_in_account_currency"] += bal["credit_in_account_currency"] - bal_dict["debit"] += bal["debit"] - bal_dict["credit"] += bal["credit"] - else: - bal_dict = obj.setdefault( - "balances", - frappe._dict( - { - "balance_in_company_currency": 0, - "balance_in_account_currency": 0, - } - ), - ) - bal_dict["balance_in_company_currency"] += bal["balance_in_company_currency"] - bal_dict["balance_in_account_currency"] += bal["balance_in_account_currency"] + ppcv = frappe.get_doc("Process Period Closing Voucher", docname) + + gl_entries = [] + for x in ppcv.dates_to_process: + closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] + gl_entries.extend(closing_balances) + + # Build dimension wise dictionary from all GLE's + dimension_wise_acc_balances = build_dimension_wise_balance_dict(gl_entries) # convert dict keys to json compliant json dictionary keys json_dict = {} @@ -280,6 +251,53 @@ def get_dimension_key(res): return tuple([res.get(dimension) for dimension in get_dimensions()]) +def build_dimension_wise_balance_dict(gl_entries): + dimension_balances = frappe._dict() + for x in gl_entries: + dimension_key = get_dimension_key(x) + dimension_balances.setdefault(dimension_key, frappe._dict()).setdefault( + x.account, + frappe._dict( + { + "debit_in_account_currency": 0, + "credit_in_account_currency": 0, + "debit": 0, + "credit": 0, + "account_currency": x.account_currency, + } + ), + ) + dimension_balances[dimension_key][x.account].debit_in_account_currency += flt( + x.debit_in_account_currency + ) + dimension_balances[dimension_key][x.account].credit_in_account_currency += flt( + x.credit_in_account_currency + ) + dimension_balances[dimension_key][x.account].debit += flt(x.debit) + dimension_balances[dimension_key][x.account].credit += flt(x.credit) + + # dimension-wise total balances + dimension_balances[dimension_key].setdefault( + "balances", + frappe._dict( + { + "balance_in_account_currency": 0, + "balance_in_company_currency": 0, + } + ), + ) + balance_in_account_currency = flt(x.debit_in_account_currency) - flt(x.credit_in_account_currency) + balance_in_company_currency = flt(x.debit) - flt(x.credit) + dimension_balances[dimension_key][ + "balances" + ].balance_in_account_currency += balance_in_account_currency + dimension_balances[dimension_key][ + "balances" + ].balance_in_company_currency += balance_in_company_currency + + return dimension_balances + + def process_individual_date(docname: str, date: str): if frappe.db.get_value("Process Period Closing Voucher", docname, "status") == "Running": pcv_name = frappe.db.get_value("Process Period Closing Voucher", docname, "parent_pcv") @@ -315,51 +333,6 @@ def process_individual_date(docname: str, date: str): res = query.run(as_dict=True) - dimension_wise_acc_balances = frappe._dict() - for x in res: - dimension_key = get_dimension_key(x) - dimension_wise_acc_balances.setdefault(dimension_key, frappe._dict()).setdefault( - x.account, - frappe._dict( - { - "debit_in_account_currency": 0, - "credit_in_account_currency": 0, - "debit": 0, - "credit": 0, - "account_currency": x.account_currency, - } - ), - ) - dimension_wise_acc_balances[dimension_key][x.account].debit_in_account_currency += flt( - x.debit_in_account_currency - ) - dimension_wise_acc_balances[dimension_key][x.account].credit_in_account_currency += flt( - x.credit_in_account_currency - ) - dimension_wise_acc_balances[dimension_key][x.account].debit += flt(x.debit) - dimension_wise_acc_balances[dimension_key][x.account].credit += flt(x.credit) - - # dimension-wise total balances - dimension_wise_acc_balances[dimension_key].setdefault( - "balances", - frappe._dict( - { - "balance_in_account_currency": 0, - "balance_in_company_currency": 0, - } - ), - ) - - balance_in_account_currency = flt(x.debit_in_account_currency) - flt(x.credit_in_account_currency) - balance_in_company_currency = flt(x.debit) - flt(x.credit) - - dimension_wise_acc_balances[dimension_key][ - "balances" - ].balance_in_account_currency += balance_in_account_currency - dimension_wise_acc_balances[dimension_key][ - "balances" - ].balance_in_company_currency += balance_in_company_currency - frappe.db.set_value( "Process Period Closing Voucher Detail", {"processing_date": date, "parent": docname}, @@ -367,18 +340,11 @@ def process_individual_date(docname: str, date: str): "Completed", ) - # convert dict keys to json compliant json dictionary keys - json_dict = {} - for k, v in dimension_wise_acc_balances.items(): - str_key = [str(x) for x in k] - str_key = ",".join(str_key) - json_dict[str_key] = v - frappe.db.set_value( "Process Period Closing Voucher Detail", {"processing_date": date, "parent": docname}, "closing_balance", - frappe.json.dumps(json_dict), + frappe.json.dumps(res), ) call_next_date(docname) From 8ba199016ab742e4848170d85f33011549e4f086 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Oct 2025 13:13:33 +0530 Subject: [PATCH 12/32] refactor: for better readability --- .../process_period_closing_voucher.js | 2 +- .../process_period_closing_voucher.py | 194 +++++++++--------- 2 files changed, 100 insertions(+), 96 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js index 8f231f241be..40c760af647 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js @@ -44,7 +44,7 @@ frappe.ui.form.on("Process Period Closing Voucher", { frm.add_custom_button(execute_btn, () => { frm.call({ - method: "erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.call_next_date", + method: "erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.schedule_next_date", args: { docname: frm.doc.name, }, diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index ce82cbb53cf..bdfe63b6806 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -50,6 +50,7 @@ class ProcessPeriodClosingVoucher(Document): @frappe.whitelist() def start_pcv_processing(docname: str): if frappe.db.get_value("Process Period Closing Voucher", docname, "status") in ["Queued", "Paused"]: + # TODO: move this inside if block frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Running") if dates_to_process := frappe.db.get_all( "Process Period Closing Voucher Detail", @@ -152,19 +153,19 @@ def get_gle_for_closing_account(pcv, dimension_balance, dimensions): @frappe.whitelist() -def call_next_date(docname: str): - if next_date_to_process := frappe.db.get_all( +def schedule_next_date(docname: str): + if to_process := frappe.db.get_all( "Process Period Closing Voucher Detail", filters={"parent": docname, "status": "Queued"}, fields=["processing_date"], order_by="processing_date", limit=1, ): - next_date_to_process = next_date_to_process[0].processing_date + next_date = to_process[0].processing_date if not is_scheduler_inactive(): frappe.db.set_value( "Process Period Closing Voucher Detail", - {"processing_date": next_date_to_process, "parent": docname}, + {"processing_date": next_date, "parent": docname}, "status", "Running", ) @@ -175,66 +176,68 @@ def call_next_date(docname: str): is_async=True, enqueue_after_commit=True, docname=docname, - date=next_date_to_process, + date=next_date, ) else: - process_individual_date(docname, next_date_to_process) + process_individual_date(docname, next_date) else: - running = frappe.db.get_all( - "Process Period Closing Voucher Detail", - filters={"parent": docname, "status": "Running"}, - fields=["processing_date"], - order_by="processing_date", - limit=1, - ) - # TODO: ensure all dates are processed - if not running: - # Calculate total balances for PCV period + # summarize, build and post GL + summarize_and_post_ledger_entries(docname) - ppcv = frappe.get_doc("Process Period Closing Voucher", docname) - gl_entries = [] - for x in ppcv.dates_to_process: - closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] - gl_entries.extend(closing_balances) +def summarize_and_post_ledger_entries(docname): + # TODO: ensure all dates are processed + running = frappe.db.get_all( + "Process Period Closing Voucher Detail", + filters={"parent": docname, "status": "Running"}, + fields=["processing_date"], + order_by="processing_date", + limit=1, + ) + if not running: + # calculate balances for whole PCV period + ppcv = frappe.get_doc("Process Period Closing Voucher", docname) - # Build dimension wise dictionary from all GLE's - dimension_wise_acc_balances = build_dimension_wise_balance_dict(gl_entries) + gl_entries = [] + for x in ppcv.dates_to_process: + closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] + gl_entries.extend(closing_balances) - # convert dict keys to json compliant json dictionary keys - json_dict = {} - for k, v in dimension_wise_acc_balances.items(): - str_key = [str(x) for x in k] - str_key = ",".join(str_key) - json_dict[str_key] = v + # build dimension wise dictionary from all GLE's + dimension_wise_acc_balances = build_dimension_wise_balance_dict(gl_entries) - frappe.db.set_value( - "Process Period Closing Voucher", docname, "total", frappe.json.dumps(json_dict) + # convert tuple key to str to make it json compliant + json_dict = {} + for k, v in dimension_wise_acc_balances.items(): + str_key = [str(x) for x in k] + str_key = ",".join(str_key) + json_dict[str_key] = v + + # save + frappe.db.set_value("Process Period Closing Voucher", docname, "total", frappe.json.dumps(json_dict)) + + # build gl map + pcv = frappe.get_doc("Period Closing Voucher", ppcv.parent_pcv) + pl_accounts_reverse_gle = [] + closing_account_gle = [] + + for dimensions, account_balances in dimension_wise_acc_balances.items(): + for acc, balances in account_balances.items(): + balance_in_company_currency = flt(balances.debit) - flt(balances.credit) + if balance_in_company_currency: + pl_accounts_reverse_gle.append(get_gle_for_pl_account(pcv, acc, balances, dimensions)) + + closing_account_gle.append( + get_gle_for_closing_account(pcv, account_balances["balances"], dimensions) ) - # Build GL map - pcv = frappe.get_doc("Period Closing Voucher", ppcv.parent_pcv) - pl_accounts_reverse_gle = [] - closing_account_gle = [] + gl_entries = pl_accounts_reverse_gle + closing_account_gle + from erpnext.accounts.general_ledger import make_gl_entries - for dimensions, account_balances in dimension_wise_acc_balances.items(): - for acc, balances in account_balances.items(): - balance_in_company_currency = flt(balances.debit) - flt(balances.credit) - if balance_in_company_currency: - pl_accounts_reverse_gle.append(get_gle_for_pl_account(pcv, acc, balances, dimensions)) + if gl_entries: + make_gl_entries(gl_entries, merge_entries=False) - # closing liability account - closing_account_gle.append( - get_gle_for_closing_account(pcv, account_balances["balances"], dimensions) - ) - - gl_entries = pl_accounts_reverse_gle + closing_account_gle - from erpnext.accounts.general_ledger import make_gl_entries - - if gl_entries: - make_gl_entries(gl_entries, merge_entries=False) - - frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") + frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") def get_dimensions(): @@ -299,52 +302,53 @@ def build_dimension_wise_balance_dict(gl_entries): def process_individual_date(docname: str, date: str): - if frappe.db.get_value("Process Period Closing Voucher", docname, "status") == "Running": - pcv_name = frappe.db.get_value("Process Period Closing Voucher", docname, "parent_pcv") - pcv = frappe.get_doc("Period Closing Voucher", pcv_name) + if frappe.db.get_value("Process Period Closing Voucher", docname, "status") != "Running": + return - dimensions = get_dimensions() + pcv_name = frappe.db.get_value("Process Period Closing Voucher", docname, "parent_pcv") + company = frappe.db.get_value("Period Closing Voucher", pcv_name, "company") - p_l_accounts = frappe.db.get_all( - "Account", filters={"company": pcv.company, "report_type": "Profit and Loss"}, pluck="name" - ) + dimensions = get_dimensions() - gle = qb.DocType("GL Entry") - query = qb.from_(gle).select(gle.account) - for dim in dimensions: - query = query.select(gle[dim]) + p_l_accounts = frappe.db.get_all( + "Account", filters={"company": company, "report_type": "Profit and Loss"}, pluck="name" + ) - query = query.select( - Sum(gle.debit).as_("debit"), - Sum(gle.credit).as_("credit"), - Sum(gle.debit_in_account_currency).as_("debit_in_account_currency"), - Sum(gle.credit_in_account_currency).as_("credit_in_account_currency"), - gle.account_currency, - ).where( - (gle.company.eq(pcv.company)) - & (gle.is_cancelled.eq(0)) - & (gle.posting_date.eq(date)) - & (gle.account.isin(p_l_accounts)) - ) + # summarize + gle = qb.DocType("GL Entry") + query = qb.from_(gle).select(gle.account) + for dim in dimensions: + query = query.select(gle[dim]) + query = query.select( + Sum(gle.debit).as_("debit"), + Sum(gle.credit).as_("credit"), + Sum(gle.debit_in_account_currency).as_("debit_in_account_currency"), + Sum(gle.credit_in_account_currency).as_("credit_in_account_currency"), + gle.account_currency, + ).where( + (gle.company.eq(company)) + & (gle.is_cancelled.eq(0)) + & (gle.posting_date.eq(date)) + & (gle.account.isin(p_l_accounts)) + ) + query = query.groupby(gle.account) + for dim in dimensions: + query = query.groupby(gle[dim]) + res = query.run(as_dict=True) - query = query.groupby(gle.account) - for dim in dimensions: - query = query.groupby(gle[dim]) + # save results + frappe.db.set_value( + "Process Period Closing Voucher Detail", + {"processing_date": date, "parent": docname}, + "closing_balance", + frappe.json.dumps(res), + ) + frappe.db.set_value( + "Process Period Closing Voucher Detail", + {"processing_date": date, "parent": docname}, + "status", + "Completed", + ) - res = query.run(as_dict=True) - - frappe.db.set_value( - "Process Period Closing Voucher Detail", - {"processing_date": date, "parent": docname}, - "status", - "Completed", - ) - - frappe.db.set_value( - "Process Period Closing Voucher Detail", - {"processing_date": date, "parent": docname}, - "closing_balance", - frappe.json.dumps(res), - ) - - call_next_date(docname) + # chain call + schedule_next_date(docname) From c738b6d3563a758314ac9d73a0b5faef93ce5409 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Oct 2025 15:34:08 +0530 Subject: [PATCH 13/32] refactor: process on submit --- .../process_period_closing_voucher.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index bdfe63b6806..3d3c022298d 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -46,6 +46,9 @@ class ProcessPeriodClosingVoucher(Document): for x in dates: self.append("dates_to_process", {"processing_date": x, "status": "Queued"}) + def on_submit(self): + start_pcv_processing(self.name) + @frappe.whitelist() def start_pcv_processing(docname: str): From 9e93298f12f5841a1865e73a1f41ce890cc76eaf Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Oct 2025 17:05:43 +0530 Subject: [PATCH 14/32] refactor: more stable pause and resume --- .../process_period_closing_voucher.js | 12 ++-- .../process_period_closing_voucher.py | 57 ++++++++++++++++--- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js index 40c760af647..23d410e9bb0 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js @@ -3,7 +3,7 @@ frappe.ui.form.on("Process Period Closing Voucher", { refresh(frm) { - if (frm.doc.docstatus == 1 && ["Queued", "Paused"].find((x) => x == frm.doc.status)) { + if (frm.doc.docstatus == 1 && ["Queued"].find((x) => x == frm.doc.status)) { let execute_btn = __("Start"); frm.add_custom_button(execute_btn, () => { @@ -32,25 +32,25 @@ frappe.ui.form.on("Process Period Closing Voucher", { }, }).then((r) => { if (!r.exc) { - frappe.show_alert(__("Job Started")); + frappe.show_alert(__("PCV Paused")); frm.reload_doc(); } }); }); } - if (frm.doc.docstatus == 1 && ["Completed"].find((x) => x == frm.doc.status)) { - let execute_btn = __("Call Next"); + if (frm.doc.docstatus == 1 && ["Paused"].find((x) => x == frm.doc.status)) { + let execute_btn = __("Resume"); frm.add_custom_button(execute_btn, () => { frm.call({ - method: "erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.schedule_next_date", + method: "erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.resume_pcv_processing", args: { docname: frm.doc.name, }, }).then((r) => { if (!r.exc) { - frappe.show_alert(__("Call next date for processing")); + frappe.show_alert(__("PCV Resumed")); frm.reload_doc(); } }); diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 3d3c022298d..f3697c7acb3 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -6,11 +6,11 @@ from datetime import timedelta import frappe from frappe import qb from frappe.model.document import Document -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import Count, Sum from frappe.utils import add_days, flt, get_datetime from frappe.utils.scheduler import is_scheduler_inactive -BACKGROUND = False +BACKGROUND = True class ProcessPeriodClosingVoucher(Document): @@ -52,7 +52,7 @@ class ProcessPeriodClosingVoucher(Document): @frappe.whitelist() def start_pcv_processing(docname: str): - if frappe.db.get_value("Process Period Closing Voucher", docname, "status") in ["Queued", "Paused"]: + if frappe.db.get_value("Process Period Closing Voucher", docname, "status") in ["Queued", "Running"]: # TODO: move this inside if block frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Running") if dates_to_process := frappe.db.get_all( @@ -64,6 +64,13 @@ def start_pcv_processing(docname: str): ): if not is_scheduler_inactive(): for x in dates_to_process: + frappe.db.set_value( + "Process Period Closing Voucher Detail", + {"processing_date": x.processing_date, "parent": docname}, + "status", + "Running", + ) + if BACKGROUND: frappe.enqueue( method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", @@ -84,13 +91,28 @@ def pause_pcv_processing(docname: str): ppcv = qb.DocType("Process Period Closing Voucher") qb.update(ppcv).set(ppcv.status, "Paused").where(ppcv.name.eq(docname)).run() - queued_dates = frappe.db.get_all( + if queued_dates := frappe.db.get_all( "Process Period Closing Voucher Detail", filters={"parent": docname, "status": "Queued"}, pluck="name", - ) - ppcvd = qb.DocType("Process Period Closing Voucher Detail") - qb.update(ppcvd).set(ppcvd.status, "Paused").where(ppcvd.name.isin(queued_dates)).run() + ): + ppcvd = qb.DocType("Process Period Closing Voucher Detail") + qb.update(ppcvd).set(ppcvd.status, "Paused").where(ppcvd.name.isin(queued_dates)).run() + + +@frappe.whitelist() +def resume_pcv_processing(docname: str): + ppcv = qb.DocType("Process Period Closing Voucher") + qb.update(ppcv).set(ppcv.status, "Running").where(ppcv.name.eq(docname)).run() + + if paused_dates := frappe.db.get_all( + "Process Period Closing Voucher Detail", + filters={"parent": docname, "status": "Paused"}, + pluck="name", + ): + ppcvd = qb.DocType("Process Period Closing Voucher Detail") + qb.update(ppcvd).set(ppcvd.status, "Queued").where(ppcvd.name.isin(paused_dates)).run() + start_pcv_processing(docname) def get_gle_for_pl_account(pcv, acc, balances, dimensions): @@ -185,7 +207,18 @@ def schedule_next_date(docname: str): process_individual_date(docname, next_date) else: # summarize, build and post GL - summarize_and_post_ledger_entries(docname) + ppcvd = qb.DocType("Process Period Closing Voucher Detail") + total_no_of_dates = ( + qb.from_(ppcvd).select(Count(ppcvd.star)).where(ppcvd.parent.eq(docname)).run()[0][0] + ) + completed = ( + qb.from_(ppcvd) + .select(Count(ppcvd.star)) + .where(ppcvd.parent.eq(docname) & ppcvd.status.eq("Completed")) + .run()[0][0] + ) + if total_no_of_dates == completed: + summarize_and_post_ledger_entries(docname) def summarize_and_post_ledger_entries(docname): @@ -305,7 +338,12 @@ def build_dimension_wise_balance_dict(gl_entries): def process_individual_date(docname: str, date: str): - if frappe.db.get_value("Process Period Closing Voucher", docname, "status") != "Running": + current_date_status = frappe.db.get_value( + "Process Period Closing Voucher Detail", + {"processing_date": date, "parent": docname}, + "status", + ) + if current_date_status != "Running": return pcv_name = frappe.db.get_value("Process Period Closing Voucher", docname, "parent_pcv") @@ -346,6 +384,7 @@ def process_individual_date(docname: str, date: str): "closing_balance", frappe.json.dumps(res), ) + frappe.db.set_value( "Process Period Closing Voucher Detail", {"processing_date": date, "parent": docname}, From 186d540502130cb66aa53b2aaa2d3855b8561616 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 11:27:03 +0530 Subject: [PATCH 15/32] refactor: maintain report type on each date --- .../process_period_closing_voucher_detail.json | 11 ++++++++++- .../process_period_closing_voucher_detail.py | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json index 6d452c18fc0..b64871b2ff0 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json @@ -6,6 +6,7 @@ "engine": "InnoDB", "field_order": [ "processing_date", + "report_type", "status", "closing_balance" ], @@ -29,13 +30,21 @@ "fieldtype": "JSON", "in_list_view": 1, "label": "Closing Balance" + }, + { + "default": "Profit and Loss", + "fieldname": "report_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Report Type", + "options": "Profit and Loss\nBalance Sheet" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-10-09 16:46:37.778199", + "modified": "2025-10-17 11:28:34.775743", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher Detail", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py index f15d917a295..6bec6622ee0 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py @@ -19,6 +19,7 @@ class ProcessPeriodClosingVoucherDetail(Document): parentfield: DF.Data parenttype: DF.Data processing_date: DF.Date | None + report_type: DF.Literal["Profit and Loss", "Balance Sheet"] status: DF.Literal["Queued", "Running", "Paused", "Completed"] # end: auto-generated types From 324bebfd44e4351c04c90072cc7719fa97c6981a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 11:49:15 +0530 Subject: [PATCH 16/32] refactor: balances for both P&L and Balance sheet accounts --- .../process_period_closing_voucher.py | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index f3697c7acb3..bb31b2fdcca 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -44,7 +44,13 @@ class ProcessPeriodClosingVoucher(Document): end = get_datetime(pcv.period_end_date) dates = [start + timedelta(days=x) for x in range((end - start).days + 1)] for x in dates: - self.append("dates_to_process", {"processing_date": x, "status": "Queued"}) + self.append( + "dates_to_process", + {"processing_date": x, "status": "Queued", "report_type": "Profit and Loss"}, + ) + self.append( + "dates_to_process", {"processing_date": x, "status": "Queued", "report_type": "Balance Sheet"} + ) def on_submit(self): start_pcv_processing(self.name) @@ -58,15 +64,19 @@ def start_pcv_processing(docname: str): if dates_to_process := frappe.db.get_all( "Process Period Closing Voucher Detail", filters={"parent": docname, "status": "Queued"}, - fields=["processing_date"], + fields=["processing_date", "report_type"], order_by="processing_date", - limit=4, + limit=1, ): if not is_scheduler_inactive(): for x in dates_to_process: frappe.db.set_value( "Process Period Closing Voucher Detail", - {"processing_date": x.processing_date, "parent": docname}, + { + "processing_date": x.processing_date, + "parent": docname, + "report_type": x.report_type, + }, "status", "Running", ) @@ -79,9 +89,10 @@ def start_pcv_processing(docname: str): enqueue_after_commit=True, docname=docname, date=x.processing_date, + report_type=x.report_type, ) else: - process_individual_date(docname, x.processing_date) + process_individual_date(docname, x.processing_date, x.report_type) else: frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") @@ -182,15 +193,16 @@ def schedule_next_date(docname: str): if to_process := frappe.db.get_all( "Process Period Closing Voucher Detail", filters={"parent": docname, "status": "Queued"}, - fields=["processing_date"], + fields=["processing_date", "report_type"], order_by="processing_date", - limit=1, + limit=4, ): next_date = to_process[0].processing_date + report_type = to_process[0].report_type if not is_scheduler_inactive(): frappe.db.set_value( "Process Period Closing Voucher Detail", - {"processing_date": next_date, "parent": docname}, + {"processing_date": next_date, "parent": docname, "report_type": report_type}, "status", "Running", ) @@ -202,9 +214,10 @@ def schedule_next_date(docname: str): enqueue_after_commit=True, docname=docname, date=next_date, + report_type=report_type, ) else: - process_individual_date(docname, next_date) + process_individual_date(docname, next_date, report_type) else: # summarize, build and post GL ppcvd = qb.DocType("Process Period Closing Voucher Detail") @@ -236,8 +249,9 @@ def summarize_and_post_ledger_entries(docname): gl_entries = [] for x in ppcv.dates_to_process: - closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] - gl_entries.extend(closing_balances) + if x.report_type == "Profit and Loss": + closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] + gl_entries.extend(closing_balances) # build dimension wise dictionary from all GLE's dimension_wise_acc_balances = build_dimension_wise_balance_dict(gl_entries) @@ -337,10 +351,10 @@ def build_dimension_wise_balance_dict(gl_entries): return dimension_balances -def process_individual_date(docname: str, date: str): +def process_individual_date(docname: str, date: str, report_type): current_date_status = frappe.db.get_value( "Process Period Closing Voucher Detail", - {"processing_date": date, "parent": docname}, + {"processing_date": date, "parent": docname, "report_type": report_type}, "status", ) if current_date_status != "Running": @@ -351,8 +365,8 @@ def process_individual_date(docname: str, date: str): dimensions = get_dimensions() - p_l_accounts = frappe.db.get_all( - "Account", filters={"company": company, "report_type": "Profit and Loss"}, pluck="name" + accounts = frappe.db.get_all( + "Account", filters={"company": company, "report_type": report_type}, pluck="name" ) # summarize @@ -370,7 +384,7 @@ def process_individual_date(docname: str, date: str): (gle.company.eq(company)) & (gle.is_cancelled.eq(0)) & (gle.posting_date.eq(date)) - & (gle.account.isin(p_l_accounts)) + & (gle.account.isin(accounts)) ) query = query.groupby(gle.account) for dim in dimensions: @@ -380,14 +394,14 @@ def process_individual_date(docname: str, date: str): # save results frappe.db.set_value( "Process Period Closing Voucher Detail", - {"processing_date": date, "parent": docname}, + {"processing_date": date, "parent": docname, "report_type": report_type}, "closing_balance", frappe.json.dumps(res), ) frappe.db.set_value( "Process Period Closing Voucher Detail", - {"processing_date": date, "parent": docname}, + {"processing_date": date, "parent": docname, "report_type": report_type}, "status", "Completed", ) From cef879bb3b6d50f4c61ddd01d63d774d32a7cb58 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 13:05:03 +0530 Subject: [PATCH 17/32] chore: rename closing balance field --- .../process_period_closing_voucher.json | 8 ++++---- .../process_period_closing_voucher.py | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index 5db50764153..aebdfd1fb75 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -7,7 +7,7 @@ "field_order": [ "parent_pcv", "status", - "total", + "p_l_closing_balance", "dates_to_process", "amended_from" ], @@ -46,9 +46,9 @@ "search_index": 1 }, { - "fieldname": "total", + "fieldname": "p_l_closing_balance", "fieldtype": "JSON", - "label": "Total", + "label": "P&L Closing Balance", "no_copy": 1 } ], @@ -56,7 +56,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-10-15 12:46:03.627166", + "modified": "2025-10-17 13:04:26.353250", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index bb31b2fdcca..523a3b23bf5 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -28,9 +28,9 @@ class ProcessPeriodClosingVoucher(Document): amended_from: DF.Link | None dates_to_process: DF.Table[ProcessPeriodClosingVoucherDetail] + p_l_closing_balance: DF.JSON | None parent_pcv: DF.Link status: DF.Literal["Queued", "Running", "Completed"] - total: DF.JSON | None # end: auto-generated types def validate(self): @@ -264,7 +264,9 @@ def summarize_and_post_ledger_entries(docname): json_dict[str_key] = v # save - frappe.db.set_value("Process Period Closing Voucher", docname, "total", frappe.json.dumps(json_dict)) + frappe.db.set_value( + "Process Period Closing Voucher", docname, "p_l_closing_balance", frappe.json.dumps(json_dict) + ) # build gl map pcv = frappe.get_doc("Period Closing Voucher", ppcv.parent_pcv) From 86edacb781107211d99604fc9c32312ba72dd3d4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 13:16:56 +0530 Subject: [PATCH 18/32] refactor: populate opening balances calculation table --- .../process_period_closing_voucher.json | 10 ++++- .../process_period_closing_voucher.py | 37 +++++++++++++++---- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index aebdfd1fb75..6ad27c880b1 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -9,6 +9,7 @@ "status", "p_l_closing_balance", "dates_to_process", + "opening_balances", "amended_from" ], "fields": [ @@ -50,13 +51,20 @@ "fieldtype": "JSON", "label": "P&L Closing Balance", "no_copy": 1 + }, + { + "fieldname": "opening_balances", + "fieldtype": "Table", + "label": "Opening Balances", + "no_copy": 1, + "options": "Process Period Closing Voucher Detail" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-10-17 13:04:26.353250", + "modified": "2025-10-17 13:10:04.024903", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 523a3b23bf5..9939b61dc1e 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -6,7 +6,7 @@ from datetime import timedelta import frappe from frappe import qb from frappe.model.document import Document -from frappe.query_builder.functions import Count, Sum +from frappe.query_builder.functions import Count, Max, Min, Sum from frappe.utils import add_days, flt, get_datetime from frappe.utils.scheduler import is_scheduler_inactive @@ -28,6 +28,7 @@ class ProcessPeriodClosingVoucher(Document): amended_from: DF.Link | None dates_to_process: DF.Table[ProcessPeriodClosingVoucherDetail] + opening_balances: DF.Table[ProcessPeriodClosingVoucherDetail] p_l_closing_balance: DF.JSON | None parent_pcv: DF.Link status: DF.Literal["Queued", "Running", "Completed"] @@ -35,14 +36,20 @@ class ProcessPeriodClosingVoucher(Document): def validate(self): self.status = "Queued" - self.populate_processing_table() + self.populate_processing_tables() - def populate_processing_table(self): + def populate_processing_tables(self): + self.generate_pcv_dates() + self.generate_opening_balances_dates() + + def get_dates(self, start, end): + return [start + timedelta(days=x) for x in range((end - start).days + 1)] + + def generate_pcv_dates(self): self.dates_to_process = [] pcv = frappe.get_doc("Period Closing Voucher", self.parent_pcv) - start = get_datetime(pcv.period_start_date) - end = get_datetime(pcv.period_end_date) - dates = [start + timedelta(days=x) for x in range((end - start).days + 1)] + + dates = self.get_dates(get_datetime(pcv.period_start_date), get_datetime(pcv.period_end_date)) for x in dates: self.append( "dates_to_process", @@ -52,6 +59,22 @@ class ProcessPeriodClosingVoucher(Document): "dates_to_process", {"processing_date": x, "status": "Queued", "report_type": "Balance Sheet"} ) + def generate_opening_balances_dates(self): + self.opening_balances = [] + + pcv = frappe.get_doc("Period Closing Voucher", self.parent_pcv) + if pcv.is_first_period_closing_voucher(): + gl = qb.DocType("GL Entry") + min = qb.from_(gl).select(Min(gl.posting_date)).where(gl.company.eq(pcv.company)).run()[0][0] + max = qb.from_(gl).select(Max(gl.posting_date)).where(gl.company.eq(pcv.company)).run()[0][0] + + dates = self.get_dates(get_datetime(min), get_datetime(max)) + for x in dates: + self.append( + "opening_balances", + {"processing_date": x, "status": "Queued", "report_type": "Balance Sheet"}, + ) + def on_submit(self): start_pcv_processing(self.name) @@ -66,7 +89,7 @@ def start_pcv_processing(docname: str): filters={"parent": docname, "status": "Queued"}, fields=["processing_date", "report_type"], order_by="processing_date", - limit=1, + limit=4, ): if not is_scheduler_inactive(): for x in dates_to_process: From 643e1fdce8b30d532ff7bf4128b385d34e2aa047 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 14:42:29 +0530 Subject: [PATCH 19/32] refactor: calculate both balances from single queue --- .../process_period_closing_voucher.json | 22 +++--- .../process_period_closing_voucher.py | 67 ++++++++++++------- 2 files changed, 52 insertions(+), 37 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index 6ad27c880b1..9b89b405eed 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -8,8 +8,8 @@ "parent_pcv", "status", "p_l_closing_balance", - "dates_to_process", - "opening_balances", + "normal_balances", + "z_opening_balances", "amended_from" ], "fields": [ @@ -29,13 +29,6 @@ "no_copy": 1, "options": "Queued\nRunning\nCompleted" }, - { - "fieldname": "dates_to_process", - "fieldtype": "Table", - "label": "Dates to Process", - "no_copy": 1, - "options": "Process Period Closing Voucher Detail" - }, { "fieldname": "amended_from", "fieldtype": "Link", @@ -53,7 +46,14 @@ "no_copy": 1 }, { - "fieldname": "opening_balances", + "fieldname": "normal_balances", + "fieldtype": "Table", + "label": "Dates to Process", + "no_copy": 1, + "options": "Process Period Closing Voucher Detail" + }, + { + "fieldname": "z_opening_balances", "fieldtype": "Table", "label": "Opening Balances", "no_copy": 1, @@ -64,7 +64,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-10-17 13:10:04.024903", + "modified": "2025-10-17 13:44:33.397172", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 9939b61dc1e..54363ec2889 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -27,11 +27,11 @@ class ProcessPeriodClosingVoucher(Document): ) amended_from: DF.Link | None - dates_to_process: DF.Table[ProcessPeriodClosingVoucherDetail] - opening_balances: DF.Table[ProcessPeriodClosingVoucherDetail] + normal_balances: DF.Table[ProcessPeriodClosingVoucherDetail] p_l_closing_balance: DF.JSON | None parent_pcv: DF.Link status: DF.Literal["Queued", "Running", "Completed"] + z_opening_balances: DF.Table[ProcessPeriodClosingVoucherDetail] # end: auto-generated types def validate(self): @@ -46,21 +46,21 @@ class ProcessPeriodClosingVoucher(Document): return [start + timedelta(days=x) for x in range((end - start).days + 1)] def generate_pcv_dates(self): - self.dates_to_process = [] + self.normal_balances = [] pcv = frappe.get_doc("Period Closing Voucher", self.parent_pcv) dates = self.get_dates(get_datetime(pcv.period_start_date), get_datetime(pcv.period_end_date)) for x in dates: self.append( - "dates_to_process", + "normal_balances", {"processing_date": x, "status": "Queued", "report_type": "Profit and Loss"}, ) self.append( - "dates_to_process", {"processing_date": x, "status": "Queued", "report_type": "Balance Sheet"} + "normal_balances", {"processing_date": x, "status": "Queued", "report_type": "Balance Sheet"} ) def generate_opening_balances_dates(self): - self.opening_balances = [] + self.z_opening_balances = [] pcv = frappe.get_doc("Period Closing Voucher", self.parent_pcv) if pcv.is_first_period_closing_voucher(): @@ -71,7 +71,7 @@ class ProcessPeriodClosingVoucher(Document): dates = self.get_dates(get_datetime(min), get_datetime(max)) for x in dates: self.append( - "opening_balances", + "z_opening_balances", {"processing_date": x, "status": "Queued", "report_type": "Balance Sheet"}, ) @@ -84,21 +84,22 @@ def start_pcv_processing(docname: str): if frappe.db.get_value("Process Period Closing Voucher", docname, "status") in ["Queued", "Running"]: # TODO: move this inside if block frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Running") - if dates_to_process := frappe.db.get_all( + if normal_balances := frappe.db.get_all( "Process Period Closing Voucher Detail", filters={"parent": docname, "status": "Queued"}, - fields=["processing_date", "report_type"], - order_by="processing_date", + fields=["processing_date", "report_type", "parentfield"], + order_by="parentfield, idx, processing_date", limit=4, ): if not is_scheduler_inactive(): - for x in dates_to_process: + for x in normal_balances: frappe.db.set_value( "Process Period Closing Voucher Detail", { "processing_date": x.processing_date, "parent": docname, "report_type": x.report_type, + "parentfield": x.parentfield, }, "status", "Running", @@ -113,9 +114,10 @@ def start_pcv_processing(docname: str): docname=docname, date=x.processing_date, report_type=x.report_type, + parentfield=x.parentfield, ) else: - process_individual_date(docname, x.processing_date, x.report_type) + process_individual_date(docname, x.processing_date, x.report_type, x.parentfield) else: frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") @@ -216,16 +218,19 @@ def schedule_next_date(docname: str): if to_process := frappe.db.get_all( "Process Period Closing Voucher Detail", filters={"parent": docname, "status": "Queued"}, - fields=["processing_date", "report_type"], - order_by="processing_date", - limit=4, + fields=["processing_date", "report_type", "parentfield"], + order_by="parentfield, idx, processing_date", + limit=1, ): - next_date = to_process[0].processing_date - report_type = to_process[0].report_type if not is_scheduler_inactive(): frappe.db.set_value( "Process Period Closing Voucher Detail", - {"processing_date": next_date, "parent": docname, "report_type": report_type}, + { + "processing_date": to_process[0].processing_date, + "parent": docname, + "report_type": to_process[0].report_type, + "parentfield": to_process[0].parentfield, + }, "status", "Running", ) @@ -236,11 +241,17 @@ def schedule_next_date(docname: str): is_async=True, enqueue_after_commit=True, docname=docname, - date=next_date, - report_type=report_type, + date=to_process[0].processing_date, + report_type=to_process[0].report_type, + parentfield=to_process[0].parentfield, ) else: - process_individual_date(docname, next_date, report_type) + process_individual_date( + docname, + to_process[0].processing_date, + to_process[0].report_type, + to_process[0].parentfield, + ) else: # summarize, build and post GL ppcvd = qb.DocType("Process Period Closing Voucher Detail") @@ -271,7 +282,7 @@ def summarize_and_post_ledger_entries(docname): ppcv = frappe.get_doc("Process Period Closing Voucher", docname) gl_entries = [] - for x in ppcv.dates_to_process: + for x in ppcv.normal_balances: if x.report_type == "Profit and Loss": closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] gl_entries.extend(closing_balances) @@ -376,10 +387,10 @@ def build_dimension_wise_balance_dict(gl_entries): return dimension_balances -def process_individual_date(docname: str, date: str, report_type): +def process_individual_date(docname: str, date, report_type, parentfield): current_date_status = frappe.db.get_value( "Process Period Closing Voucher Detail", - {"processing_date": date, "parent": docname, "report_type": report_type}, + {"processing_date": date, "report_type": report_type, "parentfield": parentfield}, "status", ) if current_date_status != "Running": @@ -411,6 +422,10 @@ def process_individual_date(docname: str, date: str, report_type): & (gle.posting_date.eq(date)) & (gle.account.isin(accounts)) ) + + if parentfield == "z_opening_balances": + query = query.where(gle.is_opening.eq("Yes")) + query = query.groupby(gle.account) for dim in dimensions: query = query.groupby(gle[dim]) @@ -419,14 +434,14 @@ def process_individual_date(docname: str, date: str, report_type): # save results frappe.db.set_value( "Process Period Closing Voucher Detail", - {"processing_date": date, "parent": docname, "report_type": report_type}, + {"processing_date": date, "parent": docname, "report_type": report_type, "parentfield": parentfield}, "closing_balance", frappe.json.dumps(res), ) frappe.db.set_value( "Process Period Closing Voucher Detail", - {"processing_date": date, "parent": docname, "report_type": report_type}, + {"processing_date": date, "parent": docname, "report_type": report_type, "parentfield": parentfield}, "status", "Completed", ) From 09e37bc98c0b27cbbc621c66ed47f0e753b3b28b Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 15:17:26 +0530 Subject: [PATCH 20/32] refactor: store closing balance for Balnace sheet accounts --- .../process_period_closing_voucher.json | 8 ++++- .../process_period_closing_voucher.py | 31 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index 9b89b405eed..fd0186557dc 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -9,6 +9,7 @@ "status", "p_l_closing_balance", "normal_balances", + "bs_closing_balance", "z_opening_balances", "amended_from" ], @@ -58,13 +59,18 @@ "label": "Opening Balances", "no_copy": 1, "options": "Process Period Closing Voucher Detail" + }, + { + "fieldname": "bs_closing_balance", + "fieldtype": "JSON", + "label": "Balance Sheet Closing Balance" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-10-17 13:44:33.397172", + "modified": "2025-10-17 15:16:26.324369", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 54363ec2889..7905465f9dd 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -27,6 +27,7 @@ class ProcessPeriodClosingVoucher(Document): ) amended_from: DF.Link | None + bs_closing_balance: DF.JSON | None normal_balances: DF.Table[ProcessPeriodClosingVoucherDetail] p_l_closing_balance: DF.JSON | None parent_pcv: DF.Link @@ -258,6 +259,7 @@ def schedule_next_date(docname: str): total_no_of_dates = ( qb.from_(ppcvd).select(Count(ppcvd.star)).where(ppcvd.parent.eq(docname)).run()[0][0] ) + # consider both normal and opening balance completed = ( qb.from_(ppcvd) .select(Count(ppcvd.star)) @@ -281,6 +283,7 @@ def summarize_and_post_ledger_entries(docname): # calculate balances for whole PCV period ppcv = frappe.get_doc("Process Period Closing Voucher", docname) + # P&L Accounts gl_entries = [] for x in ppcv.normal_balances: if x.report_type == "Profit and Loss": @@ -288,11 +291,11 @@ def summarize_and_post_ledger_entries(docname): gl_entries.extend(closing_balances) # build dimension wise dictionary from all GLE's - dimension_wise_acc_balances = build_dimension_wise_balance_dict(gl_entries) + pl_dimension_wise_acc_balance = build_dimension_wise_balance_dict(gl_entries) # convert tuple key to str to make it json compliant json_dict = {} - for k, v in dimension_wise_acc_balances.items(): + for k, v in pl_dimension_wise_acc_balance.items(): str_key = [str(x) for x in k] str_key = ",".join(str_key) json_dict[str_key] = v @@ -302,12 +305,34 @@ def summarize_and_post_ledger_entries(docname): "Process Period Closing Voucher", docname, "p_l_closing_balance", frappe.json.dumps(json_dict) ) + # Balance Sheet Accounts + gl_entries = [] + for x in ppcv.normal_balances + ppcv.z_opening_balances: + if x.report_type == "Balance Sheet": + closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] + gl_entries.extend(closing_balances) + + # build dimension wise dictionary from all GLE's + bs_dimension_wise_acc_balance = build_dimension_wise_balance_dict(gl_entries) + + # convert tuple key to str to make it json compliant + json_dict = {} + for k, v in bs_dimension_wise_acc_balance.items(): + str_key = [str(x) for x in k] + str_key = ",".join(str_key) + json_dict[str_key] = v + + # save + frappe.db.set_value( + "Process Period Closing Voucher", docname, "bs_closing_balance", frappe.json.dumps(json_dict) + ) + # build gl map pcv = frappe.get_doc("Period Closing Voucher", ppcv.parent_pcv) pl_accounts_reverse_gle = [] closing_account_gle = [] - for dimensions, account_balances in dimension_wise_acc_balances.items(): + for dimensions, account_balances in pl_dimension_wise_acc_balance.items(): for acc, balances in account_balances.items(): balance_in_company_currency = flt(balances.debit) - flt(balances.credit) if balance_in_company_currency: From 6e32769e37b94dd268374fbba9658f82de97aaf2 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 15:35:32 +0530 Subject: [PATCH 21/32] refactor: make Accounts Closing Balance as well --- .../process_period_closing_voucher.py | 84 +++++++++++++++---- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 7905465f9dd..63e901b8af4 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -1,6 +1,7 @@ # Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +import copy from datetime import timedelta import frappe @@ -10,6 +11,10 @@ from frappe.query_builder.functions import Count, Max, Min, Sum from frappe.utils import add_days, flt, get_datetime from frappe.utils.scheduler import is_scheduler_inactive +from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import ( + make_closing_entries, +) + BACKGROUND = True @@ -305,6 +310,27 @@ def summarize_and_post_ledger_entries(docname): "Process Period Closing Voucher", docname, "p_l_closing_balance", frappe.json.dumps(json_dict) ) + # build gl map + pcv = frappe.get_doc("Period Closing Voucher", ppcv.parent_pcv) + pl_accounts_reverse_gle = [] + closing_account_gle = [] + + for dimensions, account_balances in pl_dimension_wise_acc_balance.items(): + for acc, balances in account_balances.items(): + balance_in_company_currency = flt(balances.debit) - flt(balances.credit) + if balance_in_company_currency: + pl_accounts_reverse_gle.append(get_gle_for_pl_account(pcv, acc, balances, dimensions)) + + closing_account_gle.append( + get_gle_for_closing_account(pcv, account_balances["balances"], dimensions) + ) + + gl_entries = pl_accounts_reverse_gle + closing_account_gle + from erpnext.accounts.general_ledger import make_gl_entries + + if gl_entries: + make_gl_entries(gl_entries, merge_entries=False) + # Balance Sheet Accounts gl_entries = [] for x in ppcv.normal_balances + ppcv.z_opening_balances: @@ -327,30 +353,58 @@ def summarize_and_post_ledger_entries(docname): "Process Period Closing Voucher", docname, "bs_closing_balance", frappe.json.dumps(json_dict) ) - # build gl map - pcv = frappe.get_doc("Period Closing Voucher", ppcv.parent_pcv) - pl_accounts_reverse_gle = [] - closing_account_gle = [] + # make closing entries + pl_closing_entries = copy.deepcopy(pl_accounts_reverse_gle) + for d in pl_accounts_reverse_gle: + # reverse debit and credit + gle_copy = copy.deepcopy(d) + gle_copy.debit = d.credit + gle_copy.credit = d.debit + gle_copy.debit_in_account_currency = d.credit_in_account_currency + gle_copy.credit_in_account_currency = d.debit_in_account_currency + gle_copy.is_period_closing_voucher_entry = 0 + gle_copy.period_closing_voucher = pcv.name + pl_closing_entries.append(gle_copy) - for dimensions, account_balances in pl_dimension_wise_acc_balance.items(): + bs_closing_entries = [] + for dimensions, account_balances in bs_dimension_wise_acc_balance.items(): for acc, balances in account_balances.items(): balance_in_company_currency = flt(balances.debit) - flt(balances.credit) - if balance_in_company_currency: - pl_accounts_reverse_gle.append(get_gle_for_pl_account(pcv, acc, balances, dimensions)) + if acc != "balances" and balance_in_company_currency: + bs_closing_entries.append(get_closing_entry(pcv, acc, balances, dimensions)) - closing_account_gle.append( - get_gle_for_closing_account(pcv, account_balances["balances"], dimensions) - ) + closing_entries_for_closing_account = copy.deepcopy(closing_account_gle) + for d in closing_entries_for_closing_account: + d.period_closing_voucher = pcv.name - gl_entries = pl_accounts_reverse_gle + closing_account_gle - from erpnext.accounts.general_ledger import make_gl_entries - - if gl_entries: - make_gl_entries(gl_entries, merge_entries=False) + closing_entries = pl_closing_entries + bs_closing_entries + closing_entries_for_closing_account + make_closing_entries(closing_entries, pcv.name, pcv.company, pcv.period_end_date) + # TODO: Update processing status on PCV and Process document frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") +def get_closing_entry(pcv, account, balances, dimensions): + closing_entry = frappe._dict( + { + "company": pcv.company, + "closing_date": pcv.period_end_date, + "period_closing_voucher": pcv.name, + "account": account, + "account_currency": balances.account_currency, + "debit_in_account_currency": flt(balances.debit_in_account_currency), + "debit": flt(balances.debit), + "credit_in_account_currency": flt(balances.credit_in_account_currency), + "credit": flt(balances.credit), + "is_period_closing_voucher_entry": 0, + } + ) + # update dimensions + for i, dimension in enumerate(dimensions): + closing_entry[dimension] = dimensions[i] + return closing_entry + + def get_dimensions(): from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( get_accounting_dimensions, From 653ae84b3e6431616cb72525fc455624de61fef6 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 15:40:53 +0530 Subject: [PATCH 22/32] refactor: cleanup and for better readability --- .../process_period_closing_voucher.py | 188 +++++++++--------- 1 file changed, 89 insertions(+), 99 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 63e901b8af4..bff371a46dd 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -157,6 +157,11 @@ def resume_pcv_processing(docname: str): start_pcv_processing(docname) +def update_default_dimensions(dimension_fields, gl_entry, dimension_values): + for i, dimension in enumerate(dimension_fields): + gl_entry[dimension] = dimension_values[i] + + def get_gle_for_pl_account(pcv, acc, balances, dimensions): balance_in_account_currency = flt(balances.debit_in_account_currency) - flt( balances.credit_in_account_currency @@ -185,8 +190,7 @@ def get_gle_for_pl_account(pcv, acc, balances, dimensions): } ) # update dimensions - for i, dimension in enumerate(dimensions): - gl_entry[dimension] = dimensions[i] + update_default_dimensions(get_dimensions(), gl_entry, dimensions) return gl_entry @@ -214,8 +218,7 @@ def get_gle_for_closing_account(pcv, dimension_balance, dimensions): } ) # update dimensions - for i, dimension in enumerate(dimensions): - gl_entry[dimension] = dimensions[i] + update_default_dimensions(get_dimensions(), gl_entry, dimensions) return gl_entry @@ -259,129 +262,117 @@ def schedule_next_date(docname: str): to_process[0].parentfield, ) else: - # summarize, build and post GL ppcvd = qb.DocType("Process Period Closing Voucher Detail") total_no_of_dates = ( qb.from_(ppcvd).select(Count(ppcvd.star)).where(ppcvd.parent.eq(docname)).run()[0][0] ) - # consider both normal and opening balance completed = ( qb.from_(ppcvd) .select(Count(ppcvd.star)) .where(ppcvd.parent.eq(docname) & ppcvd.status.eq("Completed")) .run()[0][0] ) + # Ensure both normal and opening balances are processed for all dates if total_no_of_dates == completed: summarize_and_post_ledger_entries(docname) def summarize_and_post_ledger_entries(docname): - # TODO: ensure all dates are processed - running = frappe.db.get_all( - "Process Period Closing Voucher Detail", - filters={"parent": docname, "status": "Running"}, - fields=["processing_date"], - order_by="processing_date", - limit=1, + # calculate balances for whole PCV period + ppcv = frappe.get_doc("Process Period Closing Voucher", docname) + + # P&L Accounts + gl_entries = [] + for x in ppcv.normal_balances: + if x.report_type == "Profit and Loss": + closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] + gl_entries.extend(closing_balances) + + # build dimension wise dictionary from all GLE's + pl_dimension_wise_acc_balance = build_dimension_wise_balance_dict(gl_entries) + + # convert tuple key to str to make it json compliant + json_dict = {} + for k, v in pl_dimension_wise_acc_balance.items(): + str_key = [str(x) for x in k] + str_key = ",".join(str_key) + json_dict[str_key] = v + + # save + frappe.db.set_value( + "Process Period Closing Voucher", docname, "p_l_closing_balance", frappe.json.dumps(json_dict) ) - if not running: - # calculate balances for whole PCV period - ppcv = frappe.get_doc("Process Period Closing Voucher", docname) - # P&L Accounts - gl_entries = [] - for x in ppcv.normal_balances: - if x.report_type == "Profit and Loss": - closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] - gl_entries.extend(closing_balances) + # build gl map + pcv = frappe.get_doc("Period Closing Voucher", ppcv.parent_pcv) + pl_accounts_reverse_gle = [] + closing_account_gle = [] - # build dimension wise dictionary from all GLE's - pl_dimension_wise_acc_balance = build_dimension_wise_balance_dict(gl_entries) + for dimensions, account_balances in pl_dimension_wise_acc_balance.items(): + for acc, balances in account_balances.items(): + balance_in_company_currency = flt(balances.debit) - flt(balances.credit) + if balance_in_company_currency: + pl_accounts_reverse_gle.append(get_gle_for_pl_account(pcv, acc, balances, dimensions)) - # convert tuple key to str to make it json compliant - json_dict = {} - for k, v in pl_dimension_wise_acc_balance.items(): - str_key = [str(x) for x in k] - str_key = ",".join(str_key) - json_dict[str_key] = v + closing_account_gle.append(get_gle_for_closing_account(pcv, account_balances["balances"], dimensions)) - # save - frappe.db.set_value( - "Process Period Closing Voucher", docname, "p_l_closing_balance", frappe.json.dumps(json_dict) - ) + gl_entries = pl_accounts_reverse_gle + closing_account_gle + from erpnext.accounts.general_ledger import make_gl_entries - # build gl map - pcv = frappe.get_doc("Period Closing Voucher", ppcv.parent_pcv) - pl_accounts_reverse_gle = [] - closing_account_gle = [] + if gl_entries: + make_gl_entries(gl_entries, merge_entries=False) - for dimensions, account_balances in pl_dimension_wise_acc_balance.items(): - for acc, balances in account_balances.items(): - balance_in_company_currency = flt(balances.debit) - flt(balances.credit) - if balance_in_company_currency: - pl_accounts_reverse_gle.append(get_gle_for_pl_account(pcv, acc, balances, dimensions)) + # Balance Sheet Accounts + gl_entries = [] + for x in ppcv.normal_balances + ppcv.z_opening_balances: + if x.report_type == "Balance Sheet": + closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] + gl_entries.extend(closing_balances) - closing_account_gle.append( - get_gle_for_closing_account(pcv, account_balances["balances"], dimensions) - ) + # build dimension wise dictionary from all GLE's + bs_dimension_wise_acc_balance = build_dimension_wise_balance_dict(gl_entries) - gl_entries = pl_accounts_reverse_gle + closing_account_gle - from erpnext.accounts.general_ledger import make_gl_entries + # convert tuple key to str to make it json compliant + json_dict = {} + for k, v in bs_dimension_wise_acc_balance.items(): + str_key = [str(x) for x in k] + str_key = ",".join(str_key) + json_dict[str_key] = v - if gl_entries: - make_gl_entries(gl_entries, merge_entries=False) + # save + frappe.db.set_value( + "Process Period Closing Voucher", docname, "bs_closing_balance", frappe.json.dumps(json_dict) + ) - # Balance Sheet Accounts - gl_entries = [] - for x in ppcv.normal_balances + ppcv.z_opening_balances: - if x.report_type == "Balance Sheet": - closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] - gl_entries.extend(closing_balances) + # make closing entries + pl_closing_entries = copy.deepcopy(pl_accounts_reverse_gle) + for d in pl_accounts_reverse_gle: + # reverse debit and credit + gle_copy = copy.deepcopy(d) + gle_copy.debit = d.credit + gle_copy.credit = d.debit + gle_copy.debit_in_account_currency = d.credit_in_account_currency + gle_copy.credit_in_account_currency = d.debit_in_account_currency + gle_copy.is_period_closing_voucher_entry = 0 + gle_copy.period_closing_voucher = pcv.name + pl_closing_entries.append(gle_copy) - # build dimension wise dictionary from all GLE's - bs_dimension_wise_acc_balance = build_dimension_wise_balance_dict(gl_entries) + bs_closing_entries = [] + for dimensions, account_balances in bs_dimension_wise_acc_balance.items(): + for acc, balances in account_balances.items(): + balance_in_company_currency = flt(balances.debit) - flt(balances.credit) + if acc != "balances" and balance_in_company_currency: + bs_closing_entries.append(get_closing_entry(pcv, acc, balances, dimensions)) - # convert tuple key to str to make it json compliant - json_dict = {} - for k, v in bs_dimension_wise_acc_balance.items(): - str_key = [str(x) for x in k] - str_key = ",".join(str_key) - json_dict[str_key] = v + closing_entries_for_closing_account = copy.deepcopy(closing_account_gle) + for d in closing_entries_for_closing_account: + d.period_closing_voucher = pcv.name - # save - frappe.db.set_value( - "Process Period Closing Voucher", docname, "bs_closing_balance", frappe.json.dumps(json_dict) - ) + closing_entries = pl_closing_entries + bs_closing_entries + closing_entries_for_closing_account + make_closing_entries(closing_entries, pcv.name, pcv.company, pcv.period_end_date) - # make closing entries - pl_closing_entries = copy.deepcopy(pl_accounts_reverse_gle) - for d in pl_accounts_reverse_gle: - # reverse debit and credit - gle_copy = copy.deepcopy(d) - gle_copy.debit = d.credit - gle_copy.credit = d.debit - gle_copy.debit_in_account_currency = d.credit_in_account_currency - gle_copy.credit_in_account_currency = d.debit_in_account_currency - gle_copy.is_period_closing_voucher_entry = 0 - gle_copy.period_closing_voucher = pcv.name - pl_closing_entries.append(gle_copy) - - bs_closing_entries = [] - for dimensions, account_balances in bs_dimension_wise_acc_balance.items(): - for acc, balances in account_balances.items(): - balance_in_company_currency = flt(balances.debit) - flt(balances.credit) - if acc != "balances" and balance_in_company_currency: - bs_closing_entries.append(get_closing_entry(pcv, acc, balances, dimensions)) - - closing_entries_for_closing_account = copy.deepcopy(closing_account_gle) - for d in closing_entries_for_closing_account: - d.period_closing_voucher = pcv.name - - closing_entries = pl_closing_entries + bs_closing_entries + closing_entries_for_closing_account - make_closing_entries(closing_entries, pcv.name, pcv.company, pcv.period_end_date) - - # TODO: Update processing status on PCV and Process document - frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") + # TODO: Update processing status on PCV and Process document + frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") def get_closing_entry(pcv, account, balances, dimensions): @@ -400,8 +391,7 @@ def get_closing_entry(pcv, account, balances, dimensions): } ) # update dimensions - for i, dimension in enumerate(dimensions): - closing_entry[dimension] = dimensions[i] + update_default_dimensions(get_dimensions(), closing_entry, dimensions) return closing_entry From 5b464ae4c15fc3a17070bfdb36d284e23f18b314 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 19:48:35 +0530 Subject: [PATCH 23/32] refactor: utility to convert tuple key to str --- .../process_period_closing_voucher.py | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index bff371a46dd..4d0e4271e78 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -277,6 +277,20 @@ def schedule_next_date(docname: str): summarize_and_post_ledger_entries(docname) +def make_dict_json_compliant(dimension_wise_balance) -> dict: + """ + convert tuple -> str + JSON doesn't support dictionary with tuple keys + """ + converted_dict = {} + for k, v in dimension_wise_balance.items(): + str_key = [str(x) for x in k] + str_key = ",".join(str_key) + converted_dict[str_key] = v + + return converted_dict + + def summarize_and_post_ledger_entries(docname): # calculate balances for whole PCV period ppcv = frappe.get_doc("Process Period Closing Voucher", docname) @@ -291,14 +305,8 @@ def summarize_and_post_ledger_entries(docname): # build dimension wise dictionary from all GLE's pl_dimension_wise_acc_balance = build_dimension_wise_balance_dict(gl_entries) - # convert tuple key to str to make it json compliant - json_dict = {} - for k, v in pl_dimension_wise_acc_balance.items(): - str_key = [str(x) for x in k] - str_key = ",".join(str_key) - json_dict[str_key] = v - # save + json_dict = make_dict_json_compliant(pl_dimension_wise_acc_balance) frappe.db.set_value( "Process Period Closing Voucher", docname, "p_l_closing_balance", frappe.json.dumps(json_dict) ) @@ -332,14 +340,8 @@ def summarize_and_post_ledger_entries(docname): # build dimension wise dictionary from all GLE's bs_dimension_wise_acc_balance = build_dimension_wise_balance_dict(gl_entries) - # convert tuple key to str to make it json compliant - json_dict = {} - for k, v in bs_dimension_wise_acc_balance.items(): - str_key = [str(x) for x in k] - str_key = ",".join(str_key) - json_dict[str_key] = v - # save + json_dict = make_dict_json_compliant(bs_dimension_wise_acc_balance) frappe.db.set_value( "Process Period Closing Voucher", docname, "bs_closing_balance", frappe.json.dumps(json_dict) ) From 7406d8326097435446498bb653ec916c853ffea7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 19:55:09 +0530 Subject: [PATCH 24/32] refactor: utility to consolidate results from all dates --- .../process_period_closing_voucher.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 4d0e4271e78..0b163e555c5 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -291,16 +291,21 @@ def make_dict_json_compliant(dimension_wise_balance) -> dict: return converted_dict +def get_consolidated_gles(balances, report_type) -> list: + gl_entries = [] + for x in balances: + if x.report_type == report_type: + closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] + gl_entries.extend(closing_balances) + return gl_entries + + def summarize_and_post_ledger_entries(docname): # calculate balances for whole PCV period ppcv = frappe.get_doc("Process Period Closing Voucher", docname) # P&L Accounts - gl_entries = [] - for x in ppcv.normal_balances: - if x.report_type == "Profit and Loss": - closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] - gl_entries.extend(closing_balances) + gl_entries = get_consolidated_gles(ppcv.normal_balances, "Profit and Loss") # build dimension wise dictionary from all GLE's pl_dimension_wise_acc_balance = build_dimension_wise_balance_dict(gl_entries) @@ -331,11 +336,7 @@ def summarize_and_post_ledger_entries(docname): make_gl_entries(gl_entries, merge_entries=False) # Balance Sheet Accounts - gl_entries = [] - for x in ppcv.normal_balances + ppcv.z_opening_balances: - if x.report_type == "Balance Sheet": - closing_balances = [frappe._dict(gle) for gle in frappe.json.loads(x.closing_balance)] - gl_entries.extend(closing_balances) + gl_entries = get_consolidated_gles(ppcv.normal_balances + ppcv.z_opening_balances, "Balance Sheet") # build dimension wise dictionary from all GLE's bs_dimension_wise_acc_balance = build_dimension_wise_balance_dict(gl_entries) From fa3bd6f5a7d1199d1fb1e4d32d11bef267b3cc7a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 20:27:22 +0530 Subject: [PATCH 25/32] refactor: smaller methods --- .../process_period_closing_voucher.py | 65 ++++++++++++++----- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 0b163e555c5..3940a26ad1b 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -300,14 +300,14 @@ def get_consolidated_gles(balances, report_type) -> list: return gl_entries -def summarize_and_post_ledger_entries(docname): - # calculate balances for whole PCV period +def get_gl_entries(docname): + """ + Calculate total closing balance of all P&L accounts across PCV start and end date + """ ppcv = frappe.get_doc("Process Period Closing Voucher", docname) - # P&L Accounts + # calculate balance gl_entries = get_consolidated_gles(ppcv.normal_balances, "Profit and Loss") - - # build dimension wise dictionary from all GLE's pl_dimension_wise_acc_balance = build_dimension_wise_balance_dict(gl_entries) # save @@ -329,13 +329,15 @@ def summarize_and_post_ledger_entries(docname): closing_account_gle.append(get_gle_for_closing_account(pcv, account_balances["balances"], dimensions)) - gl_entries = pl_accounts_reverse_gle + closing_account_gle - from erpnext.accounts.general_ledger import make_gl_entries + return pl_accounts_reverse_gle, closing_account_gle - if gl_entries: - make_gl_entries(gl_entries, merge_entries=False) - # Balance Sheet Accounts +def calculate_balance_sheet_balance(docname): + """ + Calculate total closing balance of all P&L accounts across PCV start and end date. + If it is first PCV, opening entries are also considered + """ + ppcv = frappe.get_doc("Process Period Closing Voucher", docname) gl_entries = get_consolidated_gles(ppcv.normal_balances + ppcv.z_opening_balances, "Balance Sheet") # build dimension wise dictionary from all GLE's @@ -346,10 +348,12 @@ def summarize_and_post_ledger_entries(docname): frappe.db.set_value( "Process Period Closing Voucher", docname, "bs_closing_balance", frappe.json.dumps(json_dict) ) + return bs_dimension_wise_acc_balance - # make closing entries - pl_closing_entries = copy.deepcopy(pl_accounts_reverse_gle) - for d in pl_accounts_reverse_gle: + +def get_p_l_closing_entries(pl_gles, pcv): + pl_closing_entries = copy.deepcopy(pl_gles) + for d in pl_gles: # reverse debit and credit gle_copy = copy.deepcopy(d) gle_copy.debit = d.credit @@ -360,18 +364,47 @@ def summarize_and_post_ledger_entries(docname): gle_copy.period_closing_voucher = pcv.name pl_closing_entries.append(gle_copy) - bs_closing_entries = [] - for dimensions, account_balances in bs_dimension_wise_acc_balance.items(): + return pl_closing_entries + + +def get_bs_closing_entries(dimension_wise_balance, pcv): + closing_entries = [] + for dimensions, account_balances in dimension_wise_balance.items(): for acc, balances in account_balances.items(): balance_in_company_currency = flt(balances.debit) - flt(balances.credit) if acc != "balances" and balance_in_company_currency: - bs_closing_entries.append(get_closing_entry(pcv, acc, balances, dimensions)) + closing_entries.append(get_closing_entry(pcv, acc, balances, dimensions)) + return closing_entries + + +def get_closing_account_closing_entry(closing_account_gle, pcv): closing_entries_for_closing_account = copy.deepcopy(closing_account_gle) for d in closing_entries_for_closing_account: d.period_closing_voucher = pcv.name + return closing_entries_for_closing_account + +def summarize_and_post_ledger_entries(docname): + # P&L accounts + pl_accounts_reverse_gle, closing_account_gle = get_gl_entries(docname) + gl_entries = pl_accounts_reverse_gle + closing_account_gle + from erpnext.accounts.general_ledger import make_gl_entries + + if gl_entries: + make_gl_entries(gl_entries, merge_entries=False) + + pcv_name = frappe.db.get_value("Process Period Closing Voucher", docname, "parent_pcv") + pcv = frappe.get_doc("Period Closing Voucher", pcv_name) + + # Balance sheet accounts + bs_dimension_wise_acc_balance = calculate_balance_sheet_balance(docname) + + pl_closing_entries = get_p_l_closing_entries(pl_accounts_reverse_gle, pcv) + bs_closing_entries = get_bs_closing_entries(bs_dimension_wise_acc_balance, pcv) + closing_entries_for_closing_account = get_closing_account_closing_entry(closing_account_gle, pcv) closing_entries = pl_closing_entries + bs_closing_entries + closing_entries_for_closing_account + make_closing_entries(closing_entries, pcv.name, pcv.company, pcv.period_end_date) # TODO: Update processing status on PCV and Process document From cae123785949415fa0d4cc95df13a5e03a80a7c8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Oct 2025 10:59:59 +0530 Subject: [PATCH 26/32] refactor: more changes 1. 'Accounts Manager' has access to submit, cancel and delete 2. cancel and delete operation of PCV is linked with Proces PCV --- .../period_closing_voucher.js | 2 ++ .../period_closing_voucher.py | 30 ++++++++++++++----- .../process_period_closing_voucher.json | 21 +++++++++++-- .../process_period_closing_voucher.py | 2 +- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js index 15de8cec243..f07d90b6ef3 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.js @@ -4,6 +4,8 @@ frappe.ui.form.on("Period Closing Voucher", { onload: function (frm) { if (!frm.doc.transaction_date) frm.doc.transaction_date = frappe.datetime.obj_to_str(new Date()); + + frm.ignore_doctypes_on_cancel_all = ["Process Period Closing Voucher"]; }, setup: function (frm) { diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 97c503f5885..40ed4230a5c 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -135,7 +135,8 @@ class PeriodClosingVoucher(AccountsController): self.db_set("gle_processing_status", "In Progress") self.make_gl_entries() else: - print("submit") + ppcv = frappe.get_doc({"doctype": "Process Period Closing Voucher", "parent_pcv": self.name}) + ppcv.save().submit() def on_cancel(self): self.ignore_linked_doctypes = ( @@ -143,13 +144,28 @@ class PeriodClosingVoucher(AccountsController): "Stock Ledger Entry", "Payment Ledger Entry", "Account Closing Balance", + "Process Period Closing Voucher", ) - if frappe.get_single_value("Accounts Settings", "use_legacy_controller_for_pcv"): - self.block_if_future_closing_voucher_exists() - self.db_set("gle_processing_status", "In Progress") - self.cancel_gl_entries() - else: - print("cancel") + self.block_if_future_closing_voucher_exists() + + if not frappe.get_single_value("Accounts Settings", "use_legacy_controller_for_pcv"): + self.cancel_process_pcv_docs() + + self.db_set("gle_processing_status", "In Progress") + self.cancel_gl_entries() + + def cancel_process_pcv_docs(self): + ppcvs = frappe.db.get_all("Process Period Closing Voucher", {"parent_pcv": self.name, "docstatus": 1}) + for x in ppcvs: + frappe.get_doc("Process Period Closing Voucher", x.name).cancel() + + def on_trash(self): + super().on_trash() + ppcvs = frappe.db.get_all( + "Process Period Closing Voucher", {"parent_pcv": self.name, "docstatus": ["in", [1, 2]]} + ) + for x in ppcvs: + frappe.delete_doc("Process Period Closing Voucher", x.name) def make_gl_entries(self): if frappe.db.estimate_count("GL Entry") > 100_000: diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index fd0186557dc..fc6ccda4488 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -1,6 +1,6 @@ { "actions": [], - "allow_rename": 1, + "autoname": "format:Process-PCV-{###}", "creation": "2025-09-25 15:44:03.534699", "doctype": "DocType", "engine": "InnoDB", @@ -70,13 +70,15 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-10-17 15:16:26.324369", + "modified": "2025-10-20 08:06:26.786490", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", + "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -86,6 +88,21 @@ "report": 1, "role": "System Manager", "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, + "submit": 1, "write": 1 } ], diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 3940a26ad1b..9990f58ff55 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -407,7 +407,7 @@ def summarize_and_post_ledger_entries(docname): make_closing_entries(closing_entries, pcv.name, pcv.company, pcv.period_end_date) - # TODO: Update processing status on PCV and Process document + frappe.db.set_value("Period Closing Voucher", pcv.name, "gle_processing_status", "Completed") frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") From 090e155fd0d65eabc240e6588e7d1439e6ba5777 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Oct 2025 12:20:02 +0530 Subject: [PATCH 27/32] refactor: abort processing of all tasks upon cancellation --- .../process_period_closing_voucher.json | 4 ++-- .../process_period_closing_voucher.py | 19 ++++++++++++++++++- ...process_period_closing_voucher_detail.json | 4 ++-- .../process_period_closing_voucher_detail.py | 2 +- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index fc6ccda4488..71e6cdd659a 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -28,7 +28,7 @@ "fieldtype": "Select", "label": "Status", "no_copy": 1, - "options": "Queued\nRunning\nCompleted" + "options": "Queued\nRunning\nCompleted\nCancelled" }, { "fieldname": "amended_from", @@ -70,7 +70,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-10-20 08:06:26.786490", + "modified": "2025-10-20 12:06:32.613247", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index 9990f58ff55..f4a2a76f790 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -36,7 +36,7 @@ class ProcessPeriodClosingVoucher(Document): normal_balances: DF.Table[ProcessPeriodClosingVoucherDetail] p_l_closing_balance: DF.JSON | None parent_pcv: DF.Link - status: DF.Literal["Queued", "Running", "Completed"] + status: DF.Literal["Queued", "Running", "Completed", "Cancelled"] z_opening_balances: DF.Table[ProcessPeriodClosingVoucherDetail] # end: auto-generated types @@ -84,6 +84,9 @@ class ProcessPeriodClosingVoucher(Document): def on_submit(self): start_pcv_processing(self.name) + def on_cancel(self): + cancel_pcv_processing(self.name) + @frappe.whitelist() def start_pcv_processing(docname: str): @@ -142,6 +145,20 @@ def pause_pcv_processing(docname: str): qb.update(ppcvd).set(ppcvd.status, "Paused").where(ppcvd.name.isin(queued_dates)).run() +@frappe.whitelist() +def cancel_pcv_processing(docname: str): + ppcv = qb.DocType("Process Period Closing Voucher") + qb.update(ppcv).set(ppcv.status, "Cancelled").where(ppcv.name.eq(docname)).run() + + if queued_dates := frappe.db.get_all( + "Process Period Closing Voucher Detail", + filters={"parent": docname, "status": "Queued"}, + pluck="name", + ): + ppcvd = qb.DocType("Process Period Closing Voucher Detail") + qb.update(ppcvd).set(ppcvd.status, "Cancelled").where(ppcvd.name.isin(queued_dates)).run() + + @frappe.whitelist() def resume_pcv_processing(docname: str): ppcv = qb.DocType("Process Period Closing Voucher") diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json index b64871b2ff0..8d881ade663 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json @@ -23,7 +23,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Status", - "options": "Queued\nRunning\nPaused\nCompleted" + "options": "Queued\nRunning\nPaused\nCompleted\nCancelled" }, { "fieldname": "closing_balance", @@ -44,7 +44,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-10-17 11:28:34.775743", + "modified": "2025-10-20 12:03:59.106931", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher Detail", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py index 6bec6622ee0..f3a8302ac5b 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.py @@ -20,7 +20,7 @@ class ProcessPeriodClosingVoucherDetail(Document): parenttype: DF.Data processing_date: DF.Date | None report_type: DF.Literal["Profit and Loss", "Balance Sheet"] - status: DF.Literal["Queued", "Running", "Paused", "Completed"] + status: DF.Literal["Queued", "Running", "Paused", "Completed", "Cancelled"] # end: auto-generated types pass From 191c0e65a1a95fd457f7a2510a4182af5a7dbbd1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Oct 2025 12:22:45 +0530 Subject: [PATCH 28/32] chore: remove scaffolding --- .../process_period_closing_voucher.py | 55 +++++++------------ 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index f4a2a76f790..b52ac54b6b8 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -15,8 +15,6 @@ from erpnext.accounts.doctype.account_closing_balance.account_closing_balance im make_closing_entries, ) -BACKGROUND = True - class ProcessPeriodClosingVoucher(Document): # begin: auto-generated types @@ -91,7 +89,6 @@ class ProcessPeriodClosingVoucher(Document): @frappe.whitelist() def start_pcv_processing(docname: str): if frappe.db.get_value("Process Period Closing Voucher", docname, "status") in ["Queued", "Running"]: - # TODO: move this inside if block frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Running") if normal_balances := frappe.db.get_all( "Process Period Closing Voucher Detail", @@ -113,20 +110,16 @@ def start_pcv_processing(docname: str): "status", "Running", ) - - if BACKGROUND: - frappe.enqueue( - method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", - queue="long", - is_async=True, - enqueue_after_commit=True, - docname=docname, - date=x.processing_date, - report_type=x.report_type, - parentfield=x.parentfield, - ) - else: - process_individual_date(docname, x.processing_date, x.report_type, x.parentfield) + frappe.enqueue( + method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", + queue="long", + is_async=True, + enqueue_after_commit=True, + docname=docname, + date=x.processing_date, + report_type=x.report_type, + parentfield=x.parentfield, + ) else: frappe.db.set_value("Process Period Closing Voucher", docname, "status", "Completed") @@ -260,24 +253,16 @@ def schedule_next_date(docname: str): "status", "Running", ) - if BACKGROUND: - frappe.enqueue( - method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", - queue="long", - is_async=True, - enqueue_after_commit=True, - docname=docname, - date=to_process[0].processing_date, - report_type=to_process[0].report_type, - parentfield=to_process[0].parentfield, - ) - else: - process_individual_date( - docname, - to_process[0].processing_date, - to_process[0].report_type, - to_process[0].parentfield, - ) + frappe.enqueue( + method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", + queue="long", + is_async=True, + enqueue_after_commit=True, + docname=docname, + date=to_process[0].processing_date, + report_type=to_process[0].report_type, + parentfield=to_process[0].parentfield, + ) else: ppcvd = qb.DocType("Process Period Closing Voucher Detail") total_no_of_dates = ( From 0b88f98a861ad07f592cdf9c6b8530e57cbb88c8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Oct 2025 12:41:15 +0530 Subject: [PATCH 29/32] chore: progress bar --- .../process_period_closing_voucher.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js index 23d410e9bb0..a5b186826e6 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.js @@ -56,5 +56,16 @@ frappe.ui.form.on("Process Period Closing Voucher", { }); }); } + // progress bar + let progress = 0; + + let normal_finished = frm.doc.normal_balances.filter((x) => x.status == "Completed").length; + let opening_finished = frm.doc.z_opening_balances.filter((x) => x.status == "Completed").length; + + progress = + ((normal_finished + opening_finished) / + (frm.doc.normal_balances.length + frm.doc.z_opening_balances.length)) * + 100; + frm.dashboard.add_progress("Books closure progress", progress, ""); }, }); From fe39ce03bbb2b6419452ced07aad510b861dfa69 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Oct 2025 13:16:18 +0530 Subject: [PATCH 30/32] refactor: enable legacy controller by default for pcv --- .../doctype/accounts_settings/accounts_settings.json | 4 ++-- .../test_period_closing_voucher.py | 4 ++++ erpnext/patches.txt | 1 + .../v15_0/toggle_legacy_controller_for_period_closing.py | 9 +++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v15_0/toggle_legacy_controller_for_period_closing.py diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index f810a2073a9..64b8b27cd76 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -654,7 +654,7 @@ "label": "Use Legacy Budget Controller" }, { - "default": "0", + "default": "1", "fieldname": "use_legacy_controller_for_pcv", "fieldtype": "Check", "label": "Use Legacy Controller For Period Closing Voucher" @@ -666,7 +666,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-10-01 15:17:47.168354", + "modified": "2025-10-20 14:06:08.870427", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py index b29f97beacc..e9bc137fcd4 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py @@ -13,6 +13,10 @@ from erpnext.accounts.utils import get_fiscal_year class TestPeriodClosingVoucher(IntegrationTestCase): + def setUp(self): + super().setUp() + frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1) + def test_closing_entry(self): frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'") frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'") diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e2a77df8ce0..d78481268ab 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -443,3 +443,4 @@ erpnext.patches.v16_0.update_serial_no_reference_name erpnext.patches.v16_0.rename_subcontracted_quantity erpnext.patches.v16_0.add_new_stock_entry_types erpnext.patches.v15_0.set_asset_status_if_not_already_set +erpnext.patches.v15_0.toggle_legacy_controller_for_period_closing diff --git a/erpnext/patches/v15_0/toggle_legacy_controller_for_period_closing.py b/erpnext/patches/v15_0/toggle_legacy_controller_for_period_closing.py new file mode 100644 index 00000000000..7e7cb2f7b49 --- /dev/null +++ b/erpnext/patches/v15_0/toggle_legacy_controller_for_period_closing.py @@ -0,0 +1,9 @@ +import frappe + + +def execute(): + """ + Description: + Enable Legacy controller for Period Closing Voucher + """ + frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1) From 9c13edc0b9e34c28a067871f260ebef866640558 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 3 Nov 2025 16:44:26 +0530 Subject: [PATCH 31/32] refactor: minor changes on status 1. set to 'In Progress' on start of both legacy and new controller 2. force delete to avoid permission issues 3. default to 1hr timeout --- .../doctype/period_closing_voucher/period_closing_voucher.py | 4 ++-- .../process_period_closing_voucher.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 40ed4230a5c..210dbc5bbf5 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -131,8 +131,8 @@ class PeriodClosingVoucher(AccountsController): frappe.throw(_("Currency of the Closing Account must be {0}").format(company_currency)) def on_submit(self): + self.db_set("gle_processing_status", "In Progress") if frappe.get_single_value("Accounts Settings", "use_legacy_controller_for_pcv"): - self.db_set("gle_processing_status", "In Progress") self.make_gl_entries() else: ppcv = frappe.get_doc({"doctype": "Process Period Closing Voucher", "parent_pcv": self.name}) @@ -165,7 +165,7 @@ class PeriodClosingVoucher(AccountsController): "Process Period Closing Voucher", {"parent_pcv": self.name, "docstatus": ["in", [1, 2]]} ) for x in ppcvs: - frappe.delete_doc("Process Period Closing Voucher", x.name) + frappe.delete_doc("Process Period Closing Voucher", x.name, force=True, ignore_permissions=True) def make_gl_entries(self): if frappe.db.estimate_count("GL Entry") > 100_000: diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index b52ac54b6b8..e6f9913dcb2 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -113,6 +113,7 @@ def start_pcv_processing(docname: str): frappe.enqueue( method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", queue="long", + timeout="3600", is_async=True, enqueue_after_commit=True, docname=docname, @@ -256,6 +257,7 @@ def schedule_next_date(docname: str): frappe.enqueue( method="erpnext.accounts.doctype.process_period_closing_voucher.process_period_closing_voucher.process_individual_date", queue="long", + timeout="3600", is_async=True, enqueue_after_commit=True, docname=docname, From fca7abf4d61a3a05dfaeb68afa28ec095e5f1dcb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 5 Nov 2025 11:42:31 +0530 Subject: [PATCH 32/32] refactor: add paused to select option --- .../process_period_closing_voucher.json | 4 ++-- .../process_period_closing_voucher.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json index 71e6cdd659a..a06a16f156c 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.json @@ -28,7 +28,7 @@ "fieldtype": "Select", "label": "Status", "no_copy": 1, - "options": "Queued\nRunning\nCompleted\nCancelled" + "options": "Queued\nRunning\nPaused\nCompleted\nCancelled" }, { "fieldname": "amended_from", @@ -70,7 +70,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-10-20 12:06:32.613247", + "modified": "2025-11-05 11:40:24.996403", "modified_by": "Administrator", "module": "Accounts", "name": "Process Period Closing Voucher", diff --git a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py index e6f9913dcb2..c63c5652cb3 100644 --- a/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py +++ b/erpnext/accounts/doctype/process_period_closing_voucher/process_period_closing_voucher.py @@ -34,7 +34,7 @@ class ProcessPeriodClosingVoucher(Document): normal_balances: DF.Table[ProcessPeriodClosingVoucherDetail] p_l_closing_balance: DF.JSON | None parent_pcv: DF.Link - status: DF.Literal["Queued", "Running", "Completed", "Cancelled"] + status: DF.Literal["Queued", "Running", "Paused", "Completed", "Cancelled"] z_opening_balances: DF.Table[ProcessPeriodClosingVoucherDetail] # end: auto-generated types