addons-cm/hr_timesheet_sheet_delete_cascade/wizards/delete_sheet_wizard.py

152 lines
5.8 KiB
Python

# Copyright 2026 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from markupsafe import Markup
from odoo import _, api, fields, models
from ..models.res_company import LEAVE_POLICY_SELECTION
class HrTimesheetSheetDeleteWizard(models.TransientModel):
_name = "hr_timesheet.sheet.delete.wizard"
_description = "Delete Timesheet Sheets"
sheet_ids = fields.Many2many(
comodel_name="hr_timesheet.sheet",
string="Timesheet Sheets",
)
sheet_count = fields.Integer(
string="Sheets",
compute="_compute_delete_cascade_data",
)
timesheet_count = fields.Integer(
string="Timesheet Entries",
compute="_compute_delete_cascade_data",
)
timesheet_hours = fields.Float(
string="Hours",
compute="_compute_delete_cascade_data",
)
invoiced_count = fields.Integer(
string="Invoiced Entries",
compute="_compute_delete_cascade_data",
)
leave_policy = fields.Selection(
selection=LEAVE_POLICY_SELECTION,
string="Time Off Policy",
compute="_compute_delete_cascade_data",
)
leave_ids = fields.Many2many(
comodel_name="hr.leave",
relation="hr_timesheet_sheet_delete_wizard_leave_rel",
string="Time Off To Cancel",
compute="_compute_delete_cascade_data",
)
leave_count = fields.Integer(
string="Time Off Entries",
compute="_compute_delete_cascade_data",
)
leave_days = fields.Float(
string="Days Restored",
compute="_compute_delete_cascade_data",
)
partial_leave_ids = fields.Many2many(
comodel_name="hr.leave",
relation="hr_timesheet_sheet_delete_wizard_partial_leave_rel",
string="Time Off Beyond The Sheet Period",
compute="_compute_delete_cascade_data",
)
blocking_reason = fields.Text(
string="Blocking Reason",
compute="_compute_delete_cascade_data",
)
summary_html = fields.Html(
string="Summary",
compute="_compute_delete_cascade_data",
sanitize=False,
)
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
if self.env.context.get("active_model") == "hr_timesheet.sheet":
res["sheet_ids"] = [(6, 0, self.env.context.get("active_ids") or [])]
return res
@api.depends("sheet_ids")
def _compute_delete_cascade_data(self):
for wizard in self:
sheets = wizard.sheet_ids
data = sheets._delete_cascade_prepare()
wizard.sheet_count = data["sheet_count"]
wizard.timesheet_count = data["timesheet_count"]
wizard.timesheet_hours = data["timesheet_hours"]
wizard.invoiced_count = data["invoiced_count"]
wizard.leave_policy = data["leave_policy"]
wizard.leave_count = data["leave_count"]
wizard.leave_days = data["leave_days"]
cancel_sheets = sheets._delete_cascade_cancel_sheets()
wizard.leave_ids = cancel_sheets._sheet_leaves_to_cancel().ids
wizard.partial_leave_ids = cancel_sheets._sheet_leaves_beyond_period().ids
wizard.blocking_reason = "\n".join(data["blocking"]) or False
wizard.summary_html = wizard._build_summary_html(data)
def _build_summary_html(self, data):
self.ensure_one()
if not data["sheet_count"]:
return Markup("<p>%s</p>") % _("No timesheet sheet was selected.")
parts = []
for section in data["sections"]:
parts.append(Markup("<h4>%s</h4>") % section["title"])
parts.append(
Markup("<ul>%s</ul>")
% Markup("").join(
Markup("<li>%s</li>") % line for line in section["lines"]
)
)
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 next_action