# Copyright 2026 Criptomart # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). import logging from odoo import api, models from odoo.osv import expression _logger = logging.getLogger(__name__) class HrTimesheetSheet(models.Model): _inherit = "hr_timesheet.sheet" @api.model def _unfreeze_domain(self): """Hook for extensions: which sheets are considered frozen.""" return [("state", "in", ("confirm", "done"))] @api.model def _unfreeze_sheets(self, sheets=None): """Send frozen sheets back to draft. Returns the number of sheets affected. Without ``sheets``, every frozen sheet the current user can see is released, which is the historical behaviour of this hook. With ``sheets``, only those of them that are still frozen are released: the wizard passes the recordset it showed the user, so the confirmation message counts what the warning announced and not what the database happens to hold by the time the button is pressed. The update is issued in SQL on purpose: going through ``write()`` would trigger the sheet recomputation and one tracking message per sheet, on a volume that is measured in thousands of records. """ domain = self._unfreeze_domain() if sheets is not None: domain = expression.AND([domain, [("id", "in", sheets.ids)]]) sheets = self.search(domain) if not sheets: _logger.info("No frozen timesheet sheets to unfreeze.") return 0 sheets.check_access_rights("write") sheets.check_access_rule("write") self.env.flush_all() self.env.cr.execute( """ UPDATE hr_timesheet_sheet SET state = 'draft', reviewer_id = NULL, write_uid = %s, write_date = (now() at time zone 'UTC') WHERE id IN %s """, (self.env.uid, tuple(sheets.ids)), ) count = self.env.cr.rowcount self.env.invalidate_all(flush=False) _logger.info("Unfroze %s timesheet sheet(s).", count) return count