Functional Weave
Code in Python

payroll.minimum-wage-check@1.0.0

impl/python.py

3,065 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

from .dates_add_months import add_months  ← from dates.add-months ^1.0.0 · built alongside by fune
from .dates_age import age  ← from dates.age ^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_minimum_wage_check_data import NMW_RATES, NMW_RATES_HISTORY, NmwRate  ← this capability’s own data, compiled from data/rates.json into the same file by fune build
from .payroll_minimum_wage_check_types import MinimumWageCheck


def _rates_in_force(period_start: str) -> List[NmwRate]:
    rows = [r for r in NMW_RATES if period_start >= r.valid_from and (r.valid_to is None or period_start <= r.valid_to)]
    if rows:
        return rows
    if NMW_RATES_HISTORY != "full":
        earliest = min((r.valid_from for r in NMW_RATES), default=None)
        if earliest is not None and period_start < earliest:
            raise ValueError(
                "no minimum wage rate for %s: this build was installed with history=%s, "
                "so it only carries rates from %s. Reinstall with history=full to check older pay."
                % (period_start, NMW_RATES_HISTORY, earliest)
            )
    raise ValueError("no minimum wage rate for %s" % (period_start,))


def minimum_wage_check(
    pay: Money,
    minutes_worked: int,
    birth_date: str,
    period_start: str,
    apprenticeship_start: Optional[str],
) -> MinimumWageCheck:
    """Minimum wage check for one pay reference period. The rate and the age
    are both taken on the first day of the period, never on the pay date.
    """
    if pay.currency != "GBP":
        raise ValueError("the minimum wage is in GBP, received %s" % (pay.currency,))
    if isinstance(minutes_worked, bool) or not isinstance(minutes_worked, int) or minutes_worked < 0:
        raise ValueError("minutesWorked must be a whole number of minutes, not negative, received %s" % (minutes_worked,))
    years = age(birth_date, period_start)
    if years < 16:
        raise ValueError("a worker aged %d is below school leaving age: no minimum wage applies" % (years,))
    if apprenticeship_start is not None and apprenticeship_start > period_start:
        raise ValueError("apprenticeshipStart %s is after periodStart %s" % (apprenticeship_start, period_start))
    rows = _rates_in_force(period_start)
    apprentice_rate = apprenticeship_start is not None and (
        years < 19 or period_start < add_months(apprenticeship_start, 12)
    )
    chosen: Optional[NmwRate] = None
    for row in rows:
        if apprentice_rate:
            if row.band == "apprentice":
                chosen = row
        elif row.band != "apprentice" and row.min_age <= years and (chosen is None or row.min_age > chosen.min_age):
            chosen = row
    if chosen is None:
        raise ValueError("no minimum wage rate for %s" % (period_start,))
    required = round_div(chosen.rate_minor * minutes_worked, 60, "up")
    shortfall = max(0, required - pay.minor)
    return MinimumWageCheck(
        band=chosen.band,  # type: ignore[arg-type]
        hourly_rate=money(chosen.rate_minor, "GBP"),
        required=money(required, "GBP"),
        shortfall=money(shortfall, "GBP"),
        compliant=shortfall == 0,
    )