fix: error handling and messages

- remove savepoints since submission should stop if any error occurs

- refactor variable naming and msgprints

- test Salary Slip creation failure

- fix(test): explicitly commit after payroll entry creation so that the first salary slip creation failure does not rollback the Payroll Entry insert
This commit is contained in:
Rucha Mahabal
2022-06-02 15:08:49 +05:30
parent 661e05e693
commit d641f26035
2 changed files with 38 additions and 28 deletions

View File

@@ -54,7 +54,7 @@ class PayrollEntry(Document):
if self.validate_employee_attendance(): if self.validate_employee_attendance():
frappe.throw(_("Cannot Submit, Employees left to mark attendance")) frappe.throw(_("Cannot Submit, Employees left to mark attendance"))
def set_status(self, status=None, update=True): def set_status(self, status=None, update=False):
if not status: if not status:
status = {0: "Draft", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0] status = {0: "Draft", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
@@ -850,7 +850,6 @@ def log_payroll_failure(process, payroll_entry, error):
def create_salary_slips_for_employees(employees, args, publish_progress=True): def create_salary_slips_for_employees(employees, args, publish_progress=True):
try: try:
frappe.db.savepoint("salary_slip_creation")
payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry) payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
salary_slips_exist_for = get_existing_salary_slips(employees, args) salary_slips_exist_for = get_existing_salary_slips(employees, args)
count = 0 count = 0
@@ -879,7 +878,7 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True):
) )
except Exception as e: except Exception as e:
frappe.db.rollback(save_point="salary_slip_creation") frappe.db.rollback()
log_payroll_failure("creation", payroll_entry, e) log_payroll_failure("creation", payroll_entry, e)
finally: finally:
@@ -887,24 +886,25 @@ def create_salary_slips_for_employees(employees, args, publish_progress=True):
frappe.publish_realtime("completed_salary_slip_creation") frappe.publish_realtime("completed_salary_slip_creation")
def show_payroll_submission_status(submitted, not_submitted, salary_slip): def show_payroll_submission_status(submitted, unsubmitted, payroll_entry):
if not submitted and not not_submitted: if not submitted and not unsubmitted:
frappe.msgprint( frappe.msgprint(
_( _(
"No salary slip found to submit for the above selected criteria OR salary slip already submitted" "No salary slip found to submit for the above selected criteria OR salary slip already submitted"
) )
) )
return elif submitted and not unsubmitted:
if submitted:
frappe.msgprint( frappe.msgprint(
_("Salary Slip submitted for period from {0} to {1}").format( _("Salary Slips submitted for period from {0} to {1}").format(
salary_slip.start_date, salary_slip.end_date payroll_entry.start_date, payroll_entry.end_date
)
)
elif unsubmitted:
frappe.msgprint(
_("Could not submit some Salary Slips: {}").format(
", ".join(get_link_to_form("Salary Slip", entry) for entry in unsubmitted)
) )
) )
if not_submitted:
frappe.msgprint(_("Could not submit some Salary Slips"))
def get_existing_salary_slips(employees, args): def get_existing_salary_slips(employees, args):
@@ -922,23 +922,21 @@ def get_existing_salary_slips(employees, args):
def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True): def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True):
try: try:
frappe.db.savepoint("salary_slip_submission")
submitted = [] submitted = []
not_submitted = [] unsubmitted = []
frappe.flags.via_payroll_entry = True frappe.flags.via_payroll_entry = True
count = 0 count = 0
for entry in salary_slips: for entry in salary_slips:
salary_slip = frappe.get_doc("Salary Slip", entry[0]) salary_slip = frappe.get_doc("Salary Slip", entry[0])
if salary_slip.net_pay < 0: if salary_slip.net_pay < 0:
not_submitted.append(entry[0]) unsubmitted.append(entry[0])
else: else:
try: try:
salary_slip.submit() salary_slip.submit()
submitted.append(salary_slip) submitted.append(salary_slip)
except frappe.ValidationError: except frappe.ValidationError:
not_submitted.append(entry[0]) unsubmitted.append(entry[0])
count += 1 count += 1
if publish_progress: if publish_progress:
@@ -949,10 +947,10 @@ def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progr
payroll_entry.email_salary_slip(submitted) payroll_entry.email_salary_slip(submitted)
payroll_entry.db_set({"salary_slips_submitted": 1, "status": "Submitted", "error_message": ""}) payroll_entry.db_set({"salary_slips_submitted": 1, "status": "Submitted", "error_message": ""})
show_payroll_submission_status(submitted, not_submitted, salary_slip) show_payroll_submission_status(submitted, unsubmitted, payroll_entry)
except Exception as e: except Exception as e:
frappe.db.rollback(save_point="salary_slip_submission") frappe.db.rollback()
log_payroll_failure("submission", payroll_entry, e) log_payroll_failure("submission", payroll_entry, e)
finally: finally:

