420 lines
14 KiB
Python
420 lines
14 KiB
Python
|
from trytond.model import fields, ModelSQL, ModelView, Unique, sequence_ordered
|
|||
|
from trytond.pool import PoolMeta, Pool
|
|||
|
from trytond.pyson import Eval, Not, Equal, And
|
|||
|
from trytond.i18n import gettext
|
|||
|
from .exceptions import PartyIdentifierError
|
|||
|
|
|||
|
import logging
|
|||
|
|
|||
|
logger = logging.getLogger(__name__)
|
|||
|
|
|||
|
__all__ = [
|
|||
|
'PartyIdentifier',
|
|||
|
'Party',
|
|||
|
'ContactMechanism',
|
|||
|
'Address',
|
|||
|
'PartyTaxLevelCode'
|
|||
|
]
|
|||
|
|
|||
|
|
|||
|
TAX_CO_IDENTIFIERS = [
|
|||
|
(None, ''),
|
|||
|
('11', 'Registro Civil'),
|
|||
|
('12', 'Tarjeta de Identidad'),
|
|||
|
('13', 'Cedula de Ciudadania'),
|
|||
|
('21', 'Tarjeta de Extranjeria'),
|
|||
|
('22', 'Cedula de Extranjeria'),
|
|||
|
('31', 'NIT'),
|
|||
|
('41', 'Pasaporte'),
|
|||
|
('42', 'Tipo de Documento Extranjero'),
|
|||
|
('43', 'Documento de identificación extranjero'),
|
|||
|
('50', 'NIT de otro país'),
|
|||
|
('91', 'NUIP')
|
|||
|
]
|
|||
|
CO_CONTACT_MECHANISM_TYPES = [
|
|||
|
('sms', 'SMS')
|
|||
|
]
|
|||
|
|
|||
|
|
|||
|
class Party(metaclass=PoolMeta):
|
|||
|
"Party Colombian"
|
|||
|
__name__ = 'party.party'
|
|||
|
|
|||
|
first_name = fields.Char("Primer Nombre", states={
|
|||
|
'invisible': Not(Eval('type_person') == '2'), }
|
|||
|
)
|
|||
|
second_name = fields.Char("Segundo Nombre", states={
|
|||
|
'invisible': Not(Eval('type_person') == '2'), }
|
|||
|
)
|
|||
|
first_surname = fields.Char("Primer Apellido", states={
|
|||
|
'invisible': Not(Eval('type_person') == '2'), }
|
|||
|
)
|
|||
|
second_surname = fields.Char("Segundo Apellido", states={
|
|||
|
'invisible': Not(Eval('type_person') == '2'), },
|
|||
|
depends=['type_person'])
|
|||
|
birth_date = fields.Date('Fecha de nacimiento')
|
|||
|
tax_regime = fields.Selection([
|
|||
|
(None, ''),
|
|||
|
('48', 'Responsable del impuesto sobre las ventas –IVA'),
|
|||
|
('49', 'No responsable de IVA')
|
|||
|
], 'Tax Regime')
|
|||
|
type_taxpayer = fields.Selection([
|
|||
|
(None, ''),
|
|||
|
('regimen_simplificado', 'Regimen Simplificado'),
|
|||
|
('regimen_comun', 'Regimen Comun'),
|
|||
|
('gran_contribuyente', 'Gran Contribuyente'),
|
|||
|
('entidad_estatal', 'Entidad Estatal'),
|
|||
|
('domiciliado_extranjero', 'Domiciliado en Extranjero'),
|
|||
|
], 'Type Taxpayer')
|
|||
|
type_person = fields.Selection([
|
|||
|
(None, ''),
|
|||
|
('1', 'Persona Juridica'),
|
|||
|
('2', 'Persona Natural'),
|
|||
|
], 'Type Person')
|
|||
|
self_withholding = fields.Boolean('Self Withholding')
|
|||
|
declarant = fields.Boolean('Declarant')
|
|||
|
main_ciiu = fields.Char('Main Activity', size=4)
|
|||
|
secondary_ciiu = fields.Char('Secondary Activity', size=4)
|
|||
|
other_ciiu = fields.Char('Other Activity', size=4)
|
|||
|
age = fields.Function(fields.Integer('Edad'), 'on_change_with_age')
|
|||
|
type_tax_identifier = fields.Function(
|
|||
|
fields.Selection('get_types_identifier', 'Tipo de Identidad'),
|
|||
|
'on_change_with_type_tax_identifier')
|
|||
|
tax_level_code = fields.Many2Many(
|
|||
|
'party.party_tax_level_code', 'party', 'tax_level_code',
|
|||
|
"TaxLevelCode")
|
|||
|
|
|||
|
@classmethod
|
|||
|
def __setup__(cls):
|
|||
|
super(Party, cls).__setup__()
|
|||
|
cls.name.required = True
|
|||
|
|
|||
|
@staticmethod
|
|||
|
def default_type_person():
|
|||
|
return '2'
|
|||
|
|
|||
|
@classmethod
|
|||
|
def tax_identifier_types(cls):
|
|||
|
return ['12', '13', '31']
|
|||
|
|
|||
|
@classmethod
|
|||
|
def get_tax_level_code(cls):
|
|||
|
pool = Pool()
|
|||
|
TaxLevelCode = pool.get('party.tax_level_code')
|
|||
|
return TaxLevelCode.fields_get(['code'])['code']
|
|||
|
|
|||
|
@fields.depends('birth_date')
|
|||
|
def on_change_with_age(self, name=None):
|
|||
|
if self.birth_date:
|
|||
|
Date = Pool().get('ir.date')
|
|||
|
today = Date.today()
|
|||
|
age = today.year - self.birth_date.year
|
|||
|
age -= (today.month, today.day) < (
|
|||
|
self.birth_date.month, self.birth_date.day)
|
|||
|
return age
|
|||
|
|
|||
|
@fields.depends('name', 'first_name', 'second_name', 'first_surname',
|
|||
|
'second_surname', 'type_person')
|
|||
|
def on_change_type_person(self):
|
|||
|
if self.type_person == '2':
|
|||
|
self.on_change_name()
|
|||
|
if self.type_person == '1':
|
|||
|
pass
|
|||
|
|
|||
|
@fields.depends('name', 'first_name', 'second_name', 'first_surname',
|
|||
|
'second_surname', 'type_person')
|
|||
|
def on_change_name(self):
|
|||
|
self.rebuild_name()
|
|||
|
|
|||
|
@fields.depends('name', 'first_name', 'second_name', 'first_surname',
|
|||
|
'second_surname', 'type_person')
|
|||
|
def on_change_first_name(self):
|
|||
|
self.rebuild_name()
|
|||
|
|
|||
|
@fields.depends('name', 'first_name', 'second_name', 'first_surname',
|
|||
|
'second_surname', 'type_person')
|
|||
|
def on_change_second_name(self):
|
|||
|
self.rebuild_name()
|
|||
|
|
|||
|
@fields.depends('name', 'first_name', 'second_name', 'first_surname',
|
|||
|
'second_surname', 'type_person')
|
|||
|
def on_change_second_surname(self):
|
|||
|
self.rebuild_name()
|
|||
|
|
|||
|
@fields.depends('name', 'first_name', 'second_name', 'first_surname',
|
|||
|
'second_surname', 'type_person')
|
|||
|
def on_change_first_surname(self):
|
|||
|
self.rebuild_name()
|
|||
|
|
|||
|
def rebuild_name(self):
|
|||
|
"""reconstruye los campos relacionados con el nombre dependiendo de
|
|||
|
estos y del tipo de persona."""
|
|||
|
|
|||
|
if self.type_person == '2':
|
|||
|
if not (self.first_name or self.second_name
|
|||
|
or self.first_surname or self.second_surname):
|
|||
|
if self.name:
|
|||
|
self.first_name, self.second_name, self.first_surname, \
|
|||
|
self.second_surname = self.split_name(self.name)
|
|||
|
else:
|
|||
|
return
|
|||
|
else:
|
|||
|
self.name = self.get_full_name()
|
|||
|
if self.type_person == '1':
|
|||
|
if self.name:
|
|||
|
self.clean_split_name()
|
|||
|
else:
|
|||
|
self.name = self.get_full_name()
|
|||
|
|
|||
|
def clean_split_name(self):
|
|||
|
self.first_name = self.second_name = self.first_surname = self.second_surname = ''
|
|||
|
|
|||
|
@staticmethod
|
|||
|
def split_name(name):
|
|||
|
"""Divide un nombre completo en una lista de 4 elementos"""
|
|||
|
|
|||
|
second_surname = None
|
|||
|
first_surname = None
|
|||
|
second_name = None
|
|||
|
first_name = None
|
|||
|
names = name.split(' ')
|
|||
|
if len(names) > 0:
|
|||
|
first_name = names[0]
|
|||
|
if len(names) == 2:
|
|||
|
second_surname = None
|
|||
|
first_surname = names[1]
|
|||
|
elif len(names) == 3:
|
|||
|
first_surname = names[1]
|
|||
|
second_surname = names[2]
|
|||
|
elif len(names) >= 4:
|
|||
|
second_name = names[1]
|
|||
|
first_surname = names[2]
|
|||
|
second_surname = " ".join(names[3:])
|
|||
|
return [first_name, second_name, first_surname, second_surname]
|
|||
|
|
|||
|
@fields.depends('identifiers')
|
|||
|
def on_change_with_type_tax_identifier(self, name=None):
|
|||
|
types = self._tax_identifier_types()
|
|||
|
if self.identifiers:
|
|||
|
for identifier in self.identifiers:
|
|||
|
if identifier.type in types:
|
|||
|
return identifier.type
|
|||
|
|
|||
|
@fields.depends('identifiers')
|
|||
|
def on_change_with_tax_identifier(self, name=None):
|
|||
|
if self.identifiers:
|
|||
|
return self.get_tax_identifier(name)
|
|||
|
|
|||
|
@classmethod
|
|||
|
def _tax_identifier_types(cls):
|
|||
|
types = super(Party, cls).tax_identifier_types()
|
|||
|
types.extend(['31', '13'])
|
|||
|
return types
|
|||
|
|
|||
|
def get_full_name(self, name=None):
|
|||
|
if self.type_person == '2':
|
|||
|
name_list = [self.first_name, self.second_name,
|
|||
|
self.first_surname, self.second_surname]
|
|||
|
return " ".join([x for x in name_list if x])
|
|||
|
return self.name
|
|||
|
|
|||
|
@staticmethod
|
|||
|
def get_types_identifier():
|
|||
|
PartyIdentifier = Pool().get('party.identifier')
|
|||
|
return PartyIdentifier.type.selection
|
|||
|
|
|||
|
def pre_validate(self):
|
|||
|
super(Party, self).pre_validate()
|
|||
|
if not self.identifiers:
|
|||
|
raise PartyIdentifierError(gettext(
|
|||
|
'account_co_co.msg_not_identifier'))
|
|||
|
|
|||
|
|
|||
|
class PartyIdentifier(metaclass=PoolMeta):
|
|||
|
"Party Identifier Colombian"
|
|||
|
__name__ = "party.identifier"
|
|||
|
|
|||
|
expedition_city = fields.Many2One(
|
|||
|
"country.subdivision", 'Ciudad de Expedicion',
|
|||
|
help="Lugar donde fue expedido el documento de identidad",
|
|||
|
domain=[
|
|||
|
('parent', '!=', None)],
|
|||
|
states={'invisible': Not(Equal(Eval('type'), '13')), }
|
|||
|
)
|
|||
|
rut = fields.Boolean('Rut',
|
|||
|
states={
|
|||
|
'invisible': And(
|
|||
|
Not(Equal(Eval('type'), ('12'))),
|
|||
|
Not(Equal(Eval('type'), ('13'))),
|
|||
|
Not(Equal(Eval('type'), ('31')))),
|
|||
|
'readonly': Equal(Eval('type'), ('31'))})
|
|||
|
check_digit = fields.Function(
|
|||
|
fields.Integer('DV',
|
|||
|
help='Digito de Verificacion colombiano',
|
|||
|
states={'invisible': ~Eval('rut', False)}),
|
|||
|
'on_change_with_check_digit')
|
|||
|
|
|||
|
@classmethod
|
|||
|
def __setup__(cls):
|
|||
|
super(PartyIdentifier, cls).__setup__()
|
|||
|
cls.type.selection = TAX_CO_IDENTIFIERS
|
|||
|
cls.type.required = True
|
|||
|
table = cls.__table__()
|
|||
|
cls._sql_constraints += [
|
|||
|
('UniqueIdentifier', Unique(table, table.code, table.type),
|
|||
|
'La identificacion ya existe')
|
|||
|
]
|
|||
|
|
|||
|
@fields.depends('rut', 'type')
|
|||
|
def on_change_type(self):
|
|||
|
if self.type == '31':
|
|||
|
self.rut = True
|
|||
|
|
|||
|
@fields.depends('type', 'code')
|
|||
|
def on_change_with_check_digit(self, name=None):
|
|||
|
try:
|
|||
|
import stdnum.co.nit # la version debe ser mayor a la 1.2 o 1.3
|
|||
|
except BaseException:
|
|||
|
raise PartyIdentifierError(gettext(
|
|||
|
'account_co_co.msg_stdnum_error'))
|
|||
|
if self.type and self.code:
|
|||
|
if self.type in ('12', '13', '31') and self.code.isdigit():
|
|||
|
return int(stdnum.co.nit.calc_check_digit(self.code))
|
|||
|
|
|||
|
@fields.depends('type', 'party', 'code', 'rut')
|
|||
|
def check_code(self):
|
|||
|
|
|||
|
super(PartyIdentifier, self).check_code()
|
|||
|
tax_identifiers = ['12', '13', '31']
|
|||
|
|
|||
|
if self.type == '31':
|
|||
|
self.rut = True
|
|||
|
if self.rut is not False and self.type in tax_identifiers:
|
|||
|
# generalizar multiples paises
|
|||
|
# puede ser por el country del party de la company actual
|
|||
|
try:
|
|||
|
# la version debe ser mayor a la 1.2 o 1.3
|
|||
|
import stdnum.co.nit
|
|||
|
except BaseException:
|
|||
|
raise PartyIdentifierError(gettext(
|
|||
|
'account_co_co.msg_stdnum_error'))
|
|||
|
|
|||
|
if self.code.isdigit() is not True:
|
|||
|
raise PartyIdentifierError(gettext(
|
|||
|
'account_co_co.msg_invalid_vat_str'))
|
|||
|
|
|||
|
if not stdnum.co.nit.is_valid(self.code + str(self.check_digit)):
|
|||
|
if self.party and self.party.id > 0:
|
|||
|
party = self.party.rec_name
|
|||
|
else:
|
|||
|
party = ''
|
|||
|
raise PartyIdentifierError(
|
|||
|
gettext('account_co_co.msg_invalid_vat',
|
|||
|
code=self.code))
|
|||
|
|
|||
|
if self.type in tax_identifiers and not self.code.isdigit():
|
|||
|
if self.party and self.party.id > 0:
|
|||
|
party = self.party.rec_name
|
|||
|
else:
|
|||
|
party = ''
|
|||
|
raise PartyIdentifierError(
|
|||
|
gettext('account_co_co.msg_invalid_vat',
|
|||
|
code=self.code))
|
|||
|
|
|||
|
@staticmethod
|
|||
|
def default_rut():
|
|||
|
return False
|
|||
|
|
|||
|
def get_rec_name(self, name=None):
|
|||
|
if self.code:
|
|||
|
return self.code
|
|||
|
elif self.id:
|
|||
|
return self.id
|
|||
|
return ''
|
|||
|
|
|||
|
|
|||
|
class PartyTaxLevelCode(ModelSQL, ModelView):
|
|||
|
"Party Tax Level Code"
|
|||
|
__name__ = 'party.party_tax_level_code'
|
|||
|
|
|||
|
party = fields.Many2One('party.party', 'Party', )
|
|||
|
tax_level_code = fields.Many2One('party.tax_level_code', 'TaxLevelCode')
|
|||
|
|
|||
|
|
|||
|
class ContactMechanism(sequence_ordered(), ModelSQL, ModelView):
|
|||
|
"Contact Mechanism"
|
|||
|
__name__ = 'party.contact_mechanism'
|
|||
|
__metaclass__ = PoolMeta
|
|||
|
|
|||
|
@classmethod
|
|||
|
def __setup__(cls):
|
|||
|
super(ContactMechanism, cls).__setup__()
|
|||
|
cls.type.selection.extend(CO_CONTACT_MECHANISM_TYPES)
|
|||
|
|
|||
|
|
|||
|
class Address(ModelSQL, ModelView):
|
|||
|
"Address"
|
|||
|
|
|||
|
__metaclass__ = PoolMeta
|
|||
|
__name__ = 'party.address'
|
|||
|
|
|||
|
subdivision_municipality = fields.Many2One(
|
|||
|
"country.subdivision",
|
|||
|
'Municipality',
|
|||
|
domain=[
|
|||
|
('country', '=', Eval('country', -1)),
|
|||
|
('parent', '=', Eval('subdivision', -1)),
|
|||
|
('type', '=', 'municipality')
|
|||
|
],
|
|||
|
depends=['country', 'subdivision', 'city'],
|
|||
|
required=True
|
|||
|
)
|
|||
|
subdivision_localities = fields.Many2One(
|
|||
|
"country.subdivision",
|
|||
|
'Localities',
|
|||
|
domain=[
|
|||
|
('country', '=', Eval('country', -1)),
|
|||
|
('parent', '=', Eval('subdivision', -1)),
|
|||
|
('type', '=', 'localities'),
|
|||
|
],
|
|||
|
depends=['country', 'subdivision'],
|
|||
|
)
|
|||
|
|
|||
|
@fields.depends('subdivision', 'subdivision_municipality', 'country')
|
|||
|
def on_change_country(self):
|
|||
|
super(Address, self).on_change_country()
|
|||
|
if (self.subdivision_municipality
|
|||
|
and self.subdivision_municipality.parent != self.subdivision):
|
|||
|
self.subdivision_municipality = None
|
|||
|
self.subdivision_localities = None
|
|||
|
|
|||
|
@fields.depends('subdivision', 'subdivision_municipality',
|
|||
|
'subdivision_localities')
|
|||
|
def on_change_subdivision(self):
|
|||
|
pool = Pool()
|
|||
|
Subdivision = pool.get('country.subdivision')
|
|||
|
bogota = Subdivision.search(['code', '=', 'CO-11001'])
|
|||
|
|
|||
|
if hasattr(super(Address, self), 'on_change_subdivision'):
|
|||
|
super(Address, self).on_change_subdivision()
|
|||
|
if (self.subdivision_municipality
|
|||
|
and self.subdivision_municipality.parent != self.subdivision):
|
|||
|
self.subdivision_municipality = None
|
|||
|
self.subdivision_localities = None
|
|||
|
|
|||
|
if self.subdivision and self.subdivision.code == "CO-11":
|
|||
|
self.subdivision_municipality = bogota[0].id
|
|||
|
else:
|
|||
|
self.subdivision_localities = None
|
|||
|
|
|||
|
if self.subdivision_localities and self.subdivision.code != "CO-11":
|
|||
|
self.subdivision_localities = None
|
|||
|
|
|||
|
@fields.depends('city', 'subdivision_municipality')
|
|||
|
def on_change_subdivision_municipality(self):
|
|||
|
if self.subdivision_municipality:
|
|||
|
self.city = self.subdivision_municipality.name
|
|||
|
else:
|
|||
|
self.city = None
|
|||
|
self.subdivision_localities = None
|