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:
|
for emp in self:
|
||||||
emp.x_fc_is_clocked_in = emp.id in open_emp_ids
|
emp.x_fc_is_clocked_in = emp.id in open_emp_ids
|
||||||
|
|
||||||
def _search_x_fc_is_clocked_in(self, operator, value):
|
def _search_x_fc_is_clocked_in(self, *args):
|
||||||
"""Lets `[('x_fc_is_clocked_in', '=', True)]` work as a domain."""
|
"""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')
|
Att = self.env.get('hr.attendance')
|
||||||
if Att is None:
|
if Att is None:
|
||||||
return [('id', '=', False)]
|
return [('id', '=', False)]
|
||||||
open_ids = Att.sudo().search([
|
|
||||||
('check_out', '=', False),
|
|
||||||
]).mapped('employee_id').ids
|
|
||||||
if operator in ('=', '!='):
|
if operator in ('=', '!='):
|
||||||
wanted = bool(value)
|
match_set = {bool(value)}
|
||||||
if operator == '!=':
|
elif operator in ('in', 'not in'):
|
||||||
wanted = not wanted
|
match_set = set(map(bool, value))
|
||||||
return [('id', 'in' if wanted else 'not in', open_ids)]
|
else:
|
||||||
return []
|
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
|
@api.model
|
||||||
def _fp_clocked_in_user_ids(self):
|
def _fp_clocked_in_user_ids(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user