From c8e3da0a713efda19a2a3b457fb487d08cdd06db Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 25 Sep 2025 16:35:54 +0530 Subject: [PATCH 01/33] feat: process period closing voucher (cherry picked from commit 7a93630629518fcdc6a12545a2155277e12ac637) --- .../__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 bfc0044d23830bd1f2b134c09784b549755b5956 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 1 Oct 2025 15:20:07 +0530 Subject: [PATCH 02/33] refactor: checkbox for pcv controller (cherry picked from commit 4888461be215e9aadf11e6bfa6e7279768ee7e41) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json # erpnext/accounts/doctype/accounts_settings/accounts_settings.py --- .../accounts_settings/accounts_settings.json | 20 +++++++++++++++++++ .../accounts_settings/accounts_settings.py | 5 +++++ 2 files changed, 25 insertions(+) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index abb9b96020a..dad7165b665 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -75,6 +75,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", @@ -629,6 +630,21 @@ "fieldname": "fetch_valuation_rate_for_internal_transaction", "fieldtype": "Check", "label": "Fetch Valuation Rate for Internal Transaction" +<<<<<<< HEAD +======= + }, + { + "default": "0", + "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" +>>>>>>> 4888461be2 (refactor: checkbox for pcv controller) } ], "icon": "icon-cog", @@ -636,7 +652,11 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD "modified": "2025-07-18 13:56:47.192437", +======= + "modified": "2025-10-01 15:17:47.168354", +>>>>>>> 4888461be2 (refactor: checkbox for pcv controller) "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 f5a5eb70f96..7ec9da682dc 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -73,6 +73,11 @@ class AccountsSettings(Document): submit_journal_entries: DF.Check unlink_advance_payment_on_cancelation_of_order: DF.Check unlink_payment_on_cancellation_of_invoice: DF.Check +<<<<<<< HEAD +======= + use_legacy_budget_controller: DF.Check + use_legacy_controller_for_pcv: DF.Check +>>>>>>> 4888461be2 (refactor: checkbox for pcv controller) # end: auto-generated types def validate(self): From 902ce45a36267c8caf404b15efa00029f70ea7ef Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 3 Oct 2025 11:17:29 +0530 Subject: [PATCH 03/33] refactor: more data structure changes (cherry picked from commit a15578f8f47134c3efec451647934d92a80fce4c) --- .../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 b484db3ffd18ddd0d02506327724a9b7ee2636ab Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 3 Oct 2025 14:24:35 +0530 Subject: [PATCH 04/33] refactor: child table in process pcv (cherry picked from commit 0d09d21d2e12a5cc7cecf4c18389fa45051e79b9) --- .../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 bc07de8c120d77f8167bdf7a9fc03f6c8a7568c0 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 8 Oct 2025 10:23:24 +0530 Subject: [PATCH 05/33] refactor: temporarily save balances in JSON (cherry picked from commit f44c908a8de6aad79dceccf89c014a77a920d42f) --- .../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 19b911120ca757f25f3792e6c87deea15204eb03 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 8 Oct 2025 11:02:52 +0530 Subject: [PATCH 06/33] refactor: barebones functions (cherry picked from commit 1c92b015423fd89b609bd653739610eea9bae8bf) --- .../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 43acfdff82c291e9d7b99d3605441cecf885f237 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 10 Oct 2025 10:50:03 +0530 Subject: [PATCH 07/33] refactor: stable start, pause, resume and completion stages (cherry picked from commit c839ebf593082cf44820e92fa6397f633bc011a7) --- .../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 1999de0b7591cd5454272008017f2b27bb3f923a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 13 Oct 2025 13:40:40 +0530 Subject: [PATCH 08/33] refactor: store closing balance as JSON (cherry picked from commit 1a31825409a211225e7c139fb3779b200efe15ee) --- .../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 76bdf7944c4ed9d79ffd133edfab952252ef58c7 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 14 Oct 2025 16:31:46 +0530 Subject: [PATCH 09/33] refactor: store daily balances based on dimensions key dimensions key is manually converted to string (cherry picked from commit 1846de0d493494311555b956865649ba1dd2de47) --- .../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 b6b552422839d2455fc73f828eb534503a7c60c8 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 15 Oct 2025 13:09:29 +0530 Subject: [PATCH 10/33] refactor: build and post gl entries (cherry picked from commit e88074ddec52cd6a99b1c8afeaf4bd387ebc9acd) --- .../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 e8f8abd6855f7e54b48263c5b21a05b31b19fa64 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Oct 2025 11:54:17 +0530 Subject: [PATCH 11/33] refactor: store results as is and convert at the end (cherry picked from commit f25ee3c53f1cf8302c43970edf504da7ac1389bf) --- .../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 633ccef2ff8a3a6d9cd60716c54a72de4eced499 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Oct 2025 13:13:33 +0530 Subject: [PATCH 12/33] refactor: for better readability (cherry picked from commit 8ba199016ab742e4848170d85f33011549e4f086) --- .../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 908f8ed462e60c2a055817c339fb00cb45412a8d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Oct 2025 15:34:08 +0530 Subject: [PATCH 13/33] refactor: process on submit (cherry picked from commit c738b6d3563a758314ac9d73a0b5faef93ce5409) --- .../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 d911e1dab282f452d192746e11dfd1bf06cc8b79 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 16 Oct 2025 17:05:43 +0530 Subject: [PATCH 14/33] refactor: more stable pause and resume (cherry picked from commit 9e93298f12f5841a1865e73a1f41ce890cc76eaf) --- .../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 6c94ca664f4b30bd93ee4b96f6b08786a2f7eabe Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 11:27:03 +0530 Subject: [PATCH 15/33] refactor: maintain report type on each date (cherry picked from commit 186d540502130cb66aa53b2aaa2d3855b8561616) --- .../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 87e297e8997d1279defef3210c758401a9b5c138 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 11:49:15 +0530 Subject: [PATCH 16/33] refactor: balances for both P&L and Balance sheet accounts (cherry picked from commit 324bebfd44e4351c04c90072cc7719fa97c6981a) --- .../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 2c880dd6099bd5e2733045e03965e295a80e496d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 13:05:03 +0530 Subject: [PATCH 17/33] chore: rename closing balance field (cherry picked from commit cef879bb3b6d50f4c61ddd01d63d774d32a7cb58) --- .../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 f381b99b14007e4a5cbcd80b15713b960263c26c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 13:16:56 +0530 Subject: [PATCH 18/33] refactor: populate opening balances calculation table (cherry picked from commit 86edacb781107211d99604fc9c32312ba72dd3d4) --- .../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 55222468f9812998c79285189ae22f5889f29d8d Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 14:42:29 +0530 Subject: [PATCH 19/33] refactor: calculate both balances from single queue (cherry picked from commit 643e1fdce8b30d532ff7bf4128b385d34e2aa047) --- .../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 449fa05d7d62bbbbf8f2ca6d9ad4576fb3463e14 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 15:17:26 +0530 Subject: [PATCH 20/33] refactor: store closing balance for Balnace sheet accounts (cherry picked from commit 09e37bc98c0b27cbbc621c66ed47f0e753b3b28b) --- .../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 3406e44b037ee09f0e5aa4605a0a8466fe888622 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 15:35:32 +0530 Subject: [PATCH 21/33] refactor: make Accounts Closing Balance as well (cherry picked from commit 6e32769e37b94dd268374fbba9658f82de97aaf2) --- .../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 d139db296a6581cd8153a87da146db863da40fcf Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 15:40:53 +0530 Subject: [PATCH 22/33] refactor: cleanup and for better readability (cherry picked from commit 653ae84b3e6431616cb72525fc455624de61fef6) --- .../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 0151f5f191bd8864b06b3f8a72bda5aa65a5be75 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 19:48:35 +0530 Subject: [PATCH 23/33] refactor: utility to convert tuple key to str (cherry picked from commit 5b464ae4c15fc3a17070bfdb36d284e23f18b314) --- .../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 5071cad1618332c4de74c6a9b9fd5ed2975bd07f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 19:55:09 +0530 Subject: [PATCH 24/33] refactor: utility to consolidate results from all dates (cherry picked from commit 7406d8326097435446498bb653ec916c853ffea7) --- .../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 d94000fecfebd163712a5e1a8f3e3bc18129a6e1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 17 Oct 2025 20:27:22 +0530 Subject: [PATCH 25/33] refactor: smaller methods (cherry picked from commit fa3bd6f5a7d1199d1fb1e4d32d11bef267b3cc7a) --- .../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 29e8801b7f3b003245a79f1487de5541549ae399 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Oct 2025 10:59:59 +0530 Subject: [PATCH 26/33] 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 (cherry picked from commit cae123785949415fa0d4cc95df13a5e03a80a7c8) --- .../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 5cc6a1771d2603eb891ad8daa61ae67233843c22 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Oct 2025 12:20:02 +0530 Subject: [PATCH 27/33] refactor: abort processing of all tasks upon cancellation (cherry picked from commit 090e155fd0d65eabc240e6588e7d1439e6ba5777) --- .../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 4ebddc591f0e2e40ba0bc0100864705a8ea256a4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Oct 2025 12:22:45 +0530 Subject: [PATCH 28/33] chore: remove scaffolding (cherry picked from commit 191c0e65a1a95fd457f7a2510a4182af5a7dbbd1) --- .../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 10df19227591ee3bf04452483734b20d60e3ac91 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Oct 2025 12:41:15 +0530 Subject: [PATCH 29/33] chore: progress bar (cherry picked from commit 0b88f98a861ad07f592cdf9c6b8530e57cbb88c8) --- .../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 d5a36fe8aa4b685453798c6709470b69149b29b9 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 20 Oct 2025 13:16:18 +0530 Subject: [PATCH 30/33] refactor: enable legacy controller by default for pcv (cherry picked from commit fe39ce03bbb2b6419452ced07aad510b861dfa69) # Conflicts: # erpnext/accounts/doctype/accounts_settings/accounts_settings.json # erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py --- .../doctype/accounts_settings/accounts_settings.json | 6 +++++- .../test_period_closing_voucher.py | 8 ++++++++ erpnext/patches.txt | 1 + .../v15_0/toggle_legacy_controller_for_period_closing.py | 9 +++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) 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 dad7165b665..6a640d3464c 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -640,7 +640,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" @@ -652,11 +652,15 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], +<<<<<<< HEAD <<<<<<< HEAD "modified": "2025-07-18 13:56:47.192437", ======= "modified": "2025-10-01 15:17:47.168354", >>>>>>> 4888461be2 (refactor: checkbox for pcv controller) +======= + "modified": "2025-10-20 14:06:08.870427", +>>>>>>> fe39ce03bb (refactor: enable legacy controller by default for pcv) "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 34e2fdd9082..809e447aef4 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,7 +13,15 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.accounts.utils import get_fiscal_year +<<<<<<< HEAD class TestPeriodClosingVoucher(unittest.TestCase): +======= +class TestPeriodClosingVoucher(IntegrationTestCase): + def setUp(self): + super().setUp() + frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1) + +>>>>>>> fe39ce03bb (refactor: enable legacy controller by default for pcv) 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 4e05b974d16..bb3d5f07ef8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -423,3 +423,4 @@ erpnext.patches.v15_0.add_company_payment_gateway_account erpnext.patches.v15_0.update_uae_zero_rated_fetch erpnext.patches.v15_0.update_fieldname_in_accounting_dimension_filter 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 d8e50754242a89711b8e94110dabddd788660cd1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 3 Nov 2025 16:44:26 +0530 Subject: [PATCH 31/33] 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 (cherry picked from commit 9c13edc0b9e34c28a067871f260ebef866640558) --- .../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 e09ee63d32cb170703fcd0fd4a0567c054d17035 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 5 Nov 2025 11:42:31 +0530 Subject: [PATCH 32/33] refactor: add paused to select option (cherry picked from commit fca7abf4d61a3a05dfaeb68afa28ec095e5f1dcb) --- .../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 From 5c6cc1ea2a1353565dee8dbbb4fdcf82312e86ef Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 5 Nov 2025 17:05:51 +0530 Subject: [PATCH 33/33] chore: resolve conflicts --- .../accounts_settings/accounts_settings.json | 17 ----------------- .../accounts_settings/accounts_settings.py | 4 ---- .../test_period_closing_voucher.py | 4 ---- .../test_process_period_closing_voucher.py | 16 ---------------- 4 files changed, 41 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 6a640d3464c..b6032bba1fe 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -630,21 +630,12 @@ "fieldname": "fetch_valuation_rate_for_internal_transaction", "fieldtype": "Check", "label": "Fetch Valuation Rate for Internal Transaction" -<<<<<<< HEAD -======= - }, - { - "default": "0", - "fieldname": "use_legacy_budget_controller", - "fieldtype": "Check", - "label": "Use Legacy Budget Controller" }, { "default": "1", "fieldname": "use_legacy_controller_for_pcv", "fieldtype": "Check", "label": "Use Legacy Controller For Period Closing Voucher" ->>>>>>> 4888461be2 (refactor: checkbox for pcv controller) } ], "icon": "icon-cog", @@ -652,15 +643,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], -<<<<<<< HEAD -<<<<<<< HEAD - "modified": "2025-07-18 13:56:47.192437", -======= - "modified": "2025-10-01 15:17:47.168354", ->>>>>>> 4888461be2 (refactor: checkbox for pcv controller) -======= "modified": "2025-10-20 14:06:08.870427", ->>>>>>> fe39ce03bb (refactor: enable legacy controller by default for pcv) "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 7ec9da682dc..5144e87a3a2 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -73,11 +73,7 @@ class AccountsSettings(Document): submit_journal_entries: DF.Check unlink_advance_payment_on_cancelation_of_order: DF.Check unlink_payment_on_cancellation_of_invoice: DF.Check -<<<<<<< HEAD -======= - use_legacy_budget_controller: DF.Check use_legacy_controller_for_pcv: DF.Check ->>>>>>> 4888461be2 (refactor: checkbox for pcv controller) # end: auto-generated types def validate(self): 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 809e447aef4..2aa484d05a1 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,15 +13,11 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal from erpnext.accounts.utils import get_fiscal_year -<<<<<<< HEAD class TestPeriodClosingVoucher(unittest.TestCase): -======= -class TestPeriodClosingVoucher(IntegrationTestCase): def setUp(self): super().setUp() frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1) ->>>>>>> fe39ce03bb (refactor: enable legacy controller by default for pcv) 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/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 index 17ce2c13b09..e695b11bcb5 100644 --- 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 @@ -2,19 +2,3 @@ # 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