import re from typing import Optional from .money_amount import money from .payroll_tax_code_parse_types import TaxCode # The whitespace every language trims alike. str.strip() and \s also take # \x1c-\x1f and \x85, JavaScript's trim() and \s also take \ufeff, and Rust's # trim() takes \x85, so each implementation names the same set instead. SPACE = " \t\n\r\x0b\x0c\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000" # The basis marker may be written with or without a space: "1257L W1", "1257LM1", "1257L X". BASIS = re.compile(r"(.*?)[ \t\n\r\x0b\x0c\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]*(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(SPACE).upper() if text == "": raise ValueError('unrecognised tax code "%s"' % (original,)) cumulative = True basis = BASIS.fullmatch(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.fullmatch(body) k = K_CODE.fullmatch(body) d = D_CODE.fullmatch(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, )