#!/usr/bin/env python
from decimal import Decimal
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, \
CANCEL_SIGNATURE
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']:
if opt != 'bdfl':
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}
def config_main():
try:
obj = Emisor.select()[0]
except IndexError:
obj = None
punto_de_venta = util.get_bool(Configuracion.get_('chk_usar_punto_de_venta'))
data = {
'empresa': 'Empresa Libre',
'punto_de_venta': punto_de_venta
}
if not obj is None:
titulo = 'Empresa Libre - {}'
data['empresa'] = titulo.format(obj.nombre)
return data
def config_timbrar():
try:
obj = Emisor.select()[0]
except IndexError:
return {'cfdi_donativo': False}
conf = {
'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'),
'cfdi_donativo': obj.es_ong,
'cfdi_ine': Configuracion.get_('chk_config_ine'),
}
return conf
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',
'txt_plantilla_donataria',
)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
elif keys['fields'] == 'configotros':
fields = (
'chk_config_anticipo',
'chk_config_cuenta_predial',
'chk_config_ine',
'chk_usar_punto_de_venta',
)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
elif keys['fields'] == 'productos':
fields = (
'chk_config_cuenta_predial',
)
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():
#~ print (k, v)
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 TipoDireccion(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoTitulo(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoTelefono(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoCorreo(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoPariente(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoResponsable(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoMovimientoAlumno(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
class TipoMovimientoAlmacen(BaseModel):
nombre = TextField(unique=True)
class Meta:
order_by = ('nombre',)
def __str__(self):
return self.nombre
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')
@classmethod
def add(cls, values):
values['contraseña'] = values.pop('contra')
try:
Usuarios.create(**values)
return {'ok': True}
except Exception as e:
log.error(e)
msg = 'Ocurrio un error, consulta a soporte técnico'
return {'ok': False, 'msg': msg}
@classmethod
def remove(cls, id):
q = Usuarios.delete().where(Usuarios.id==int(id))
return bool(q.execute())
@classmethod
def edit(self, values):
id = int(values.pop('id'))
try:
values['contraseña'] = values.pop('contra')
q = Usuarios.update(**values).where(Usuarios.id==id)
result = {'ok': bool(q.execute())}
except IntegrityError:
msg = 'El usuario ya existe'
result = {'ok': False, 'msg': msg}
return result
@classmethod
def actualizar(self, values, user):
id = int(values['id'])
v = {'0': False, '1': True}
if values['field'] == 'es_superusuario' and not user.es_superusuario:
msg = 'Solo un super usuario puede hacer este cambio'
return {'ok': False, 'msg': msg}
if values['field'] == 'es_activo':
q = (Usuarios
.update(**{'es_activo': v[values['value']]})
.where(Usuarios.id==id))
result = bool(q.execute())
elif values['field'] == 'es_admin':
q = (Usuarios
.update(**{'es_admin': v[values['value']]})
.where(Usuarios.id==id))
result = bool(q.execute())
elif values['field'] == 'es_superusuario':
q = (Usuarios
.update(**{'es_superusuario': v[values['value']]})
.where(Usuarios.id==id))
result = bool(q.execute())
return {'ok': result}
@classmethod
def get_(cls, user):
if user.es_superusuario:
rows = Usuarios.select().dicts()
else:
filters = (Usuarios.es_superusuario == False)
rows = Usuarios.select().where(filters).dicts()
for row in rows:
del row['contraseña']
return tuple(rows)
class Registro(BaseModel):
usuario = TextField()
accion = TextField(default='')
tabla = TextField(default='')
fecha = DateTimeField(default=util.now)
def __str__(self):
t = '{} {}-{} ({})'
return t.format(self.usuario, self.accion, self.tabla, self.fecha)
class Meta:
order_by = ('usuario', 'fecha')
@classmethod
def add(cls, values):
try:
Registro.create(**values)
return
except:
return
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,
'REPO': obj.token_soporte,
}
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)
auth = Emisor.get_auth()
data = cert.validate(values['contra'], session['rfc'], auth)
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_(self):
rows = SATFormaPago.select().dicts()
return tuple(rows)
@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)
@classmethod
def actualizar(self, values):
id = int(values['id'])
if values['field'] == 'activo':
v = {'0': False, '1': True}
q = (SATFormaPago
.update(**{'activo': v[values['value']]})
.where(SATFormaPago.id==id))
result = bool(q.execute())
elif values['field'] == 'default':
q = SATFormaPago.update(**{'default': False})
q.execute()
v = {'false': False, 'true': True}
q = (SATFormaPago
.update(**{'default': v[values['value']]})
.where(SATFormaPago.id==id))
result = bool(q.execute())
return {'ok': result}
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 add(self, values):
try:
SATMonedas.create(**values)
return {'ok': True}
except:
return {'ok': False}
@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():
try:
q = SATImpuestos.delete().where(SATImpuestos.id==id)
return bool(q.execute())
except IntegrityError:
return False
@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 SATNivelesEducativos(BaseModel):
name = TextField(index=True)
class Meta:
order_by = ('name',)
def __str__(self):
return self.name
class NivelesEducativos(BaseModel):
nombre = TextField()
autorizacion = TextField(default='')
class Meta:
order_by = ('nombre',)
indexes = (
(('nombre', 'autorizacion'), True),
)
def __str__(self):
return '{} ({})'.format(self.nombre, self.autorizacion)
class Grupos(BaseModel):
nivel = ForeignKeyField(NivelesEducativos)
grado = TextField(default='')
nombre = TextField(default='')
class Meta:
order_by = ('nivel', 'grado', 'nombre')
indexes = (
(('nivel', 'grado', 'nombre'), True),
)
def __str__(self):
return '{} {} {}'.format(self.nivel.nombre, self.grado, self.nombre)
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 actualizar_saldo(cls, id, saldo):
fields = {'saldo': saldo}
q = CuentasBanco.update(**fields).where(CuentasBanco.id==id)
return bool(q.execute())
@classmethod
def get_saldo(cls, id):
try:
obj = CuentasBanco.get(CuentasBanco.id==id)
return obj.saldo
except CuentasBanco.DoesNotExist:
return 0
@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,
'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'])
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)
cancelado = BooleanField(default=False)
conciliado = BooleanField(default=False)
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',)
def _ultimo_saldo(self, cuenta, fecha):
query = (MovimientosBanco
.select()
.where(
(MovimientosBanco.cuenta==cuenta) &
(MovimientosBanco.fecharow.fecha) &
(MovimientosBanco.cancelado==False))
)
saldo = round(Decimal(row.saldo), DECIMALES)
for mov in query:
mov.saldo = saldo + mov.deposito - mov.retiro
mov.save()
saldo = mov.saldo
CuentasBanco.actualizar_saldo(row.cuenta, saldo)
return saldo
@classmethod
def add(cls, values):
ids = values.pop('ids', '')
actualizar = False
if 'saldo' in values:
saldo = values['saldo']
else:
actualizar = True
hora = values.pop('hora')
values['fecha'] = '{}T{}'.format(values['fecha'][:10], hora)
values['cuenta'] = int(values['cuenta'])
values['retiro'] = util.get_float(values['retiro'])
values['deposito'] = util.get_float(values['deposito'])
values['forma_pago'] = int(values['forma_pago'])
ultimo_saldo = cls._ultimo_saldo(
cls, values['cuenta'], values['fecha'])
values['saldo'] = \
ultimo_saldo - values['retiro'] + values['deposito']
with database_proxy.transaction():
try:
obj = MovimientosBanco.create(**values)
except IntegrityError:
msg = 'Este movimiento ya existe'
return {'ok': False, 'msg': msg}
if actualizar:
saldo = cls._actualizar_saldos(cls, obj)
if ids:
FacturasPagos.add(obj, util.loads(ids))
return {'ok': True, 'saldo': saldo}
@classmethod
def remove(cls, id):
try:
obj = MovimientosBanco.get(MovimientosBanco.id==id)
except MovimientosBanco.DoesNotExist:
return False
if obj.conciliado or obj.cancelado:
return False
with database_proxy.transaction():
obj.cancelado = True
obj.save()
FacturasPagos.cancelar(obj)
obj = cls._movimiento_anterior(cls, obj.cuenta, obj.fecha)
cls._actualizar_saldos(cls, obj)
return 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) &
(MovimientosBanco.cancelado==False)
)
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) &
(MovimientosBanco.cancelado==False)
)
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)
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
fecha_timbrado = DateTimeField(null=True)
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_key(self, id):
if id is None:
return
return SATUsoCfdi.get(SATUsoCfdi.id==id).key
@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'] = SATUsoCfdi.get_key(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 Contactos(BaseModel):
socio = ForeignKeyField(Socios)
titulo = ForeignKeyField(TipoTitulo)
foto = TextField(default='')
nombre = TextField(index=True)
paterno = TextField(index=True)
materno = TextField(default='')
fecha_nacimiento = DateField(null=True)
notas = TextField(default='')
class Meta:
order_by = ('socio', 'nombre')
indexes = (
(('socio', 'nombre', 'paterno', 'materno'), True),
)
class ContactoDirecciones(BaseModel):
contacto = ForeignKeyField(Contactos)
tipo = ForeignKeyField(TipoDireccion)
direccion = TextField()
class Meta:
order_by = ('contacto',)
indexes = (
(('contacto', 'tipo', 'direccion'), True),
)
class ContactoTelefonos(BaseModel):
contacto = ForeignKeyField(Contactos)
tipo = ForeignKeyField(TipoTelefono)
telefono = TextField()
class Meta:
order_by = ('contacto',)
indexes = (
(('contacto', 'tipo', 'telefono'), True),
)
class ContactoCorreos(BaseModel):
contacto = ForeignKeyField(Contactos)
tipo = ForeignKeyField(TipoCorreo)
correo = TextField()
class Meta:
order_by = ('contacto',)
indexes = (
(('contacto', 'tipo', 'correo'), True),
)
class Alumnos(BaseModel):
rfc = TextField(null=True)
curp = TextField(index=True, unique=True)
foto = TextField(default='')
nombre = TextField(index=True)
paterno = TextField(index=True)
materno = 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='')
codigo_postal = TextField(default='')
notas = TextField(default='')
telefonos = TextField(default='')
correos = TextField(default='')
es_activo = BooleanField(default=True)
fecha_alta = DateField(default=util.now)
fecha_nacimiento = DateField(null=True)
factura = ForeignKeyField(Socios, null=True)
grupo = ForeignKeyField(Grupos, null=True)
def __str__(self):
t = '{} {} {}'
return t.format(self.nombre, self.paterno, self.materno)
class Meta:
order_by = ('nombre', 'paterno')
class AlumnosParientes(BaseModel):
alumno = ForeignKeyField(Alumnos)
tipo_pariente = ForeignKeyField(TipoPariente)
foto = TextField(default='')
nombre = TextField(index=True)
paterno = TextField(index=True)
materno = TextField(default='')
fecha_nacimiento = DateField(null=True)
puede_recoger = BooleanField(default=False)
class Meta:
order_by = ('alumno',)
class ParienteDirecciones(BaseModel):
pariente = ForeignKeyField(AlumnosParientes)
tipo = ForeignKeyField(TipoDireccion)
direccion = TextField()
class Meta:
order_by = ('pariente',)
class ParienteTelefonos(BaseModel):
pariente = ForeignKeyField(AlumnosParientes)
tipo = ForeignKeyField(TipoTelefono)
telefono = TextField()
class Meta:
order_by = ('pariente',)
class ParienteCorreos(BaseModel):
pariente = ForeignKeyField(AlumnosParientes)
tipo = ForeignKeyField(TipoCorreo)
correo = TextField()
class Meta:
order_by = ('pariente',)
class Almacenes(BaseModel):
nombre = TextField(default='')
ubicacion = TextField(default='')
class Meta:
order_by = ('nombre',)
class Productos(BaseModel):
almacen = ForeignKeyField(Almacenes, null=True)
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):
clave = values.get('id', '')
if clave:
row = (Productos
.select(
Productos.id,
Productos.clave,
Productos.clave_sat,
Productos.descripcion,
SATUnidades.name.alias('unidad'),
Productos.valor_unitario,
Productos.descuento)
.join(SATUnidades).switch(Productos)
.where((Productos.id==clave) | (Productos.clave==clave))
.dicts())
if len(row):
id = row[0]['id']
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.clave_sat,
Productos.descripcion,
SATUnidades.name.alias('unidad'),
Productos.valor_unitario)
.join(SATUnidades)
.switch(Productos)
.where((Productos.descripcion.contains(name)) |
(Productos.clave.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,
Productos.cuenta_predial,
)
.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 RangosPrecios(BaseModel):
producto = ForeignKeyField(Productos)
descripcion = TextField(default='')
desde = DecimalField(default=0.0, max_digits=18, decimal_places=6,
auto_round=True)
hasta = 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 = IntegerField(default=0)
class Meta:
order_by = ('producto',)
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='')
saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
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):
if CANCEL_SIGNATURE:
return cls._cancel_signature(cls, id)
return cls._cancel_xml(cls, id)
def _cancel_xml(self, id):
msg = 'Factura cancelada correctamente'
auth = Emisor.get_auth()
certificado = Certificado.select()[0]
obj = Facturas.get(Facturas.id==id)
data, result = util.cancel_xml(auth, obj.uuid, certificado)
if data['ok']:
obj.estatus = 'Cancelada'
obj.error = ''
obj.cancelada = True
obj.fecha_cancelacion = result['Fecha']
obj.acuse = result['Acuse']
self._actualizar_saldo_cliente(self, obj, True)
else:
obj.error = data['msg']
obj.save()
return data
def _cancel_signature(self, id):
msg = 'Factura cancelada correctamente'
auth = Emisor.get_auth()
certificado = Certificado.select()[0]
obj = Facturas.get(Facturas.id==id)
data, result = util.cancel_signature(
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']
self._actualizar_saldo_cliente(self, obj, True)
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)
cls._sync_xml(cls, obj)
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, emisor):
values = {}
values['fechadof'] = str(emisor.fecha_dof)
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, sync=True):
try:
emisor = Emisor.select()[0]
except IndexError:
return b'', 'sin_datos_de_emisor.pdf'
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, emisor)
data = util.get_data_from_xml(obj, values)
doc = util.to_pdf(data, emisor.rfc)
if sync:
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
cls._sync_pdf(cls, doc, name, target)
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 _sync(self, id, auth):
return Facturas.sync(id, auth)
@util.run_in_thread
def _sync_pdf(self, pdf, name_pdf, target):
auth = Emisor.get_auth()
files = (
(pdf, name_pdf, target),
)
util.sync_cfdi(auth, files)
return
@util.run_in_thread
def _sync_xml(self, obj):
emisor = Emisor.select()[0]
auth = Emisor.get_auth()
name_xml = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
files = (
(obj.xml, name_xml, target),
)
util.sync_cfdi(auth, files)
return
@util.run_in_thread
def _actualizar_saldo_cliente(self, invoice, cancel=False):
if invoice.tipo_comprobante == 'T':
return
if invoice.donativo and invoice.forma_pago == '12':
return
importe = invoice.total_mn
if invoice.tipo_comprobante == 'E':
importe *= -1
if cancel:
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}
@classmethod
def sync(cls, id, auth):
obj = Facturas.get(Facturas.id==id)
if obj.uuid is None:
msg = 'La factura no esta timbrada'
return
emisor = Emisor.select()[0]
pdf, name_pdf = cls.get_pdf(id, auth['RFC'], False)
name_xml = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
files = (
(obj.xml, name_xml, target),
(pdf, name_pdf, target),
)
util.sync_cfdi(auth, files)
return
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_por_pagar(self, ids):
filtros = (
(Facturas.cancelada==False) &
(Facturas.uuid.is_null(False)) &
(Facturas.tipo_comprobante=='I') &
(Facturas.saldo>0)
)
if ids:
filtros &= (Facturas.id.not_in(ids))
rows = tuple(Facturas
.select(
Facturas.id,
Facturas.serie,
Facturas.folio,
Facturas.uuid,
Facturas.fecha,
Facturas.tipo_comprobante,
Facturas.estatus,
Socios.nombre.alias('cliente'),
Facturas.total,
Facturas.saldo,
)
.where(filtros)
.join(Socios)
.switch(Facturas)
.dicts()
)
return {'ok': True, 'rows': rows}
return
def _get_opt(self, values):
if values['opt'] == 'porpagar':
return self._get_por_pagar(self, util.loads(values['ids']))
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
locales_traslados = 0
locales_retenciones = 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['cuenta_predial'] = p.cuenta_predial
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'] = round(descuento * cantidad, DECIMALES)
product['precio_final'] = precio_final
product['importe'] = round(cantidad * valor_unitario, DECIMALES)
descuento_cfdi += product['descuento']
subtotal += product['importe']
FacturasDetalle.create(**product)
base = product['importe'] - product['descuento']
for tax in p.impuestos:
impuesto_producto = round(float(tax.tasa) * base, DECIMALES)
if tax.tipo == 'T' and tax.key != '000':
total_trasladados = (total_trasladados or 0) + impuesto_producto
elif tax.tipo == 'R' and tax.key != '000':
total_retenciones = (total_retenciones or 0) + impuesto_producto
elif tax.tipo == 'T' and tax.key == '000':
locales_traslados += impuesto_producto
elif tax.tipo == 'R' and tax.key == '000':
locales_retenciones += impuesto_producto
if tax.id in totals_tax:
totals_tax[tax.id].base += base
totals_tax[tax.id].suma_impuestos += impuesto_producto
else:
tax.base = base
tax.suma_impuestos = impuesto_producto
totals_tax[tax.id] = tax
for tax in totals_tax.values():
# ~ if tax.tipo == 'E' or tax.tipo == 'R':
if tax.tipo == 'E':
continue
# ~ import_tax = round(float(tax.tasa) * tax.importe, DECIMALES)
# ~ if tax.key == '000':
# ~ locales_traslados += import_tax
# ~ else:
# ~ 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.base,
'importe': tax.suma_impuestos,
}
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)
# ~ if tax.key == '000':
# ~ locales_retenciones += import_tax
# ~ else:
# ~ total_retenciones = (total_retenciones or 0) + import_tax
# ~ invoice_tax = {
# ~ 'factura': invoice.id,
# ~ 'impuesto': tax.id,
# ~ 'base': tax.base,
# ~ 'importe': tax.suma_impuestos,
# ~ }
# ~ FacturasImpuestos.create(**invoice_tax)
total = subtotal - descuento_cfdi + \
(total_trasladados or 0) - (total_retenciones or 0) \
+ locales_traslados - locales_retenciones
total_mn = round(total * invoice.tipo_cambio, DECIMALES)
data = {
'subtotal': subtotal,
'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
def _guardar_ine(self, invoice, valores):
if not valores:
return
data = {
'factura': invoice,
'nombre': 'ine',
'valores': valores,
}
FacturasComplementos.create(**data)
return
@classmethod
def add(cls, values):
productos = util.loads(values.pop('productos'))
relacionados = util.loads(values.pop('relacionados'))
ine = values.pop('ine', {})
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'])
values['donativo'] = util.get_bool(values['donativo'])
with database_proxy.atomic() as txn:
obj = Facturas.create(**values)
totals = cls._calculate_totals(cls, obj, productos)
cls._guardar_relacionados(cls, obj, relacionados)
cls._guardar_ine(cls, obj, ine)
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.saldo = 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, auth):
emisor = Emisor.select()[0]
certificado = Certificado.select()[0]
comprobante = {}
relacionados = {}
donativo = {}
complementos = FacturasComplementos.get_(invoice)
if invoice.donativo:
donativo['noAutorizacion'] = emisor.autorizacion
donativo['fechaAutorizacion'] = str(emisor.fecha_autorizacion)
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,
}
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)
if row.cuenta_predial:
concepto['CuentaPredial'] = row.cuenta_predial
taxes = {}
traslados = []
retenciones = []
for impuesto in row.producto.impuestos:
if impuesto.tipo == 'E':
continue
if impuesto.key == '000':
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)
impuestos = {}
traslados = []
retenciones = []
total_locales_trasladados = 0
total_locales_retenciones = 0
locales_trasladados = []
locales_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:
if tax.impuesto.key == '000':
tasa = str(round(tax.impuesto.tasa * 100, 2))
simporte = FORMAT.format(tax.importe)
if tax.impuesto.tipo == 'T':
traslado = {
'ImpLocTrasladado': tax.impuesto.name,
'TasadeTraslado': tasa,
'Importe': simporte,
}
locales_trasladados.append(traslado)
total_locales_trasladados += tax.importe
else:
retencion = {
'ImpLocRetenido': tax.impuesto.name,
'TasadeRetencion': tasa,
'Importe': simporte,
}
locales_retenciones.append(retencion)
total_locales_retenciones += tax.importe
continue
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
impuestos['total_locales_trasladados'] = ''
if total_locales_trasladados:
impuestos['total_locales_trasladados'] = \
FORMAT.format(total_locales_trasladados)
impuestos['total_locales_retenciones'] = ''
if total_locales_retenciones:
impuestos['total_locales_retenciones'] = \
FORMAT.format(total_locales_retenciones)
impuestos['locales_trasladados'] = locales_trasladados
impuestos['locales_retenciones'] = locales_retenciones
data = {
'comprobante': comprobante,
'relacionados': relacionados,
'emisor': emisor,
'receptor': receptor,
'conceptos': conceptos,
'impuestos': impuestos,
'donativo': donativo,
'complementos': complementos,
}
return util.make_xml(data, certificado, auth)
@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):
auth = Emisor.get_auth()
obj = Facturas.get(Facturas.id == id)
obj.xml = cls._make_xml(cls, obj, auth)
obj.estatus = 'Generada'
obj.save()
enviar_correo = util.get_bool(Configuracion.get_('correo_directo'))
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)
cls._sync(cls, id, auth)
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
data['donativo'] = obj['donativo']
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'] = str(data['comprobante']['folio'])
data['comprobante']['seriefolio'] = '{}-{}'.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'] = {}
data['donataria'] = {}
data['ine'] = {}
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, data['emisor']['rfc'])
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['cuenta_predial'] = p.cuenta_predial
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 FacturasComplementos(BaseModel):
factura = ForeignKeyField(Facturas)
nombre = TextField(default='')
valores = TextField(default='')
class Meta:
order_by = ('factura',)
@classmethod
def get_(cls, factura):
query = (FacturasComplementos
.select()
.where(FacturasComplementos.factura==factura)
)
return {r.nombre: util.loads(r.valores) for r in query}
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
if p.cuenta_predial:
info = '\nCuenta Predial Número: {}'.format(p.cuenta_predial)
producto['descripcion'] += info
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):
movimiento = ForeignKeyField(MovimientosBanco)
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 = (
(('movimiento', 'factura', 'numero'), True),
)
def _movimiento_anterior(self, mov, id):
query = (FacturasPagos
.select()
.where(FacturasPagos.factura==id)
)
if len(query):
return query[-1], len(query) + 1
else:
return None, 1
def _actualizar_saldo_cliente(self, cliente, importe):
q = (Socios
.update(saldo_cliente=Socios.saldo_cliente + importe)
.where(Socios.id==cliente.id)
)
return bool(q.execute())
def _actualizar_saldos(self, factura, saldo_anterior):
query = (FacturasPagos
.select()
.where(FacturasPagos.factura==factura)
)
saldo = saldo_anterior
for i, row in enumerate(query):
if not saldo_anterior:
saldo_anterior = row.saldo_anterior
row.numero = i + 1
row.saldo_anterior = saldo_anterior
row.saldo = saldo_anterior - row.importe
row.save()
saldo_anterior = row.saldo
saldo = row.saldo
factura.saldo = saldo
factura.pagada = False
factura.save()
return
@classmethod
def cancelar(cls, mov):
query = (FacturasPagos
.select()
.where(FacturasPagos.movimiento==mov)
)
for row in query:
cls._actualizar_saldo_cliente(cls, row.factura.cliente, row.importe)
factura = row.factura
saldo_anterior = 0
if row.numero == 1:
saldo_anterior = row.saldo_anterior
row.delete_instance()
cls._actualizar_saldos(cls, factura, saldo_anterior)
return
@classmethod
def add(cls, mov, ids):
for i, importe in ids.items():
fac = Facturas.get(Facturas.id==int(i))
mov_ant, numero = cls._movimiento_anterior(cls, mov, fac)
nuevo = {
'movimiento': mov,
'factura': fac,
'numero': numero,
'importe': importe,
}
if mov_ant is None:
nuevo['saldo_anterior'] = float(fac.saldo)
else:
nuevo['saldo_anterior'] = float(mov_ant.saldo)
nuevo['saldo'] = nuevo['saldo_anterior'] - importe
FacturasPagos.create(**nuevo)
fac.saldo = nuevo['saldo']
if nuevo['saldo'] == 0:
fac.pagada = True
fac.save()
cls._actualizar_saldo_cliente(cls, fac.cliente, importe * -1)
return
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
class CamposPersonalizados(BaseModel):
nombre = TextField()
slug = TextField(unique=True)
class Meta:
order_by = ('nombre',)
class FacturasPersonalizados(BaseModel):
factura = ForeignKeyField(Facturas)
campo = TextField()
valor = TextField()
class Meta:
order_by = ('factura',)
class Tickets(BaseModel):
cliente = ForeignKeyField(Socios, null=True)
serie = TextField(default='')
folio = IntegerField(default=0)
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
forma_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)
total = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
total_trasladados = DecimalField(
max_digits=20, decimal_places=6, auto_round=True, null=True)
estatus = TextField(default='Generado')
notas = TextField(default='')
factura = ForeignKeyField(Facturas, null=True)
cancelado = BooleanField(default=False)
vendedor = TextField(default='')
comision = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
cambio = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
class Meta:
order_by = ('fecha',)
class TicketsDetalle(BaseModel):
ticket = ForeignKeyField(Tickets)
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)
class Meta:
order_by = ('ticket',)
class TicketsImpuestos(BaseModel):
ticket = ForeignKeyField(Tickets)
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 = ('ticket',)
indexes = (
(('ticket', 'impuesto'), True),
)
class SeriesProductos(BaseModel):
factura = ForeignKeyField(FacturasDetalle, null=True)
ticket = ForeignKeyField(TicketsDetalle, null=True)
serie = TextField(default='')
class Meta:
order_by = ('serie',)
def authenticate(args):
respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''}
values = util.get_con(args['rfc'])
if not values:
return respuesta, None
conectar(values)
try:
obj = Usuarios.get(usuario=args['usuario'], es_activo=True)
except Usuarios.DoesNotExist:
return respuesta, None
except ProgrammingError as e:
log.error(e)
return respuesta, None
if not obj.contraseña.check_password(args['contra']):
return respuesta, None
obj.ultimo_ingreso = util.now()
obj.save()
respuesta['msg'] = ''
respuesta['login'] = True
respuesta['user'] = str(obj)
respuesta['super'] = obj.es_superusuario
#~ respuesta['admin'] = obj.es_superusuario or obj.es_admin
return respuesta, obj
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_monedas(key):
return util.get_sat_monedas(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', '
'),
'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, Registro, CamposPersonalizados,
Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos,
FacturasRelacionadas, FacturasComplementos, FacturasPersonalizados,
SeriesProductos, Almacenes, Productos, RangosPrecios,
PreFacturas, PreFacturasDetalle, PreFacturasImpuestos,
PreFacturasRelacionadas, Tickets, TicketsDetalle, TicketsImpuestos,
SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes,
SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos,
SATNivelesEducativos,
Socios, Contactos, ContactoCorreos, ContactoDirecciones,
ContactoTelefonos,
Tags, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco,
TipoCorreo, TipoDireccion, TipoPariente, TipoResponsable, TipoTelefono,
TipoTitulo, TipoMovimientoAlumno, TipoMovimientoAlmacen,
CfdiPagos, NivelesEducativos, Alumnos, AlumnosParientes, Grupos,
ParienteDirecciones, ParienteTelefonos, ParienteCorreos,
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 get_empresas():
data = util.get_rfcs()
rows = []
for row in data:
rows.append({'delete': '-', 'rfc': row[0]})
return tuple(rows)
def empresa_agregar(rfc):
rfc = rfc.upper()
if util.get_con(rfc):
msg = 'El RFC ya esta dado de alta'
return {'ok': False, 'msg': msg}
user = rfc.replace('&', '').lower()
if not util.crear_rol(user):
msg = 'No se pudo crear el usuario, es probable que ya exista'
return {'ok': False, 'msg': msg}
if not util.crear_db(user):
msg = 'No se pudo crear la base de datos'
return {'ok': False, 'msg': msg}
args = {
"type": "postgres",
"name": user,
"user": user,
"password": user,
}
if not conectar(args.copy()):
msg = 'No se pudo conectar a la base de datos'
return {'ok': False, 'msg': msg}
if not _add_emisor(rfc, util.dumps(args)):
msg = 'No se pudo guardar el nuevo emisor'
return {'ok': False, 'msg': msg}
if not _crear_tablas(rfc):
msg = 'No se pudo crear las tablas'
return {'ok': False, 'msg': msg}
msg = 'Emisor dado de alta correctamente'
row = {'delete': '-', 'rfc': rfc}
return {'ok': True, 'msg': msg, 'row': row}
def empresa_borrar(rfc):
return _delete_emisor(rfc)
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:
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:
with database_proxy.atomic() as txn:
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_bdfl():
try:
emisor = Emisor.select()[0]
except IndexError:
msg = 'Configura primero al emisor'
return {'ok': False, 'msg': msg}
name = '{}.sqlite'.format(emisor.rfc.lower())
path = util._join('/tmp', name)
log.info('Importando datos...')
app = util.ImportFacturaLibre(path, emisor.rfc)
if not app.is_connect:
msg = app._error
log.error('\t{}'.format(msg))
return {'ok': False, 'msg': msg}
data = app.import_data()
_importar_socios(data['Socios'])
_importar_facturas(data['Facturas'])
_importar_categorias(data['Categorias'])
msg = 'Importación terminada...'
log.info(msg)
return {'ok': True, 'msg': msg}
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)
@click.option('-bk', '--backup-dbs', 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, backup_dbs):
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)
if opt['backup_dbs']:
util.backup_dbs()
return
if __name__ == '__main__':
main()