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