from typing import List, Optional, Sequence, TypeVar from .dates_add_days import add_days from .dates_day_of_week import day_of_week from .math_round_div import round_div from .money_amount import Money, money 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"), )