View File

@@ -299,7 +299,7 @@ class TestPayrollEntry(FrappeTestCase):
# enqueue salary slip creation via payroll entry # enqueue salary slip creation via payroll entry
# Payroll Entry status should change to Queued # Payroll Entry status should change to Queued
dates = get_start_end_dates("Monthly", nowdate()) dates = get_start_end_dates("Monthly", nowdate())
payroll_entry = get_payroll_entry_data( payroll_entry = get_payroll_entry(
start_date=dates.start_date, start_date=dates.start_date,
end_date=dates.end_date, end_date=dates.end_date,
payable_account=company_doc.default_payroll_payable_account, payable_account=company_doc.default_payroll_payable_account,
@@ -308,7 +308,7 @@ class TestPayrollEntry(FrappeTestCase):
cost_center="Main - _TC", cost_center="Main - _TC",
) )
frappe.flags.enqueue_payroll_entry = True frappe.flags.enqueue_payroll_entry = True
payroll_entry.create_salary_slips() payroll_entry.submit()
payroll_entry.reload() payroll_entry.reload()
self.assertEqual(payroll_entry.status, "Queued") self.assertEqual(payroll_entry.status, "Queued")
@@ -335,7 +335,7 @@ class TestPayrollEntry(FrappeTestCase):
# salary slip submission via payroll entry # salary slip submission via payroll entry
# Payroll Entry status should change to Failed because of the missing account setup # Payroll Entry status should change to Failed because of the missing account setup
dates = get_start_end_dates("Monthly", nowdate()) dates = get_start_end_dates("Monthly", nowdate())
payroll_entry = get_payroll_entry_data( payroll_entry = get_payroll_entry(
start_date=dates.start_date, start_date=dates.start_date,
end_date=dates.end_date, end_date=dates.end_date,
payable_account=company_doc.default_payroll_payable_account, payable_account=company_doc.default_payroll_payable_account,
@@ -343,7 +343,16 @@ class TestPayrollEntry(FrappeTestCase):
company=company_doc.name, company=company_doc.name,
cost_center="Main - _TC", cost_center="Main - _TC",
) )
payroll_entry.create_salary_slips()
# set employee as Inactive to check creation failure
frappe.db.set_value("Employee", employee, "status", "Inactive")
payroll_entry.submit()
payroll_entry.reload()
self.assertEqual(payroll_entry.status, "Failed")
self.assertIsNotNone(payroll_entry.error_message)
frappe.db.set_value("Employee", employee, "status", "Active")
payroll_entry.submit()
payroll_entry.submit_salary_slips() payroll_entry.submit_salary_slips()
payroll_entry.reload() payroll_entry.reload()
@@ -369,7 +378,7 @@ class TestPayrollEntry(FrappeTestCase):
setup_salary_structure(employee, company_doc) setup_salary_structure(employee, company_doc)
dates = get_start_end_dates("Monthly", nowdate()) dates = get_start_end_dates("Monthly", nowdate())
payroll_entry = get_payroll_entry_data( payroll_entry = get_payroll_entry(
start_date=dates.start_date, start_date=dates.start_date,
end_date=dates.end_date, end_date=dates.end_date,
payable_account=company_doc.default_payroll_payable_account, payable_account=company_doc.default_payroll_payable_account,
@@ -384,7 +393,7 @@ class TestPayrollEntry(FrappeTestCase):
self.assertEqual(payroll_entry.status, "Cancelled") self.assertEqual(payroll_entry.status, "Cancelled")
def get_payroll_entry_data(**args): def get_payroll_entry(**args):
args = frappe._dict(args) args = frappe._dict(args)
payroll_entry = frappe.new_doc("Payroll Entry") payroll_entry = frappe.new_doc("Payroll Entry")
@@ -407,13 +416,16 @@ def get_payroll_entry_data(**args):
payroll_entry.payment_account = args.payment_account payroll_entry.payment_account = args.payment_account
payroll_entry.fill_employee_details() payroll_entry.fill_employee_details()
payroll_entry.save() payroll_entry.insert()
# Commit so that the first salary slip creation failure does not rollback the Payroll Entry insert.
frappe.db.commit() # nosemgrep
return payroll_entry return payroll_entry
def make_payroll_entry(**args): def make_payroll_entry(**args):
payroll_entry = get_payroll_entry_data(**args) payroll_entry = get_payroll_entry(**args)
payroll_entry.submit() payroll_entry.submit()
payroll_entry.submit_salary_slips() payroll_entry.submit_salary_slips()
if payroll_entry.get_sal_slip_list(ss_status=1): if payroll_entry.get_sal_slip_list(ss_status=1):