Functional Weave
Code in Python

payroll.holiday-pay-accrual@1.0.1

impl/python.py

3,055 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import Optional, Sequence

from .dates_add_days import parse_iso_date  ← from dates.add-days ^1.0.0 · built alongside by fune
from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune
from .payroll_holiday_pay_accrual_data import HOLIDAY_PAY_RULES, HOLIDAY_PAY_RULES_HISTORY, HolidayPayRule  ← this capability’s own data, compiled from data/holiday-pay-rules.json into the same file by fune build
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)