From 8434eed9ac9fe6dbf290b84f7a4e8ec591e4e089 Mon Sep 17 00:00:00 2001 From: "Guillaume (guva)" Date: Thu, 7 Jul 2022 16:25:15 +0000 Subject: [PATCH] [FIX] l10n_it_edi: fiscal code format According to www.agenziaentrate.gov.it/portale/web/guest/schede/istanze/richiesta-ts_cf/informazioni-codificazione-pf The tax identification number of natural persons consists of an alphanumeric expression of sixteen characters. The first fifteen characters are indicative of the master data of each individual in the following order: - 3 alphabetic characters for the surname; - 3 alphabetic characters for the first name; - 2 numeric characters for the year of birth; - 1 alphabetic character for the month of birth; - 2 numeric characters for the day of birth and sex; - 4 characters, 1 alphabetic and 3 numeric for the Italian municipality or foreign state of birth. The sixteenth character, alphabetic, serves as a control. The main fix is about this part: When two or more individuals have master data generating the same tax code (homocodes), the tax code is differentiated for each of them. For this purpose, systematic substitutions of one or more digits starting from the right one are made within the seven numeric characters contained in the code with corresponding alphabetic characters according to the following table: 0 = L 4 = Q 8 = U 1 = M 5 = R 9 = V 2 = N 6 = S 3 = P 7 = T Also the check for iva number format was incomplete. Indeed, The three penultimate digits correspond to the region of the VAT office and must be between 001 and 100 inclusive, or equal to 120, 121, 888 or 999. We use the stdnum library's methods for the validation of fiscal code and iva number format. opw-2797408 closes odoo/odoo#97633 X-original-commit: 262eb7bd021d177d9a05397f9cda3fc76a21d4cb Signed-off-by: Josse Colpaert --- addons/l10n_it_edi/models/res_partner.py | 6 +- addons/l10n_it_edi/tests/__init__.py | 1 + addons/l10n_it_edi/tests/test_res_partner.py | 61 ++++++++++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 addons/l10n_it_edi/tests/test_res_partner.py diff --git a/addons/l10n_it_edi/models/res_partner.py b/addons/l10n_it_edi/models/res_partner.py index 754a02c29c4..34510a770c4 100644 --- a/addons/l10n_it_edi/models/res_partner.py +++ b/addons/l10n_it_edi/models/res_partner.py @@ -1,5 +1,6 @@ # -*- coding:utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from stdnum.it import codicefiscale, iva from odoo import api, fields, models, _ from odoo.exceptions import UserError @@ -45,7 +46,6 @@ class ResPartner(models.Model): @api.constrains('l10n_it_codice_fiscale') def validate_codice_fiscale(self): - p = re.compile(r'^([A-Za-z]{6}[0-9]{2}[A-Za-z]{1}[0-9]{2}[A-Za-z]{1}[0-9]{3}[A-Za-z]{1}$)|([0-9]{11})|(IT[0-9]{11})$') for record in self: - if record.l10n_it_codice_fiscale and not p.match(record.l10n_it_codice_fiscale): - raise UserError(_("Invalid Codice Fiscale '%s': should be like 'MRTMTT91D08F205J' for physical person and '12345678901' or 'IT12345678901' for businesses.", record.l10n_it_codice_fiscale)) + if record.l10n_it_codice_fiscale and (not codicefiscale.is_valid(record.l10n_it_codice_fiscale) and not iva.is_valid(record.l10n_it_codice_fiscale)): + raise UserError(_("Invalid Codice Fiscale '%s': should be like 'MRTMTT91D08F205J' for physical person and '12345670546' or 'IT12345670546' for businesses.", record.l10n_it_codice_fiscale)) diff --git a/addons/l10n_it_edi/tests/__init__.py b/addons/l10n_it_edi/tests/__init__.py index c2bcfc21455..c75d90ba899 100644 --- a/addons/l10n_it_edi/tests/__init__.py +++ b/addons/l10n_it_edi/tests/__init__.py @@ -2,3 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import test_ir_mail_server +from . import test_res_partner diff --git a/addons/l10n_it_edi/tests/test_res_partner.py b/addons/l10n_it_edi/tests/test_res_partner.py new file mode 100644 index 00000000000..3c7f747a6c1 --- /dev/null +++ b/addons/l10n_it_edi/tests/test_res_partner.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo.exceptions import UserError +from odoo.tests.common import TransactionCase, tagged + + +@tagged('post_install_l10n', 'post_install', '-at_install') +class TestResPartner(TransactionCase): + + def test_validate_fiscal_code(self): + valid_codes = [ + "AORTHV05P30V295L", + "SPDTHB43S93F42VH", + "MDRTUV99H14X2MNU", + "XPTDRX73R64YPLUD", + "LOLXDR40T3MZRTSV", + "GJTIUG55DLQZRTSS", + "CDEOTG5PBLQZRTSE", + "PERTLELPALQZRTSN", + "IT12345678887", + "IT12345670546", + "IT95286931217", + "IT95867361206", + "IT94567689990", + "12345670546", + "95286931217", + "95867361206", + "94567689990", + ] + + invalid_codes = [ + "AORTHV05P34V295U", + "SPDTHB43O93F42VH", + "MDRTUVV9H14X2MNU", + "XPTDRX73RS4YPLUD", + "LOLXDRQ0T3QZRTSJ", + "GJTIUGR5DLQZRTSS", + "CDEOTG5PBLQZRTSS", + "PERTLEZPALQZRTSN", + "IT12345678901", + "IT12345678885", + "IT45689349992", + "IT78239131204", + "IT45692151219", + "12345678901", + "12345678885", + "45689349992", + "78239131204", + "45692151219", + ] + + partners = self.env['res.partner'] + + for i, code in enumerate(invalid_codes): + with self.assertRaises(UserError): + partners += self.env['res.partner'].create({'name': f'partner_{i}', 'l10n_it_codice_fiscale': code}) + + for i, code in enumerate(valid_codes): + partners += self.env['res.partner'].create({'name': f'partner_{i}', 'l10n_it_codice_fiscale': code}) + + self.assertEqual(len(partners), len(valid_codes))