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"); 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) { set_null_value: function (frm) {
if (frm.doc.budget_against == "Cost Center") { if (frm.doc.budget_against == "Cost Center") {
frm.set_value("project", null); 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", "fieldname": "account",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Account", "label": "Account",
"options": "Account", "options": "Account",
"read_only_depends_on": "eval: doc.revision_of", "read_only_depends_on": "eval: doc.revision_of",
@@ -258,7 +259,6 @@
"fieldname": "budget_amount", "fieldname": "budget_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Budget Amount", "label": "Budget Amount",
"read_only_depends_on": "eval: doc.revision_of",
"reqd": 1 "reqd": 1
}, },
{ {
@@ -304,7 +304,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-10-30 19:07:51.022844", "modified": "2025-10-31 01:13:15.114440",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Budget", "name": "Budget",

View File

@@ -160,6 +160,9 @@ class Budget(Document):
def before_save(self): def before_save(self):
self.allocate_budget() self.allocate_budget()
def on_update(self):
self.validate_distribution_totals()
def allocate_budget(self): def allocate_budget(self):
if self.revision_of: if self.revision_of:
return return
@@ -183,14 +186,14 @@ class Budget(Document):
def should_regenerate_budget_distribution(self): def should_regenerate_budget_distribution(self):
"""Check whether budget distribution should be recalculated.""" """Check whether budget distribution should be recalculated."""
old_doc = self.get_doc_before_save() if not self.is_new() else None old_doc = self.get_doc_before_save() if not self.is_new() else None
if not old_doc or not self.budget_distribution:
if not self.budget_distribution:
return True return True
if old_doc: if old_doc:
changed_fields = [ changed_fields = [
"from_fiscal_year", "from_fiscal_year",
"to_fiscal_year", "to_fiscal_year",
"budget_amount",
"allocation_frequency", "allocation_frequency",
"distribute_equally", "distribute_equally",
] ]
@@ -255,9 +258,28 @@ class Budget(Document):
row.amount = 0 row.amount = 0
row.percent = 0 row.percent = 0
else: 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) 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): def validate_expense_against_budget(args, expense_amount=0):
args = frappe._dict(args) args = frappe._dict(args)

View File

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