1# ird.py - functions for handling New Zealand IRD numbers
2# coding: utf-8
3#
4# Copyright (C) 2019 Leandro Regueiro
5#
6# This library is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Lesser General Public
8# License as published by the Free Software Foundation; either
9# version 2.1 of the License, or (at your option) any later version.
10#
11# This library is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public
17# License along with this library; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19# 02110-1301 USA
20
21"""IRD number (New Zealand Inland Revenue Department (Te Tari Tāke) number).
22
23The IRD number is used by the New Zealand Inland Revenue Department (Te Tari
24Tāke in Māori) to identify businesses and individuals for tax purposes. The
25number consists of 8 or 9 digits where the last digit is a check digit.
26
27More information:
28
29* https://www.ird.govt.nz/
30* https://www.ird.govt.nz/-/media/Project/IR/PDF/2020RWTNRWTSpecificationDocumentv10.pdf
31* https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/New%20Zealand-TIN.pdf
32
33>>> validate('4909185-0')
34'49091850'
35>>> validate('NZ 49-098-576')
36'49098576'
37>>> validate('136410133')
38Traceback (most recent call last):
39    ...
40InvalidChecksum: ...
41>>> validate('9125568')
42Traceback (most recent call last):
43    ...
44InvalidLength: ...
45>>> format('49098576')
46'49-098-576'
47"""
48
49from stdnum.exceptions import *
50from stdnum.util import clean, isdigits
51
52
53def compact(number):
54    """Convert the number to the minimal representation."""
55    number = clean(number, ' -').upper().strip()
56    if number.startswith('NZ'):
57        return number[2:]
58    return number
59
60
61def calc_check_digit(number):
62    """Calculate the check digit.
63
64    The number passed should not have the check digit included.
65    """
66    primary_weights = (3, 2, 7, 6, 5, 4, 3, 2)
67    secondary_weights = (7, 4, 3, 2, 5, 2, 7, 6)
68    # pad with leading zeros
69    number = (8 - len(number)) * '0' + number
70    s = -sum(w * int(n) for w, n in zip(primary_weights, number)) % 11
71    if s != 10:
72        return str(s)
73    s = -sum(w * int(n) for w, n in zip(secondary_weights, number)) % 11
74    return str(s)
75
76
77def validate(number):
78    """Check if the number is a valid IRD number."""
79    number = compact(number)
80    if len(number) not in (8, 9):
81        raise InvalidLength()
82    if not isdigits(number):
83        raise InvalidFormat()
84    if not 10000000 < int(number) < 150000000:
85        raise InvalidComponent()
86    if number[-1] != calc_check_digit(number[:-1]):
87        raise InvalidChecksum()
88    return number
89
90
91def is_valid(number):
92    """Check if the number is a valid IRD number."""
93    try:
94        return bool(validate(number))
95    except ValidationError:
96        return False
97
98
99def format(number):
100    """Reformat the number to the standard presentation format."""
101    number = compact(number)
102    return '-'.join([number[:-6], number[-6:-3], number[-3:]])
103