Mercurial > modules > party
changeset 743:aeb2d1398456
Add a list of identifier to party
issue3869
review17331002
| author | Cédric Krier <ced@b2ck.com> |
|---|---|
| date | Tue, 28 Jul 2015 07:56:09 +0200 |
| parents | 6dbf7f336f00 |
| children | c441c0b60c8d |
| files | CHANGELOG INSTALL __init__.py party.py party.xml setup.py view/check_vies_no_result.xml view/identifier_form.xml view/identifier_list.xml view/party_form.xml |
| diffstat | 10 files changed, 155 insertions(+), 146 deletions(-) [+] |
line wrap: on
line diff
--- a/CHANGELOG Mon Jul 13 23:58:26 2015 +0200 +++ b/CHANGELOG Tue Jul 28 07:56:09 2015 +0200 @@ -1,3 +1,5 @@ +* Add a list of identifier to party + Version 3.6.0 - 2015-04-20 * Bug fixes (see mercurial logs for details) * Add support for PyPy
--- a/INSTALL Mon Jul 13 23:58:26 2015 +0200 +++ b/INSTALL Tue Jul 28 07:56:09 2015 +0200 @@ -7,7 +7,7 @@ * Python 2.7 or later (http://www.python.org/) * trytond (http://www.tryton.org/) * trytond_country (http://www.tryton.org/) - * Optional: vatnumber (http://code.google.com/p/vatnumber/) + * python-stdnum (http://arthurdejong.org/python-stdnum/) Installation ------------
--- a/__init__.py Mon Jul 13 23:58:26 2015 +0200 +++ b/__init__.py Tue Jul 28 07:56:09 2015 +0200 @@ -14,8 +14,8 @@ Category, Party, PartyCategory, + PartyIdentifier, CheckVIESResult, - CheckVIESNoResult, Address, ContactMechanism, Configuration,
--- a/party.py Mon Jul 13 23:58:26 2015 +0200 +++ b/party.py Tue Jul 28 07:56:09 2015 +0200 @@ -1,33 +1,21 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -import logging -from importlib import import_module - +import stdnum.eu.vat as vat +import stdnum.exceptions +from sql import Null from sql.functions import CharLength from trytond.model import ModelView, ModelSQL, fields, Unique from trytond.wizard import Wizard, StateTransition, StateView, Button -from trytond.pyson import Bool, Eval +from trytond.pyson import Eval from trytond.transaction import Transaction from trytond.pool import Pool - -__all__ = ['Party', 'PartyCategory', 'CheckVIESNoResult', 'CheckVIESResult', - 'CheckVIES'] +from trytond import backend -logger = logging.getLogger(__name__) +__all__ = ['Party', 'PartyCategory', 'PartyIdentifier', + 'CheckVIESResult', 'CheckVIES'] -HAS_VATNUMBER = False VAT_COUNTRIES = [('', '')] -try: - import vatnumber - HAS_VATNUMBER = True - for country in vatnumber.countries(): - VAT_COUNTRIES.append((country, country)) -except ImportError: - logger.warning( - 'Unable to import vatnumber. VAT number validation disabled.', - exc_info=True) - STATES = { 'readonly': ~Eval('active', True), } @@ -49,18 +37,10 @@ 'get_code_readonly') lang = fields.Many2One("ir.lang", 'Language', states=STATES, depends=DEPENDS) - vat_number = fields.Char('VAT Number', help="Value Added Tax number", - states={ - 'readonly': ~Eval('active', True), - 'required': Bool(Eval('vat_country')), - }, - depends=['active', 'vat_country']) - vat_country = fields.Selection(VAT_COUNTRIES, 'VAT Country', states=STATES, - depends=DEPENDS, - help="Setting VAT country will enable validation of the VAT number.", - translate=False) + identifiers = fields.One2Many('party.identifier', 'party', 'Identifiers', + states=STATES, depends=DEPENDS) vat_code = fields.Function(fields.Char('VAT Code'), - 'on_change_with_vat_code', searcher='search_vat_code') + 'get_vat_code', searcher='search_vat_code') addresses = fields.One2Many('party.address', 'party', 'Addresses', states=STATES, depends=DEPENDS) contact_mechanisms = fields.One2Many('party.contact_mechanism', 'party', @@ -83,10 +63,6 @@ ('code_uniq', Unique(t, t.code), 'The code of the party must be unique.') ] - cls._error_messages.update({ - 'invalid_vat': ('Invalid VAT number "%(vat)s" on party ' - '"%(party)s".'), - }) cls._order.insert(0, ('name', 'ASC')) @staticmethod @@ -128,40 +104,22 @@ def get_code_readonly(self, name): return True - @fields.depends('vat_country', 'vat_number') - def on_change_with_vat_code(self, name=None): - return (self.vat_country or '') + (self.vat_number or '') + @classmethod + def _vat_types(cls): + return ['eu_vat'] - @fields.depends('vat_country', 'vat_number') - def on_change_with_vat_number(self): - if not self.vat_country: - return self.vat_number - code = self.vat_country.lower() - vat_module = None - try: - module = import_module('stdnum.%s' % code) - vat_module = getattr(module, 'vat', None) - if not vat_module: - vat_module = import_module('stdnum.%s.vat' % code) - except ImportError: - pass - if vat_module: - return vat_module.compact(self.vat_number) - return self.vat_number + def get_vat_code(self, name): + types = self._vat_types() + for identifier in self.identifiers: + if identifier.type in types: + return identifier.code @classmethod def search_vat_code(cls, name, clause): - res = [] - value = clause[2] - for country, _ in VAT_COUNTRIES: - if isinstance(value, basestring) \ - and country \ - and value.upper().startswith(country): - res.append(('vat_country', '=', country)) - value = value[len(country):] - break - res.append(('vat_number', clause[1], value)) - return res + return [ + ('identifier.code',) + tuple(clause[1:]), + ('identifier.type', 'in', cls._vat_types()), + ] def get_full_name(self, name): return self.name @@ -207,6 +165,7 @@ bool_op = 'OR' return [bool_op, ('code',) + tuple(clause[1:]), + ('identifiers.code',) + tuple(clause[1:]), ('name',) + tuple(clause[1:]), ] @@ -229,35 +188,6 @@ return address return default_address - @classmethod - def validate(cls, parties): - super(Party, cls).validate(parties) - for party in parties: - party.check_vat() - - def check_vat(self): - ''' - Check the VAT number depending of the country. - http://sima-pc.com/nif.php - ''' - if not HAS_VATNUMBER: - return - - if not self.vat_country: - return - - vat_number = self.on_change_with_vat_number() - if vat_number != self.vat_number: - self.vat_number = vat_number - self.save() - - if not getattr(vatnumber, 'check_vat_' + - self.vat_country.lower())(vat_number): - self.raise_user_error('invalid_vat', { - 'vat': vat_number, - 'party': self.rec_name, - }) - class PartyCategory(ModelSQL): 'Party - Category' @@ -269,9 +199,81 @@ ondelete='CASCADE', required=True, select=True) -class CheckVIESNoResult(ModelView): - 'Check VIES' - __name__ = 'party.check_vies.no_result' +class PartyIdentifier(ModelSQL, ModelView): + 'Party Identifier' + __name__ = 'party.identifier' + _rec_name = 'code' + party = fields.Many2One('party.party', 'Party', ondelete='CASCADE', + required=True, select=True) + type = fields.Selection('get_types', 'Type') + code = fields.Char('Code', required=True) + + @classmethod + def __setup__(cls): + super(PartyIdentifier, cls).__setup__() + cls._error_messages.update({ + 'invalid_vat': ('Invalid VAT number "%(code)s" ' + 'on party "%(party)s".'), + }) + + @classmethod + def __register__(cls, module_name): + pool = Pool() + Party = pool.get('party.party') + TableHandler = backend.get('TableHandler') + cursor = Transaction().cursor + party = Party.__table__() + + super(PartyIdentifier, cls).__register__(module_name) + + party_h = TableHandler(cursor, Party, module_name) + if (party_h.column_exist('vat_number') + and party_h.column_exist('vat_country')): + identifiers = [] + cursor.execute(*party.select( + party.id, party.vat_number, party.vat_country, + where=(party.vat_number != Null) + & (party.vat_country != Null))) + for party_id, number, country in cursor.fetchall(): + code = (country or '') + (number or '') + type = None + if vat.is_valid(code): + type = 'eu_vat' + identifiers.append( + cls(party=party_id, code=code, type=type)) + cls.save(identifiers) + party_h.drop_column('vat_number') + party_h.drop_column('vat_country') + + @classmethod + def get_types(cls): + return [ + (None, ''), + ('eu_vat', 'VAT'), + ] + + @fields.depends('type', 'code') + def on_change_with_code(self): + if self.type == 'eu_vat': + try: + return vat.compact(self.code) + except stdnum.exceptions.ValidationError: + pass + return self.code + + @classmethod + def validate(cls, identifiers): + super(PartyIdentifier, cls).validate(identifiers) + for identifier in identifiers: + identifier.check_code() + + def check_code(self): + if self.type == 'eu_vat': + if not vat.is_valid(self.code): + self.raise_user_error('invalid_eu_vat', { + 'code': self.code, + 'party': self.party.rec_name, + }) class CheckVIESResult(ModelView): @@ -297,10 +299,6 @@ 'party.check_vies_result', [ Button('OK', 'end', 'tryton-ok', True), ]) - no_result = StateView('party.check_vies.no_result', - 'party.check_vies_no_result', [ - Button('OK', 'end', 'tryton-ok', True), - ]) @classmethod def __setup__(cls): @@ -313,32 +311,30 @@ def transition_check(self): Party = Pool().get('party.party') - if not HAS_VATNUMBER or not hasattr(vatnumber, 'check_vies'): - return 'no_result' - parties_succeed = [] parties_failed = [] parties = Party.browse(Transaction().context.get('active_ids')) for party in parties: - if not party.vat_code: - continue - try: - if not vatnumber.check_vies(party.vat_code): - parties_failed.append(party.id) - else: - parties_succeed.append(party.id) - except Exception, e: - if hasattr(e, 'faultstring') \ - and hasattr(e.faultstring, 'find'): - if e.faultstring.find('INVALID_INPUT'): + for identifier in party.identifiers: + if identifier.type != 'eu_vat': + continue + try: + if not vat.check_vies(identifier.code): parties_failed.append(party.id) - continue - if e.faultstring.find('SERVICE_UNAVAILABLE') \ - or e.faultstring.find('MS_UNAVAILABLE') \ - or e.faultstring.find('TIMEOUT') \ - or e.faultstring.find('SERVER_BUSY'): - self.raise_user_error('vies_unavailable') - raise + else: + parties_succeed.append(party.id) + except Exception, e: + if hasattr(e, 'faultstring') \ + and hasattr(e.faultstring, 'find'): + if e.faultstring.find('INVALID_INPUT'): + parties_failed.append(party.id) + continue + if e.faultstring.find('SERVICE_UNAVAILABLE') \ + or e.faultstring.find('MS_UNAVAILABLE') \ + or e.faultstring.find('TIMEOUT') \ + or e.faultstring.find('SERVER_BUSY'): + self.raise_user_error('vies_unavailable') + raise self.result.parties_succeed = parties_succeed self.result.parties_failed = parties_failed return 'result'
--- a/party.xml Mon Jul 13 23:58:26 2015 +0200 +++ b/party.xml Tue Jul 28 07:56:09 2015 +0200 @@ -105,6 +105,17 @@ <field name="code">party.party</field> </record> + <record model="ir.ui.view" id="identifier_form"> + <field name="model">party.identifier</field> + <field name="type">form</field> + <field name="name">identifier_form</field> + </record> + <record model="ir.ui.view" id="identifier_list"> + <field name="model">party.identifier</field> + <field name="type">tree</field> + <field name="name">identifier_list</field> + </record> + <record model="ir.action.wizard" id="wizard_check_vies"> <field name="name">Check VIES</field> <field name="wiz_name">party.check_vies</field> @@ -116,12 +127,6 @@ <field name="action" ref="wizard_check_vies"/> </record> - <record model="ir.ui.view" id="check_vies_no_result"> - <field name="model">party.check_vies.no_result</field> - <field name="type">form</field> - <field name="name">check_vies_no_result</field> - </record> - <record model="ir.ui.view" id="check_vies_result"> <field name="model">party.check_vies.result</field> <field name="type">form</field>
--- a/setup.py Mon Jul 13 23:58:26 2015 +0200 +++ b/setup.py Tue Jul 28 07:56:09 2015 +0200 @@ -96,7 +96,7 @@ license='GPL-3', install_requires=requires, extras_require={ - 'VAT': ['vatnumber', 'python-stdnum'], + 'VAT': ['python-stdnum'], }, zip_safe=False, entry_points="""
--- a/view/check_vies_no_result.xml Mon Jul 13 23:58:26 2015 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -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. --> -<form string="VAT Information Exchange System"> - <image name="tryton-dialog-information" xexpand="0" xfill="0"/> - <label string="You must have a recent version of "vatnumber" installed!" - id="vatnumber" - yalign="0.0" xalign="0.0" xexpand="1"/> -</form> -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/identifier_form.xml Tue Jul 28 07:56:09 2015 +0200 @@ -0,0 +1,12 @@ +<?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. --> +<form string="Party Identifier"> + <label name="party"/> + <field name="party"/> + <newline/> + <label name="type"/> + <field name="type"/> + <label name="code"/> + <field name="code"/> +</form>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/view/identifier_list.xml Tue Jul 28 07:56:09 2015 +0200 @@ -0,0 +1,8 @@ +<?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. --> +<tree string="Party Identifiers"> + <field name="party"/> + <field name="type"/> + <field name="code" expand="1"/> +</tree>
--- a/view/party_form.xml Mon Jul 13 23:58:26 2015 +0200 +++ b/view/party_form.xml Tue Jul 28 07:56:09 2015 +0200 @@ -24,12 +24,8 @@ <field name="categories" colspan="2" view_ids="party.category_view_list"/> </page> - <page string="Accounting" id="accounting"> - <separator string="VAT" colspan="4" id="vat"/> - <label name="vat_country"/> - <field name="vat_country" xexpand="0"/> - <label name="vat_number"/> - <field name="vat_number"/> + <page name="identifiers"> + <field name="identifiers" colspan="4"/> </page> </notebook> </form>
