diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index 60d3f78534e..b3fb8264ed4 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -46,6 +46,7 @@ from erpnext.payroll.doctype.payroll_period.payroll_period import (
get_payroll_period,
get_period_factor,
)
+from erpnext.payroll.utils import prepare_error_msg, sanitize_expression
from erpnext.utilities.transaction_base import TransactionBase
@@ -726,32 +727,53 @@ class SalarySlip(TransactionBase):
return data, default_data
- def eval_condition_and_formula(self, d, data):
+ def eval_condition_and_formula(self, struct_row, data):
try:
- condition = d.condition.strip().replace("\n", " ") if d.condition else None
+ condition = sanitize_expression(struct_row.condition)
if condition:
if not frappe.safe_eval(condition, self.whitelisted_globals, data):
return None
- amount = d.amount
- if d.amount_based_on_formula:
- formula = d.formula.strip().replace("\n", " ") if d.formula else None
+ amount = struct_row.amount
+ if struct_row.amount_based_on_formula:
+ formula = sanitize_expression(struct_row.formula)
if formula:
- amount = flt(frappe.safe_eval(formula, self.whitelisted_globals, data), d.precision("amount"))
+ amount = flt(
+ frappe.safe_eval(formula, self.whitelisted_globals, data), struct_row.precision("amount")
+ )
if amount:
- data[d.abbr] = amount
+ data[struct_row.abbr] = amount
return amount
- except NameError as err:
- frappe.throw(
- _("{0}
This error can be due to missing or deleted field.").format(err),
- title=_("Name error"),
+ except NameError as ne:
+ message = prepare_error_msg(
+ row=struct_row,
+ error=ne,
+ expression=formula or condition,
+ description=_("This error can be due to missing or deleted field."),
)
- except SyntaxError as err:
- frappe.throw(_("Syntax error in formula or condition: {0}").format(err))
+
+ frappe.throw(message, title=_("Name error"))
+
+ except SyntaxError as se:
+ message = prepare_error_msg(
+ row=struct_row,
+ error=se,
+ expression=formula or condition,
+ description=_("Please check the syntax of your formula."),
+ )
+
+ frappe.throw(message, title=_("Syntax error"))
+
except Exception as e:
- frappe.throw(_("Error in formula or condition: {0}").format(e))
- raise
+ message = prepare_error_msg(
+ row=struct_row,
+ error=e,
+ expression=formula or condition,
+ description=_("This error can be due to invalid formula or condition."),
+ )
+
+ frappe.throw(message, title=_("Error in formula or condition"))
def add_employee_benefits(self, payroll_period):
for struct_row in self._salary_structure_doc.get("earnings"):
diff --git a/erpnext/payroll/utils.py b/erpnext/payroll/utils.py
new file mode 100644
index 00000000000..78f181779fe
--- /dev/null
+++ b/erpnext/payroll/utils.py
@@ -0,0 +1,66 @@
+from typing import Optional
+
+import frappe
+from frappe import _
+from frappe.utils import get_link_to_form
+
+
+def sanitize_expression(string: Optional[str] = None) -> Optional[str]:
+ """
+ Sanitizes an expression string by removing leading/trailing spaces, newlines, and line boundaries.
+
+ Args:
+ string (Optional[str]): The input string to be sanitized (default: None).
+
+ Returns:
+ Optional[str]: The sanitized string or None if the input string is empty or None.
+
+ Example:
+ expression = "\r\n gross_pay > 10000\n "
+ sanitized_expr = sanitize_expression(expression)
+ """
+ if not string:
+ return None
+
+ parts = string.strip().splitlines()
+ string = " ".join(parts)
+
+ return string
+
+
+def prepare_error_msg(*, row: dict, error: str, expression: str, description: str) -> str:
+ """
+ Prepares an error message string with formatted information about the error.
+
+ Args:
+ row (dict): A dictionary representing the row data.
+ error (str): The error message.
+ expression (str): The expression that caused the error.
+ description (str): Additional description or hint for the error (optional).
+
+ Returns:
+ str: The formatted error message string.
+
+ Example:
+ row = {
+ "parenttype": "Salary Structure",
+ "parent": "Salary Structure-00001",
+ "parentfield": "earnings",
+ "idx": 1
+ }
+ error = "SyntaxError: invalid syntax"
+ expression = " 200 if (gross_pay>10000 and month!= 'Feb')) else 0 "
+ description = "Check the syntax of the expression."
+ error_msg = prepare_error_msg(row=row, error=error, expression=expression, description=description)
+ """
+ msg = _("Error in {0} while evaluating the {1} {2} at row {3}").format(
+ row.parentfield.title(), row.parenttype, get_link_to_form(row.parenttype, row.parent), row.idx
+ )
+ msg += "
{0}: {1}
{2}: {3}".format(
+ frappe.bold(_("Expression:")), expression, frappe.bold(_("Error:")), error
+ )
+
+ if description:
+ msg += "
{0}: {1}".format(frappe.bold(_("Hint:")), description)
+
+ return msg