62 lines
2.6 KiB
Python
62 lines
2.6 KiB
Python
# Copyright 2026 Criptomart
|
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
|
|
|
|
from odoo import api, models
|
|
|
|
# The context of an RPC call is controlled by the client: odoo.api.call_kw
|
|
# rebuilds the environment with whatever dictionary arrives in the request, so
|
|
# a flag carrying a JSON value could be forged by any authenticated user and
|
|
# the cascade would delete a sheet without the confirmation wizard. This
|
|
# sentinel is compared by identity and cannot be produced by a deserializer,
|
|
# so it only ever reaches the consumer from inside this process.
|
|
_SHEET_DELETE_CASCADE = object()
|
|
|
|
|
|
class AccountAnalyticLine(models.Model):
|
|
_inherit = "account.analytic.line"
|
|
|
|
def _sheet_delete_cascade_token(self):
|
|
"""Hook for extensions"""
|
|
return _SHEET_DELETE_CASCADE
|
|
|
|
def _in_sheet_delete_cascade(self):
|
|
return (
|
|
self.env.context.get("sheet_delete_cascade")
|
|
is self._sheet_delete_cascade_token()
|
|
)
|
|
|
|
def _sheet_leave_policy(self):
|
|
"""Hook for extensions"""
|
|
companies = self.sheet_id.company_id or self.company_id or self.env.company
|
|
policies = companies.mapped("timesheet_sheet_delete_leave_policy")
|
|
return "cancel" if "cancel" in policies else "detach"
|
|
|
|
def _in_deliberate_delete_cascade(self):
|
|
"""Hook for extensions
|
|
|
|
The sentinel alone is not enough: the elevated privileges that only the
|
|
cancellation branch of the sheet grants are required as a second
|
|
barrier, so an in-process caller that merely carries the cascade
|
|
context cannot delete a time off entry on its way.
|
|
"""
|
|
return self._in_sheet_delete_cascade() and self.env.su
|
|
|
|
def _detach_leave_lines_on_unlink(self):
|
|
if self._in_deliberate_delete_cascade():
|
|
return self._sheet_leave_policy() != "cancel"
|
|
return super()._detach_leave_lines_on_unlink()
|
|
|
|
@api.ondelete(at_uninstall=False)
|
|
def _unlink_except_linked_leave(self):
|
|
# Defence in depth. The cancellation branch of the cascade never gets
|
|
# here with a time off entry: project_timesheet_holidays clears
|
|
# holiday_id before unlinking the entries of a refused request
|
|
# (models/hr_holidays.py:127-134), so by then they are ordinary lines.
|
|
# This keeps the contract true for any other caller that unlinks a time
|
|
# off entry from inside a deliberate cascade.
|
|
if (
|
|
self._in_deliberate_delete_cascade()
|
|
and self._sheet_leave_policy() == "cancel"
|
|
):
|
|
return
|
|
return super()._unlink_except_linked_leave()
|