refactor: better manual budget distribution ux

This commit is contained in:
khushi8112
2025-10-31 13:43:07 +05:30
parent 1cb03db43b
commit e40fe9919c
4 changed files with 59 additions and 11 deletions

View File

@@ -49,6 +49,15 @@ frappe.ui.form.on("Budget", {
frm.trigger("toggle_reqd_fields");
},
budget_amount(frm) {
if (frm.doc.budget_distribution?.length) {
frm.doc.budget_distribution.forEach((row) => {
row.amount = flt((row.percent / 100) * frm.doc.budget_amount, 2);
});
frm.refresh_field("budget_distribution");
}
},
set_null_value: function (frm) {
if (frm.doc.budget_against == "Cost Center") {
frm.set_value("project", null);
@@ -85,3 +94,20 @@ frappe.ui.form.on("Budget", {
);
},
});
frappe.ui.form.on("Budget Distribution", {
amount(frm, cdt, cdn) {
let row = frappe.get_doc(cdt, cdn);
if (frm.doc.budget_amount) {
row.percent = flt((row.amount / frm.doc.budget_amount) * 100, 2);
frm.refresh_field("budget_distribution");
}
},
percent(frm, cdt, cdn) {
let row = frappe.get_doc(cdt, cdn);
if (frm.doc.budget_amount) {
row.amount = flt((row.percent / 100) * frm.doc.budget_amount, 2);
frm.refresh_field("budget_distribution");
}
},
});

View File

@@ -231,6 +231,7 @@
{
"fieldname": "account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Account",
"options": "Account",
"read_only_depends_on": "eval: doc.revision_of",
@@ -258,7 +259,6 @@
"fieldname": "budget_amount",
"fieldtype": "Currency",
"label": "Budget Amount",
"read_only_depends_on": "eval: doc.revision_of",
"reqd": 1
},
{
@@ -304,7 +304,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-10-30 19:07:51.022844",
"modified": "2025-10-31 01:13:15.114440",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Budget",

View File

@@ -160,6 +160,9 @@ class Budget(Document):
def before_save(self):
self.allocate_budget()
def on_update(self):
self.validate_distribution_totals()
def allocate_budget(self):
if self.revision_of:
return
@@ -183,14 +186,14 @@ class Budget(Document):
def should_regenerate_budget_distribution(self):
"""Check whether budget distribution should be recalculated."""
old_doc = self.get_doc_before_save() if not self.is_new() else None
if not self.budget_distribution:
if not old_doc or not self.budget_distribution:
return True
if old_doc:
changed_fields = [
"from_fiscal_year",
"to_fiscal_year",
"budget_amount",
"allocation_frequency",
"distribute_equally",
]
@@ -255,9 +258,28 @@ class Budget(Document):
row.amount = 0
row.percent = 0
else:
row.amount = flt(self.budget_amount * row_percent / 100, 2)
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():
return
total_amount = sum(d.amount for d in self.budget_distribution)
total_percent = sum(d.percent for d in self.budget_distribution)
if flt(abs(total_amount - self.budget_amount), 2) > 0.10:
frappe.throw(
_("Total distributed amount {0} must equal Budget Amount {1}").format(
flt(total_amount, 2), self.budget_amount
)
)
if round(total_percent, 2) != 100:
frappe.throw(
_("Total distribution percent must equal 100 (currently {0})").format(round(total_percent, 2))
)
def validate_expense_against_budget(args, expense_amount=0):
args = frappe._dict(args)

View File

@@ -551,16 +551,16 @@ class TestBudget(ERPNextTestSuite):
budget_amount=30000,
budget_start_date="2025-04-01",
budget_end_date="2025-06-30",
do_not_save=True,
do_not_save=False,
submit_budget=False,
)
budget.budget_distribution = []
for row in [
{"start_date": "2025-04-01", "end_date": "2025-04-30", "amount": 10000},
{"start_date": "2025-05-01", "end_date": "2025-05-31", "amount": 15000},
{"start_date": "2025-06-01", "end_date": "2025-06-30", "amount": 5000},
{"start_date": "2025-04-01", "end_date": "2025-04-30", "amount": 10000, "percent": 33.33},
{"start_date": "2025-05-01", "end_date": "2025-05-31", "amount": 15000, "percent": 50.00},
{"start_date": "2025-06-01", "end_date": "2025-06-30", "amount": 5000, "percent": 16.67},
]:
budget.append("budget_distribution", row)
@@ -608,10 +608,10 @@ class TestBudget(ERPNextTestSuite):
def test_duplicate_budget_validation(self):
make_budget(
budget_against="Cost Center",
distribute_equally=0,
distribute_equally=1,
budget_amount=15000,
do_not_save=False,
submit_budget=False,
submit_budget=True,
)
budget = frappe.new_doc("Budget")