From 75999a7ae4d62927687a3448a9e2cd0e331d9210 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 10 Dec 2025 00:38:33 +0530 Subject: [PATCH 1/6] fix: make amount and percent field read only when distribute equally is enabled --- erpnext/accounts/doctype/budget/budget.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/erpnext/accounts/doctype/budget/budget.js b/erpnext/accounts/doctype/budget/budget.js index 3ac7b8fe8f8..968700ede4c 100644 --- a/erpnext/accounts/doctype/budget/budget.js +++ b/erpnext/accounts/doctype/budget/budget.js @@ -42,6 +42,8 @@ frappe.ui.form.on("Budget", { ); } } + + toggle_distribution_fields(frm); }, budget_against: function (frm) { @@ -58,6 +60,12 @@ frappe.ui.form.on("Budget", { } }, + distribute_equally: function (frm) { + console.log("here"); + + toggle_distribution_fields(frm); + }, + set_null_value: function (frm) { if (frm.doc.budget_against == "Cost Center") { frm.set_value("project", null); @@ -111,3 +119,13 @@ frappe.ui.form.on("Budget Distribution", { } }, }); + +function toggle_distribution_fields(frm) { + const grid = frm.fields_dict.budget_distribution.grid; + + ["amount", "percent"].forEach((field) => { + grid.update_docfield_property(field, "read_only", frm.doc.distribute_equally); + }); + + grid.refresh(); +} From 6a03fc6edecf93e9ff43e8bdf0724cb5c1f2905c Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 10 Dec 2025 00:41:39 +0530 Subject: [PATCH 2/6] fix: add company-based filter to account field --- erpnext/accounts/doctype/budget/budget.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erpnext/accounts/doctype/budget/budget.js b/erpnext/accounts/doctype/budget/budget.js index 968700ede4c..4ab94ed0f55 100644 --- a/erpnext/accounts/doctype/budget/budget.js +++ b/erpnext/accounts/doctype/budget/budget.js @@ -12,6 +12,15 @@ frappe.ui.form.on("Budget", { }; }); + frm.set_query("account", function () { + return { + filters: { + is_group: 0, + company: frm.doc.company, + }, + }; + }); + erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); frappe.db.get_single_value("Accounts Settings", "use_legacy_budget_controller").then((value) => { if (value) { From d42aad18a71c0f40f25fc9d604b3f4fafe07fa9d Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 10 Dec 2025 00:46:03 +0530 Subject: [PATCH 3/6] fix: remove revise budget permission --- erpnext/accounts/doctype/budget/budget.js | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/erpnext/accounts/doctype/budget/budget.js b/erpnext/accounts/doctype/budget/budget.js index 4ab94ed0f55..0d590b4817f 100644 --- a/erpnext/accounts/doctype/budget/budget.js +++ b/erpnext/accounts/doctype/budget/budget.js @@ -33,23 +33,13 @@ frappe.ui.form.on("Budget", { frm.trigger("toggle_reqd_fields"); if (!frm.doc.__islocal && frm.doc.docstatus == 1) { - let exception_role = await frappe.db.get_value( - "Company", - frm.doc.company, - "exception_budget_approver_role" + frm.add_custom_button( + __("Revise Budget"), + function () { + frm.events.revise_budget_action(frm); + }, + __("Actions") ); - - const role = exception_role.message.exception_budget_approver_role; - - if (role && frappe.user.has_role(role)) { - frm.add_custom_button( - __("Revise Budget"), - function () { - frm.events.revise_budget_action(frm); - }, - __("Actions") - ); - } } toggle_distribution_fields(frm); From 1c82f42fa81351805b744fa6685a7423a2cf29c4 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 10 Dec 2025 02:19:29 +0530 Subject: [PATCH 4/6] fix: better manual budget distribution on update --- erpnext/accounts/doctype/budget/budget.js | 2 - erpnext/accounts/doctype/budget/budget.py | 62 ++++++++++++++++------- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/budget/budget.js b/erpnext/accounts/doctype/budget/budget.js index 0d590b4817f..6f8e0cdd43a 100644 --- a/erpnext/accounts/doctype/budget/budget.js +++ b/erpnext/accounts/doctype/budget/budget.js @@ -60,8 +60,6 @@ frappe.ui.form.on("Budget", { }, distribute_equally: function (frm) { - console.log("here"); - toggle_distribution_fields(frm); }, diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index d798da5b589..7280f2ee5fc 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -235,23 +235,43 @@ class Budget(Document): self.validate_distribution_totals() def allocate_budget(self): - if self.revision_of: + if self._should_skip_allocation(): + return + + if self._should_recalculate_manual_distribution(): + self._recalculate_manual_distribution() return if not self.should_regenerate_budget_distribution(): return - self.set("budget_distribution", []) + self._regenerate_distribution() - periods = self.get_budget_periods() - total_periods = len(periods) - row_percent = 100 / total_periods if total_periods else 0 + def _should_skip_allocation(self): + return self.revision_of and not self.distribute_equally - for start_date, end_date in periods: - row = self.append("budget_distribution", {}) - row.start_date = start_date - row.end_date = end_date - self.add_allocated_amount(row, row_percent) + def _should_recalculate_manual_distribution(self): + return ( + not self.distribute_equally + and bool(self.budget_distribution) + and self._is_only_budget_amount_changed() + ) + + def _is_only_budget_amount_changed(self): + old = self.get_doc_before_save() + if not old: + return False + + return ( + old.budget_amount != self.budget_amount + and old.distribution_frequency == self.distribution_frequency + and old.budget_start_date == self.budget_start_date + and old.budget_end_date == self.budget_end_date + ) + + def _recalculate_manual_distribution(self): + for row in self.budget_distribution: + row.amount = flt((row.percent / 100) * self.budget_amount, 3) def should_regenerate_budget_distribution(self): """Check whether budget distribution should be recalculated.""" @@ -265,7 +285,6 @@ class Budget(Document): "to_fiscal_year", "budget_amount", "distribution_frequency", - "distribute_equally", ] for field in changed_fields: if old_doc.get(field) != self.get(field): @@ -273,6 +292,19 @@ class Budget(Document): return bool(self.distribute_equally) + def _regenerate_distribution(self): + self.set("budget_distribution", []) + + periods = self.get_budget_periods() + total_periods = len(periods) + row_percent = 100 / total_periods if total_periods else 0 + + for start_date, end_date in periods: + row = self.append("budget_distribution", {}) + row.start_date = start_date + row.end_date = end_date + self.add_allocated_amount(row, row_percent) + def get_budget_periods(self): """Return list of (start_date, end_date) tuples based on frequency.""" frequency = self.distribution_frequency @@ -312,12 +344,8 @@ class Budget(Document): }.get(frequency, 1) def add_allocated_amount(self, row, row_percent): - if not self.distribute_equally: - row.amount = 0 - row.percent = 0 - else: - row.amount = flt(self.budget_amount * row_percent / 100, 3) - row.percent = flt(row_percent, 3) + row.amount = flt(self.budget_amount * row_percent / 100, 3) + row.percent = flt(row_percent, 3) def validate_distribution_totals(self): if self.should_regenerate_budget_distribution(): From f194ac093c0a4e75201ab8b48b83274a631153c9 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 10 Dec 2025 02:48:44 +0530 Subject: [PATCH 5/6] feat: show budget distribution total --- erpnext/accounts/doctype/budget/budget.js | 15 +++++++++++ erpnext/accounts/doctype/budget/budget.json | 28 +++++++++++++++++++-- erpnext/accounts/doctype/budget/budget.py | 4 +++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/budget/budget.js b/erpnext/accounts/doctype/budget/budget.js index 6f8e0cdd43a..5128fdc9c92 100644 --- a/erpnext/accounts/doctype/budget/budget.js +++ b/erpnext/accounts/doctype/budget/budget.js @@ -55,6 +55,7 @@ frappe.ui.form.on("Budget", { frm.doc.budget_distribution.forEach((row) => { row.amount = flt((row.percent / 100) * frm.doc.budget_amount, 2); }); + set_total_budget_amount(frm); frm.refresh_field("budget_distribution"); } }, @@ -105,6 +106,8 @@ frappe.ui.form.on("Budget Distribution", { let row = frappe.get_doc(cdt, cdn); if (frm.doc.budget_amount) { row.percent = flt((row.amount / frm.doc.budget_amount) * 100, 2); + + set_total_budget_amount(frm); frm.refresh_field("budget_distribution"); } }, @@ -112,11 +115,23 @@ frappe.ui.form.on("Budget Distribution", { let row = frappe.get_doc(cdt, cdn); if (frm.doc.budget_amount) { row.amount = flt((row.percent / 100) * frm.doc.budget_amount, 2); + + set_total_budget_amount(frm); frm.refresh_field("budget_distribution"); } }, }); +function set_total_budget_amount(frm) { + let total = 0; + + (frm.doc.budget_distribution || []).forEach((row) => { + total += flt(row.amount); + }); + + frm.set_value("budget_distribution_total", total); +} + function toggle_distribution_fields(frm) { const grid = frm.fields_dict.budget_distribution.grid; diff --git a/erpnext/accounts/doctype/budget/budget.json b/erpnext/accounts/doctype/budget/budget.json index 8476a2831f0..960d62e4c99 100644 --- a/erpnext/accounts/doctype/budget/budget.json +++ b/erpnext/accounts/doctype/budget/budget.json @@ -25,6 +25,10 @@ "distribute_equally", "section_break_fpdt", "budget_distribution", + "section_break_wkqb", + "column_break_paum", + "column_break_nwor", + "budget_distribution_total", "section_break_6", "applicable_on_material_request", "action_if_annual_budget_exceeded_on_mr", @@ -222,7 +226,8 @@ }, { "fieldname": "section_break_fpdt", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_border": 1 }, { "fieldname": "budget_distribution", @@ -303,13 +308,32 @@ "options": "Monthly\nQuarterly\nHalf-Yearly\nYearly", "read_only_depends_on": "eval: doc.revision_of", "reqd": 1 + }, + { + "fieldname": "section_break_wkqb", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_paum", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_nwor", + "fieldtype": "Column Break" + }, + { + "fieldname": "budget_distribution_total", + "fieldtype": "Currency", + "label": "Budget Distribution Total", + "no_copy": 1, + "read_only": 1 } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-11-19 17:00:00.648224", + "modified": "2025-12-10 02:35:01.197613", "modified_by": "Administrator", "module": "Accounts", "name": "Budget", diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index 7280f2ee5fc..39528da99db 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -53,6 +53,7 @@ class Budget(Document): budget_against: DF.Literal["", "Cost Center", "Project"] budget_amount: DF.Currency budget_distribution: DF.Table[BudgetDistribution] + budget_distribution_total: DF.Currency budget_end_date: DF.Date | None budget_start_date: DF.Date | None company: DF.Link @@ -230,6 +231,7 @@ class Budget(Document): def before_save(self): self.allocate_budget() + self.budget_distribution_total = sum(flt(row.amount) for row in self.budget_distribution) def on_update(self): self.validate_distribution_totals() @@ -305,6 +307,8 @@ class Budget(Document): row.end_date = end_date self.add_allocated_amount(row, row_percent) + self.budget_distribution_total = self.budget_amount + def get_budget_periods(self): """Return list of (start_date, end_date) tuples based on frequency.""" frequency = self.distribution_frequency From ed4c17d3a20df5c83eea2b67e4ec06dbc51183cb Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Wed, 10 Dec 2025 03:06:05 +0530 Subject: [PATCH 6/6] fix: patch to set budget distribution total --- erpnext/patches.txt | 3 ++- .../v16_0/populate_budget_distribution_total.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v16_0/populate_budget_distribution_total.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3e640e7bf0c..c0f8ff6e2e2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -450,4 +450,5 @@ erpnext.patches.v16_0.set_valuation_method_on_companies erpnext.patches.v15_0.migrate_old_item_wise_tax_detail_data_to_table erpnext.patches.v16_0.migrate_budget_records_to_new_structure erpnext.patches.v16_0.update_currency_exchange_settings_for_frankfurter -erpnext.patches.v16_0.migrate_account_freezing_settings_to_company \ No newline at end of file +erpnext.patches.v16_0.migrate_account_freezing_settings_to_company +erpnext.patches.v16_0.populate_budget_distribution_total \ No newline at end of file diff --git a/erpnext/patches/v16_0/populate_budget_distribution_total.py b/erpnext/patches/v16_0/populate_budget_distribution_total.py new file mode 100644 index 00000000000..033fb968b4f --- /dev/null +++ b/erpnext/patches/v16_0/populate_budget_distribution_total.py @@ -0,0 +1,11 @@ +import frappe +from frappe.utils import flt + + +def execute(): + budgets = frappe.get_all("Budget", filters={"docstatus": ["in", [0, 1]]}, fields=["name"]) + + for b in budgets: + doc = frappe.get_doc("Budget", b.name) + total = sum(flt(row.amount) for row in doc.budget_distribution) + doc.db_set("budget_distribution_total", total, update_modified=False)