diff --git a/erpnext/controllers/budget_controller.py b/erpnext/controllers/budget_controller.py index 6db7cfa0cc2..49e63aea6e6 100644 --- a/erpnext/controllers/budget_controller.py +++ b/erpnext/controllers/budget_controller.py @@ -4,7 +4,7 @@ import frappe from frappe import _, qb from frappe.query_builder import Criterion from frappe.query_builder.functions import IfNull, Sum -from frappe.utils import flt, fmt_money, get_link_to_form +from frappe.utils import comma_and, flt, fmt_money, get_link_to_form from erpnext.accounts.doctype.budget.budget import BudgetError, get_accumulated_monthly_budget from erpnext.accounts.utils import get_fiscal_year @@ -87,7 +87,8 @@ class BudgetValidation: self.get_ordered_amount(key) self.get_requested_amount(key) - self.handle_action(key, v) + # Pre-emptive validation before hitting ledger + self.handle_actions(key, v) # Validation happens after submit for Purchase Order and # Material Request and so will be included in the query @@ -96,7 +97,7 @@ class BudgetValidation: v.current_actual_exp_amount = sum([x.debit - x.credit for x in v.get("gl_to_process", [])]) self.get_actual_expense(key) - self.handle_action(key, v) + self.handle_actions(key, v) def get_child_nodes(self, budget_against, dimension): lft, rgt = frappe.db.get_all( @@ -328,7 +329,7 @@ class BudgetValidation: ) self.execute_action(config.action_for_monthly, _msg) - def handle_action(self, key, v_map): + def handle_purchase_order_overlimit(self, key, v_map): self.handle_individual_doctype_action( key, frappe._dict( @@ -344,6 +345,8 @@ class BudgetValidation: v_map.current_ordered_amount, v_map.accumulated_monthly_budget, ) + + def handle_material_request_overlimit(self, key, v_map): self.handle_individual_doctype_action( key, frappe._dict( @@ -359,6 +362,8 @@ class BudgetValidation: v_map.current_requested_amount, v_map.accumulated_monthly_budget, ) + + def handle_actual_expense_overlimit(self, key, v_map): self.handle_individual_doctype_action( key, frappe._dict( @@ -375,6 +380,49 @@ class BudgetValidation: v_map.accumulated_monthly_budget, ) + def handle_actions(self, key, v_map): + self.handle_purchase_order_overlimit(key, v_map) + self.handle_material_request_overlimit(key, v_map) + self.handle_actual_expense_overlimit(key, v_map) + # PO + MR + Actual Expense + self.handle_cumulative_overlimit(key, v_map) + + def handle_cumulative_overlimit(self, key, v_map): + self.handle_cumulative_overlimit_for_monthly(key, v_map) + self.handle_cumulative_overlimit_for_annual(key, v_map) + + def budget_applicable_for(self, budget_doc) -> str: + doctypes = [] + if budget_doc.applicable_on_purchase_order: + doctypes.append("Purchase Order") + if budget_doc.applicable_on_material_request: + doctypes.append("Material Request") + if budget_doc.applicable_on_booking_actual_expenses: + doctypes.append("Actual Expense") + return comma_and(doctypes) + + def handle_cumulative_overlimit_for_monthly(self, key, v_map): + current_amt = ( + v_map.current_ordered_amount + v_map.current_requested_amount + v_map.current_actual_exp_amount + ) + monthly_diff = ( + v_map.ordered_amount + v_map.requested_amount + v_map.actual_expense + current_amt + ) - v_map.accumulated_monthly_budget + if monthly_diff > 0: + currency = frappe.get_cached_value("Company", self.company, "default_currency") + _msg = _( + "Accumulated Monthly Budget for Account {0} against {1} {2} is {3}. It will be collectively ({4}) exceeded by {5}" + ).format( + frappe.bold(key[2]), + frappe.bold(frappe.unscrub(key[0])), + frappe.bold(key[1]), + frappe.bold(fmt_money(v_map.accumulated_montly_budget, currency=currency)), + self.budget_applicable_for(v_map.budget_doc), + frappe.bold(fmt_money(monthly_diff, currency=currency)), + ) + self.stop(_msg) + + def handle_cumulative_overlimit_for_annual(self, key, v_map): current_amt = ( v_map.current_ordered_amount + v_map.current_requested_amount + v_map.current_actual_exp_amount ) @@ -384,12 +432,13 @@ class BudgetValidation: if total_diff > 0: currency = frappe.get_cached_value("Company", self.company, "default_currency") _msg = _( - "Annual Budget for Account {0} against {1} {2} is {3}. It will be exceeded by {4}" + "Annual Budget for Account {0} against {1} {2} is {3}. It will be collectively ({4}) exceeded by {5}" ).format( frappe.bold(key[2]), frappe.bold(frappe.unscrub(key[0])), frappe.bold(key[1]), frappe.bold(fmt_money(v_map.budget_amount, currency=currency)), + self.budget_applicable_for(v_map.budget_doc), frappe.bold(fmt_money(total_diff, currency=currency)), ) self.stop(_msg)