import re from typing import List, Optional from .finance_tax_vat_rate_data import VAT_RULES, VAT_RULES_HISTORY from .finance_tax_vat_rate_types import VatRate ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}") def vat_rate(jurisdiction: str, category: str, on_date: str) -> VatRate: """The VAT rate in force for a jurisdiction and category on a given date. The date matters. A credit note against a 2009 invoice has to use the rate that applied in 2009, not the one that applies today, and a system that hardcodes 0.20 cannot do that at all. ISO dates compare correctly as strings, so this needs no date library. """ 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,)) best: Optional[VatRate] = None for rule in VAT_RULES: if rule.jurisdiction != jurisdiction or rule.category != category: continue if on_date < rule.valid_from: continue if rule.valid_to is not None and on_date > rule.valid_to: continue if best is None or rule.valid_from > best.valid_from: best = VatRate( jurisdiction=rule.jurisdiction, category=rule.category, basis_points=rule.basis_points, valid_from=rule.valid_from, valid_to=rule.valid_to, ) if best is None: # A build installed with history=current carries only the rules still in # force. Answering an older date with today's rate would be the exact # failure this capability exists to prevent, so say what happened instead. # The horizon that matters is this jurisdiction and category's own # earliest surviving rule, not the registry-wide one. if VAT_RULES_HISTORY != "full": earliest = None for rule in VAT_RULES: if rule.jurisdiction != jurisdiction or rule.category != category: continue if earliest is None or rule.valid_from < earliest: earliest = rule.valid_from if earliest is not None and on_date < earliest: raise ValueError( "no VAT rule for %s/%s on %s: this build was installed with history=%s, " "so it only carries rules from %s. Reinstall with history=full to reprint older invoices." % (jurisdiction, category, on_date, VAT_RULES_HISTORY, earliest) ) raise ValueError("no VAT rule for %s/%s on %s" % (jurisdiction, category, on_date)) return best def vat_categories(jurisdiction: str, on_date: str) -> List[str]: """Every category this registry knows a rate for, for a jurisdiction and date.""" seen = set() for rule in VAT_RULES: if rule.jurisdiction != jurisdiction: continue if on_date < rule.valid_from: continue if rule.valid_to is not None and on_date > rule.valid_to: continue seen.add(rule.category) return sorted(seen)