import re from .math_round_div import round_div from .money_amount import Money, money from .retail_sales_tax_us_data import STATE_RATES, STATE_RATES_HISTORY from .retail_sales_tax_us_types import UsSalesTax _ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}") def _state_rate(state: str, on_date: str) -> int: if not any(r.state == state for r in STATE_RATES): raise ValueError('unknown US state "%s"' % (state,)) best = None for r in STATE_RATES: if r.state != state or on_date < r.valid_from or (r.valid_to is not None and on_date > r.valid_to): continue if best is None or r.valid_from > best.valid_from: best = r if best is not None: return best.rate_per_million if STATE_RATES_HISTORY != "full": earliest = None for r in STATE_RATES: if r.state == state and (earliest is None or r.valid_from < earliest): earliest = r.valid_from if earliest is not None and on_date < earliest: raise ValueError( "no sales tax rate for %s on %s: this build was installed with history=%s, " "so it only carries rates from %s. Reinstall with history=full for older sales." % (state, on_date, STATE_RATES_HISTORY, earliest) ) raise ValueError("no sales tax rate for %s on %s" % (state, on_date)) def sales_tax_us(amount: Money, state: str, local_rate_per_million: int, on_date: str) -> UsSalesTax: """US sales tax: the state's base rate on the date of sale plus the caller's local rate, applied once and rounded half up to the cent. """ if not isinstance(on_date, str) or not _ISO_DATE.fullmatch(on_date): raise ValueError('onDate must be an ISO date (YYYY-MM-DD), received "%s"' % (on_date,)) if amount.currency != "USD": raise ValueError("US sales tax is charged in USD, received %s" % amount.currency) if ( isinstance(local_rate_per_million, bool) or not isinstance(local_rate_per_million, int) or local_rate_per_million < 0 or local_rate_per_million >= 1000000 ): raise ValueError("localRatePerMillion must be from 0 to 999999, received %r" % (local_rate_per_million,)) state_rate_per_million = _state_rate(state, on_date) combined = state_rate_per_million + local_rate_per_million tax = money(round_div(amount.minor * combined, 1000000, "half-up"), "USD") return UsSalesTax( state=state, state_rate_per_million=state_rate_per_million, local_rate_per_million=local_rate_per_million, combined_rate_per_million=combined, taxable=amount, tax=tax, total=money(amount.minor + tax.minor, "USD"), )