Functional Weave
Code in Python

payroll.statutory-maternity-pay@1.0.1

impl/python.py

3,440 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 List, Optional, Sequence, TypeVar

from .dates_add_days import add_days  ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_day_of_week import day_of_week  ← from dates.day-of-week ^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_statutory_maternity_pay_data import (
    SMP_LOWER_EARNINGS_LIMITS,
    SMP_LOWER_EARNINGS_LIMITS_HISTORY,
    SMP_RATES,
    SMP_RATES_HISTORY,
)
from .payroll_statutory_maternity_pay_types import MaternityPay, MaternityPayWeek

SMP_WEEKS = 39
EARNINGS_RELATED_WEEKS = 6
# 90% of average weekly earnings (SSCBA 1992 s.166(1)(a)).
EARNINGS_BASIS_POINTS = 9000

T = TypeVar("T")


def _lookup(rows: Sequence[T], history: str, date: str, what: str) -> T:
    for row in rows:
        if date >= row.valid_from and (row.valid_to is None or date <= row.valid_to):
            return row
    if history != "full":
        earliest = min((r.valid_from for r in rows), default=None)
        if earliest is not None and date < earliest:
            raise ValueError(
                "no %s for %s: this build was installed with history=%s, so it only carries rules from %s. "
                "Reinstall with history=full for older cases." % (what, date, history, earliest)
            )
    raise ValueError("no %s for %s" % (what, date))


def statutory_maternity_pay(average_weekly_earnings: Money, due_date: str, pay_start: str) -> MaternityPay:
    """Statutory Maternity Pay: whether it is due, and the 39 weekly amounts.

    Two dates decide the figures and are easy to confuse. Eligibility uses the
    lower earnings limit in force at the end of the qualifying week, which can
    be in an earlier tax year than the pay; each week's flat rate is the one in
    force on the day that SMP week starts.
    """
    if average_weekly_earnings.currency != "GBP":
        raise ValueError("SMP is paid in GBP, received %s" % (average_weekly_earnings.currency,))
    if average_weekly_earnings.minor < 0:
        raise ValueError("averageWeeklyEarnings must not be negative")

    # The expected week of childbirth runs Sunday to Saturday (SSCBA 1992 s.171(1)).
    expected_week = add_days(due_date, -(day_of_week(due_date) % 7))
    qualifying_week_start = add_days(expected_week, -15 * 7)
    add_days(pay_start, 0)
    lel = _lookup(SMP_LOWER_EARNINGS_LIMITS, SMP_LOWER_EARNINGS_LIMITS_HISTORY, add_days(qualifying_week_start, 6), "lower earnings limit").weekly_limit

    weeks: List[MaternityPayWeek] = []
    total = 0
    if average_weekly_earnings.minor >= lel:
        # Kept in hundredths of a penny: HMRC says not to round average weekly
        # earnings, and reg 28 rounds only the payment, up.
        earnings_rate = average_weekly_earnings.minor * EARNINGS_BASIS_POINTS
        for week in range(1, SMP_WEEKS + 1):
            start = add_days(pay_start, 7 * (week - 1))
            rate = earnings_rate
            if week > EARNINGS_RELATED_WEEKS:
                rate = min(rate, _lookup(SMP_RATES, SMP_RATES_HISTORY, start, "SMP rate").weekly_rate * 10000)
            amount = round_div(rate, 10000, "up")
            total += amount
            weeks.append(MaternityPayWeek(week=week, start=start, end=add_days(start, 6), amount=money(amount, "GBP")))
    return MaternityPay(
        eligible=average_weekly_earnings.minor >= lel,
        qualifying_week_start=qualifying_week_start,
        lower_earnings_limit=money(lel, "GBP"),
        weeks=weeks,
        total=money(total, "GBP"),
    )