Use party identifier to store SIREN and SIRET

issue5834
review397071002
This commit is contained in:
Cédric Krier
2022-02-13 00:43:49 +01:00
parent ca54c4e8fd
commit 8524705dbc
12 changed files with 104 additions and 150 deletions
+1
View File
@@ -1,3 +1,4 @@
* Use party identifier to store SIREN and SIRET
* Add support for Python 3.10
* Remove support for Python 3.6
+76 -26
View File
@@ -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
-5
View File
@@ -8,10 +8,5 @@ this repository contains the full copyright notices and license terms. -->
<field name="inherit" ref="party.address_view_tree"/>
<field name="name">address_tree</field>
</record>
<record model="ir.ui.view" id="address_view_form">
<field name="model">party.address</field>
<field name="inherit" ref="party.address_view_form"/>
<field name="name">address_form</field>
</record>
</data>
</tryton>
-12
View File
@@ -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
-18
View File
@@ -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
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.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. -->
<tryton>
<data grouped="1">
<record model="ir.message" id="msg_invalid_siren">
<field name="text">The SIREN number "%(number)s" for party "%(party)s" is not valid.</field>
</record>
<record model="ir.message" id="msg_invalid_siret">
<field name="text">The SIRET number "%(number)s" for address "%(address)s" is not valid.</field>
</record>
</data>
</tryton>
+27 -20
View File
@@ -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
-5
View File
@@ -8,10 +8,5 @@ this repository contains the full copyright notices and license terms. -->
<field name="inherit" ref="party.party_view_tree"/>
<field name="name">party_tree</field>
</record>
<record model="ir.ui.view" id="party_view_form">
<field name="model">party.party</field>
<field name="inherit" ref="party.party_view_form"/>
<field name="name">party_form</field>
</record>
</data>
</tryton>
-27
View File
@@ -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()
-1
View File
@@ -6,4 +6,3 @@ depends:
xml:
party.xml
address.xml
message.xml
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.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. -->
<data>
<xpath expr="/form/field[@name='name']" position="after">
<group id="siret" colspan="2" col="-1">
<label name="siret_nic"/>
<field name="siret_nic"/>
<label name="siret"/>
<field name="siret"/>
</group>
</xpath>
</data>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.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. -->
<data>
<xpath expr="//page[@name='identifiers']/field[@name='identifiers']" position="before">
<label name="siren"/>
<field name="siren"/>
<newline/>
</xpath>
</data>