mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-17 00:25:01 +00:00
refactor: better manual budget distribution ux
This commit is contained in:
@@ -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");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user