diff --git a/CHANGELOG b/CHANGELOG index 533c5d2097..77736ea900 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Use party identifier to store SIREN and SIRET * Add support for Python 3.10 * Remove support for Python 3.6 diff --git a/address.py b/address.py index bc29fc1e74..9fbcc82442 100644 --- a/address.py +++ b/address.py @@ -1,37 +1,87 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.i18n import gettext -from trytond.model import fields -from trytond.pool import PoolMeta +from sql import Literal, Null +from sql.operators import Concat -from . import luhn -from .exceptions import SIRETValidationError +from trytond.model import fields +from trytond.pool import Pool, PoolMeta +from trytond.transaction import Transaction class Address(metaclass=PoolMeta): __name__ = 'party.address' - siret_nic = fields.Char("SIRET NIC", select=True, size=5) - siret = fields.Function(fields.Char('SIRET'), 'get_siret') - - def get_siret(self, name): - if self.party.siren and self.siret_nic: - return self.party.siren + self.siret_nic + siret = fields.Function(fields.Many2One( + 'party.identifier', "SIRET"), + 'get_siret', searcher='search_siret') @classmethod - def validate(cls, addresses): - super(Address, cls).validate(addresses) - for address in addresses: - address.check_siret() + def __register__(cls, module): + pool = Pool() + Party = pool.get('party.party') + Identifier = pool.get('party.identifier') + cursor = Transaction().connection.cursor() + party = Party.__table__() + address = cls.__table__() + identifier = Identifier.__table__() - def check_siret(self): - ''' - Check validity of SIRET - ''' - if self.siret: - if (len(self.siret) != 14 - or not luhn.validate(self.siret)): - raise SIRETValidationError( - gettext('party_siret.msg_invalid_siret', - number=self.siret, - address=self.rec_name)) + super().__register__(module) + + table_h = cls.__table_handler__(module) + party_h = Party.__table_handler__(module) + + # Migrate from 6.2: replace siren and siret by identifier + if party_h.column_exist('siren'): + cursor.execute(*identifier.insert( + [identifier.party, + identifier.type, identifier.code, + identifier.active], + party.select( + party.id, Literal('fr_siren'), + party.siren, party.active, + where=(party.siren != Null) + & (party.siren != '')))) + if table_h.column_exist('siret_nic'): + cursor.execute(*identifier.insert( + [identifier.party, identifier.address, + identifier.type, identifier.code, + identifier.active], + address.join( + party, condition=address.party == party.id + ).select( + address.party, address.id, + Literal('fr_siret'), + Concat(party.siren, address.siret_nic), + address.active, + where=(address.siret_nic != Null) + & (address.siret_nic != '') + & (party.siren != Null) + & (party.siren != '')))) + table_h.drop_column('siret_nic') + party_h.drop_column('siren') + + def get_siret(self, name): + for identifier in self.identifiers: + if identifier.type == 'fr_siret': + return identifier.id + + @classmethod + def search_siret(cls, name, clause): + _, operator, value = clause + domain = [ + ('identifiers', 'where', [ + ('code', operator, value), + ('type', 'in', 'fr_siren'), + ]), + ] + # Add party without tax identifier + if ((operator == '=' and value is None) + or (operator == 'in' and None in value)): + domain = ['OR', + domain, [ + ('identifiers', 'not where', [ + ('type', '=', 'fr_siren'), + ]), + ], + ] + return domain diff --git a/address.xml b/address.xml index 1e8b443cbd..b3b9835b1e 100644 --- a/address.xml +++ b/address.xml @@ -8,10 +8,5 @@ this repository contains the full copyright notices and license terms. --> address_tree - - party.address - - address_form - diff --git a/exceptions.py b/exceptions.py deleted file mode 100644 index 429ed4d2d8..0000000000 --- a/exceptions.py +++ /dev/null @@ -1,12 +0,0 @@ -# This file is part of Tryton. The COPYRIGHT file at the top level of -# this repository contains the full copyright notices and license terms. - -from trytond.model.exceptions import ValidationError - - -class SIRENValidationError(ValidationError): - pass - - -class SIRETValidationError(SIRENValidationError): - pass diff --git a/luhn.py b/luhn.py deleted file mode 100644 index e48e2da092..0000000000 --- a/luhn.py +++ /dev/null @@ -1,18 +0,0 @@ -# This file is part of Tryton. The COPYRIGHT file at the top level of -# this repository contains the full copyright notices and license terms. - - -def validate(value): - """ - Validate value with Luhn algorithm - :param value: the value - :return: a boolean - """ - if not isinstance(value, str): - value = str(value) - try: - evens = sum(int(x) for x in value[-1::-2]) - odds = sum(sum(divmod(int(x) * 2, 10)) for x in value[-2::-2]) - return (evens + odds) % 10 == 0 - except ValueError: - return False diff --git a/message.xml b/message.xml deleted file mode 100644 index 623e858850..0000000000 --- a/message.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - The SIREN number "%(number)s" for party "%(party)s" is not valid. - - - The SIRET number "%(number)s" for address "%(address)s" is not valid. - - - diff --git a/party.py b/party.py index 1b358d75cb..856c9cde03 100644 --- a/party.py +++ b/party.py @@ -1,31 +1,38 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.i18n import gettext from trytond.model import fields from trytond.pool import PoolMeta -from . import luhn -from .exceptions import SIRENValidationError - class Party(metaclass=PoolMeta): __name__ = 'party.party' - siren = fields.Char("SIREN", select=True, size=9) + siren = fields.Function(fields.Many2One( + 'party.identifier', "SIREN"), + 'get_siren', searcher='search_siren') + + def get_siren(self, name): + for identifier in self.identifiers: + if identifier.type == 'fr_siren': + return identifier.id @classmethod - def validate(cls, parties): - super(Party, cls).validate(parties) - for party in parties: - party.check_siren() - - def check_siren(self): - ''' - Check validity of SIREN - ''' - if self.siren: - if len(self.siren) != 9 or not luhn.validate(self.siren): - raise SIRENValidationError( - gettext('party_siret.msg_invalid_siren', - number=self.siren, - party=self.rec_name)) + def search_siren(cls, name, clause): + _, operator, value = clause + domain = [ + ('identifiers', 'where', [ + ('code', operator, value), + ('type', 'in', 'fr_siren'), + ]), + ] + # Add party without tax identifier + if ((operator == '=' and value is None) + or (operator == 'in' and None in value)): + domain = ['OR', + domain, [ + ('identifiers', 'not where', [ + ('type', '=', 'fr_siren'), + ]), + ], + ] + return domain diff --git a/party.xml b/party.xml index 85f307213f..7e887ad828 100644 --- a/party.xml +++ b/party.xml @@ -8,10 +8,5 @@ this repository contains the full copyright notices and license terms. --> party_tree - - party.party - - party_form - diff --git a/tests/test_party_siret.py b/tests/test_party_siret.py index ec83e8fc66..10904868c7 100644 --- a/tests/test_party_siret.py +++ b/tests/test_party_siret.py @@ -3,7 +3,6 @@ import unittest import trytond.tests.test_tryton -from trytond.modules.party_siret import luhn from trytond.tests.test_tryton import ModuleTestCase @@ -11,32 +10,6 @@ class PartySiretTestCase(ModuleTestCase): 'Test PartySiret module' module = 'party_siret' - def test_luhn(self): - 'Test luhn' - values = ( - (4111111111111111, True), - ('4111111111111111', True), - (4222222222222, True), - (378734493671000, True), - (5424000000000015, True), - (5555555555554444, True), - (1008, True), - ('0000001008', True), - ('000000001008', True), - (4012888888881881, True), - (1234567890123456789012345678909, True), - (4111111111211111, False), - (42222222222224, False), - (100, False), - ('100', False), - ('0000100', False), - ('abc', False), - (None, False), - (object(), False), - ) - for value, test in values: - self.assertTrue(luhn.validate(value) == test) - def suite(): suite = trytond.tests.test_tryton.suite() diff --git a/tryton.cfg b/tryton.cfg index b806971089..9e6cae02bb 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -6,4 +6,3 @@ depends: xml: party.xml address.xml - message.xml diff --git a/view/address_form.xml b/view/address_form.xml deleted file mode 100644 index b07347e583..0000000000 --- a/view/address_form.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - diff --git a/view/party_form.xml b/view/party_form.xml deleted file mode 100644 index 142bd1fa7b..0000000000 --- a/view/party_form.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - -