389 lines
14 KiB
Python
389 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, Or, And
|
||
from trytond.i18n import gettext
|
||
from trytond.exceptions import UserError
|
||
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(ModelSQL,ModelView):
|
||
"Party Colombian"
|
||
|
||
__name__='party.party'
|
||
__metaclass__ = PoolMeta
|
||
|
||
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(ModelSQL, ModelView):
|
||
"Party Identifier Colombian"
|
||
|
||
__name__ = "party.identifier"
|
||
__metaclass__ = PoolMeta
|
||
|
||
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:
|
||
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 != False and self.type in tax_identifiers:
|
||
#generalizar multiples paises
|
||
#puede ser por el country del party de la company actual
|
||
try:
|
||
import stdnum.co.nit #la version debe ser mayor a la 1.2 o 1.3
|
||
except:
|
||
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_type_document():
|
||
return '13'
|
||
|
||
@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', select=True)
|
||
tax_level_code = fields.Many2One('party.tax_level_code', 'TaxLevelCode', select=True)
|
||
|
||
|
||
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
|
||
)
|
||
|
||
|
||
@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
|
||
|
||
@fields.depends('subdivision', 'subdivision_municipality')
|
||
def on_change_subdivision(self):
|
||
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
|
||
|
||
|
||
@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
|