forked from elmau/empresa-libre
3701 lines
115 KiB
Python
3701 lines
115 KiB
Python
#!/usr/bin/env python
|
|
|
|
import sqlite3
|
|
import click
|
|
from peewee import *
|
|
from playhouse.fields import PasswordField, ManyToManyField
|
|
from playhouse.shortcuts import case, SQL, cast
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import os, sys
|
|
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, parent_dir)
|
|
|
|
|
|
from controllers import util
|
|
from settings import log, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \
|
|
INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO
|
|
|
|
|
|
FORMAT = '{0:.2f}'
|
|
|
|
|
|
database_proxy = Proxy()
|
|
class BaseModel(Model):
|
|
class Meta:
|
|
database = database_proxy
|
|
|
|
|
|
def conectar(opt):
|
|
db = {
|
|
'sqlite': SqliteDatabase,
|
|
'postgres': PostgresqlDatabase,
|
|
'mysql': MySQLDatabase,
|
|
}
|
|
db_type = opt.pop('type')
|
|
db_name = opt.pop('name')
|
|
if not db_type in db:
|
|
log.error('Tipo de base de datos no soportado')
|
|
return False
|
|
#~ print ('DB NAME', db_name)
|
|
database = db[db_type](db_name, **opt)
|
|
try:
|
|
database_proxy.initialize(database)
|
|
database_proxy.connect()
|
|
log.info('Conectado a la BD...')
|
|
return True
|
|
except OperationalError as e:
|
|
log.error('Error al intentar conectar a la base de datos')
|
|
log.error(e)
|
|
return False
|
|
|
|
|
|
def desconectar():
|
|
if database_proxy.obj is None:
|
|
return
|
|
if not database_proxy.is_closed():
|
|
database_proxy.close()
|
|
log.info('Desconectado a la BD...')
|
|
return
|
|
|
|
|
|
def upload_file(rfc, opt, file_obj):
|
|
result = util.upload_file(rfc, opt, file_obj)
|
|
if result['ok']:
|
|
Configuracion.add({opt: file_obj.filename})
|
|
return result
|
|
|
|
|
|
def validar_timbrar():
|
|
try:
|
|
obj = Emisor.select()[0]
|
|
except IndexError:
|
|
msg = 'Es necesario agregar los datos del emisor'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
try:
|
|
obj = Folios.select()[0]
|
|
except IndexError:
|
|
msg = 'Es necesaria al menos una serie de folios'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
msg = 'Es necesario configurar un certificado de sellos'
|
|
try:
|
|
obj = Certificado.select()[0]
|
|
except IndexError:
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
if not obj.serie:
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
dias = obj.hasta - util.now()
|
|
if dias.days < 0:
|
|
msg = 'El certificado ha vencido, es necesario cargar uno nuevo'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
msg = ''
|
|
if dias.days < 15:
|
|
msg = 'El certificado vence en: {} días.'.format(dias.days)
|
|
|
|
return {'ok': True, 'msg': msg}
|
|
|
|
|
|
class Configuracion(BaseModel):
|
|
clave = TextField(unique=True)
|
|
valor = TextField(default='')
|
|
|
|
def __str__(self):
|
|
return '{} = {}'.format(self.clave, self.valor)
|
|
|
|
@classmethod
|
|
def get_(cls, keys):
|
|
if isinstance(keys, str):
|
|
data = (Configuracion
|
|
.select(Configuracion.valor)
|
|
.where(Configuracion.clave == keys)
|
|
)
|
|
if data:
|
|
return data[0].valor
|
|
return ''
|
|
|
|
if keys['fields'] == 'correo':
|
|
fields = ('correo_servidor', 'correo_puerto', 'correo_ssl',
|
|
'correo_usuario', 'correo_contra', 'correo_copia',
|
|
'correo_asunto', 'correo_mensaje', 'correo_directo')
|
|
data = (Configuracion
|
|
.select()
|
|
.where(Configuracion.clave.in_(fields))
|
|
)
|
|
elif keys['fields'] == 'path_cer':
|
|
fields = ('path_key', 'path_cer')
|
|
data = (Configuracion
|
|
.select()
|
|
.where(Configuracion.clave.in_(fields))
|
|
)
|
|
elif keys['fields'] == 'templates':
|
|
fields = (
|
|
'txt_plantilla_factura_32',
|
|
'txt_plantilla_factura_33',
|
|
'txt_plantilla_factura_33j')
|
|
data = (Configuracion
|
|
.select()
|
|
.where(Configuracion.clave.in_(fields))
|
|
)
|
|
|
|
values = {r.clave: r.valor for r in data}
|
|
return values
|
|
|
|
@classmethod
|
|
def add(cls, values):
|
|
try:
|
|
for k, v in values.items():
|
|
obj, created = Configuracion.get_or_create(clave=k)
|
|
obj.valor = v
|
|
obj.save()
|
|
return {'ok': True}
|
|
except Exception as e:
|
|
log.error(str(e))
|
|
return {'ok': False, 'msg': str(e)}
|
|
|
|
class Meta:
|
|
order_by = ('clave',)
|
|
indexes = (
|
|
(('clave', 'valor'), True),
|
|
)
|
|
|
|
|
|
class Tags(BaseModel):
|
|
tag = TextField(index=True, unique=True)
|
|
|
|
class Meta:
|
|
order_by = ('tag',)
|
|
|
|
|
|
class Usuarios(BaseModel):
|
|
usuario = TextField(unique=True)
|
|
nombre = TextField(default='')
|
|
apellidos = TextField(default='')
|
|
correo = TextField(default='')
|
|
contraseña = PasswordField()
|
|
es_superusuario = BooleanField(default=False)
|
|
es_admin = BooleanField(default=False)
|
|
es_activo = BooleanField(default=True)
|
|
fecha_ingreso = DateTimeField(default=util.now)
|
|
ultimo_ingreso = DateTimeField(null=True)
|
|
|
|
def __str__(self):
|
|
t = '{} {} ({})'
|
|
return t.format(self.nombre, self.apellidos, self.usuario)
|
|
|
|
class Meta:
|
|
order_by = ('nombre', 'apellidos')
|
|
|
|
|
|
class SATRegimenes(BaseModel):
|
|
key = TextField(index=True, unique=True)
|
|
name = TextField(index=True)
|
|
activo = BooleanField(default=False)
|
|
default = BooleanField(default=False)
|
|
fisica = BooleanField(default=False)
|
|
moral = BooleanField(default=False)
|
|
|
|
class Meta:
|
|
order_by = ('-default', 'name',)
|
|
indexes = (
|
|
(('key', 'name'), True),
|
|
)
|
|
|
|
def __str__(self):
|
|
return '{} ({})'.format(self.name, self.key)
|
|
|
|
@classmethod
|
|
def get_(cls, ids):
|
|
if isinstance(ids, int):
|
|
ids = [ids]
|
|
return SATRegimenes.select().where(SATRegimenes.id.in_(ids))
|
|
|
|
@classmethod
|
|
def get_activos(cls, rfc):
|
|
where = ((SATRegimenes.activo==True) & (SATRegimenes.fisica==True))
|
|
if (len(rfc) == 12):
|
|
where = ((SATRegimenes.activo==True) & (SATRegimenes.moral==True))
|
|
|
|
rows = (SATRegimenes
|
|
.select(
|
|
SATRegimenes.id,
|
|
SATRegimenes.name.alias('value'))
|
|
.where(where)
|
|
.dicts()
|
|
)
|
|
return tuple(rows)
|
|
|
|
|
|
class Emisor(BaseModel):
|
|
rfc = TextField(unique=True)
|
|
nombre = TextField(default='')
|
|
nombre_comercial = TextField(default='')
|
|
calle = TextField(default='')
|
|
no_exterior = TextField(default='')
|
|
no_interior = TextField(default='')
|
|
colonia = TextField(default='')
|
|
municipio = TextField(default='')
|
|
estado = TextField(default='')
|
|
pais = TextField(default='México')
|
|
codigo_postal = TextField(default='')
|
|
cp_expedicion = TextField(default='')
|
|
es_moral = BooleanField(default=False)
|
|
es_ong = BooleanField(default=False)
|
|
es_escuela = BooleanField(default=False)
|
|
autorizacion = TextField(default='')
|
|
fecha_autorizacion = DateField(null=True)
|
|
fecha_dof = DateField(null=True)
|
|
telefono = TextField(default='')
|
|
correo = TextField(default='')
|
|
web = TextField(default='')
|
|
curp = TextField(default='')
|
|
correo_timbrado = TextField(default='')
|
|
token_timbrado = TextField(default='')
|
|
token_soporte = TextField(default='')
|
|
logo = TextField(default='')
|
|
regimenes = ManyToManyField(SATRegimenes, related_name='emisores')
|
|
|
|
def __str__(self):
|
|
t = '{} ({})'
|
|
return t.format(self.nombre, self.rfc)
|
|
|
|
class Meta:
|
|
order_by = ('nombre',)
|
|
|
|
@classmethod
|
|
def get_(cls, rfc):
|
|
regimenes = SATRegimenes.get_activos(rfc)
|
|
row = {'regimenes': regimenes}
|
|
|
|
obj = Emisor.select().where(Emisor.rfc==rfc)
|
|
if bool(obj):
|
|
obj = obj[0]
|
|
row['emisor'] = {
|
|
'emisor_rfc': obj.rfc,
|
|
'emisor_nombre': obj.nombre,
|
|
'emisor_cp': obj.codigo_postal,
|
|
'emisor_cp2': obj.cp_expedicion,
|
|
'emisor_calle': obj.calle,
|
|
'emisor_no_exterior': obj.no_exterior,
|
|
'emisor_no_interior': obj.no_interior,
|
|
'emisor_colonia': obj.colonia,
|
|
'emisor_municipio': obj.municipio,
|
|
'emisor_estado': obj.estado,
|
|
'emisor_pais': obj.pais,
|
|
'emisor_logo': obj.logo,
|
|
'emisor_nombre_comercial': obj.nombre_comercial,
|
|
'emisor_telefono': obj.telefono,
|
|
'emisor_correo': obj.correo,
|
|
'emisor_web': obj.web,
|
|
'es_escuela': obj.es_escuela,
|
|
'es_ong': obj.es_ong,
|
|
'ong_autorizacion': obj.autorizacion,
|
|
'ong_fecha': obj.fecha_autorizacion,
|
|
'ong_fecha_dof': obj.fecha_dof,
|
|
'correo_timbrado': obj.correo_timbrado,
|
|
'token_timbrado': obj.token_timbrado,
|
|
'token_soporte': obj.token_soporte,
|
|
'regimenes': [row.id for row in obj.regimenes]
|
|
}
|
|
else:
|
|
row['emisor'] = {'emisor_rfc': rfc}
|
|
|
|
return {'ok': True, 'row': row}
|
|
|
|
@classmethod
|
|
def get_auth(cls):
|
|
try:
|
|
obj = Emisor.select()[0]
|
|
data = {
|
|
'RFC': obj.rfc,
|
|
'USER': obj.correo_timbrado,
|
|
'PASS': obj.token_timbrado,
|
|
}
|
|
return data
|
|
except:
|
|
return {}
|
|
|
|
@classmethod
|
|
def get_regimenes(cls):
|
|
try:
|
|
obj = Emisor.select()[0]
|
|
except IndexError:
|
|
return ()
|
|
|
|
rows = [{'id': row.key, 'value': row.name} for row in obj.regimenes]
|
|
return tuple(rows)
|
|
|
|
def _clean(self, values):
|
|
fields = util.clean(values)
|
|
fields['rfc'] = fields.pop('emisor_rfc')
|
|
fields['nombre'] = fields.pop('emisor_nombre')
|
|
fields['codigo_postal'] = fields.pop('emisor_cp')
|
|
fields['cp_expedicion'] = fields.pop('emisor_cp2', '') or fields['codigo_postal']
|
|
fields['calle'] = fields.pop('emisor_calle', '')
|
|
fields['no_exterior'] = fields.pop('emisor_no_exterior', '')
|
|
fields['no_interior'] = fields.pop('emisor_no_interior', '')
|
|
fields['colonia'] = fields.pop('emisor_colonia', '')
|
|
fields['municipio'] = fields.pop('emisor_municipio', '')
|
|
fields['estado'] = fields.pop('emisor_estado', '')
|
|
fields['pais'] = fields.pop('emisor_pais', 'México')
|
|
fields['logo'] = fields.pop('emisor_logo', '')
|
|
fields['nombre_comercial'] = fields.pop('emisor_nombre_comercial', '')
|
|
fields['telefono'] = fields.pop('emisor_telefono', '')
|
|
fields['correo'] = fields.pop('emisor_correo', '')
|
|
fields['web'] = fields.pop('emisor_web', '')
|
|
fields['es_escuela'] = bool(fields['es_escuela'].replace('0', ''))
|
|
fields['es_ong'] = bool(fields['es_ong'].replace('0', ''))
|
|
fields['autorizacion'] = fields.pop('ong_autorizacion', '')
|
|
fields['fecha_autorizacion'] = fields.pop('ong_fecha', None)
|
|
fields['fecha_dof'] = fields.pop('ong_fecha_dof', None)
|
|
if len(fields['rfc']) == 12:
|
|
fields['es_moral'] = True
|
|
fields['regimenes'] = SATRegimenes.get_(
|
|
util.loads(fields['regimenes']))
|
|
return fields
|
|
|
|
@classmethod
|
|
def add(cls, values):
|
|
fields = cls._clean(cls, values)
|
|
obj, created = Emisor.get_or_create(rfc=fields['rfc'])
|
|
obj.regimenes = fields.pop('regimenes')
|
|
q = Emisor.update(**fields).where(Emisor.id==obj.id)
|
|
return {'ok': bool(q.execute())}
|
|
|
|
|
|
class Certificado(BaseModel):
|
|
key = BlobField(null=True)
|
|
key_enc = TextField(default='')
|
|
cer = BlobField(null=True)
|
|
cer_pem = TextField(default='')
|
|
cer_txt = TextField(default='')
|
|
p12 = BlobField(null=True)
|
|
serie = TextField(default='')
|
|
rfc = TextField(default='')
|
|
desde = DateTimeField(null=True)
|
|
hasta = DateTimeField(null=True)
|
|
|
|
def __str__(self):
|
|
return self.serie
|
|
|
|
@classmethod
|
|
def get_data(cls):
|
|
obj = cls.get_(cls)
|
|
row = {
|
|
'cert_rfc': obj.rfc,
|
|
'cert_serie': obj.serie,
|
|
'cert_desde': obj.desde,
|
|
'cert_hasta': obj.hasta,
|
|
}
|
|
return row
|
|
|
|
def get_(cls):
|
|
return Certificado.select()[0]
|
|
|
|
@classmethod
|
|
def add(cls, file_obj):
|
|
if file_obj.filename.endswith('key'):
|
|
path_key = util.save_temp(file_obj.file.read())
|
|
Configuracion.add({'path_key': path_key})
|
|
elif file_obj.filename.endswith('cer'):
|
|
path_cer = util.save_temp(file_obj.file.read())
|
|
Configuracion.add({'path_cer': path_cer})
|
|
return {'status': 'server'}
|
|
|
|
@classmethod
|
|
def validate(cls, values, session):
|
|
row = {}
|
|
result = False
|
|
|
|
obj = cls.get_(cls)
|
|
paths = Configuracion.get_({'fields': 'path_cer'})
|
|
cert = util.Certificado(paths)
|
|
data = cert.validate(values['contra'], session['rfc'])
|
|
if data:
|
|
msg = 'Certificado guardado correctamente'
|
|
q = Certificado.update(**data).where(Certificado.id==obj.id)
|
|
if q.execute():
|
|
result = True
|
|
row = {
|
|
'cert_rfc': data['rfc'],
|
|
'cert_serie': data['serie'],
|
|
'cert_desde': data['desde'],
|
|
'cert_hasta': data['hasta'],
|
|
}
|
|
else:
|
|
msg = cert.error
|
|
|
|
Configuracion.add({'path_key': ''})
|
|
Configuracion.add({'path_cer': ''})
|
|
|
|
return {'ok': result, 'msg': msg, 'data': row}
|
|
|
|
|
|
|
|
class Folios(BaseModel):
|
|
serie = TextField(unique=True)
|
|
inicio = IntegerField(default=1)
|
|
default = BooleanField(default=False)
|
|
usarcon = TextField(default='')
|
|
|
|
class Meta:
|
|
order_by = ('-default', 'serie', 'inicio')
|
|
indexes = (
|
|
(('serie', 'inicio'), True),
|
|
)
|
|
|
|
@classmethod
|
|
def get_all(cls):
|
|
rows = (Folios
|
|
.select(
|
|
Folios.id,
|
|
Folios.serie.alias('value'),
|
|
Folios.usarcon,
|
|
)
|
|
.dicts()
|
|
)
|
|
return tuple(rows)
|
|
|
|
@classmethod
|
|
def get_(cls):
|
|
rows = (Folios
|
|
.select(
|
|
Folios.id,
|
|
SQL(" '-' AS delete"),
|
|
Folios.serie,
|
|
Folios.inicio,
|
|
case(Folios.usarcon, (
|
|
('I', 'Ingreso'),
|
|
('E', 'Egreso'),
|
|
('T', 'Traslado'),
|
|
), 'Todos').alias('usarcon'),
|
|
case(Folios.default, (
|
|
(True, 'Si'),
|
|
(False, 'No'),
|
|
)).alias('pre')
|
|
)
|
|
.dicts()
|
|
)
|
|
return tuple(rows)
|
|
|
|
@classmethod
|
|
def add(cls, values):
|
|
uc = {
|
|
'': 'Todos',
|
|
'I': 'Ingreso',
|
|
'E': 'Egreso',
|
|
'T': 'Traslado',
|
|
}
|
|
pre = {
|
|
True: 'Si',
|
|
False: 'No',
|
|
}
|
|
|
|
if 'default' in values:
|
|
values['default'] = True
|
|
try:
|
|
obj = Folios.create(**values)
|
|
except IntegrityError:
|
|
msg = 'Serie ya existe'
|
|
return {'ok': False, 'msg': msg}
|
|
row = {
|
|
'id': obj.id,
|
|
'delete' : '-',
|
|
'serie' : obj.serie,
|
|
'inicio' : obj.inicio,
|
|
'usarcon' : uc[obj.usarcon],
|
|
'pre' : pre[obj.default],
|
|
}
|
|
return {'ok': True, 'row': row}
|
|
|
|
@classmethod
|
|
def remove(cls, id):
|
|
q = Folios.delete().where(Folios.id==id)
|
|
return bool(q.execute())
|
|
|
|
|
|
class Categorias(BaseModel):
|
|
categoria = TextField()
|
|
padre = ForeignKeyField('self', null=True, related_name='hijos')
|
|
|
|
class Meta:
|
|
order_by = ('categoria',)
|
|
indexes = (
|
|
(('categoria', 'padre'), True),
|
|
)
|
|
|
|
@classmethod
|
|
def exists(cls, filters):
|
|
return Categorias.select().where(filters).exists()
|
|
|
|
@classmethod
|
|
def get_all(cls):
|
|
rows = (Categorias.select(
|
|
Categorias.id,
|
|
Categorias.categoria.alias('value'),
|
|
Categorias.padre.alias('parent_id'))
|
|
).dicts()
|
|
for row in rows:
|
|
if row['parent_id'] is None:
|
|
row['parent_id'] = 0
|
|
return tuple(rows)
|
|
|
|
|
|
class CondicionesPago(BaseModel):
|
|
condicion = TextField(unique=True)
|
|
|
|
class Meta:
|
|
order_by = ('condicion',)
|
|
|
|
def __str__(self):
|
|
return self.condicion
|
|
|
|
@classmethod
|
|
def get_(cls):
|
|
q = CondicionesPago.select(CondicionesPago.condicion).tuples()
|
|
data = [r[0] for r in q]
|
|
return data
|
|
|
|
@classmethod
|
|
def get_or(cls, value):
|
|
if value is None:
|
|
return value
|
|
obj, _ = CondicionesPago.get_or_create(condicion=value)
|
|
return obj
|
|
|
|
|
|
class SATUnidades(BaseModel):
|
|
key = TextField(unique=True, index=True)
|
|
name = TextField(default='', index=True)
|
|
activo = BooleanField(default=False)
|
|
default = BooleanField(default=False)
|
|
|
|
class Meta:
|
|
order_by = ('-default', 'name')
|
|
indexes = (
|
|
(('key', 'name'), True),
|
|
)
|
|
|
|
def __str__(self):
|
|
return '{} ({})'.format(self.name, self.key)
|
|
|
|
@classmethod
|
|
def get_(self):
|
|
rows = SATUnidades.select().dicts()
|
|
return tuple(rows)
|
|
|
|
@classmethod
|
|
def add(self, values):
|
|
try:
|
|
SATUnidades.create(**values)
|
|
return {'ok': True}
|
|
except:
|
|
return {'ok': False}
|
|
|
|
@classmethod
|
|
def actualizar(self, values):
|
|
id = int(values['id'])
|
|
if values['field'] == 'activo':
|
|
v = {'0': False, '1': True}
|
|
q = (SATUnidades
|
|
.update(**{'activo': v[values['value']]})
|
|
.where(SATUnidades.id==id))
|
|
result = bool(q.execute())
|
|
elif values['field'] == 'default':
|
|
q = SATUnidades.update(**{'default': False})
|
|
q.execute()
|
|
|
|
v = {'false': False, 'true': True}
|
|
q = (SATUnidades
|
|
.update(**{'default': v[values['value']]})
|
|
.where(SATUnidades.id==id))
|
|
result = bool(q.execute())
|
|
|
|
return {'ok': result}
|
|
|
|
@classmethod
|
|
def get_activos(cls):
|
|
rows = (SATUnidades
|
|
.select(
|
|
SATUnidades.id,
|
|
SATUnidades.name.alias('value'))
|
|
.where(SATUnidades.activo==True)
|
|
.dicts()
|
|
)
|
|
return tuple(rows)
|
|
|
|
|
|
class SATFormaPago(BaseModel):
|
|
key = TextField(unique=True, index=True)
|
|
name = TextField(default='', index=True)
|
|
activo = BooleanField(default=False)
|
|
default = BooleanField(default=False)
|
|
|
|
class Meta:
|
|
order_by = ('-default', 'name',)
|
|
indexes = (
|
|
(('key', 'name'), True),
|
|
)
|
|
|
|
def __str__(self):
|
|
return 'Forma de pago: ({}) {}'.format(self.key, self.name)
|
|
|
|
@classmethod
|
|
def get_by_key(cls, key):
|
|
return SATFormaPago.get(SATFormaPago.key==key)
|
|
|
|
@classmethod
|
|
def get_activos(cls, values):
|
|
field = SATFormaPago.id
|
|
if values:
|
|
field = SATFormaPago.key.alias('id')
|
|
rows = (SATFormaPago
|
|
.select(field, SATFormaPago.name.alias('value'))
|
|
.where(SATFormaPago.activo==True)
|
|
.dicts()
|
|
)
|
|
return tuple(rows)
|
|
|
|
|
|
class SATAduanas(BaseModel):
|
|
key = TextField(unique=True, index=True)
|
|
name = TextField(default='', index=True)
|
|
activo = BooleanField(default=False)
|
|
default = BooleanField(default=False)
|
|
|
|
class Meta:
|
|
order_by = ('-default', 'name',)
|
|
indexes = (
|
|
(('key', 'name'), True),
|
|
)
|
|
|
|
|
|
class SATMonedas(BaseModel):
|
|
key = TextField(unique=True, index=True)
|
|
name = TextField(default='', index=True)
|
|
activo = BooleanField(default=False)
|
|
default = BooleanField(default=False)
|
|
|
|
class Meta:
|
|
order_by = ('-default', 'name',)
|
|
indexes = (
|
|
(('key', 'name'), True),
|
|
)
|
|
|
|
def __str__(self):
|
|
return 'Moneda: ({}) {}'.format(self.key, self.name)
|
|
|
|
@classmethod
|
|
def get_(self):
|
|
rows = SATMonedas.select().dicts()
|
|
return tuple(rows)
|
|
|
|
@classmethod
|
|
def get_activos(cls):
|
|
rows = (SATMonedas
|
|
.select(
|
|
SATMonedas.key.alias('id'),
|
|
SATMonedas.name.alias('value'))
|
|
.where(SATMonedas.activo==True)
|
|
.dicts()
|
|
)
|
|
return tuple(rows)
|
|
|
|
@classmethod
|
|
def get_activos_by_id(cls):
|
|
rows = (SATMonedas
|
|
.select(
|
|
SATMonedas.id,
|
|
SATMonedas.name.alias('value'))
|
|
.where(SATMonedas.activo==True)
|
|
.dicts()
|
|
)
|
|
return tuple(rows)
|
|
|
|
@classmethod
|
|
def actualizar(self, values):
|
|
id = int(values['id'])
|
|
if values['field'] == 'activo':
|
|
v = {'0': False, '1': True}
|
|
q = (SATMonedas
|
|
.update(**{'activo': v[values['value']]})
|
|
.where(SATMonedas.id==id))
|
|
result = bool(q.execute())
|
|
elif values['field'] == 'default':
|
|
q = SATMonedas.update(**{'default': False})
|
|
q.execute()
|
|
|
|
v = {'false': False, 'true': True}
|
|
q = (SATMonedas
|
|
.update(**{'default': v[values['value']]})
|
|
.where(SATMonedas.id==id))
|
|
result = bool(q.execute())
|
|
|
|
return {'ok': result}
|
|
|
|
|
|
class SATImpuestos(BaseModel):
|
|
key = TextField(index=True)
|
|
name = TextField(default='', index=True)
|
|
factor = TextField(default='T')
|
|
tipo = TextField(default='T')
|
|
tasa = DecimalField(default=0.0, decimal_places=6, auto_round=True)
|
|
activo = BooleanField(default=False)
|
|
default = BooleanField(default=False)
|
|
|
|
class Meta:
|
|
order_by = ('-default', 'name',)
|
|
indexes = (
|
|
(('key', 'factor', 'tipo', 'tasa'), True),
|
|
)
|
|
|
|
@classmethod
|
|
def get_o_crea(self, values):
|
|
obj, _ = SATImpuestos.get_or_create(**values)
|
|
return obj
|
|
|
|
@classmethod
|
|
def add(self, values):
|
|
tasa = float(values['tasa'])
|
|
tipo = 'T'
|
|
if tasa < 0:
|
|
tipo = 'R'
|
|
|
|
row = {
|
|
'key': IMPUESTOS.get(values['impuesto']),
|
|
'name': values['impuesto'],
|
|
'tipo': tipo,
|
|
'tasa': abs(tasa),
|
|
}
|
|
|
|
try:
|
|
obj = SATImpuestos.create(**row)
|
|
row['id'] = obj.id
|
|
row['delete'] = '-'
|
|
return {'ok': True, 'row': row}
|
|
except IntegrityError:
|
|
return {'ok': False, 'msg': 'El impuesto ya existe'}
|
|
|
|
@classmethod
|
|
def remove(cls, id):
|
|
with database_proxy.transaction():
|
|
q = SATImpuestos.delete().where(SATImpuestos.id==id)
|
|
return bool(q.execute())
|
|
|
|
@classmethod
|
|
def get_(self):
|
|
rows = (SATImpuestos.select(
|
|
SATImpuestos.id,
|
|
SQL(" '-' AS delete"),
|
|
SATImpuestos.name,
|
|
SATImpuestos.tipo,
|
|
SATImpuestos.tasa,
|
|
SATImpuestos.activo,
|
|
SATImpuestos.default)
|
|
.dicts()
|
|
)
|
|
return tuple(rows)
|
|
|
|
@classmethod
|
|
def get_activos(self):
|
|
rows = SATImpuestos.select().where(SATImpuestos.activo==True).dicts()
|
|
return tuple(rows)
|
|
|
|
@classmethod
|
|
def actualizar(self, values):
|
|
id = int(values['id'])
|
|
if values['field'] == 'activo':
|
|
v = {'0': False, '1': True}
|
|
q = (SATImpuestos
|
|
.update(**{'activo': v[values['value']]})
|
|
.where(SATImpuestos.id==id))
|
|
result = bool(q.execute())
|
|
elif values['field'] == 'default':
|
|
q = SATImpuestos.update(**{'default': False})
|
|
q.execute()
|
|
|
|
v = {'false': False, 'true': True}
|
|
q = (SATImpuestos
|
|
.update(**{'default': v[values['value']]})
|
|
.where(SATImpuestos.id==id))
|
|
result = bool(q.execute())
|
|
|
|
return {'ok': result}
|
|
|
|
|
|
class SATTipoRelacion(BaseModel):
|
|
key = TextField(index=True, unique=True)
|
|
name = TextField(default='', index=True)
|
|
activo = BooleanField(default=False)
|
|
default = BooleanField(default=False)
|
|
|
|
class Meta:
|
|
order_by = ('-default', 'name',)
|
|
indexes = (
|
|
(('key', 'name'), True),
|
|
)
|
|
|
|
def __str__(self):
|
|
return 'Tipo de relación: ({}) {}'.format(self.key, self.name)
|
|
|
|
@classmethod
|
|
def get_activos(cls, values):
|
|
field = SATTipoRelacion.id
|
|
if values:
|
|
field = SATTipoRelacion.key.alias('id')
|
|
rows = (SATTipoRelacion
|
|
.select(field, SATTipoRelacion.name.alias('value'))
|
|
.where(SATTipoRelacion.activo==True)
|
|
.dicts()
|
|
)
|
|
return ({'id': '-', 'value': ''},) + tuple(rows)
|
|
|
|
|
|
|
|
class SATBancos(BaseModel):
|
|
key = TextField(index=True, unique=True)
|
|
name = TextField(index=True)
|
|
razon_social = TextField(default='')
|
|
rfc = TextField(default='')
|
|
activo = BooleanField(default=False)
|
|
|
|
class Meta:
|
|
order_by = ('-activo', 'name')
|
|
indexes = (
|
|
(('key', 'name'), True),
|
|
)
|
|
|
|
def __str__(self):
|
|
return 'Banco: {} ({})'.format(self.name, self.key)
|
|
|
|
@classmethod
|
|
def get_(cls):
|
|
rows = SATBancos.select().dicts()
|
|
return tuple(rows)
|
|
|
|
@classmethod
|
|
def get_activos_by_id(cls):
|
|
rows = (SATBancos
|
|
.select(
|
|
SATBancos.id,
|
|
SATBancos.name.alias('value'))
|
|
.where(SATBancos.activo==True)
|
|
.dicts()
|
|
)
|
|
return tuple(rows)
|
|
|
|
@classmethod
|
|
def actualizar(cls, values):
|
|
id = int(values['id'])
|
|
if values['field'] == 'activo':
|
|
v = {'0': False, '1': True}
|
|
q = (SATBancos
|
|
.update(**{'activo': v[values['value']]})
|
|
.where(SATBancos.id==id))
|
|
result = bool(q.execute())
|
|
return {'ok': result}
|
|
|
|
|
|
class CuentasBanco(BaseModel):
|
|
de_emisor = BooleanField(default=False)
|
|
activa = BooleanField(default=True)
|
|
nombre = TextField()
|
|
banco = ForeignKeyField(SATBancos)
|
|
fecha_apertura = DateField(null=True)
|
|
cuenta = TextField(default='')
|
|
clabe = TextField(default='')
|
|
moneda = ForeignKeyField(SATMonedas)
|
|
saldo_inicial = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
|
|
class Meta:
|
|
order_by = ('nombre',)
|
|
indexes = (
|
|
(('banco', 'cuenta'), True),
|
|
)
|
|
|
|
def __str__(self):
|
|
return '{} ({})'.format(self.banco.name, self.cuenta[-4:])
|
|
|
|
@classmethod
|
|
def remove(cls, id):
|
|
try:
|
|
with database_proxy.atomic() as txn:
|
|
q = MovimientosBanco.delete().where(MovimientosBanco.cuenta==id)
|
|
q.execute()
|
|
q = CuentasBanco.delete().where(CuentasBanco.id==id)
|
|
q.execute()
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
@classmethod
|
|
def get_years(cls):
|
|
data = [{'id': -1, 'value': 'Todos'}]
|
|
year1 = (CuentasBanco
|
|
.select(fn.Min(CuentasBanco.fecha_apertura.year))
|
|
.where(CuentasBanco.de_emisor==True, CuentasBanco.activa==True)
|
|
.group_by(CuentasBanco.fecha_apertura.year)
|
|
.order_by(CuentasBanco.fecha_apertura.year)
|
|
.scalar()
|
|
)
|
|
|
|
if year1:
|
|
year2 = util.now().year + 1
|
|
data += [{'id': y, 'value': y} for y in range(int(year1), year2)]
|
|
|
|
return data
|
|
|
|
@classmethod
|
|
def get_(cls, values):
|
|
if values['tipo'] == '1':
|
|
rows = (CuentasBanco
|
|
.select()
|
|
.where(CuentasBanco.de_emisor==True, CuentasBanco.activa==True)
|
|
)
|
|
if not (len(rows)):
|
|
return {'ok': False}
|
|
|
|
first = rows[0]
|
|
rows = [{'id': r.id, 'value': '{} ({})'.format(
|
|
r.banco.name, r.cuenta[-4:])} for r in rows]
|
|
data = {
|
|
'ok': True,
|
|
'rows': tuple(rows),
|
|
'moneda': first.moneda.name,
|
|
'saldo': first.saldo,
|
|
}
|
|
return data
|
|
|
|
return
|
|
|
|
@classmethod
|
|
def emisor(cls):
|
|
rows = (CuentasBanco
|
|
.select(
|
|
CuentasBanco.id,
|
|
CuentasBanco.activa,
|
|
CuentasBanco.nombre,
|
|
SATBancos.name.alias('banco'),
|
|
CuentasBanco.fecha_apertura,
|
|
CuentasBanco.cuenta,
|
|
CuentasBanco.clabe,
|
|
SATMonedas.name.alias('moneda'),
|
|
CuentasBanco.saldo
|
|
)
|
|
.join(SATBancos).switch(CuentasBanco)
|
|
.join(SATMonedas).switch(CuentasBanco)
|
|
.where(CuentasBanco.de_emisor==True).dicts()
|
|
)
|
|
return tuple(rows)
|
|
|
|
@classmethod
|
|
def add(cls, values):
|
|
w = '37137137137137137'
|
|
dv = str(
|
|
(10 -
|
|
sum([(int(v) * int(values['clabe'][i])) % 10 for i, v in enumerate(w)])
|
|
% 10) % 10)
|
|
if dv != values['clabe'][-1]:
|
|
msg = 'Digito de control de la CLABE es incorrecto'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
fecha_deposito = values.pop('fecha_deposito', None)
|
|
|
|
with database_proxy.transaction():
|
|
try:
|
|
obj = CuentasBanco.create(**values)
|
|
except IntegrityError:
|
|
msg = 'Esta cuenta ya existe'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
nuevo_mov= {
|
|
'cuenta': obj.id,
|
|
'fecha': fecha_deposito,
|
|
'movimiento': 1,
|
|
'descripcion': 'Saldo inicial',
|
|
'forma_pago': SATFormaPago.get_by_key('99'),
|
|
'deposito': values['saldo'],
|
|
'saldo': values['saldo'],
|
|
}
|
|
MovimientosBanco.add(nuevo_mov)
|
|
|
|
rows = (CuentasBanco
|
|
.select(
|
|
CuentasBanco.id,
|
|
CuentasBanco.activa,
|
|
CuentasBanco.nombre,
|
|
SATBancos.name.alias('banco'),
|
|
CuentasBanco.fecha_apertura,
|
|
CuentasBanco.cuenta,
|
|
CuentasBanco.clabe,
|
|
SATMonedas.name.alias('moneda'),
|
|
CuentasBanco.saldo
|
|
)
|
|
.join(SATBancos).switch(CuentasBanco)
|
|
.join(SATMonedas).switch(CuentasBanco)
|
|
.where(CuentasBanco.id==obj.id).dicts()
|
|
)
|
|
data = {'ok': True, 'row': rows[0]}
|
|
return data
|
|
|
|
|
|
class MovimientosBanco(BaseModel):
|
|
cuenta = ForeignKeyField(CuentasBanco)
|
|
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
|
|
movimiento = IntegerField(default=0)
|
|
descripcion = TextField(default='')
|
|
forma_pago = ForeignKeyField(SATFormaPago)
|
|
retiro = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
deposito = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
moneda = TextField(default='MXN') # Complemento de pagos
|
|
tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6,
|
|
auto_round=True)
|
|
numero_operacion = TextField(default='')
|
|
origen_rfc = TextField(default='')
|
|
origen_nombre = TextField(default='')
|
|
origen_cuenta = TextField(default='')
|
|
destino_rfc = TextField(default='')
|
|
destino_cuenta = TextField(default='')
|
|
tipo_cadena_pago = TextField(default='')
|
|
certificado_pago = TextField(default='')
|
|
cadena_pago = TextField(default='')
|
|
sello_pago = TextField(default='')
|
|
|
|
class Meta:
|
|
order_by = ('fecha',)
|
|
indexes = (
|
|
(('cuenta', 'movimiento'), True),
|
|
)
|
|
|
|
@classmethod
|
|
def add(cls, values):
|
|
with database_proxy.transaction():
|
|
try:
|
|
obj = MovimientosBanco.create(**values)
|
|
except IntegrityError:
|
|
msg = 'Este movimiento ya existe'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
return {'ok': True}
|
|
|
|
@classmethod
|
|
def con(cls, id):
|
|
cant = (MovimientosBanco
|
|
.select(MovimientosBanco.id)
|
|
.where(MovimientosBanco.cuenta==id)
|
|
.count()
|
|
)
|
|
if cant > 2:
|
|
return {'ok': True}
|
|
|
|
return {'ok': False}
|
|
|
|
|
|
@classmethod
|
|
def get_(cls, values):
|
|
cuenta = int(values['cuenta'])
|
|
if 'fechas' in values:
|
|
rango = values['fechas']
|
|
fd = (MovimientosBanco.fecha.between(
|
|
util.get_date(rango['start']),
|
|
util.get_date(rango['end'], True)))
|
|
filtros = (fd & (MovimientosBanco.cuenta==cuenta))
|
|
else:
|
|
year = int(values['year'])
|
|
mes = int(values['mes'])
|
|
if year == -1:
|
|
fy = (MovimientosBanco.fecha.year > 0)
|
|
else:
|
|
fy = (MovimientosBanco.fecha.year == year)
|
|
if mes == -1:
|
|
fm = (MovimientosBanco.fecha.month > 0)
|
|
else:
|
|
fm = (MovimientosBanco.fecha.month == mes)
|
|
filtros = (fy & fm & (MovimientosBanco.cuenta==cuenta))
|
|
|
|
rows = tuple(MovimientosBanco
|
|
.select(
|
|
MovimientosBanco.id,
|
|
MovimientosBanco.fecha,
|
|
MovimientosBanco.numero_operacion,
|
|
MovimientosBanco.descripcion,
|
|
MovimientosBanco.retiro,
|
|
MovimientosBanco.deposito,
|
|
MovimientosBanco.saldo)
|
|
.where(filtros)
|
|
.dicts()
|
|
)
|
|
|
|
return {'ok': True, 'rows': rows}
|
|
|
|
|
|
class CfdiPagos(BaseModel):
|
|
movimiento = ForeignKeyField(MovimientosBanco)
|
|
xml = TextField(default='')
|
|
uuid = UUIDField(null=True)
|
|
estatus = TextField(default='Guardado')
|
|
estatus_sat = TextField(default='')
|
|
notas = TextField(default='')
|
|
cancelado = BooleanField(default=False)
|
|
|
|
class Meta:
|
|
order_by = ('movimiento',)
|
|
|
|
|
|
class SATUsoCfdi(BaseModel):
|
|
key = TextField(index=True, unique=True)
|
|
name = TextField(default='', index=True)
|
|
activo = BooleanField(default=False)
|
|
default = BooleanField(default=False)
|
|
fisica = BooleanField(default=True)
|
|
moral = BooleanField(default=False)
|
|
|
|
class Meta:
|
|
order_by = ('-default', 'name',)
|
|
indexes = (
|
|
(('key', 'name'), True),
|
|
)
|
|
|
|
def __str__(self):
|
|
return 'Uso del CFDI: {} ({})'.format(self.name, self.key)
|
|
|
|
@classmethod
|
|
def get_id(self, key):
|
|
if key is None:
|
|
return
|
|
return SATUsoCfdi.get(SATUsoCfdi.key==key).id
|
|
|
|
@classmethod
|
|
def get_activos(cls):
|
|
rows = (SATUsoCfdi
|
|
.select(
|
|
SATUsoCfdi.key.alias('id'),
|
|
SATUsoCfdi.name.alias('value'),
|
|
SATUsoCfdi.fisica,
|
|
SATUsoCfdi.moral,
|
|
)
|
|
.where(SATUsoCfdi.activo==True)
|
|
.dicts()
|
|
)
|
|
return tuple(rows)
|
|
|
|
|
|
class TipoCambio(BaseModel):
|
|
dia = DateField(default=util.now)
|
|
moneda = ForeignKeyField(SATMonedas)
|
|
tipo_cambio = DecimalField(max_digits=15, decimal_places=6, auto_round=True)
|
|
|
|
class Meta:
|
|
order_by = ('-dia',)
|
|
|
|
|
|
class Addendas(BaseModel):
|
|
nombre = TextField(unique=True)
|
|
addenda = TextField()
|
|
|
|
class Meta:
|
|
order_by = ('nombre',)
|
|
|
|
|
|
class Socios(BaseModel):
|
|
tipo_persona = IntegerField(default=1)
|
|
rfc = TextField(index=True)
|
|
nombre = TextField(index=True)
|
|
slug = TextField(default='')
|
|
nombre_comercial = TextField(index=True, default='')
|
|
calle = TextField(default='')
|
|
no_exterior = TextField(default='')
|
|
no_interior = TextField(default='')
|
|
colonia = TextField(default='')
|
|
municipio = TextField(default='')
|
|
estado = TextField(default='')
|
|
pais = TextField(default='')
|
|
codigo_postal = TextField(default='')
|
|
notas = TextField(default='')
|
|
telefonos = TextField(default='')
|
|
es_activo = BooleanField(default=True)
|
|
es_ong = BooleanField(default=False)
|
|
fecha_alta = DateField(default=util.now)
|
|
dias_pago = IntegerField(default=0)
|
|
dias_habiles = BooleanField(default=False)
|
|
es_cliente = BooleanField(default=False)
|
|
es_proveedor = BooleanField(default=False)
|
|
cuenta_cliente = TextField(default='')
|
|
cuenta_proveedor = TextField(default='')
|
|
saldo_cliente = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
saldo_proveedor = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
web = TextField(default='')
|
|
correo_facturas = TextField(default='')
|
|
forma_pago = ForeignKeyField(SATFormaPago, null=True)
|
|
condicion_pago = ForeignKeyField(CondicionesPago, null=True)
|
|
addenda = ForeignKeyField(Addendas, null=True)
|
|
uso_cfdi = ForeignKeyField(SATUsoCfdi, null=True)
|
|
tags = ManyToManyField(Tags, related_name='socios_tags')
|
|
|
|
def __str__(self):
|
|
t = '{} ({})'
|
|
return t.format(self.nombre, self.rfc)
|
|
|
|
class Meta:
|
|
order_by = ('nombre',)
|
|
indexes = (
|
|
(('rfc', 'slug'), True),
|
|
)
|
|
|
|
def _clean(self, values):
|
|
fields = util.clean(values)
|
|
fields['rfc'] = fields['rfc'].upper()
|
|
fields['nombre'] = util.spaces(fields['nombre'])
|
|
fields['slug'] = util.to_slug(fields['nombre'])
|
|
uso_cfdi = fields.pop('uso_cfdi_socio', None)
|
|
fields['uso_cfdi'] = SATUsoCfdi.get_id(uso_cfdi)
|
|
fields['condicion_pago'] = \
|
|
CondicionesPago.get_or(fields.get('condicion_pago', None))
|
|
|
|
fb = ('dias_habiles', 'es_activo', 'es_cliente',
|
|
'es_proveedor', 'es_ong')
|
|
for name in fb:
|
|
fields[name] = bool(fields[name].replace('0', ''))
|
|
return fields
|
|
|
|
@classmethod
|
|
def get_(cls, values):
|
|
print ('values', values)
|
|
id = values.get('id', 0)
|
|
if id:
|
|
id = int(values['id'])
|
|
row = Socios.select().where(Socios.id==id).dicts()[0]
|
|
row['uso_cfdi_socio'] = row.pop('uso_cfdi')
|
|
if not row['condicion_pago'] is None:
|
|
row['condicion_pago'] = \
|
|
str(CondicionesPago.get(id=row['condicion_pago']))
|
|
return row
|
|
|
|
#~ return {'data': data['rows'][:100], 'pos':0, 'total_count': 1300}
|
|
#~ start = 0
|
|
#~ count = 0
|
|
#~ end = 100
|
|
#~ if values:
|
|
#~ {'start': '100', 'count': '100', 'continue': 'true'}
|
|
#~ start = int(values['start'])
|
|
#~ cont = int(values['count'])
|
|
#~ end = start + count
|
|
|
|
total = Socios.select().count()
|
|
|
|
rows = (Socios
|
|
.select(
|
|
Socios.id,
|
|
Socios.rfc,
|
|
Socios.nombre,
|
|
Socios.saldo_cliente)
|
|
.dicts()
|
|
)
|
|
return {'pos': 0, 'total_count': total, 'data': tuple(rows)}
|
|
|
|
@classmethod
|
|
def get_by_client(cls, values):
|
|
id = int(values.get('id', 0))
|
|
if id:
|
|
row = (Socios
|
|
.select(
|
|
Socios.id, Socios.nombre, Socios.rfc,
|
|
SATFormaPago.key.alias('forma_pago'),
|
|
SATUsoCfdi.key.alias('uso_cfdi'))
|
|
.join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios)
|
|
.join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios)
|
|
.where((Socios.id==id) & (Socios.es_cliente==True))
|
|
.dicts()
|
|
)
|
|
if len(row):
|
|
return {'ok': True, 'row': row[0]}
|
|
return {'ok': False}
|
|
|
|
name = values.get('name', '')
|
|
if name:
|
|
rows = (Socios
|
|
.select(Socios.id, Socios.nombre, Socios.rfc,
|
|
SATFormaPago.key.alias('forma_pago'),
|
|
SATUsoCfdi.key.alias('uso_cfdi'))
|
|
.join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios)
|
|
.join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios)
|
|
.where((Socios.es_cliente==True) &
|
|
(Socios.rfc.contains(name) |
|
|
Socios.nombre.contains(name)))
|
|
.dicts())
|
|
return tuple(rows)
|
|
return {'ok': False}
|
|
|
|
@classmethod
|
|
def add(cls, values):
|
|
fields = cls._clean(cls, values)
|
|
try:
|
|
obj = Socios.create(**fields)
|
|
except IntegrityError:
|
|
msg = 'Ya existe el RFC y Razón Social'
|
|
data = {'ok': False, 'row': {}, 'new': True, 'msg': msg}
|
|
return data
|
|
|
|
#~ ToDo Agregar tags
|
|
|
|
row = {
|
|
'id': obj.id,
|
|
'rfc': obj.rfc,
|
|
'nombre': obj.nombre,
|
|
'saldo_cliente': obj.saldo_cliente,
|
|
}
|
|
data = {'ok': True, 'row': row, 'new': True}
|
|
return data
|
|
|
|
@classmethod
|
|
def actualizar(cls, values, id):
|
|
fields = cls._clean(cls, values)
|
|
try:
|
|
q = Socios.update(**fields).where(Socios.id==id)
|
|
q.execute()
|
|
except IntegrityError:
|
|
msg = 'Ya existe el RFC y Razón Social'
|
|
data = {'ok': False, 'row': {}, 'new': True, 'msg': msg}
|
|
return data
|
|
|
|
row = {
|
|
'id': id,
|
|
'rfc': fields['rfc'],
|
|
'nombre': fields['nombre'],
|
|
}
|
|
data = {'ok': True, 'row': row, 'new': False}
|
|
return data
|
|
|
|
@classmethod
|
|
def remove(cls, id):
|
|
count = (Facturas
|
|
.select(fn.COUNT(Facturas.id)).join(Socios)
|
|
.where(Socios.id==id)
|
|
.count())
|
|
if count:
|
|
return False
|
|
|
|
q = Socios.delete().where(Socios.id==id)
|
|
return bool(q.execute())
|
|
|
|
|
|
class Productos(BaseModel):
|
|
categoria = ForeignKeyField(Categorias, null=True)
|
|
clave = TextField(unique=True, index=True)
|
|
clave_sat = TextField(default='')
|
|
descripcion = TextField(index=True)
|
|
unidad = ForeignKeyField(SATUnidades)
|
|
valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
ultimo_costo = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
inventario = BooleanField(default=False)
|
|
existencia = DecimalField(default=0.0, max_digits=18, decimal_places=2,
|
|
auto_round=True)
|
|
minimo = DecimalField(default=0.0, max_digits=18, decimal_places=2,
|
|
auto_round=True)
|
|
codigo_barras = TextField(default='')
|
|
cuenta_predial = TextField(default='')
|
|
es_activo = BooleanField(default=True)
|
|
impuestos = ManyToManyField(SATImpuestos, related_name='productos')
|
|
tags = ManyToManyField(Tags, related_name='productos_tags')
|
|
|
|
class Meta:
|
|
order_by = ('descripcion',)
|
|
|
|
@classmethod
|
|
def next_key(cls):
|
|
value = (Productos
|
|
.select(fn.Max(Productos.id).alias('fm'))
|
|
.order_by(SQL('fm'))
|
|
.scalar())
|
|
if value is None:
|
|
value = 1
|
|
else:
|
|
value += 1
|
|
return {'value': value}
|
|
|
|
@classmethod
|
|
def get_by(cls, values):
|
|
id = int(values.get('id', 0))
|
|
if id:
|
|
row = (Productos
|
|
.select(
|
|
Productos.id,
|
|
Productos.clave,
|
|
Productos.descripcion,
|
|
SATUnidades.name.alias('unidad'),
|
|
Productos.valor_unitario,
|
|
Productos.descuento)
|
|
.join(SATUnidades).switch(Productos)
|
|
.where(Productos.id==id).dicts())
|
|
if len(row):
|
|
model_pt = Productos.impuestos.get_through_model()
|
|
taxes = tuple(model_pt
|
|
.select(
|
|
model_pt.productos_id.alias('product'),
|
|
model_pt.satimpuestos_id.alias('tax'))
|
|
.where(model_pt.productos_id==id).dicts())
|
|
return {'ok': True, 'row': row[0], 'taxes': taxes}
|
|
return {'ok': False}
|
|
|
|
name = values.get('name', '')
|
|
if name:
|
|
rows = (Productos
|
|
.select(
|
|
Productos.id, Productos.clave, Productos.descripcion,
|
|
SATUnidades.name.alias('unidad'), Productos.valor_unitario)
|
|
.join(SATUnidades)
|
|
.switch(Productos)
|
|
.where(Productos.descripcion.contains(name))
|
|
.dicts()
|
|
)
|
|
return tuple(rows)
|
|
return {'ok': False}
|
|
|
|
@classmethod
|
|
def get_(cls, values):
|
|
if values:
|
|
id = int(values['id'])
|
|
row = (Productos
|
|
.select(
|
|
Productos.id,
|
|
Productos.es_activo.alias('es_activo_producto'),
|
|
Productos.categoria,
|
|
Productos.clave,
|
|
Productos.clave_sat,
|
|
Productos.descripcion,
|
|
Productos.unidad,
|
|
Productos.valor_unitario,
|
|
)
|
|
.where(Productos.id==id).dicts()[0]
|
|
)
|
|
obj = Productos.get(Productos.id==id)
|
|
taxes = [row.id for row in obj.impuestos]
|
|
return {'row': row, 'taxes': taxes}
|
|
|
|
rows = (Productos
|
|
.select(
|
|
Productos.id,
|
|
Productos.clave,
|
|
Productos.descripcion,
|
|
SATUnidades.name.alias('unidad'),
|
|
Productos.valor_unitario)
|
|
.join(SATUnidades)
|
|
.dicts()
|
|
)
|
|
return {'ok': True, 'rows': tuple(rows)}
|
|
|
|
def _clean(self, values):
|
|
taxes = util.loads(values.pop('taxes'))
|
|
fields = util.clean(values)
|
|
|
|
fields['es_activo'] = fields.pop('es_activo_producto')
|
|
fields['descripcion'] = util.spaces(fields['descripcion'])
|
|
fields['unidad'] = int(fields['unidad'])
|
|
fields['valor_unitario'] = fields['valor_unitario'].replace(
|
|
'$', '').replace(',', '')
|
|
|
|
fb = ('es_activo', 'inventario')
|
|
for name in fb:
|
|
fields[name] = bool(fields[name].replace('0', ''))
|
|
|
|
return fields, taxes
|
|
|
|
@classmethod
|
|
def add(cls, values):
|
|
fields, taxes = cls._clean(cls, values)
|
|
|
|
if Productos.select().where(Productos.clave==fields['clave']).exists():
|
|
msg = 'Clave ya existe'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes))
|
|
|
|
with database_proxy.transaction():
|
|
obj = Productos.create(**fields)
|
|
obj.impuestos = obj_taxes
|
|
row = {
|
|
'id': obj.id,
|
|
'clave': obj.clave,
|
|
'descripcion': obj.descripcion,
|
|
'unidad': obj.unidad.name,
|
|
'valor_unitario': obj.valor_unitario,
|
|
}
|
|
data = {'ok': True, 'row': row, 'new': True}
|
|
return data
|
|
|
|
@classmethod
|
|
def actualizar(cls, values, id):
|
|
fields, taxes = cls._clean(cls, values)
|
|
obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes))
|
|
with database_proxy.transaction():
|
|
q = Productos.update(**fields).where(Productos.id==id)
|
|
try:
|
|
q.execute()
|
|
except IntegrityError:
|
|
msg = 'Ya existe un producto con esta clave'
|
|
data = {'ok': False, 'row': {}, 'new': False, 'msg': msg}
|
|
return data
|
|
|
|
obj = Productos.get(Productos.id==id)
|
|
obj.impuestos = obj_taxes
|
|
row = {
|
|
'id': obj.id,
|
|
'clave': obj.clave,
|
|
'descripcion': obj.descripcion,
|
|
'unidad': obj.unidad.name,
|
|
'valor_unitario': obj.valor_unitario,
|
|
}
|
|
data = {'ok': True, 'row': row, 'new': False}
|
|
return data
|
|
|
|
@classmethod
|
|
def remove(cls, id):
|
|
count = (FacturasDetalle
|
|
.select(fn.COUNT(FacturasDetalle.id)).join(Productos)
|
|
.where(Productos.id==id)
|
|
.count()
|
|
)
|
|
if count:
|
|
return False
|
|
|
|
with database_proxy.transaction():
|
|
obj = Productos.get(Productos.id==id)
|
|
obj.impuestos.clear()
|
|
obj.tags.clear()
|
|
q = Productos.delete().where(Productos.id==id)
|
|
return bool(q.execute())
|
|
|
|
|
|
class Facturas(BaseModel):
|
|
cliente = ForeignKeyField(Socios)
|
|
version = TextField(default=CURRENT_CFDI)
|
|
serie = TextField(default='')
|
|
folio = IntegerField(default=0)
|
|
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
|
|
fecha_timbrado = DateTimeField(null=True)
|
|
forma_pago = TextField(default='')
|
|
condiciones_pago = TextField(default='')
|
|
subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
moneda = TextField(default='MXN')
|
|
tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6,
|
|
auto_round=True)
|
|
total = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
tipo_comprobante = TextField(default='I')
|
|
metodo_pago = TextField(default='PUE')
|
|
lugar_expedicion = TextField(default='')
|
|
confirmacion = TextField(default='')
|
|
uso_cfdi = TextField(default='')
|
|
total_retenciones = DecimalField(
|
|
max_digits=20, decimal_places=6, auto_round=True, null=True)
|
|
total_trasladados = DecimalField(
|
|
max_digits=20, decimal_places=6, auto_round=True, null=True)
|
|
xml = TextField(default='')
|
|
uuid = UUIDField(null=True)
|
|
estatus = TextField(default='Guardada')
|
|
estatus_sat = TextField(default='Vigente')
|
|
regimen_fiscal = TextField(default='')
|
|
notas = TextField(default='')
|
|
pagada = BooleanField(default=False)
|
|
cancelada = BooleanField(default=False)
|
|
fecha_cancelacion = DateTimeField(null=True)
|
|
acuse = TextField(default='')
|
|
donativo = BooleanField(default=False)
|
|
anticipo = BooleanField(default=False)
|
|
egreso_anticipo = BooleanField(default=False)
|
|
tipo_relacion = TextField(default='')
|
|
error = TextField(default='')
|
|
|
|
class Meta:
|
|
order_by = ('fecha',)
|
|
|
|
@classmethod
|
|
def cancel(cls, id):
|
|
msg = 'Factura cancelada correctamente'
|
|
auth = Emisor.get_auth()
|
|
certificado = Certificado.select()[0]
|
|
obj = Facturas.get(Facturas.id==id)
|
|
data, result = util.cancel_cfdi(
|
|
obj.uuid, certificado.p12, certificado.rfc, auth)
|
|
if data['ok']:
|
|
obj.estatus = 'Cancelada'
|
|
obj.error = ''
|
|
obj.cancelada = True
|
|
obj.fecha_cancelacion = result['Fecha']
|
|
obj.acuse = result['Acuse']
|
|
else:
|
|
obj.error = data['msg']
|
|
obj.save()
|
|
return data
|
|
|
|
@classmethod
|
|
def filter_years(cls):
|
|
data = [{'id': -1, 'value': 'Todos'}]
|
|
rows = (Facturas
|
|
.select(Facturas.fecha.year.alias('year'))
|
|
.group_by(Facturas.fecha.year)
|
|
.order_by(Facturas.fecha.year)
|
|
)
|
|
if not rows is None:
|
|
data += [{'id': int(r.year), 'value': int(r.year)} for r in rows]
|
|
return tuple(data)
|
|
|
|
@classmethod
|
|
def get_xml(cls, id):
|
|
obj = Facturas.get(Facturas.id==id)
|
|
name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
|
return obj.xml, name
|
|
|
|
#~ Revisar
|
|
def _get_data_cfdi_to_pdf(self, xml, cancel, version):
|
|
pre_nomina = PRE['NOMINA'][version]
|
|
|
|
data['nomina'] = {}
|
|
node = doc.find('{}Complemento/{}Nomina'.format(pre, pre_nomina))
|
|
if not node is None:
|
|
data['nomina']['nomina'] = node.attrib.copy()
|
|
subnode = node.find('{}Emisor'.format(pre_nomina))
|
|
if not subnode is None:
|
|
data['emisor'].update(subnode.attrib.copy())
|
|
subnode = node.find('{}Receptor'.format(pre_nomina))
|
|
data['receptor'].update(subnode.attrib.copy())
|
|
|
|
subnode = node.find('{}Percepciones'.format(pre_nomina))
|
|
data['nomina']['percepciones'] = subnode.attrib.copy()
|
|
detalle = []
|
|
for n in subnode.getchildren():
|
|
if 'SeparacionIndemnizacion' in n.tag:
|
|
continue
|
|
detalle.append(n.attrib.copy())
|
|
data['nomina']['percepciones']['detalle'] = detalle
|
|
|
|
data['nomina']['deducciones'] = None
|
|
subnode = node.find('{}Deducciones'.format(pre_nomina))
|
|
if not subnode is None:
|
|
data['nomina']['deducciones'] = subnode.attrib.copy()
|
|
detalle = []
|
|
for n in subnode.getchildren():
|
|
detalle.append(n.attrib.copy())
|
|
data['nomina']['deducciones']['detalle'] = detalle
|
|
|
|
data['nomina']['incapacidades'] = None
|
|
subnode = node.find('{}Incapacidades'.format(pre_nomina))
|
|
if not subnode is None:
|
|
detalle = []
|
|
for n in subnode.getchildren():
|
|
detalle.append(n.attrib.copy())
|
|
data['nomina']['incapacidades'] = detalle
|
|
|
|
data['nomina']['otrospagos'] = None
|
|
subnode = node.find('{}OtrosPagos'.format(pre_nomina))
|
|
if not subnode is None:
|
|
data['nomina']['otrospagos'] = subnode.attrib.copy()
|
|
detalle = []
|
|
for n in subnode.getchildren():
|
|
detalle.append(n.attrib.copy())
|
|
ns = n.find('{}SubsidioAlEmpleo'.format(pre_nomina))
|
|
if not ns is None:
|
|
data['nomina']['otrospagos']['SubsidioCausado'] = ns.attrib['SubsidioCausado']
|
|
data['nomina']['otrospagos']['detalle'] = detalle
|
|
|
|
return data
|
|
|
|
def _get_not_in_xml(self, invoice):
|
|
values = {}
|
|
|
|
if invoice.version == '3.2':
|
|
return values
|
|
|
|
obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal)
|
|
values['regimenfiscal'] = str(obj)
|
|
|
|
obj = SATUsoCfdi.get(SATUsoCfdi.key==invoice.uso_cfdi)
|
|
values['usocfdi'] = str(obj)
|
|
|
|
mp = {
|
|
'PUE': 'Pago en una sola exhibición',
|
|
'PPD': 'Pago en parcialidades o diferido',
|
|
}
|
|
values['metododepago'] = 'Método de Pago: ({}) {}'.format(
|
|
invoice.metodo_pago, mp[invoice.metodo_pago])
|
|
|
|
obj = SATFormaPago.get(SATFormaPago.key==invoice.forma_pago)
|
|
values['formadepago'] = str(obj)
|
|
|
|
obj = SATMonedas.get(SATMonedas.key==invoice.moneda)
|
|
values['moneda'] = str(obj)
|
|
|
|
if invoice.tipo_relacion:
|
|
obj = SATTipoRelacion.get(SATTipoRelacion.key==invoice.tipo_relacion)
|
|
values['tiporelacion'] = str(obj)
|
|
|
|
return values
|
|
|
|
@classmethod
|
|
def get_pdf(cls, id, rfc):
|
|
obj = Facturas.get(Facturas.id==id)
|
|
name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
|
if obj.uuid is None:
|
|
return b'', name
|
|
|
|
values = cls._get_not_in_xml(cls, obj)
|
|
data = util.get_data_from_xml(obj, values)
|
|
doc = util.to_pdf(data)
|
|
return doc, name
|
|
|
|
@classmethod
|
|
def get_zip(cls, id, rfc):
|
|
obj = Facturas.get(Facturas.id==id)
|
|
name_zip = '{}{}_{}.zip'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
|
if obj.uuid is None:
|
|
return b'', name_zip
|
|
|
|
file_xml = cls.get_xml(id)
|
|
if not file_xml[0]:
|
|
return b'', name_zip
|
|
|
|
file_pdf = cls.get_pdf(id, rfc)
|
|
if not file_pdf[0]:
|
|
return b'', name_zip
|
|
|
|
file_zip = util.to_zip(file_xml, file_pdf)
|
|
|
|
return file_zip, name_zip
|
|
|
|
@util.run_in_thread
|
|
def _send(self, id, rfc):
|
|
return Facturas.send(id, rfc)
|
|
|
|
@util.run_in_thread
|
|
def _actualizar_saldo_cliente(self, invoice):
|
|
if invoice.tipo_comprobante == 'T':
|
|
return
|
|
|
|
importe = invoice.total_mn
|
|
if invoice.tipo_comprobante == 'E':
|
|
importe *= -1
|
|
|
|
q = (Socios
|
|
.update(saldo_cliente=Socios.saldo_cliente + importe)
|
|
.where(Socios.id==invoice.cliente.id)
|
|
)
|
|
return bool(q.execute())
|
|
|
|
@classmethod
|
|
def send(cls, id, rfc):
|
|
values = Configuracion.get_({'fields': 'correo'})
|
|
if not values:
|
|
msg = 'No esta configurado el servidor de correo de salida'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
obj = Facturas.get(Facturas.id==id)
|
|
if obj.uuid is None:
|
|
msg = 'La factura no esta timbrada'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
if not obj.cliente.correo_facturas:
|
|
msg = 'El cliente no tiene configurado el correo para facturas'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
files = (cls.get_zip(id, rfc),)
|
|
|
|
fields = util.make_fields(obj.xml)
|
|
server = {
|
|
'servidor': values['correo_servidor'],
|
|
'puerto': values['correo_puerto'],
|
|
'ssl': bool(int(values['correo_ssl'])),
|
|
'usuario': values['correo_usuario'],
|
|
'contra': values['correo_contra'],
|
|
}
|
|
options = {
|
|
'para': obj.cliente.correo_facturas,
|
|
'copia': values['correo_copia'],
|
|
'asunto': util.make_info_mail(values['correo_asunto'], fields),
|
|
'mensaje': util.make_info_mail(values['correo_mensaje'], fields),
|
|
'files': files,
|
|
}
|
|
data= {
|
|
'server': server,
|
|
'options': options,
|
|
}
|
|
result = util.send_mail(data)
|
|
if not result['ok'] or result['msg']:
|
|
return {'ok': False, 'msg': result['msg']}
|
|
|
|
msg = 'Factura enviada correctamente'
|
|
return {'ok': True, 'msg': msg}
|
|
|
|
def _get_filter_folios(self, values):
|
|
if not 'folio' in values:
|
|
return ''
|
|
|
|
folios = values['folio'].split('-')
|
|
if len(folios) == 1:
|
|
try:
|
|
folio1 = int(folios[0])
|
|
except ValueError:
|
|
return ''
|
|
|
|
folio2 = folio1
|
|
else:
|
|
try:
|
|
folio1 = int(folios[0])
|
|
folio2 = int(folios[1])
|
|
except ValueError:
|
|
return ''
|
|
|
|
return (Facturas.folio.between(folio1, folio2))
|
|
|
|
def _get_opt(self, values):
|
|
cfdis = util.loads(values['cfdis'])
|
|
|
|
if values['year'] == '-1':
|
|
fy = (Facturas.fecha.year > 0)
|
|
else:
|
|
fy = (Facturas.fecha.year == int(values['year']))
|
|
if values['month'] == '-1':
|
|
fm = (Facturas.fecha.month > 0)
|
|
else:
|
|
fm = (Facturas.fecha.month == int(values['month']))
|
|
|
|
if values['opt'] == 'relacionados':
|
|
folios = self._get_filter_folios(self, values)
|
|
uuid = values.get('uuid', '')
|
|
if uuid:
|
|
f_uuid = (cast(Facturas.uuid, 'text').contains(uuid))
|
|
cliente = (Facturas.cliente == int(values['id_cliente']))
|
|
if cfdis:
|
|
f_ids = (Facturas.id.not_in(cfdis))
|
|
else:
|
|
f_ids = (Facturas.id > 0)
|
|
|
|
filters = (fy & fm & cliente & f_ids)
|
|
if folios:
|
|
filters = filters & folios
|
|
elif uuid:
|
|
filters = filters & f_uuid
|
|
|
|
if values['anticipo'] == '1':
|
|
filters = filters & (Facturas.anticipo == True)
|
|
|
|
rows = tuple(Facturas
|
|
.select(Facturas.id, Facturas.serie, Facturas.folio,
|
|
Facturas.uuid, Facturas.fecha, Facturas.tipo_comprobante,
|
|
Facturas.estatus, Facturas.total_mn)
|
|
.where(filters).dicts()
|
|
)
|
|
|
|
return {'ok': True, 'rows': rows}
|
|
|
|
@classmethod
|
|
def get_(cls, values):
|
|
opt = values.get('opt', '')
|
|
if opt:
|
|
return cls._get_opt(cls, values)
|
|
|
|
if 'start' in values:
|
|
filters = Facturas.fecha.between(
|
|
util.get_date(values['start']),
|
|
util.get_date(values['end'], True)
|
|
)
|
|
else:
|
|
if values['year'] == '-1':
|
|
fy = (Facturas.fecha.year > 0)
|
|
else:
|
|
fy = (Facturas.fecha.year == int(values['year']))
|
|
if values['month'] == '-1':
|
|
fm = (Facturas.fecha.month > 0)
|
|
else:
|
|
fm = (Facturas.fecha.month == int(values['month']))
|
|
filters = (fy & fm)
|
|
|
|
rows = tuple(Facturas
|
|
.select(Facturas.id, Facturas.serie, Facturas.folio, Facturas.uuid,
|
|
Facturas.fecha, Facturas.tipo_comprobante, Facturas.estatus,
|
|
Facturas.total_mn, Socios.nombre.alias('cliente'))
|
|
.where(filters)
|
|
.join(Socios)
|
|
.switch(Facturas).dicts()
|
|
)
|
|
return {'ok': True, 'rows': rows}
|
|
|
|
@classmethod
|
|
def remove(cls, id):
|
|
obj = Facturas.get(Facturas.id==id)
|
|
if obj.uuid:
|
|
return False
|
|
|
|
q = FacturasDetalle.delete().where(FacturasDetalle.factura==obj)
|
|
q.execute()
|
|
q = FacturasImpuestos.delete().where(FacturasImpuestos.factura==obj)
|
|
q.execute()
|
|
q = FacturasRelacionadas.delete().where(FacturasRelacionadas.factura==obj)
|
|
q.execute()
|
|
return bool(obj.delete_instance())
|
|
|
|
def _get_folio(self, serie):
|
|
inicio_serie = Folios.select(
|
|
Folios.inicio).where(Folios.serie==serie).scalar()
|
|
|
|
inicio = (Facturas
|
|
.select(fn.Max(Facturas.folio).alias('mf'))
|
|
.where(Facturas.serie==serie)
|
|
.order_by(SQL('mf'))
|
|
.scalar())
|
|
|
|
if inicio is None:
|
|
inicio = inicio_serie
|
|
else:
|
|
inicio += 1
|
|
|
|
return inicio
|
|
|
|
def _calculate_totals(self, invoice, products):
|
|
subtotal = 0
|
|
descuento_cfdi = 0
|
|
totals_tax = {}
|
|
total_trasladados = None
|
|
total_retenciones = None
|
|
total_iva = 0
|
|
|
|
for product in products:
|
|
id_product = product.pop('id')
|
|
p = Productos.get(Productos.id==id_product)
|
|
|
|
product['unidad'] = p.unidad.key
|
|
product['clave'] = p.clave
|
|
product['clave_sat'] = p.clave_sat
|
|
|
|
product['factura'] = invoice.id
|
|
product['producto'] = id_product
|
|
|
|
cantidad = float(product['cantidad'])
|
|
valor_unitario = float(product['valor_unitario'])
|
|
descuento = float(product['descuento'])
|
|
precio_final = valor_unitario - descuento
|
|
importe = round(cantidad * precio_final, DECIMALES)
|
|
|
|
product['cantidad'] = cantidad
|
|
product['valor_unitario'] = valor_unitario
|
|
product['descuento'] = descuento
|
|
product['precio_final'] = precio_final
|
|
product['importe'] = round(cantidad * valor_unitario, DECIMALES)
|
|
|
|
descuento_cfdi += descuento
|
|
subtotal += importe
|
|
|
|
FacturasDetalle.create(**product)
|
|
|
|
for tax in p.impuestos:
|
|
if tax.id in totals_tax:
|
|
#~ totals_tax[tax.id].importe += product['importe']
|
|
totals_tax[tax.id].importe += importe
|
|
else:
|
|
#~ tax.importe = product['importe']
|
|
tax.importe = importe
|
|
totals_tax[tax.id] = tax
|
|
|
|
for tax in totals_tax.values():
|
|
if tax.tipo == 'E' or tax.tipo == 'R':
|
|
continue
|
|
|
|
import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
|
|
total_trasladados = (total_trasladados or 0) + import_tax
|
|
if tax.name == 'IVA':
|
|
total_iva += import_tax
|
|
|
|
invoice_tax = {
|
|
'factura': invoice.id,
|
|
'impuesto': tax.id,
|
|
'base': tax.importe,
|
|
'importe': import_tax,
|
|
}
|
|
FacturasImpuestos.create(**invoice_tax)
|
|
|
|
for tax in totals_tax.values():
|
|
if tax.tipo == 'E' or tax.tipo == 'T':
|
|
continue
|
|
if tax.tasa == round(Decimal(2/3), 6):
|
|
import_tax = round(float(tax.tasa) * total_iva, DECIMALES)
|
|
else:
|
|
import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
|
|
total_retenciones = (total_retenciones or 0) + import_tax
|
|
|
|
invoice_tax = {
|
|
'factura': invoice.id,
|
|
'impuesto': tax['id'],
|
|
'base': tax['importe'],
|
|
'importe': import_tax,
|
|
}
|
|
FacturasImpuestos.create(**invoice_tax)
|
|
|
|
total = subtotal + (total_trasladados or 0) - (total_retenciones or 0)
|
|
total_mn = round(total * invoice.tipo_cambio, DECIMALES)
|
|
data = {
|
|
'subtotal': subtotal + descuento,
|
|
'descuento': descuento_cfdi,
|
|
'total': total,
|
|
'total_mn': total_mn,
|
|
'total_trasladados': total_trasladados,
|
|
'total_retenciones': total_retenciones,
|
|
}
|
|
return data
|
|
|
|
def _guardar_relacionados(self, invoice, relacionados):
|
|
for cfdi in relacionados:
|
|
data = {
|
|
'factura': invoice,
|
|
'factura_origen': cfdi,
|
|
}
|
|
FacturasRelacionadas.create(**data)
|
|
return
|
|
|
|
@classmethod
|
|
def add(cls, values):
|
|
productos = util.loads(values.pop('productos'))
|
|
relacionados = util.loads(values.pop('relacionados'))
|
|
|
|
emisor = Emisor.select()[0]
|
|
values['folio'] = cls._get_folio(cls, values['serie'])
|
|
values['tipo_cambio'] = float(values['tipo_cambio'])
|
|
values['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
|
|
values['anticipo'] = util.get_bool(values['anticipo'])
|
|
|
|
with database_proxy.atomic() as txn:
|
|
obj = Facturas.create(**values)
|
|
totals = cls._calculate_totals(cls, obj, productos)
|
|
cls._guardar_relacionados(cls, obj, relacionados)
|
|
obj.subtotal = totals['subtotal']
|
|
obj.descuento = totals['descuento']
|
|
obj.total_trasladados = totals['total_trasladados']
|
|
obj.total_retenciones = totals['total_retenciones']
|
|
obj.total = totals['total']
|
|
obj.total_mn = totals['total_mn']
|
|
obj.save()
|
|
|
|
msg = 'Factura guardada correctamente. Enviando a timbrar'
|
|
row = {
|
|
'id': obj.id,
|
|
'serie': obj.serie,
|
|
'folio': obj.folio,
|
|
'uuid': obj.uuid,
|
|
'fecha': obj.fecha,
|
|
'tipo_comprobante': obj.tipo_comprobante,
|
|
'estatus': obj.estatus,
|
|
'total_mn': obj.total_mn,
|
|
'cliente': obj.cliente.nombre,
|
|
}
|
|
data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg}
|
|
return data
|
|
|
|
def _make_xml(self, invoice):
|
|
emisor = Emisor.select()[0]
|
|
certificado = Certificado.select()[0]
|
|
comprobante = {}
|
|
relacionados = {}
|
|
if invoice.serie:
|
|
comprobante['Serie'] = invoice.serie
|
|
if invoice.condiciones_pago:
|
|
comprobante['CondicionesDePago'] = invoice.condiciones_pago
|
|
if invoice.descuento:
|
|
comprobante['Descuento'] = invoice.descuento
|
|
|
|
comprobante['Folio'] = str(invoice.folio)
|
|
comprobante['Fecha'] = invoice.fecha.isoformat()[:19]
|
|
comprobante['FormaPago'] = invoice.forma_pago
|
|
comprobante['NoCertificado'] = certificado.serie
|
|
comprobante['Certificado'] = certificado.cer_txt
|
|
comprobante['SubTotal'] = FORMAT.format(invoice.subtotal)
|
|
comprobante['Moneda'] = invoice.moneda
|
|
comprobante['TipoCambio'] = '1'
|
|
if comprobante['Moneda'] != 'MXN':
|
|
comprobante['TipoCambio'] = FORMAT.format(invoice.tipo_cambio)
|
|
comprobante['Total'] = FORMAT.format(invoice.total)
|
|
comprobante['TipoDeComprobante'] = invoice.tipo_comprobante
|
|
comprobante['MetodoPago'] = invoice.metodo_pago
|
|
comprobante['LugarExpedicion'] = invoice.lugar_expedicion
|
|
if invoice.descuento:
|
|
comprobante['Descuento'] = FORMAT.format(invoice.descuento)
|
|
|
|
if invoice.tipo_relacion:
|
|
relacionados = {
|
|
'tipo': invoice.tipo_relacion,
|
|
'cfdis': FacturasRelacionadas.get_(invoice),
|
|
}
|
|
|
|
emisor = {
|
|
'Rfc': emisor.rfc,
|
|
'Nombre': emisor.nombre,
|
|
'RegimenFiscal': invoice.regimen_fiscal,
|
|
}
|
|
|
|
receptor = {
|
|
'Rfc': invoice.cliente.rfc,
|
|
'Nombre': invoice.cliente.nombre,
|
|
'UsoCFDI': invoice.uso_cfdi,
|
|
}
|
|
|
|
#~ descuento = 0
|
|
conceptos = []
|
|
rows = FacturasDetalle.select().where(FacturasDetalle.factura==invoice)
|
|
for row in rows:
|
|
concepto = {
|
|
'ClaveProdServ': row.producto.clave_sat,
|
|
'NoIdentificacion': row.producto.clave,
|
|
'Cantidad': FORMAT.format(row.cantidad),
|
|
'ClaveUnidad': row.producto.unidad.key,
|
|
'Unidad': row.producto.unidad.name,
|
|
'Descripcion': row.descripcion,
|
|
'ValorUnitario': FORMAT.format(row.valor_unitario),
|
|
'Importe': FORMAT.format(row.importe),
|
|
}
|
|
if row.descuento:
|
|
concepto['Descuento'] = FORMAT.format(row.descuento)
|
|
#~ descuento += row.descuento
|
|
|
|
taxes = {}
|
|
traslados = []
|
|
retenciones = []
|
|
|
|
for impuesto in row.producto.impuestos:
|
|
if impuesto.tipo == 'E':
|
|
continue
|
|
base = row.importe - row.descuento
|
|
import_tax = round(impuesto.tasa * base, DECIMALES)
|
|
tipo_factor = 'Tasa'
|
|
if impuesto.factor != 'T':
|
|
tipo_factor = 'Cuota'
|
|
tax = {
|
|
"Base": FORMAT.format(base),
|
|
"Impuesto": impuesto.key,
|
|
"TipoFactor": tipo_factor,
|
|
"TasaOCuota": str(impuesto.tasa),
|
|
"Importe": FORMAT.format(import_tax),
|
|
}
|
|
if impuesto.tipo == 'T':
|
|
traslados.append(tax)
|
|
else:
|
|
retenciones.append(tax)
|
|
|
|
if traslados:
|
|
taxes['traslados'] = traslados
|
|
if retenciones:
|
|
taxes['retenciones'] = retenciones
|
|
concepto['impuestos'] = taxes
|
|
conceptos.append(concepto)
|
|
|
|
#~ if descuento:
|
|
#~ comprobante['Descuento'] = FORMAT.format(descuento)
|
|
|
|
impuestos = {}
|
|
traslados = []
|
|
retenciones = []
|
|
if not invoice.total_trasladados is None:
|
|
impuestos['TotalImpuestosTrasladados'] = \
|
|
FORMAT.format(invoice.total_trasladados)
|
|
if not invoice.total_retenciones is None:
|
|
impuestos['TotalImpuestosRetenidos'] = \
|
|
FORMAT.format(invoice.total_retenciones)
|
|
|
|
taxes = (FacturasImpuestos
|
|
.select()
|
|
.where(FacturasImpuestos.factura==invoice))
|
|
for tax in taxes:
|
|
tipo_factor = 'Tasa'
|
|
if tax.impuesto.factor != 'T':
|
|
tipo_factor = 'Cuota'
|
|
if tax.impuesto.tipo == 'T':
|
|
traslado = {
|
|
"Impuesto": tax.impuesto.key,
|
|
"TipoFactor": tipo_factor,
|
|
"TasaOCuota": str(tax.impuesto.tasa),
|
|
"Importe": FORMAT.format(tax.importe),
|
|
}
|
|
traslados.append(traslado)
|
|
else:
|
|
retencion = {
|
|
"Impuesto": tax.impuesto.key,
|
|
"Importe": FORMAT.format(tax.importe),
|
|
}
|
|
retenciones.append(retencion)
|
|
|
|
impuestos['traslados'] = traslados
|
|
impuestos['retenciones'] = retenciones
|
|
|
|
data = {
|
|
'comprobante': comprobante,
|
|
'relacionados': relacionados,
|
|
'emisor': emisor,
|
|
'receptor': receptor,
|
|
'conceptos': conceptos,
|
|
'impuestos': impuestos,
|
|
}
|
|
return util.make_xml(data, certificado)
|
|
|
|
@classmethod
|
|
def get_status_sat(cls, id):
|
|
obj = Facturas.get(Facturas.id == id)
|
|
obj.estatus_sat = util.get_sat(obj.xml)
|
|
obj.save()
|
|
return obj.estatus_sat
|
|
|
|
@classmethod
|
|
def anticipo_egreso(cls, id):
|
|
origen = Facturas.get(Facturas.id == id)
|
|
relacionadas = (FacturasRelacionadas
|
|
.select(FacturasRelacionadas.factura_origen)
|
|
.where(FacturasRelacionadas.factura==origen))
|
|
conceptos = (FacturasDetalle
|
|
.select()
|
|
.where(FacturasDetalle.factura==origen))
|
|
impuestos = (FacturasImpuestos
|
|
.select()
|
|
.where(FacturasImpuestos.factura==origen))
|
|
|
|
#~ egreso_anticipo = BooleanField(default=False)
|
|
|
|
serie = Folios.get_egreso(origen.serie)
|
|
nueva = {
|
|
'cliente': origen.cliente,
|
|
'tipo_comprobante': 'E',
|
|
'forma_pago': '30',
|
|
'serie': serie,
|
|
'folio': cls._get_folio(cls, serie),
|
|
'tipo_relacion': '07',
|
|
'pagada': True,
|
|
'lugar_expedicion': origen.lugar_expedicion,
|
|
'uso_cfdi': origen.uso_cfdi,
|
|
'moneda': origen.moneda,
|
|
'tipo_cambio': origen.tipo_cambio,
|
|
'regimen_fiscal': origen.regimen_fiscal,
|
|
'subtotal': origen.subtotal,
|
|
'total': origen.total,
|
|
'total_trasladados': origen.total_trasladados,
|
|
'total_retenciones': origen.total_retenciones,
|
|
}
|
|
return
|
|
|
|
@classmethod
|
|
def timbrar(cls, id):
|
|
obj = Facturas.get(Facturas.id == id)
|
|
obj.xml = cls._make_xml(cls, obj)
|
|
obj.estatus = 'Generada'
|
|
obj.save()
|
|
|
|
enviar_correo = util.get_bool(Configuracion.get_('correo_directo'))
|
|
auth = Emisor.get_auth()
|
|
|
|
anticipo = False
|
|
msg = 'Factura timbrada correctamente'
|
|
result = util.timbra_xml(obj.xml, auth)
|
|
if result['ok']:
|
|
obj.xml = result['xml']
|
|
obj.uuid = result['uuid']
|
|
obj.fecha_timbrado = result['fecha']
|
|
obj.estatus = 'Timbrada'
|
|
obj.error = ''
|
|
obj.save()
|
|
row = {'uuid': obj.uuid, 'estatus': 'Timbrada'}
|
|
if enviar_correo:
|
|
cls._send(cls, id, auth['RFC'])
|
|
if obj.tipo_comprobante == 'I' and obj.tipo_relacion == '07':
|
|
anticipo = True
|
|
cls._actualizar_saldo_cliente(cls, obj)
|
|
else:
|
|
msg = result['error']
|
|
obj.estatus = 'Error'
|
|
obj.error = msg
|
|
obj.save()
|
|
row = {'estatus': 'Error'}
|
|
|
|
result = {
|
|
'ok': result['ok'],
|
|
'msg': msg,
|
|
'row': row,
|
|
'anticipo': anticipo
|
|
}
|
|
|
|
return result
|
|
|
|
|
|
class PreFacturas(BaseModel):
|
|
cliente = ForeignKeyField(Socios)
|
|
serie = TextField(default='PRE')
|
|
folio = IntegerField(default=0)
|
|
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
|
|
forma_pago = TextField(default='')
|
|
condiciones_pago = TextField(default='')
|
|
subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
moneda = TextField(default='MXN')
|
|
tipo_cambio = DecimalField(default=1.0, decimal_places=6, auto_round=True)
|
|
total = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
tipo_comprobante = TextField(default='I')
|
|
metodo_pago = TextField(default='PUE')
|
|
lugar_expedicion = TextField(default='')
|
|
uso_cfdi = TextField(default='')
|
|
total_retenciones = DecimalField(
|
|
max_digits=20, decimal_places=6, auto_round=True, null=True)
|
|
total_trasladados = DecimalField(
|
|
max_digits=20, decimal_places=6, auto_round=True, null=True)
|
|
estatus = TextField(default='Generada')
|
|
regimen_fiscal = TextField(default='')
|
|
notas = TextField(default='')
|
|
donativo = BooleanField(default=False)
|
|
tipo_relacion = TextField(default='')
|
|
|
|
class Meta:
|
|
order_by = ('fecha',)
|
|
|
|
@classmethod
|
|
def enviar(cls, id):
|
|
values = Configuracion.get_({'fields': 'correo'})
|
|
if not values:
|
|
msg = 'No esta configurado el servidor de correo de salida'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
obj = PreFacturas.get(PreFacturas.id==id)
|
|
if not obj.cliente.correo_facturas:
|
|
msg = 'El cliente no tiene configurado el correo para facturas'
|
|
return {'ok': False, 'msg': msg}
|
|
|
|
files = (cls.get_pdf(id),)
|
|
|
|
invoice = PreFacturas.select().where(PreFacturas.id==id).dicts()[0]
|
|
fields = {
|
|
'receptor_nombre': obj.cliente.nombre,
|
|
'receptor_rfc': obj.cliente.rfc,
|
|
}
|
|
fields.update(invoice)
|
|
|
|
asunto = 'Enviamos la prefactura: PRE-{}'.format(obj.folio)
|
|
server = {
|
|
'servidor': values['correo_servidor'],
|
|
'puerto': values['correo_puerto'],
|
|
'ssl': bool(int(values['correo_ssl'])),
|
|
'usuario': values['correo_usuario'],
|
|
'contra': values['correo_contra'],
|
|
}
|
|
options = {
|
|
'para': obj.cliente.correo_facturas,
|
|
'copia': values['correo_copia'],
|
|
'asunto': asunto,
|
|
'mensaje': util.make_info_mail(values['correo_mensaje'], fields),
|
|
'files': files,
|
|
}
|
|
data= {
|
|
'server': server,
|
|
'options': options,
|
|
}
|
|
result = util.send_mail(data)
|
|
if not result['ok'] or result['msg']:
|
|
return {'ok': False, 'msg': result['msg']}
|
|
|
|
msg = 'Pre Factura enviada correctamente'
|
|
return {'ok': True, 'msg': msg}
|
|
|
|
def _get_info_to_pdf(self, id):
|
|
data = {}
|
|
obj = PreFacturas.select().where(PreFacturas.id==id).dicts()[0]
|
|
regimen = SATRegimenes.get(SATRegimenes.key==obj['regimen_fiscal'])
|
|
usocfdi = SATUsoCfdi.get(SATUsoCfdi.key==obj['uso_cfdi'])
|
|
formapago = SATFormaPago.get(SATFormaPago.key==obj['forma_pago'])
|
|
moneda = SATMonedas.get(SATMonedas.key==obj['moneda'])
|
|
|
|
emisor = util.get_dict(Emisor.select().dicts()[0])
|
|
emisor['nointerior'] = emisor['no_interior']
|
|
emisor['noexterior'] = emisor['no_exterior']
|
|
emisor['codigopostal'] = emisor['codigo_postal']
|
|
emisor['regimenfiscal'] = str(regimen)
|
|
|
|
receptor = Socios.select().where(Socios.id==obj['cliente']).dicts()[0]
|
|
receptor['usocfdi'] = str(usocfdi)
|
|
receptor['nointerior'] = receptor['no_interior']
|
|
receptor['noexterior'] = receptor['no_exterior']
|
|
receptor['codigopostal'] = receptor['codigo_postal']
|
|
|
|
data['es_pre'] = True
|
|
data['cancelada'] = False
|
|
|
|
tipos = {
|
|
'I': 'ingreso',
|
|
'E': 'egreso',
|
|
'T': 'traslado',
|
|
}
|
|
mp = {
|
|
'PUE': 'Pago en una sola exhibición',
|
|
'PPD': 'Pago en parcialidades o diferido',
|
|
}
|
|
|
|
data['comprobante'] = obj
|
|
data['comprobante']['version'] = CURRENT_CFDI
|
|
data['comprobante']['folio'] = '{}-{}'.format(
|
|
data['comprobante']['serie'], data['comprobante']['folio'])
|
|
data['comprobante']['fecha'] = str(data['comprobante']['fecha'])
|
|
data['comprobante']['tipodecomprobante'] = tipos.get(
|
|
data['comprobante']['tipo_comprobante'])
|
|
data['comprobante']['lugarexpedicion'] = \
|
|
'C.P. de Expedición: {}'.format(
|
|
data['comprobante']['lugar_expedicion'])
|
|
data['comprobante']['metododepago'] = 'Método de Pago: ({}) {}'.format(
|
|
obj['metodo_pago'], mp[obj['metodo_pago']])
|
|
data['comprobante']['formadepago'] = str(formapago)
|
|
data['comprobante']['condicionesdepago'] = \
|
|
data['comprobante']['condiciones_pago']
|
|
data['comprobante']['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format(
|
|
data['comprobante']['tipo_cambio'])
|
|
data['comprobante']['totalenletras'] = util.to_letters(
|
|
data['comprobante']['total'], data['comprobante']['moneda'])
|
|
data['comprobante']['moneda'] = str(moneda)
|
|
|
|
data['emisor'] = emisor
|
|
data['receptor'] = receptor
|
|
|
|
data['conceptos'] = PreFacturasDetalle.get_(id)
|
|
data['totales'] = {}
|
|
data['totales']['moneda'] = data['comprobante']['moneda']
|
|
data['totales']['subtotal'] = str(data['comprobante']['subtotal'])
|
|
data['totales']['total'] = str(data['comprobante']['total'])
|
|
|
|
if obj['descuento']:
|
|
data['totales']['descuento'] = float(obj['descuento'])
|
|
|
|
taxes = PreFacturasImpuestos.get_(id)
|
|
data['totales']['traslados'] = taxes['traslados']
|
|
data['totales']['retenciones'] = taxes['retenciones']
|
|
data['totales']['taxlocales'] = taxes['taxlocales']
|
|
data['timbre'] = {}
|
|
|
|
return data
|
|
|
|
@classmethod
|
|
def get_pdf(cls, id):
|
|
obj = PreFacturas.get(PreFacturas.id==id)
|
|
name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc)
|
|
data = cls._get_info_to_pdf(cls, id)
|
|
doc = util.to_pdf(data)
|
|
return doc, name
|
|
|
|
@classmethod
|
|
def remove(cls, id):
|
|
obj = PreFacturas.get(PreFacturas.id==id)
|
|
|
|
q = PreFacturasDetalle.delete().where(
|
|
PreFacturasDetalle.factura==obj)
|
|
q.execute()
|
|
q = PreFacturasImpuestos.delete().where(
|
|
PreFacturasImpuestos.factura==obj)
|
|
q.execute()
|
|
q = PreFacturasRelacionadas.delete().where(
|
|
PreFacturasRelacionadas.factura==obj)
|
|
q.execute()
|
|
return bool(obj.delete_instance())
|
|
|
|
@classmethod
|
|
def filter_years(cls):
|
|
data = [{'id': -1, 'value': 'Todos'}]
|
|
rows = (PreFacturas
|
|
.select(PreFacturas.fecha.year)
|
|
.group_by(PreFacturas.fecha.year)
|
|
.order_by(PreFacturas.fecha.year)
|
|
.scalar(as_tuple=True)
|
|
)
|
|
if not rows is None:
|
|
data += [{'id': int(row), 'value': int(row)} for row in rows]
|
|
return tuple(data)
|
|
|
|
@classmethod
|
|
def get_(cls, values):
|
|
if values['year'] == '-1':
|
|
fy = (PreFacturas.fecha.year > 0)
|
|
else:
|
|
fy = (PreFacturas.fecha.year == int(values['year']))
|
|
if values['month'] == '-1':
|
|
fm = (PreFacturas.fecha.month > 0)
|
|
else:
|
|
fm = (PreFacturas.fecha.month == int(values['month']))
|
|
filters = (fy & fm)
|
|
|
|
rows = tuple(PreFacturas
|
|
.select(
|
|
PreFacturas.id,
|
|
PreFacturas.folio,
|
|
PreFacturas.fecha,
|
|
PreFacturas.tipo_comprobante,
|
|
PreFacturas.total_mn,
|
|
Socios.nombre.alias('cliente'))
|
|
.where(filters)
|
|
.join(Socios)
|
|
.switch(PreFacturas).dicts()
|
|
)
|
|
return {'ok': True, 'rows': rows}
|
|
|
|
def _get_folio(self, serie):
|
|
inicio = (PreFacturas
|
|
.select(fn.Max(PreFacturas.folio).alias('mf'))
|
|
.where(PreFacturas.serie==serie)
|
|
.order_by(SQL('mf'))
|
|
.scalar())
|
|
|
|
if inicio is None:
|
|
inicio = 1
|
|
else:
|
|
inicio += 1
|
|
|
|
return inicio
|
|
|
|
def _calculate_totals(self, invoice, products):
|
|
subtotal = 0
|
|
descuento_cfdi = 0
|
|
totals_tax = {}
|
|
total_trasladados = None
|
|
total_retenciones = None
|
|
total_iva = 0
|
|
|
|
for product in products:
|
|
id_product = product.pop('id')
|
|
p = Productos.get(Productos.id==id_product)
|
|
|
|
product['unidad'] = p.unidad.key
|
|
product['clave'] = p.clave
|
|
product['clave_sat'] = p.clave_sat
|
|
|
|
product['factura'] = invoice.id
|
|
product['producto'] = id_product
|
|
|
|
cantidad = float(product['cantidad'])
|
|
valor_unitario = float(product['valor_unitario'])
|
|
descuento = float(product['descuento'])
|
|
precio_final = valor_unitario - descuento
|
|
importe = round(cantidad * precio_final, DECIMALES)
|
|
|
|
product['cantidad'] = cantidad
|
|
product['valor_unitario'] = valor_unitario
|
|
product['descuento'] = descuento
|
|
product['precio_final'] = precio_final
|
|
product['importe'] = round(cantidad * valor_unitario, DECIMALES)
|
|
|
|
descuento_cfdi += descuento
|
|
subtotal += importe
|
|
|
|
PreFacturasDetalle.create(**product)
|
|
for tax in p.impuestos:
|
|
if tax.id in totals_tax:
|
|
totals_tax[tax.id].importe += importe
|
|
else:
|
|
tax.importe = importe
|
|
totals_tax[tax.id] = tax
|
|
|
|
for tax in totals_tax.values():
|
|
if tax.tipo == 'E' or tax.tipo == 'R':
|
|
continue
|
|
import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
|
|
total_trasladados = (total_trasladados or 0) + import_tax
|
|
if tax.name == 'IVA':
|
|
total_iva += import_tax
|
|
|
|
invoice_tax = {
|
|
'factura': invoice.id,
|
|
'impuesto': tax.id,
|
|
'base': tax.importe,
|
|
'importe': import_tax,
|
|
}
|
|
PreFacturasImpuestos.create(**invoice_tax)
|
|
|
|
for tax in totals_tax.values():
|
|
if tax.tipo == 'E' or tax.tipo == 'T':
|
|
continue
|
|
if tax.tasa == round(Decimal(2/3), 6):
|
|
import_tax = round(float(tax.tasa) * total_iva, DECIMALES)
|
|
else:
|
|
import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
|
|
total_retenciones = (total_retenciones or 0) + import_tax
|
|
|
|
invoice_tax = {
|
|
'factura': invoice.id,
|
|
'impuesto': tax['id'],
|
|
'base': tax['importe'],
|
|
'importe': import_tax,
|
|
}
|
|
PreFacturasImpuestos.create(**invoice_tax)
|
|
|
|
total = subtotal + (total_trasladados or 0) - (total_retenciones or 0)
|
|
total_mn = round(total * invoice.tipo_cambio, DECIMALES)
|
|
data = {
|
|
'subtotal': subtotal + descuento,
|
|
'descuento': descuento_cfdi,
|
|
'total': total,
|
|
'total_mn': total_mn,
|
|
'total_trasladados': total_trasladados,
|
|
'total_retenciones': total_retenciones,
|
|
}
|
|
return data
|
|
|
|
@classmethod
|
|
def add(cls, values):
|
|
productos = util.loads(values.pop('productos'))
|
|
|
|
emisor = Emisor.select()[0]
|
|
values['serie'] = 'PRE'
|
|
values['folio'] = cls._get_folio(cls, values['serie'])
|
|
values['tipo_cambio'] = float(values['tipo_cambio'])
|
|
values['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
|
|
|
|
with database_proxy.atomic() as txn:
|
|
obj = PreFacturas.create(**values)
|
|
totals = cls._calculate_totals(cls, obj, productos)
|
|
obj.subtotal = totals['subtotal']
|
|
obj.descuento = totals['descuento']
|
|
obj.total_trasladados = totals['total_trasladados']
|
|
obj.total_retenciones = totals['total_retenciones']
|
|
obj.total = totals['total']
|
|
obj.total_mn = totals['total_mn']
|
|
obj.save()
|
|
|
|
msg = 'Factura guardada correctamente'
|
|
row = {
|
|
'id': obj.id,
|
|
'folio': obj.folio,
|
|
'fecha': obj.fecha,
|
|
'tipo_comprobante': obj.tipo_comprobante,
|
|
'total_mn': obj.total_mn,
|
|
'cliente': obj.cliente.nombre,
|
|
}
|
|
data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg}
|
|
return data
|
|
|
|
|
|
class FacturasRelacionadas(BaseModel):
|
|
factura = ForeignKeyField(Facturas, related_name='original')
|
|
factura_origen = ForeignKeyField(Facturas, related_name='relacion')
|
|
|
|
class Meta:
|
|
order_by = ('factura',)
|
|
|
|
@classmethod
|
|
def get_(cls, invoice):
|
|
query = (FacturasRelacionadas
|
|
.select()
|
|
.where(FacturasRelacionadas.factura==invoice)
|
|
)
|
|
return [str(r.factura_origen.uuid) for r in query]
|
|
|
|
|
|
class CfdiPagosFacturas(BaseModel):
|
|
pago = ForeignKeyField(CfdiPagos)
|
|
factura = ForeignKeyField(Facturas)
|
|
|
|
class Meta:
|
|
order_by = ('pago',)
|
|
indexes = (
|
|
(('pago', 'factura'), True),
|
|
)
|
|
|
|
|
|
class PreFacturasRelacionadas(BaseModel):
|
|
factura = ForeignKeyField(PreFacturas, related_name='original')
|
|
factura_origen = ForeignKeyField(PreFacturas, related_name='relacion')
|
|
|
|
class Meta:
|
|
order_by = ('factura',)
|
|
|
|
|
|
class FacturasDetalle(BaseModel):
|
|
factura = ForeignKeyField(Facturas)
|
|
producto = ForeignKeyField(Productos, null=True)
|
|
cantidad = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
precio_final = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
importe = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
descripcion = TextField(default='')
|
|
unidad = TextField(default='')
|
|
clave = TextField(default='')
|
|
clave_sat = TextField(default='')
|
|
categoria = TextField(default='')
|
|
aduana = TextField(default='')
|
|
pedimento = TextField(default='')
|
|
fecha_pedimento = DateField(null=True)
|
|
alumno = TextField(default='')
|
|
curp = TextField(default='')
|
|
nivel = TextField(default='')
|
|
autorizacion = TextField(default='')
|
|
cuenta_predial = TextField(default='')
|
|
|
|
class Meta:
|
|
order_by = ('factura',)
|
|
|
|
|
|
class PreFacturasDetalle(BaseModel):
|
|
factura = ForeignKeyField(PreFacturas)
|
|
producto = ForeignKeyField(Productos, null=True)
|
|
descripcion = TextField(default='')
|
|
cantidad = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
precio_final = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
importe = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
aduana = TextField(default='')
|
|
pedimento = TextField(default='')
|
|
fecha_pedimento = DateField(null=True)
|
|
alumno = TextField(default='')
|
|
curp = TextField(default='')
|
|
nivel = TextField(default='')
|
|
autorizacion = TextField(default='')
|
|
cuenta_predial = TextField(default='')
|
|
|
|
class Meta:
|
|
order_by = ('factura',)
|
|
|
|
def _get_impuestos(self, id):
|
|
model_pt = Productos.impuestos.get_through_model()
|
|
impuestos = tuple(model_pt
|
|
.select(
|
|
model_pt.productos_id.alias('product'),
|
|
model_pt.satimpuestos_id.alias('tax'))
|
|
.where(model_pt.productos_id==id).dicts())
|
|
return impuestos
|
|
|
|
@classmethod
|
|
def facturar(cls, id):
|
|
data = []
|
|
|
|
q = PreFacturas.select(PreFacturas.cliente).where(PreFacturas.id==id)[0]
|
|
receptor = {
|
|
'id': q.cliente.id,
|
|
'nombre': q.cliente.nombre,
|
|
'rfc': q.cliente.rfc,
|
|
'forma_pago': q.cliente.forma_pago.key,
|
|
'uso_cfdi': q.cliente.uso_cfdi.key,
|
|
}
|
|
|
|
productos = PreFacturasDetalle.select().where(
|
|
PreFacturasDetalle.factura==id)
|
|
|
|
for p in productos:
|
|
row = {'id': p.producto.id}
|
|
row['clave'] = p.producto.clave
|
|
row['descripcion'] = p.descripcion
|
|
row['unidad'] = p.producto.unidad.name
|
|
row['cantidad'] = p.cantidad
|
|
row['valor_unitario'] = p.valor_unitario
|
|
row['descuento'] = p.descuento
|
|
pf = p.valor_unitario - p.descuento
|
|
row['importe'] = round(pf * p.cantidad, DECIMALES)
|
|
impuestos = cls._get_impuestos(cls, row['id'])
|
|
data.append({'row': row, 'taxes': impuestos})
|
|
|
|
return {'rows': data, 'receptor': receptor}
|
|
|
|
@classmethod
|
|
def get_(cls, id):
|
|
data = []
|
|
|
|
productos = PreFacturasDetalle.select().where(
|
|
PreFacturasDetalle.factura==id)
|
|
|
|
for p in productos:
|
|
producto = {}
|
|
producto['noidentificacion'] = '{}\n(SAT {})'.format(
|
|
p.producto.clave, p.producto.clave_sat)
|
|
producto['descripcion'] = p.descripcion
|
|
producto['unidad'] = '{}\n({})'.format(
|
|
p.producto.unidad.name, p.producto.unidad.key)
|
|
producto['cantidad'] = str(p.cantidad)
|
|
producto['valorunitario'] = str(p.valor_unitario)
|
|
producto['importe'] = str(p.importe)
|
|
data.append(producto)
|
|
|
|
return data
|
|
|
|
|
|
class FacturasImpuestos(BaseModel):
|
|
factura = ForeignKeyField(Facturas)
|
|
impuesto = ForeignKeyField(SATImpuestos)
|
|
base = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
importe = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
|
|
class Meta:
|
|
order_by = ('factura',)
|
|
indexes = (
|
|
(('factura', 'impuesto'), True),
|
|
)
|
|
|
|
|
|
class FacturasPagos(BaseModel):
|
|
factura = ForeignKeyField(Facturas)
|
|
numero = IntegerField(default=1)
|
|
saldo_anterior = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
importe = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
saldo = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
|
|
class Meta:
|
|
order_by = ('factura',)
|
|
indexes = (
|
|
(('factura', 'numero'), True),
|
|
)
|
|
|
|
|
|
class PreFacturasImpuestos(BaseModel):
|
|
factura = ForeignKeyField(PreFacturas)
|
|
impuesto = ForeignKeyField(SATImpuestos)
|
|
base = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
importe = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
|
|
class Meta:
|
|
order_by = ('factura',)
|
|
indexes = (
|
|
(('factura', 'impuesto'), True),
|
|
)
|
|
|
|
@classmethod
|
|
def get_(cls, id):
|
|
data = {
|
|
'traslados': [],
|
|
'retenciones': [],
|
|
'taxlocales': [],
|
|
}
|
|
|
|
taxes = PreFacturasImpuestos.select().where(
|
|
PreFacturasImpuestos.factura==id)
|
|
|
|
for tax in taxes:
|
|
if tax.impuesto.tipo == 'T':
|
|
title = 'Traslado {} {}'.format(
|
|
tax.impuesto.name, str(tax.impuesto.tasa))
|
|
data['traslados'].append((title, str(tax.importe)))
|
|
elif tax.impuesto.tipo == 'R':
|
|
title = 'Retención {} {}'.format(
|
|
tax.impuesto.name, str(tax.impuesto.tasa))
|
|
data['retenciones'].append((title, str(tax.importe)))
|
|
|
|
return data
|
|
|
|
|
|
def authenticate(args):
|
|
respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''}
|
|
values = util.get_con(args['rfc'])
|
|
if not values:
|
|
return respuesta
|
|
|
|
conectar(values)
|
|
try:
|
|
obj = Usuarios.get(usuario=args['usuario'], es_activo=True)
|
|
except Usuarios.DoesNotExist:
|
|
return respuesta
|
|
|
|
if not obj.contraseña.check_password(args['contra']):
|
|
return respuesta
|
|
|
|
obj.ultimo_ingreso = util.now()
|
|
obj.save()
|
|
respuesta['msg'] = ''
|
|
respuesta['login'] = True
|
|
respuesta['user'] = str(obj)
|
|
respuesta['super'] = obj.es_superusuario
|
|
#~ desconectar()
|
|
return respuesta
|
|
|
|
|
|
def get_cp(cp):
|
|
con = sqlite3.connect(PATH_CP)
|
|
cursor = con.cursor()
|
|
sql = """
|
|
SELECT colonia, municipio, estado
|
|
FROM colonias, municipios, estados
|
|
WHERE colonias.id_municipio=municipios.id
|
|
AND municipios.id_estado=estados.id
|
|
AND cp=?
|
|
ORDER BY colonia"""
|
|
cursor.execute(sql, (cp,))
|
|
rows = cursor.fetchall()
|
|
cursor.close()
|
|
con.close()
|
|
|
|
data = {}
|
|
if rows:
|
|
data = {
|
|
'estado': rows[0][2],
|
|
'municipio': rows[0][1],
|
|
}
|
|
if len(rows) == 1:
|
|
data['colonia'] = rows[0][0]
|
|
else:
|
|
data['colonia'] = [r[0] for r in rows]
|
|
return data
|
|
|
|
|
|
def get_sat_key(key):
|
|
return util.get_sat_key('productos', key)
|
|
|
|
|
|
def get_sat_unidades(key):
|
|
return util.get_sat_unidades(key)
|
|
|
|
def get_sat_productos(key):
|
|
return util.get_sat_productos(key)
|
|
|
|
|
|
def test_correo(values):
|
|
server = {
|
|
'servidor': values['correo_servidor'],
|
|
'puerto': values['correo_puerto'],
|
|
'ssl': bool(values['correo_ssl'].replace('0', '')),
|
|
'usuario': values['correo_usuario'],
|
|
'contra': values['correo_contra'],
|
|
}
|
|
options = {
|
|
'para': values['correo_usuario'],
|
|
'copia': values['correo_copia'],
|
|
'asunto': values['correo_asunto'],
|
|
'mensaje': values['correo_mensaje'].replace('\n', '<br/>'),
|
|
'files': [],
|
|
}
|
|
data= {
|
|
'server': server,
|
|
'options': options,
|
|
}
|
|
return util.send_mail(data)
|
|
|
|
|
|
def _init_values(rfc):
|
|
data = (
|
|
{'clave': 'version', 'valor': VERSION},
|
|
{'clave': 'migracion', 'valor': '0'},
|
|
{'clave': 'rfc_publico', 'valor': 'XAXX010101000'},
|
|
{'clave': 'rfc_extranjero', 'valor': 'XEXX010101000'},
|
|
{'clave': 'decimales', 'valor': '2'},
|
|
{'clave': 'path_key', 'valor': ''},
|
|
{'clave': 'path_cer', 'valor': ''},
|
|
)
|
|
for row in data:
|
|
try:
|
|
with database_proxy.atomic() as txn:
|
|
Configuracion.create(**row)
|
|
except IntegrityError:
|
|
pass
|
|
|
|
if not Certificado.select().count():
|
|
Certificado.create(rfc=rfc)
|
|
|
|
log.info('Valores iniciales insertados...')
|
|
return
|
|
|
|
|
|
def _crear_tablas(rfc):
|
|
tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion,
|
|
Folios,
|
|
Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos,
|
|
FacturasRelacionadas, Productos,
|
|
PreFacturas, PreFacturasDetalle, PreFacturasImpuestos,
|
|
PreFacturasRelacionadas,
|
|
SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes,
|
|
SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos,
|
|
Socios, Tags, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco,
|
|
CfdiPagos, CfdiPagosFacturas,
|
|
Emisor.regimenes.get_through_model(),
|
|
Socios.tags.get_through_model(),
|
|
Productos.impuestos.get_through_model(),
|
|
Productos.tags.get_through_model(),
|
|
]
|
|
log.info('Creando tablas...')
|
|
database_proxy.create_tables(tablas, True)
|
|
log.info('Tablas creadas correctamente...')
|
|
|
|
usuarios = (
|
|
{'usuario': 'superadmin', 'contraseña': DEFAULT_PASSWORD,
|
|
'es_superusuario': True},
|
|
{'usuario': 'admin', 'contraseña': DEFAULT_PASSWORD, 'es_admin': True},
|
|
)
|
|
|
|
for usuario in usuarios:
|
|
try:
|
|
with database_proxy.atomic() as txn:
|
|
Usuarios.create(**usuario)
|
|
log.info('Usuario creado correctamente...')
|
|
except IntegrityError:
|
|
log.info('Usuario ya existe...')
|
|
|
|
_init_values(rfc)
|
|
_importar_valores('', rfc)
|
|
|
|
return True
|
|
|
|
|
|
def migrate_tables():
|
|
connect()
|
|
log.info('Tablas migradas correctamente...')
|
|
return
|
|
|
|
|
|
def _agregar_superusuario():
|
|
args = util.get_con()
|
|
if not args:
|
|
return
|
|
|
|
conectar(args)
|
|
usuario = input('Introduce el nuevo nombre para el superusuario: ').strip()
|
|
if not usuario:
|
|
msg = 'El nombre de usuario es requerido'
|
|
log.erro(msg)
|
|
return
|
|
ok, contraseña = util.get_pass()
|
|
if not ok:
|
|
log.error(contraseña)
|
|
return
|
|
try:
|
|
obj = Usuarios.create(
|
|
usuario=usuario, contraseña=contraseña, es_superusuario=True)
|
|
except IntegrityError:
|
|
msg = 'El usuario ya existe'
|
|
log.error(msg)
|
|
return
|
|
|
|
log.info('SuperUsuario creado correctamente...')
|
|
return
|
|
|
|
|
|
def _cambiar_contraseña():
|
|
args = util.get_con()
|
|
if not args:
|
|
return
|
|
|
|
conectar(args)
|
|
usuario = input('Introduce el nombre de usuario: ').strip()
|
|
if not usuario:
|
|
msg = 'El nombre de usuario es requerido'
|
|
log.error(msg)
|
|
return
|
|
|
|
try:
|
|
obj = Usuarios.get(usuario=usuario)
|
|
except Usuarios.DoesNotExist:
|
|
msg = 'El usuario no existe'
|
|
log.error(msg)
|
|
return
|
|
|
|
ok, contraseña = util.get_pass()
|
|
if not ok:
|
|
log.error(contraseña)
|
|
return
|
|
|
|
obj.contraseña = contraseña
|
|
obj.save()
|
|
|
|
log.info('Contraseña cambiada correctamente...')
|
|
return
|
|
|
|
|
|
def _add_emisor(rfc, args):
|
|
util._valid_db_companies()
|
|
con = sqlite3.connect(COMPANIES)
|
|
cursor = con.cursor()
|
|
sql = """
|
|
INSERT INTO names
|
|
VALUES (?, ?)"""
|
|
try:
|
|
cursor.execute(sql, (rfc, args))
|
|
except sqlite3.IntegrityError as e:
|
|
log.error(e)
|
|
return False
|
|
|
|
con.commit()
|
|
cursor.close()
|
|
con.close()
|
|
return True
|
|
|
|
|
|
def _delete_emisor(rfc):
|
|
util._valid_db_companies()
|
|
con = sqlite3.connect(COMPANIES)
|
|
cursor = con.cursor()
|
|
sql = """
|
|
DELETE FROM names
|
|
WHERE rfc = ?"""
|
|
try:
|
|
cursor.execute(sql, (rfc,))
|
|
except Exception as e:
|
|
log.error(e)
|
|
return False
|
|
|
|
con.commit()
|
|
cursor.close()
|
|
con.close()
|
|
return True
|
|
|
|
|
|
def _iniciar_bd():
|
|
rfc = input('Introduce el RFC: ').strip().upper()
|
|
if not rfc:
|
|
msg = 'El RFC es requerido'
|
|
log.error(msg)
|
|
return
|
|
|
|
args = util.get_con(rfc)
|
|
if not args:
|
|
return
|
|
|
|
conectar(args)
|
|
if _crear_tablas(rfc):
|
|
return
|
|
|
|
log.error('No se pudieron crear las tablas')
|
|
return
|
|
|
|
|
|
def _agregar_rfc():
|
|
rfc = input('Introduce el nuevo RFC: ').strip().upper()
|
|
if not rfc:
|
|
msg = 'El RFC es requerido'
|
|
log.error(msg)
|
|
return
|
|
|
|
datos = input('Introduce los datos de conexión: ').strip()
|
|
if not datos:
|
|
msg = 'Los datos de conexión son requeridos'
|
|
log.error(msg)
|
|
return
|
|
|
|
opt = util.parse_con(datos)
|
|
if not opt:
|
|
log.error('Datos de conexión incompletos')
|
|
return
|
|
|
|
args = opt.copy()
|
|
if conectar(args):
|
|
if _add_emisor(rfc, util.dumps(opt)) and _crear_tablas(rfc):
|
|
log.info('RFC agregado correctamente...')
|
|
return
|
|
|
|
log.error('No se pudo agregar el RFC')
|
|
return
|
|
|
|
|
|
def _borrar_rfc():
|
|
rfc = input('Introduce el RFC a borrar: ').strip().upper()
|
|
if not rfc:
|
|
msg = 'El RFC es requerido'
|
|
log.error(msg)
|
|
return
|
|
|
|
confirm = input('¿Estás seguro de borrar el RFC?')
|
|
|
|
if _delete_emisor(rfc):
|
|
log.info('RFC borrado correctamente...')
|
|
return
|
|
|
|
log.error('No se pudo borrar el RFC')
|
|
return
|
|
|
|
|
|
def _listar_rfc():
|
|
data = util.get_rfcs()
|
|
for row in data:
|
|
msg = 'RFC: {}\n\t{}'.format(row[0], row[1])
|
|
log.info(msg)
|
|
return
|
|
|
|
def _importar_valores(archivo='', rfc=''):
|
|
if not rfc:
|
|
rfc = input('Introduce el RFC: ').strip().upper()
|
|
if not rfc:
|
|
msg = 'El RFC es requerido'
|
|
log.error(msg)
|
|
return
|
|
|
|
args = util.get_con(rfc)
|
|
if not args:
|
|
return
|
|
|
|
conectar(args)
|
|
|
|
if not archivo:
|
|
archivo = INIT_VALUES
|
|
|
|
log.info('Importando datos...')
|
|
regimen = ''
|
|
rows = util.loads(open(archivo, 'r').read())
|
|
for row in rows:
|
|
log.info('\tImportando tabla: {}'.format(row['tabla']))
|
|
table = globals()[row['tabla']]
|
|
for r in row['datos']:
|
|
try:
|
|
with database_proxy.atomic() as txn:
|
|
table.create(**r)
|
|
except IntegrityError:
|
|
pass
|
|
|
|
log.info('Importación terminada...')
|
|
return
|
|
|
|
|
|
def _importar_socios(rows):
|
|
log.info('\tImportando Clientes...')
|
|
for row in rows:
|
|
try:
|
|
with database_proxy.atomic() as txn:
|
|
Socios.create(**row)
|
|
except IntegrityError:
|
|
msg = '\tSocio existente: {}'.format(row['nombre'])
|
|
log.info(msg)
|
|
log.info('\tClientes importados...')
|
|
return
|
|
|
|
|
|
def _existe_factura(row):
|
|
filtro = (Facturas.uuid==row['uuid'])
|
|
if row['uuid'] is None:
|
|
filtro = (
|
|
(Facturas.serie==row['serie']) &
|
|
(Facturas.folio==row['folio'])
|
|
)
|
|
return Facturas.select().where(filtro).exists()
|
|
|
|
|
|
def _importar_facturas(rows):
|
|
log.info('\tImportando Facturas...')
|
|
for row in rows:
|
|
try:
|
|
detalles = row.pop('detalles')
|
|
impuestos = row.pop('impuestos')
|
|
cliente = row.pop('cliente')
|
|
row['cliente'] = Socios.get(**cliente)
|
|
with database_proxy.atomic() as txn:
|
|
if _existe_factura(row):
|
|
msg = '\tFactura existente: {}{}'.format(
|
|
row['serie'], row['folio'])
|
|
log.info(msg)
|
|
continue
|
|
obj = Facturas.create(**row)
|
|
for detalle in detalles:
|
|
detalle['factura'] = obj
|
|
FacturasDetalle.create(**detalle)
|
|
for impuesto in impuestos:
|
|
imp = SATImpuestos.get(**impuesto['filtro'])
|
|
new = {
|
|
'factura': obj,
|
|
'impuesto': imp,
|
|
'importe': impuesto['importe'],
|
|
}
|
|
FacturasImpuestos.create(**new)
|
|
except IntegrityError as e:
|
|
#~ print (e)
|
|
msg = '\tFactura: id: {}'.format(row['serie'] + str(row['folio']))
|
|
log.error(msg)
|
|
log.info('\tFacturas importadas...')
|
|
return
|
|
|
|
|
|
def _importar_categorias(rows):
|
|
log.info('\tImportando Categorías...')
|
|
for row in rows:
|
|
if row['padre'] is None:
|
|
filters = (
|
|
(Categorias.categoria==row['categoria']) &
|
|
(Categorias.padre.is_null(True))
|
|
)
|
|
else:
|
|
filters = (
|
|
(Categorias.categoria==row['categoria']) &
|
|
(Categorias.padre==row['padre'])
|
|
)
|
|
|
|
if Categorias.exists(filters):
|
|
continue
|
|
|
|
try:
|
|
Categorias.create(**row)
|
|
except IntegrityError:
|
|
msg = '\tCategoria: ({}) {}'.format(row['padre'], row['categoria'])
|
|
log.error(msg)
|
|
|
|
log.info('\tCategorías importadas...')
|
|
return
|
|
|
|
|
|
def _get_id_unidad(unidad):
|
|
try:
|
|
if 'pieza' in unidad.lower():
|
|
unidad = 'pieza'
|
|
obj = SATUnidades.get(SATUnidades.name.contains(unidad))
|
|
except SATUnidades.DoesNotExist:
|
|
msg = '\tNo se encontró la unidad: {}'.format(unidad)
|
|
log.error(msg)
|
|
return unidad
|
|
|
|
return str(obj.id)
|
|
|
|
|
|
def _get_impuestos(impuestos):
|
|
lines = '|'
|
|
for impuesto in impuestos:
|
|
if impuesto['tasa'] == '-2/3':
|
|
tasa = str(round(2/3, 6))
|
|
else:
|
|
tasa = str(round(float(impuesto['tasa']) / 100.0, 6))
|
|
|
|
info = (
|
|
IMPUESTOS.get(impuesto['nombre']),
|
|
impuesto['nombre'],
|
|
impuesto['tipo'][0],
|
|
tasa,
|
|
)
|
|
lines += '|'.join(info)
|
|
return lines
|
|
|
|
|
|
def _generar_archivo_productos(archivo):
|
|
rfc = input('Introduce el RFC: ').strip().upper()
|
|
if not rfc:
|
|
msg = 'El RFC es requerido'
|
|
log.error(msg)
|
|
return
|
|
|
|
args = util.get_con(rfc)
|
|
if not args:
|
|
return
|
|
|
|
conectar(args)
|
|
|
|
log.info('Importando datos...')
|
|
app = util.ImportFacturaLibre(archivo, rfc)
|
|
if not app.is_connect:
|
|
log.error('\t{}'.format(app._error))
|
|
return
|
|
|
|
rows = app.import_productos()
|
|
|
|
p, _, _, _ = util.get_path_info(archivo)
|
|
path_txt = util._join(p, 'productos.txt')
|
|
log.info('\tGenerando archivo: {}'.format(path_txt))
|
|
|
|
fields = (
|
|
'clave',
|
|
'clave_sat',
|
|
'unidad',
|
|
'categoria',
|
|
'descripcion',
|
|
'valor_unitario',
|
|
'existencia',
|
|
'inventario',
|
|
'codigo_barras',
|
|
'cuenta_predial',
|
|
'ultimo_precio',
|
|
'minimo',
|
|
)
|
|
|
|
data = ['|'.join(fields)]
|
|
for row in rows:
|
|
impuestos = row.pop('impuestos', ())
|
|
line = [str(row[r]) for r in fields]
|
|
if line[10] == 'None':
|
|
line[10] = '0.0'
|
|
line[2] = _get_id_unidad(line[2])
|
|
line = '|'.join(line) + _get_impuestos(impuestos)
|
|
data.append(line)
|
|
|
|
with open(path_txt, 'w') as fh:
|
|
fh.write('\n'.join(data))
|
|
|
|
log.info('\tArchivo generado: {}'.format(path_txt))
|
|
return
|
|
|
|
|
|
def _importar_factura_libre(archivo):
|
|
rfc = input('Introduce el RFC: ').strip().upper()
|
|
if not rfc:
|
|
msg = 'El RFC es requerido'
|
|
log.error(msg)
|
|
return
|
|
|
|
args = util.get_con(rfc)
|
|
if not args:
|
|
return
|
|
|
|
conectar(args)
|
|
|
|
log.info('Importando datos...')
|
|
app = util.ImportFacturaLibre(archivo, rfc)
|
|
if not app.is_connect:
|
|
log.error('\t{}'.format(app._error))
|
|
return
|
|
|
|
data = app.import_data()
|
|
|
|
_importar_socios(data['Socios'])
|
|
_importar_facturas(data['Facturas'])
|
|
_importar_categorias(data['Categorias'])
|
|
|
|
log.info('Importación terminada...')
|
|
return
|
|
|
|
|
|
def _importar_productos(archivo):
|
|
rfc = input('Introduce el RFC: ').strip().upper()
|
|
if not rfc:
|
|
msg = 'El RFC es requerido'
|
|
log.error(msg)
|
|
return
|
|
|
|
args = util.get_con(rfc)
|
|
if not args:
|
|
return
|
|
|
|
conectar(args)
|
|
log.info('Importando productos...')
|
|
|
|
fields = (
|
|
'clave',
|
|
'clave_sat',
|
|
'unidad',
|
|
'categoria',
|
|
'descripcion',
|
|
'valor_unitario',
|
|
'existencia',
|
|
'inventario',
|
|
'codigo_barras',
|
|
'cuenta_predial',
|
|
'ultimo_precio',
|
|
'minimo',
|
|
)
|
|
|
|
rows = util.read_file(archivo, 'r').split('\n')
|
|
for i, row in enumerate(rows):
|
|
if i == 0:
|
|
continue
|
|
data = row.split('|')
|
|
|
|
new = {}
|
|
for i, f in enumerate(fields):
|
|
if not len(data[0]):
|
|
continue
|
|
|
|
if i in (2, 3):
|
|
try:
|
|
new[f] = int(data[i])
|
|
except ValueError:
|
|
continue
|
|
elif i in (5, 6, 10, 11):
|
|
new[f] = float(data[i])
|
|
elif i == 7:
|
|
new[f] = bool(data[i])
|
|
else:
|
|
new[f] = data[i]
|
|
|
|
impuestos = data[i + 1:]
|
|
if not impuestos:
|
|
taxes = [SATImpuestos.select().where(SATImpuestos.id==6)]
|
|
else:
|
|
taxes = []
|
|
for i in range(0, len(impuestos), 4):
|
|
w = {
|
|
'key': impuestos[i],
|
|
'name': impuestos[i+1],
|
|
'tipo': impuestos[i+2],
|
|
'tasa': float(impuestos[i+3]),
|
|
}
|
|
taxes.append(SATImpuestos.get_o_crea(w))
|
|
|
|
with database_proxy.transaction():
|
|
try:
|
|
obj = Productos.create(**new)
|
|
obj.impuestos = taxes
|
|
except IntegrityError:
|
|
pass
|
|
|
|
log.info('Importación terminada...')
|
|
return
|
|
|
|
|
|
def _test():
|
|
rfc = input('Introduce el RFC: ').strip().upper()
|
|
if not rfc:
|
|
msg = 'El RFC es requerido'
|
|
log.error(msg)
|
|
return
|
|
|
|
args = util.get_con(rfc)
|
|
if not args:
|
|
return
|
|
|
|
conectar(args)
|
|
|
|
rows = Categorias.select().where(
|
|
Categorias.categoria=='Productos', Categorias.padre.is_null(True)).exists()
|
|
print (rows)
|
|
return
|
|
|
|
|
|
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
|
help_create_tables = 'Crea las tablas en la base de datos'
|
|
help_migrate_db = 'Migra las tablas en la base de datos'
|
|
help_superuser = 'Crea un nuevo super usuario'
|
|
help_change_pass = 'Cambia la contraseña a un usuario'
|
|
help_rfc = 'Agrega un nuevo RFC'
|
|
help_br = 'Elimina un RFC'
|
|
help_lr = 'Listar RFCs'
|
|
|
|
@click.command(context_settings=CONTEXT_SETTINGS)
|
|
@click.option('-bd', '--iniciar-bd',help=help_create_tables,
|
|
is_flag=True, default=False)
|
|
@click.option('-m', '--migrar-bd', help=help_migrate_db,
|
|
is_flag=True, default=False)
|
|
@click.option('-ns', '--nuevo-superusuario', help=help_superuser,
|
|
is_flag=True, default=False)
|
|
@click.option('-cc', '--cambiar-contraseña', help=help_change_pass,
|
|
is_flag=True, default=False)
|
|
@click.option('-rfc', '--rfc', help=help_rfc, is_flag=True, default=False)
|
|
@click.option('-br', '--borrar-rfc', help=help_br, is_flag=True, default=False)
|
|
@click.option('-lr', '--listar-rfc', help=help_lr, is_flag=True, default=False)
|
|
@click.option('-i', '--importar-valores', is_flag=True, default=False)
|
|
@click.option('-a', '--archivo')
|
|
@click.option('-fl', '--factura-libre', is_flag=True, default=False)
|
|
@click.option('-t', '--test', is_flag=True, default=False)
|
|
@click.option('-gap', '--generar-archivo-productos', is_flag=True, default=False)
|
|
@click.option('-ip', '--importar-productos', is_flag=True, default=False)
|
|
def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc,
|
|
borrar_rfc, listar_rfc, importar_valores, archivo, factura_libre, test,
|
|
generar_archivo_productos, importar_productos):
|
|
|
|
opt = locals()
|
|
|
|
if opt['test']:
|
|
_test()
|
|
sys.exit(0)
|
|
|
|
if opt['iniciar_bd']:
|
|
_iniciar_bd()
|
|
sys.exit(0)
|
|
|
|
if opt['migrar_bd']:
|
|
migrate_tables()
|
|
sys.exit(0)
|
|
|
|
if opt['nuevo_superusuario']:
|
|
_agregar_superusuario()
|
|
sys.exit(0)
|
|
|
|
if opt['cambiar_contraseña']:
|
|
_cambiar_contraseña()
|
|
sys.exit(0)
|
|
|
|
if opt['rfc']:
|
|
_agregar_rfc()
|
|
sys.exit(0)
|
|
|
|
if opt['borrar_rfc']:
|
|
_borrar_rfc()
|
|
sys.exit(0)
|
|
|
|
if opt['listar_rfc']:
|
|
_listar_rfc()
|
|
sys.exit(0)
|
|
|
|
if opt['importar_valores']:
|
|
if not opt['archivo']:
|
|
msg = 'Falta la ruta del archivo importar'
|
|
raise click.ClickException(msg)
|
|
if not util.is_file(opt['archivo']):
|
|
msg = 'No es un archivo'
|
|
raise click.ClickException(msg)
|
|
|
|
_importar_valores(opt['archivo'])
|
|
sys.exit(0)
|
|
|
|
if opt['factura_libre']:
|
|
if not opt['archivo']:
|
|
msg = 'Falta la ruta de la base de datos'
|
|
raise click.ClickException(msg)
|
|
if not util.is_file(opt['archivo']):
|
|
msg = 'No es un archivo'
|
|
raise click.ClickException(msg)
|
|
_, _, _, ext = util.get_path_info(opt['archivo'])
|
|
if ext != '.sqlite':
|
|
msg = 'No es una base de datos'
|
|
raise click.ClickException(msg)
|
|
|
|
_importar_factura_libre(opt['archivo'])
|
|
sys.exit(0)
|
|
|
|
if opt['generar_archivo_productos']:
|
|
if not opt['archivo']:
|
|
msg = 'Falta la ruta de la base de datos'
|
|
raise click.ClickException(msg)
|
|
if not util.is_file(opt['archivo']):
|
|
msg = 'No es un archivo'
|
|
raise click.ClickException(msg)
|
|
_, _, _, ext = util.get_path_info(opt['archivo'])
|
|
if ext != '.sqlite':
|
|
msg = 'No es una base de datos'
|
|
raise click.ClickException(msg)
|
|
|
|
_generar_archivo_productos(opt['archivo'])
|
|
sys.exit(0)
|
|
|
|
if opt['importar_productos']:
|
|
if not opt['archivo']:
|
|
msg = 'Falta la ruta del archivo'
|
|
raise click.ClickException(msg)
|
|
if not util.is_file(opt['archivo']):
|
|
msg = 'No es un archivo'
|
|
raise click.ClickException(msg)
|
|
_, _, _, ext = util.get_path_info(opt['archivo'])
|
|
if ext != '.txt':
|
|
msg = 'No es un archivo de texto'
|
|
raise click.ClickException(msg)
|
|
|
|
_importar_productos(opt['archivo'])
|
|
sys.exit(0)
|
|
|
|
return
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|