# Copyright 2026 Criptomart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from odoo import _, models from odoo.exceptions import UserError from odoo.tools.misc import format_date # 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. These # sentinels are compared by identity and cannot be produced by a deserializer, # so they only ever reach the consumer from inside this process. _SHEET_LINK_WRITE = object() _SHEET_RECOMPUTE = object() class AccountAnalyticLine(models.Model): _inherit = "account.analytic.line" def _sheet_neutral_fields(self): """Hook for extensions""" return {"sheet_id"} def _sheet_link_write_token(self): """Hook for extensions""" return _SHEET_LINK_WRITE def _sheet_recompute_token(self): """Hook for extensions""" return _SHEET_RECOMPUTE def _in_sheet_link_write(self): return ( self.env.context.get("sheet_link_write") is self._sheet_link_write_token() ) def _detach_leave_lines_on_unlink(self): """Hook for extensions""" return self.env.context.get("sheet_recompute") is self._sheet_recompute_token() def _check_can_update_timesheet(self): if self._in_sheet_link_write(): return True if not self.env.su and self.filtered("holiday_id"): return False return super()._check_can_update_timesheet() def write(self, vals): if vals and self.filtered("holiday_id"): if set(vals) <= self._sheet_neutral_fields(): linking = self.with_context( sheet_link_write=self._sheet_link_write_token() ) return super(AccountAnalyticLine, linking).write(vals) if not self._check_can_update_timesheet(): line = self.filtered("holiday_id")[:1] raise UserError( _( "Time off hours are managed from the Time Off application" " and cannot be changed from a timesheet sheet.\n" "Sheet: %(sheet)s\n" "Day: %(day)s", sheet=line.sheet_id.complete_name or "-", day=format_date(self.env, line.date), ) ) return super().write(vals) def unlink(self): leave_lines = self.filtered("holiday_id") if leave_lines and leave_lines._detach_leave_lines_on_unlink(): leave_lines.write({"sheet_id": False}) return super(AccountAnalyticLine, self - leave_lines).unlink() return super().unlink()