hr_timesheet_sheet* fixes: back to list after remove a timesheet. bulk unfreeze confirmed and pending sheets
This commit is contained in:
parent
8b0d0bb6cc
commit
0323b6672e
11 changed files with 354 additions and 34 deletions
|
|
@ -304,5 +304,9 @@ class Sheet(models.Model):
|
|||
active_model="hr_timesheet.sheet",
|
||||
active_ids=self.ids,
|
||||
active_id=self.ids[0] if self.ids else False,
|
||||
# The button lives in the header of the sheet form: once the
|
||||
# sheet is gone that form cannot be restored, and the wizard
|
||||
# has to send the user somewhere else.
|
||||
sheet_delete_cascade_from_form=True,
|
||||
),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@ section that depends on the configured policy. Nothing is deleted until
|
|||
**Delete permanently** is pressed, and the button is hidden when something
|
||||
blocks the deletion.
|
||||
|
||||
Deleting from the form leaves the user on the list of timesheet sheets, since
|
||||
the form he was on shows a sheet that no longer exists: *My Timesheet Sheets*,
|
||||
or *All Timesheet Sheets* when a timesheet administrator deletes the sheet of
|
||||
another employee. The deleted sheet is not kept in the breadcrumb. Deleting
|
||||
several sheets from the **Action** menu of a list leaves that list where it
|
||||
was, with its filters and its grouping.
|
||||
|
||||
A plain ``sheet.unlink()`` from a script or a server action raises a
|
||||
``UserError`` that points to the button, so the confirmation step cannot be
|
||||
skipped by accident. Code that really means to delete does
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class TestHrTimesheetSheetDeleteCascade(TransactionCase):
|
|||
cls.employee = cls._new_employee("Cascade Employee", cls.user)
|
||||
cls.employee_other = cls._new_employee("Other Employee", cls.user_other)
|
||||
cls.employee_span = cls._new_employee("Span Employee", cls.user_span)
|
||||
cls._new_employee("Cascade Manager", cls.user_manager)
|
||||
cls.employee_manager = cls._new_employee("Cascade Manager", cls.user_manager)
|
||||
|
||||
cls.project = cls.env["project.project"].create(
|
||||
{
|
||||
|
|
@ -215,6 +215,16 @@ class TestHrTimesheetSheetDeleteCascade(TransactionCase):
|
|||
.create({})
|
||||
)
|
||||
|
||||
def _wizard_from_form(self, sheets, user=None):
|
||||
user = user or self.user
|
||||
action = sheets.with_user(user).action_delete_sheet()
|
||||
return (
|
||||
self.env["hr_timesheet.sheet.delete.wizard"]
|
||||
.with_user(user)
|
||||
.with_context(**action["context"])
|
||||
.create({})
|
||||
)
|
||||
|
||||
def _leave_balance(self, employee):
|
||||
leave_type = self.leave_type.with_context(employee_id=employee.id)
|
||||
leave_type.invalidate_recordset()
|
||||
|
|
@ -357,6 +367,63 @@ class TestHrTimesheetSheetDeleteCascade(TransactionCase):
|
|||
self.assertEqual(action["res_model"], "hr_timesheet.sheet.delete.wizard")
|
||||
self.assertEqual(action["context"]["active_ids"], self.sheet.ids)
|
||||
|
||||
# Navigation once the sheet the user was looking at is gone
|
||||
|
||||
def test_delete_from_form_returns_to_the_sheet_list(self):
|
||||
wizard = self._wizard_from_form(self.sheet)
|
||||
action = wizard.action_confirm_delete()
|
||||
self.assertEqual(action["type"], "ir.actions.act_window")
|
||||
self.assertEqual(action["res_model"], "hr_timesheet.sheet")
|
||||
self.assertEqual(action["target"], "main")
|
||||
self.assertIn("tree", action["view_mode"])
|
||||
self.assertFalse(self.sheet.exists())
|
||||
|
||||
def test_delete_from_form_returns_own_sheets_to_an_employee(self):
|
||||
action = self._wizard_from_form(self.sheet).action_confirm_delete()
|
||||
self.assertEqual(
|
||||
action["xml_id"],
|
||||
"hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets",
|
||||
)
|
||||
|
||||
def test_delete_from_form_returns_own_sheets_to_a_manager(self):
|
||||
manager_sheet = self._new_sheet(self.employee_manager)
|
||||
action = self._wizard_from_form(
|
||||
manager_sheet, user=self.user_manager
|
||||
).action_confirm_delete()
|
||||
self.assertEqual(
|
||||
action["xml_id"],
|
||||
"hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets",
|
||||
)
|
||||
|
||||
def test_delete_of_another_employee_sheet_returns_all_sheets(self):
|
||||
action = self._wizard_from_form(
|
||||
self.sheet_other, user=self.user_manager
|
||||
).action_confirm_delete()
|
||||
self.assertEqual(
|
||||
action["xml_id"],
|
||||
"hr_timesheet_sheet.act_hr_timesheet_sheet_all_timesheets",
|
||||
)
|
||||
self.assertFalse(self.sheet_other.exists())
|
||||
|
||||
def test_delete_from_list_stays_on_the_list(self):
|
||||
action = self._wizard(self.sheet).action_confirm_delete()
|
||||
self.assertEqual(action, {"type": "ir.actions.act_window_close"})
|
||||
self.assertFalse(self.sheet.exists())
|
||||
|
||||
def test_wizard_is_not_bound_to_the_sheet_form(self):
|
||||
action = self.env.ref(
|
||||
"hr_timesheet_sheet_delete_cascade"
|
||||
".action_hr_timesheet_sheet_delete_wizard"
|
||||
)
|
||||
self.assertNotIn("form", action.binding_view_types)
|
||||
|
||||
def test_blocked_deletion_navigates_nowhere(self):
|
||||
self.regular_line.timesheet_invoice_id = self._invoice()
|
||||
wizard = self._wizard_from_form(self.sheet)
|
||||
with self.assertRaises(UserError):
|
||||
wizard.action_confirm_delete()
|
||||
self.assertTrue(self.sheet.exists())
|
||||
|
||||
# RF-02
|
||||
|
||||
def test_cascade_deletes_timesheets(self):
|
||||
|
|
|
|||
|
|
@ -107,7 +107,46 @@ class HrTimesheetSheetDeleteWizard(models.TransientModel):
|
|||
)
|
||||
return Markup("").join(parts)
|
||||
|
||||
def _delete_cascade_list_action_xml_id(self):
|
||||
"""Hook for extensions
|
||||
|
||||
The list the user plausibly comes from. Deleting the sheet of another
|
||||
employee requires *Timesheets / Administrator*, and that sheet is only
|
||||
listed by *All Timesheet Sheets*; every other case is a user looking at
|
||||
his own sheets, and *My Timesheet Sheets* is the only list of sheets an
|
||||
employee outside that group has a menu for.
|
||||
"""
|
||||
self.ensure_one()
|
||||
own_sheets = self.sheet_ids.user_id == self.env.user
|
||||
is_manager = self.env.user.has_group("hr_timesheet.group_timesheet_manager")
|
||||
if own_sheets or not is_manager:
|
||||
return "hr_timesheet_sheet.act_hr_timesheet_sheet_my_timesheets"
|
||||
return "hr_timesheet_sheet.act_hr_timesheet_sheet_all_timesheets"
|
||||
|
||||
def _delete_cascade_next_action(self):
|
||||
"""Hook for extensions
|
||||
|
||||
Called before the deletion, while the sheets can still be read.
|
||||
|
||||
Opened from a list, the caller is left on that list: closing the dialog
|
||||
reloads it and keeps its filters, its grouping and its selection, and
|
||||
there is no dead record on screen. Opened from the form of a sheet that
|
||||
no longer exists, closing the dialog would leave that form on screen
|
||||
with the values of a deleted record: return the list of sheets instead,
|
||||
with ``target="main"`` so the deleted form is dropped from the
|
||||
breadcrumb rather than kept one step behind.
|
||||
"""
|
||||
self.ensure_one()
|
||||
if not self.env.context.get("sheet_delete_cascade_from_form"):
|
||||
return {"type": "ir.actions.act_window_close"}
|
||||
action = self.env["ir.actions.act_window"]._for_xml_id(
|
||||
self._delete_cascade_list_action_xml_id()
|
||||
)
|
||||
action["target"] = "main"
|
||||
return action
|
||||
|
||||
def action_confirm_delete(self):
|
||||
self.ensure_one()
|
||||
next_action = self._delete_cascade_next_action()
|
||||
self.sheet_ids._with_delete_cascade().unlink()
|
||||
return {"type": "ir.actions.act_window_close"}
|
||||
return next_action
|
||||
|
|
|
|||
|
|
@ -55,6 +55,11 @@
|
|||
name="binding_model_id"
|
||||
ref="hr_timesheet_sheet.model_hr_timesheet_sheet"
|
||||
/>
|
||||
<field name="binding_view_types">list,kanban,form</field>
|
||||
<!--
|
||||
Not bound to the form view: there the Delete Sheet button of the
|
||||
header is the only entry point, and it is the one that tells the
|
||||
wizard the sheet form has to be left behind once the sheet is gone.
|
||||
-->
|
||||
<field name="binding_view_types">list,kanban</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue