from typing import Sequence from .dates_add_days import add_days, days_between from .dates_business_days_between import business_days_between from .math_round_div import round_div from .money_amount import Money, money from .payroll_pro_rata_salary_types import ProRata, ProRataMethod def _count_days(start: str, end: str, method: str, holidays: Sequence[str]) -> int: if method == "calendar-days": return days_between(start, end) + 1 if method == "working-days": return business_days_between(start, add_days(end, 1), holidays) raise ValueError('unknown pro-rata method "%s"' % (method,)) def pro_rata_salary( period_salary: Money, fte_basis_points: int, period_start: str, period_end: str, worked_from: str, worked_to: str, method: ProRataMethod, holidays: Sequence[str], ) -> ProRata: """Pro-rate a period's salary by time employed and FTE, rounding once.""" if isinstance(fte_basis_points, bool) or not isinstance(fte_basis_points, int) or not 1 <= fte_basis_points <= 10000: raise ValueError("fteBasisPoints must be a whole number from 1 to 10000, received %s" % (fte_basis_points,)) if days_between(period_start, period_end) < 0: raise ValueError("periodEnd is before periodStart") if days_between(worked_from, worked_to) < 0: raise ValueError("workedTo is before workedFrom") if worked_from < period_start or worked_to > period_end: raise ValueError("the worked dates must fall inside the pay period") days_in_period = _count_days(period_start, period_end, method, holidays) if days_in_period == 0: raise ValueError("the pay period has no working days to pro-rate over") days_counted = _count_days(worked_from, worked_to, method, holidays) minor = round_div(period_salary.minor * fte_basis_points * days_counted, 10000 * days_in_period, "half-up") return ProRata(pay=money(minor, period_salary.currency), days_counted=days_counted, days_in_period=days_in_period)