add hr_timesheet_sheet_no_review, hr_timesheet_sheet_holidays, hr_timesheet_sheet_delete_cascade

This commit is contained in:
Luis 2026-09-22 10:58:36 +02:00
parent b07082f70d
commit 8b0d0bb6cc
53 changed files with 3953 additions and 0 deletions

View file

@ -0,0 +1,3 @@
* Criptomart
* Luis Díez <luis@criptomart.net>

View file

@ -0,0 +1,24 @@
``hr_timesheet_sheet`` works on every analytic line of a period at once, while
``project_timesheet_holidays`` protects the analytic lines it generates for validated
time off against any modification or deletion. Together they make any timesheet sheet
that spans a day with validated time off unmanageable: opening, recomputing, creating or
saving such a sheet raises a ``UserError`` and the employee cannot record their hours.
This bridge module makes time off lines inert with respect to the bulk operations of the
sheet while keeping them visible inside it, so that the week is shown complete with its
time off hours included. Two different code paths are involved and both are handled: the
bulk ``write`` that links or unlinks ``sheet_id``, and the ``Command.DELETE`` the web
client emits for the lines that leave the set cached by the onchange, which ends up in a
real ``unlink()``. In the second case the time off line is detached from the sheet
(``sheet_id = False``) instead of being deleted; the time off request and the employee
balance are never touched.
The protection itself is not relaxed: the hours, date, project, task, employee and
description of a time off line still cannot be changed from a timesheet sheet, the line
still cannot be deleted by hand and new timesheets still cannot be created on a time off
task. Only a write that touches nothing but the neutral link fields is let through.
The module also closes a pre-existing hole in ``hr_timesheet_sheet``, whose
``_check_can_update_timesheet()`` returned ``True`` for any recordset without
``sheet_id`` and therefore silently disabled the core protection for every time off line
not linked to a sheet.

View file

@ -0,0 +1,5 @@
* The 2D matrix widget cannot make a single row read-only, so the time off row looks
editable even though any attempt to change it is rejected. Making the cell read-only in
the client would require an extension of ``web_widget_x2many_2d_matrix``.
* Lines generated from public holidays (``global_leave_id``) are not covered: they are
not protected by ``project_timesheet_holidays`` and therefore never blocked the sheet.

View file

@ -0,0 +1,39 @@
Nothing to configure. Once installed, a timesheet sheet covering a week with validated
time off can be opened, recomputed, created and saved by the employee, and the time off
lines are shown in the *Summary* matrix and in the *Details* tab with their project, task
and hours, adding up to the total of the sheet.
Time off hours are still read-only from the sheet. Trying to change them raises an error
pointing the user to the *Time Off* application, naming the sheet and the day concerned.
To change them, cancel or modify the time off request in the *Time Off* application.
A time off line may end up detached from every sheet (``sheet_id = False``) when the
period or the employee of a sheet is changed in the form. Nothing is lost: the line, the
time off request and the balance are untouched, and the line is linked again as soon as a
sheet covering that day is recomputed.
For developers, the module publishes two hooks on ``account.analytic.line``:
* ``_sheet_neutral_fields()`` returns the set of fields whose write is considered a pure
sheet link operation (``{"sheet_id"}`` by default). Widening it requires reviewing the
protection of time off hours.
* ``_detach_leave_lines_on_unlink()`` returns whether an ``unlink()`` of time off lines
must detach them instead of deleting them.
Both behaviours are driven by the internal context flags ``sheet_link_write`` and
``sheet_recompute``. The context of an RPC call is controlled by the client, so those
flags do **not** carry a boolean: they carry a module-level sentinel object, obtained
from ``_sheet_link_write_token()`` / ``_sheet_recompute_token()`` and compared by
identity. A JSON deserializer cannot produce such an object, so the flags cannot be
forged from outside the process. Never replace a token with a value that survives
serialization, and never set either flag from a view, an action or a user context.
Three tests are mandatory regression tests for any change to those flags:
* ``test_no_context_leak_after_sheet_operation`` — the flag does not escape: right after
a sheet operation, in the same transaction, a direct write or unlink on a time off line
is blocked again.
* ``test_forged_sheet_link_write_flag_does_not_unlock_write`` and
``test_forged_sheet_recompute_flag_does_not_unlock_unlink`` — the flag cannot be
injected: a boolean sent through ``odoo.api.call_kw``, the entry point the web client
uses, unlocks nothing.