Repository: joke2k/faker
Pull request #1072 (2019-12-06 10:12:34)

Add NIP generator
### What does this changes

Add NIP generator

Number of tax identification.
Polish: Numer identyfikacji podatkowej (NIP)

IlfirinPL: I think now everything should be OK, corner case handled. Maybe so code optimization is required, waiting for comments
malefice: @fcurella do you have any feedback on this PR?
fcurella: LGTM! Thanks @IlfirinPL  and thank you @malefice for reviewing! ✨ 

Patch:
diff --git a/faker/providers/person/pl_PL/__init__.py b/faker/providers/person/pl_PL/__init__.py
index 025b50f1..5f0b35c2 100644
--- a/faker/providers/person/pl_PL/__init__.py
+++ b/faker/providers/person/pl_PL/__init__.py
@@ -741,7 +741,7 @@ class Provider(PersonProvider):
 
     @staticmethod
     def pwz_doctor_compute_check_digit(x):
-        return sum((i+1)*d for i, d in enumerate(x)) % 11
+        return sum((i + 1) * d for i, d in enumerate(x)) % 11
 
     def pwz_doctor(self):
         """
@@ -774,3 +774,86 @@ class Provider(PersonProvider):
         kind_char = 'A' if kind == 'midwife' else 'P'
 
         return '{:02d}{}{}'.format(region, ''.join(map(str, core)), kind_char)
+
+    tax_office_codes = (
+        '101', '102', '103', '104', '105', '106', '107', '108', '109', '111', '112', '113', '114', '115', '116', '117',
+        '118', '119', '121', '122', '123', '124', '125', '126', '127', '128', '129', '131', '132', '133', '134', '135',
+        '136', '137', '138', '139', '141', '142', '143', '144', '145', '146', '147', '148', '149', '151', '152', '153',
+        '154', '155', '156', '157', '158', '159', '161', '162', '163', '164', '165', '166', '167', '168', '169', '171',
+        '172', '173', '174', '175', '176', '177', '178', '179', '181', '182', '183', '184', '185', '186', '187', '188',
+        '189', '191', '192', '193', '194', '195', '196', '197', '198', '199', '201', '202', '203', '204', '205', '206',
+        '207', '208', '209', '211', '212', '213', '214', '215', '216', '217', '218', '219', '221', '222', '223', '224',
+        '225', '226', '227', '228', '229', '231', '232', '233', '234', '235', '236', '237', '238', '239', '241', '242',
+        '243', '244', '245', '246', '247', '248', '249', '251', '252', '253', '254', '255', '256', '257', '258', '259',
+        '261', '262', '263', '264', '265', '266', '267', '268', '269', '271', '272', '273', '274', '275', '276', '277',
+        '278', '279', '281', '282', '283', '284', '285', '286', '287', '288', '289', '291', '292', '293', '294', '295',
+        '296', '297', '298', '301', '302', '311', '312', '313', '314', '315', '316', '317', '318', '319', '321', '322',
+        '323', '324', '325', '326', '327', '328', '329', '331', '332', '333', '334', '335', '336', '337', '338', '339',
+        '341', '342', '343', '344', '345', '346', '347', '348', '349', '351', '352', '353', '354', '355', '356', '357',
+        '358', '359', '361', '362', '363', '364', '365', '366', '367', '368', '369', '371', '372', '373', '374', '375',
+        '376', '377', '378', '379', '381', '382', '383', '384', '385', '386', '387', '388', '389', '391', '392', '393',
+        '394', '395', '396', '397', '398', '399', '411', '412', '413', '414', '415', '416', '417', '418', '419', '421',
+        '422', '423', '424', '425', '426', '427', '428', '429', '431', '432', '433', '434', '435', '436', '437', '438',
+        '439', '441', '442', '443', '444', '445', '446', '447', '448', '449', '451', '452', '453', '454', '455', '456',
+        '457', '458', '459', '461', '462', '463', '464', '465', '466', '467', '468', '469', '471', '472', '473', '474',
+        '475', '476', '477', '478', '479', '481', '482', '483', '484', '485', '486', '487', '488', '489', '491', '492',
+        '493', '494', '495', '496', '497', '498', '499', '501', '502', '503', '504', '505', '506', '507', '508', '509',
+        '511', '512', '513', '514', '516', '519', '521', '522', '523', '524', '525', '526', '527', '528', '529', '531',
+        '532', '533', '534', '535', '536', '537', '538', '539', '541', '542', '543', '544', '545', '546', '547', '548',
+        '549', '551', '552', '553', '554', '555', '556', '557', '558', '559', '561', '562', '563', '564', '565', '566',
+        '567', '568', '569', '571', '572', '573', '574', '575', '576', '577', '578', '579', '581', '582', '583', '584',
+        '585', '586', '587', '588', '589', '591', '592', '593', '594', '595', '596', '597', '598', '599', '601', '602',
+        '603', '604', '605', '606', '607', '608', '609', '611', '612', '613', '614', '615', '616', '617', '618', '619',
+        '621', '622', '623', '624', '625', '626', '627', '628', '629', '631', '632', '633', '634', '635', '636', '637',
+        '638', '639', '641', '642', '643', '644', '645', '646', '647', '648', '649', '651', '652', '653', '654', '655',
+        '656', '657', '658', '659', '661', '662', '663', '664', '665', '666', '667', '668', '669', '671', '672', '673',
+        '674', '675', '676', '677', '678', '679', '681', '682', '683', '684', '685', '686', '687', '688', '689', '691',
+        '692', '693', '694', '695', '696', '697', '698', '699', '701', '711', '712', '713', '714', '715', '716', '717',
+        '718', '719', '721', '722', '723', '724', '725', '726', '727', '728', '729', '731', '732', '733', '734', '735',
+        '736', '737', '738', '739', '741', '742', '743', '744', '745', '746', '747', '748', '749', '751', '752', '753',
+        '754', '755', '756', '757', '758', '759', '761', '762', '763', '764', '765', '766', '767', '768', '769', '771',
+        '772', '773', '774', '775', '776', '777', '778', '779', '781', '782', '783', '784', '785', '786', '787', '788',
+        '789', '791', '792', '793', '794', '795', '796', '797', '798', '799', '811', '812', '813', '814', '815', '816',
+        '817', '818', '819', '821', '822', '823', '824', '825', '826', '827', '828', '829', '831', '832', '833', '834',
+        '835', '836', '837', '838', '839', '841', '842', '843', '844', '845', '846', '847', '848', '849', '851', '852',
+        '853', '854', '855', '856', '857', '858', '859', '861', '862', '863', '864', '865', '866', '867', '868', '869',
+        '871', '872', '873', '874', '875', '876', '877', '878', '879', '881', '882', '883', '884', '885', '886', '887',
+        '888', '889', '891', '892', '893', '894', '895', '896', '897', '898', '899', '911', '912', '913', '914', '915',
+        '916', '917', '918', '919', '921', '922', '923', '924', '925', '926', '927', '928', '929', '931', '932', '933',
+        '934', '935', '936', '937', '938', '939', '941', '942', '943', '944', '945', '946', '947', '948', '949', '951',
+        '952', '953', '954', '955', '956', '957', '958', '959', '961', '962', '963', '964', '965', '966', '967', '968',
+        '969', '971', '972', '973', '974', '975', '976', '977', '978', '979', '981', '982', '983', '984', '985', '986',
+        '987', '988', '989', '991', '992', '993', '994', '995', '996', '997', '998',
+    )
+
+    def nip(self):
+        """
+        Returns 10 digit of Number of tax identification.
+        Polish: Numer identyfikacji podatkowej (NIP).
+
+        https://pl.wikipedia.org/wiki/NIP
+        list of codes
+        http://www.algorytm.org/numery-identyfikacyjne/nip.html
+
+        """
+
+        nip = [int(i) for i in self.random_element(self.tax_office_codes)]
+        for _ in range(6):
+            nip.append(self.random_digit())
+
+        weights = (6, 5, 7, 2, 3, 4, 5, 6, 7)
+        check_sum = sum(d * w for d, w in zip(nip, weights)) % 11
+
+        if check_sum % 11 == 10:
+            position = self.random_int(3, 8)
+            if nip[position] < 9:
+                nip[position] = (nip[position] + 1) % 10
+                nip.append((check_sum + weights[position]) % 11)
+            else:
+                nip[position] = (nip[position] - 1) % 10
+                nip.append((check_sum - weights[position]) % 11)
+
+        else:
+            nip.append(check_sum % 11)
+
+        return ''.join(str(character) for character in nip)


Test patch:
diff --git a/tests/providers/test_person.py b/tests/providers/test_person.py
index ae42f17c..58d0720a 100644
--- a/tests/providers/test_person.py
+++ b/tests/providers/test_person.py
@@ -21,8 +21,7 @@ from faker.providers.person.sv_SE import Provider as SvSEProvider
 from faker.providers.person.cs_CZ import Provider as CsCZProvider
 from faker.providers.person.pl_PL import Provider as PlPLProvider
 from faker.providers.person.pl_PL import (
-    checksum_identity_card_number as pl_checksum_identity_card_number,
-)
+    checksum_identity_card_number as pl_checksum_identity_card_number)
 from faker.providers.person.zh_CN import Provider as ZhCNProvider
 from faker.providers.person.zh_TW import Provider as ZhTWProvider
 from faker.providers.person.ta_IN import Provider as TaINProvider
@@ -223,7 +222,7 @@ class TestPlPL(unittest.TestCase):
     def test_pesel_birth_date(self, mock_random_digit):
         mock_random_digit.side_effect = [3, 5, 8, 8, 7, 9, 9, 3]
         assert self.fake.pesel(datetime.date(1999, 12, 31)) == '99123135885'
-        assert self.fake.pesel(datetime.date(2000,  1,  1)) == '00210179936'
+        assert self.fake.pesel(datetime.date(2000, 1, 1)) == '00210179936'
 
     @mock.patch.object(PlPLProvider, 'random_digit')
     def test_pesel_sex_male(self, mock_random_digit):
@@ -257,6 +256,24 @@ class TestPlPL(unittest.TestCase):
         assert self.fake.pwz_nurse(kind='nurse') == '4534567P'
         assert self.fake.pwz_nurse(kind='midwife') == '0317512A'
 
+    @staticmethod
+    def validate_nip(nip_str):
+        """
+        Validates NIP using recomended code
+        https://pl.wikibooks.org/wiki/Kody_%C5%BAr%C3%B3d%C5%82owe/Implementacja_NIP
+        """
+        nip_str = nip_str.replace('-', '')
+        if len(nip_str) != 10 or not nip_str.isdigit():
+            return False
+        digits = [int(i) for i in nip_str]
+        weights = (6, 5, 7, 2, 3, 4, 5, 6, 7)
+        check_sum = sum(d * w for d, w in zip(digits, weights)) % 11
+        return check_sum == digits[9]
+
+    def test_nip(self):
+        for _ in range(100):
+            assert self.validate_nip(self.fake.nip())
+
 
 class TestCsCZ(unittest.TestCase):