mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-19 09:28:46 +00:00
test(timesheet): characterize sales-invoice link/unlink and submit guard
Pin TimesheetBillingService behaviour before refactor: billing a timesheet into a Sales Invoice links the timesheet detail and marks it Billed on submit, and clears the link / reverts status on cancel; an unsubmitted timesheet cannot be invoiced.
This commit is contained in:
@@ -126,6 +126,80 @@ class TestTimesheet(ERPNextTestSuite):
|
||||
self.assertEqual(ts.per_billed, 100)
|
||||
self.assertEqual(ts.time_logs[0].sales_invoice, sales_invoice.name)
|
||||
|
||||
def _bill_timesheet_into_invoice(self, emp):
|
||||
"""Submit a billable timesheet into a Sales Invoice; return (timesheet, invoice)."""
|
||||
timesheet = make_timesheet(emp, simulate=True, is_billable=1)
|
||||
sales_invoice = make_sales_invoice(timesheet.name, "_Test Item", "_Test Customer", currency="INR")
|
||||
sales_invoice.due_date = nowdate()
|
||||
sales_invoice.submit()
|
||||
timesheet.reload()
|
||||
# Submitting links the timesheet detail to the invoice and marks it billed
|
||||
self.assertEqual(timesheet.time_logs[0].sales_invoice, sales_invoice.name)
|
||||
self.assertEqual(timesheet.status, "Billed")
|
||||
return timesheet, sales_invoice
|
||||
|
||||
def test_timesheet_billing_link_lifecycle(self):
|
||||
emp = make_employee("test_employee_6@salary.com", company="_Test Company")
|
||||
|
||||
with self.subTest("link released on cancel"):
|
||||
timesheet, sales_invoice = self._bill_timesheet_into_invoice(emp)
|
||||
sales_invoice.reload()
|
||||
sales_invoice.cancel()
|
||||
timesheet.reload()
|
||||
self.assertFalse(timesheet.time_logs[0].sales_invoice)
|
||||
self.assertNotEqual(timesheet.status, "Billed")
|
||||
|
||||
with self.subTest("link released on sales return"):
|
||||
timesheet, sales_invoice = self._bill_timesheet_into_invoice(emp)
|
||||
sales_return = make_sales_return(sales_invoice.name)
|
||||
sales_return.insert()
|
||||
sales_return.submit()
|
||||
timesheet.reload()
|
||||
self.assertFalse(timesheet.time_logs[0].sales_invoice)
|
||||
|
||||
def test_timesheet_billing_validations(self):
|
||||
emp = make_employee("test_employee_6@salary.com", company="_Test Company")
|
||||
|
||||
with self.subTest("unsubmitted timesheet is rejected"):
|
||||
draft = make_timesheet(emp, simulate=True, is_billable=1, do_not_submit=True)
|
||||
sales_invoice = self._invoice_with_timesheet_row(draft.name, draft.time_logs[0].name)
|
||||
self.assertRaises(frappe.ValidationError, sales_invoice.save)
|
||||
|
||||
with self.subTest("already invoiced detail is rejected"):
|
||||
timesheet, _ = self._bill_timesheet_into_invoice(emp)
|
||||
sales_invoice = self._invoice_with_timesheet_row(timesheet.name, timesheet.time_logs[0].name)
|
||||
self.assertRaises(frappe.ValidationError, sales_invoice.save)
|
||||
|
||||
@ERPNextTestSuite.change_settings("Projects Settings", {"fetch_timesheet_in_sales_invoice": 1})
|
||||
def test_timesheet_billing_data_population(self):
|
||||
emp = make_employee("test_employee_6@salary.com", company="_Test Company")
|
||||
|
||||
with self.subTest("blank hours/amount are back-filled from the timesheet"):
|
||||
timesheet = make_timesheet(emp, simulate=True, is_billable=1)
|
||||
sales_invoice = self._invoice_with_timesheet_row(
|
||||
timesheet.name, timesheet.time_logs[0].name, with_amounts=False
|
||||
)
|
||||
sales_invoice.save()
|
||||
self.assertEqual(sales_invoice.timesheets[0].billing_hours, 2)
|
||||
self.assertEqual(sales_invoice.timesheets[0].billing_amount, 100)
|
||||
|
||||
with self.subTest("project invoice auto-fetches the project's timesheets"):
|
||||
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
make_timesheet(emp, simulate=True, is_billable=1, project=project, company="_Test Company")
|
||||
sales_invoice = create_sales_invoice(do_not_save=True)
|
||||
sales_invoice.project = project
|
||||
sales_invoice.set("timesheets", [])
|
||||
sales_invoice.save()
|
||||
self.assertTrue(sales_invoice.timesheets)
|
||||
|
||||
def _invoice_with_timesheet_row(self, time_sheet, timesheet_detail, with_amounts=True):
|
||||
sales_invoice = create_sales_invoice(do_not_save=True)
|
||||
row = {"time_sheet": time_sheet, "timesheet_detail": timesheet_detail}
|
||||
if with_amounts:
|
||||
row.update({"billing_hours": 2, "billing_amount": 100})
|
||||
sales_invoice.append("timesheets", row)
|
||||
return sales_invoice
|
||||
|
||||
def test_timesheet_time_overlap(self):
|
||||
emp = make_employee("test_employee_6@salary.com", company="_Test Company")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user