empresa-libre/source/app/models/main.py

1603 lines
49 KiB
Python

#!/usr/bin/env python
import sqlite3
import click
from peewee import *
from playhouse.fields import PasswordField, ManyToManyField
from playhouse.shortcuts import case
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
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
class Configuracion(BaseModel):
clave = TextField()
valor = TextField(default='')
class Meta:
order_by = ('clave',)
indexes = (
(('clave', 'valor'), True),
)
class Tags(BaseModel):
tag = TextField(index=True, unique=True)
class Meta:
order_by = ('tag',)
class Usuarios(BaseModel):
usuario = TextField(unique=True)
nombre = TextField(default='')
apellidos = TextField(default='')
correo = TextField(default='')
contraseña = PasswordField()
es_superusuario = BooleanField(default=False)
es_admin = BooleanField(default=False)
es_activo = BooleanField(default=True)
fecha_ingreso = DateTimeField(default=util.now)
ultimo_ingreso = DateTimeField(null=True)
def __str__(self):
t = '{} {} ({})'
return t.format(self.nombre, self.apellidos, self.usuario)
class Meta:
order_by = ('nombre', 'apellidos')
class SATRegimenes(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(index=True)
activo = BooleanField(default=False)
default = BooleanField(default=False)
fisica = BooleanField(default=False)
moral = BooleanField(default=False)
class Meta:
order_by = ('-default', 'name',)
indexes = (
(('key', 'name'), True),
)
@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='')
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_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,
'regimenes': [row.id for row in obj.regimenes]
}
else:
row['emisor'] = {'emisor_rfc': rfc}
return {'ok': True, 'row': row}
@classmethod
def get_regimenes(cls):
obj = Emisor.select()[0]
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['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['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_tmp = BlobField(null=True)
key_enc = TextField(default='')
cer = BlobField(null=True)
cer_tmp = 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):
if Certificado.select().count():
obj = Certificado.select()[0]
else:
obj = Certificado()
return obj
@classmethod
def add(cls, file_object):
obj = cls.get_(cls)
if file_object.filename.endswith('key'):
obj.key_tmp = file_object.file.read()
elif file_object.filename.endswith('cer'):
obj.cer_tmp = file_object.file.read()
obj.save()
return {'status': 'server'}
@classmethod
def validate(cls, values, session):
row = {}
result = False
obj = cls.get_(cls)
cert = util.Certificado(obj.key_tmp, obj.cer_tmp)
data = cert.validate(values['contra'], session['rfc'])
if data:
msg = 'Certificado guardado correctamente'
q = Certificado.update(**data).where(Certificado.id==obj.id)
if q.execute():
result = True
row = {
'cert_rfc': data['rfc'],
'cert_serie': data['serie'],
'cert_desde': data['desde'],
'cert_hasta': data['hasta'],
}
else:
msg = cert.error
obj.key_tmp = None
obj.cer_tmp = None
obj.save()
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 get_all(cls):
rows = (Categorias.select(
Categorias.id,
Categorias.categoria.alias('value'),
Categorias.padre.alias('parent_id'))
).dicts()
return tuple(rows)
class CondicionesPago(BaseModel):
condicion = TextField(unique=True)
class Meta:
order_by = ('condicion',)
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),
)
@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),
)
@classmethod
def get_activos(cls, values):
field = SATFormaPago.id
if values:
field = SATFormaPago.key.alias('id')
rows = (SATFormaPago
.select(field, SATFormaPago.name.alias('value'))
.where(SATFormaPago.activo==True)
.dicts()
)
return tuple(rows)
class SATAduanas(BaseModel):
key = TextField(unique=True, index=True)
name = TextField(default='', index=True)
activo = BooleanField(default=False)
default = BooleanField(default=False)
class Meta:
order_by = ('-default', 'name',)
indexes = (
(('key', 'name'), True),
)
class SATMonedas(BaseModel):
key = TextField(unique=True, index=True)
name = TextField(default='', index=True)
activo = BooleanField(default=False)
default = BooleanField(default=False)
class Meta:
order_by = ('-default', 'name',)
indexes = (
(('key', 'name'), True),
)
@classmethod
def get_activos(cls):
rows = (SATMonedas
.select(
SATMonedas.key.alias('id'),
SATMonedas.name.alias('value'))
.where(SATMonedas.activo==True)
.dicts()
)
return tuple(rows)
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_activos(self):
rows = SATImpuestos.select().where(SATImpuestos.activo==True).dicts()
return tuple(rows)
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),
)
@classmethod
def get_activos(cls, values):
field = SATUsoCfdi.id
if values:
field = SATUsoCfdi.key.alias('id')
rows = (SATUsoCfdi
.select(
field,
SATUsoCfdi.name.alias('value'),
SATUsoCfdi.fisica,
SATUsoCfdi.moral,
)
.where(SATUsoCfdi.activo==True)
.dicts()
)
return tuple(rows)
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='')
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'])
fields['uso_cfdi'] = fields.pop('uso_cfdi_socio', 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):
if values:
id = int(values['id'])
row = Socios.select().where(Socios.id==id).dicts()[0]
row['uso_cfdi_socio'] = row.pop('uso_cfdi')
return row
rows = Socios.select(Socios.id, Socios.rfc, Socios.nombre).dicts()
return {'ok': True, 'rows': 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).switch(Socios)
.join(SATUsoCfdi).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).switch(Socios)
.join(SATUsoCfdi).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 Condicion de pago y tags
row = {
'id': obj.id,
'rfc': obj.rfc,
'nombre': obj.nombre,
}
data = {'ok': True, 'row': row, 'new': True}
return data
@classmethod
def actualizar(cls, values, id):
fields = cls._clean(cls, values)
try:
q = Socios.update(**fields).where(Socios.id==id)
q.execute()
except IntegrityError:
msg = 'Ya existe el RFC y Razón Social'
data = {'ok': False, 'row': {}, 'new': True, 'msg': msg}
return data
row = {
'id': id,
'rfc': fields['rfc'],
'nombre': fields['nombre'],
}
data = {'ok': True, 'row': row, 'new': False}
return data
@classmethod
def remove(cls, id):
count = (Facturas
.select(fn.COUNT(Facturas.id)).join(Socios)
.where(Socios.id==id)
.count())
if count:
return False
q = Socios.delete().where(Socios.id==id)
return bool(q.execute())
class Productos(BaseModel):
categoria = ForeignKeyField(Categorias, null=True)
clave = TextField(unique=True, index=True)
clave_sat = TextField()
descripcion = TextField(index=True)
unidad = ForeignKeyField(SATUnidades)
valor_unitario = DecimalField(default=0.0, decimal_places=6, auto_round=True)
ultimo_costo = DecimalField(default=0.0, decimal_places=6, auto_round=True)
descuento = DecimalField(default=0.0, decimal_places=6, auto_round=True)
inventario = BooleanField(default=False)
existencia = DoubleField(default=0.0)
minimo = DoubleField(default=0.0)
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)).scalar()
if value is None:
value = 1
else:
value += 1
return {'value': value}
@classmethod
def get_by(cls, values):
id = int(values.get('id', 0))
if id:
row = (Productos
.select(Productos.id, Productos.clave, Productos.descripcion,
SATUnidades.name.alias('unidad'), Productos.valor_unitario)
.join(SATUnidades).switch(Productos)
.where(Productos.id==id).dicts())
if len(row):
model_pt = Productos.impuestos.get_through_model()
taxes = tuple(model_pt
.select(
model_pt.productos_id.alias('product'),
model_pt.satimpuestos_id.alias('tax'))
.where(model_pt.productos_id==id).dicts())
return {'ok': True, 'row': row[0], 'taxes': taxes}
return {'ok': False}
name = values.get('name', '')
if name:
rows = (Productos
.select(
Productos.id, Productos.clave, Productos.descripcion,
SATUnidades.name.alias('unidad'), Productos.valor_unitario)
.join(SATUnidades)
.switch(Productos)
.where(Productos.descripcion.contains(name))
.dicts()
)
return tuple(rows)
return {'ok': False}
@classmethod
def get_(cls, values):
if values:
id = int(values['id'])
row = (Productos
.select(
Productos.id,
Productos.es_activo.alias('es_activo_producto'),
Productos.categoria,
Productos.clave,
Productos.clave_sat,
Productos.descripcion,
Productos.unidad,
Productos.valor_unitario,
)
.where(Productos.id==id).dicts()[0]
)
obj = Productos.get(Productos.id==id)
taxes = [row.id for row in obj.impuestos]
return {'row': row, 'taxes': taxes}
rows = (Productos
.select(
Productos.id,
Productos.clave,
Productos.descripcion,
SATUnidades.name.alias('unidad'),
Productos.valor_unitario)
.join(SATUnidades)
.dicts()
)
return {'ok': True, 'rows': tuple(rows)}
def _clean(self, values):
taxes = util.loads(values.pop('taxes'))
fields = util.clean(values)
fields['es_activo'] = fields.pop('es_activo_producto')
fields['descripcion'] = util.spaces(fields['descripcion'])
fields['unidad'] = int(fields['unidad'])
fields['valor_unitario'] = fields['valor_unitario'].replace(
'$', '').replace(',', '')
fb = ('es_activo', 'inventario')
for name in fb:
fields[name] = bool(fields[name].replace('0', ''))
return fields, taxes
@classmethod
def add(cls, values):
fields, taxes = cls._clean(cls, values)
if Productos.select().where(Productos.clave==fields['clave']).exists():
msg = 'Clave ya existe'
return {'ok': False, 'msg': msg}
obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes))
with database_proxy.transaction():
obj = Productos.create(**fields)
obj.impuestos = obj_taxes
row = {
'id': obj.id,
'clave': obj.clave,
'descripcion': obj.descripcion,
'unidad': obj.unidad.name,
'valor_unitario': obj.valor_unitario,
}
data = {'ok': True, 'row': row, 'new': True}
return data
@classmethod
def actualizar(cls, values, id):
fields, taxes = cls._clean(cls, values)
obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes))
with database_proxy.transaction():
q = Productos.update(**fields).where(Productos.id==id)
try:
q.execute()
except IntegrityError:
msg = 'Ya existe un producto con esta clave'
data = {'ok': False, 'row': {}, 'new': False, 'msg': msg}
return data
obj = Productos.get(Productos.id==id)
obj.impuestos = obj_taxes
row = {
'id': obj.id,
'clave': obj.clave,
'descripcion': obj.descripcion,
'unidad': obj.unidad.name,
'valor_unitario': obj.valor_unitario,
}
data = {'ok': True, 'row': row, 'new': False}
return data
@classmethod
def remove(cls, id):
count = (FacturasDetalle
.select(fn.COUNT(FacturasDetalle.id)).join(Productos)
.where(Productos.id==id)
.count()
)
if count:
return False
with database_proxy.transaction():
obj = Productos.get(Productos.id==id)
obj.impuestos.clear()
obj.tags.clear()
q = Productos.delete().where(Productos.id==id)
return bool(q.execute())
class Facturas(BaseModel):
cliente = ForeignKeyField(Socios)
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, decimal_places=6, auto_round=True)
descuento = DecimalField(default=0.0, 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, decimal_places=6, auto_round=True)
total_mn = DecimalField(default=0.0, 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(
decimal_places=6, auto_round=True, null=True)
total_trasladados = DecimalField(
decimal_places=6, auto_round=True, null=True)
xml = TextField(default='')
uuid = UUIDField(null=True)
estatus = TextField(default='Guardada')
regimen_fiscal = TextField(default='')
notas = TextField(default='')
pagada = BooleanField(default=False)
error = TextField(default='')
class Meta:
order_by = ('fecha',)
@classmethod
def get_(cls, values):
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'))
.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()
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))
.where(Facturas.serie==serie)
.scalar())
if inicio is None:
inicio = inicio_serie
else:
inicio += 1
return inicio
def _calculate_totals(self, invoice, products):
subtotal = 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['descripcion'] = p.descripcion
product['unidad'] = p.unidad.key
product['clave'] = p.clave
product['clave_sat'] = p.clave_sat
product['factura'] = invoice.id
product['producto'] = id_product
product['importe'] = round(
float(product['cantidad']) * float(product['valor_unitario']), 2)
subtotal += product['importe']
FacturasDetalle.create(**product)
for tax in p.impuestos:
if tax.id in totals_tax:
totals_tax[tax.id]['importe'] += product['importe']
else:
tax.importe = product['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, 2)
total_trasladados = (total_trasladados or 0) + import_tax
if tax.name == 'IVA':
total_iva += import_tax
invoice_tax = {
'factura': invoice.id,
'impuesto': tax.id,
'base': tax.importe,
'importe': import_tax,
}
FacturasImpuestos.create(**invoice_tax)
for tax in totals_tax.values():
if tax.tipo == 'E' or tax.tipo == 'T':
continue
if tax.tasa == round(Decimal(2/3), 6):
import_tax = round(float(tax.tasa) * total_iva, 2)
else:
import_tax = round(float(tax.tasa) * tax.importe, 2)
total_retenciones = (total_retenciones or 0) + import_tax
invoice_tax = {
'factura': invoice.id,
'impuesto': tax['id'],
'base': tax['importe'],
'importe': import_tax,
}
FacturasImpuestos.create(**invoice_tax)
total = subtotal + (total_trasladados or 0) - (total_retenciones or 0)
total_mn = round(total * invoice.tipo_cambio, 2)
data = {
'subtotal': subtotal,
'total': total,
'total_mn': total_mn,
'total_trasladados': total_trasladados,
'total_retenciones': total_retenciones,
}
return data
@classmethod
def add(cls, values):
#~ print ('VALUES', values)
productos = util.loads(values.pop('productos'))
emisor = Emisor.select()[0]
values['folio'] = cls._get_folio(cls, values['serie'])
values['tipo_cambio'] = float(values['tipo_cambio'])
values['lugar_expedicion'] = emisor.codigo_postal
with database_proxy.atomic() as txn:
obj = Facturas.create(**values)
totals = cls._calculate_totals(cls, obj, productos)
obj.subtotal = totals['subtotal']
obj.total_trasladados = totals['total_trasladados']
obj.total_retenciones = totals['total_retenciones']
obj.total = totals['total']
obj.total_mn = totals['total_mn']
obj.save()
msg = 'Factura guardada correctamente. Enviando a timbrar'
row = {
'id': obj.id,
'serie': obj.serie,
'folio': obj.folio,
'uuid': obj.uuid,
'fecha': obj.fecha,
'tipo_comprobante': obj.tipo_comprobante,
'estatus': obj.estatus,
'total_mn': obj.total_mn,
'cliente': obj.cliente.nombre,
}
data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg}
return data
def _make_xml(self, invoice):
emisor = Emisor.select()[0]
certificado = Certificado.select()[0]
comprobante = {}
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
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.producto.descripcion,
'ValorUnitario': FORMAT.format(row.valor_unitario),
'Importe': FORMAT.format(row.importe),
}
taxes = {}
traslados = []
retenciones = []
for impuesto in row.producto.impuestos:
if impuesto.tipo == 'E':
continue
import_tax = round(impuesto.tasa * row.importe, 2)
tipo_factor = 'Tasa'
if impuesto.factor != 'T':
tipo_factor = 'Cuota'
tax = {
"Base": FORMAT.format(row.importe),
"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)
taxes['traslados'] = traslados
taxes['retenciones'] = retenciones
concepto['impuestos'] = taxes
conceptos.append(concepto)
impuestos = {}
traslados = []
retenciones = []
if not invoice.total_trasladados is None:
impuestos['TotalImpuestosTrasladados'] = \
FORMAT.format(invoice.total_trasladados)
if not invoice.total_retenciones is None:
impuestos['TotalImpuestosRetenidos'] = \
FORMAT.format(invoice.total_retenciones)
taxes = (FacturasImpuestos
.select()
.where(FacturasImpuestos.factura==invoice))
for tax in taxes:
tipo_factor = 'Tasa'
if tax.impuesto.factor != 'T':
tipo_factor = 'Cuota'
if tax.impuesto.tipo == 'T':
traslado = {
"Impuesto": tax.impuesto.key,
"TipoFactor": tipo_factor,
"TasaOCuota": str(tax.impuesto.tasa),
"Importe": FORMAT.format(tax.importe),
}
traslados.append(traslado)
else:
retencion = {
"Impuesto": tax.impuesto.key,
"Importe": FORMAT.format(tax.importe),
}
retenciones.append(retencion)
impuestos['traslados'] = traslados
impuestos['retenciones'] = retenciones
data = {
'comprobante': comprobante,
'emisor': emisor,
'receptor': receptor,
'conceptos': conceptos,
'impuestos': impuestos,
}
return util.make_xml(data, certificado)
@classmethod
def timbrar(cls, id):
obj = Facturas.get(Facturas.id == id)
obj.xml = cls._make_xml(cls, obj)
obj.estatus = 'Generada'
obj.save()
error = False
msg = 'Factura timbrada correctamente'
result = util.timbra_xml(obj.xml)
if result['ok']:
obj.xml = result['xml']
obj.uuid = result['uuid']
obj.fecha_timbrado = result['fecha']
obj.estatus = 'Timbrada'
obj.save()
row = {'uuid': obj.uuid, 'estatus': 'Timbrada'}
else:
error = True
msg = result['error']
obj.estatus = 'Error'
obj.error = msg
row = {'estatus': 'Error'}
obj.save()
return {'ok': result['ok'], 'msg': msg, 'row': row}
class FacturasDetalle(BaseModel):
factura = ForeignKeyField(Facturas)
producto = ForeignKeyField(Productos, null=True)
cantidad = DecimalField(default=0.0, decimal_places=6, auto_round=True)
valor_unitario = DecimalField(default=0.0, decimal_places=6, auto_round=True)
descuento = DecimalField(default=0.0, decimal_places=6, auto_round=True)
precio_final = DecimalField(default=0.0, decimal_places=6, auto_round=True)
importe = DecimalField(default=0.0, 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 FacturasImpuestos(BaseModel):
factura = ForeignKeyField(Facturas)
impuesto = ForeignKeyField(SATImpuestos)
base = DecimalField(default=0.0, decimal_places=6, auto_round=True)
importe = DecimalField(default=0.0, decimal_places=6, auto_round=True)
class Meta:
order_by = ('factura',)
indexes = (
(('factura', 'impuesto'), True),
)
def authenticate(args):
respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''}
values = util.get_con(args['rfc'])
if not values:
return respuesta
conectar(values)
try:
obj = Usuarios.get(usuario=args['usuario'], es_activo=True)
except Usuarios.DoesNotExist:
return respuesta
if not obj.contraseña.check_password(args['contra']):
return respuesta
obj.ultimo_ingreso = util.now()
obj.save()
respuesta['msg'] = ''
respuesta['login'] = True
respuesta['user'] = str(obj)
respuesta['super'] = obj.es_superusuario
#~ desconectar()
return respuesta
def get_cp(cp):
con = sqlite3.connect(PATH_CP)
cursor = con.cursor()
sql = """
SELECT colonia, municipio, estado
FROM colonias, municipios, estados
WHERE colonias.id_municipio=municipios.id
AND municipios.id_estado=estados.id
AND cp=?
ORDER BY colonia"""
cursor.execute(sql, (cp,))
rows = cursor.fetchall()
cursor.close()
con.close()
data = {}
if rows:
data = {
'estado': rows[0][2],
'municipio': rows[0][1],
}
if len(rows) == 1:
data['colonia'] = rows[0][0]
else:
data['colonia'] = [r[0] for r in rows]
return data
def get_sat_key(key):
return util.get_sat_key('products', key)
def _init_values():
data = (
{'key': 'version', 'value': VERSION},
{'key': 'rfc_publico', 'value': 'XAXX010101000'},
{'key': 'rfc_extranjero', 'value': 'XEXX010101000'},
)
for row in data:
try:
Configuration.create(**row)
except IntegrityError:
pass
log.info('Valores iniciales insertados...')
return
def _crear_tablas():
tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion,
Emisor, Facturas, FacturasDetalle, FacturasImpuestos, Folios, Productos,
SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes,
SATUnidades, SATUsoCfdi, Socios, Tags, Usuarios,
Emisor.regimenes.get_through_model(),
Socios.tags.get_through_model(),
Productos.impuestos.get_through_model(),
Productos.tags.get_through_model(),
]
database_proxy.create_tables(tablas, True)
log.info('Tablas creadas correctamente...')
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):
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 _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():
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():
log.info('RFC agregado correctamente...')
return
log.error('No se pudo agregar el RFC')
return
def _listar_rfc():
data = util.get_rfcs()
for row in data:
msg = 'RFC: {}\n\t{}'.format(row[0], row[1])
log.info(msg)
return
def _importar_valores(archivo):
rfc = 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...')
regimen = ''
rows = util.loads(open(archivo, 'r').read())
for row in rows:
log.info('\tImportando tabla: {}'.format(row['tabla']))
if row['tabla'] == 'Emisor' and 'regimen' in row:
regimen = row['regimen']
table = globals()[row['tabla']]
for r in row['datos']:
try:
table.create(**r)
except IntegrityError:
pass
if regimen:
emisor = Emisor.select()[0]
regimen = SATRegimenes.get(SATRegimenes.key == regimen)
emisor.regimenes.clear()
emisor.regimenes.add(regimen)
log.info('Importación terminada...')
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')
def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc,
borrar_rfc, listar_rfc, importar_valores, archivo):
opt = locals()
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)
return
if __name__ == '__main__':
main()