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:
gsinghpal
2026-05-31 11:20:06 -04:00
parent e230e42d81
commit 3f78f652e7
15 changed files with 376 additions and 48 deletions

View File

@@ -455,53 +455,13 @@ class FusionClockReport(models.Model):
@api.model
def _calculate_current_period(self, schedule_type, period_start_str, reference_date):
"""Calculate the period start/end dates based on schedule type."""
from dateutil.relativedelta import relativedelta
import datetime
"""Calculate the period start/end dates based on schedule type.
if period_start_str:
try:
anchor = fields.Date.from_string(period_start_str)
except Exception:
anchor = reference_date.replace(day=1)
else:
anchor = reference_date.replace(day=1)
if schedule_type == 'weekly':
days_diff = (reference_date - anchor).days
period_num = days_diff // 7
period_start = anchor + timedelta(days=period_num * 7)
period_end = period_start + timedelta(days=6)
elif schedule_type == 'biweekly':
days_diff = (reference_date - anchor).days
period_num = days_diff // 14
period_start = anchor + timedelta(days=period_num * 14)
period_end = period_start + timedelta(days=13)
elif schedule_type == 'semi_monthly':
if reference_date.day <= 15:
period_start = reference_date.replace(day=1)
period_end = reference_date.replace(day=15)
else:
period_start = reference_date.replace(day=16)
# Last day of month
next_month = reference_date.replace(day=28) + timedelta(days=4)
period_end = next_month - timedelta(days=next_month.day)
elif schedule_type == 'monthly':
period_start = reference_date.replace(day=1)
next_month = reference_date.replace(day=28) + timedelta(days=4)
period_end = next_month - timedelta(days=next_month.day)
else:
# Default biweekly
days_diff = (reference_date - anchor).days
period_num = days_diff // 14
period_start = anchor + timedelta(days=period_num * 14)
period_end = period_start + timedelta(days=13)
return period_start, period_end
Delegates to the shared pure helper so reports, the attendance period
filters and the Bi-Weekly Period picker all use one implementation.
"""
from .pay_period import compute_pay_period
return compute_pay_period(schedule_type, period_start_str, reference_date)
@api.model
def action_generate_historical_reports(self):