From f854aed8f680230a7769e4b3323009faa48f172f Mon Sep 17 00:00:00 2001 From: Patrick Trabocchi Date: Mon, 16 Dec 2024 19:06:11 +0100 Subject: [PATCH 1/3] [IMP] l10n_it_fiscalcode: update library to check fiscal code validity --- l10n_it_fiscalcode/README.rst | 44 +++++++++---------- l10n_it_fiscalcode/__manifest__.py | 2 +- l10n_it_fiscalcode/model/res_partner.py | 4 +- .../static/description/index.html | 11 +++-- l10n_it_fiscalcode/tests/test_fiscalcode.py | 16 +++++-- l10n_it_fiscalcode/wizard/compute_fc.py | 24 +++++----- requirements.txt | 2 +- 7 files changed, 58 insertions(+), 45 deletions(-) diff --git a/l10n_it_fiscalcode/README.rst b/l10n_it_fiscalcode/README.rst index 6f22a0636c55..360e301d88eb 100644 --- a/l10n_it_fiscalcode/README.rst +++ b/l10n_it_fiscalcode/README.rst @@ -85,13 +85,13 @@ fragment is included. ] 16.0.1.0.0 (2022-11-11) ----------------------- -- [MIG] Migration from Odoo 14.0 to 16.0 -- [IMP] Black, isort, prettier (pre-commit) +- [MIG] Migration from Odoo 14.0 to 16.0 +- [IMP] Black, isort, prettier (pre-commit) 16.0.1.0.1 (2022-11-16) ----------------------- -- [IMP] Add codicefiscale.isvalid() to improve fiscalcode validation +- [IMP] Add codicefiscale.isvalid() to improve fiscalcode validation Bug Tracker =========== @@ -116,25 +116,25 @@ Authors Contributors ------------ -- Davide Corio -- Luca Subiaco -- Simone Orsi -- Mario Riva -- Mauro Soligo -- Giovanni Barzan -- Lorenzo Battistini -- Roberto Onnis -- Franco Tampieri -- Andrea Cometa -- Andrea Gallina -- Matteo Bilotta -- Giuseppe Borruso - Dinamiche Aziendali srl - -- Alex Comba -- Marco Colombo -- `Aion Tech `__: - - - Simone Rubino +- Davide Corio +- Luca Subiaco +- Simone Orsi +- Mario Riva +- Mauro Soligo +- Giovanni Barzan +- Lorenzo Battistini +- Roberto Onnis +- Franco Tampieri +- Andrea Cometa +- Andrea Gallina +- Matteo Bilotta +- Giuseppe Borruso - Dinamiche Aziendali srl + +- Alex Comba +- Marco Colombo +- `Aion Tech `__: + + - Simone Rubino Maintainers ----------- diff --git a/l10n_it_fiscalcode/__manifest__.py b/l10n_it_fiscalcode/__manifest__.py index e0acbb125cb1..46d1eb498c9d 100644 --- a/l10n_it_fiscalcode/__manifest__.py +++ b/l10n_it_fiscalcode/__manifest__.py @@ -18,7 +18,7 @@ "license": "AGPL-3", "depends": ["account"], "external_dependencies": { - "python": ["codicefiscale"], + "python": ["python-codicefiscale"], }, "data": [ "security/ir.model.access.csv", diff --git a/l10n_it_fiscalcode/model/res_partner.py b/l10n_it_fiscalcode/model/res_partner.py index a23bd557b57f..99e2a66f932a 100644 --- a/l10n_it_fiscalcode/model/res_partner.py +++ b/l10n_it_fiscalcode/model/res_partner.py @@ -1,7 +1,7 @@ # Copyright 2024 Simone Rubino - Aion Tech # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from codicefiscale import isvalid +from codicefiscale import codicefiscale from odoo import _, api, fields, models from odoo.exceptions import ValidationError @@ -30,7 +30,7 @@ def check_fiscalcode(self): # Check fiscalcode length of a person msg = _("The fiscal code must have 16 characters.") raise ValidationError(msg) - if not isvalid(partner.fiscalcode): + if not codicefiscale.is_valid(partner.fiscalcode): # Check fiscalcode validity msg = _("The fiscal code isn't valid.") raise ValidationError(msg) diff --git a/l10n_it_fiscalcode/static/description/index.html b/l10n_it_fiscalcode/static/description/index.html index e111620ba3a6..8946534656a6 100644 --- a/l10n_it_fiscalcode/static/description/index.html +++ b/l10n_it_fiscalcode/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -484,7 +485,9 @@

Contributors

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

diff --git a/l10n_it_fiscalcode/tests/test_fiscalcode.py b/l10n_it_fiscalcode/tests/test_fiscalcode.py index dbbcbd4713a2..e6a206f8682f 100644 --- a/l10n_it_fiscalcode/tests/test_fiscalcode.py +++ b/l10n_it_fiscalcode/tests/test_fiscalcode.py @@ -1,7 +1,7 @@ # Copyright 2024 Simone Rubino - Aion Tech # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -from codicefiscale import isvalid +from codicefiscale import codicefiscale from odoo.exceptions import ValidationError from odoo.tests.common import TransactionCase @@ -75,6 +75,16 @@ def test_fiscalcode_check(self): "fiscalcode": "AAAMRA00H04H5010", } ) + # Omocode FC - Test if an omocode fiscalcode is considered valid + # Fiscalcode in this test is get from + # https://pypi.org/project/python-codicefiscale/ examples + self.env["res.partner"].create( + { + "name": "Person", + "is_company": False, + "fiscalcode": "CCCFBA85D03L21VE", + } + ) def test_fiscal_code_check_change_to_person(self): """ @@ -91,7 +101,7 @@ def test_fiscal_code_check_change_to_person(self): ) partner.fiscalcode = wrong_person_fiscalcode # pre-condition - self.assertFalse(isvalid(partner.fiscalcode)) + self.assertFalse(codicefiscale.is_valid(partner.fiscalcode)) # Act with self.assertRaises(ValidationError) as ve: @@ -118,7 +128,7 @@ def test_fiscal_code_check_company_VAT_change_to_person(self): ) partner.fiscalcode = company_vat # pre-condition - self.assertFalse(isvalid(partner.fiscalcode)) + self.assertFalse(codicefiscale.is_valid(partner.fiscalcode)) # Act with self.assertRaises(ValidationError) as ve: diff --git a/l10n_it_fiscalcode/wizard/compute_fc.py b/l10n_it_fiscalcode/wizard/compute_fc.py index 2271a985df4a..307b52fb5cad 100644 --- a/l10n_it_fiscalcode/wizard/compute_fc.py +++ b/l10n_it_fiscalcode/wizard/compute_fc.py @@ -1,23 +1,16 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import datetime import logging +from codicefiscale import codicefiscale + from odoo import _, api, fields, models from odoo.exceptions import UserError from odoo.osv import expression _logger = logging.getLogger(__name__) -try: - from codicefiscale import build - -except ImportError: - _logger.warning( - "codicefiscale library not found. " - "If you plan to use it, please install the codicefiscale library" - " from https://pypi.python.org/pypi/codicefiscale" - ) - class WizardComputeFc(models.TransientModel): _name = "wizard.compute.fc" @@ -185,16 +178,23 @@ def compute_fc(self): or not f.sex ): raise UserError(_("One or more fields are missing")) + nat_code = self._get_national_code( f.birth_city.name, f.birth_province.code, f.birth_date ) if not nat_code: raise UserError(_("National code is missing")) - c_f = build( + + if isinstance(f.birth_date, datetime.date): + birth_date = f.birth_date.strftime("%d/%m/%Y") + else: + birth_date = f.birth_date + + c_f = codicefiscale.encode( f.fiscalcode_surname, f.fiscalcode_firstname, - f.birth_date, f.sex, + birth_date, nat_code, ) if partner.fiscalcode and partner.fiscalcode != c_f: diff --git a/requirements.txt b/requirements.txt index 7052f10569c4..f66bf0b0d4e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ # generated from manifests external_dependencies asn1crypto -codicefiscale elementpath mock openupgradelib +python-codicefiscale unidecode xmlschema From 3bb88074cf3b29a2f32cf86b47eb12ccc7e44a11 Mon Sep 17 00:00:00 2001 From: Patrick Trabocchi Date: Mon, 16 Dec 2024 19:11:09 +0100 Subject: [PATCH 2/3] [IMP] l10n_it_fatturapa_in: update value of intermediary fiscal code in files IT05979361218_003.xml and IT05979361218_004.xml --- l10n_it_fatturapa_in/tests/data/IT05979361218_003.xml | 2 +- l10n_it_fatturapa_in/tests/data/IT05979361218_004.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/l10n_it_fatturapa_in/tests/data/IT05979361218_003.xml b/l10n_it_fatturapa_in/tests/data/IT05979361218_003.xml index 240035ebebbe..9a3f65ef0eb7 100644 --- a/l10n_it_fatturapa_in/tests/data/IT05979361218_003.xml +++ b/l10n_it_fatturapa_in/tests/data/IT05979361218_003.xml @@ -53,7 +53,7 @@ xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1. IT 03533590174 - MRORSS90E25B111T + MRORSS90E25B111K MARIO ROSSI diff --git a/l10n_it_fatturapa_in/tests/data/IT05979361218_004.xml b/l10n_it_fatturapa_in/tests/data/IT05979361218_004.xml index fc0442860da3..f53382d25691 100644 --- a/l10n_it_fatturapa_in/tests/data/IT05979361218_004.xml +++ b/l10n_it_fatturapa_in/tests/data/IT05979361218_004.xml @@ -53,7 +53,7 @@ xsi:schemaLocation="http://ivaservizi.agenziaentrate.gov.it/docs/xsd/fatture/v1. IT 03533590174 - MRORSS90E25B111T + MRORSS90E25B111K MARIO ROSSI From 9b9e594b08062bda0d1bd7c07276c8affac27fca Mon Sep 17 00:00:00 2001 From: Patrick Trabocchi Date: Mon, 16 Dec 2024 19:13:09 +0100 Subject: [PATCH 3/3] [IMP] l10n_it_fatturapa_import_zip: update value of intermediary fiscal code in invoices of archive file --- .../tests/data/xml_import.zip | Bin 88706 -> 88708 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/l10n_it_fatturapa_import_zip/tests/data/xml_import.zip b/l10n_it_fatturapa_import_zip/tests/data/xml_import.zip index f32f41b58d463686384cf9133bd444d7a9eb0027..d9f9f6e479ff3e37100b0c69c1864ba3b1eaa416 100644 GIT binary patch delta 4961 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 G75@hu^@wNy