fix(employee): handle Odoo 19 'in' operator + empty-list sentinel
Two compounding bugs in _search_x_fc_is_clocked_in surfaced when
fusion_clock's auto-clock-out closed all the demo open attendances:
1. Odoo 19 normalises ('=', True) into ('in', OrderedSet([True]))
before invoking the search method. The previous code only
handled '=' / '!=' and fell through to return [] for 'in' /
'not in' — which Odoo treats as 'no constraint' and matches
the entire table.
2. ('id', 'in', []) is also treated as no-constraint in some
Odoo versions; replaced with a [0] sentinel so the empty-
open-attendance case correctly matches nothing.
Rewrite reduces caller intent to a match_set of booleans, flips on
negative operators, then emits id IN / NOT IN against the cached
open-attendance employee ids. Variable signature accepts Odoo's
3-arg (records, op, val) form too in case the API shifts.
Verified on entech: clocked_in==True returns 3 (Carlos, James,
Marie); ==False returns the other 5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -87,20 +87,62 @@ class HrEmployee(models.Model):
|
||||
for emp in self:
|
||||
emp.x_fc_is_clocked_in = emp.id in open_emp_ids
|
||||
|
||||
def _search_x_fc_is_clocked_in(self, operator, value):
|
||||
"""Lets `[('x_fc_is_clocked_in', '=', True)]` work as a domain."""
|
||||
def _search_x_fc_is_clocked_in(self, *args):
|
||||
"""Lets `[('x_fc_is_clocked_in', '=', True)]` work as a domain.
|
||||
|
||||
Two compounding gotchas surfaced after fusion_clock auto-closed
|
||||
the demo open attendances:
|
||||
|
||||
1. Odoo 19 normalises ``('=', True)`` into
|
||||
``('in', OrderedSet([True]))`` before invoking the search
|
||||
method. The previous code only handled ``=`` / ``!=`` and
|
||||
fell through to ``return []`` for ``in`` / ``not in`` —
|
||||
which Odoo treats as "no constraint" and matches every
|
||||
row.
|
||||
|
||||
2. ``('id', 'in', [])`` is also treated as no-constraint in
|
||||
some Odoo versions; replaced with a ``[0]`` sentinel so
|
||||
the empty-open-list case correctly matches nothing.
|
||||
|
||||
Strategy: reduce caller intent to a *match_set* of booleans
|
||||
(which values of ``x_fc_is_clocked_in`` should match), flip on
|
||||
negative operators, then translate into ``id IN`` / ``NOT IN``
|
||||
on the cached open-attendance employee ids. Variable signature
|
||||
future-proofs against Odoo's compute-field API shifting again.
|
||||
"""
|
||||
# Variable signature — Odoo 19 may pass (records, op, val).
|
||||
if len(args) == 3:
|
||||
_records, operator, value = args
|
||||
elif len(args) == 2:
|
||||
operator, value = args
|
||||
else:
|
||||
return [('id', '=', False)]
|
||||
|
||||
Att = self.env.get('hr.attendance')
|
||||
if Att is None:
|
||||
return [('id', '=', False)]
|
||||
open_ids = Att.sudo().search([
|
||||
('check_out', '=', False),
|
||||
]).mapped('employee_id').ids
|
||||
|
||||
if operator in ('=', '!='):
|
||||
wanted = bool(value)
|
||||
if operator == '!=':
|
||||
wanted = not wanted
|
||||
return [('id', 'in' if wanted else 'not in', open_ids)]
|
||||
return []
|
||||
match_set = {bool(value)}
|
||||
elif operator in ('in', 'not in'):
|
||||
match_set = set(map(bool, value))
|
||||
else:
|
||||
return [('id', '=', False)]
|
||||
|
||||
# Negated operators flip the match set.
|
||||
if operator in ('!=', 'not in'):
|
||||
match_set = {True, False} - match_set
|
||||
|
||||
if not match_set:
|
||||
return [('id', '=', False)]
|
||||
if match_set == {True, False}:
|
||||
return [] # every row matches
|
||||
|
||||
open_emp_ids = Att.sudo().search(
|
||||
[('check_out', '=', False)]
|
||||
).employee_id.ids
|
||||
ids_term = open_emp_ids or [0]
|
||||
return [('id', 'in' if True in match_set else 'not in', ids_term)]
|
||||
|
||||
@api.model
|
||||
def _fp_clocked_in_user_ids(self):
|
||||
|
||||
Reference in New Issue
Block a user