from typing import List, Optional from .dates_add_months import add_months from .dates_age import age from .math_round_div import round_div from .money_amount import Money, money from .payroll_minimum_wage_check_data import NMW_RATES, NMW_RATES_HISTORY, NmwRate 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, )