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

11788 lines
380 KiB
Python

#!/usr/bin/env python3
# ~ Empresa Libre
# ~ Copyright (C) 2016-2021 Mauricio Baeza Servin (public@correolibre.net)
# ~
# ~ This program is free software: you can redistribute it and/or modify
# ~ it under the terms of the GNU General Public License as published by
# ~ the Free Software Foundation, either version 3 of the License, or
# ~ (at your option) any later version.
# ~
# ~ This program is distributed in the hope that it will be useful,
# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# ~ GNU General Public License for more details.
# ~
# ~ You should have received a copy of the GNU General Public License
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
from decimal import Decimal
import sqlite3
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, COMPANIES, VERSION, PATH_CP, PRE, CURRENT_CFDI, \
INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \
CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET, CURRENT_CFDI_NOMINA, \
DEFAULT_SAT_NOMINA, DECIMALES_TAX, TITLE_APP, MV, DECIMALES_PRECIOS, \
DEFAULT_CFDIPAY, CURRENCY_MN
# ~ v2
from controllers import utils
from settings import (
DEBUG,
CANCEL_VERSION,
CARTA_PORTE,
DEFAULT_GLOBAL,
DB_COMPANIES,
EXT,
IS_MV,
MXN,
PATHS,
URL,
VALUES_PDF,
VERSION as VERSION_EMPRESA_LIBRE,
RFCS,
)
FORMAT = '{0:.2f}'
FORMAT3 = '{0:.3f}'
FORMAT4 = '{0:.4f}'
FORMAT6 = '{0:.6f}'
FORMAT_TAX = FORMAT4
FORMAT_PRECIO = FORMAT4
# ~ RFC_PUBLICO = 'XAXX010101000'
RFC_EXTRANJERO = 'XEXX010101000'
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
opt['host'] = opt.get('host', 'localhost')
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 UploadFile(object):
def _read_productsadd(self, result):
emisor = result['data']['emisor']
rfc = emisor['rfc']
where = ((Socios.rfc == rfc) & (Socios.es_proveedor == True))
try:
partner = Socios.select(Socios.id).where(where).get()
emisor['id'] = partner.id
except Socios.DoesNotExist:
emisor['id'] = 0
result['data']['emisor'] = emisor
return result
@classmethod
def read(cls, rfc, opt, file_obj):
result = utils.upload_file(rfc, opt, file_obj)
method = f'_read_{opt}'
if hasattr(cls, method):
return getattr(cls, method)(cls, result)
return result
def upload_file(rfc, opt, file_obj):
if opt == 'cfdixml':
sxml = file_obj.file.read().decode()
xml = util.parse_xml(sxml)
# ~ sxml = util.to_pretty_xml(data)
if xml is None:
return {'status': 'error'}
else:
return Facturas.import_cfdi(xml, sxml)
if opt == 'emisorlogo':
return Emisor.save_logo(file_obj)
# ~ v2
names = ('productsadd', '_3.3_cp_2.0.ods')
if opt in names:
result = UploadFile.read(rfc, opt, file_obj)
return result
result = util.upload_file(rfc, opt, file_obj)
if result['ok']:
names = ('bdfl', 'employees', 'nomina', 'products', 'invoiceods')
if not opt in names:
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.get(Certificado.es_fiel==False)
except Exception as e:
return {'ok': False, 'msg': msg}
if not obj.serie:
return {'ok': False, 'msg': msg}
diff = obj.hasta - utils.now()
if diff.days < 0:
msg = 'El certificado ha vencido, es necesario cargar uno nuevo'
return {'ok': False, 'msg': msg}
auth = Configuracion.get_({'fields': 'pac_auth'})
if not auth:
msg = 'Es necesario configurar los datos de timbrado del PAC'
return {'ok': False, 'msg': msg}
msg = ''
if diff.days < 15:
msg = 'El certificado vence en: {} dĆ­as.'.format(diff.days)
return {'ok': True, 'msg': msg}
def _get_taxes_product(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
def import_invoice():
log.info('Importando factura...')
emisor = Emisor.select()[0]
rows, msg = util.import_invoice(emisor.rfc)
if not rows:
return {'ok': False, 'msg': msg}
products = []
for i, row in enumerate(rows):
try:
if not isinstance(row[0], str):
msg = 'Fila: {} - La clave debe ser TEXTO'.format(i+1)
return {'ok': False, 'msg': msg}
obj = Productos.get(Productos.clave==row[0])
vu = round(row[2], 2)
if isinstance(row[3], str):
msg = 'El descuento debe ser un nĆŗmero, debe ser 0.00, si no tiene'
return {'ok': False, 'msg': msg}
descuento = round(row[3], 2)
cant = round(row[4], 2)
description = row[1].strip()
pedimento = row[5].strip()
pf = vu - descuento
p = {
'id_product': obj.id,
'delete': '-',
'clave': obj.clave,
'descripcion': description or obj.descripcion,
'pedimento': pedimento,
'unidad': obj.unidad.id,
'cantidad': cant,
'valor_unitario': vu,
'descuento': descuento,
'importe': round(pf * cant, DECIMALES),
'taxes': _get_taxes_product(obj.id),
}
products.append(p)
except Productos.DoesNotExist:
pass
log.info('Factura importada...')
return {'ok': True, 'rows': tuple(products)}
def get_doc(type_doc, id, rfc):
types = {
'xml': 'application/xml',
'xmlpago': 'application/xml',
'nomxml': 'application/xml',
'ods': 'application/octet-stream',
'zip': 'application/octet-stream',
'nomlog': 'application/txt',
'html': 'text/html',
}
content_type = types.get(type_doc, 'application/pdf')
if type_doc == 'xml':
data, file_name = Facturas.get_xml(id)
elif type_doc == 'pdf':
data, file_name = Facturas.get_pdf(id, rfc)
elif type_doc == 'html':
data, file_name = Facturas.get_html(id)
elif type_doc == 'ods':
data, file_name = Facturas.get_ods(id, rfc)
elif type_doc == 'zip':
data, file_name = Facturas.get_zip(id, rfc)
elif type_doc == 'pre':
data, file_name = PreFacturas.get_pdf(id)
elif type_doc == 'tpdf':
data, file_name = Tickets.get_pdf(id)
elif type_doc == 'nomxml':
data, file_name = CfdiNomina.get_xml(id)
elif type_doc == 'nomlog':
data, file_name = util.get_log('nomina')
elif type_doc == 'nompdf':
data, file_name = CfdiNomina.get_pdf(id, rfc)
elif type_doc == 'xmlpago':
data, file_name = CfdiPagos.get_file_xml(id)
elif type_doc == 'pdfpago':
data, file_name = CfdiPagos.get_file_pdf(id)
return data, file_name, content_type
def config_main(user):
try:
obj = Emisor.select()[0]
except IndexError:
obj = None
punto_de_venta = Configuracion.get_bool('chk_usar_punto_de_venta')
nomina = Configuracion.get_bool('chk_usar_nomina')
if not user.es_admin:
nomina = False
data = {
'empresa': get_title_app(3),
'punto_de_venta': punto_de_venta,
'escuela': False,
'nomina': nomina,
'timbres': 0,
'decimales_precios': DECIMALES,
'pagos': Configuracion.get_bool('chk_config_pagos'),
'pays_data_bank': Configuracion.get_bool('chk_cfg_pays_data_bank')
}
dp = Configuracion.get_bool('chk_config_decimales_precios')
if dp:
data['decimales_precios'] = DECIMALES_PRECIOS
if not obj is None:
titulo = '{} - <b><font color="#610B0B">{}</font></b>'
data['empresa'] = titulo.format(data['empresa'], obj.nombre)
data['escuela'] = obj.es_escuela
data['timbres'] = Emisor.get_timbres()
data['multi_currency'] = SATMonedas.get_multi_currency()
return data
def config_timbrar():
try:
obj = Emisor.select()[0]
except IndexError:
return {'cfdi_donativo': False}
conf = {
'cfdi_donativo': obj.es_ong,
'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'),
'cfdi_ine': Configuracion.get_bool('chk_config_ine'),
'cfdi_edu': Configuracion.get_bool('chk_config_edu'),
'cfdi_carta_porte': Configuracion.get_bool('chk_config_carta_porte'),
'cfdi_comercioe': Configuracion.get_bool('chk_config_comercio_exterior'),
'cfdi_divisas': Configuracion.get_bool('chk_config_divisas'),
'cfdi_metodo_pago': Configuracion.get_bool('chk_config_ocultar_metodo_pago'),
'cfdi_condicion_pago': Configuracion.get_bool('chk_config_ocultar_condiciones_pago'),
'cfdi_open_pdf': Configuracion.get_bool('chk_config_open_pdf'),
'cfdi_show_pedimento': Configuracion.get_bool('chk_config_show_pedimento'),
'cfdi_tax_locales': Configuracion.get_bool('chk_config_tax_locales'),
'cfdi_tax_decimals': Configuracion.get_bool('chk_config_tax_decimals'),
'cfdi_with_taxes': Configuracion.get_bool('chk_config_price_with_taxes_in_invoice'),
'cfdi_add_same_product': Configuracion.get_bool('chk_config_add_same_product'),
'cfdi_tax_locales_truncate': Configuracion.get_bool('chk_config_tax_locales_truncate'),
'cfdi_folio_custom': Configuracion.get_bool('chk_folio_custom'),
'cfdi_leyendasfiscales': Configuracion.get_bool('chk_config_leyendas_fiscales'),
'cfdi_show_total_cant': Configuracion.get_bool('chk_config_show_total_cant'),
}
return conf
def config_ticket():
conf = {
'open_pdf': Configuracion.get_bool('chk_ticket_pdf_show'),
'direct_print': Configuracion.get_bool('chk_ticket_direct_print'),
'edit_cant': Configuracion.get_bool('chk_ticket_edit_cant'),
'total_up': Configuracion.get_bool('chk_ticket_total_up'),
}
return conf
class Configuracion(BaseModel):
clave = TextField(unique=True)
valor = TextField(default='')
class Meta:
order_by = ('clave',)
indexes = (
(('clave', 'valor'), True),
)
def __str__(self):
return '{} = {}'.format(self.clave, self.valor)
def _clean_email(self, values={}):
fields = (
'correo_asunto',
'correo_confirmacion',
'correo_contra',
'correo_directo',
'correo_mensaje',
'correo_puerto',
'correo_servidor',
'correo_ssl',
'correo_usuario',
)
q = (Configuracion
.update(**{'valor': ''})
.where(Configuracion.clave.in_(fields))
)
result = q.execute()
msg = 'ConfiguraciĆ³n guardada correctamente'
if not result:
msg = 'No se pudo guardar la configuraciĆ³n'
return {'ok': result, 'msg': msg}
def _save_mail(self, values):
rfc = Emisor.select()[0].rfc
values['correo_contra'] = utils.encrypt(values['correo_contra'], rfc)
for k, v in values.items():
obj, created = Configuracion.get_or_create(clave=k)
obj.valor = v
obj.save()
return {'ok': True}
@classmethod
def get_bool(cls, key):
data = (Configuracion
.select(Configuracion.valor)
.where(Configuracion.clave == key)
)
if data and data[0].valor == '1':
return True
return False
def _get_partners(self, args={}):
fields = (
'chk_config_change_balance_partner',
)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
values = {r.clave: util.get_bool(r.valor) for r in data}
return values
def _get_admin_products(self, args={}):
fields = (
'chk_config_cuenta_predial',
'chk_config_codigo_barras',
'chk_config_precio_con_impuestos',
'chk_llevar_inventario',
'chk_multi_stock',
# ~ 'chk_use_packing',
)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
values = {r.clave: util.get_bool(r.valor) for r in data}
if not 'chk_multi_stock' in values:
values['chk_multi_stock'] = False
return values
def _get_main_products(self, args={}):
fields = (
'chk_config_cuenta_predial',
'chk_config_codigo_barras',
'chk_config_precio_con_impuestos',
'chk_llevar_inventario',
# ~ 'chk_use_packing',
'chk_multi_stock',
)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
values = {r.clave: r.valor for r in data}
values['default_tax'] = SATImpuestos.select()[0].id
values['default_unidad'] = SATUnidades.get_default()
return values
def _get_complements(self, args={}):
fields = (
'chk_config_ine',
'chk_config_edu',
'chk_config_carta_porte',
'chk_config_comercio_exterior',
'chk_config_pagos',
'chk_config_divisas',
'chk_cfg_pays_data_bank',
'chk_usar_nomina',
'chk_config_leyendas_fiscales',
)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
values = {r.clave: util.get_bool(r.valor) for r in data}
fields = (
'txt_ticket_printer',
'txt_config_nomina_serie',
'txt_config_nomina_folio',
'txt_config_cfdipay_serie',
'txt_config_cfdipay_folio',
)
for f in fields:
values[f] = Configuracion.get_(f)
return values
def _get_folios(self, args={}):
fields = (
'chk_folio_custom',
)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
values = {r.clave: util.get_bool(r.valor) for r in data}
return values
def _get_correo(self, args={}):
fields = ('correo_servidor', 'correo_puerto',
'correo_ssl', 'correo_starttls',
'correo_usuario', 'correo_copia', 'correo_asunto',
'correo_mensaje', 'correo_directo', 'correo_confirmacion')
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
values = {r.clave: r.valor for r in data}
return values
def _get_admin_config_users(self, args={}):
fields = (
'chk_users_notify_access',
)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
values = {r.clave: util.get_bool(r.valor) for r in data}
return values
@classmethod
def get_value(cls, key, default=''):
value = default
data = (Configuracion
.select(Configuracion.valor)
.where(Configuracion.clave == key)
)
if data:
value = data[0].valor
return value
def _get_pac(cls, pac):
user_field = f'user_timbrado_{pac}'
token_field = f'token_timbrado_{pac}'
fields = (user_field, token_field)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
data = {r.clave: r.valor for r in data}
values = {
'user_timbrado': data.get(user_field, ''),
'token_timbrado': data.get(token_field, ''),
}
return values
def _get_pac_auth(cls, args={}):
pac = cls.get_('lst_pac').lower()
user = cls.get_(f'user_timbrado_{pac}')
token = cls.get_(f'token_timbrado_{pac}')
data = {}
if pac and user and token:
data['pac'] = pac
data['user'] = user
data['pass'] = token
return data
def _get_auth_by_pac(cls, args):
pac = args['pac']
user = cls.get_(f'user_timbrado_{pac}')
token = cls.get_(f'token_timbrado_{pac}')
data = {}
if pac and user and token:
data['pac'] = pac
data['user'] = user
data['pass'] = token
return data
def _get_admin_sucursales(cls, args):
values = {
'folios': Folios.get_for_sucursales(),
'warehouse': Almacenes.get_for_sucursales(),
'multi_stock': Configuracion.get_bool('chk_multi_stock')
}
return values
@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 ''
options = ('partners',
'admin_products',
'main_products',
'complements',
'folios',
'correo',
'admin_config_users',
'pac_auth',
'auth_by_pac',
'admin_sucursales',
)
opt = keys['fields']
if opt in options:
return getattr(cls, f'_get_{opt}')(cls, keys)
if opt == 'pac':
return cls._get_pac(cls, keys['pac'])
if keys['fields'] == 'configtemplates':
try:
emisor = Emisor.select()[0]
is_ong = emisor.es_ong
except IndexError:
is_ong = False
values = {'txt_plantilla_donataria': is_ong}
fields = (
('chk_usar_punto_de_venta', 'txt_plantilla_ticket'),
)
for s, key in fields:
value = util.get_bool(Configuracion.get_(s))
values[key] = value
return values
if keys['fields'] == 'configotros':
fields = (
'chk_config_ocultar_metodo_pago',
'chk_config_ocultar_condiciones_pago',
'chk_config_send_zip',
'chk_config_open_pdf',
'chk_config_show_pedimento',
'chk_config_tax_locales',
'chk_config_tax_decimals',
'chk_config_price_with_taxes_in_invoice',
'chk_config_add_same_product',
'chk_config_tax_locales_truncate',
'chk_config_decimales_precios',
'chk_config_user_show_doc',
'chk_config_anticipo',
'chk_usar_punto_de_venta',
'chk_ticket_pdf_show',
'chk_ticket_direct_print',
'chk_ticket_edit_cant',
'chk_ticket_total_up',
'chk_ticket_user_show_doc',
'chk_config_invoice_by_ticket',
'chk_config_show_total_cant',
'chk_cancel_invoices_by_admin',
'chk_cancel_tickets_by_admin',
)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
values = {r.clave: util.get_bool(r.valor) for r in data}
fields = (
('lst_pac', 'comercio'),
)
for k, d in fields:
values[k] = Configuracion.get_value(k, d)
return values
if 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_html',
'txt_plantilla_factura_css',
'txt_plantilla_factura_json',
'txt_plantilla_nomina1233',
'txt_plantilla_pagos10',
'txt_plantilla_ticket',
'txt_plantilla_donataria',
'make_pdf_from',
)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
elif keys['fields'] == 'timbrar':
fields = (
'chk_config_ocultar_metodo_pago',
'chk_config_ocultar_condiciones_pago',
'chk_config_anticipo',
'chk_config_ine',
'chk_config_open_pdf',
)
data = (Configuracion
.select()
.where(Configuracion.clave.in_(fields))
)
values = {r.clave: r.valor for r in data}
return values
def _save_pac(cls, values):
pac = values['lst_pac']
user = values['user_timbrado']
token = values['token_timbrado']
data = {
'lst_pac': pac,
f'user_timbrado_{pac}': user,
f'token_timbrado_{pac}': token,
}
for k, v in data.items():
obj, _ = Configuracion.get_or_create(clave=k)
obj.valor = v
obj.save()
return {'ok': True}
@classmethod
def add(cls, values):
opt = values.pop('opt', '')
if opt:
return getattr(cls, f'_{opt}')(cls, values)
# ~ print (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)}
@classmethod
def remove(cls, key):
q = Configuracion.delete().where(Configuracion.clave==key)
return bool(q.execute())
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 Roles(BaseModel):
nombre = TextField(default='')
class Meta:
order_by = ('nombre',)
class Permisos(BaseModel):
rol = ForeignKeyField(Roles)
modulo = TextField(default='')
ver = BooleanField(default=False)
agregar = BooleanField(default=False)
editar = BooleanField(default=False)
eliminar = BooleanField(default=False)
especiales = TextField(default='')
class Meta:
order_by = ('rol', 'modulo')
indexes = (
(('rol', 'modulo'), True),
)
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_by_key(cls, key):
return SATRegimenes.get(SATRegimenes.key==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)
@classmethod
def _get_actives(cls, filters, user):
where = ((SATRegimenes.activo==True) & (SATRegimenes.fisica==True))
if (filters['morales']=='true'):
where = ((SATRegimenes.activo==True) & (SATRegimenes.moral==True))
rows = (SATRegimenes
.select(
SATRegimenes.id,
SATRegimenes.name.alias('value'))
.where(where)
.dicts()
)
return tuple(rows)
@classmethod
def get_data(cls, filters, user):
opt = filters['opt']
return getattr(cls, f'_get_{opt}')(filters, user)
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='')
registro_patronal = TextField(default='')
regimenes = ManyToManyField(SATRegimenes, related_name='emisores')
class Meta:
order_by = ('nombre',)
def __str__(self):
t = '{} ({})'
return t.format(self.nombre, self.rfc)
@classmethod
def save_logo(cls, file_obj):
result = {'status': 'error', 'ok': False}
name = file_obj.filename.split('.')
if name[-1].lower() != EXT['PNG']:
return result
emisor = Emisor.select()[0]
rfc = emisor.rfc.lower()
name = f'{rfc}.png'
path = util._join(PATHS['LOGOS'], name)
if util.save_file(path, file_obj.file.read()):
emisor.logo = file_obj.filename
emisor.save()
result = {'status': 'server', 'name': file_obj.filename, 'ok': True}
return result
@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_curp': obj.curp,
'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,
'emisor_logo': obj.logo,
'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,
'token_soporte': obj.token_soporte,
'emisor_registro_patronal': obj.registro_patronal,
'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_timbres(cls):
try:
obj = Emisor.select()[0]
except IndexError:
return 's/e'
auth = Configuracion.get_({'fields': 'pac_auth'})
result = utils.get_client_balance(auth, obj.rfc)
return result
@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['curp'] = fields.pop('emisor_curp', '')
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', ''))
ong_auth = fields.pop('ong_autorizacion', '')
ong_date = fields.pop('ong_fecha', None)
ong_date_dof = fields.pop('ong_fecha_dof', None)
fields['autorizacion'] = ong_auth
fields['fecha_autorizacion'] = ong_date
fields['fecha_dof'] = ong_date_dof
fields['correo_timbrado'] = fields.pop('correo_timbrado', '')
fields['token_timbrado'] = fields.pop('token_timbrado', '')
fields['token_soporte'] = fields.pop('token_soporte', '')
if len(fields['rfc']) == 12:
fields['es_moral'] = True
fields['registro_patronal'] = fields.pop('emisor_registro_patronal', '')
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)
es_fiel = BooleanField(default=False)
def __str__(self):
return self.serie
@classmethod
def _get_cert(cls, args):
obj = Certificado.get(Certificado.es_fiel==False)
data = {
'cert_rfc': obj.rfc,
'cert_serie': obj.serie,
'cert_desde': obj.desde,
'cert_hasta': obj.hasta,
}
return data
@classmethod
def get_data(cls, values):
opt = values['opt']
return getattr(cls, f'_get_{opt}')(values)
@classmethod
def _validate_cert(cls, args):
msg = 'Certificado guardado correctamente'
result = {'ok': True, 'msg': msg, 'data': {}}
cert = utils.get_cert(args)
if not cert.is_valid:
result['ok'] = False
result['msg'] = cert.error
return result
obj = Certificado.get(Certificado.es_fiel==False)
if obj.rfc != cert.rfc:
result['ok'] = False
result['msg'] = 'El RFC del certificado no corresponde.'
return result
obj.key = cert._key
obj.key_enc = cert.key_enc
obj.cer = cert.cer
obj.serie = cert.serial_number
obj.cer_txt = cert.cer_txt
obj.p12 = utils.encrypt(cert._keyp, cert.serial_number)
obj.desde = cert.not_before
obj.hasta = cert.not_after
obj.save()
data = {
'cert_rfc': obj.rfc,
'cert_serie': obj.serie,
'cert_desde': obj.desde,
'cert_hasta': obj.hasta,
}
result['data'] = data
return result
@classmethod
def post(cls, values):
opt = values['opt']
return getattr(cls, f'_{opt}')(values)
class Folios(BaseModel):
serie = TextField(unique=True)
inicio = IntegerField(default=1)
default = BooleanField(default=False)
usarcon = TextField(default='')
plantilla = TextField(default='')
class Meta:
order_by = ('-default', 'serie', 'inicio')
indexes = (
(('serie', 'inicio'), True),
)
@classmethod
def get_default(cls):
folio = Folios.select()[0]
return folio.serie
@classmethod
def get_id(cls, serie):
try:
obj = Folios.get(Folios.serie==serie)
return obj.id
except:
return Folios.select()[0].id
@classmethod
def get_all(cls):
rows = (Folios
.select(
Folios.id,
Folios.serie.alias('value'),
Folios.usarcon,
)
.dicts()
)
return tuple(rows)
@classmethod
def get_for_sucursales(cls):
rows = (Folios
.select(
Folios.serie.alias('id'),
Folios.serie.alias('value'),
)
# ~ .where(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_by_id(cls, id):
try:
return Categorias.get(Categorias.id==id)
except:
return None
@classmethod
def _get_by_name(cls, name):
try:
return Categorias.get(Categorias.categoria==name)
except:
return None
@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 SATUnidadesPeso(BaseModel):
key = TextField(unique=True, index=True)
name = TextField(default='', index=True)
activo = BooleanField(default=False)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return '{} ({})'.format(self.name, self.key)
@classmethod
def _get_all(cls, values, user):
rows = tuple(SATUnidadesPeso.select().dicts())
return rows
@classmethod
def _get_active(cls, values, user):
fields = (
SATUnidadesPeso.key.alias('id'),
SATUnidadesPeso.name.alias('value'),
)
where = (SATUnidadesPeso.activo==True)
rows = tuple(
SATUnidadesPeso.select(*fields).where(where).dicts()
)
return rows
@classmethod
def get_data(cls, values, user):
opt = values.pop('opt')
return getattr(cls, f'_get_{opt}')(values, user)
@classmethod
def _add(cls, values, user):
result = True
try:
SATUnidadesPeso.create(**values)
except:
result = False
return {'ok': result}
@classmethod
def _delete(cls, values, user):
id = values['id']
q = SATUnidadesPeso.delete().where(SATUnidadesPeso.id==id)
result = bool(q.execute())
response = {'ok': result}
return response
@classmethod
def _update_active(cls, values, user):
id = values['id']
update = {'activo': bool(values['activo'])}
where = (SATUnidadesPeso.id==id)
q = SATUnidadesPeso.update(**update).where(where)
result = bool(q.execute())
response = {'ok': result}
return response
@classmethod
def post(cls, values, user):
opt = values['opt']
args = utils.loads(values['values'])
return getattr(cls, f'_{opt}')(args, user)
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_by_name(self, name):
try:
return SATUnidades.get(SATUnidades.name==name)
except:
return None
@classmethod
def get_key_by_id(self, id):
obj = SATUnidades.get(SATUnidades.id==id)
return obj.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_default(cls):
obj = SATUnidades.select()[0]
if obj.default:
return obj.id
return 0
@classmethod
def get_activos(cls):
rows = (SATUnidades
.select(
SATUnidades.id,
SATUnidades.name.alias('value'))
.where(SATUnidades.activo==True)
.dicts()
)
return tuple(rows)
@classmethod
def get_activos_by_key(cls):
rows = (SATUnidades
.select(
SATUnidades.key.alias('id'),
SATUnidades.name.alias('value'))
.where(SATUnidades.activo==True)
.dicts()
)
return tuple(rows)
@classmethod
def remove(cls, id):
with database_proxy.transaction():
try:
q = SATUnidades.delete().where(SATUnidades.id==id)
return bool(q.execute())
except IntegrityError:
return False
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_values(cls, values):
opt = values.pop('opt')
return getattr(cls, '_get_{}'.format(opt))(cls, values)
def _get_active_by_id(self, values):
rows = (SATFormaPago
.select(
SATFormaPago.id,
SATFormaPago.name.alias('value'))
.where(SATFormaPago.activo==True)
.dicts()
)
return tuple(rows)
def _get_active_by_key(self, values):
rows = (SATFormaPago
.select(
SATFormaPago.key.alias('id'),
SATFormaPago.name.alias('value'))
.where(SATFormaPago.activo==True)
.dicts()
)
return tuple(rows)
@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 get_by_key(cls, key):
return SATMonedas.get(SATMonedas.key==key)
@classmethod
def get_multi_currency(cls):
count_currencies = len(cls.get_activos())
multi_currency = False
if count_currencies > 1:
multi_currency = True
return multi_currency
@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)
def _updaterfc(self, args):
id = int(args['id'])
values = util.loads(args['values'])
msg = util.validate_rfc(values['rfc'])
if msg:
return {'ok': False, 'msg': msg}
q = (SATBancos
.update(**values)
.where(SATBancos.id==id))
result = bool(q.execute())
msg = 'RFC actualizado correctamente'
return {'ok': result, 'msg': msg}
@classmethod
def post(cls, values):
opt = values.pop('opt')
return getattr(cls, '_{}'.format(opt))(cls, values)
@classmethod
def get_values(cls, values):
opt = values.pop('opt')
return getattr(cls, '_get_{}'.format(opt))(cls, values)
def _get_active(cls, values):
rows = (SATBancos
.select(
SATBancos.id,
SATBancos.name.alias('value'))
.where(SATBancos.activo==True)
.dicts()
)
return tuple(rows)
@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}
@classmethod
def get_by_key(cls, key):
if not key:
return
try:
obj = SATBancos.get(SATBancos.key==key)
return obj
except SATBancos.DoesNotExist:
msg = 'SATBancos no existe: {}'.format(key)
log.error(msg)
return
@classmethod
def get_by_name(cls, name):
try:
obj = SATBancos.get(SATBancos.name==name)
return obj
except SATBancos.DoesNotExist:
msg = 'SATBancos no existe: {}'.format(key)
log.error(msg)
return
class SATNivelesEducativos(BaseModel):
name = TextField(index=True)
class Meta:
order_by = ('name',)
def __str__(self):
return self.name
@classmethod
def get_by(cls):
rows = SATNivelesEducativos.select(
SATNivelesEducativos.name).tuples()
return tuple([r[0] for r in rows])
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)
@classmethod
def get_all(cls):
rows = NivelesEducativos.select().dicts()
return tuple(rows)
def _add_group(self, obj):
Grupos.get_or_create(**{'nivel': obj})
return
@classmethod
def add(cls, values):
try:
obj = NivelesEducativos.create(**values)
# Revisar
cls._add_group(cls, obj)
result = {'ok': True}
except IntegrityError:
msg = 'Nivel Educativo existente'
result = {'ok': False, 'msg': msg}
return result
@classmethod
def remove(cls, id):
obj = NivelesEducativos.get(NivelesEducativos.id==int(id))
q = Grupos.delete().where(Grupos.nivel==obj)
try:
q.execute()
except IntegrityError:
return False
q = NivelesEducativos.delete().where(NivelesEducativos.id==int(id))
return bool(q.execute())
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)
@classmethod
def get_by(cls, values):
rows = (Grupos.select(
Grupos.id.alias('id'),
NivelesEducativos.nombre.alias('value'))
.join(NivelesEducativos)
.switch(Grupos)
.dicts()
)
return tuple(rows)
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.nombre, self.cuenta[-4:])
@classmethod
def activate(cls, values):
result = False
id = int(values['id'])
if values['field'] == 'activa':
v = {'0': False, '1': True}
q = (CuentasBanco
.update(**{'activa': v[values['value']]})
.where(CuentasBanco.id==id))
result = bool(q.execute())
return {'ok': result}
@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):
opt = values.pop('opt', '')
if opt:
return getattr(cls, '_get_{}'.format(opt))(cls, values)
if values['tipo'] == '1':
rows = (CuentasBanco
.select()
.where(CuentasBanco.de_emisor==True, CuentasBanco.activa==True)
.order_by(CuentasBanco.id)
)
if not (len(rows)):
return {'ok': False}
first = rows[0]
rows = [{'id': r.id, 'value': '{} ({})'.format(
r.nombre, r.cuenta[-4:])} for r in rows]
data = {
'ok': True,
'rows': tuple(rows),
'moneda': '{} ({})'.format(first.moneda.name, first.moneda.key),
'key_currency': first.moneda.key,
'saldo': first.saldo,
}
return data
return {'ok': False}
def _get_currency(self, values):
id = int(values['id'])
account = CuentasBanco.get(CuentasBanco.id==id)
data = {
'ok': True,
'currency': '{} ({})'.format(account.moneda.name, account.moneda.key),
'key_currency': account.moneda.key,
}
return data
@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= {
'opt': 'add',
'cuenta': obj.id,
'fecha': fecha_deposito,
'descripcion': 'Saldo inicial',
'forma_pago': SATFormaPago.get_by_key('99'),
'deposito': values['saldo'],
'saldo': values['saldo'],
}
MovimientosBanco.post(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 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 = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Uso del CFDI: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
return SATUsoCfdi.get(SATUsoCfdi.key==key)
@classmethod
def actualizar(self, values):
id = int(values['id'])
if values['field'] == 'activo':
v = {'0': False, '1': True}
q = (SATUsoCfdi
.update(**{'activo': v[values['value']]})
.where(SATUsoCfdi.id==id))
result = bool(q.execute())
return {'ok': result}
@classmethod
def get_all(self):
rows = SATUsoCfdi.select().dicts()
return tuple(rows)
@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 SATEstados(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
pais = TextField(default='')
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name', 'pais'), True),
)
def __str__(self):
return 'Estado: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATEstados.get(SATEstados.key==key)
return obj
except SATEstados.DoesNotExist:
msg = 'SATEstados no existe: {}'.format(key)
log.error(msg)
return
class SATOrigenRecurso(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Origen Recurso: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATOrigenRecurso.get(SATOrigenRecurso.key==key)
return obj
except SATOrigenRecurso.DoesNotExist:
msg = 'SATOrigenRecurso no existe: {}'.format(key)
log.error(msg)
return
class SATPeriodicidadPago(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Periodicidad de Pago: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATPeriodicidadPago.get(SATPeriodicidadPago.key==key)
return obj
except SATPeriodicidadPago.DoesNotExist:
msg = 'SATPeriodicidadPago no existe: {}'.format(key)
log.error(msg)
return
class SATTipoContrato(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Tipo de Contrato: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATTipoContrato.get(SATTipoContrato.key==key)
return obj
except SATTipoContrato.DoesNotExist:
msg = 'SATTipoContrato no existe: {}'.format(key)
log.error(msg)
return
class SATTipoDeduccion(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
clave = TextField(default='')
nombre = TextField(default='')
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Tipo de DeducciĆ³n: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATTipoDeduccion.get(SATTipoDeduccion.key==key)
return obj
except SATTipoDeduccion.DoesNotExist:
msg = 'SATTipoDeduccion no existe: {}'.format(key)
log.error(msg)
return
class SATTipoHoras(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Tipo de Horas: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATTipoHoras.get(SATTipoHoras.key==key)
return obj
except SATTipoHoras.DoesNotExist:
msg = 'SATTipoHoras no existe: {}'.format(key)
log.error(msg)
return
class SATTipoIncapacidad(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Tipo de Incapacidad: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATTipoIncapacidad.get(SATTipoIncapacidad.key==key)
return obj
except SATTipoIncapacidad.DoesNotExist:
msg = 'SATTipoIncapacidad no existe: {}'.format(key)
log.error(msg)
return
class SATTipoJornada(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Tipo de Jornada: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATTipoJornada.get(SATTipoJornada.key==key)
return obj
except SATTipoJornada.DoesNotExist:
msg = 'SATTipoJornada no existe: {}'.format(key)
log.error(msg)
return
class SATTipoNomina(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Tipo de NĆ³mina: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATTipoNomina.get(SATTipoNomina.key==key)
return obj
except SATTipoNomina.DoesNotExist:
msg = 'SATTipoNomina no existe: {}'.format(key)
log.error(msg)
return
class SATTipoOtroPago(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
clave = TextField(default='')
nombre = TextField(default='')
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Tipo de Otro Pago: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATTipoOtroPago.get(SATTipoOtroPago.key==key)
return obj
except SATTipoOtroPago.DoesNotExist:
msg = 'SATTipoOtroPago no existe: {}'.format(key)
log.error(msg)
return
class SATTipoPercepcion(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
clave = TextField(default='')
nombre = TextField(default='')
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Tipo de PercepciĆ³n: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATTipoPercepcion.get(SATTipoPercepcion.key==key)
return obj
except SATTipoPercepcion.DoesNotExist:
msg = 'SATTipoPercepcion no existe: {}'.format(key)
log.error(msg)
return
class SATTipoRegimen(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Regimen de contrataciĆ³n: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATTipoRegimen.get(SATTipoRegimen.key==key)
return obj
except SATTipoRegimen.DoesNotExist:
msg = 'SATTipoRegimen no existe: {}'.format(key)
log.error(msg)
return
class SATRiesgoPuesto(BaseModel):
key = TextField(index=True, unique=True)
name = TextField(default='', index=True)
activo = BooleanField(default=True)
class Meta:
order_by = ('name',)
indexes = (
(('key', 'name'), True),
)
def __str__(self):
return 'Riesgo Puesto: {} ({})'.format(self.name, self.key)
@classmethod
def get_by_key(cls, key):
try:
obj = SATRiesgoPuesto.get(SATRiesgoPuesto.key==key)
return obj
except SATRiesgoPuesto.DoesNotExist:
msg = 'SATRiesgoPuesto no existe: {}'.format(key)
log.error(msg)
return
class SATLeyendasFiscales(BaseModel):
texto_leyenda = TextField(index=True)
norma = TextField(default='')
disposicion_fiscal = TextField(default='')
active = BooleanField(default=True)
default = BooleanField(default=False)
class Meta:
order_by = ('texto_leyenda',)
indexes = (
(('texto_leyenda', 'norma', 'disposicion_fiscal'), True),
)
def _get_all(self, values):
rows = (SATLeyendasFiscales.select(
SATLeyendasFiscales.id,
SQL(" '-' AS delete"),
SATLeyendasFiscales.texto_leyenda,
SATLeyendasFiscales.norma,
SATLeyendasFiscales.disposicion_fiscal)
.dicts()
)
return tuple(rows)
def _get_active(self, values):
rows = (SATLeyendasFiscales.select(
SATLeyendasFiscales.id,
SATLeyendasFiscales.texto_leyenda,
SATLeyendasFiscales.norma,
SATLeyendasFiscales.disposicion_fiscal)
.where(SATLeyendasFiscales.active==True)
.dicts()
)
return tuple(rows)
@classmethod
def get_by_id(cls, ids):
rows = (SATLeyendasFiscales.select(
SATLeyendasFiscales.texto_leyenda.alias('textoLeyenda'),
SATLeyendasFiscales.norma,
SATLeyendasFiscales.disposicion_fiscal.alias('disposicionFiscal'))
.where(SATLeyendasFiscales.id.in_(ids))
.dicts()
)
for row in rows:
if not row['norma']:
del row['norma']
if not row['disposicionFiscal']:
del row['disposicionFiscal']
return tuple(rows)
@classmethod
def get_values(cls, values):
opt = values.pop('opt')
return getattr(cls, '_get_{}'.format(opt))(cls, values)
def _new(self, values):
try:
obj = SATLeyendasFiscales.create(**values)
values['id'] = obj.id
values['delete'] = '-'
result = {'ok': True, 'row': values}
except Exception as e:
result = {'ok': False, 'msg': 'La Leyenda Fiscal ya existe'}
return result
@classmethod
def post(cls, values):
opt = values.pop('opt')
return getattr(cls, '_{}'.format(opt))(cls, values)
@classmethod
def remove(cls, id):
q = SATLeyendasFiscales.delete().where(SATLeyendasFiscales.id==id)
return bool(q.execute())
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)
id_fiscal = TextField(default='')
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='')
correo_facturasp = 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')
plantilla = TextField(default='')
regimen_fiscal = TextField(default='')
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)
change_balance = Configuracion.get_bool('chk_config_change_balance_partner')
balance = fields.pop('partner_balance', '').replace('$', '').replace(',', '')
if change_balance:
fields['saldo_cliente'] = round(float(balance), 2)
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', ''))
ft = ('nombre_comercial', 'calle', 'no_exterior', 'no_interior',
'colonia', 'municipio', 'estado', 'pais', 'codigo_postal', 'notas',
'telefonos', 'cuenta_cliente', 'cuenta_proveedor', 'web',
'correo_facturas', 'plantilla', 'id_fiscal')
for name in ft:
fields[name] = values.get(name, '')
fields['correo_facturasp'] = values.pop('partner_email_fp', '')
if fields['pais'] != 'MĆ©xico':
fields['pais'] = fields['pais'].upper()
if 'regimenes' in fields:
fields['regimenes'] = utils.loads(fields['regimenes'])
if isinstance(fields['regimenes'], list):
fields['regimenes'] = tuple(map(int, fields['regimenes']))
else:
fields['regimenes'] = (fields['regimenes'],)
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']))
row['partner_balance'] = row.pop('saldo_cliente')
row['partner_email_fp'] = row.pop('correo_facturasp')
row['regimenes'] = SociosRegimenes.get_by_socio(row['id'])
return row
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'),
Socios.codigo_postal)
.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):
client = row[0]
client['regimenes'] = SociosRegimenes.get_by_key(client['id'])
return {'ok': True, 'row': client}
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'),
Socios.codigo_postal)
.join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios)
.join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios)
.where((Socios.es_cliente==True & Socios.es_activo==True) &
(Socios.rfc.contains(name) |
Socios.nombre.contains(name)))
.dicts())
return tuple(rows)
return {'ok': False}
@classmethod
def add(cls, values):
accounts = util.loads(values.pop('accounts', '[]'))
fields = cls._clean(cls, values)
regimenes = fields.pop('regimenes', ())
w = ((Socios.rfc==fields['rfc']) & (Socios.slug==fields['slug']))
if Socios.select().where(w).exists():
msg = 'Ya existe el RFC y RazĆ³n Social'
data = {'ok': False, 'row': {}, 'new': True, 'msg': msg}
return data
try:
obj = Socios.create(**fields)
except IntegrityError as e:
msg = 'Ocurrio un error, al dar de alta el emisor'
data = {'ok': False, 'row': {}, 'new': True, 'msg': msg}
return data
#~ ToDo Agregar tags
for account in accounts:
try:
bank = SATBancos.get_by_name(account['banco'])
fields = {
'socio': obj,
'banco': bank,
'cuenta': account['cuenta'],
'clabe': account['clabe'],
}
o = SociosCuentasBanco.create(**fields)
except IntegrityError:
pass
for regimen in regimenes:
try:
fields = {
'socio': obj,
'regimen': regimen,
}
SociosRegimenes.create(**fields)
except IntegrityError:
pass
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)
fields.pop('accounts', '')
regimenes = fields.pop('regimenes', ())
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
obj = Socios.get(Socios.id==id)
q = SociosRegimenes.delete().where(SociosRegimenes.socio==id)
q.execute()
for regimen in regimenes:
try:
fields = {
'socio': obj,
'regimen': regimen,
}
SociosRegimenes.create(**fields)
except IntegrityError:
pass
obj = Socios.get(Socios.id==id)
row = {
'id': id,
'rfc': obj.rfc,
'nombre': obj.nombre,
'saldo_cliente': obj.saldo_cliente,
}
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 = SociosCuentasBanco.delete().where(SociosCuentasBanco.socio==id)
q.execute()
q = SociosRegimenes.delete().where(SociosRegimenes.socio==id)
q.execute()
q = Socios.delete().where(Socios.id==id)
return bool(q.execute())
@classmethod
def opt(cls, args):
opt = args.get('opt', '')
if opt == 'reset':
return cls._reset_saldo(cls, args['id'])
return {'ok': False}
# ~ v2
@classmethod
def post(cls, values):
opt = values['opt']
args = values['values']
return getattr(cls, f'_{opt}')(cls, args)
class SociosCuentasBanco(BaseModel):
socio = ForeignKeyField(Socios)
activa = BooleanField(default=True)
banco = ForeignKeyField(SATBancos)
cuenta = TextField(default='')
clabe = TextField(default='')
moneda = ForeignKeyField(SATMonedas, null=True)
class Meta:
order_by = ('socio', 'banco', 'cuenta')
indexes = (
(('socio', 'banco', 'cuenta'), True),
)
def __str__(self):
return '{} ({})'.format(self.banco.name, self.cuenta[-4:])
def _get_by_name(self, values):
name = values['name']
query = (SociosCuentasBanco
.select()
.where(SociosCuentasBanco.activa==True, Socios.nombre==name)
.join(Socios).switch(SociosCuentasBanco)
)
rows = tuple([{'id': q.id, 'value': str(q)} for q in query])
return {'ok': True, 'values': rows}
def _get_by_partner(self, values):
id = int(values['id_partner'])
rows = (SociosCuentasBanco.select(
SociosCuentasBanco.id,
SQL(" '-' AS delete"),
SATBancos.name.alias('banco'),
SociosCuentasBanco.cuenta,
SociosCuentasBanco.clabe)
.join(SATBancos).switch(SociosCuentasBanco)
.where(SociosCuentasBanco.socio==id)
.dicts())
return tuple(rows)
@classmethod
def get_values(cls, values):
opt = values.pop('opt')
return getattr(cls, '_get_{}'.format(opt))(cls, values)
@classmethod
def post(cls, values):
opt = values.pop('opt')
return getattr(cls, '_{}'.format(opt))(cls, values)
def _new(self, values):
values = util.loads(values['values'])
bank = SATBancos.get_by_name(values['banco'])
fields = {
'socio': values['id_partner'],
'banco': bank,
'cuenta': values['cuenta'],
'clabe': values['clabe'],
}
try:
obj = SociosCuentasBanco.create(**fields)
except IntegrityError as e:
msg = 'Ya existe esta cuenta'
data = {'ok': False, 'msg': msg}
return data
msg = 'Cuenta de banco agregada correctamente'
data = {'ok': True, 'id': obj.id, 'msg': msg}
return data
def _delete(self, values):
values = util.loads(values['values'])
id = int(values['id'])
q = SociosCuentasBanco.delete().where(SociosCuentasBanco.id==id)
result = bool(q.execute())
msg = 'Cuenta borrada correctamente'
return {'ok': result, 'msg': msg}
@classmethod
def validate_partner_account(cls, id, invoices):
id_invoice = int(tuple(invoices.keys())[0])
account = SociosCuentasBanco.get(SociosCuentasBanco.id==id)
invoice = Facturas.get(Facturas.id==id_invoice)
return account.socio == invoice.cliente
class SociosRegimenes(BaseModel):
socio = ForeignKeyField(Socios)
regimen = ForeignKeyField(SATRegimenes)
class Meta:
indexes = (
(('socio', 'regimen'), True),
)
@classmethod
def get_by_key(cls, socio, user=None):
fields = (SATRegimenes.key.alias('id'), SATRegimenes.name.alias('value'))
where = (SociosRegimenes.socio == socio)
regimenes = (SociosRegimenes
.select(*fields)
.where(where)
.join(SATRegimenes).switch(SociosRegimenes)
.dicts()
)
return tuple(regimenes)
@classmethod
def get_by_socio(cls, socio, user=None):
fields = (SATRegimenes.id,)
where = (SociosRegimenes.socio == socio)
regimenes = (SociosRegimenes
.select(*fields)
.where(where)
.join(SATRegimenes).switch(SociosRegimenes)
.tuples()
)
regimenes = [r[0] for r in regimenes]
return regimenes
@classmethod
def _get_by_id(cls, filters, user):
id = int(filters['id'])
return cls.get_by_key(id)
@classmethod
def get_data(cls, filters, user):
# ~ print('FILERS', filters)
opt = filters['opt']
return getattr(cls, f'_get_{opt}')(filters, user)
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 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='')
cuenta_socio = ForeignKeyField(SociosCuentasBanco, null=True)
tipo_cadena_pago = TextField(default='')
certificado_pago = TextField(default='')
cadena_pago = TextField(default='')
sello_pago = TextField(default='')
class Meta:
order_by = ('fecha', 'id')
def _ultimo_saldo(self, cuenta, fecha):
query = (MovimientosBanco
.select()
.where(
(MovimientosBanco.cuenta==cuenta) &
(MovimientosBanco.fecha<=fecha) &
(MovimientosBanco.cancelado==False))[-1]
)
return round(float(query.saldo), DECIMALES)
def _movimiento_anterior(self, cuenta, fecha):
query = (MovimientosBanco
.select()
.where(
(MovimientosBanco.cuenta==cuenta) &
(MovimientosBanco.fecha<fecha) &
(MovimientosBanco.cancelado==False))[-1]
)
return query
def _actualizar_saldos(self, row):
query = (MovimientosBanco
.select()
.where(
(MovimientosBanco.cuenta==row.cuenta) &
(MovimientosBanco.fecha>row.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
def _update_description(self, values):
try:
obj = MovimientosBanco.get(MovimientosBanco.id==values['id'])
except MovimientosBanco.DoesNotExist:
msg = 'No se encontrĆ³ el movimiento'
return {'ok': False, 'msg': msg}
obj.descripcion = values['description']
obj.save()
msg = 'DescripciĆ³n actualizada correctamente'
return {'ok': True, 'msg': msg}
@classmethod
def post(cls, values):
opt = values.pop('opt')
return getattr(cls, '_{}'.format(opt))(cls, values)
def _add(self, values):
ids = values.pop('ids', '')
cuenta_socio = values.get('cuenta_socio', None)
if not ids:
cuenta_socio = None
if ids and cuenta_socio and \
not SociosCuentasBanco.validate_partner_account(cuenta_socio, util.loads(ids)):
msg = 'La cuenta seleccionada no corresponde al cliente'
data = {'ok': False, 'msg': msg}
return data
if ids and not Facturas.validate_count_partners(util.loads(ids)):
msg = 'Facturas relacionadas a diferentes clientes'
data = {'ok': False, 'msg': msg}
return data
actualizar = False
if 'saldo' in values:
saldo = values['saldo']
else:
actualizar = True
hora = values.pop('hora')
account = CuentasBanco.get(CuentasBanco.id==int(values['cuenta']))
values['fecha'] = '{}T{}'.format(values['fecha'][:10], hora)
values['cuenta'] = account
values['cuenta_socio'] = cuenta_socio
values['moneda'] = account.moneda.key
values['retiro'] = util.get_float(values['retiro'])
values['deposito'] = util.get_float(values['deposito'])
values['tipo_cambio'] = util.get_float(
values.get('tipo_cambio', 1.00), True)
values['forma_pago'] = int(values['forma_pago'])
ultimo_saldo = self._ultimo_saldo(
self, values['cuenta'], values['fecha'])
values['saldo'] = \
ultimo_saldo - values['retiro'] + values['deposito']
with database_proxy.transaction():
try:
obj = MovimientosBanco.create(**values)
except IntegrityError:
msg = 'Error al agregar el movimiento'
return {'ok': False, 'msg': msg}
if actualizar:
saldo = self._actualizar_saldos(self, obj)
if ids:
FacturasPagos.add(obj, util.loads(ids))
return {'ok': True, 'saldo': saldo}
def _cancel(self, values):
id = int(values['id'])
try:
obj = MovimientosBanco.get(MovimientosBanco.id==id)
except MovimientosBanco.DoesNotExist:
msg = 'No se encontrĆ³ el movimiento'
return {'ok': False, 'msg': msg}
if obj.cancelado:
msg = 'El movimiento ya esta cancelado'
return {'ok': False, 'msg': msg}
if obj.conciliado:
msg = 'El movimiento esta conciliado, no se puede cancelar'
return {'ok': False, 'msg': msg}
# ~ filters = (CfdiPagos.movimiento==obj)
# ~ cp = CfdiPagos.select().where(filters).count()
# ~ if cp > 0:
# ~ msg = 'El movimiento tiene Factura de Pago, no se puede cancelar'
# ~ return {'ok': False, 'msg': msg}
with database_proxy.transaction():
obj.cancelado = True
obj.save()
FacturasPagos.cancelar(obj)
obj = self._movimiento_anterior(self, obj.cuenta, obj.fecha)
self._actualizar_saldos(self, obj)
balance = CuentasBanco.get_saldo(obj.cuenta.id)
msg = 'Movimiento cancelado correctamente'
return {'ok': True, 'msg': msg, 'balance': balance}
@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 = util.loads(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,
SATFormaPago.name.alias('way_payment'),
MovimientosBanco.descripcion,
MovimientosBanco.retiro,
MovimientosBanco.deposito,
MovimientosBanco.saldo,
fn.COUNT(CfdiPagos.id).alias('invoice')
)
.join(SATFormaPago).switch(MovimientosBanco)
.join(CfdiPagos, JOIN.LEFT_OUTER).switch(MovimientosBanco)
.where(filtros)
.group_by(MovimientosBanco.id, SATFormaPago.name)
.dicts()
)
return {'ok': True, 'rows': rows}
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')
def _clean(self, values):
fields = util.clean(util.loads(values))
fields['rfc'] = fields['rfc'].upper().strip()
fields['curp'] = fields['curp'].upper()
fields['nombre'] = util.spaces(fields['nombre'])
fields['paterno'] = util.spaces(fields['paterno'])
fields['materno'] = util.spaces(fields['materno'])
return fields
def _get(self, where):
rows = (Alumnos
.select()
.where(where)
.dicts()
)
return tuple(rows)
@classmethod
def get_by_name(cls, values):
rows = ()
name = values.get('name', '')
if name:
rows = (Alumnos
.select(
Alumnos.id,
Alumnos.nombre,
Alumnos.paterno,
Alumnos.materno,
Alumnos.rfc)
.where((Alumnos.es_activo==True) &
(Alumnos.nombre.contains(name) |
Alumnos.paterno.contains(name) |
Alumnos.materno.contains(name) |
Alumnos.rfc.contains(name)))
.dicts())
rows = tuple(rows)
return rows
@classmethod
def get_by(cls, values):
if 'id' in values:
id = int(values['id'])
w = (Alumnos.id==id)
rows = cls._get(cls, w)
return rows[0]
if not values:
w = None
return cls._get(cls, w)
@classmethod
def add(cls, values):
fields = cls._clean(cls, values)
try:
obj = Alumnos.create(**fields)
except IntegrityError as e:
msg = 'Ya existe un alumno con este CURP'
data = {'ok': False, 'msg': msg}
return data
data = {'ok': True}
return data
@classmethod
def actualizar(cls, values):
fields = cls._clean(cls, values)
id = int(fields.pop('id'))
try:
q = Alumnos.update(**fields).where(Alumnos.id==id)
q.execute()
except IntegrityError:
msg = 'Ya existe un Alumno con este CURP'
data = {'ok': False, 'msg': msg}
return data
data = {'ok': True}
return data
@classmethod
def remove(cls, id):
q = Alumnos.delete().where(Alumnos.id==id)
return bool(q.execute())
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):
name = TextField(unique=True)
ubicacion = TextField(default='')
class Meta:
order_by = ('name',)
@classmethod
def get_for_sucursales(cls):
rows = (Almacenes
.select(
Almacenes.id,
Almacenes.name.alias('value'),
)
.dicts()
)
return tuple(rows)
@classmethod
def _get_for_select(cls, values={}):
rows = (Almacenes
.select(
Almacenes.id,
Almacenes.name.alias('value'),
)
.dicts()
)
return tuple(rows)
@classmethod
def _get_all(cls, values):
rows = (Almacenes
.select(
Almacenes.id,
Almacenes.name,
)
.dicts()
)
return tuple(rows)
@classmethod
def get_data(cls, values):
opt = values['opt']
return getattr(cls, f'_get_{opt}')(values)
@classmethod
def _create(cls, args, user):
try:
obj, created = Almacenes.get_or_create(**args)
except Exception as e:
log.error(e)
msg = 'Ocurrio un error al agregar el almacen'
result = {'ok': False, 'msg': msg}
return result
if created:
rows = Almacenes._get_all({})
result = {'ok': True, 'rows': rows}
else:
msg = 'El almacen ya existe'
result = {'ok': True, 'msg': msg}
return result
@classmethod
def _delete(cls, args, user):
id = args['id']
try:
q = Almacenes.delete().where(Almacenes.id==id)
r = bool(q.execute())
result = {'ok': r}
except Exception as e:
log.error(e)
result = {'ok': False, 'msg': 'Ocurrio un error al intentar eliminar'}
return result
@classmethod
def post(cls, values, user):
opt = values['opt']
args = utils.loads(values['values'])
return getattr(cls, f'_{opt}')(args, user)
class Sucursales(BaseModel):
warehouse = ForeignKeyField(Almacenes, null=True)
nombre = TextField(default='')
direccion = TextField(default='')
serie_facturas = TextField(default='')
serie_tickets = TextField(default='')
class Meta:
order_by = ('nombre',)
@classmethod
def _get_for_admin(cls, args):
rows = (Sucursales
.select(
Sucursales.id,
Sucursales.nombre.alias('name'),
Sucursales.serie_facturas.alias('serie_invoice'),
Sucursales.serie_tickets,
Almacenes.name.alias('warehouse'),
)
.join(Almacenes, JOIN.LEFT_OUTER).switch(Sucursales)
.dicts()
)
return tuple(rows)
@classmethod
def _get_for_select(cls, args):
fields = (
Sucursales.id,
Sucursales.nombre.alias('value')
)
rows = Sucursales.select(*fields).dicts()
return tuple(rows)
@classmethod
def _get_there_are_branchs(cls, args):
result = bool(Sucursales.select().count())
return result
@classmethod
def get_data(cls, values):
opt = values['opt']
return getattr(cls, f'_get_{opt}')(values)
@classmethod
def _create(cls, args):
try:
values = utils.loads(args)
Sucursales.create(**values)
result = {'ok': True}
except Exception as e:
log.error(e)
msg = 'Ocurrio un error al agregar la sucursal'
result = {'ok': False, 'msg': msg}
return result
@classmethod
def _delete(cls, args):
values = utils.loads(args)
id = values['id']
try:
q = Sucursales.delete().where(Sucursales.id==id)
r = bool(q.execute())
result = {'ok': r}
except Exception as e:
log.error(e)
result = {'ok': False, 'msg': 'Ocurrio un error al intentar eliminar'}
return result
@classmethod
def post(cls, values):
opt = values['opt']
args = values['values']
return getattr(cls, f'_{opt}')(args)
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)
sucursal = ForeignKeyField(Sucursales, null=True)
rol = ForeignKeyField(Roles, null=True)
def __str__(self):
t = '{} {} ({})'
return t.format(self.nombre, self.apellidos, self.usuario)
class Meta:
order_by = ('nombre', 'apellidos')
def _get_is_admin(self, filters, user):
return {'is_admin': user.es_admin}
def _get_is_superadmin(self, filters, user):
return {'is_superadmin': user.es_superusuario}
@classmethod
def get_data(cls, filters, user):
method = f"_get_{filters['opt']}"
return getattr(cls, method)(cls, filters, user)
@classmethod
def _set_branch_null(cls, args, user):
id = args['id']
values = {'sucursal': None}
where = (Usuarios.id == id)
q = (Usuarios
.update(**values)
.where(where)
)
result = bool(q.execute())
response = {'ok': result}
return response
@classmethod
def _set_branch(cls, args, user):
id = args['id_user']
values = {'sucursal': args['id_branch']}
where = (Usuarios.id == id)
q = (Usuarios
.update(**values)
.where(where)
)
result = bool(q.execute())
response = {'ok': result}
return response
@classmethod
def post(cls, values, user):
opt = values['opt']
args = utils.loads(values['values'])
return getattr(cls, f'_{opt}')(args, user)
@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):
# ~ print (values)
id = int(values.pop('id'))
try:
if 'contra' in values:
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 Productos(BaseModel):
categoria = ForeignKeyField(Categorias, null=True)
clave = TextField(unique=True, index=True)
clave_sat = TextField(default='')
descripcion = TextField(index=True)
unidad = ForeignKeyField(SATUnidades)
valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6,
auto_round=True)
ultimo_costo = DecimalField(default=0.0, max_digits=18, decimal_places=6,
auto_round=True)
descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6,
auto_round=True)
inventario = BooleanField(default=False)
existencia = DecimalField(default=0.0, max_digits=18, decimal_places=2,
auto_round=True)
minimo = DecimalField(default=0.0, max_digits=18, decimal_places=2,
auto_round=True)
codigo_barras = TextField(default='')
cuenta_predial = TextField(default='')
es_activo = BooleanField(default=True)
impuestos = ManyToManyField(SATImpuestos, related_name='productos')
tags = ManyToManyField(Tags, related_name='productos_tags')
cantidad_empaque = DecimalField(default=0.0, max_digits=14, decimal_places=4,
auto_round=True)
is_discontinued = BooleanField(default=False)
objeto_impuesto = TextField(default='02')
class Meta:
order_by = ('descripcion',)
@classmethod
def _get_by_key(cls, args, user):
try:
warehouse = user.sucursal.warehouse
except:
warehouse = None
key = args['key']
select = (
Productos.id.alias('id_product'),
Productos.clave,
Productos.clave_sat,
Productos.descripcion,
SATUnidades.id.alias('unidad'),
Productos.valor_unitario,
Productos.descuento,
Productos.inventario,
Productos.existencia,
Productos.objeto_impuesto,
)
where = (
(Productos.es_activo==True) &
(
(Productos.clave==key) |
(Productos.codigo_barras==key)
)
)
row = (Productos
.select(*select)
.join(SATUnidades).switch(Productos)
.where(where)
.limit(1)
.dicts()
)
if not len(row):
return {'ok': False}
obj = row[0]
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==obj['id_product']).dicts()
)
if obj['inventario'] and not warehouse is None:
obj['existencia'] = WareHouseProduct.get_exists_by_warehouse(
obj['id_product'], warehouse)
result = {'ok': True, 'row': obj, 'taxes': taxes}
return result
@classmethod
def get_data(cls, values, user):
opt = values.pop('opt')
return getattr(cls, f'_get_{opt}')(values, user)
@classmethod
def next_key(cls):
try:
with database_proxy.transaction():
value = (Productos
.select(fn.Max(cast(Productos.clave, 'int')).alias('fm'))
.order_by(SQL('fm'))
.scalar())
except Exception as e:
values = (Productos
.select(Productos.clave)
.order_by(Productos.clave)
.tuples()
)
values = [int(v[0]) for v in values if v[0].isdigit()]
value = 0
if values:
value = max(values)
value = value or 0
value += 1
return {'value': value}
@classmethod
def get_by_key(cls, values):
clave = values.get('key', '')
row = (Productos
.select(
Productos.id.alias('id_product'),
Productos.clave,
Productos.clave_sat,
Productos.descripcion,
SATUnidades.id.alias('unidad'),
Productos.valor_unitario,
Productos.descuento,
Productos.inventario,
Productos.existencia,
Productos.objeto_impuesto)
.join(SATUnidades).switch(Productos)
.where((Productos.es_activo==True) &
((Productos.clave==clave) | (Productos.codigo_barras==clave)))
.dicts()
)
if len(row):
id = row[0]['id_product']
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())
product = row[0]
return {'ok': True, 'row': product, 'taxes': taxes}
return {'ok': False}
def _validate_import(self, product):
product['categoria'] = Categorias.get_by_id(int(product['categoria']))
product['unidad'] = SATUnidades.get_by_name(product['unidad'])
product['inventario'] = bool(product['inventario'])
if not product['inventario']:
product['existencia'] = 0.0
impuestos = product['impuestos'].split('|')
if not impuestos:
taxes = [SATImpuestos.select().where(SATImpuestos.id==6)]
else:
taxes = []
for i in range(0, len(impuestos), 3):
w = (
(SATImpuestos.key == impuestos[i]) &
(SATImpuestos.name == impuestos[i+1]) &
(SATImpuestos.tasa == float(impuestos[i+2]))
)
try:
taxes.append(SATImpuestos.get(w))
except:
pass
product['impuestos'] = taxes
w = (Productos.clave==product['clave'])
return product, w
def _import(self):
emisor = Emisor.select()[0]
rows, msg = util.import_products(emisor.rfc)
if not rows:
return {'ok': False, 'msg': msg}
cs = 0
np = 0
ap = 0
for p in rows:
data, w = self._validate_import(self, p)
if data['unidad'] is None:
msg = 'Producto: {} - No se encontrĆ³ la unidad'.format(
data['clave'])
log.error(msg)
continue
result = util.get_sat_key('productos', data['clave_sat'])
if not result['ok']:
msg = 'Producto: {} - Clave SAT incorrecta: {}'.format(
data['clave'], data['clave_sat'])
log.error(msg)
cs += 1
continue
# ~ print (data)
taxes = data.pop('impuestos')
try:
with database_proxy.transaction():
if Productos.select().where(w).exists():
q = Productos.update(**data).where(w)
q.execute()
obj = Productos.get(w)
obj.impuestos = taxes
log.info('\tProducto actualizado: {}'.format(data['clave']))
ap += 1
else:
obj = Productos.create(**data)
obj.impuestos = taxes
log.info('\tProducto agregado: {}'.format(data['clave']))
np += 1
except Exception as e:
msg = 'Error al importar producto: {}'.format(data['clave'])
log.error(msg)
log.error(e)
msg = 'Productos encontrados: {}<BR>'.format(len(rows))
msg += 'Productos agregados: {}<BR>'.format(np)
msg += 'Productos actualizados: {}<BR>'.format(ap)
msg += 'Productos con problemas: {}<BR>'.format(len(rows) - np - ap)
msg += 'Productos con clave SAT erronea: {}'.format(cs)
return {'ok': True, 'msg': msg}
@classmethod
def opt(cls, values):
if values['opt'] == 'import':
return cls._import(cls)
return {'ok': False, 'msg': 'Sin opciĆ³n'}
@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.es_activo==True) &
((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.es_activo==True) &
((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.codigo_barras,
Productos.cuenta_predial,
Productos.inventario,
Productos.existencia,
Productos.minimo,
Productos.objeto_impuesto,
# ~ Productos.cantidad_empaque.alias('cant_by_packing'),
)
.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.es_activo,
Productos.clave_sat,
Productos.clave,
Productos.descripcion,
SATUnidades.name.alias('unidad'),
Productos.valor_unitario,
Productos.existencia)
.join(SATUnidades)
.dicts()
)
return {'ok': True, 'rows': tuple(rows)}
def _clean(self, values):
taxes = util.loads(values.pop('taxes'))
descripcion = util.spaces(values.pop('descripcion'))
fields = util.clean(values)
fields['cantidad_empaque'] = fields.pop('cant_by_packing', 0.0)
fields.pop('precio_con_impuestos', '')
fields['es_activo'] = fields.pop('es_activo_producto')
fields['descripcion'] = 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,
'es_activo': obj.es_activo,
'clave': obj.clave,
'clave_sat': obj.clave_sat,
'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):
values['cuenta_predial'] = values.get('cuenta_predial', '')
values['codigo_barras'] = values.get('codigo_barras', '')
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
except Exception as e:
msg = str(e)
if 'productos_clave' in msg:
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,
'es_activo': obj.es_activo,
'clave': obj.clave,
'clave_sat': obj.clave_sat,
'descripcion': obj.descripcion,
'unidad': obj.unidad.name,
'valor_unitario': obj.valor_unitario,
'existencia': obj.existencia,
}
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 WareHouseProduct(BaseModel):
warehouse = ForeignKeyField(Almacenes)
product = ForeignKeyField(Productos)
exists = DecimalField(default=0.0, max_digits=18, decimal_places=2,
auto_round=True)
class Meta:
indexes = (
(('warehouse', 'product'), True),
)
@classmethod
def _get_by_product(cls, args, user):
id = int(args['id'])
select = (
Almacenes.id,
Almacenes.name.alias('warehouse'),
WareHouseProduct.exists,
)
where = (WareHouseProduct.product==id)
rows = (WareHouseProduct
.select(*select)
.join(Almacenes)
.switch(WareHouseProduct)
.where(where)
.dicts()
)
return tuple(rows)
@classmethod
def get_exists_by_warehouse(cls, product, warehouse):
where = (
(WareHouseProduct.product==product) &
(WareHouseProduct.warehouse==warehouse)
)
exists = (WareHouseProduct
.select(WareHouseProduct.exists)
.where(where)
.limit(1)
.scalar()
) or 0
return exists
@classmethod
def _get_exists(cls, args, user):
id_product = int(args['id'])
warehouse = None
try:
warehouse = user.sucursal.warehouse
except:
obj = Productos.get(id==id_product)
return obj.existencia
where = (
(WareHouseProduct.product==id) &
(WareHouseProduct.warehouse==warehouse)
)
exists = (WareHouseProduct
.select(WareHouseProduct.exists)
.where(where)
.limit(1)
.scalar()
)
return exists
@classmethod
def get_data(cls, values, user):
opt = values.pop('opt')
return getattr(cls, f'_get_{opt}')(values, user)
@classmethod
def update_exists(cls, args):
if args['storage'] is None:
return
values = dict(
warehouse = args['storage'],
product = args['product'],
)
obj, created = WareHouseProduct.get_or_create(**values)
cant = args['cant']
if created:
cant = float(cant)
obj.exists += cant
obj.save()
return
@classmethod
def _adjust_stock(cls, args, user):
cant = Decimal(args['cant'])
fields = dict(
warehouse = args['storage'],
product = args['id_product'],
)
obj = WareHouseProduct.get(**fields)
obj.exists += cant
obj.save()
total = (WareHouseProduct
.select(fn.Sum(WareHouseProduct.exists)).alias('total')
.where(WareHouseProduct.product==fields['product'])
.limit(1)
.scalar()
)
query = (Productos
.update(existencia=total)
.where(Productos.id==fields['product'])
)
query.execute()
msg = 'ajustar stock'
_save_log(user.usuario, msg, 'WHP')
result = {'ok': True, 'row': {'existencia': total}}
return result
@classmethod
def post(cls, values, user):
opt = values['opt']
args = utils.loads(values['values'])
return getattr(cls, f'_{opt}')(args, user)
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 = BigIntegerField(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='')
divisas = 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='')
exportacion = TextField(default='01')
receptor_regimen = TextField(default='')
periodicidad = TextField(default='')
class Meta:
order_by = ('fecha',)
@classmethod
def validate_count_partners(cls, ids):
"""Validate if invoices IDs are of unique client"""
filters = (Facturas.id.in_(tuple(ids.keys())))
partners = (Facturas.select(fn.COUNT(Socios.rfc))
.where(filters)
.join(Socios).switch(Facturas)
.group_by(Socios.rfc)
.order_by(Socios.rfc).tuples())
if len(partners) > 1:
return False
return True
@classmethod
def cancel(cls, args):
obj = Facturas.get(Facturas.id==args['id'])
if obj.uuid is None:
obj.estatus = 'Cancelada'
obj.cancelada = True
obj.fecha_cancelacion = util.now()
obj.save()
msg = 'Factura cancelada correctamente'
return {'ok': True, 'msg': msg, 'row': {'estatus': obj.estatus}}
return cls._cancel_xml_sign(obj, args)
@classmethod
def _cancel_xml_sign(cls, invoice, args):
if not invoice.version in CANCEL_VERSION:
msg = 'Solo es posible cancelar CFDI >= 3.3'
return {'ok': False, 'msg': msg}
pac = utils.get_pac_by_rfc(invoice.xml)
auth = Configuracion.get_({'fields': 'auth_by_pac', 'pac': pac})
certificado = Certificado.get(Certificado.es_fiel==False)
result = utils.cancel_xml_sign(invoice, args, auth, certificado)
if result['ok']:
invoice.estatus = 'Cancelada'
invoice.error = ''
invoice.cancelada = True
invoice.fecha_cancelacion = result['date']
invoice.acuse = result['acuse'] or ''
cls._actualizar_saldo_cliente(cls, invoice, True)
cls._update_inventory(cls, invoice, True)
cls._uncancel_tickets(cls, invoice)
else:
invoice.error = result['msg']
invoice.save()
data = {'ok': result['ok'], 'msg': result['msg'], 'row': result['row']}
return data
def _cancel_xml(self, id):
msg = 'Factura cancelada correctamente'
auth = Emisor.get_auth()
certificado = Certificado.select()[0]
obj = Facturas.get(Facturas.id==id)
if obj.version == '3.2':
msg = 'No es posible cancelar CFDI 3.2'
return {'ok': False, 'msg': msg}
data, result = util.cancel_xml(auth, obj.uuid, certificado)
if data['ok']:
obj.estatus = 'Cancelada'
obj.error = ''
obj.cancelada = True
obj.fecha_cancelacion = result['date']
obj.acuse = result['acuse'] or ''
self._actualizar_saldo_cliente(self, obj, True)
self._update_inventory(self, obj, True)
self._uncancel_tickets(self, obj)
else:
obj.error = data['msg']
obj.save()
return data
@utils.run_in_thread
def _uncancel_tickets(self, invoice):
query = (Tickets
.update(estatus='Generado', cancelado=False, factura=None)
.where(Tickets.factura==invoice)
)
query.execute()
return
def _get_filters(self, values):
if 'start' in values and 'end' in values:
filters = Facturas.fecha.between(
utils.parse_date(values['start']),
utils.parse_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)
if 'client' in values:
filters &= (Socios.nombre==values['client'])
return filters
def _get_invoices(self, filters, user):
filter_serie = False
filter_by_user = Configuracion.get_bool('chk_config_user_show_doc')
if not filter_by_user:
if not user.sucursal is None and not user.sucursal.serie_facturas is None:
filter_serie = (Facturas.serie==user.sucursal.serie_facturas)
if filter_serie:
filters &= filter_serie
rows = tuple(Facturas.select(
Facturas.id,
Facturas.serie,
Facturas.folio,
Facturas.uuid,
Facturas.fecha,
Facturas.tipo_comprobante,
Facturas.estatus,
case(Facturas.pagada, (
(True, 'Si'),
(False, 'No'),
)).alias('paid'),
Facturas.metodo_pago,
Facturas.total,
Facturas.moneda.alias('currency'),
Facturas.total_mn,
Socios.nombre.alias('cliente'))
.where(filters)
.join(Socios)
.switch(Facturas).dicts()
)
return {'ok': True, 'rows': rows}
def _get_by_dates(self, filters, user):
filters = self._get_filters(self, filters)
return self._get_invoices(self, filters, user)
def _get_by_notes(self, filters, user):
notes = filters['notes']
filters = self._get_filters(self, filters)
filters &= (Facturas.notas.contains(notes))
return self._get_invoices(self, filters, user)
@classmethod
def get_by(cls, filters, user):
return getattr(cls, f"_get_by_{filters['by']}")(cls, filters, user)
@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
@classmethod
def get_notes(cls, id):
obj = Facturas.get(Facturas.id==int(id))
return {'notes': obj.notas}
@classmethod
def save_notes(cls, values):
obj = Facturas.get(Facturas.id==int(values['id']))
obj.notas = values.get('notes', '')
obj.save()
return {'ok': True, 'msg': 'Notas guardadas correctamente'}
def _get_not_in_xml(self, invoice, emisor):
pdf_from = Configuracion.get_('make_pdf_from') or '1'
values = {'el.version': VERSION_EMPRESA_LIBRE}
values['notas'] = invoice.notas
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',
}
if invoice.metodo_pago:
values['metododepago'] = 'MĆ©todo de Pago: ({}) {}'.format(
invoice.metodo_pago, mp[invoice.metodo_pago])
if invoice.forma_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)
receptor = Socios.select().where(Socios.id==invoice.cliente.id).dicts()[0]
values['receptor'] = {}
for k, v in receptor.items():
values['receptor'][k] = v
# ~ use_packing = Configuracion.get_bool('chk_use_packing')
# ~ if use_packing:
# ~ w = FacturasDetalle.factura == invoice
# ~ q = (FacturasDetalle
# ~ .select(FacturasDetalle.empaques)
# ~ .where(w)
# ~ .order_by(FacturasDetalle.id.asc())
# ~ .tuples())
# ~ values['pakings'] = [str(int(r[0])) for r in q]
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
pdf_from = Configuracion.get_('make_pdf_from') or '1'
values = cls._get_not_in_xml(cls, obj, emisor)
#Tmp to v2
data = util.get_data_from_xml(obj, values, pdf_from)
data.update(utils.CfdiToDict(obj.xml).values)
doc = util.to_pdf(data, emisor.rfc, pdf_from=pdf_from)
if sync:
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
cls._sync_pdf(cls, doc, name, target)
return doc, name
def _get_concepto(self, node, currency):
d = util.get_dict(node.attrib)
concepto = {
'clave': f"{d['noidentificacion']}<BR>({d['claveprodserv']})",
'descripcion': self._get_description(self, node, d),
'unidad': f"{d['unidad']}<BR>({d['claveunidad']})",
'cantidad': d['cantidad'],
'valorunitario': util.format_currency(d['valorunitario'], currency),
'importe': util.format_currency(d['importe'], currency),
}
return concepto
def _get_description(self, node, data):
return data['descripcion']
def _get_tax(self, node, data, currency):
type_tax = 'Traslado'
if 'Retenciones' in node.tag:
type_tax = 'RetenciĆ³n'
for n in node:
d = util.get_dict(n.attrib)
name = VALUES_PDF['TAX'].get(d['impuesto'])
tasa = ''
if 'tasaocuota' in d:
tasa = f"{float(d['tasaocuota']):.2f}"
title = f"{type_tax} {name} {tasa}"
importe = util.format_currency(d['importe'], currency)
data['totales'].append((title, importe))
return
def _get_others_values(self, invoice, emisor):
data = {
'rfc': emisor.rfc.lower(),
'version': invoice.version,
'cancelada': VALUES_PDF['CANCEL'].get(invoice.cancelada),
'cfdi_notas': invoice.notas,
}
xml = util.parse_xml2(invoice.xml)
d = util.get_dict(xml.attrib)
d.pop('Certificado', '')
currency = d.get('Moneda', 'MXN')
data.update({f'cfdi_{k.lower()}': v for k, v in d.items()})
data['cfdi_tipodecomprobante'] = \
VALUES_PDF['TYPE'][data['cfdi_tipodecomprobante']]
if data.get('cfdi_formapago', ''):
data['cfdi_formapago'] = str(
SATFormaPago.get_by_key(data['cfdi_formapago']))
if data.get('cfdi_metodopago', ''):
data['cfdi_metodopago'] = 'MĆ©todo de Pago: ({}) {}'.format(
data['cfdi_metodopago'],
VALUES_PDF['METHOD'][data['cfdi_metodopago']])
data['cfdi_moneda'] = str(SATMonedas.get_by_key(currency))
data['cfdi_tipocambio'] = util.format_currency(
data['cfdi_tipocambio'], currency, 4)
data['cfdi_totalenletras'] = util.to_letters(
float(data['cfdi_total']), currency)
data['totales'] = [(
'Subtotal', util.format_currency(data['cfdi_subtotal'], currency))]
for node in xml:
if 'Emisor' in node.tag:
d = {f'emisor_{k.lower()}': v for k, v in node.attrib.items()}
data.update(d)
elif 'Receptor' in node.tag:
d = {f'receptor_{k.lower()}': v for k, v in node.attrib.items()}
data.update(d)
elif 'Conceptos' in node.tag:
data['conceptos'] = []
for subnode in node:
concepto = self._get_concepto(self, subnode, currency)
data['conceptos'].append(concepto)
elif 'Impuestos' in node.tag:
for subnode in node:
self._get_tax(self, subnode, data, currency)
elif 'Complemento' in node.tag:
for sn in node:
if 'TimbreFiscalDigital' in sn.tag:
d = {f'timbre_{k.lower()}': v for k, v in sn.attrib.items()}
data.update(d)
data['emisor_logo'] = data['emisor_rfc'].lower()
data['emisor_regimenfiscal'] = str(
SATRegimenes.get_by_key(data['emisor_regimenfiscal']))
data['receptor_usocfdi'] = str(
SATUsoCfdi.get_by_key(data['receptor_usocfdi']))
data['totales'].append((
'Total', util.format_currency(data['cfdi_total'], currency)))
data['timbre_cadenaoriginal'] = (
f"||{data['timbre_version']}|"
f"{data['timbre_uuid']}|"
f"{data['timbre_fechatimbrado']}|"
f"{data['timbre_sellocfd']}|"
f"{data['timbre_nocertificadosat']}||"
)
qr_data = (
f"https://verificacfdi.facturaelectronica.sat.gob.mx/"
f"default.aspx?&id={data['timbre_uuid']}&re={data['emisor_rfc']}"
f"&rr={data['receptor_rfc']}&tt={data['cfdi_total']}"
f"&fe={data['cfdi_sello'][-8:]}"
)
cbb = utils.get_qr(qr_data, 'png', True)
data['cbb'] = f'data:image/png;base64,{cbb}'
return data
@classmethod
def get_html(cls, id):
try:
emisor = Emisor.select()[0]
except IndexError:
return '', 'sin_datos_de_emisor.html'
obj = Facturas.get(Facturas.id==id)
name = '{}{}_{}.html'.format(obj.serie, obj.folio, obj.cliente.rfc)
if obj.uuid is None:
return '', name
data = cls._get_others_values(cls, obj, emisor)
doc = util.to_html(data)
# ~ util.html_to_pdf(doc)
return doc, name
@classmethod
def get_ods(cls, id, rfc):
try:
emisor = Emisor.select()[0]
except IndexError:
return b'', 'sin_datos_de_emisor.pdf'
obj = Facturas.get(Facturas.id==id)
name = '{}{}_{}.ods'.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)
data.update(utils.CfdiToDict(obj.xml).values)
doc = util.to_pdf(data, emisor.rfc, True)
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, rfc):
return Facturas.sync(id, rfc)
@util.run_in_thread
def _sync_pdf(self, pdf, name_pdf, target):
# ~ auth = Emisor.get_auth()
files = (
(pdf, name_pdf, target),
)
util.sync_cfdi(files)
return
@util.run_in_thread
def _sync_xml(self, obj):
emisor = Emisor.select()[0]
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(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
if invoice.cliente.rfc == RFCS['PUBLIC']:
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'})
contra = Configuracion.get_('correo_contra')
in_zip = Configuracion.get_bool('chk_config_send_zip')
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}
if in_zip:
files = (cls.get_zip(id, rfc),)
else:
files = (cls.get_pdf(id, rfc), cls.get_xml(id))
fields = util.make_fields(obj.xml)
starttls = bool(int(values.get('correo_starttls', '0')))
server = {
'servidor': values['correo_servidor'],
'puerto': values['correo_puerto'],
'ssl': bool(int(values['correo_ssl'])),
'starttls': starttls,
'usuario': values['correo_usuario'],
'contra': utils.decrypt(contra, rfc),
}
options = {
'para': obj.cliente.correo_facturas,
'copia': values.get('correo_copia', ''),
'confirmar': util.get_bool(values.get('correo_confirmacion', '0')),
'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, rfc):
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, rfc, False)
name_xml = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc)
target = rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
files = (
(obj.xml, name_xml, target),
(pdf, name_pdf, target),
)
util.sync_cfdi(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.in_(('I', 'ingreso'))) &
(Facturas.saldo>0)
)
if ids:
filtros &= (Facturas.id.not_in(ids))
currency = case(Facturas.moneda, (
('peso', 'MXN'),
), Facturas.moneda)
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,
currency.alias('currency'),
Facturas.total_mn,
Facturas.saldo,
)
.where(filtros)
.join(Socios)
.switch(Facturas)
.dicts()
)
return {'ok': True, 'rows': rows}
def _get_reinvoice(self, id):
obj = Facturas.get(Facturas.id==id)
receptor = {
'id': obj.cliente.id,
'nombre': obj.cliente.nombre,
'rfc': obj.cliente.rfc,
'codigo_postal': obj.cliente.codigo_postal,
'notas': obj.notas,
'regimenes': SociosRegimenes.get_by_key(obj.cliente.id)
}
invoice = {
'tipo_comprobante': obj.tipo_comprobante,
'serie': Folios.get_id(obj.serie),
'uso_cfdi': obj.uso_cfdi,
'metodo_pago': obj.metodo_pago,
'forma_pago': obj.forma_pago,
'condicion_pago': obj.condiciones_pago,
}
products = FacturasDetalle.reinvoice(id)
data = {'receptor': receptor, 'invoice': invoice, 'products': products}
return data
def _get_opt(self, values):
if values['opt'] == 'foliocustom':
return self._get_folio_custom(self, values)
if values['opt'] == 'porpagar':
return self._get_por_pagar(self, util.loads(values['ids']))
if values['opt'] == 'reinvoice':
return self._get_reinvoice(self, int(values['id']))
if values['opt'] == 'detalle':
return FacturasDetalle.get_detalle(int(values['id']))
if values['opt'] == 'statussat':
return self.get_status_sat(int(values['id']))
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,
case(Facturas.pagada, (
(True, 'Si'),
(False, 'No'),
)).alias('paid'),
Facturas.total,
Facturas.moneda.alias('currency'),
Facturas.total_mn,
Socios.nombre.alias('cliente'))
.where(filters)
.join(Socios)
.switch(Facturas).dicts()
)
return {'ok': True, 'rows': rows}
@classmethod
def remove(cls, id, user):
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()
q = FacturasComplementos.delete().where(FacturasComplementos.factura==obj)
q.execute()
Tickets.uncancel(obj)
m = 'B {}'.format(obj.id)
_save_log(user.usuario, m, 'F')
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 or inicio_serie > inicio:
inicio = inicio_serie
else:
inicio += 1
return inicio
def _get_folio_custom(self, values):
result = {'ok': False, 'msg': 'Folio libre para facturar'}
try:
folio = int(values['folio'])
except ValueError:
result['ok'] = True
result['msg'] = 'Valor de folio invƔlido'
return result
serie = values['serie']
result['ok'] = (Facturas
.select()
.where((Facturas.serie==serie) & (Facturas.folio==folio))
.exists())
if result['ok']:
result['msg'] = 'Folio previamente usado'
return result
def _calculate_totals(self, invoice, products, tipo_comprobante, user):
tax_locales = Configuracion.get_bool('chk_config_tax_locales')
tax_locales_truncate = Configuracion.get_bool('chk_config_tax_locales_truncate')
tax_decimals = Configuracion.get_bool('chk_config_tax_decimals')
# ~ use_packing = Configuracion.get_bool('chk_use_packing')
subtotal = 0
descuento_cfdi = 0
totals_tax = {}
total_trasladados = None
total_retenciones = None
locales_traslados = 0
locales_retenciones = 0
warehouse = None
try:
warehouse = user.sucursal.warehouse
except:
pass
for product in products:
id_product = product.pop('id_product')
id_student = product.pop('id_student', 0)
p = Productos.get(Productos.id==id_product)
product['unidad'] = SATUnidades.get(SATUnidades.id==product['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)
# ~ if use_packing and p.cantidad_empaque:
# ~ product['empaques'] = utils.round_up(
# ~ cantidad / float(p.cantidad_empaque))
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']
if id_student:
student = Alumnos.get(Alumnos.id==id_student)
product['alumno'] = str(student)
product['curp'] = student.curp
product['nivel'] = student.grupo.nivel.nombre
product['autorizacion'] = student.grupo.nivel.autorizacion.strip()
product['warehouse'] = warehouse
FacturasDetalle.create(**product)
if tipo_comprobante == 'T':
continue
base = product['importe'] - product['descuento']
for tax in p.impuestos:
if tax_locales and tax.tipo == 'R' and tax.key == '000':
base = product['importe']
if tax_decimals:
impuesto_producto = round(float(tax.tasa) * base, DECIMALES_TAX)
else:
impuesto_producto = round(float(tax.tasa) * base, DECIMALES)
if tax.key == '000' and tax_locales_truncate:
impuesto_producto = util.truncate(float(tax.tasa) * base)
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
if tipo_comprobante == 'T':
subtotal = 0.0
descuento = 0.0
else:
for tax in totals_tax.values():
if tax.tipo == 'E':
continue
invoice_tax = {
'factura': invoice.id,
'impuesto': tax.id,
'base': tax.base,
'importe': round(tax.suma_impuestos, DECIMALES),
}
FacturasImpuestos.create(**invoice_tax)
if not total_trasladados is None:
total_trasladados = round(total_trasladados, DECIMALES)
if not total_retenciones is None:
total_retenciones = round(total_retenciones, DECIMALES)
total = subtotal - descuento_cfdi + \
(total_trasladados or 0) - (total_retenciones or 0) \
+ locales_traslados - locales_retenciones
total = round(total, DECIMALES)
# ~ exchange_rate = float(invoice.tipo_cambio)
total_mn = round(total * invoice.tipo_cambio, DECIMALES)
# ~ total_mn = round(total * exchange_rate, 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
def _save_cartaporte(self, invoice, valores):
if not valores:
return
values = utils.loads(valores)
total_distance = 0.00
total_weight = 0.00
mercancias = values['mercancias']
for mercancia in mercancias['mercancias']:
total_weight += float(mercancia['PesoEnKg'])
if isinstance(mercancia['PesoEnKg'], (int, float)):
mercancia['PesoEnKg'] = f"{mercancia['PesoEnKg']:.2f}"
mercancias['PesoBrutoTotal'] = f"{total_weight:.2f}"
ubicaciones = values['ubicaciones']
for ubicacion in ubicaciones:
if 'DistanciaRecorrida' in ubicacion:
if ubicacion['TipoUbicacion'] == 'Origen':
del ubicacion['DistanciaRecorrida']
elif ubicacion['DistanciaRecorrida']:
total_distance += float(ubicacion['DistanciaRecorrida'])
if isinstance(ubicacion['DistanciaRecorrida'], (int, float)):
ubicacion['DistanciaRecorrida'] = f"{ubicacion['DistanciaRecorrida']:.2f}"
municipio = ubicacion.pop('Municipio')
estado = ubicacion.pop('Estado')
pais = ubicacion.pop('Pais')
cp = ubicacion.pop('CodigoPostal')
if municipio and estado and pais and cp:
ubicacion['domicilio'] = {
'Municipio': municipio,
'Estado': estado,
'Pais': pais,
'CodigoPostal': cp,
}
values['TotalDistRec'] = f"{total_distance:.2f}"
# ~ print(2, values)
data = {
'factura': invoice,
'nombre': 'cartaporte',
'valores': utils.dumps(values),
}
FacturasComplementos.create(**data)
return
def _save_comercioe(self, invoice, valores):
if not valores:
return
# ~ values = utils.loads(valores)
data = {
'factura': invoice,
'nombre': 'comercioe',
# ~ 'valores': utils.dumps(values),
'valores': valores,
}
FacturasComplementos.create(**data)
return
def _get_serie(self, user, default_serie):
if user.sucursal is None:
return default_serie
return user.sucursal.serie_facturas or default_serie
@classmethod
def add(cls, values, user):
productos = util.loads(values.pop('productos'))
relacionados = util.loads(values.pop('relacionados'))
ine = values.pop('ine', {})
cartaporte = values.pop('cartaporte', {})
comercioe = values.pop('comercioe', {})
tipo_comprobante = values['tipo_comprobante']
folio_custom = values.pop('folio_custom', '')
divisas = values.pop('divisas', '')
if Configuracion.get_bool('chk_config_divisas'):
divisas = divisas.lower()
if divisas == 'ninguna':
divisas = ''
values['divisas'] = divisas
leyendas_fiscales = utils.loads(values.pop('leyendas_fiscales', '[]'))
emisor = Emisor.select()[0]
values['serie'] = cls._get_serie(cls, user, values['serie'])
if Configuracion.get_bool('chk_folio_custom') and folio_custom:
fc = {'folio': folio_custom, 'serie': values['serie']}
result = cls._get_folio_custom(cls, fc)
if result['ok']:
data = {'ok': False, 'row': {}, 'new': True, 'msg': result['msg']}
return data
values['folio'] = int(folio_custom)
else:
values['folio'] = cls._get_folio(cls, values['serie'])
values['tipo_cambio'] = round(float(values['tipo_cambio']), 4)
values['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
values['anticipo'] = util.get_bool(values['anticipo'])
values['donativo'] = util.get_bool(values['donativo'])
if tipo_comprobante == 'T':
values['metodo_pago'] = ''
values['forma_pago'] = ''
values['moneda'] = CARTA_PORTE['MONEDA']
self_client = Socios.get(Socios.rfc==emisor.rfc)
if values['cliente'] != self_client.id:
values['cliente'] = self_client
with database_proxy.atomic() as txn:
# ~ print('VALUES\n\n', values)
obj = Facturas.create(**values)
totals = cls._calculate_totals(
cls, obj, productos, tipo_comprobante, user)
cls._guardar_relacionados(cls, obj, relacionados)
cls._guardar_ine(cls, obj, ine)
cls._save_cartaporte(cls, obj, cartaporte)
cls._save_comercioe(cls, obj, comercioe)
cls._save_leyendas_fiscales(cls, obj, leyendas_fiscales)
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 = 'G {}'.format(obj.id)
_save_log(user.usuario, msg, 'F')
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,
'paid': 'No',
'total': obj.total,
'currency': obj.moneda,
'total_mn': obj.total_mn,
'cliente': obj.cliente.nombre,
}
data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg}
return data
def _save_leyendas_fiscales(self, invoice, valores):
if not valores:
return
data = {
'factura': invoice,
'nombre': 'leyendas',
'valores': utils.dumps(valores),
}
FacturasComplementos.create(**data)
return
def _make_xml(self, invoice):
tax_decimals = Configuracion.get_bool('chk_config_tax_decimals')
decimales_precios = Configuracion.get_bool('chk_config_decimales_precios')
invoice_by_ticket = Configuracion.get_bool('chk_config_invoice_by_ticket')
# ~ is_global = (invoice.cliente.rfc == RFCS['PUBLIC']) and invoice_by_ticket
is_global = bool(invoice.periodicidad)
data_global = {}
if is_global:
now = utils.now()
data_global['Periodicidad'] = invoice.periodicidad
data_global['Meses'] = now.strftime('%m')
data_global['AƱo'] = now.strftime('%Y')
frm_vu = FORMAT
if decimales_precios:
frm_vu = FORMAT_PRECIO
tmp = 0
emisor = Emisor.select()[0]
certificado = Certificado.get(Certificado.es_fiel==False)
is_edu = False
comprobante = {}
relacionados = {}
donativo = {}
complementos = FacturasComplementos.get_by_invoice(invoice)
comprobante['divisas'] = invoice.divisas
if invoice.divisas:
complementos['divisas'] = True
if 'leyendas' in complementos:
ids = complementos['leyendas']
complementos['leyendas'] = SATLeyendasFiscales.get_by_id(ids)
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['SubTotal'] = FORMAT.format(invoice.subtotal)
comprobante['Moneda'] = invoice.moneda
comprobante['TipoCambio'] = '1'
if comprobante['Moneda'] != 'MXN':
comprobante['TipoCambio'] = FORMAT_TAX.format(invoice.tipo_cambio)
comprobante['Total'] = FORMAT.format(invoice.total)
comprobante['TipoDeComprobante'] = invoice.tipo_comprobante
if invoice.metodo_pago:
comprobante['MetodoPago'] = invoice.metodo_pago
comprobante['LugarExpedicion'] = invoice.lugar_expedicion
if invoice.descuento:
comprobante['Descuento'] = FORMAT.format(invoice.descuento)
if invoice.tipo_comprobante == 'T':
comprobante['SubTotal'] = '0'
comprobante['Total'] = '0'
del comprobante['FormaPago']
del comprobante['TipoCambio']
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,
'DomicilioFiscalReceptor': invoice.cliente.codigo_postal,
'RegimenFiscalReceptor': invoice.receptor_regimen
}
if invoice.cliente.tipo_persona == 4:
if invoice.cliente.pais:
receptor['ResidenciaFiscal'] = invoice.cliente.pais
if invoice.cliente.id_fiscal:
receptor['NumRegIdTrib'] = invoice.cliente.id_fiscal
conceptos = []
rows = FacturasDetalle.select().where(FacturasDetalle.factura==invoice)
for row in rows:
if is_global:
key_sat = row.clave_sat
key = row.clave
else:
key_sat = row.producto.clave_sat
key = row.producto.clave
concepto = {
'ClaveProdServ': key_sat,
'NoIdentificacion': key,
'Cantidad': FORMAT.format(row.cantidad),
'ClaveUnidad': row.unidad,
'Unidad': SATUnidades.get(SATUnidades.key==row.unidad).name[:20],
'Descripcion': row.descripcion,
'ValorUnitario': frm_vu.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
if row.pedimento:
concepto['Pedimento'] = row.pedimento
if row.autorizacion:
is_edu = True
concepto['student'] = {
'version': '1.0',
'nombreAlumno': row.alumno,
'CURP': row.curp,
'nivelEducativo': row.nivel,
'autRVOE': row.autorizacion,
}
taxes = {}
traslados = []
retenciones = []
if invoice.tipo_comprobante != 'T':
if is_global:
ticket = (Tickets
.get(fn.Concat(Tickets.serie, Tickets.folio)==row.clave)
)
product_taxes = (TicketsImpuestos
.select()
.where(TicketsImpuestos.ticket==ticket)
)
else:
product_taxes = row.producto.impuestos
for impuesto in product_taxes:
base = float(row.importe - row.descuento)
if is_global:
base = float(impuesto.base)
impuesto = impuesto.impuesto
if impuesto.tipo == 'E':
tax = {
'Base': FORMAT.format(base),
'Impuesto': '002',
'TipoFactor': 'Exento',
}
traslados.append(tax)
continue
if impuesto.key == '000':
continue
tasa = float(impuesto.tasa)
if tax_decimals:
import_tax = round(tasa * base, DECIMALES_TAX)
tmp += import_tax
xml_importe = FORMAT_TAX.format(import_tax)
else:
import_tax = round(tasa * base, DECIMALES)
xml_importe = FORMAT.format(import_tax)
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": xml_importe,
}
if impuesto.tipo == 'T':
traslados.append(tax)
else:
retenciones.append(tax)
if traslados:
taxes['traslados'] = traslados
if retenciones:
taxes['retenciones'] = retenciones
concepto['impuestos'] = taxes
# cfdi4
if row.producto.objeto_impuesto:
concepto['ObjetoImp'] = row.producto.objeto_impuesto
else:
if taxes:
concepto['ObjetoImp'] = '02'
else:
concepto['ObjetoImp'] = '01'
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_decimals:
xml_importe = FORMAT_TAX.format(tax.importe)
xml_tax_base = FORMAT_TAX.format(tax.base)
else:
xml_importe = FORMAT.format(tax.importe)
xml_tax_base = FORMAT.format(tax.base)
if tax.impuesto.tipo == 'T':
traslado = {
"Base": xml_tax_base,
"Impuesto": tax.impuesto.key,
"TipoFactor": tipo_factor,
"TasaOCuota": str(tax.impuesto.tasa),
"Importe": xml_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
if invoice.tipo_comprobante == 'T':
impuestos = {}
data = {
'comprobante': comprobante,
'relacionados': relacionados,
'emisor': emisor,
'receptor': receptor,
'conceptos': conceptos,
'impuestos': impuestos,
'donativo': donativo,
'edu': is_edu,
'complementos': complementos,
'global': data_global,
}
return utils.make_xml(data, certificado)
@classmethod
def get_status_sat(cls, id):
obj = Facturas.get(Facturas.id == id)
estatus_sat = utils.get_status_sat(obj.xml)
if obj.estatus_sat != estatus_sat:
obj.estatus_sat = estatus_sat
obj.save()
if obj.estatus_sat == 'Vigente' and obj.estatus == 'Cancelada':
days = utils.get_days(obj.fecha_cancelacion)
if days > 3:
estatus_sat = 'uncancel'
return estatus_sat
@classmethod
def get_verify_sat(cls, id):
emisor = Emisor.select()[0].rfc
obj = Facturas.get(Facturas.id == id)
xml = util.parse_xml(obj.xml)
sello = xml.attrib.get('sello', xml.attrib.get('Sello'))[-8:]
data = {
'url': 'https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?',
'uuid': '&id={}'.format(obj.uuid),
'emisor': '&re={}'.format(emisor),
'receptor': '&rr={}'.format(obj.cliente.rfc),
'total': '&tt={}'.format(str(obj.total)),
'sello': '&fe={}'.format(sello),
}
result = {
'url': '{url}{uuid}{emisor}{receptor}{total}{sello}'.format(**data)
}
return result
@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, values, user):
id = int(values['id'])
update = util.loads(values.get('update', 'true'))
rfc = Emisor.select()[0].rfc
obj = Facturas.get(Facturas.id == id)
xml = cls._make_xml(cls, obj)
if obj.cliente.rfc == RFCS['PUBLIC']:
update = False
enviar_correo = util.get_bool(Configuracion.get_('correo_directo'))
auth = Configuracion.get_({'fields': 'pac_auth'})
anticipo = False
msg = 'Factura timbrada correctamente'
result = utils.xml_stamp(xml, auth)
if result['ok']:
cfdi_uuid = result['uuid']
values = dict(
xml = result['xml'],
uuid = cfdi_uuid,
fecha_timbrado = result['date'],
estatus = 'Timbrada',
error = '',
)
q = (Facturas
.update(**values)
.where(Facturas.id == id)
)
q.execute()
row = {'uuid': cfdi_uuid, 'estatus': 'Timbrada'}
if enviar_correo:
cls._send(cls, id, rfc)
if obj.tipo_comprobante == 'I' and obj.tipo_relacion == '07':
anticipo = True
cls._actualizar_saldo_cliente(cls, obj)
if update:
cls._update_inventory(cls, obj, user=user)
cls._sync(cls, id, rfc)
m = 'T {}'.format(obj.id)
_save_log(user.usuario, m, 'F')
else:
msg = result['error']
obj.estatus = 'Error'
obj.xml = xml
obj.error = msg
obj.save()
row = {'estatus': 'Error'}
result = {
'ok': result['ok'],
'msg': msg,
'row': row,
'anticipo': anticipo
}
return result
def _validate_import_32(self, data, sxml):
currencies = {
'Peso Mexicano': 'MXN',
}
regimenes = {
'REGIMEN GENERAL DE LEY PERSONAS MORALES': '601',
}
# ~ print(data)
invoice = data['invoice']
receptor = data['receptor']
name = receptor.get('nombre', '')
tipo_persona = 1
if receptor['rfc'] == 'XEXX010101000':
tipo_persona = 4
elif receptor['rfc'] == RFCS['PUBLIC']:
tipo_persona = 3
elif len(receptor['rfc']) == 12:
tipo_persona = 2
new = {
'tipo_persona': tipo_persona,
'rfc': receptor['rfc'],
'slug': util.to_slug(name),
'nombre': name,
'es_cliente': True,
}
cliente, _ = Socios.get_or_create(**new)
tipo_cambio = float(invoice.get('TipoCambio', '1.0'))
total = float(invoice['Total'])
invoice = {
'cliente': cliente,
'version': invoice['version'],
'serie': invoice.get('serie', ''),
'folio': int(invoice.get('folio', '0')),
'fecha': invoice['fecha'],
'fecha_timbrado': invoice['FechaTimbrado'],
'forma_pago': invoice['formaDePago'],
'condiciones_pago': invoice.get('CondicionesDePago', ''),
'subtotal': float(invoice['SubTotal']),
'descuento': float(invoice.get('Descuento', '0.0')),
'moneda': currencies[invoice['Moneda']],
'tipo_cambio': tipo_cambio,
'total': total,
'saldo': total,
'total_mn': round(float(total * tipo_cambio), DECIMALES),
'tipo_comprobante': invoice['TipoDeComprobante'],
'metodo_pago': invoice['metodoDePago'],
'lugar_expedicion': invoice['LugarExpedicion'],
# ~ 'uso_cfdi': invoice['UsoCFDI'],
'total_retenciones': float(invoice.get('TotalImpuestosRetenidos', '0.0')),
'total_trasladados': float(invoice.get('TotalImpuestosTrasladados', '0.0')),
'xml': sxml,
'uuid': invoice['uuid'],
'estatus': 'Importada',
'regimen_fiscal': regimenes[invoice['regimen_fiscal']],
'pagada': False,
'tipo_relacion': invoice.get('TipoRelacion', '')
}
# ~ donativo = BooleanField(default=False)
conceptos = []
for concepto in data['conceptos']:
valor_unitario = float(concepto['ValorUnitario'])
descuento = float(concepto.get('Descuento', '0.0'))
c = {
'cantidad': float(concepto['Cantidad']),
'valor_unitario': valor_unitario,
'descuento': descuento,
'precio_final': round(valor_unitario - descuento, DECIMALES),
'importe': float(concepto['Importe']),
'descripcion': concepto['Descripcion'],
'unidad': concepto.get('Unidad', ''),
'clave': concepto.get('NoIdentificacion', ''),
'clave_sat': '01010101',
}
conceptos.append(c)
new = {
'invoice': invoice,
'conceptos': conceptos,
}
return True, new
def _validate_import(self, data, sxml):
try:
emisor = Emisor.select()[0]
except IndexError:
msg = 'Falta Emisor'
log.error(msg)
return False, {}
if not DEBUG:
if emisor.rfc != data['emisor']['rfc']:
msg = 'El CFDI no es del Emisor'
log.error(msg)
return False, {}
invoice = data['invoice']
w = (Facturas.uuid==invoice['uuid'])
if Facturas.select().where(w).exists():
msg = 'Factura ya existe: {}'.format(invoice['uuid'])
log.error(msg)
return False, {}
if invoice['version'] != '3.3':
if invoice['version'] != '3.2':
msg = 'CFDI no soportado'
log.error(msg)
return False, {}
return self._validate_import_32(self, data, sxml)
receptor = data['receptor']
name = receptor.get('nombre', '')
tipo_persona = 1
if receptor['rfc'] == 'XEXX010101000':
tipo_persona = 4
elif receptor['rfc'] == RFCS['PUBLIC']:
tipo_persona = 3
elif len(receptor['rfc']) == 12:
tipo_persona = 2
new = {
'tipo_persona': tipo_persona,
'rfc': receptor['rfc'],
'slug': util.to_slug(name),
'nombre': name,
'es_cliente': True,
}
cliente, _ = Socios.get_or_create(**new)
tipo_cambio = float(invoice.get('TipoCambio', '1.0'))
total = float(invoice['Total'])
invoice = {
'cliente': cliente,
'version': invoice['version'],
'serie': invoice.get('serie', ''),
'folio': int(invoice.get('folio', '0')),
'fecha': invoice['fecha'],
'fecha_timbrado': invoice['FechaTimbrado'],
'forma_pago': invoice['FormaPago'],
'condiciones_pago': invoice.get('CondicionesDePago', ''),
'subtotal': float(invoice['SubTotal']),
'descuento': float(invoice.get('Descuento', '0.0')),
'moneda': invoice['Moneda'],
'tipo_cambio': tipo_cambio,
'total': total,
'saldo': total,
'total_mn': round(float(total * tipo_cambio), DECIMALES),
'tipo_comprobante': invoice['TipoDeComprobante'],
'metodo_pago': invoice['MetodoPago'],
'lugar_expedicion': invoice['LugarExpedicion'],
'uso_cfdi': invoice['UsoCFDI'],
'total_retenciones': float(invoice.get('TotalImpuestosRetenidos', '0.0')),
'total_trasladados': float(invoice.get('TotalImpuestosTrasladados', '0.0')),
'xml': sxml,
'uuid': invoice['uuid'],
'estatus': 'Importada',
'regimen_fiscal': invoice['RegimenFiscal'],
'pagada': False,
'tipo_relacion': invoice.get('TipoRelacion', '')
}
# ~ donativo = BooleanField(default=False)
conceptos = []
for concepto in data['conceptos']:
valor_unitario = float(concepto['ValorUnitario'])
descuento = float(concepto.get('Descuento', '0.0'))
clave = concepto.get('NoIdentificacion', '')
producto = None
if Productos.select().where(Productos.clave==clave).exists():
producto = Productos.get(Productos.clave==clave)
c = {
'producto': producto,
'cantidad': float(concepto['Cantidad']),
'valor_unitario': valor_unitario,
'descuento': descuento,
'precio_final': round(valor_unitario - descuento, DECIMALES),
'importe': float(concepto['Importe']),
'descripcion': concepto['Descripcion'],
'unidad': concepto.get('Unidad', ''),
'clave': clave,
'clave_sat': concepto['ClaveProdServ'],
}
conceptos.append(c)
new = {
'invoice': invoice,
'conceptos': conceptos,
}
return True, new
@classmethod
def import_cfdi(cls, xml, sxml):
result = {'status': 'error'}
data = util.ImportCFDI(xml).get_data()
ok, new = cls._validate_import(cls, data, sxml)
if not ok:
return result
with database_proxy.atomic() as txn:
obj = Facturas.create(**new['invoice'])
for product in new['conceptos']:
product['factura'] = obj
FacturasDetalle.create(**product)
return {'status': 'server'}
def _set_invoices_payed(self, ids, user):
ids = util.loads(ids)
ok_ids = []
for id in ids:
with database_proxy.atomic() as txn:
obj = Facturas.get(Facturas.id==id)
obj.pagada = True
saldo = obj.saldo
obj.saldo = 0.00
obj.save()
q = (Socios
.update(saldo_cliente=Socios.saldo_cliente - saldo)
.where(Socios.id==obj.cliente.id)
)
q.execute()
ok_ids.append(id)
if ok_ids:
return {'ok': True, 'rows': ok_ids}
return {'ok': False}
@classmethod
def opt(cls, args, user):
if args['opt'] == 'invoicepayed':
return cls._set_invoices_payed(cls, args['ids'], user)
if args['opt'] == 'timbrar':
return cls.timbrar(args, user)
if args['opt'] == 'cancel':
admin_cancel = Configuracion.get_bool('chk_cancel_invoices_by_admin')
if admin_cancel and not user.es_admin:
msg = 'Solo un admin puede cancelar'
result = {'ok': False, 'msg': msg, 'row': {}}
return result
values = utils.loads(args['values'])
result = cls.cancel(values)
if result['ok']:
m = 'C {}'.format(values['id'])
_save_log(user.usuario, m, 'F')
return result
return {'ok': False}
# ~ v2
@utils.run_in_thread
def _update_inventory(self, invoice, cancel=False, user=None):
if invoice.tipo_comprobante != 'I':
return
if invoice.cliente.rfc == RFCS['PUBLIC']:
return
warehouse = None
try:
warehouse = user.sucursal.warehouse
except:
pass
products = FacturasDetalle.get_by_invoice(invoice.id)
for p in products:
if p.producto is None:
continue
if p.producto.inventario:
cant = Decimal(p.cantidad)
if cancel:
cant *= -1
warehouse = p.warehouse
p.producto.existencia -= cant
p.producto.save()
if warehouse is None:
continue
fields = (
WareHouseProduct.warehouse==warehouse,
WareHouseProduct.product==p.producto,
)
obj = WareHouseProduct.get(*fields)
obj.exists -= cant
obj.save()
return
@utils.run_in_thread
def _update_client_balance(self, invoice, cancel=False):
if invoice.tipo_comprobante == 'T':
return
if invoice.donativo and invoice.forma_pago == '12':
return
if invoice.cliente.rfc == RFCS['PUBLIC']:
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())
def _put_uncancel(self, args, user):
id = int(args['id'])
obj = Facturas.get(Facturas.id==id)
obj.estatus = 'Timbrada'
obj.error = ''
obj.cancelada = False
obj.fecha_cancelacion = None
obj.acuse = ''
self._update_client_balance(self, obj)
self._update_inventory(self, obj)
obj.save()
msg = 'Factura actualizada correctamente'
result = {'result': True, 'msg': msg, 'values': {'estatus': 'Timbrada'}}
return result
@classmethod
def put(cls, args, user):
return getattr(cls, f"_put_{args['opt']}")(cls, args, user)
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',)
@util.run_in_thread
def _send_in_thread(self, id, obj, values):
log.info('Generando PDF...')
files = (self.get_pdf(id),)
log.info('PDF Generado...')
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'],
'confirmar': util.get_bool(values.get('correo_confirmacion', '0')),
'asunto': asunto,
'mensaje': util.make_info_mail(values['correo_mensaje'], fields),
'files': files,
}
data= {
'server': server,
'options': options,
}
log.info('Enviando prefactura...')
result = util.send_mail(data)
log.info('Prefactura enviada...')
return
@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}
rows = PreFacturasDetalle.count(id)
if rows > 300:
cls._send_in_thread(cls, id, obj, values)
msg = 'Enviando correo...'
return {'ok': True, '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)
contra = Configuracion.get_('correo_contra')
rfc = Emisor.select()[0].rfc
asunto = 'Enviamos la prefactura: PRE-{}'.format(obj.folio)
starttls = bool(int(values.get('correo_starttls', '0')))
server = {
'servidor': values['correo_servidor'],
'puerto': values['correo_puerto'],
'ssl': bool(int(values['correo_ssl'])),
'starttls': starttls,
'usuario': values['correo_usuario'],
'contra': utils.decrypt(contra, rfc),
}
options = {
'para': obj.cliente.correo_facturas,
'copia': values['correo_copia'],
'confirmar': util.get_bool(values.get('correo_confirmacion', '0')),
'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
@util.run_in_thread
def _get_pdf_in_thread(self, id):
obj = PreFacturas.get(PreFacturas.id==id)
name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc)
data = self._get_info_to_pdf(self, id)
doc = util.to_pdf(data, data['emisor']['rfc'])
emisor = Emisor.select()[0]
target = emisor.rfc + '/Prefacturas/'
files = (
(doc, name, target),
)
util.sync_cfdi(files)
return
@classmethod
def get_pdf_in_thread(cls, id):
return cls._get_pdf_in_thread(cls, id)
@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.alias('year'))
.group_by(PreFacturas.fecha.year)
.order_by(PreFacturas.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_(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):
tax_locales = Configuracion.get_bool('chk_config_tax_locales')
tax_decimals = Configuracion.get_bool('chk_config_tax_decimals')
tax_locales_truncate = Configuracion.get_bool('chk_config_tax_locales_truncate')
subtotal = 0
descuento_cfdi = 0
totals_tax = {}
total_trasladados = None
total_retenciones = None
locales_traslados = 0
locales_retenciones = 0
for product in products:
id_product = product.pop('id_product')
p = Productos.get(Productos.id==id_product)
product['unidad'] = SATUnidades.get(SATUnidades.id==product['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']
PreFacturasDetalle.create(**product)
if invoice.tipo_comprobante == 'T':
continue
base = product['importe'] - product['descuento']
for tax in p.impuestos:
if tax_locales and tax.tipo == 'R' and tax.key == '000':
base = product['importe']
if tax_decimals:
impuesto_producto = round(float(tax.tasa) * base, DECIMALES_TAX)
else:
impuesto_producto = round(float(tax.tasa) * base, DECIMALES)
if tax.key == '000' and tax_locales_truncate:
impuesto_producto = util.truncate(float(tax.tasa) * base)
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
if invoice.tipo_comprobante == 'T':
subtotal = 0.0
descuento = 0.0
else:
for tax in totals_tax.values():
if tax.tipo == 'E':
continue
invoice_tax = {
'factura': invoice.id,
'impuesto': tax.id,
'base': tax.base,
'importe': tax.suma_impuestos,
}
PreFacturasImpuestos.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
@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: utils.loads(r.valores) for r in query}
@classmethod
def get_by_invoice(cls, invoice):
query = (FacturasComplementos
.select()
.where(FacturasComplementos.factura==invoice)
)
return {r.nombre: utils.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='')
empaques = DecimalField(default=0.0, max_digits=14, decimal_places=4,
auto_round=True)
warehouse = ForeignKeyField(Almacenes, null=True)
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 get_by_invoice(cls, id):
return FacturasDetalle.select().where(FacturasDetalle.factura==id)
@classmethod
def reinvoice(cls, id):
data = []
products = FacturasDetalle.select().where(FacturasDetalle.factura==id)
for p in products:
row = {'delete': '-', 'id_product': p.producto.id}
row['clave'] = p.clave
row['clave_sat'] = p.clave_sat
row['descripcion'] = p.descripcion
row['pedimento'] = p.pedimento
row['unidad'] = p.producto.unidad.id
row['cantidad'] = p.cantidad
row['valor_unitario'] = p.valor_unitario
descuento = p.valor_unitario - p.precio_final
row['descuento'] = descuento
pf = p.valor_unitario - descuento
row['importe'] = round(pf * p.cantidad, DECIMALES)
impuestos = cls._get_impuestos(cls, row['id_product'])
data.append({'row': row, 'taxes': impuestos})
return data
@classmethod
def get_detalle(cls, id):
data = []
products = FacturasDetalle.select().where(FacturasDetalle.factura==id)
for p in products:
row = {'id_product': p.producto.id}
row['clave'] = p.clave
row['clave_sat'] = p.clave_sat
row['descripcion'] = p.descripcion
row['unidad'] = p.producto.unidad.name
row['cantidad'] = p.cantidad
row['valor_unitario'] = p.valor_unitario
descuento = p.valor_unitario - p.precio_final
row['descuento'] = descuento
pf = p.valor_unitario - descuento
row['importe'] = round(pf * p.cantidad, DECIMALES)
data.append(row)
return data
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='')
unidad = 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().where(PreFacturas.id==id)[0]
receptor = {
'id': q.cliente.id,
'nombre': q.cliente.nombre,
'rfc': q.cliente.rfc,
'forma_pago': q.forma_pago,
'uso_cfdi': q.uso_cfdi,
'codigo_postal': q.cliente.codigo_postal,
'regimenes': SociosRegimenes.get_by_key(q.cliente.id),
'notas': q.notas,
}
productos = PreFacturasDetalle.select().where(
PreFacturasDetalle.factura==id)
for p in productos:
row = {'id_product': p.producto.id}
row['clave'] = p.producto.clave
row['descripcion'] = p.descripcion
row['pedimento'] = p.pedimento
row['unidad'] = p.producto.unidad.id
row['cantidad'] = p.cantidad
row['valor_unitario'] = p.valor_unitario
descuento = p.valor_unitario - p.precio_final
row['descuento'] = descuento
pf = p.valor_unitario - descuento
row['importe'] = round(pf * p.cantidad, DECIMALES)
impuestos = cls._get_impuestos(cls, row['id_product'])
data.append({'row': row, 'taxes': impuestos})
return {'rows': data, 'receptor': receptor}
@classmethod
def count(cls, id):
c = PreFacturasDetalle.select().where(
PreFacturasDetalle.factura==id).count()
return c
@classmethod
def can_open(cls, id):
c = cls.count(id)
PreFacturas.get_pdf_in_thread(id)
return c < 300
@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
if p.pedimento:
info = '\nNĆŗmero Pedimento: {}'.format(p.pedimento)
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)
tipo_cambio = DecimalField(default=1.0, max_digits=15, 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, values in ids.items():
fac = Facturas.get(Facturas.id==int(i))
this_pay = values['this_pay']
importe = values['importe']
type_change = round(1 / values['type_change'], 6)
validate = round(this_pay / type_change, 2)
while validate > importe:
type_change += 0.000001
validate = round(this_pay / type_change, 2)
mov_ant, numero = cls._movimiento_anterior(cls, mov, fac)
nuevo = {
'movimiento': mov,
'factura': fac,
'numero': numero,
'importe': importe,
'tipo_cambio': type_change,
}
if mov_ant is None:
nuevo['saldo_anterior'] = float(fac.saldo)
else:
nuevo['saldo_anterior'] = float(mov_ant.saldo)
if(fac.moneda in ('MXN', 'peso')):
nuevo['saldo'] = nuevo['saldo_anterior'] - importe
else:
nuevo['importe'] = this_pay
nuevo['saldo'] = nuevo['saldo_anterior'] - this_pay
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
def _get_related(self, values):
id = int(values['id'])
filters = (FacturasPagos.movimiento==id)
rows = tuple(FacturasPagos
.select(
Facturas.id,
Facturas.serie,
Facturas.folio,
Facturas.uuid,
Facturas.fecha,
Facturas.tipo_comprobante,
Facturas.estatus,
Socios.nombre.alias('cliente'),
Facturas.total,
FacturasPagos.saldo,
FacturasPagos.importe,
)
.join(Facturas).switch(FacturasPagos)
.join(Socios, on=(Facturas.cliente==Socios.id))
.where(filters)
.dicts()
)
return {'ok': True, 'rows': rows}
@classmethod
def get_values(cls, values):
opt = values.pop('opt')
return getattr(cls, '_get_{}'.format(opt))(cls, values)
class CfdiPagos(BaseModel):
movimiento = ForeignKeyField(MovimientosBanco)
socio = 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)
tipo_comprobante = TextField(default=DEFAULT_CFDIPAY['TYPE'])
lugar_expedicion = TextField(default='')
regimen_fiscal = TextField(default='')
tipo_relacion = TextField(default='')
uuid_relacionado = UUIDField(null=True)
xml = TextField(default='')
uuid = UUIDField(null=True)
estatus = TextField(default='Guardada')
estatus_sat = TextField(default='')
notas = TextField(default='')
error = TextField(default='')
cancelada = BooleanField(default=False)
fecha_cancelacion = DateTimeField(null=True)
receptor_regimen = TextField(default='')
class Meta:
order_by = ('movimiento',)
@classmethod
def with_invoice(cls, id):
if CfdiPagos.select().where(CfdiPagos.movimiento==id).count():
return 'Si'
return ''
@classmethod
def post(cls, values):
opt = values.pop('opt')
return getattr(cls, '_{}'.format(opt))(cls, values)
def _delete(self, values):
id_mov = int(values['id_mov'])
filters = (
(CfdiPagos.movimiento==id_mov)
)
last = CfdiPagos.select().where(filters)
if not last:
msg = 'El depĆ³sito no tiene facturas de pago'
data = {'ok': False, 'msg': msg}
return data
if len(last) > 1:
msg = 'Hay mĆ”s de una factura activa para este depĆ³sito'
data = {'ok': False, 'msg': msg}
return data
last = last[0]
if last.uuid:
msg = 'La factura esta timbrada, no se puede eliminar'
data = {'ok': False, 'msg': msg}
return data
q = CfdiPagos.delete().where(CfdiPagos.id==last.id)
q.execute()
msg = 'Factura de pago eliminada correctamente'
data = {'ok': True, 'msg': msg}
return data
def _cancel(self, values):
id_mov = int(values['id_mov'])
args = utils.loads(values['args'])
filters = (
(CfdiPagos.movimiento==id_mov) &
(CfdiPagos.cancelada==False)
)
last = CfdiPagos.select().where(filters)
if not last:
msg = 'El depĆ³sito no tiene facturas de pago activas'
data = {'ok': False, 'msg': msg}
return data
if len(last) > 1:
msg = 'Hay mĆ”s de una factura activa para este depĆ³sito'
data = {'ok': False, 'msg': msg}
return data
last = last[0]
if not last.uuid:
msg = 'La factura no esta timbrada'
data = {'ok': False, 'msg': msg}
return data
pac = utils.get_pac_by_rfc(last.xml)
auth = Configuracion.get_({'fields': 'auth_by_pac', 'pac': pac})
certificado = Certificado.get(Certificado.es_fiel==False)
result = utils.cancel_xml_sign(last, args, auth, certificado)
if result['ok']:
last.estatus = 'Cancelada'
last.error = ''
last.cancelada = True
last.fecha_cancelacion = result['date']
else:
last.error = result['msg']
last.save()
return {'ok': result['ok'], 'msg': result['msg'], 'id': last.id}
def _get_folio(self, serie):
folio = int(Configuracion.get_('txt_config_cfdipay_folio') or '0')
start = (CfdiPagos
.select(fn.Max(CfdiPagos.folio).alias('mf'))
.where(CfdiPagos.serie==serie)
.order_by(SQL('mf'))
.scalar())
if start is None:
next_folio = 1
else:
next_folio = start + 1
if folio > next_folio:
next_folio = folio
return next_folio
def _new(self, values):
id_mov = int(values['id_mov'])
filters = (FacturasPagos.movimiento==id_mov)
related = FacturasPagos.select().where(filters)
if not related:
msg = 'El pago no tiene facturas relacionadas'
data = {'ok': False, 'msg': msg}
return data
partner = tuple(set([f.factura.cliente.rfc for f in related]))
if len(partner) > 1:
msg = 'Facturas relacionadas a diferentes clientes'
data = {'ok': False, 'msg': msg}
return data
partner = related[0].factura.cliente
partner_name = related[0].factura.cliente.nombre
receptor_regimen = related[0].factura.receptor_regimen
emisor = Emisor.select()[0]
# ~ regimen_fiscal = related[0].factura.regimen_fiscal
regimen_fiscal = emisor.regimenes[0].key
filters = (
(CfdiPagos.movimiento==id_mov) &
(CfdiPagos.cancelada==False)
)
previous = CfdiPagos.select().where(filters)
if previous:
previous = previous[0]
if previous.uuid:
msg = 'Hay una factura activa, es necesario cancelarla primero'
data = {'ok': False, 'msg': msg}
return data
else:
data = {'ok': True, 'new': False}
return data
fields = {}
filters = (
(CfdiPagos.movimiento==id_mov) &
(CfdiPagos.cancelada==True)
)
previous = CfdiPagos.select().where(filters).order_by(CfdiPagos.id.desc())
if previous:
previous = previous[0]
fields['tipo_relacion'] = DEFAULT_CFDIPAY['TYPE_RELATION']
fields['uuid_relacionado'] = previous.uuid
emisor = Emisor.select()[0]
serie = Configuracion.get_('txt_config_cfdipay_serie') or DEFAULT_CFDIPAY['SERIE']
fields['movimiento'] = id_mov
fields['socio'] = partner
fields['serie'] = serie
fields['folio'] = self._get_folio(self, serie)
fields['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
fields['regimen_fiscal'] = regimen_fiscal
fields['receptor_regimen'] = receptor_regimen
with database_proxy.atomic() as txn:
obj = CfdiPagos.create(**fields)
row = {
'id': obj.id,
'serie': obj.serie,
'folio': obj.folio,
'uuid': obj.uuid,
'fecha': obj.fecha,
'tipo_comprobante': obj.tipo_comprobante,
'estatus': obj.estatus,
'cliente': partner_name,
}
data = {'ok': True, 'row': row, 'new': True}
return data
def _get_taxes_by_pay(self, pay, taxes_pay):
# ~ print(pay['ImpPagado']
invoice = Facturas.get(Facturas.uuid==pay['IdDocumento'])
impuestos = {}
traslados = []
retenciones = []
where = (FacturasImpuestos.factura==invoice)
taxes = FacturasImpuestos.select().where(where)
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_decimals:
# ~ xml_importe = FORMAT_TAX.format(tax.importe)
# ~ xml_tax_base = FORMAT_TAX.format(tax.base)
# ~ else:
xml_importe = FORMAT.format(tax.importe)
xml_tax_base = FORMAT.format(tax.base)
values = {
"BaseDR": xml_tax_base,
"ImpuestoDR": tax.impuesto.key,
"TipoFactorDR": tipo_factor,
"TasaOCuotaDR": str(tax.impuesto.tasa),
"ImporteDR": xml_importe,
}
tax_key = tax.impuesto.key
if tax.impuesto.tipo == 'T':
traslados.append(values)
if tax_key in taxes_pay['traslados']:
taxes_pay['traslados'][tax_key]['ImporteP'] += tax.importe
else:
values = {
"BaseP": tax.base,
"ImpuestoP": tax.impuesto.key,
"TipoFactorP": tipo_factor,
"TasaOCuotaP": str(tax.impuesto.tasa),
"ImporteP": tax.importe,
}
taxes_pay['traslados'][tax_key] = values
else:
retenciones.append(values)
if tax_key in taxes_pay['retenciones']:
taxes_pay['retenciones'][tax_key] += tax.importe
else:
taxes_pay['retenciones'][tax_key] = tax.importe
impuestos['traslados'] = traslados
impuestos['retenciones'] = retenciones
return impuestos
def _get_related_xml(self, id_mov, currency):
TAX_IVA_16 = '002|0.160000'
filters = (FacturasPagos.movimiento==id_mov)
related = tuple(FacturasPagos.select(
Facturas.uuid.alias('IdDocumento'),
Facturas.serie.alias('Serie'),
Facturas.folio.alias('Folio'),
Facturas.moneda.alias('MonedaDR'),
FacturasPagos.tipo_cambio.alias('TipoCambioDR'),
# ~ Facturas.metodo_pago.alias('MetodoDePagoDR'),
FacturasPagos.numero.alias('NumParcialidad'),
FacturasPagos.saldo_anterior.alias('ImpSaldoAnt'),
FacturasPagos.importe.alias('ImpPagado'),
FacturasPagos.saldo.alias('ImpSaldoInsoluto'),
).join(Facturas).switch(FacturasPagos)
.where(filters)
.dicts())
taxes_pay = {'retenciones': {}, 'traslados': {}, 'totales': {}}
for r in related:
r['taxes'] = self._get_taxes_by_pay(self, r, taxes_pay)
# ~ print('\n\nMONEDA', currency, r['MonedaDR'])
r['IdDocumento'] = str(r['IdDocumento'])
r['Folio'] = str(r['Folio'])
r['NumParcialidad'] = str(r['NumParcialidad'])
r['TipoCambioDR'] = FORMAT6.format(r['TipoCambioDR'])
# ~ r['MetodoDePagoDR'] = DEFAULT_CFDIPAY['WAYPAY']
# REVISAR
r['EquivalenciaDR'] = '1'
r['ObjetoImpDR'] = '02'
r['ImpSaldoAnt'] = FORMAT.format(r['ImpSaldoAnt'])
r['ImpPagado'] = FORMAT.format(r['ImpPagado'])
if round(r['ImpSaldoInsoluto'], 2) == 0.0:
r['ImpSaldoInsoluto'] = '0.00'
else:
r['ImpSaldoInsoluto'] = FORMAT.format(r['ImpSaldoInsoluto'])
if currency == r['MonedaDR']:
del r['TipoCambioDR']
if not r['Serie']:
del r['Serie']
total_tax_iva_16_base = 0
total_tax_iva_16_importe = 0
for key, importe in taxes_pay['retenciones'].items():
taxes_pay['retenciones'][key] = FORMAT.format(importe)
for k, tax in taxes_pay['traslados'].items():
tax_type = taxes_pay['traslados'][k]['ImpuestoP']
tax_tasa = taxes_pay['traslados'][k]['TasaOCuotaP']
tax_base = taxes_pay['traslados'][k]['BaseP']
importe = taxes_pay['traslados'][k]['ImporteP']
if f'{tax_type}|{tax_tasa}' == TAX_IVA_16:
total_tax_iva_16_base += tax_base
total_tax_iva_16_importe += importe
taxes_pay['traslados'][k]['BaseP'] = FORMAT.format(tax_base)
taxes_pay['traslados'][k]['ImporteP'] = FORMAT.format(importe)
taxes_pay['totales'] = {
'TotalTrasladosBaseIVA16': FORMAT.format(total_tax_iva_16_base),
'TotalTrasladosImpuestoIVA16': FORMAT.format(total_tax_iva_16_importe),
}
return related, taxes_pay
def _generate_xml(self, invoice):
emisor = Emisor.select()[0]
certificado = Certificado.get(Certificado.es_fiel==False)
used_data_bank = Configuracion.get_bool('chk_cfg_pays_data_bank')
cfdi = {}
related = {}
cfdi['Serie'] = invoice.serie
cfdi['Folio'] = str(invoice.folio)
cfdi['Fecha'] = invoice.fecha.isoformat()[:19]
cfdi['NoCertificado'] = certificado.serie
cfdi['SubTotal'] = '0'
cfdi['Moneda'] = DEFAULT_CFDIPAY['CURRENCY']
# ~ cfdi['TipoCambio'] = DEFAULT_CFDIPAY['TC']
cfdi['Total'] = '0'
cfdi['TipoDeComprobante'] = invoice.tipo_comprobante
cfdi['LugarExpedicion'] = invoice.lugar_expedicion
if invoice.tipo_relacion:
related = {
'tipo': invoice.tipo_relacion,
'cfdis': (str(invoice.uuid_relacionado),),
}
emisor = {
'Rfc': emisor.rfc,
'Nombre': emisor.nombre,
'RegimenFiscal': invoice.regimen_fiscal,
}
receptor = {
'Rfc': invoice.socio.rfc,
'Nombre': invoice.socio.nombre,
'UsoCFDI': DEFAULT_CFDIPAY['USED'],
'DomicilioFiscalReceptor': invoice.socio.codigo_postal,
'RegimenFiscalReceptor': invoice.receptor_regimen
}
if invoice.socio.tipo_persona == 4:
if invoice.socio.pais:
receptor['ResidenciaFiscal'] = invoice.socio.pais
if invoice.socio.id_fiscal:
receptor['NumRegIdTrib'] = invoice.socio.id_fiscal
conceptos = ({
'ClaveProdServ': DEFAULT_CFDIPAY['KEYSAT'],
'Cantidad': '1',
'ClaveUnidad': DEFAULT_CFDIPAY['UNITKEY'],
'Descripcion': DEFAULT_CFDIPAY['DESCRIPTION'],
'ValorUnitario': '0',
'Importe': '0',
'ObjetoImp': '01',
},)
impuestos = {}
mov = invoice.movimiento
currency = mov.moneda
related_docs, taxes_pay = self._get_related_xml(self, invoice.movimiento, currency)
totales = taxes_pay.pop('totales')
pagos = {
'FechaPago': mov.fecha.isoformat()[:19],
'FormaDePagoP': mov.forma_pago.key,
'MonedaP': currency,
'TipoCambioP': '1',
'Monto': FORMAT.format(mov.deposito),
'relacionados': related_docs,
'taxes_pay': taxes_pay,
}
if mov.numero_operacion:
pagos['NumOperacion'] = mov.numero_operacion
if used_data_bank and mov.cuenta_socio:
if mov.cuenta_socio.banco.rfc:
pagos['RfcEmisorCtaOrd'] = mov.cuenta_socio.banco.rfc
if mov.cuenta_socio.banco.rfc == RFC_EXTRANJERO:
pagos['NomBancoOrdExt'] = mov.cuenta_socio.banco.name
pagos['CtaOrdenante'] = mov.cuenta_socio.cuenta
if mov.cuenta.banco.rfc:
pagos['RfcEmisorCtaBen'] = mov.cuenta.banco.rfc
pagos['CtaBeneficiario'] = mov.cuenta.cuenta
if currency != CURRENCY_MN:
pagos['TipoCambioP'] = FORMAT_TAX.format(mov.tipo_cambio)
totales['MontoTotalPagos'] = pagos['Monto']
pagos['totales'] = totales
complementos = {'pagos': pagos}
data = {
'comprobante': cfdi,
'relacionados': related,
'emisor': emisor,
'receptor': receptor,
'conceptos': conceptos,
'impuestos': impuestos,
'donativo': {},
'edu': False,
'complementos': complementos,
}
return utils.make_xml(data, certificado)
def _stamp(self, values):
id_mov = int(values['id_mov'])
send_email = Configuracion.get_bool('correo_directo')
auth = Configuracion.get_({'fields': 'pac_auth'})
filters = (
(CfdiPagos.movimiento==id_mov) &
(CfdiPagos.uuid.is_null(True))
)
obj = CfdiPagos.get(filters)
obj.xml = self._generate_xml(self, obj)
obj.estatus = 'Generada'
obj.save()
msg = 'Factura timbrada correctamente'
result = utils.xml_stamp(obj.xml, auth)
if result['ok']:
obj.xml = result['xml']
obj.uuid = result['uuid']
obj.fecha_timbrado = result['date']
obj.estatus = 'Timbrada'
obj.error = ''
row = {'uuid': obj.uuid, 'estatus': 'Timbrada'}
else:
msg = result['error']
obj.estatus = 'Error'
obj.error = msg
row = {'estatus': 'Error'}
obj.save()
result = {
'ok': result['ok'],
'msg': msg,
'id': obj.id,
'row': row,
}
if result['ok']:
self._sync(self, obj.id, send_email)
return result
def _get_related(self, values):
id_mov = int(values['id_mov'])
filters = (
(CfdiPagos.movimiento==id_mov)
)
rows = tuple(CfdiPagos.select(
CfdiPagos.id,
CfdiPagos.serie,
CfdiPagos.folio,
CfdiPagos.uuid,
CfdiPagos.fecha,
CfdiPagos.tipo_comprobante,
CfdiPagos.estatus,
Socios.nombre.alias('cliente'),
).join(Socios).switch(CfdiPagos)
.where(filters).dicts())
return {'ok': True, 'rows': rows}
@classmethod
def get_file_xml(cls, id):
obj = CfdiPagos.get(CfdiPagos.id==id)
folio = str(obj.folio).zfill(6)
name = '{}{}_{}.xml'.format(obj.serie, folio, obj.socio.rfc)
emisor = Emisor.select()[0]
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
files = (
(obj.xml, name, target),
)
cls._sync_files(cls, files)
return obj.xml, name
def _get_not_in_xml(self, invoice, emisor):
values = {'el.version': VERSION_EMPRESA_LIBRE}
values['notas'] = invoice.notas
values['fechadof'] = None
obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal)
values['regimenfiscal'] = str(obj)
obj = SATUsoCfdi.get(SATUsoCfdi.key=='P01')
values['usocfdi'] = str(obj)
values['moneda'] = 'XXX'
if invoice.tipo_relacion:
obj = SATTipoRelacion.get(SATTipoRelacion.key==invoice.tipo_relacion)
values['tiporelacion'] = str(obj)
receptor = Socios.select().where(Socios.id==invoice.socio.id).dicts()[0]
values['receptor'] = {}
for k, v in receptor.items():
values['receptor'][k] = v
values['pagos'] = True
return values
@classmethod
def get_file_pdf(cls, id):
try:
emisor = Emisor.select()[0]
except IndexError:
return b'', 'sin_datos_de_emisor.pdf'
obj = CfdiPagos.get(CfdiPagos.id==id)
folio = str(obj.folio).zfill(6)
name = '{}{}_{}.pdf'.format(obj.serie, folio, obj.socio.rfc)
if obj.uuid is None:
return b'', name
target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/')
values = cls._get_not_in_xml(cls, obj, emisor)
data = util.get_data_from_xml(obj, values)
obj = SATFormaPago.get(SATFormaPago.key==data['pays']['FormaDePagoP'])
data['pays']['formadepago'] = '{} ({})'.format(obj.name, obj.key)
doc = util.to_pdf(data, emisor.rfc)
files = (
(doc, name, target),
)
cls._sync_files(cls, files)
return doc, name
@util.run_in_thread
def _sync(self, id, send_email=False):
files = (CfdiPagos.get_file_xml(id), CfdiPagos.get_file_pdf(id))
if send_email:
self._send(self, {'id': id}, files)
return
@util.run_in_thread
def _sync_files(self, files):
util.sync_files(files)
return
@classmethod
def get_values(cls, values):
opt = values.pop('opt')
return getattr(cls, '_get_{}'.format(opt))(cls, values)
def _get_status_sat(self, values):
id = int(values['id'])
obj = CfdiPagos.get(CfdiPagos.id == id)
estatus_sat = utils.get_status_sat(obj.xml)
if obj.estatus_sat != estatus_sat:
obj.estatus_sat = estatus_sat
obj.save()
# ~ if obj.estatus_sat == 'Vigente' and obj.estatus == 'Cancelada':
# ~ days = utils.get_days(obj.fecha_cancelacion)
# ~ if days > 3:
# ~ estatus_sat = 'uncancel'
return estatus_sat
def _get_table(self, values):
# ~ print('\n\n', values, '\n')
if 'start' in values:
filters = CfdiPagos.fecha.between(
util.get_date(values['start']),
util.get_date(values['end'], True))
else:
if values['year'] == '-1':
fy = (CfdiPagos.fecha.year > 0)
else:
fy = (CfdiPagos.fecha.year == int(values['year']))
if values['month'] == '-1':
fm = (CfdiPagos.fecha.month > 0)
else:
fm = (CfdiPagos.fecha.month == int(values['month']))
filters = (fy & fm)
rows = tuple(CfdiPagos.select(
CfdiPagos.id,
CfdiPagos.serie,
CfdiPagos.folio,
CfdiPagos.uuid,
CfdiPagos.fecha,
CfdiPagos.tipo_comprobante,
CfdiPagos.estatus,
case(MovimientosBanco.cancelado, (
(True, 'Cancelado'),
(False, 'Activo'),
)).alias('movimiento'),
MovimientosBanco.deposito.alias('total'),
MovimientosBanco.moneda.alias('currency'),
(MovimientosBanco.deposito * MovimientosBanco.tipo_cambio).alias('total_mn'),
Socios.nombre.alias('cliente'))
.join(MovimientosBanco).switch(CfdiPagos)
.join(Socios).switch(CfdiPagos)
.where(filters).dicts())
return {'ok': True, 'rows': rows}
def _send(self, values, files=()):
id = int(values['id'])
values = Configuracion.get_({'fields': 'correo'})
contra = Configuracion.get_('correo_contra')
rfc = Emisor.select()[0].rfc
if not values:
msg = 'No esta configurado el servidor de correo de salida'
return {'ok': False, 'msg': msg}
obj = CfdiPagos.get(CfdiPagos.id==id)
if obj.uuid is None:
msg = 'La factura no esta timbrada'
return {'ok': False, 'msg': msg}
to = obj.socio.correo_facturasp or obj.socio.correo_facturas
if not to:
msg = 'El cliente no tiene configurado el correo para facturas'
return {'ok': False, 'msg': msg}
if not files:
files = (self.get_file_pdf(id), self.get_file_xml(id))
fields = util.make_fields(obj.xml)
starttls = bool(int(values.get('correo_starttls', '0')))
server = {
'servidor': values['correo_servidor'],
'puerto': values['correo_puerto'],
'ssl': bool(int(values['correo_ssl'])),
'starttls': starttls,
'usuario': values['correo_usuario'],
'contra': utils.decrypt(contra, rfc),
}
options = {
'para': to,
'copia': values.get('correo_copia', ''),
'confirmar': util.get_bool(values.get('correo_confirmacion', '0')),
'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}
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',)
def _get_folio(self, serie):
inicio = (Tickets
.select(fn.Max(Tickets.folio).alias('mf'))
.where(Tickets.serie==serie)
.order_by(SQL('mf'))
.scalar())
if inicio is None:
inicio = 1
else:
inicio += 1
return inicio
def _without_tax(self, importe, obj):
# ~ Por ahora se asume que solo tiene IVA trasladado 0.16
for tax in obj.impuestos:
tasa = 1.0 + float(tax.tasa)
base = round(importe / tasa, DECIMALES)
return base
def _calcular_totales(self, ticket, productos, user=None):
subtotal = 0
descuento_cfdi = 0
totals_tax = {}
total_trasladados = None
warehouse = None
try:
warehouse = user.sucursal.warehouse
except:
pass
for producto in productos:
id_producto = producto.pop('id_product')
p = Productos.get(Productos.id==id_producto)
producto['descripcion'] = p.descripcion
producto['ticket'] = ticket.id
producto['producto'] = id_producto
cantidad = float(producto['cantidad'])
valor_unitario = self._without_tax(self, producto['valor_unitario'], p)
descuento = float(producto['descuento'] or 0)
precio_final = valor_unitario - descuento
importe = round(cantidad * precio_final, DECIMALES)
producto['cantidad'] = cantidad
producto['valor_unitario'] = valor_unitario
producto['descuento'] = descuento
producto['precio_final'] = precio_final
producto['importe'] = importe
producto['warehouse'] = warehouse
descuento_cfdi += descuento
subtotal += importe
TicketsDetalle.create(**producto)
# ~ if p.inventario:
# ~ p.existencia -= Decimal(cantidad)
# ~ p.save()
base = producto['importe']
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
if tax.id in totals_tax:
totals_tax[tax.id].base += base
totals_tax[tax.id].importe += impuesto_producto
else:
tax.base = base
tax.importe = impuesto_producto
totals_tax[tax.id] = tax
for tax in totals_tax.values():
if tax.tipo == 'E':
continue
ticket_tax = {
'ticket': ticket.id,
'impuesto': tax.id,
'base': tax.base,
'importe': tax.importe,
}
TicketsImpuestos.create(**ticket_tax)
total = subtotal + (total_trasladados or 0)
data = {
'subtotal': subtotal + descuento,
'descuento': descuento_cfdi,
'total': total,
'total_trasladados': total_trasladados,
}
return data
def _get_serie(self, user, invoice=False):
default_serie = DEFAULT_SERIE_TICKET
if invoice:
default_serie = Folios.get_default()
if user.sucursal is None:
return default_serie
if invoice:
return user.sucursal.serie_facturas or default_serie
else:
return user.sucursal.serie_tickets or default_serie
@classmethod
def get_notes(cls, tickets):
rows = Tickets.select(Tickets.notas).where(Tickets.id.in_(tickets))
return '\n'.join([r.notas for r in rows])
@utils.run_in_thread
def _update_inventory(cls, id_ticket, cancel=False, user=None):
warehouse = None
try:
warehouse = user.sucursal.warehouse
except:
pass
products = (TicketsDetalle
.select()
.where(TicketsDetalle.ticket==id_ticket)
)
for p in products:
if p.producto.inventario:
cant = Decimal(p.cantidad)
if cancel:
cant *= -1
warehouse = p.warehouse
p.producto.existencia -= cant
p.producto.save()
if warehouse is None:
continue
where = (
WareHouseProduct.warehouse==warehouse,
WareHouseProduct.product==p.producto,
)
obj = WareHouseProduct.get(*where)
obj.exists -= cant
obj.save()
return
@classmethod
def add(cls, values, user):
productos = util.loads(values.pop('productos'))
values['serie'] = cls._get_serie(cls, user)
values['folio'] = cls._get_folio(cls, values['serie'])
with database_proxy.atomic() as txn:
obj = Tickets.create(**values)
totals = cls._calcular_totales(cls, obj, productos, user)
obj.subtotal = totals['subtotal']
obj.descuento = totals['descuento']
obj.total_trasladados = totals['total_trasladados']
obj.total = totals['total']
obj.save()
cls._update_inventory(cls, obj.id, user=user)
row = {
'id': obj.id,
'serie': obj.serie,
'folio': obj.folio,
'fecha': obj.fecha,
'estatus': obj.estatus,
'total': obj.total,
}
data = {'ok': True, 'row': row}
return data
def _get_folio_invoice(self, serie):
inicio = (Facturas
.select(fn.Max(Facturas.folio).alias('mf'))
.where(Facturas.serie==serie)
.order_by(SQL('mf'))
.scalar())
if inicio is None:
inicio = 1
else:
inicio += 1
return inicio
@classmethod
def uncancel(cls, invoice):
query = (Tickets
.update(estatus='Generado', cancelado=False, factura=None)
.where(Tickets.factura==invoice)
)
result = query.execute()
return result
def _cancel_tickets(self, invoice, tickets):
query = (Tickets
.update(estatus='Facturado', cancelado=True, factura=invoice)
.where(Tickets.id.in_(tickets))
)
result = query.execute()
return result
def _calculate_totals_invoice(self, invoice, tickets):
subtotal = 0
descuento_cfdi = 0
totals_tax = {}
total_trasladados = None
total_retenciones = None
notes = Tickets.get_notes(tickets)
details = TicketsDetalle.select().where(TicketsDetalle.ticket.in_(tickets))
for detail in details:
product = {}
p = detail.producto
product['unidad'] = p.unidad.key
product['clave'] = p.clave
product['clave_sat'] = p.clave_sat
product['factura'] = invoice.id
product['producto'] = p.id
product['descripcion'] = detail.descripcion
cantidad = float(detail.cantidad)
valor_unitario = float(detail.valor_unitario)
descuento = float(detail.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':
continue
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)
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,
'notas': notes,
}
return data
def _get_totals_invoice_by_ticket(self, invoice, ids):
subtotal = 0
descuento_cfdi = 0
totals_tax = {}
total_trasladados = None
total_retenciones = None
notes = Tickets.get_notes(ids)
rows = Tickets.select().where(Tickets.id.in_(ids))
tax_sum = {}
for row in rows:
details = DEFAULT_GLOBAL.copy()
details['clave'] = row.serie + str(row.folio)
details['factura'] = invoice.id
unit_value = row.subtotal
discount = row.descuento
final_price = unit_value - discount
importe = final_price
details['valor_unitario'] = unit_value
details['descuento'] = discount
details['precio_final'] = final_price
details['importe'] = importe
descuento_cfdi += details['descuento']
subtotal += details['importe']
FacturasDetalle.create(**details)
taxes = (TicketsImpuestos
.select(
TicketsImpuestos.impuesto,
TicketsImpuestos.base,
TicketsImpuestos.importe)
.where(TicketsImpuestos.ticket == row.id)
)
for r in taxes:
tax_id = r.impuesto.id
tasa = r.impuesto.tasa
tax_importe = round(tasa * r.base, DECIMALES)
if tax_id in tax_sum:
tax_sum[tax_id]['base'] += r.base
tax_sum[tax_id]['importe'] += tax_importe
else:
values = {
'tipo': r.impuesto.tipo,
'key': r.impuesto.key,
'base': r.base,
'importe': tax_importe}
tax_sum[tax_id] = values
for i, tax in tax_sum.items():
tax_importe = round(tax['importe'], DECIMALES)
invoice_tax = {
'factura': invoice.id,
'impuesto': i,
'base': tax['base'],
'importe': tax_importe
}
FacturasImpuestos.create(**invoice_tax)
if tax['tipo'] == 'T' and tax['key'] != '000':
total_trasladados = (total_trasladados or 0) + tax_importe
elif tax['tipo'] == 'R' and tax['key'] != '000':
total_retenciones = (total_retenciones or 0) + tax_importe
total = subtotal - descuento_cfdi + \
(total_trasladados or 0) - (total_retenciones or 0)
type_change = Decimal(invoice.tipo_cambio)
total_mn = round(total * type_change, DECIMALES)
data = {
'subtotal': subtotal,
'descuento': descuento_cfdi,
'total': total,
'total_mn': total_mn,
'total_trasladados': total_trasladados,
'total_retenciones': total_retenciones,
'notas': notes,
}
return data
@classmethod
def invoice(cls, values, user):
is_invoice_day = util.get_bool(values['is_invoice_day'])
periodicidad = values['periodicidad']
id_client = int(values['client'])
tickets = util.loads(values['tickets'])
invoice_by_ticket = Configuracion.get_bool('chk_config_invoice_by_ticket')
if is_invoice_day:
filters = (
Socios.rfc == RFCS['PUBLIC'] and
Socios.slug == 'publico_en_general')
try:
client = Socios.get(filters)
except Socios.DoesNotExist:
msg = 'No existe el cliente PĆŗblico en General. Agregalo primero.'
data = {'ok': False, 'msg': msg}
return data
else:
client = Socios.get(Socios.id==id_client)
periodicidad = ''
if client.forma_pago is None:
msg = 'La Forma de Pago del cliente, no esta asignada'
data = {'ok': False, 'msg': msg}
return data
try:
receptor_regimen = SociosRegimenes.get_by_key(client)[0]['id']
except Exception as e:
log.error(e)
msg = 'Error al obtener el Regimen Fiscal del receptor'
data = {'ok': False, 'msg': msg}
return data
payment_type = cls._get_payment_type(cls, tickets)
emisor = Emisor.select()[0]
data = {}
data['cliente'] = client
data['serie'] = cls._get_serie(cls, user, True)
data['folio'] = cls._get_folio_invoice(cls, data['serie'])
data['forma_pago'] = payment_type
data['tipo_cambio'] = 1.00
data['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
data['uso_cfdi'] = client.uso_cfdi.key
data['regimen_fiscal'] = emisor.regimenes[0].key
data['receptor_regimen'] = receptor_regimen
data['periodicidad'] = periodicidad
with database_proxy.atomic() as txn:
obj = Facturas.create(**data)
if is_invoice_day and invoice_by_ticket:
totals = cls._get_totals_invoice_by_ticket(cls, obj, tickets)
else:
totals = cls._calculate_totals_invoice(cls, obj, tickets)
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.notas = totals['notas']
obj.save()
cls._cancel_tickets(cls, obj, tickets)
msg = 'Factura generada correctamente.<BR><BR>Enviando a timbrar'
data = {'ok': True, 'msg': msg, 'id': obj.id}
return data
def _get_payment_type(self, ids):
"""Get max of payment type"""
query = (Tickets
.select(
Tickets.forma_pago,
fn.Sum(Tickets.subtotal).alias('total'))
.where(Tickets.id.in_(ids))
.group_by(Tickets.forma_pago)
.order_by(SQL('total').desc())
.limit(1)
.scalar()
)
return query
# ~ def _update_inventory_if_cancel(self, id):
# ~ products = TicketsDetalle.select().where(TicketsDetalle.ticket==id)
# ~ for p in products:
# ~ if p.producto.inventario:
# ~ p.producto.existencia += p.cantidad
# ~ p.producto.save()
# ~ return
@classmethod
def cancel(cls, values, user):
admin_cancel = Configuracion.get_bool('chk_cancel_tickets_by_admin')
if admin_cancel and not user.es_admin:
msg = 'Solo un admin puede cancelar tickets'
result = {'ok': False, 'msg': msg, 'row': {}}
return result
id = int(values['id'])
msg = 'Ticket cancelado correctamente'
fields = {'cancelado': True, 'estatus': 'Cancelado'}
with database_proxy.atomic() as txn:
obj = Tickets.update(**fields).where(Tickets.id==id)
result = bool(obj.execute())
if result:
cls._update_inventory(cls, id, True, user)
row = {'estatus': 'Cancelado'}
return {'ok': result, 'row': row, 'msg': msg}
def _get_filters(self, values):
opt = values.get('opt', '')
if not opt:
return
if opt == 'active':
filters = (Tickets.cancelado==False)
return filters
if opt == 'today':
t = util.today()
filters = (
(Tickets.fecha.day == t.day) &
(Tickets.fecha.month == t.month) &
(Tickets.fecha.year == t.year)
)
return filters
if opt == 'yearmonth':
if values['year'] == '-1':
fy = (Tickets.fecha.year > 0)
else:
fy = (Tickets.fecha.year == int(values['year']))
if values['month'] == '-1':
fm = (Tickets.fecha.month > 0)
else:
fm = (Tickets.fecha.month == int(values['month']))
filters = (fy & fm)
return filters
if opt == 'dates':
dates = util.loads(values['range'])
filters = Tickets.fecha.between(
util.get_date(dates['start']),
util.get_date(dates['end'], True)
)
return filters
return
@classmethod
def get_by(cls, values, user):
filter_serie = False
filter_by_user = Configuracion.get_bool('chk_ticket_user_show_doc')
if not filter_by_user:
if not user.sucursal is None and not user.sucursal.serie_tickets is None:
filter_serie = (Tickets.serie==user.sucursal.serie_tickets)
filters = cls._get_filters(cls, values)
if filter_serie:
filters = filters & filter_serie
rows = tuple(Tickets
.select(
Tickets.id,
Tickets.serie,
Tickets.folio,
Tickets.fecha,
Tickets.estatus,
Tickets.total)
.where(filters)
.dicts()
)
return {'ok': True, 'rows': rows}
@classmethod
def filter_years(cls):
data = [{'id': -1, 'value': 'Todos'}]
rows = (Tickets
.select(Tickets.fecha.year.alias('year'))
.group_by(Tickets.fecha.year)
.order_by(Tickets.fecha.year)
)
if not rows is None:
data += [{'id': int(r.year), 'value': int(r.year)} for r in rows]
return tuple(data)
def _get_info_to_pdf(self, id):
data = {}
obj = Tickets.select().where(Tickets.id==id).dicts()[0]
formapago = SATFormaPago.get(SATFormaPago.key==obj['forma_pago'])
emisor = util.get_dict(Emisor.select().dicts()[0])
emisor['nointerior'] = emisor['no_interior']
emisor['noexterior'] = emisor['no_exterior']
emisor['codigopostal'] = emisor['codigo_postal']
data['is_ticket'] = True
data['cancelada'] = obj['cancelado']
data['donativo'] = ''
if obj['estatus'] == 'Facturado':
data['cancelada'] = False
data['comprobante'] = obj
data['comprobante']['version'] = 'ticket'
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']['formadepago'] = str(formapago)
data['comprobante']['totalenletras'] = util.to_letters(
data['comprobante']['total'], 'peso')
data['emisor'] = emisor
data['receptor'] = {'nombre': 'PĆŗblico en general'}
data['conceptos'] = TicketsDetalle.get_by_ticket(id)
data['totales'] = {}
data['totales']['total'] = str(data['comprobante']['total'])
data['totales']['subtotal'] = str(data['comprobante']['subtotal'])
data['totales']['moneda'] = 'peso'
data['totales']['traslados'] = ()
data['totales']['retenciones'] = ()
data['totales']['taxlocales'] = ()
data['timbre'] = {}
data['donataria'] = {}
data['ine'] = {}
data['leyendas'] = ()
return data
@classmethod
def get_pdf(cls, id):
obj = Tickets.get(Tickets.id==id)
name = '{}{}.pdf'.format(obj.serie, obj.folio)
data = cls._get_info_to_pdf(cls, id)
doc = util.to_pdf(data, data['emisor']['rfc'])
return doc, name
def _format_ticket(self, id):
emisor = util.get_dict(Emisor.select().dicts()[0])
regimen = Emisor.select()[0].regimenes[0].name
ticket = Tickets.select().where(Tickets.id==id).dicts()[0]
products = TicketsDetalle.get_by_print(id)
emisor['name'] = '{}\n'.format(emisor['nombre'])
emisor['rfc'] = 'RFC: {}\n'.format(emisor['rfc'])
emisor['regimen'] = 'Regimen: {}\n'.format(regimen)
interior = ''
if emisor['no_interior']:
interior = ', {}'.format(emisor['no_interior'])
colonia = ''
if emisor['colonia']:
colonia = ', Col. {}'.format(emisor['colonia'])
municipio = ''
if emisor['municipio']:
municipio = ', {}'.format(emisor['municipio'])
estado = ''
if emisor['estado']:
estado = ', {}'.format(emisor['estado'])
cp = ''
if emisor['codigo_postal']:
cp = ', C.P. {}'.format(emisor['codigo_postal'])
pais = ''
if emisor['pais']:
pais = ', {}'.format(emisor['pais'])
direccion = '{} {}{}{}{}{}{}{}\n\n'.format(emisor['calle'],
emisor['no_exterior'], interior, colonia, municipio, estado, cp,
pais)
emisor['address'] = direccion
ticket['title'] = 'Ticket: {}{}\n\n'.format(ticket['serie'],
ticket['folio'])
ticket['date'] = 'Fecha y hora: {}\n'.format(ticket['fecha'])
ticket['letters'] = '{}\n\n'.format(
util.to_letters(ticket['total'], 'peso'))
ticket['total'] = 'TOTAL: $ {:>12,.2f}\n\n'.format(ticket['total'])
data = {
'emisor': emisor,
'receptor': {'name': '{}\n\n'.format(PUBLIC)},
'ticket': ticket,
'products': products,
}
return data
def _get_info_printer(self):
info = {}
value = Configuracion.get_('txt_ticket_printer')
if not value:
return info
values = value.split(':')
if len(values) == 1:
info = {'ip': values[0], 'usb': ()}
elif len(values) == 2:
info = {'ip': '', 'usb': (int(values[0], 16), int(values[1], 16))}
elif len(values) == 5:
info = {'ip': '', 'usb': (
int(values[0], 16),
int(values[1], 16),
int(values[2]),
int(values[3], 16),
int(values[4], 16),
)
}
return info
@classmethod
def printer(cls, values):
id = int(values['id'])
info_printer = cls._get_info_printer(cls)
if not info_printer:
msg = 'Es necesario configurar una impresora.'
result = {'ok': False, 'msg': msg}
return result
data = cls._format_ticket(cls, id)
result = util.print_ticket(data, info_printer)
msg = 'Ticket impreso correctamente'
if not result:
msg = 'Asegurate de que la impresora este conectada y funcionando.'
result = {'ok': result, 'msg': msg}
return result
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)
warehouse = ForeignKeyField(Almacenes, null=True)
class Meta:
order_by = ('ticket',)
def _with_tax(self, product):
precio_final = float(product.precio_final)
base = float(product.precio_final)
for tax in product.producto.impuestos:
impuesto_producto = round(float(tax.tasa) * base, DECIMALES)
precio_final += impuesto_producto
return precio_final
@classmethod
def get_by_print(cls, id):
products = TicketsDetalle.select().where(TicketsDetalle.ticket==id)
lines = []
for p in products:
price_with_tax = cls._with_tax(cls, p)
importe = round(price_with_tax * float(p.cantidad), DECIMALES)
l = '{:>6,.2f} {:<4} {:<14} {:>9,.2f} {:>10,.2f}\n'.format(
p.cantidad, p.producto.unidad.name[:5], p.descripcion[:14],
price_with_tax, importe
)
lines.append(l)
return lines
@classmethod
def get_by_ticket(cls, id):
data = []
productos = TicketsDetalle.select().where(TicketsDetalle.ticket==id)
for p in productos:
producto = {}
producto['noidentificacion'] = p.producto.clave
producto['descripcion'] = p.descripcion
producto['unidad'] = p.producto.unidad.name
producto['cantidad'] = str(p.cantidad)
price_with_tax = cls._with_tax(cls, p)
producto['valorunitario'] = str(price_with_tax)
importe = round(price_with_tax * float(p.cantidad), DECIMALES)
producto['importe'] = str(importe)
data.append(producto)
return data
def _get_by_ticket_id(cls, args, user):
products = []
id = int(args['id'])
rows = TicketsDetalle.select().where(TicketsDetalle.ticket==id)
for row in rows:
# ~ descuento
# ~ precio_final
# ~ inventario
# ~ existencia
product = dict(
id_product = row.producto.id,
clave = row.producto.clave,
clave_sat = row.producto.clave_sat,
descripcion = row.descripcion,
unidad = row.producto.unidad.id,
cantidad = float(row.cantidad),
valor_unitario = cls._with_tax(cls, row),
)
product['importe'] = round(
product['valor_unitario'] * product['cantidad'], DECIMALES)
products.append(product)
return tuple(products)
@classmethod
def get_data(cls, values, user):
opt = values.pop('opt')
method = f'_get_{opt}'
return getattr(cls, method)(cls, values, user)
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',)
class Departamentos(BaseModel):
nombre = TextField(default='', unique=True)
descripcion = TextField(default='')
class Meta:
order_by = ('nombre',)
class Puestos(BaseModel):
departamento = ForeignKeyField(Departamentos, null=True)
nombre = TextField(default='')
descripcion = TextField(default='')
class Meta:
order_by = ('nombre',)
indexes = (
(('departamento', 'nombre'), True),
)
@classmethod
def get_by_depto(cls, puesto, depto):
departamento = None
if depto:
with database_proxy.transaction():
departamento, _ = Departamentos.get_or_create(nombre=depto)
data = {'departamento': departamento, 'nombre': puesto}
obj, _ = Puestos.get_or_create(**data)
return obj
class Empleados(BaseModel):
num_empleado = TextField(default='')
rfc = TextField(default='', unique=True)
curp = TextField(default='', unique=True)
nombre = TextField(default='')
paterno = TextField(default='')
materno = TextField(default='')
nombre_completo = TextField(default='')
es_activo = BooleanField(default=True)
es_extranjero = BooleanField(default=False)
fecha_alta = DateField(default=util.now)
fecha_ingreso = DateField(null=True)
imss = TextField(default='')
tipo_contrato = ForeignKeyField(SATTipoContrato)
es_sindicalizado = BooleanField(default=False)
tipo_jornada = ForeignKeyField(SATTipoJornada, null=True)
tipo_regimen = ForeignKeyField(SATTipoRegimen)
puesto = ForeignKeyField(Puestos, null=True)
riesgo_puesto = ForeignKeyField(SATRiesgoPuesto, null=True)
periodicidad_pago = ForeignKeyField(SATPeriodicidadPago)
banco = ForeignKeyField(SATBancos, null=True)
cuenta_bancaria = TextField(default='')
clabe = TextField(default='')
salario_base = DecimalField(default=0.0, max_digits=18, decimal_places=6,
auto_round=True)
salario_diario = DecimalField(default=0.0, max_digits=18, decimal_places=6,
auto_round=True)
estado = ForeignKeyField(SATEstados)
codigo_postal = TextField(default='')
notas = TextField(default='')
correo = TextField(default='')
class Meta:
order_by = ('nombre_completo',)
indexes = (
(('num_empleado', 'rfc'), True),
)
def _validate_import(self, row):
sn = {'si': True, 'no': False}
data = row.copy()
data['nombre_completo'] = '{} {} {}'.format(
row['nombre'], row['paterno'], row['materno']).strip()
data['fecha_ingreso'] = None
if row['fecha_ingreso']:
data['fecha_ingreso'] = util.calc_to_date(row['fecha_ingreso'])
data['tipo_contrato'] = SATTipoContrato.get_by_key(row['tipo_contrato'])
data['es_sindicalizado'] = sn.get(row['es_sindicalizado'].lower(), False)
data['tipo_jornada'] = SATTipoJornada.get_by_key(row['tipo_jornada'])
data['tipo_regimen'] = SATTipoRegimen.get_by_key(row['tipo_regimen'])
data['puesto'] = Puestos.get_by_depto(row['puesto'], row['departamento'])
data['riesgo_puesto'] = SATRiesgoPuesto.get_by_key(row['riesgo_puesto'])
data['periodicidad_pago'] = SATPeriodicidadPago.get_by_key(row['periodicidad_pago'])
data['banco'] = SATBancos.get_by_key(row['banco'])
data['estado'] = SATEstados.get_by_key(row['estado'])
del data['departamento']
return data
def _import(self):
emisor = Emisor.select()[0]
rows, msg = util.import_employees(emisor.rfc)
if not rows:
return {'ok': False, 'msg': msg}
en = 0
ea = 0
for row in rows:
if not DEBUG and row['rfc'] == 'BASM740115RW0':
continue
data = self._validate_import(self, row)
w = (Empleados.rfc==row['rfc'])
with database_proxy.transaction():
if Empleados.select().where(w).exists():
q = Empleados.update(**data).where(w)
q.execute()
ea += 1
else:
obj = Empleados.create(**data)
en += 1
msg = 'Empleados encontrados: {}<BR>'.format(len(rows))
msg += 'Empleados nuevos: {}<BR>'.format(en)
msg += 'Empleados actualizados: {}<BR>'.format(ea)
msg += 'Empleados no importados: {}'.format(len(rows) - en - ea)
return {'ok': True, 'msg': msg}
def _get(self):
rows = (Empleados
.select(
Empleados.id,
Empleados.num_empleado,
Empleados.rfc,
Empleados.curp,
Empleados.nombre_completo,
Empleados.imss,
Empleados.salario_base,
Empleados.salario_diario,
Empleados.fecha_ingreso)
.dicts()
)
return {'ok': True, 'rows': tuple(rows)}
@classmethod
def get_by(cls, values):
if not 'opt' in values:
return cls._get(cls)
if values['opt'] == 'import':
return cls._import(cls)
@classmethod
def remove(cls, id):
try:
q = Empleados.delete().where(Empleados.id==id)
return bool(q.execute())
except IntegrityError:
return False
class CfdiNomina(BaseModel):
empleado = ForeignKeyField(Empleados)
version = TextField(default=CURRENT_CFDI)
serie = TextField(default='N')
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='N')
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='Guardado')
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='')
tipo_relacion = TextField(default='')
error = TextField(default='')
version_nomina = TextField(default=CURRENT_CFDI_NOMINA)
registro_patronal = TextField(default='')
rfc_patron_origen = TextField(default='')
tipo_nomina = ForeignKeyField(SATTipoNomina)
fecha_pago = DateField()
fecha_inicial_pago = DateField()
fecha_final_pago = DateField()
dias_pagados = DecimalField(default=0.0, max_digits=12, decimal_places=2,
auto_round=True)
origen_recurso = ForeignKeyField(SATOrigenRecurso, null=True)
monto_recurso_propio = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
class Meta:
order_by = ('fecha',)
@classmethod
def _get_status_sat(cls, filters, user):
id = filters['id']
obj = CfdiNomina.get(CfdiNomina.id == id)
estatus_sat = utils.get_status_sat(obj.xml)
if obj.estatus_sat != estatus_sat:
obj.estatus_sat = estatus_sat
obj.save()
if obj.estatus_sat == 'Vigente' and obj.estatus == 'Cancelada':
days = utils.get_days(obj.fecha_cancelacion)
if days > 3:
estatus_sat = 'uncancel'
return estatus_sat
@classmethod
def _get_yearmonth(cls, filters, user):
if filters['year'] == '-1':
fy = (CfdiNomina.fecha.year > 0)
else:
fy = (CfdiNomina.fecha.year == int(filters['year']))
if filters['month'] == '-1':
fm = (CfdiNomina.fecha.month > 0)
else:
fm = (CfdiNomina.fecha.month == int(filters['month']))
filters = (fy & fm)
return cls._get(cls, filters)
@classmethod
def _get_dates(cls, filters, user):
dates = util.loads(filters['range'])
filters = CfdiNomina.fecha.between(
util.get_date(dates['start']),
util.get_date(dates['end'], True)
)
return cls._get(cls, filters)
@classmethod
def _get_import(cls, filters, user):
return cls._import(cls)
@classmethod
def _get_stamp(cls, filters, user):
return cls._stamp(cls)
@classmethod
def get_data(cls, values, user):
opt = values.pop('opt')
return getattr(cls, f'_get_{opt}')(values, user)
def _cancel(self, values, user):
id = int(values['id'])
args = utils.loads(values['args'])
obj = CfdiNomina.get(CfdiNomina.id==id)
if obj.uuid is None:
msg = 'Solo se pueden cancelar recibos timbrados'
return {'ok': False, 'msg': msg}
auth = Configuracion.get_({'fields': 'pac_auth'})
certificado = Certificado.get(Certificado.es_fiel==False)
result = utils.cancel_xml_sign(obj, args, auth, certificado)
if result['ok']:
obj.estatus = 'Cancelado'
obj.error = ''
obj.cancelada = True
obj.fecha_cancelacion = result['date']
obj.acuse = result['acuse'] or ''
else:
obj.error = result['msg']
obj.save()
return result
def _send_mail(self, values, user):
id = int(values['id'])
msg = 'Recibo de NĆ³mina enviado correctamente'
result = {'ok': True, 'msg': msg}
config = Configuracion.get_({'fields': 'correo'})
contra = Configuracion.get_('correo_contra')
if not config:
msg = 'EnvĆ­o de correo no configurado.'
result['ok'] = False
result['msg'] = msg
return result
emisor = Emisor.select()[0]
obj = CfdiNomina.get(CfdiNomina.id==id)
to = obj.empleado.correo
if not to:
msg = 'El empleado no tiene correo.'
result['ok'] = False
result['msg'] = msg
return result
name = '{}{}_{}'.format(obj.serie, obj.folio, obj.empleado.rfc)
values = self._get_not_in_xml(self, obj, emisor)
data = util.get_data_from_xml(obj, values)
doc = util.to_pdf(data, emisor.rfc)
files = (
(obj.xml, f'{name}.xml'),
(doc, f'{name}.pdf'),
)
message = subject = f"Enviamos tu recibo de nĆ³mina"
starttls = bool(int(values.get('correo_starttls', '0')))
server = {
'server': config['correo_servidor'],
'port': config['correo_puerto'],
'ssl': utils.to_bool(config['correo_ssl']),
'starttls': starttls,
'user': config['correo_usuario'],
'pass': utils.decrypt(contra, emisor.rfc),
}
mail = {
'to': to,
'copy': config.get('correo_copia', ''),
'subject': subject,
'message': message,
'files': files,
}
data= {
'server': server,
'mail': mail,
}
r = utils.send_mail(data)
if not r['ok']:
result['ok'] = False
result['msg'] = r['msg']
return result
@classmethod
def post(cls, values, user):
opt = values.pop('opt')
return getattr(cls, '_{}'.format(opt))(cls, values, user)
def _get_serie(self):
serie = Configuracion.get_('txt_config_nomina_serie')
if not serie:
serie = DEFAULT_SAT_NOMINA['SERIE']
return serie
def _get_folio(self, serie):
folio = int(Configuracion.get_('txt_config_nomina_folio') or '0')
inicio = (CfdiNomina
.select(fn.Max(CfdiNomina.folio).alias('mf'))
.where(CfdiNomina.serie==serie)
.order_by(SQL('mf'))
.scalar())
if inicio is None:
new = 1
else:
new = inicio + 1
if folio > new:
new = folio
return new
def _get_relacionados(values):
rows = values.split('|')
msg = ''
uuids = []
for row in rows:
sf = row.split('-')
if len(sf) == 1:
msg = 'Formato incorrecto en CFDI relacionado: SERIE-FOLIO'
return [], msg
try:
obj = CfdiNomina.get(
CfdiNomina.serie==sf[0], CfdiNomina.folio==int(sf[1]))
except CfdiNomina.DoesNotExist:
msg = 'No existe el CFDI relacionado: {}'.format(row)
return [], msg
uuids.append({'cfdi_origen': obj.id})
return uuids, msg
def _validate_nomina(self, row):
sn = {'si': True, 'no': False}
data = row.copy()
rfc = data.pop('rfc')
days_pay = data.pop('dias_pagados')
try:
data['empleado'] = Empleados.get(Empleados.rfc==rfc)
except Empleados.DoesNotExist:
msg = 'No existe el Empleado con RFC: {}'.format(rfc)
return {}, msg
tipo_nomina = SATTipoNomina.get_by_key(row['tipo_nomina'])
if tipo_nomina is None:
msg = 'RFC: {}, Tipo de NĆ³mina no existe: {}'.format(row['tipo_nomina'])
return {}, msg
if data['relacionados']:
data['relacionados'], msg = self._get_relacionados(data['relacionados'])
if msg:
return {}, msg
data['serie'] = self._get_serie(self)
data['folio'] = self._get_folio(self, data['serie'])
data['forma_pago'] = DEFAULT_SAT_NOMINA['FORMA_PAGO']
data['uso_cfdi'] = DEFAULT_SAT_NOMINA['USO_CFDI']
data['tipo_nomina'] = tipo_nomina
data['fecha_pago'] = util.calc_to_date(row['fecha_pago'])
data['fecha_inicial_pago'] = util.calc_to_date(row['fecha_inicial_pago'])
data['fecha_final_pago'] = util.calc_to_date(row['fecha_final_pago'])
data['dias_pagados'] = days_pay
return data, ''
def _validate_percepciones(self, headers, row):
total_gravado = 0.0
total_exento = 0.0
total_jubilacion = 0.0
total_separacion = 0.0
data = []
for i, key in enumerate(headers[::2]):
gravado = 0.0
exento = 0.0
if isinstance(row[i * 2], float):
gravado = round(row[i * 2], DECIMALES)
if isinstance(row[i * 2 + 1], float):
exento = round(row[i * 2 + 1], DECIMALES)
if not gravado and not exento:
continue
tp = SATTipoPercepcion.get_by_key(key)
if tp is None:
continue
total_gravado += gravado
total_exento += exento
if key in ('039', '044'):
total_jubilacion += gravado + exento
elif key in ('022', '023', '025'):
total_separacion += gravado + exento
new = {
'tipo_percepcion': tp,
'importe_gravado': gravado,
'importe_exento': exento,
}
data.append(new)
total_percepciones = round(total_gravado + total_exento, DECIMALES)
total_sueldos = round(total_percepciones - total_jubilacion - total_separacion, DECIMALES)
totals = {
'total_gravado': total_gravado,
'total_exento': total_exento,
'total_jubilacion': total_jubilacion,
'total_separacion': total_separacion,
'total_sueldos': total_sueldos,
'total_percepciones': total_percepciones
}
return data, totals, ''
def _validate_deducciones(self, headers, row, new_titles):
total_retenciones = 0.0
total_otras_deducciones = 0.0
data = []
for i, value in enumerate(row):
key = headers[0][i]
importe = 0.0
if isinstance(value, float):
importe = round(value, DECIMALES)
if not importe:
continue
td = SATTipoDeduccion.get_by_key(key)
if td is None:
continue
if key == '002':
total_retenciones += importe
else:
total_otras_deducciones += importe
new = {
'tipo_deduccion': td,
'importe': importe,
'concepto': new_titles.get(i, ''),
}
data.append(new)
totals = {
'total_retenciones': total_retenciones,
'total_otras_deducciones': total_otras_deducciones,
'total_deducciones': round(
total_retenciones + total_otras_deducciones, DECIMALES)
}
return data, totals, ''
def _validate_otros_pagos(self, headers, row):
total_otros_pagos = 0.0
data = []
subsidio_causado = round(row[0], DECIMALES)
for i, value in enumerate(row):
if not i:
continue
key = headers[0][i]
importe = 0.0
if isinstance(value, float):
importe = round(value, DECIMALES)
# ~ if not importe:
# ~ continue
td = SATTipoOtroPago.get_by_key(key)
if td is None:
continue
total_otros_pagos += importe
new = {
'tipo_otro_pago': td,
'importe': importe,
}
if key == '002':
new['subsidio_causado'] = subsidio_causado
data.append(new)
totals = {'total_otros_pagos': total_otros_pagos}
return data, totals, ''
def _validate_separacion(self, row):
if not row[0]:
return {}, ''
if not row[2] or isinstance(row[2], str):
msg = 'El campo Ultimo Sueldo debe ser un importe'
return {}, msg
if isinstance(row[3], str):
msg = 'El campo Ingreso Acumulable debe ser un importe'
return {}, msg
if isinstance(row[4], str):
msg = 'El campo Ingreso No Acumulable debe ser un importe'
return {}, msg
new = {
'total_pagado': round(row[0], DECIMALES),
'years_servicio': int(row[1]),
'ultimo_sueldo': round(row[2], DECIMALES),
'ingreso_acumulable': round(row[3], DECIMALES),
'ingreso_no_acumulable': round(row[4], DECIMALES),
}
return new, ''
def _validate_horas_extras(self, row):
data = []
for i, key in enumerate(row[::4]):
days = 0
if isinstance(row[i * 4], float):
days = int(row[i * 4])
key = row[i * 4 + 1]
the = SATTipoHoras.get_by_key(key)
if the is None:
continue
hours = 0
if isinstance(row[i * 4 + 2], float):
hours = int(row[i * 4 + 2])
importe = 0.0
if isinstance(row[i * 4 + 3], float):
importe = round(row[i * 4 + 3], DECIMALES)
if not hours or not importe:
continue
new = {
'dias': days,
'tipos_horas': the,
'horas_extra': hours,
'importe_pagado': importe,
}
data.append(new)
return data, ''
def _validate_incapacidades(self, row):
data = []
for i, key in enumerate(row[::3]):
msg = ''
key = row[i * 3]
ti = SATTipoIncapacidad.get_by_key(key)
if ti is None:
msg = 'No se encontrĆ³n el tipo de incapacidad'
break
if isinstance(row[i * 3 + 1], str):
msg = 'Los dias de incapacidad debe ser un nĆŗmero'
break
if isinstance(row[i * 3 + 2], str):
msg = 'El importe de la incapacidad debe ser un nĆŗmero'
break
days = int(row[i * 3 + 1])
importe = round(row[i * 3 + 2], DECIMALES)
if not days or not importe:
break
new = {
'dias': days,
'tipo': ti,
'importe': importe,
}
data.append(new)
return data, msg
def _validate_exists(self, values):
result = (CfdiNomina
.select()
.where(
(CfdiNomina.empleado==values['empleado']) &
(CfdiNomina.fecha_pago==values['fecha_pago']) &
(CfdiNomina.fecha_inicial_pago==values['fecha_inicial_pago']) &
(CfdiNomina.fecha_final_pago==values['fecha_final_pago']) &
(CfdiNomina.total==values['total']) &
(CfdiNomina.cancelada==False)
)
.exists())
return result
def _import(self):
util.log_file('nomina', kill=True)
emisor = Emisor.select()[0]
data, msg = util.import_nomina(emisor.rfc)
if not data:
return {'ok': False, 'msg': msg}
hp = data['percepciones'][0]
percepciones = data['percepciones'][2:]
hd = data['deducciones'][:1]
deducciones = data['deducciones'][2:]
ho = data['otros_pagos'][:1]
otros_pagos = data['otros_pagos'][2:]
separacion = data['separacion'][2:]
horas_extras = data['horas_extras'][2:]
incapacidades = data['incapacidades'][2:]
new_titles = data['new_titles']
for i, row in enumerate(data['nomina']):
row['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
row['regimen_fiscal'] = emisor.regimenes[0].key
row['registro_patronal'] = emisor.registro_patronal
new_nomina, msg = self._validate_nomina(self, row)
if msg:
util.log_file('nomina', msg)
continue
relacionados = new_nomina.pop('relacionados', '')
if relacionados:
new_nomina['tipo_relacion'] = '04'
new_percepciones, total_percepciones, msg = \
self._validate_percepciones(self, hp, percepciones[i])
if msg:
util.log_file('nomina', msg)
continue
new_deducciones, total_deducciones, msg = \
self._validate_deducciones(self, hd, deducciones[i], new_titles)
if msg:
util.log_file('nomina', msg)
continue
new_otros_pagos, total_otros_pagos, msg = \
self._validate_otros_pagos(self, ho, otros_pagos[i])
if msg:
util.log_file('nomina', msg)
continue
new_separacion, msg = self._validate_separacion(self, separacion[i])
if msg:
util.log_file('nomina', msg)
continue
new_horas_extras, msg = self._validate_horas_extras(self, horas_extras[i])
if msg:
util.log_file('nomina', msg)
continue
new_incapacidades, msg = self._validate_incapacidades(self, incapacidades[i])
if msg:
util.log_file('nomina', msg)
continue
totals = total_percepciones.copy()
totals.update(total_deducciones)
totals.update(total_otros_pagos)
totals['subtotal'] = round(totals['total_percepciones'] +
totals['total_otros_pagos'], DECIMALES)
totals['descuento'] = totals['total_deducciones']
totals['total'] = round(totals['subtotal'] -
totals['descuento'], DECIMALES)
new_nomina['subtotal'] = totals['subtotal']
new_nomina['descuento'] = totals['descuento']
new_nomina['total'] = totals['total']
new_nomina['total_mn'] = totals['total']
if self._validate_exists(self, new_nomina):
info = '{}'.format(new_nomina['empleado'].nombre_completo)
msg = 'Nomina existente: {}'.format(info)
util.log_file('nomina', msg)
continue
try:
with database_proxy.transaction():
obj = CfdiNomina.create(**new_nomina)
for row in new_percepciones:
row['cfdi'] = obj
CfdiNominaPercepciones.create(**row)
for row in new_deducciones:
row['cfdi'] = obj
CfdiNominaDeducciones.create(**row)
for row in new_otros_pagos:
row['cfdi'] = obj
CfdiNominaOtroPago.create(**row)
for row in new_horas_extras:
row['cfdi'] = obj
CfdiNominaHorasExtra.create(**row)
for row in new_incapacidades:
row['cfdi'] = obj
CfdiNominaIncapacidad.create(**row)
for row in relacionados:
row['cfdi'] = obj
CfdiNominaRelacionados.create(**row)
if new_separacion:
new_separacion['cfdi'] = obj
CfdiNominaSeparacion.create(**new_separacion)
concepto = {
'cfdi': obj,
'valor_unitario': totals['subtotal'],
'importe': totals['subtotal'],
'descuento': totals['total_deducciones'],
}
CfdiNominaDetalle.create(**concepto)
totals['cfdi'] = obj
CfdiNominaTotales.create(**totals)
except Exception as e:
msg = 'ERROR: {}-{}'.format(new_nomina['serie'], new_nomina['folio'])
util.log_file('nomina', msg)
util.log_file('nomina', str(e))
continue
msg = 'NĆ³mina importada correctamente'
return {'ok': True, 'msg': msg}
def _get(self, where=''):
if not where:
where = ((CfdiNomina.uuid.is_null(True)) & (CfdiNomina.cancelada==False))
rows = (CfdiNomina
.select(
CfdiNomina.id,
CfdiNomina.serie,
CfdiNomina.folio,
CfdiNomina.uuid,
CfdiNomina.fecha,
CfdiNomina.estatus,
CfdiNomina.fecha_pago,
CfdiNomina.total,
Empleados.nombre_completo.alias('empleado')
)
.where(where)
.join(Empleados)
.switch(CfdiNomina)
.order_by(CfdiNomina.id)
.dicts()
)
return {'ok': True, 'rows': tuple(rows)}
def _validate_rules_nomina(self, data):
receptor = data['receptor']
if receptor['TipoContrato'] in ('09', '10', '99'):
data['emisor'].pop('RegistroPatronal', None)
# ~ msg = 'Recibo: {}-{}'.format(row.serie, row.folio)
# ~ util.log_file('nomina', msg)
# ~ NOM 154
if data['nomina']['TipoNomina'] == 'E':
data['receptor']['PeriodicidadPago'] = '99'
# ~ NOM 162
# ~ Si el atributo TipoContrato estĆ” entre 01 al 08, el atributo
# ~ Nomina.Emisor.RegistroPatronal debe existir.
return
def _make_xml(self, cfdi):
emisor = Emisor.select()[0]
empleado = cfdi.empleado
certificado = Certificado.get(Certificado.es_fiel==False)
totals = CfdiNominaTotales.select().where(CfdiNominaTotales.cfdi==cfdi)[0]
comprobante = {}
relacionados = {}
complementos = None
comprobante['Serie'] = cfdi.serie
comprobante['Folio'] = str(cfdi.folio)
comprobante['Fecha'] = cfdi.fecha.isoformat()[:19]
comprobante['FormaPago'] = cfdi.forma_pago
comprobante['NoCertificado'] = certificado.serie
comprobante['Certificado'] = certificado.cer_txt
comprobante['SubTotal'] = FORMAT.format(cfdi.subtotal)
comprobante['Moneda'] = cfdi.moneda
comprobante['Total'] = FORMAT.format(cfdi.total)
comprobante['TipoDeComprobante'] = cfdi.tipo_comprobante
comprobante['MetodoPago'] = cfdi.metodo_pago
comprobante['LugarExpedicion'] = cfdi.lugar_expedicion
if cfdi.descuento:
comprobante['Descuento'] = FORMAT.format(cfdi.descuento)
if cfdi.tipo_relacion:
relacionados = {
'tipo': cfdi.tipo_relacion,
'cfdis': CfdiNominaRelacionados.get_(cfdi),
}
cfdi_emisor = {
'Rfc': emisor.rfc,
'Nombre': emisor.nombre,
'RegimenFiscal': cfdi.regimen_fiscal,
}
receptor = {
'Rfc': cfdi.empleado.rfc,
'Nombre': cfdi.empleado.nombre_completo,
'UsoCFDI': cfdi.uso_cfdi,
}
conceptos = []
rows = CfdiNominaDetalle.select().where(CfdiNominaDetalle.cfdi==cfdi)
for row in rows:
concepto = {
'ClaveProdServ': row.clave_sat,
'Cantidad': '1',
'ClaveUnidad': row.clave_unidad,
'Descripcion': row.descripcion,
'ValorUnitario': FORMAT.format(row.valor_unitario),
'Importe': FORMAT.format(row.importe),
}
if row.descuento:
concepto['Descuento'] = FORMAT.format(row.descuento)
conceptos.append(concepto)
nomina = {
'Version': cfdi.version_nomina,
'TipoNomina': cfdi.tipo_nomina.key,
'FechaPago': str(cfdi.fecha_pago),
'FechaInicialPago': str(cfdi.fecha_inicial_pago),
'FechaFinalPago': str(cfdi.fecha_final_pago),
'NumDiasPagados': FORMAT3.format(cfdi.dias_pagados),
}
if totals.total_percepciones:
nomina['TotalPercepciones'] = FORMAT.format(totals.total_percepciones)
if totals.total_deducciones:
nomina['TotalDeducciones'] = FORMAT.format(totals.total_deducciones)
# ~ if totals.total_otros_pagos:
nomina['TotalOtrosPagos'] = FORMAT.format(totals.total_otros_pagos)
nomina_emisor = {}
if emisor.curp:
nomina_emisor['Curp'] = emisor.curp
if emisor.registro_patronal:
nomina_emisor['RegistroPatronal'] = emisor.registro_patronal
nomina_receptor = {
'Curp': empleado.curp,
'TipoContrato': empleado.tipo_contrato.key,
'Sindicalizado': {True: 'Si', False: 'No'}.get(empleado.es_sindicalizado),
'TipoJornada': empleado.tipo_jornada.key,
'TipoRegimen': empleado.tipo_regimen.key,
'NumEmpleado': str(empleado.num_empleado),
'PeriodicidadPago': empleado.periodicidad_pago.key,
'ClaveEntFed': empleado.estado.key,
}
if empleado.riesgo_puesto.key != '99':
nomina_receptor['RiesgoPuesto'] = empleado.riesgo_puesto.key
if empleado.imss:
nomina_receptor['NumSeguridadSocial'] = empleado.imss.replace('-', '')
if empleado.fecha_ingreso:
nomina_receptor['FechaInicioRelLaboral'] = str(empleado.fecha_ingreso)
days = util.get_days(empleado.fecha_ingreso, cfdi.fecha_final_pago)
weeks = days // 7
if weeks:
ant = 'P{}W'.format(weeks)
else:
ant = 'P{}D'.format(days)
nomina_receptor['AntigĆ¼edad'] = ant
if empleado.puesto.nombre:
if empleado.puesto.departamento.nombre:
nomina_receptor['Departamento'] = empleado.puesto.departamento.nombre
nomina_receptor['Puesto'] = empleado.puesto.nombre
if empleado.clabe:
nomina_receptor['CuentaBancaria'] = empleado.clabe.replace('-', '')
elif empleado.cuenta_bancaria:
nomina_receptor['CuentaBancaria'] = empleado.cuenta_bancaria.replace('-', '')
nomina_receptor['Banco'] = empleado.banco.key
if empleado.salario_base:
nomina_receptor['SalarioBaseCotApor'] = FORMAT.format(empleado.salario_base)
if empleado.salario_diario:
nomina_receptor['SalarioDiarioIntegrado'] = FORMAT.format(empleado.salario_diario)
percepciones = {
'TotalSueldos': FORMAT.format(totals.total_sueldos),
'TotalGravado': FORMAT.format(totals.total_gravado),
'TotalExento': FORMAT.format(totals.total_exento),
}
if totals.total_separacion:
percepciones['TotalSeparacionIndemnizacion'] = FORMAT.format(totals.total_separacion)
if totals.total_jubilacion:
percepciones['TotalJubilacionPensionRetiro'] = FORMAT.format(totals.total_jubilacion)
rows = CfdiNominaPercepciones.select().where(
CfdiNominaPercepciones.cfdi==cfdi)
details = []
for row in rows:
concepto = row.concepto or row.tipo_percepcion.nombre or row.tipo_percepcion.name
p = {
'TipoPercepcion': row.tipo_percepcion.key,
'Clave': row.tipo_percepcion.clave or row.tipo_percepcion.key,
'Concepto': concepto[:100],
'ImporteGravado': FORMAT.format(row.importe_gravado),
'ImporteExento': FORMAT.format(row.importe_exento),
}
details.append(p)
percepciones['details'] = details
rows = CfdiNominaSeparacion.select().where(CfdiNominaSeparacion.cfdi==cfdi)
for row in rows:
separacion = {
'TotalPagado': FORMAT.format(row.total_pagado),
'NumAƱosServicio': str(row.years_servicio),
'UltimoSueldoMensOrd': FORMAT.format(row.ultimo_sueldo),
'IngresoAcumulable': FORMAT.format(row.ingreso_acumulable),
'IngresoNoAcumulable': FORMAT.format(row.ingreso_no_acumulable),
}
percepciones['separacion'] = separacion
break
rows = CfdiNominaHorasExtra.select().where(CfdiNominaHorasExtra.cfdi==cfdi)
details = []
for row in rows:
n = {
'Dias': str(row.dias),
'TipoHoras': row.tipos_horas.key,
'HorasExtra': str(row.horas_extra),
'ImportePagado': FORMAT.format(row.importe_pagado),
}
details.append(n)
percepciones['hours_extra'] = details
deducciones = {
'TotalOtrasDeducciones': FORMAT.format(totals.total_otras_deducciones),
}
if totals.total_retenciones:
deducciones['TotalImpuestosRetenidos'] = \
FORMAT.format(totals.total_retenciones)
rows = CfdiNominaDeducciones.select().where(CfdiNominaDeducciones.cfdi==cfdi)
details = []
for row in rows:
concepto = row.concepto or row.tipo_deduccion.nombre or row.tipo_deduccion.name
p = {
'TipoDeduccion': row.tipo_deduccion.key,
'Clave': row.tipo_deduccion.clave or row.tipo_deduccion.key,
'Concepto': concepto[:100],
'Importe': FORMAT.format(row.importe),
}
details.append(p)
deducciones['details'] = details
rows = CfdiNominaOtroPago.select().where(CfdiNominaOtroPago.cfdi==cfdi)
otros_pagos = []
for row in rows:
concepto = row.concepto or row.tipo_otro_pago.nombre or row.tipo_otro_pago.name
if nomina_receptor['TipoRegimen'] != '02' and \
row.tipo_otro_pago.key in ('002', '007', '008'):
continue
p = {
'TipoOtroPago': row.tipo_otro_pago.key,
'Clave': row.tipo_otro_pago.clave or row.tipo_otro_pago.key,
'Concepto': concepto[:100],
'Importe': FORMAT.format(row.importe),
}
if row.tipo_otro_pago.key == '002':
p['subsidio'] = {
'SubsidioCausado': FORMAT.format(row.subsidio_causado)
}
otros_pagos.append(p)
if not otros_pagos:
del nomina['TotalOtrosPagos']
rows = CfdiNominaIncapacidad.select().where(CfdiNominaIncapacidad.cfdi==cfdi)
incapacidades = []
for row in rows:
n = {
'DiasIncapacidad': str(row.dias),
'TipoIncapacidad': row.tipo.key,
'ImporteMonetario': FORMAT.format(row.importe),
}
incapacidades.append(n)
nomina = {
'nomina': nomina,
'emisor': nomina_emisor,
'receptor': nomina_receptor,
'percepciones': percepciones,
'deducciones': deducciones,
'otros_pagos': otros_pagos,
'incapacidades': incapacidades,
}
self._validate_rules_nomina(self, nomina)
data = {
'comprobante': comprobante,
'relacionados': relacionados,
'emisor': cfdi_emisor,
'receptor': receptor,
'conceptos': conceptos,
'complementos': complementos,
'nomina': nomina,
'impuestos': {},
'donativo': {},
}
# ~ return util.make_xml(data, certificado, auth)
return utils.make_xml(data, certificado)
def _stamp_id(self, id):
# ~ auth = Emisor.get_auth()
auth = Configuracion.get_({'fields': 'pac_auth'})
obj = CfdiNomina.get(CfdiNomina.id==id)
obj.xml = self._make_xml(self, obj)
obj.estatus = 'Generado'
obj.save()
# ~ result = util.timbra_xml(obj.xml, auth)
result = utils.xml_stamp(obj.xml, auth)
# ~ print (result)
if result['ok']:
obj.xml = result['xml']
obj.uuid = result['uuid']
obj.fecha_timbrado = result['date']
obj.estatus = 'Timbrado'
obj.error = ''
obj.save()
# ~ cls._sync(cls, id, auth)
else:
msg = result['error']
obj.estatus = 'Error'
obj.error = msg
obj.save()
return result['ok'], obj.error
def _stamp(self):
msg = ''
where = ((CfdiNomina.uuid.is_null(True)) & (CfdiNomina.cancelada==False))
rows = CfdiNomina.select().where(where).order_by(CfdiNomina.id)
util.log_file('nomina', kill=True)
if not len(rows):
msg = 'Sin recibos por timbrar'
return {'ok': True, 'msg_ok': msg}
msg_error = ''
ok_stamp = 0
for row in rows:
msg = 'Timbrando el recibo: {}-{}'.format(row.serie, row.folio)
util.log_file('nomina', msg)
result, msg = self._stamp_id(self, row.id)
if result:
msg = 'Recibo: {}-{}, timbrado correctamente'.format(row.serie, row.folio)
ok_stamp += 1
util.log_file('nomina', msg)
else:
msg = 'Error la timbrar: {}-{}, {}'.format(row.serie, row.folio, msg)
util.log_file('nomina', msg)
msg_error = msg
break
ok = False
if ok_stamp:
msg = 'Se timbraron {} recibos'.format(ok_stamp)
ok = True
error = False
if msg_error:
error = True
return {'ok': ok, 'msg_ok': msg, 'error': error, 'msg_error': msg_error}
def _get_by_download(self, filters):
emisor = Emisor.select()[0]
ids = util.loads(filters['ids'])
w = CfdiNomina.id.in_(ids)
rows = CfdiNomina.select().where(w)
files = {}
for row in rows:
name = '{}{}_{}'.format(row.serie, row.folio, row.empleado.rfc)
files[f'{name}.xml'] = row.xml
values = self._get_not_in_xml(self, row, emisor)
data = util.get_data_from_xml(row, values)
doc = util.to_pdf(data, emisor.rfc)
files[f'{name}.pdf'] = doc
fz = utils.to_zip(files)
return {'data': fz, 'name': name + 'zip'}
@classmethod
def get_by(cls, values):
if not values:
return cls._get(cls)
if values.get('by', ''):
return getattr(cls, f"_get_by_{values['by']}")(cls, values)
@classmethod
def remove(cls, ids):
ids = util.loads(ids)
for id in ids:
obj = CfdiNomina.get(CfdiNomina.id==id)
if obj.uuid:
continue
with database_proxy.transaction():
q = CfdiNominaDetalle.delete().where(CfdiNominaDetalle.cfdi==obj)
q.execute()
q = CfdiNominaTotales.delete().where(CfdiNominaTotales.cfdi==obj)
q.execute()
q = CfdiNominaPercepciones.delete().where(CfdiNominaPercepciones.cfdi==obj)
q.execute()
q = CfdiNominaDeducciones.delete().where(CfdiNominaDeducciones.cfdi==obj)
q.execute()
q = CfdiNominaOtroPago.delete().where(CfdiNominaOtroPago.cfdi==obj)
q.execute()
q = CfdiNominaSeparacion.delete().where(CfdiNominaSeparacion.cfdi==obj)
q.execute()
q = CfdiNominaHorasExtra.delete().where(CfdiNominaHorasExtra.cfdi==obj)
q.execute()
q = CfdiNominaIncapacidad.delete().where(CfdiNominaIncapacidad.cfdi==obj)
q.execute()
obj.delete_instance()
return True
@classmethod
def get_xml(cls, id):
obj = CfdiNomina.get(CfdiNomina.id==id)
name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.empleado.rfc)
# ~ cls._sync_xml(cls, obj)
return obj.xml, name
def _get_not_in_xml(self, invoice, emisor):
values = {'is_nomina': True, 'el.version': VERSION_EMPRESA_LIBRE}
if invoice.version == '3.2':
return values
obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal)
values['regimenfiscal'] = str(obj)
obj = SATFormaPago.get(SATFormaPago.key==invoice.forma_pago)
values['formadepago'] = '{} ({})'.format(obj.name, obj.key)
obj = SATPeriodicidadPago.get(SATPeriodicidadPago.id==invoice.empleado.periodicidad_pago)
values['periodicidaddepago'] = '{} ({})'.format(obj.name, obj.key)
values['tiporelacion'] = invoice.tipo_relacion
values['usocfdi'] = invoice.uso_cfdi
values['receptor'] = {}
values['fechadof'] = None
values['version'] = invoice.version_nomina
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 = CfdiNomina.get(CfdiNomina.id==id)
name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.empleado.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 filter_years(cls):
data = [{'id': -1, 'value': 'Todos'}]
rows = (CfdiNomina
.select(CfdiNomina.fecha.year.alias('year'))
.group_by(CfdiNomina.fecha.year)
.order_by(CfdiNomina.fecha.year)
)
if not rows is None:
data += [{'id': int(r.year), 'value': int(r.year)} for r in rows]
return tuple(data)
class CfdiNominaDetalle(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
clave_sat = TextField(default=DEFAULT_SAT_NOMINA['CLAVE'])
cantidad = DecimalField(default=1.0, max_digits=18, decimal_places=6,
auto_round=True)
clave_unidad = TextField(default=DEFAULT_SAT_NOMINA['UNIDAD'])
descripcion = TextField(default=DEFAULT_SAT_NOMINA['DESCRIPCION'])
valor_unitario = 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)
descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6,
auto_round=True)
class Meta:
order_by = ('cfdi',)
class CfdiNominaTotales(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
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_percepciones = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
total_gravado = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
total_exento = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
total_deducciones = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
total_otros_pagos = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
total_sueldos = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
total_separacion = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
total_jubilacion = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
total_retenciones = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
total_otras_deducciones = 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)
class CfdiNominaJubilacion(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
total_una_exhibicion = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
total_parcialidad = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
monto_diario = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
ingreso_acumulable = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
ingreso_no_acumulable = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
class CfdiNominaSeparacion(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
total_pagado = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
years_servicio = IntegerField(default=0)
ultimo_sueldo = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
ingreso_acumulable = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
ingreso_no_acumulable = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
class CfdiNominaPercepciones(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
tipo_percepcion = ForeignKeyField(SATTipoPercepcion)
concepto = TextField(default='')
importe_gravado = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
importe_exento = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
valor_mercado = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
precio_al_ortorgarse = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
class CfdiNominaDeducciones(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
tipo_deduccion = ForeignKeyField(SATTipoDeduccion)
concepto = TextField(default='')
importe = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
class CfdiNominaOtroPago(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
tipo_otro_pago = ForeignKeyField(SATTipoOtroPago)
concepto = TextField(default='')
importe = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
subsidio_causado = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
saldo_a_favor = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
year = IntegerField(default=0)
remanente_saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
class CfdiNominaIncapacidad(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
dias = IntegerField(default=0)
tipo = ForeignKeyField(SATTipoIncapacidad)
importe = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
class CfdiNominaHorasExtra(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
dias = IntegerField(default=0)
tipos_horas = ForeignKeyField(SATTipoHoras)
horas_extra = IntegerField(default=0)
importe_pagado = DecimalField(default=0.0, max_digits=20,
decimal_places=6, auto_round=True)
class CfdiNominaSubcontratos(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
rfc = TextField(default='')
porcentaje = DecimalField(default=0.0, max_digits=12, decimal_places=2,
auto_round=True)
class Meta:
order_by = ('cfdi',)
class CfdiNominaOtros(BaseModel):
cfdi = ForeignKeyField(CfdiNomina)
node = TextField(default='')
key = TextField(default='')
value = TextField(default='')
class CfdiNominaRelacionados(BaseModel):
cfdi = ForeignKeyField(CfdiNomina, related_name='original')
cfdi_origen = ForeignKeyField(CfdiNomina, related_name='relacion')
class Meta:
order_by = ('cfdi',)
@classmethod
def get_(cls, cfdi):
query = (CfdiNominaRelacionados
.select()
.where(CfdiNominaRelacionados.cfdi==cfdi)
)
return [str(r.cfdi_origen.uuid) for r in query]
class PartnerProducts(BaseModel):
partner = ForeignKeyField(Socios)
key = TextField(default='')
product = ForeignKeyField(Productos)
class Meta:
order_by = ('partner', 'key')
indexes = (
(('partner', 'key', 'product'), True),
)
def _get_product(self, filters):
partner = utils.loads(filters['partner'])
id = int(partner['id'])
rfc = partner['rfc']
key = filters['product_key']
where = (Socios.rfc == rfc)
if id:
where = (Socios.id == id)
where = (where & (PartnerProducts.key == key))
select = (
Productos.id.alias('id_product'),
Productos.clave_sat.alias('key_sat1'),
Productos.descripcion.alias('description1'),
Productos.valor_unitario.alias('unit_value1'),
)
try:
obj = (PartnerProducts
.select(*select)
.join(Socios).switch(PartnerProducts)
.join(Productos).switch(PartnerProducts)
.where(where)
.dicts()[0]
)
except IndexError:
msg = 'No se encontrĆ³ un producto existente'
data = {'ok': False, 'msg': msg}
return data
data = {'ok': True, 'row': obj}
return data
@classmethod
def get_data(cls, filters):
method = f"_get_{filters['opt']}"
return getattr(cls, method)(cls, filters)
class PartnerInvoices(BaseModel):
partner = ForeignKeyField(Socios)
serie = TextField(default='')
folio = TextField(default='')
cfdi_date = DateTimeField()
cfdi_stamp = DateTimeField()
pay_way = TextField(default='')
pay_conditions = TextField(default='')
pay_method = TextField(default='')
subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
discount = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
currency = TextField(default='MXN')
type_change = 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)
type_cfdi = TextField(default='')
cfdi_use = 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()
status = TextField(default='Guardada')
status_sat = TextField(default='Vigente')
notes = TextField(default='')
balance = DecimalField(default=0.0, max_digits=20, decimal_places=6,
auto_round=True)
pagada = BooleanField(default=False)
cancelada = BooleanField(default=False)
date_cancel = DateTimeField(null=True)
error = TextField(default='')
class Meta:
order_by = ('-cfdi_date',)
class InventoryEntries(BaseModel):
storage = ForeignKeyField(Almacenes, null=True)
invoice = ForeignKeyField(PartnerInvoices, null=True)
product = ForeignKeyField(Productos)
date_add = DateTimeField(default=utils.now)
cant = DecimalField(default=0.0, max_digits=18, decimal_places=2,
auto_round=True)
class Meta:
order_by = ('-date_add',)
indexes = (
(('storage', 'product', 'date_add', 'cant'), True),
)
@classmethod
def get_data(cls, filters):
method = f"_get_{filters['opt']}"
return getattr(cls, method)(cls, filters)
def _get_or_create_partner(self, args):
obj = None
msg = ''
id = int(args['id'])
type_person = 1
if len(args['rfc']) == 12:
type_person = 2
if args['rfc'] == RFCS['FOREIGN']:
type_person = 4
fields = dict(
tipo_persona = type_person,
rfc = args['rfc'],
nombre = args['nombre'],
slug = utils.to_slug(args['nombre']),
es_proveedor = True,
)
try:
if id:
obj = Socios.get(Socios.id==id)
else:
obj = Socios.create(**fields)
except IntegrityError as e:
msg = 'Ocurrio un error, al dar de alta el emisor'
return obj, msg
def _get_or_create_product(self, args, cant):
# ~ almacen = ForeignKeyField(Almacenes, null=True)
id = int(args.get('id_product', '0'))
if id:
obj = Productos.get(Productos.id==id)
else:
category = Categorias._get_by_name('Productos')
next_key = Productos.next_key()['value']
unit = SATUnidades.get_by_name(args['unit'])
fields = dict(
categoria = category,
clave = next_key,
clave_sat = args['key_sat'],
descripcion = args['description'],
unidad = unit,
valor_unitario = Decimal(args['unit_value']),
inventario = True,
)
obj = Productos.create(**fields)
where = (SATImpuestos.activo==True & SATImpuestos.default==True)
taxes = SATImpuestos.select().where(where)
obj.impuestos = taxes
obj.save()
res = (Productos
.update(existencia=Productos.existencia + cant)
.where(Productos.id==obj.id).execute()
)
return obj
def _add_entries(self, products, partner):
msg = ''
for p in products:
cant = float(p['cant'])
product = self._get_or_create_product(self, p, cant)
values = dict(
product = product,
cant = cant,
)
InventoryEntries.create(**values)
values = dict(
partner = partner,
key = p['key'],
product = product,
)
PartnerProducts.get_or_create(**values)
return msg
def _create(self, args, user):
# ~ print('ARGS', args)
if not user.es_admin:
msg = 'Solo un administrador puede usar esta funciĆ³n.'
result = {'ok': False, 'msg': msg}
return result
partner, error = self._get_or_create_partner(self, args['partner'])
if error:
result = {'ok': False, 'msg': error}
return result
# ~ ToDo - Save invoice
error = self._add_entries(self, args['products'], partner)
if error:
result = {'ok': False, 'msg': error}
return result
result = {'ok': True, 'msg': ''}
return result
def _create_manual(self, args, user):
if not user.es_admin:
msg = 'Solo un administrador puede usar esta funciĆ³n.'
result = {'ok': False, 'msg': msg}
return result
product = Productos.get(Productos.id==args['product_id'])
if not product.inventario:
msg = 'Este producto no lleva inventario.'
result = {'ok': False, 'msg': msg}
return result
storage = None
if args['warehouse']:
storage = args['warehouse']
cant = Decimal(args['cant'])
values = dict(
storage = storage,
product = product,
cant = cant,
)
obj = InventoryEntries.create(**values)
msg = 'a {}'.format(obj.id)
_save_log(user.usuario, msg, 'IE')
WareHouseProduct.update_exists(values)
new_cant = product.existencia + cant
q = (Productos
.update(existencia=Productos.existencia + cant)
.where(Productos.id==product.id)
)
q.execute()
result = {'ok': True, 'row': {'existencia': new_cant}}
return result
def _warehouse_movement(self, args, user):
if not user.es_admin:
msg = 'Solo un administrador puede usar esta funciĆ³n.'
result = {'ok': False, 'msg': msg}
return result
product = Productos.get(Productos.id==args['id_product'])
if not product.inventario:
msg = 'Este producto no lleva inventario.'
result = {'ok': False, 'msg': msg}
return result
storage = args['target']
cant = Decimal(args['cant'])
values = dict(
storage = storage,
product = product,
cant = cant,
)
obj = InventoryEntries.create(**values)
msg = 'a {}'.format(obj.id)
_save_log(user.usuario, msg, 'IE')
WareHouseProduct.update_exists(values)
values['storage'] = args['source']
values['cant'] *= -1
obj = InventoryEntries.create(**values)
msg = 'a {}'.format(obj.id)
_save_log(user.usuario, msg, 'IE')
WareHouseProduct.update_exists(values)
result = {'ok': True}
return result
@classmethod
def post(cls, values, user=None):
opt = values['opt']
args = utils.loads(values['values'])
return getattr(cls, f'_{opt}')(cls, args, user)
@util.run_in_thread
def _save_log(user, action, table):
data = {'usuario': user, 'accion': action, 'tabla': table}
Registro.add(data)
return
@util.run_in_thread
def _send_notify_access(args):
admins = (Usuarios
.select(Usuarios.correo)
.where(Usuarios.es_admin==True)
.scalar(as_tuple=True))
if not admins:
return
config = Configuracion.get_({'fields': 'correo'})
contra = Configuracion.get_('correo_contra')
if not config:
return
user = args['usuario']
rfc = args['rfc']
ip = args['ip']
url = f"http://ip-api.com/line/{ip}?fields=city"
city = utils.get_url(url)
message = f"Desde la IP: {ip} en: {city}"
server = {
'server': config['correo_servidor'],
'port': config['correo_puerto'],
'ssl': utils.to_bool(config['correo_ssl']),
'starttls': utils.to_bool(config['correo_starttls']),
'user': config['correo_usuario'],
'pass': utils.decrypt(contra, rfc),
}
mail = {
'to': ','.join(admins),
'subject': f"Usuario {user} identificado",
'message': message,
}
data= {
'server': server,
'mail': mail,
}
result = utils.send_mail(data)
return
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
notify_access = Configuracion.get_bool('chk_users_notify_access')
if notify_access:
_send_notify_access(args)
return respuesta, obj
def get_cp(cp):
con = sqlite3.connect(PATH_CP)
cursor = con.cursor()
sql = """
SELECT colonia, municipio, estado, municipios.id_municipio
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],
'key_municipio': str(rows[0][3]).zfill(3),
}
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_unidadespeso(key):
return util.get_sat_unidadespeso(key)
def get_sat_productos(key):
return util.get_sat_productos(key)
def get_title_app(by=1):
html = {
1: '<font color="#610B0B">{}</font>',
2: '<font color="#610B0B">Bienvenido a {}</font>',
3: '<font color="#000000">{}</font>',
}
return html[by].format(TITLE_APP)
def test_correo(values):
server = {
'servidor': values['correo_servidor'],
'puerto': values['correo_puerto'],
'ssl': util.get_bool(values['correo_ssl']),
'starttls': util.get_bool(values['correo_starttls']),
'usuario': values['correo_usuario'],
'contra': values['correo_contra'],
}
ccp = values.get('correo_copia', '')
options = {
'para': values['correo_usuario'],
'copia': ccp,
'confirmar': util.get_bool(values['correo_confirmacion']),
'asunto': values['correo_asunto'],
'mensaje': values['correo_mensaje'].replace('\n', '<br/>'),
'files': [],
}
data= {
'server': server,
'options': options,
}
return util.send_mail(data)
def _init_values(rfc):
data = (
{'clave': 'version', 'valor': VERSION},
{'clave': 'migracion', 'valor': '0'},
{'clave': 'rfc_publico', 'valor': RFCS['PUBLIC']},
{'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.upper())
log.info('Valores iniciales insertados...')
return
def _crear_tablas(rfc):
tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion,
Folios, Registro, CamposPersonalizados, Permisos,
Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos,
FacturasRelacionadas, FacturasComplementos, FacturasPersonalizados,
SeriesProductos, Almacenes, Productos, RangosPrecios, Sucursales,
PreFacturas, PreFacturasDetalle, PreFacturasImpuestos,
PreFacturasRelacionadas, Tickets, TicketsDetalle, TicketsImpuestos,
SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes,
SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos,
SATNivelesEducativos, SATEstados, SATRiesgoPuesto, SATPeriodicidadPago,
SATOrigenRecurso, SATTipoContrato, SATTipoDeduccion, SATTipoHoras,
SATTipoIncapacidad, SATTipoJornada, SATTipoNomina, SATTipoOtroPago,
SATTipoPercepcion, SATTipoRegimen, SATLeyendasFiscales,
Socios, SociosCuentasBanco, Contactos, ContactoCorreos,
ContactoDirecciones, Empleados, ContactoTelefonos, Departamentos,
Puestos,
Tags, Roles, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco,
TipoCorreo, TipoDireccion, TipoPariente, TipoResponsable, TipoTelefono,
TipoTitulo, TipoMovimientoAlumno, TipoMovimientoAlmacen,
CfdiPagos, NivelesEducativos, Alumnos, AlumnosParientes, Grupos,
ParienteDirecciones, ParienteTelefonos, ParienteCorreos,
CfdiNomina, CfdiNominaDeducciones, CfdiNominaDetalle,
CfdiNominaHorasExtra, CfdiNominaIncapacidad, CfdiNominaJubilacion,
CfdiNominaOtroPago, CfdiNominaOtros, CfdiNominaPercepciones,
CfdiNominaRelacionados, CfdiNominaSeparacion, CfdiNominaSubcontratos,
CfdiNominaTotales,
Emisor.regimenes.get_through_model(),
Socios.tags.get_through_model(),
Productos.impuestos.get_through_model(),
Productos.tags.get_through_model(),
PartnerProducts,
InventoryEntries,
PartnerInvoices,
WareHouseProduct,
SATUnidadesPeso,
SociosRegimenes,
]
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(rfc=''):
from playhouse.migrate import PostgresqlMigrator, migrate
if rfc:
rfc = rfc.strip().upper()
else:
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)
tablas = [Sucursales, SATEstados, SATRiesgoPuesto, SATPeriodicidadPago,
SATOrigenRecurso, SATTipoContrato, SATTipoDeduccion, SATTipoHoras,
SATTipoIncapacidad, SATTipoJornada, SATTipoNomina, SATTipoOtroPago,
SATTipoPercepcion, SATTipoRegimen, Departamentos, Puestos, Empleados,
CfdiNomina, CfdiNominaDeducciones, CfdiNominaDetalle,
CfdiNominaHorasExtra, CfdiNominaIncapacidad, CfdiNominaJubilacion,
CfdiNominaOtroPago, CfdiNominaOtros, CfdiNominaPercepciones,
CfdiNominaRelacionados, CfdiNominaSeparacion, CfdiNominaSubcontratos,
CfdiNominaTotales, SATNivelesEducativos, Roles, Permisos,
SociosCuentasBanco, SATLeyendasFiscales,
PartnerProducts,
InventoryEntries,
PartnerInvoices,
WareHouseProduct,
SATUnidadesPeso,
SociosRegimenes,
]
log.info('Creando tablas nuevas...')
database_proxy.create_tables(tablas, True)
log.info('Tablas creadas correctamente...')
log.info('Iniciando migraciĆ³n de tablas...')
migrations = []
migrator = PostgresqlMigrator(database_proxy)
columns = [c.name for c in database_proxy.get_columns('usuarios')]
if not 'sucursal_id' in columns:
sucursal = ForeignKeyField(Sucursales, null=True, to_field=Sucursales.id)
migrations.append(
migrator.add_column('usuarios', 'sucursal_id', sucursal))
if not 'rol_id' in columns:
rol = ForeignKeyField(Roles, null=True, to_field=Roles.id)
migrations.append(
migrator.add_column('usuarios', 'rol_id', rol))
columns = [c.name for c in database_proxy.get_columns('emisor')]
if not 'registro_patronal' in columns:
registro_patronal = TextField(default='')
migrations.append(
migrator.add_column(
'emisor', 'registro_patronal', registro_patronal))
if not 'curp' in columns:
curp = TextField(default='')
migrations.append(
migrator.add_column('emisor', 'curp', curp))
columns = [c.name for c in database_proxy.get_columns('socios')]
if not 'id_fiscal' in columns:
id_fiscal = TextField(default='')
migrations.append(
migrator.add_column('socios', 'id_fiscal', id_fiscal))
if not 'plantilla' in columns:
plantilla = TextField(default='')
migrations.append(
migrator.add_column('socios', 'plantilla', plantilla))
if not 'correo_facturasp' in columns:
correo_facturasp = TextField(default='')
migrations.append(
migrator.add_column('socios', 'correo_facturasp', correo_facturasp))
if not 'regimen_fiscal' in columns:
regimen_fiscal = TextField(default='')
migrations.append(
migrator.add_column('socios', 'regimen_fiscal', regimen_fiscal))
columns = [c.name for c in database_proxy.get_columns('folios')]
if not 'plantilla' in columns:
plantilla = TextField(default='')
migrations.append(
migrator.add_column('folios', 'plantilla', plantilla))
columns = [c.name for c in database_proxy.get_columns('prefacturasdetalle')]
if not 'unidad' in columns:
unidad = TextField(default='')
migrations.append(
migrator.add_column('prefacturasdetalle', 'unidad', unidad))
columns = [c.name for c in database_proxy.get_columns('facturaspagos')]
if 'cancelado' in columns:
migrations.append(migrator.drop_column('facturaspagos', 'cancelado'))
if not 'tipo_cambio' in columns:
tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6,
auto_round=True)
migrations.append(migrator.add_column('facturaspagos', 'tipo_cambio', tipo_cambio))
columns = [c.name for c in database_proxy.get_columns('certificado')]
if not 'es_fiel' in columns:
es_fiel = BooleanField(default=False)
migrations.append(migrator.add_column('certificado', 'es_fiel', es_fiel))
columns = [c.name for c in database_proxy.get_columns('cfdipagos')]
if not 'serie' in columns:
socio = ForeignKeyField(Socios, null=True, to_field=Socios.id)
serie = TextField(default='')
folio = IntegerField(default=0)
lugar_expedicion = TextField(default='')
regimen_fiscal = TextField(default='')
tipo_comprobante = TextField(default=DEFAULT_CFDIPAY['TYPE'])
error = TextField(default='')
tipo_relacion = TextField(default='')
uuid_relacionado = UUIDField(null=True)
cancelada = BooleanField(default=False)
migrations.append(migrator.add_column('cfdipagos', 'serie', serie))
migrations.append(migrator.add_column('cfdipagos', 'folio', folio))
migrations.append(migrator.add_column('cfdipagos', 'lugar_expedicion', lugar_expedicion))
migrations.append(migrator.add_column('cfdipagos', 'regimen_fiscal', regimen_fiscal))
migrations.append(migrator.add_column('cfdipagos', 'tipo_comprobante', tipo_comprobante))
migrations.append(migrator.add_column('cfdipagos', 'error', error))
migrations.append(migrator.add_column('cfdipagos', 'tipo_relacion', tipo_relacion))
migrations.append(migrator.add_column('cfdipagos', 'uuid_relacionado', uuid_relacionado))
migrations.append(migrator.add_column('cfdipagos', 'socio_id', socio))
migrations.append(migrator.drop_column('cfdipagos', 'cancelado'))
migrations.append(migrator.add_column('cfdipagos', 'cancelada', cancelada))
if not 'receptor_regimen' in columns:
receptor_regimen = TextField(default='')
migrations.append(migrator.add_column('cfdipagos', 'receptor_regimen', receptor_regimen))
if not 'fecha_cancelacion' in columns:
fecha_cancelacion = DateTimeField(null=True)
migrations.append(migrator.add_column('cfdipagos', 'fecha_cancelacion', fecha_cancelacion))
table = 'movimientosbanco'
columns = [c.name for c in database_proxy.get_columns(table)]
if not 'cuenta_socio_id' in columns:
cuenta_socio = ForeignKeyField(SociosCuentasBanco, null=True, to_field=SociosCuentasBanco.id)
migrations.append(migrator.add_column(table, 'cuenta_socio_id', cuenta_socio))
migrations.append(migrator.drop_column(table, 'origen_rfc'))
migrations.append(migrator.drop_column(table, 'origen_nombre'))
migrations.append(migrator.drop_column(table, 'origen_cuenta'))
migrations.append(migrator.drop_column(table, 'destino_rfc'))
migrations.append(migrator.drop_column(table, 'destino_cuenta'))
table = 'socioscuentasbanco'
columns = [c.name for c in database_proxy.get_columns(table)]
if not 'activa' in columns:
activa = BooleanField(default=True)
migrations.append(migrator.add_column(table, 'activa', activa))
table = 'productos'
columns = [c.name for c in database_proxy.get_columns(table)]
if not 'cantidad_empaque' in columns:
cantidad_empaque = DecimalField(default=0.0, max_digits=14,
decimal_places=4, auto_round=True)
migrations.append(migrator.add_column(
table, 'cantidad_empaque', cantidad_empaque))
if not 'is_discontinued' in columns:
is_discontinued = BooleanField(default=False)
migrations.append(migrator.add_column(
table, 'is_discontinued', is_discontinued))
if not 'objeto_impuesto' in columns:
objeto_impuesto = TextField(default='02')
migrations.append(migrator.add_column(table, 'objeto_impuesto', objeto_impuesto))
if 'almacen_id' in columns:
migrations.append(migrator.drop_column(table, 'almacen_id'))
table = 'facturasdetalle'
columns = [c.name for c in database_proxy.get_columns(table)]
if not 'empaques' in columns:
empaques = DecimalField(default=0.0, max_digits=14,
decimal_places=4, auto_round=True)
migrations.append(migrator.add_column(
table, 'empaques', empaques))
table = 'facturas'
columns = [c.name for c in database_proxy.get_columns(table)]
if not 'divisas' in columns:
divisas = TextField(default='')
migrations.append(migrator.add_column(table, 'divisas', divisas))
if not 'exportacion' in columns:
new_field = TextField(default='01')
migrations.append(migrator.add_column(table, 'exportacion', new_field))
if not 'receptor_regimen' in columns:
receptor_regimen = TextField(default='')
migrations.append(migrator.add_column(table, 'receptor_regimen', receptor_regimen))
if not 'periodicidad' in columns:
periodicidad = TextField(default='')
migrations.append(migrator.add_column(table, 'periodicidad', periodicidad))
table = 'almacenes'
columns = [c.name for c in database_proxy.get_columns(table)]
if 'nombre' in columns:
name = TextField(unique=True, default='')
migrations.append(migrator.drop_column(table, 'nombre'))
migrations.append(migrator.add_column(table, 'name', name))
table = 'sucursales'
columns = [c.name for c in database_proxy.get_columns(table)]
if not 'warehouse_id' in columns:
warehouse = ForeignKeyField(Almacenes, null=True, to_field=Almacenes.id)
migrations.append(migrator.add_column(table, 'warehouse_id', warehouse))
table = 'facturasdetalle'
columns = [c.name for c in database_proxy.get_columns(table)]
if not 'warehouse_id' in columns:
warehouse = ForeignKeyField(Almacenes, null=True, to_field=Almacenes.id)
migrations.append(migrator.add_column(table, 'warehouse_id', warehouse))
table = 'ticketsdetalle'
field = 'warehouse_id'
columns = [c.name for c in database_proxy.get_columns(table)]
if not field in columns:
warehouse = ForeignKeyField(Almacenes, null=True, to_field=Almacenes.id)
migrations.append(migrator.add_column(table, field, warehouse))
if migrations:
with database_proxy.atomic() as txn:
migrate(*migrations)
Configuracion.add({'version': VERSION})
log.info('Tablas migradas correctamente...')
_importar_valores('', rfc)
log.info('Actualizando valores...')
try:
q = SATRegimenes.update(**{'activo': True}).where(SATRegimenes.key=='616')
q.execute()
except Exception as e:
log.error(e)
else:
log.info('Valores actualizados...')
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 _borrar_rfc():
rfc = input('Introduce el RFC a borrar: ').strip().lower()
if not rfc:
msg = 'El RFC es requerido'
log.error(msg)
return
confirm = input('ĀæEstĆ”s seguro de borrar el RFC? [si]')
if confirm != 'si':
log.info('Proceso cancelado...')
return
if _delete_emisor(rfc.upper()):
utils.db_delete(rfc, PATHS['BK'])
log.info('RFC borrado correctamente...')
return
log.error('No se pudo borrar el RFC')
return
def _listar_rfc(detalle):
data = util.get_rfcs()
for row in data:
if detalle:
msg = 'RFC: {}\n\t{}'.format(row[0], row[1])
else:
msg = 'RFC: {}'.format(row[0])
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, no_bd):
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 no_bd:
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 no_bd:
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 no_bd:
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):
utils.db_delete(rfc.lower(), PATHS['BK'])
return True
def respaldar_dbs():
if not MV:
msg = 'Solo MV'
return {'ok': False, 'msg': msg}
result = utils.db_backup_local()
return result
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:
pass
log.info('ImportaciĆ³n terminada...')
return
def _import_from_folder(path):
files = util.get_files(path, 'json')
if not files:
msg = 'No se encontraron archivos para importar'
log.error(msg)
return
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 valores...')
for p in files:
msg = '\tImportando tabla: {}'
data = util.import_json(p)
log.info(msg.format(data['tabla']))
table = globals()[data['tabla']]
for r in data['datos']:
try:
with database_proxy.atomic() as txn:
table.create(**r)
except IntegrityError:
pass
log.info('Valores importados...')
return
def _export_documents():
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('Exportar documentos...')
n = util.now()
year = input('Introduce el aƱo [{}]: '.format(n.year)).strip()
if not year:
year = str(n.year)
month = input('Introduce el mes [{}]: '.format(n.month)).strip()
if not month:
month = str(n.month)
without_stamp = []
with_error = []
filters = {
'year': year,
'month': month,
}
result = Facturas.get_(filters)
if result['ok']:
t = len(result['rows'])
for i, row in enumerate(result['rows']):
msg = 'Extrayendo factura {} de {}: {}-{}'.format(
i+1, t, row['serie'], row['folio'])
log.info(msg)
_, name = Facturas.get_xml(row['id'])
if not row['uuid']:
without_stamp.append(f"{row['serie']}-{row['folio']}")
msg = '\tXML extraido...'
log.info(msg)
name = name[:-3] + 'pdf'
path = '/home/mau/facturas/{}/{}/{}/{}'.format(
rfc, year, month.zfill(2), name)
if util.exists(path):
continue
try:
Facturas.get_pdf(row['id'], rfc, True)
except Exception as e:
with_error.append(f"{row['serie']}-{row['folio']}")
print(e)
msg = '\tPDF generado...'
log.info(msg)
log.info('Documentos exportados...')
log.info('Sin sellar...')
for i in without_stamp:
log.info(i)
log.info('Con error...')
for i in with_error:
log.info(i)
return
# ~ v2
def companies_get():
data = utils.rfc_get()
rows = []
for row in data:
rows.append({'delete': '-', 'rfc': row[0].upper()})
return tuple(rows)
def _list_clients():
rows = utils.rfc_get()
for row in rows:
msg = f'RFC: {row[0].upper()}'
print(msg)
return
def _new_client(rfc, no_database):
rfc = rfc.lower()
if not rfc:
log.error('Falta el RFC')
return
if utils.rfc_exists(rfc):
msg = 'El RFC ya esta dado de alta'
log.error(msg)
return {'ok': False, 'msg': msg}
user = rfc.replace('&', '').lower()
if not no_database:
if not utils.db_create(user):
msg = 'No se pudo crear la base de datos'
log.error(msg)
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'
log.error(msg)
return {'ok': False, 'msg': msg}
if not utils.rfc_add(rfc, args):
msg = 'No se pudo guardar el nuevo emisor'
log.error(msg)
return {'ok': False, 'msg': msg}
if not no_database:
if not _crear_tablas(rfc):
msg = 'No se pudo crear las tablas'
log.error(msg)
return {'ok': False, 'msg': msg}
desconectar()
msg = 'Emisor dado de alta correctamente'
row = {'delete': '-', 'rfc': rfc.upper()}
result = {'ok': True, 'msg': msg, 'row': row}
return result
def _delete_client(rfc, no_database, ask=True):
rfc = rfc.lower()
if not rfc:
log.error('Falta el RFC')
return
if not utils.rfc_exists(rfc):
msg = 'El RFC no esta dado de alta'
log.error(msg)
return {'ok': False, 'msg': msg}
if ask:
confirm = input('ĀæEstĆ”s seguro de borrar el RFC? [si]')
if confirm != 'si':
log.info('Proceso cancelado...')
return False
if utils.db_delete(rfc, PATHS['BK'], no_database):
log.info('RFC borrado correctamente...')
result = True
else:
log.error('No se pudo borrar el RFC')
result = False
return result
def _update_sat():
clients = utils.rfc_get()
tables = utils.json_loads(PATHS['SAT'])
for rfc, con in clients:
log.info(f'Importando datos en: {rfc.upper()}')
conectar(utils.loads(con))
for table in tables:
t = globals()[table['tabla']]
with database_proxy.atomic():
for r in table['datos']:
try:
t.get_or_create(**r)
except:
pass
log.info(f"\tTabla importada: {table['tabla']}")
desconectar()
log.info('ImportaciĆ³n terminada...')
return
def _new_superuser(rfc):
rfc = rfc.lower()
if not rfc:
log.error('Falta el RFC')
return
if not utils.rfc_exists(rfc):
msg = 'El RFC no esta dado de alta'
log.error(msg)
return
args = utils.get_data_con(rfc)
user = input('Introduce el nuevo nombre para el superusuario: ').strip()
if not user:
msg = 'El nombre de usuario es requerido'
log.error(msg)
return
ok, password = utils.get_pass()
if not ok:
log.error(password)
return
conectar(args)
try:
obj = Usuarios.create(
usuario=user, contraseƱa=password, es_superusuario=True)
msg = 'SuperUsuario creado correctamente...'
log.info(msg)
except IntegrityError:
msg = 'El usuario ya existe'
log.error(msg)
desconectar()
return
def _change_pass(rfc):
rfc = rfc.lower()
if not rfc:
log.error('Falta el RFC')
return
if not utils.rfc_exists(rfc):
msg = 'El RFC no esta dado de alta'
log.error(msg)
return
args = utils.get_data_con(rfc)
conectar(args)
user = input('Introduce el nombre de usuario: ').strip()
if not user:
msg = 'El nombre de usuario es requerido'
log.error(msg)
desconectar()
return
try:
obj = Usuarios.get(usuario=user)
except Usuarios.DoesNotExist:
msg = 'El usuario no existe'
log.error(msg)
desconectar()
return
ok, password = utils.get_pass()
if not ok:
log.error(password)
desconectar()
return
obj.contraseƱa = password
obj.save()
desconectar()
log.info('ContraseƱa cambiada correctamente...')
return
def _migrate_cert(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)
log.info('Exportando certificados...')
auth = Emisor.get_auth()
data = {
'lst_pac': 'finkok',
'user_timbrado_finkok': auth['USER'],
'token_timbrado_finkok': auth['PASS'],
}
Configuracion.add(data)
obj = Certificado.get(Certificado.es_fiel==False)
cert = utils.SATCertificate
cert.save_cert(obj)
desconectar()
log.info('Proceso terminado correctamente...')
return
def _import_clients(rfc, path):
if not rfc:
msg = 'El RFC es requerido'
log.error(msg)
return
if not path:
msg = 'El archivo CSV es necesario'
log.error(msg)
return
args = util.get_con(rfc)
if not args:
return
conectar(args)
log.info('Importando clientes...')
data = utils.read_csv(path)
t = len(data)
ok = 0
for i, row in enumerate(data):
msg = f'\tImportando cliente {i+1} de {t}'
log.info(msg)
row['rfc'] = row['rfc'].upper()
row['nombre'] = utils.spaces(row['nombre'])
row['slug'] = utils.to_slug(row['nombre'])
w = ((Socios.rfc==row['rfc']) & (Socios.slug==row['slug']))
if Socios.select().where(w).exists():
msg = '\tYa existe el RFC y RazĆ³n Social'
log.info(msg)
continue
row['es_cliente'] = True
row['forma_pago'] = SATFormaPago.get(SATFormaPago.id==row['forma_pago'])
try:
obj = Socios.create(**row)
ok += 1
except Exception as e:
log.error(e)
break
desconectar()
msg = f'Total de clientes: {t}'
log.info(msg)
msg = f'Clientes nuevos: {ok}'
log.info(msg)
log.info('Proceso terminado correctamente...')
return
def _import_stock(rfc, path):
if not rfc:
msg = 'El RFC es requerido'
log.error(msg)
return
if not path:
msg = 'El archivo CSV es necesario'
log.error(msg)
return
args = util.get_con(rfc)
if not args:
return
conectar(args)
log.info('Importando existencias...')
data = utils.read_csv(path)
t = len(data)
ok = 0
for i, row in enumerate(data):
msg = f'\tImportando existencia {i+1} de {t}'
log.info(msg)
cant = Decimal(row['cantidad'])
if cant <= 0:
continue
storage = int(row['almacen'])
product = Productos.get(Productos.clave==row['clave'])
if not product.inventario:
msg = 'Este producto no lleva inventario.'
log.debug(msg)
continue
values = dict(
storage = storage,
product = product,
cant = cant,
)
obj = InventoryEntries.create(**values)
WareHouseProduct.update_exists(values)
new_cant = product.existencia + cant
q = (Productos
.update(existencia=Productos.existencia + cant)
.where(Productos.id==product.id)
)
q.execute()
desconectar()
msg = f'Total de productos: {t}'
log.info(msg)
log.info('Proceso terminado correctamente...')
return
def _process_command_line_arguments():
parser = argparse.ArgumentParser(
description='Empresa Libre')
parser.add_argument('-lc', '--list-clients', dest='list_clients',
action='store_true', default=False, required=False)
parser.add_argument('-nc', '--new-client', dest='new_client',
action='store_true', default=False, required=False)
parser.add_argument('-dc', '--delete-client', dest='delete_client',
action='store_true', default=False, required=False)
parser.add_argument('-ndb', '--no-database', dest='no_database',
action='store_true', default=False, required=False)
parser.add_argument('-m', '--migrate', dest='migrate',
action='store_true', default=False, required=False)
parser.add_argument('-us', '--update-sat', dest='update_sat',
action='store_true', default=False, required=False)
parser.add_argument('-ns', '--new-superuser', dest='new_superuser',
action='store_true', default=False, required=False)
parser.add_argument('-cp', '--change-pass', dest='change_pass',
action='store_true', default=False, required=False)
parser.add_argument('-bk', '--backup', dest='backup',
action='store_true', default=False, required=False)
parser.add_argument('-ed', '--export-documents', dest='export_documents',
action='store_true', default=False, required=False)
parser.add_argument('-ic', '--import-clients', dest='import_clients',
action='store_true', default=False, required=False)
parser.add_argument('-is', '--import-stock', dest='import_stock',
action='store_true', default=False, required=False)
parser.add_argument('-mc' , '--migrate-cert', dest='migrate_cert',
action='store_true', default=False, required=False)
parser.add_argument('-r', '--rfc', dest='rfc', default='')
parser.add_argument('-f', '--file', dest='file', default='')
return parser.parse_args()
def main(args):
if args.list_clients:
_list_clients()
return
if args.new_client:
_new_client(args.rfc, args.no_database)
return
if args.delete_client:
_delete_client(args.rfc, args.no_database)
return
if args.migrate:
_migrate_tables(args.rfc)
return
if args.update_sat:
_update_sat()
return
if args.new_superuser:
_new_superuser(args.rfc)
return
if args.change_pass:
_change_pass(args.rfc)
return
if args.backup:
utils.db_backup(IS_MV, URL['SEAFILE'])
return
if args.export_documents:
_export_documents()
return
if args.migrate_cert:
_migrate_cert(args.rfc)
return
if args.import_clients:
_import_clients(args.rfc, args.file)
return
if args.import_stock:
_import_stock(args.rfc, args.file)
return
return
if __name__ == '__main__':
args = _process_command_line_arguments()
main(args)