Skip to content

Commit

Permalink
fix: error messages while evaluating formulas and handle line boundar…
Browse files Browse the repository at this point in the history
…ies (#35989)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
  • Loading branch information
saurabh6790 and ruchamahabal committed Jul 11, 2023
1 parent e05bb10 commit 4af57a7
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 15 deletions.
52 changes: 37 additions & 15 deletions erpnext/payroll/doctype/salary_slip/salary_slip.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
get_payroll_period,
get_period_factor,
)
from erpnext.payroll.utils import prepare_error_msg, sanitize_expression
from erpnext.utilities.transaction_base import TransactionBase


Expand Down Expand Up @@ -726,32 +727,53 @@ def get_data_for_eval(self):

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} <br> 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"):
Expand Down
66 changes: 66 additions & 0 deletions erpnext/payroll/utils.py
Original file line number Diff line number Diff line change
@@ -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 += "<br><br>{0}: {1}<br><br>{2}: {3}".format(
frappe.bold(_("Expression:")), expression, frappe.bold(_("Error:")), error
)

if description:
msg += "<br><br>{0}: {1}".format(frappe.bold(_("Hint:")), description)

return msg

0 comments on commit 4af57a7

Please sign in to comment.