from typing import Optional from .charity_donation_matching_types import DonationMatch, MatchLimit from .money_amount import Money, money from .money_apply_rate import apply_rate def _check(name: str, currency: str, amount: Money) -> None: if amount.currency != currency: raise ValueError("currency mismatch: %s and %s" % (amount.currency, currency)) if amount.minor < 0: raise ValueError("%s must not be negative, received %d" % (name, amount.minor)) def donation_match( donation: Money, ratio_basis_points: int, donor_cap: Optional[Money], donor_matched_so_far: Money, programme_cap: Optional[Money], programme_matched_so_far: Money, ) -> DonationMatch: """An employer's matched-giving contribution. The ratio is applied first and rounded down; then the match is held to whatever room is left under the donor's cap and the programme's budget, never below zero. """ currency = donation.currency _check("donation", currency, donation) if isinstance(ratio_basis_points, bool) or not isinstance(ratio_basis_points, int) or ratio_basis_points < 0: raise ValueError("ratioBasisPoints must be a non-negative integer, received %s" % (ratio_basis_points,)) if donor_cap is not None: _check("donorCap", currency, donor_cap) _check("donorMatchedSoFar", currency, donor_matched_so_far) if programme_cap is not None: _check("programmeCap", currency, programme_cap) _check("programmeMatchedSoFar", currency, programme_matched_so_far) uncapped = apply_rate(donation, ratio_basis_points, "down").minor matched = uncapped capped_by: MatchLimit = "none" if donor_cap is not None: room = max(0, donor_cap.minor - donor_matched_so_far.minor) if room < matched: matched = room capped_by = "donor" if programme_cap is not None: room = max(0, programme_cap.minor - programme_matched_so_far.minor) if room < matched: matched = room capped_by = "programme" return DonationMatch(matched=money(matched, currency), uncapped=money(uncapped, currency), capped_by=capped_by)