import re from typing import Optional from .money_amount import money from .payroll_tax_code_parse_types import TaxCode # The basis marker may be written with or without a space: "1257L W1", "1257LM1", "1257L X". BASIS = re.compile(r"^(.*?)\s*(W1|M1|X)$") ALLOWANCE = re.compile(r"^(0|[1-9][0-9]{0,5})([LMNT])$") K_CODE = re.compile(r"^K([1-9][0-9]{0,5})$") D_CODE = re.compile(r"^D([0-9])$") def parse_tax_code(code: str) -> TaxCode: """Take a UK PAYE tax code apart into its region, kind, allowance and basis. The annual allowance is number x 10 + 9 pounds, not number x 10: a code covers a 10-pound band of allowances and HMRC's routines give the employee the top of it, so 1257L is worth 12,579 pounds a year to the payroll. """ original = code text = code.strip().upper() if text == "": raise ValueError('unrecognised tax code "%s"' % (original,)) cumulative = True basis = BASIS.match(text) if basis is not None and basis.group(1) != "": text = basis.group(1) cumulative = False region = "rest-of-uk" body = text if text.startswith("S"): region = "scotland" body = text[1:] elif text.startswith("C"): region = "wales" body = text[1:] number = 0 suffix: Optional[str] = None allowance_pounds = 0 allowance = ALLOWANCE.match(body) k = K_CODE.match(body) d = D_CODE.match(body) if body == "NT": if region != "rest-of-uk": raise ValueError('there is no Scottish or Welsh NT code: "%s"' % (original,)) kind = "no-tax" elif body == "BR": kind = "basic-rate" elif d is not None: kind = "d-rate" number = int(d.group(1)) elif k is not None: kind = "negative-allowance" number = int(k.group(1)) allowance_pounds = -(number * 10 + 9) elif allowance is not None: number = int(allowance.group(1)) suffix = allowance.group(2) if number == 0 and suffix != "T": raise ValueError('unrecognised tax code "%s": a zero allowance is only issued as 0T' % (original,)) kind = "allowance" allowance_pounds = 0 if number == 0 else number * 10 + 9 else: raise ValueError('unrecognised tax code "%s"' % (original,)) prefix = "S" if region == "scotland" else "C" if region == "wales" else "" return TaxCode( code=prefix + body, region=region, kind=kind, number=number, suffix=suffix, annual_allowance=money(allowance_pounds * 100, "GBP"), cumulative=cumulative, )