from typing import Optional, Sequence from .dates_add_days import parse_iso_date from .math_round_div import round_div from .money_amount import Money, money from .payroll_holiday_pay_accrual_data import HOLIDAY_PAY_RULES, HOLIDAY_PAY_RULES_HISTORY, HolidayPayRule from .payroll_holiday_pay_accrual_types import HolidayPay, HolidayPayMethod def _rule_for(method: str, on_date: str) -> HolidayPayRule: best: Optional[HolidayPayRule] = None for rule in HOLIDAY_PAY_RULES: if rule.method != method or on_date < rule.valid_from: continue if rule.valid_to is not None and on_date > rule.valid_to: continue if best is None or rule.valid_from > best.valid_from: best = rule if best is None: if HOLIDAY_PAY_RULES_HISTORY != "full": raise ValueError( "no %s holiday pay rule for %s: this build was installed with history=%s. " "Reinstall with history=full for older dates." % (method, on_date, HOLIDAY_PAY_RULES_HISTORY) ) raise ValueError("no %s holiday pay rule for %s" % (method, on_date)) return best def holiday_pay(method: HolidayPayMethod, period_pay: Money, weekly_pay: Sequence[Money], on_date: str) -> HolidayPay: """Holiday pay under the Working Time Regulations 1998, by one of two methods. Rolled-up: a 12.07% uplift on pay for work done, paid with it, allowed only for irregular-hours and part-year workers in leave years from 1 April 2024. Average week: a week's pay as the average over the most recent 52 weeks in which the worker was paid, skipping unpaid weeks but never looking back more than 104 weeks. """ parse_iso_date(on_date) if method == "rolled-up": rule = _rule_for(method, on_date) if period_pay.minor < 0: raise ValueError("periodPay must not be negative") return HolidayPay(amount=money(round_div(period_pay.minor * rule.accrual_basis_points, 10000, "half-up"), period_pay.currency), weeks_counted=0) if method != "average-week": raise ValueError('unknown holiday pay method "%s"' % (method,)) rule = _rule_for(method, on_date) total = 0 counted = 0 currency: Optional[str] = None window = min(len(weekly_pay), rule.lookback_weeks) for i in range(window): if counted >= rule.reference_weeks: break week = weekly_pay[i] if currency is None: currency = week.currency elif week.currency != currency: raise ValueError("currency mismatch: %s and %s" % (currency, week.currency)) if week.minor < 0: raise ValueError("weekly pay must not be negative") if week.minor == 0: continue total += week.minor counted += 1 if counted == 0 or currency is None: raise ValueError("no week with pay in the last %s weeks" % (rule.lookback_weeks,)) return HolidayPay(amount=money(round_div(total, counted, "half-up"), currency), weeks_counted=counted)