# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) """Pay-period date math shared by reports, attendance filters and the period picker. Pure functions (no ORM) so they unit-test trivially and never drift between callers.""" from datetime import date, timedelta def period_length_days(frequency): """Fixed window length for grid frequencies; None for calendar-based ones.""" return {'weekly': 7, 'biweekly': 14}.get(frequency) def compute_pay_period(frequency, anchor_str, reference_date): """Return (start_date, end_date) for the period containing reference_date. ``anchor_str`` is a 'YYYY-MM-DD' string or falsy (falls back to first-of-month). Mirrors the original fusion.clock.report._calculate_current_period logic, including floor division so dates before the anchor resolve to the correct earlier period. """ if anchor_str: try: # Truncate to 'YYYY-MM-DD' first, matching Odoo's fields.Date.from_string # (Date.to_date), so a stored datetime-ish anchor like # "2026-05-04 00:00:00" still parses instead of silently falling back. anchor = date.fromisoformat(anchor_str[:10]) except (ValueError, TypeError): anchor = reference_date.replace(day=1) else: anchor = reference_date.replace(day=1) if frequency == 'weekly': period_num = (reference_date - anchor).days // 7 start = anchor + timedelta(days=period_num * 7) end = start + timedelta(days=6) elif frequency == 'semi_monthly': if reference_date.day <= 15: start = reference_date.replace(day=1) end = reference_date.replace(day=15) else: start = reference_date.replace(day=16) next_month = reference_date.replace(day=28) + timedelta(days=4) end = next_month - timedelta(days=next_month.day) elif frequency == 'monthly': start = reference_date.replace(day=1) next_month = reference_date.replace(day=28) + timedelta(days=4) end = next_month - timedelta(days=next_month.day) else: # 'biweekly' and default period_num = (reference_date - anchor).days // 14 start = anchor + timedelta(days=period_num * 14) end = start + timedelta(days=13) return start, end def current_prev_next(frequency, anchor_str, today): """Return {'current','previous','next'} (start,end) windows. Previous/next are derived by stepping the reference date one day outside the current window, which works for grid AND calendar frequencies.""" cur = compute_pay_period(frequency, anchor_str, today) prev = compute_pay_period(frequency, anchor_str, cur[0] - timedelta(days=1)) nxt = compute_pay_period(frequency, anchor_str, cur[1] + timedelta(days=1)) return {'current': cur, 'previous': prev, 'next': nxt}