feat(fusion_clock): bi-weekly attendance filter — pay-period filters + picker
Reuse the existing Pay Period setting (Frequency + Anchor) as the single source of truth via a shared pure helper (models/pay_period.py); fusion.clock.report delegates to it. Add Current/Previous/Next Pay Period filters to the attendance search view (search-method computed booleans on hr.attendance), a Bi-Weekly Period picker wizard (pick start -> auto +2 weeks, editable; Apply opens the filtered list) reachable from an Attendance menu item and a dashboard tile. Window follows the configured frequency; TZ-correct via local-day boundaries. Bump 3.14.4 -> 3.15.0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ from datetime import datetime, timedelta
|
||||
from odoo import models, fields, api
|
||||
from odoo.tools import float_round
|
||||
from .tz_utils import get_local_today, get_local_day_boundaries
|
||||
from .pay_period import current_prev_next
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -207,6 +208,56 @@ class HrAttendance(models.Model):
|
||||
help="Selfie captured at clock-in for verification.",
|
||||
)
|
||||
|
||||
# Pay-period filters (display-only flags; the filtering is done by the
|
||||
# search methods, which compute the window from the configured frequency +
|
||||
# anchor — see models/pay_period.py).
|
||||
x_fclk_in_current_period = fields.Boolean(
|
||||
string='In Current Pay Period',
|
||||
compute='_compute_fclk_period_flags', search='_search_fclk_in_current_period')
|
||||
x_fclk_in_previous_period = fields.Boolean(
|
||||
string='In Previous Pay Period',
|
||||
compute='_compute_fclk_period_flags', search='_search_fclk_in_previous_period')
|
||||
x_fclk_in_next_period = fields.Boolean(
|
||||
string='In Next Pay Period',
|
||||
compute='_compute_fclk_period_flags', search='_search_fclk_in_next_period')
|
||||
|
||||
def _compute_fclk_period_flags(self):
|
||||
# Display-only; filtering happens entirely in the search methods.
|
||||
for att in self:
|
||||
att.x_fclk_in_current_period = False
|
||||
att.x_fclk_in_previous_period = False
|
||||
att.x_fclk_in_next_period = False
|
||||
|
||||
def _fclk_period_domain(self, which):
|
||||
"""check_in domain for the named pay-period window ('current' /
|
||||
'previous' / 'next'), computed from the configured frequency + anchor."""
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
frequency = ICP.get_param('fusion_clock.pay_period_type', 'biweekly')
|
||||
anchor = ICP.get_param('fusion_clock.pay_period_start', '')
|
||||
start, end = current_prev_next(frequency, anchor, get_local_today(self.env))[which]
|
||||
start_utc, _dummy = get_local_day_boundaries(self.env, start)
|
||||
_dummy2, end_excl_utc = get_local_day_boundaries(self.env, end)
|
||||
return ['&',
|
||||
('check_in', '>=', fields.Datetime.to_string(start_utc)),
|
||||
('check_in', '<', fields.Datetime.to_string(end_excl_utc))]
|
||||
|
||||
def _fclk_period_search(self, which, operator, value):
|
||||
"""Resolve the filter to a check_in domain. The shipped filters emit
|
||||
('=', True); handle '='/'!='+bool generally so the public field never
|
||||
silently returns the wrong set under negation."""
|
||||
domain = self._fclk_period_domain(which)
|
||||
positive = (operator == '=') == bool(value)
|
||||
return domain if positive else ['!'] + domain
|
||||
|
||||
def _search_fclk_in_current_period(self, operator, value):
|
||||
return self._fclk_period_search('current', operator, value)
|
||||
|
||||
def _search_fclk_in_previous_period(self, operator, value):
|
||||
return self._fclk_period_search('previous', operator, value)
|
||||
|
||||
def _search_fclk_in_next_period(self, operator, value):
|
||||
return self._fclk_period_search('next', operator, value)
|
||||
|
||||
@api.depends('worked_hours', 'x_fclk_break_minutes')
|
||||
def _compute_net_hours(self):
|
||||
for att in self:
|
||||
|
||||
Reference in New Issue
Block a user