10598 lines
343 KiB
Python
10598 lines
343 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# ~ Empresa Libre
|
|
# ~ Copyright (C) 2016-2018 Mauricio Baeza Servin (web@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,
|
|
DEFAULT_GLOBAL,
|
|
DB_COMPANIES,
|
|
EXT,
|
|
IS_MV,
|
|
MXN,
|
|
PATHS,
|
|
URL,
|
|
VALUES_PDF,
|
|
VERSION,
|
|
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
|
|
|
|
|
|
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)
|
|
|
|
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_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_use_packing',
|
|
)
|
|
data = (Configuracion
|
|
.select()
|
|
.where(Configuracion.clave.in_(fields))
|
|
)
|
|
values = {r.clave: util.get_bool(r.valor) for r in data}
|
|
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',
|
|
)
|
|
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_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_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
|
|
|
|
@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',
|
|
)
|
|
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',
|
|
)
|
|
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 Sucursales(BaseModel):
|
|
nombre = TextField(default='')
|
|
direccion = TextField(default='')
|
|
serie_facturas = TextField(default='')
|
|
serie_tickets = TextField(default='')
|
|
|
|
class Meta:
|
|
order_by = ('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 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')
|
|
|
|
@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 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)
|
|
|
|
|
|
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,
|
|
# ~ 'correo_timbrado': obj.correo_timbrado,
|
|
# ~ 'token_timbrado': obj.token_timbrado,
|
|
'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)
|
|
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_enc = cert.key_enc
|
|
obj.cer = cert.cer
|
|
obj.serie = 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_(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_all(cls):
|
|
rows = (Categorias.select(
|
|
Categorias.id,
|
|
Categorias.categoria.alias('value'),
|
|
Categorias.padre.alias('parent_id'))
|
|
).dicts()
|
|
for row in rows:
|
|
if row['parent_id'] is None:
|
|
row['parent_id'] = 0
|
|
return tuple(rows)
|
|
|
|
|
|
class CondicionesPago(BaseModel):
|
|
condicion = TextField(unique=True)
|
|
|
|
class Meta:
|
|
order_by = ('condicion',)
|
|
|
|
def __str__(self):
|
|
return self.condicion
|
|
|
|
@classmethod
|
|
def get_(cls):
|
|
q = CondicionesPago.select(CondicionesPago.condicion).tuples()
|
|
data = [r[0] for r in q]
|
|
return data
|
|
|
|
@classmethod
|
|
def get_or(cls, value):
|
|
if value is None:
|
|
return value
|
|
obj, _ = CondicionesPago.get_or_create(condicion=value)
|
|
return obj
|
|
|
|
|
|
class SATUnidades(BaseModel):
|
|
key = TextField(unique=True, index=True)
|
|
name = TextField(default='', index=True)
|
|
activo = BooleanField(default=False)
|
|
default = BooleanField(default=False)
|
|
|
|
class Meta:
|
|
order_by = ('-default', 'name')
|
|
indexes = (
|
|
(('key', 'name'), True),
|
|
)
|
|
|
|
def __str__(self):
|
|
return '{} ({})'.format(self.name, self.key)
|
|
|
|
@classmethod
|
|
def get_by_name(self, name):
|
|
try:
|
|
return SATUnidades.get(SATUnidades.name==name)
|
|
except:
|
|
return None
|
|
|
|
@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 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='')
|
|
|
|
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()
|
|
|
|
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')
|
|
return row
|
|
|
|
#~ return {'data': data['rows'][:100], 'pos':0, 'total_count': 1300}
|
|
#~ start = 0
|
|
#~ count = 0
|
|
#~ end = 100
|
|
#~ if values:
|
|
#~ {'start': '100', 'count': '100', 'continue': 'true'}
|
|
#~ start = int(values['start'])
|
|
#~ cont = int(values['count'])
|
|
#~ end = start + count
|
|
|
|
total = Socios.select().count()
|
|
|
|
rows = (Socios
|
|
.select(
|
|
Socios.id,
|
|
Socios.rfc,
|
|
Socios.nombre,
|
|
Socios.saldo_cliente)
|
|
.dicts()
|
|
)
|
|
return {'pos': 0, 'total_count': total, 'data': tuple(rows)}
|
|
|
|
@classmethod
|
|
def get_by_client(cls, values):
|
|
id = int(values.get('id', 0))
|
|
if id:
|
|
row = (Socios
|
|
.select(
|
|
Socios.id, Socios.nombre, Socios.rfc,
|
|
SATFormaPago.key.alias('forma_pago'),
|
|
SATUsoCfdi.key.alias('uso_cfdi'))
|
|
.join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios)
|
|
.join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios)
|
|
.where((Socios.id==id) & (Socios.es_cliente==True))
|
|
.dicts()
|
|
)
|
|
if len(row):
|
|
return {'ok': True, 'row': row[0]}
|
|
return {'ok': False}
|
|
|
|
name = values.get('name', '')
|
|
if name:
|
|
rows = (Socios
|
|
.select(Socios.id, Socios.nombre, Socios.rfc,
|
|
SATFormaPago.key.alias('forma_pago'),
|
|
SATUsoCfdi.key.alias('uso_cfdi'))
|
|
.join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios)
|
|
.join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios)
|
|
.where((Socios.es_cliente==True & Socios.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)
|
|
|
|
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
|
|
|
|
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', '')
|
|
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)
|
|
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 = 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}
|
|
|
|
|
|
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 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):
|
|
nombre = TextField(default='')
|
|
ubicacion = TextField(default='')
|
|
|
|
class Meta:
|
|
order_by = ('nombre',)
|
|
|
|
|
|
class Productos(BaseModel):
|
|
almacen = ForeignKeyField(Almacenes, null=True)
|
|
categoria = ForeignKeyField(Categorias, null=True)
|
|
clave = TextField(unique=True, index=True)
|
|
clave_sat = TextField(default='')
|
|
descripcion = TextField(index=True)
|
|
unidad = ForeignKeyField(SATUnidades)
|
|
valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
ultimo_costo = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
inventario = BooleanField(default=False)
|
|
existencia = DecimalField(default=0.0, max_digits=18, decimal_places=2,
|
|
auto_round=True)
|
|
minimo = DecimalField(default=0.0, max_digits=18, decimal_places=2,
|
|
auto_round=True)
|
|
codigo_barras = TextField(default='')
|
|
cuenta_predial = TextField(default='')
|
|
es_activo = BooleanField(default=True)
|
|
impuestos = ManyToManyField(SATImpuestos, related_name='productos')
|
|
tags = ManyToManyField(Tags, related_name='productos_tags')
|
|
cantidad_empaque = DecimalField(default=0.0, max_digits=14, decimal_places=4,
|
|
auto_round=True)
|
|
|
|
class Meta:
|
|
order_by = ('descripcion',)
|
|
|
|
@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)
|
|
.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())
|
|
return {'ok': True, 'row': row[0], '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.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
|
|
|
|
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 RangosPrecios(BaseModel):
|
|
producto = ForeignKeyField(Productos)
|
|
descripcion = TextField(default='')
|
|
desde = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
hasta = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6,
|
|
auto_round=True)
|
|
descuento = IntegerField(default=0)
|
|
|
|
class Meta:
|
|
order_by = ('producto',)
|
|
|
|
|
|
class Facturas(BaseModel):
|
|
cliente = ForeignKeyField(Socios)
|
|
version = TextField(default=CURRENT_CFDI)
|
|
serie = TextField(default='')
|
|
folio = IntegerField(default=0)
|
|
fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S'])
|
|
fecha_timbrado = DateTimeField(null=True)
|
|
forma_pago = TextField(default='')
|
|
condiciones_pago = TextField(default='')
|
|
subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
moneda = TextField(default='MXN')
|
|
tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6,
|
|
auto_round=True)
|
|
total = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6,
|
|
auto_round=True)
|
|
tipo_comprobante = TextField(default='I')
|
|
metodo_pago = TextField(default='PUE')
|
|
lugar_expedicion = TextField(default='')
|
|
confirmacion = TextField(default='')
|
|
uso_cfdi = TextField(default='')
|
|
total_retenciones = DecimalField(
|
|
max_digits=20, decimal_places=6, auto_round=True, null=True)
|
|
total_trasladados = DecimalField(
|
|
max_digits=20, decimal_places=6, auto_round=True, null=True)
|
|
xml = TextField(default='')
|
|
uuid = UUIDField(null=True)
|
|
estatus = TextField(default='Guardada')
|
|
estatus_sat = TextField(default='Vigente')
|
|
regimen_fiscal = TextField(default='')
|
|
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='')
|
|
|
|
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, id):
|
|
obj = Facturas.get(Facturas.id==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)
|
|
|
|
@classmethod
|
|
def _cancel_xml_sign(cls, invoice):
|
|
if invoice.version != '3.3':
|
|
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, 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}
|
|
|
|
# ~ pac = Configuracion.get_('lst_pac').lower()
|
|
# ~ if pac:
|
|
# ~ data, result = utils.xml_cancel(obj.xml, auth, certificado, pac)
|
|
# ~ else:
|
|
data, result = util.cancel_xml(auth, obj.uuid, certificado)
|
|
|
|
if data['ok']:
|
|
obj.estatus = 'Cancelada'
|
|
obj.error = ''
|
|
obj.cancelada = True
|
|
obj.fecha_cancelacion = result['Fecha']
|
|
obj.acuse = result['Acuse'] 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 _cancel_signature(self, id):
|
|
# ~ msg = 'Factura cancelada correctamente'
|
|
# ~ auth = Emisor.get_auth()
|
|
# ~ certificado = Certificado.select()[0]
|
|
# ~ obj = Facturas.get(Facturas.id==id)
|
|
# ~ data, result = util.cancel_signature(
|
|
# ~ obj.uuid, certificado.p12, certificado.rfc, auth)
|
|
# ~ if data['ok']:
|
|
# ~ obj.estatus = 'Cancelada'
|
|
# ~ obj.error = ''
|
|
# ~ obj.cancelada = True
|
|
# ~ obj.fecha_cancelacion = result['Fecha']
|
|
# ~ obj.acuse = result['Acuse']
|
|
# ~ self._actualizar_saldo_cliente(self, obj, True)
|
|
# ~ else:
|
|
# ~ obj.error = data['msg']
|
|
# ~ obj.save()
|
|
# ~ return data
|
|
|
|
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.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 = {'version': VERSION}
|
|
|
|
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)
|
|
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 = 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 = util.get_qr(qr_data, False)
|
|
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)
|
|
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)
|
|
server = {
|
|
'servidor': values['correo_servidor'],
|
|
'puerto': values['correo_puerto'],
|
|
'ssl': bool(int(values['correo_ssl'])),
|
|
'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,
|
|
'notas': obj.notas,
|
|
}
|
|
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()
|
|
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):
|
|
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
|
|
|
|
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()
|
|
|
|
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)
|
|
total_mn = round(total * invoice.tipo_cambio, DECIMALES)
|
|
data = {
|
|
'subtotal': subtotal,
|
|
'descuento': descuento_cfdi,
|
|
'total': total,
|
|
'total_mn': total_mn,
|
|
'total_trasladados': total_trasladados,
|
|
'total_retenciones': total_retenciones,
|
|
}
|
|
|
|
return data
|
|
|
|
def _guardar_relacionados(self, invoice, relacionados):
|
|
for cfdi in relacionados:
|
|
data = {
|
|
'factura': invoice,
|
|
'factura_origen': cfdi,
|
|
}
|
|
FacturasRelacionadas.create(**data)
|
|
return
|
|
|
|
def _guardar_ine(self, invoice, valores):
|
|
if not valores:
|
|
return
|
|
|
|
data = {
|
|
'factura': invoice,
|
|
'nombre': 'ine',
|
|
'valores': valores,
|
|
}
|
|
FacturasComplementos.create(**data)
|
|
return
|
|
|
|
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', {})
|
|
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'] = ''
|
|
|
|
with database_proxy.atomic() as txn:
|
|
obj = Facturas.create(**values)
|
|
totals = cls._calculate_totals(cls, obj, productos, tipo_comprobante)
|
|
cls._guardar_relacionados(cls, obj, relacionados)
|
|
cls._guardar_ine(cls, obj, ine)
|
|
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
|
|
|
|
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['Certificado'] = certificado.cer_txt
|
|
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.00'
|
|
comprobante['Total'] = '0.00'
|
|
del comprobante['FormaPago']
|
|
|
|
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,
|
|
}
|
|
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:
|
|
if is_global:
|
|
impuesto = impuesto.impuesto
|
|
base = float(row.importe - row.descuento)
|
|
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
|
|
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)
|
|
else:
|
|
xml_importe = FORMAT.format(tax.importe)
|
|
|
|
if tax.impuesto.tipo == 'T':
|
|
traslado = {
|
|
"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,
|
|
}
|
|
|
|
# ~ return util.make_xml(data, certificado)
|
|
return utils.make_xml(data, certificado)
|
|
|
|
@classmethod
|
|
def get_status_sat(cls, id):
|
|
obj = Facturas.get(Facturas.id == id)
|
|
estatus_sat = util.get_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)
|
|
obj.xml = cls._make_xml(cls, obj)
|
|
obj.estatus = 'Generada'
|
|
obj.save()
|
|
|
|
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(obj.xml, auth)
|
|
|
|
if result['ok']:
|
|
obj.xml = result['xml']
|
|
obj.uuid = result['uuid']
|
|
obj.fecha_timbrado = result['date']
|
|
obj.estatus = 'Timbrada'
|
|
obj.error = ''
|
|
obj.save()
|
|
row = {'uuid': obj.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)
|
|
cls._sync(cls, id, rfc)
|
|
|
|
m = 'T {}'.format(obj.id)
|
|
_save_log(user.usuario, m, 'F')
|
|
else:
|
|
msg = result['error']
|
|
obj.estatus = 'Error'
|
|
obj.error = msg
|
|
obj.save()
|
|
row = {'estatus': 'Error'}
|
|
|
|
result = {
|
|
'ok': result['ok'],
|
|
'msg': msg,
|
|
'row': row,
|
|
'anticipo': anticipo
|
|
}
|
|
|
|
return result
|
|
|
|
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':
|
|
result = cls.cancel(args['id'])
|
|
if result['ok']:
|
|
m = 'C {}'.format(args['id'])
|
|
_save_log(user.usuario, m, 'F')
|
|
return result
|
|
|
|
return {'ok': False}
|
|
|
|
# ~ v2
|
|
@utils.run_in_thread
|
|
def _update_inventory(self, invoice, cancel=False):
|
|
if invoice.tipo_comprobante != 'I':
|
|
return
|
|
|
|
products = FacturasDetalle.get_by_invoice(invoice.id)
|
|
for p in products:
|
|
if p.producto.inventario:
|
|
if cancel:
|
|
p.producto.existencia += Decimal(p.cantidad)
|
|
else:
|
|
p.producto.existencia -= Decimal(p.cantidad)
|
|
p.producto.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)
|
|
server = {
|
|
'servidor': values['correo_servidor'],
|
|
'puerto': values['correo_puerto'],
|
|
'ssl': bool(int(values['correo_ssl'])),
|
|
'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)
|
|
.group_by(PreFacturas.fecha.year)
|
|
.order_by(PreFacturas.fecha.year)
|
|
.scalar(as_tuple=True)
|
|
)
|
|
if not rows is None:
|
|
data += [{'id': int(row), 'value': int(row)} for row in rows]
|
|
return tuple(data)
|
|
|
|
@classmethod
|
|
def get_(cls, values):
|
|
if values['year'] == '-1':
|
|
fy = (PreFacturas.fecha.year > 0)
|
|
else:
|
|
fy = (PreFacturas.fecha.year == int(values['year']))
|
|
if values['month'] == '-1':
|
|
fm = (PreFacturas.fecha.month > 0)
|
|
else:
|
|
fm = (PreFacturas.fecha.month == int(values['month']))
|
|
filters = (fy & fm)
|
|
|
|
rows = tuple(PreFacturas
|
|
.select(
|
|
PreFacturas.id,
|
|
PreFacturas.folio,
|
|
PreFacturas.fecha,
|
|
PreFacturas.tipo_comprobante,
|
|
PreFacturas.total_mn,
|
|
Socios.nombre.alias('cliente'))
|
|
.where(filters)
|
|
.join(Socios)
|
|
.switch(PreFacturas).dicts()
|
|
)
|
|
return {'ok': True, 'rows': rows}
|
|
|
|
def _get_folio(self, serie):
|
|
inicio = (PreFacturas
|
|
.select(fn.Max(PreFacturas.folio).alias('mf'))
|
|
.where(PreFacturas.serie==serie)
|
|
.order_by(SQL('mf'))
|
|
.scalar())
|
|
|
|
if inicio is None:
|
|
inicio = 1
|
|
else:
|
|
inicio += 1
|
|
|
|
return inicio
|
|
|
|
def _calculate_totals(self, invoice, products):
|
|
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)
|
|
|
|
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 reversed(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 reversed(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,
|
|
'notas': q.notas,
|
|
}
|
|
|
|
productos = PreFacturasDetalle.select().where(
|
|
PreFacturasDetalle.factura==id)
|
|
|
|
for p in reversed(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 reversed(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)
|
|
|
|
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'])
|
|
|
|
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
|
|
|
|
auth = Configuracion.get_({'fields': 'pac_auth'})
|
|
certificado = Certificado.get(Certificado.es_fiel==False)
|
|
result = utils.cancel_xml_sign(last, auth, certificado)
|
|
if result['ok']:
|
|
last.estatus = 'Cancelada'
|
|
last.error = ''
|
|
last.cancelada = True
|
|
last.fecha_cancelacion = result['Fecha']
|
|
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
|
|
regimen_fiscal = related[0].factura.regimen_fiscal
|
|
|
|
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
|
|
|
|
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_related_xml(self, id_mov, currency):
|
|
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())
|
|
|
|
for r in related:
|
|
# ~ 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']
|
|
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']
|
|
|
|
return related
|
|
|
|
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['Certificado'] = cert.cer_txt
|
|
cfdi['SubTotal'] = '0'
|
|
cfdi['Moneda'] = DEFAULT_CFDIPAY['CURRENCY']
|
|
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'],
|
|
}
|
|
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',
|
|
},)
|
|
|
|
impuestos = {}
|
|
|
|
mov = invoice.movimiento
|
|
currency = mov.moneda
|
|
related_docs = self._get_related_xml(self, invoice.movimiento, currency)
|
|
pagos = {
|
|
'FechaPago': mov.fecha.isoformat()[:19],
|
|
'FormaDePagoP': mov.forma_pago.key,
|
|
'MonedaP': currency,
|
|
'Monto': FORMAT.format(mov.deposito),
|
|
'relacionados': related_docs,
|
|
}
|
|
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)
|
|
|
|
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 = {}
|
|
|
|
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 = util.get_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)
|
|
server = {
|
|
'servidor': values['correo_servidor'],
|
|
'puerto': values['correo_puerto'],
|
|
'ssl': bool(int(values['correo_ssl'])),
|
|
'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):
|
|
subtotal = 0
|
|
descuento_cfdi = 0
|
|
totals_tax = {}
|
|
total_trasladados = None
|
|
|
|
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'])
|
|
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
|
|
|
|
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])
|
|
|
|
@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)
|
|
obj.subtotal = totals['subtotal']
|
|
obj.descuento = totals['descuento']
|
|
obj.total_trasladados = totals['total_trasladados']
|
|
obj.total = totals['total']
|
|
obj.save()
|
|
|
|
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))
|
|
|
|
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)
|
|
|
|
rows = (TicketsImpuestos
|
|
.select(
|
|
TicketsImpuestos.impuesto,
|
|
fn.Sum(TicketsImpuestos.base),
|
|
fn.Sum(TicketsImpuestos.importe))
|
|
.where(TicketsImpuestos.ticket.in_(ids))
|
|
.group_by(TicketsImpuestos.impuesto)
|
|
.order_by(TicketsImpuestos.impuesto)
|
|
)
|
|
for tax in rows:
|
|
invoice_tax = {
|
|
'factura': invoice.id,
|
|
'impuesto': tax.impuesto.id,
|
|
'base': tax.base,
|
|
'importe': tax.importe,
|
|
}
|
|
FacturasImpuestos.create(**invoice_tax)
|
|
|
|
if tax.impuesto.tipo == 'T' and tax.impuesto.key != '000':
|
|
total_trasladados = (total_trasladados or 0) + tax.importe
|
|
elif tax.impuesto.tipo == 'R' and tax.impuesto.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'])
|
|
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)
|
|
|
|
if client.forma_pago is None:
|
|
msg = 'La Forma de Pago del cliente, no esta asignada'
|
|
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'] = client.forma_pago.key
|
|
data['forma_pago'] = payment_type
|
|
data['tipo_cambio'] = 1.00
|
|
data['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal
|
|
if client.uso_cfdi is None:
|
|
data['uso_cfdi'] = 'P01'
|
|
else:
|
|
data['uso_cfdi'] = client.uso_cfdi.key
|
|
data['regimen_fiscal'] = emisor.regimenes[0].key
|
|
|
|
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):
|
|
id = int(values['id'])
|
|
msg = 'Ticket cancelado correctamente'
|
|
u = {'cancelado': True, 'estatus': 'Cancelado'}
|
|
with database_proxy.atomic() as txn:
|
|
obj = Tickets.update(**u).where(Tickets.id==id)
|
|
result = bool(obj.execute())
|
|
if result:
|
|
cls._update_inventory_if_cancel(cls, id)
|
|
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)
|
|
|
|
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
|
|
|
|
|
|
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',)
|
|
|
|
def _cancel(self, values, user):
|
|
id = int(values['id'])
|
|
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, auth, certificado)
|
|
|
|
if result['ok']:
|
|
obj.estatus = 'Cancelado'
|
|
obj.error = ''
|
|
obj.cancelada = True
|
|
obj.fecha_cancelacion = result['Fecha']
|
|
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"
|
|
server = {
|
|
'server': config['correo_servidor'],
|
|
'port': config['correo_puerto'],
|
|
'ssl': utils.to_bool(config['correo_ssl']),
|
|
'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)
|
|
|
|
if values['opt'] == 'dates':
|
|
dates = util.loads(values['range'])
|
|
filters = CfdiNomina.fecha.between(
|
|
util.get_date(dates['start']),
|
|
util.get_date(dates['end'], True)
|
|
)
|
|
return cls._get(cls, filters)
|
|
|
|
if values['opt'] == 'yearmonth':
|
|
if values['year'] == '-1':
|
|
fy = (CfdiNomina.fecha.year > 0)
|
|
else:
|
|
fy = (CfdiNomina.fecha.year == int(values['year']))
|
|
if values['month'] == '-1':
|
|
fm = (CfdiNomina.fecha.month > 0)
|
|
else:
|
|
fm = (CfdiNomina.fecha.month == int(values['month']))
|
|
filters = (fy & fm)
|
|
return cls._get(cls, filters)
|
|
|
|
if values['opt'] == 'import':
|
|
return cls._import(cls)
|
|
|
|
if values['opt'] == 'stamp':
|
|
return cls._stamp(cls)
|
|
|
|
@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}
|
|
|
|
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]
|
|
|
|
|
|
@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']),
|
|
'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
|
|
FROM colonias, municipios, estados
|
|
WHERE colonias.id_municipio=municipios.id
|
|
AND municipios.id_estado=estados.id
|
|
AND cp=?
|
|
ORDER BY colonia"""
|
|
cursor.execute(sql, (cp,))
|
|
rows = cursor.fetchall()
|
|
cursor.close()
|
|
con.close()
|
|
|
|
data = {}
|
|
if rows:
|
|
data = {
|
|
'estado': rows[0][2],
|
|
'municipio': rows[0][1],
|
|
}
|
|
if len(rows) == 1:
|
|
data['colonia'] = rows[0][0]
|
|
else:
|
|
data['colonia'] = [r[0] for r in rows]
|
|
return data
|
|
|
|
|
|
def get_sat_key(key):
|
|
return util.get_sat_key('productos', key)
|
|
|
|
|
|
def get_sat_monedas(key):
|
|
return util.get_sat_monedas(key)
|
|
|
|
|
|
def get_sat_unidades(key):
|
|
return util.get_sat_unidades(key)
|
|
|
|
|
|
def get_sat_productos(key):
|
|
return util.get_sat_productos(key)
|
|
|
|
|
|
def 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']),
|
|
'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(),
|
|
]
|
|
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
|
|
]
|
|
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))
|
|
|
|
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 '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))
|
|
|
|
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 migrations:
|
|
with database_proxy.atomic() as txn:
|
|
migrate(*migrations)
|
|
|
|
Configuracion.add({'version': VERSION})
|
|
|
|
log.info('Tablas migradas correctamente...')
|
|
_importar_valores('', rfc)
|
|
|
|
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 _importar_socios(rows):
|
|
# ~ log.info('\tImportando Clientes...')
|
|
# ~ totals = len(rows)
|
|
# ~ for i, row in enumerate(rows):
|
|
# ~ msg = '\tGuardando cliente {} de {}'.format(i+1, totals)
|
|
# ~ log.info(msg)
|
|
# ~ try:
|
|
# ~ with database_proxy.atomic() as txn:
|
|
# ~ Socios.create(**row)
|
|
# ~ except IntegrityError:
|
|
# ~ msg = '\tSocio existente: {}'.format(row['nombre'])
|
|
# ~ log.info(msg)
|
|
# ~ log.info('\tClientes importados...')
|
|
# ~ return
|
|
|
|
|
|
# ~ def _existe_factura(row):
|
|
# ~ filtro = (Facturas.uuid==row['uuid'])
|
|
# ~ if row['uuid'] is None:
|
|
# ~ filtro = (
|
|
# ~ (Facturas.serie==row['serie']) &
|
|
# ~ (Facturas.folio==row['folio'])
|
|
# ~ )
|
|
# ~ return Facturas.select().where(filtro).exists()
|
|
|
|
|
|
# ~ def _importar_facturas(rows):
|
|
# ~ log.info('\tImportando Facturas...')
|
|
# ~ totals = len(rows)
|
|
# ~ for i, row in enumerate(rows):
|
|
# ~ msg = '\tGuardando factura {} de {}'.format(i+1, totals)
|
|
# ~ log.info(msg)
|
|
|
|
# ~ try:
|
|
# ~ detalles = row.pop('detalles')
|
|
# ~ impuestos = row.pop('impuestos')
|
|
# ~ cliente = row.pop('cliente')
|
|
# ~ row['cliente'] = Socios.get(**cliente)
|
|
# ~ with database_proxy.atomic() as txn:
|
|
# ~ if _existe_factura(row):
|
|
# ~ msg = '\tFactura existente: {}{}'.format(
|
|
# ~ row['serie'], row['folio'])
|
|
# ~ log.info(msg)
|
|
# ~ continue
|
|
# ~ obj = Facturas.create(**row)
|
|
# ~ for detalle in detalles:
|
|
# ~ detalle['factura'] = obj
|
|
# ~ FacturasDetalle.create(**detalle)
|
|
# ~ for impuesto in impuestos:
|
|
# ~ imp = SATImpuestos.get(**impuesto['filtro'])
|
|
# ~ new = {
|
|
# ~ 'factura': obj,
|
|
# ~ 'impuesto': imp,
|
|
# ~ 'importe': impuesto['importe'],
|
|
# ~ }
|
|
# ~ try:
|
|
# ~ with database_proxy.atomic() as txn:
|
|
# ~ FacturasImpuestos.create(**new)
|
|
# ~ except IntegrityError as e:
|
|
# ~ pass
|
|
|
|
# ~ except IntegrityError as e:
|
|
# ~ print (e)
|
|
# ~ msg = '\tFactura: id: {}'.format(row['serie'] + str(row['folio']))
|
|
# ~ log.error(msg)
|
|
# ~ break
|
|
|
|
# ~ log.info('\tFacturas importadas...')
|
|
# ~ return
|
|
|
|
|
|
# ~ def _importar_categorias(rows):
|
|
# ~ log.info('\tImportando Categorías...')
|
|
# ~ for row in rows:
|
|
# ~ with database_proxy.atomic() as txn:
|
|
# ~ try:
|
|
# ~ Categorias.create(**row)
|
|
# ~ except IntegrityError:
|
|
# ~ msg = '\tCategoria: ({}) {}'.format(row['padre'], row['categoria'])
|
|
# ~ log.error(msg)
|
|
|
|
# ~ log.info('\tCategorías importadas...')
|
|
# ~ return
|
|
|
|
|
|
# ~ def _get_id_unidad(unidad):
|
|
# ~ try:
|
|
# ~ if 'pieza' in unidad.lower():
|
|
# ~ unidad = 'pieza'
|
|
# ~ if 'metros' in unidad.lower():
|
|
# ~ unidad = 'metro'
|
|
# ~ if 'tramo' in unidad.lower():
|
|
# ~ unidad = 'paquete'
|
|
# ~ if 'juego' in unidad.lower():
|
|
# ~ unidad = 'par'
|
|
# ~ if 'bolsa' in unidad.lower():
|
|
# ~ unidad = 'globo'
|
|
# ~ if unidad.lower() == 'no aplica':
|
|
# ~ unidad = 'servicio'
|
|
|
|
# ~ obj = SATUnidades.get(SATUnidades.name.contains(unidad))
|
|
# ~ except SATUnidades.DoesNotExist:
|
|
# ~ msg = '\tNo se encontró la unidad: {}'.format(unidad)
|
|
# ~ return unidad
|
|
|
|
# ~ return str(obj.id)
|
|
|
|
|
|
# ~ def _get_impuestos(impuestos):
|
|
# ~ lines = '|'
|
|
# ~ for impuesto in impuestos:
|
|
# ~ if impuesto['tasa'] == '-2/3':
|
|
# ~ tasa = str(round(2/3, 6))
|
|
# ~ else:
|
|
# ~ if impuesto['tasa'] == 'EXENTO':
|
|
# ~ tasa = '0.00'
|
|
# ~ else:
|
|
# ~ tasa = str(round(float(impuesto['tasa']) / 100.0, 6))
|
|
|
|
# ~ info = (
|
|
# ~ IMPUESTOS.get(impuesto['nombre']),
|
|
# ~ impuesto['nombre'],
|
|
# ~ impuesto['tipo'][0],
|
|
# ~ tasa,
|
|
# ~ )
|
|
# ~ lines += '|'.join(info) + '|'
|
|
# ~ return lines
|
|
|
|
|
|
# ~ def _generar_archivo_productos(archivo):
|
|
# ~ rfc = input('Introduce el RFC: ').strip().upper()
|
|
# ~ if not rfc:
|
|
# ~ msg = 'El RFC es requerido'
|
|
# ~ log.error(msg)
|
|
# ~ return
|
|
|
|
# ~ args = util.get_con(rfc)
|
|
# ~ if not args:
|
|
# ~ return
|
|
|
|
# ~ conectar(args)
|
|
|
|
# ~ log.info('Importando datos...')
|
|
# ~ app = util.ImportFacturaLibre(archivo, rfc)
|
|
# ~ if not app.is_connect:
|
|
# ~ log.error('\t{}'.format(app._error))
|
|
# ~ return
|
|
|
|
# ~ rows = app.import_productos()
|
|
|
|
# ~ p, _, _, _ = util.get_path_info(archivo)
|
|
# ~ path_txt = util._join(p, 'productos_{}.txt'.format(rfc))
|
|
# ~ log.info('\tGenerando archivo: {}'.format(path_txt))
|
|
|
|
# ~ fields = (
|
|
# ~ 'clave',
|
|
# ~ 'clave_sat',
|
|
# ~ 'unidad',
|
|
# ~ 'categoria',
|
|
# ~ 'descripcion',
|
|
# ~ 'valor_unitario',
|
|
# ~ 'existencia',
|
|
# ~ 'inventario',
|
|
# ~ 'codigo_barras',
|
|
# ~ 'cuenta_predial',
|
|
# ~ 'ultimo_precio',
|
|
# ~ 'minimo',
|
|
# ~ )
|
|
|
|
# ~ data = ['|'.join(fields)]
|
|
# ~ not_units = []
|
|
# ~ for row in rows:
|
|
# ~ impuestos = row.pop('impuestos', ())
|
|
# ~ line = [str(row[r]) for r in fields]
|
|
# ~ if line[10] == 'None':
|
|
# ~ line[10] = '0.0'
|
|
# ~ line[2] = _get_id_unidad(line[2])
|
|
# ~ try:
|
|
# ~ int(line[2])
|
|
# ~ except ValueError:
|
|
# ~ if not line[2] in not_units:
|
|
# ~ not_units.append(line[2])
|
|
# ~ msg = 'No se encontró la unidad: {}'.format(line[2])
|
|
# ~ log.error(msg)
|
|
# ~ continue
|
|
# ~ line = '|'.join(line) + _get_impuestos(impuestos)
|
|
# ~ data.append(line)
|
|
|
|
# ~ with open(path_txt, 'w') as fh:
|
|
# ~ fh.write('\n'.join(data))
|
|
|
|
# ~ log.info('\tArchivo generado: {}'.format(path_txt))
|
|
# ~ return
|
|
|
|
|
|
# ~ def importar_bdfl():
|
|
# ~ try:
|
|
# ~ emisor = Emisor.select()[0]
|
|
# ~ except IndexError:
|
|
# ~ msg = 'Configura primero al emisor'
|
|
# ~ return {'ok': False, 'msg': msg}
|
|
|
|
# ~ name = '{}.sqlite'.format(emisor.rfc.lower())
|
|
# ~ path = util._join('/tmp', name)
|
|
|
|
# ~ log.info('Importando datos...')
|
|
# ~ app = util.ImportFacturaLibre(path, emisor.rfc)
|
|
# ~ if not app.is_connect:
|
|
# ~ msg = app._error
|
|
# ~ log.error('\t{}'.format(msg))
|
|
# ~ return {'ok': False, 'msg': msg}
|
|
|
|
# ~ data = app.import_data()
|
|
|
|
# ~ _importar_socios(data['Socios'])
|
|
# ~ _importar_facturas(data['Facturas'])
|
|
# ~ _importar_categorias(data['Categorias'])
|
|
|
|
# ~ msg = 'Importación terminada...'
|
|
# ~ log.info(msg)
|
|
|
|
# ~ return {'ok': True, 'msg': msg}
|
|
|
|
|
|
# ~ def _importar_factura_libre(archivo):
|
|
# ~ rfc = input('Introduce el RFC: ').strip().upper()
|
|
# ~ if not rfc:
|
|
# ~ msg = 'El RFC es requerido'
|
|
# ~ log.error(msg)
|
|
# ~ return
|
|
|
|
# ~ args = util.get_con(rfc)
|
|
# ~ if not args:
|
|
# ~ return
|
|
|
|
# ~ conectar(args)
|
|
|
|
# ~ log.info('Importando datos...')
|
|
# ~ app = util.ImportFacturaLibre(archivo, rfc)
|
|
# ~ if not app.is_connect:
|
|
# ~ log.error('\t{}'.format(app._error))
|
|
# ~ return
|
|
|
|
# ~ data = app.import_data()
|
|
|
|
# ~ _importar_socios(data['Socios'])
|
|
# ~ _importar_facturas(data['Facturas'])
|
|
# ~ _importar_categorias(data['Categorias'])
|
|
|
|
# ~ log.info('Importación terminada...')
|
|
# ~ return
|
|
|
|
|
|
# ~ def _exist_ticket(row):
|
|
# ~ filters = (
|
|
# ~ (Tickets.serie==row['serie']) &
|
|
# ~ (Tickets.folio==row['folio'])
|
|
# ~ )
|
|
# ~ return Tickets.select().where(filters).exists()
|
|
|
|
|
|
# ~ def _import_tickets(rows):
|
|
# ~ log.info('\tImportando Tickets...')
|
|
# ~ for row in rows:
|
|
# ~ try:
|
|
# ~ details = row.pop('details')
|
|
# ~ taxes = row.pop('taxes')
|
|
# ~ with database_proxy.atomic() as txn:
|
|
# ~ if _exist_ticket(row):
|
|
# ~ msg = '\tTicket existente: {}{}'.format(
|
|
# ~ row['serie'], row['folio'])
|
|
# ~ log.info(msg)
|
|
# ~ continue
|
|
|
|
# ~ if not row['factura'] is None and row['factura']:
|
|
# ~ row['factura'] = Facturas.get(
|
|
# ~ Facturas.serie==row['factura']['serie'],
|
|
# ~ Facturas.folio==row['factura']['folio'])
|
|
# ~ else:
|
|
# ~ row['factura'] = None
|
|
|
|
# ~ obj = Tickets.create(**row)
|
|
# ~ for detail in details:
|
|
# ~ detail['ticket'] = obj
|
|
# ~ TicketsDetalle.create(**detail)
|
|
# ~ for tax in taxes:
|
|
# ~ imp = SATImpuestos.get(**tax['filter'])
|
|
# ~ new = {
|
|
# ~ 'ticket': obj,
|
|
# ~ 'impuesto': imp,
|
|
# ~ 'importe': tax['import'],
|
|
# ~ }
|
|
# ~ TicketsImpuestos.create(**new)
|
|
# ~ except IntegrityError as e:
|
|
# ~ print (e)
|
|
# ~ msg = '\tTicket: id: {}'.format(row['serie'] + str(row['folio']))
|
|
# ~ log.error(msg)
|
|
|
|
# ~ log.info('\tTickets importadas...')
|
|
# ~ return
|
|
|
|
|
|
# ~ def _importar_productos(archivo):
|
|
# ~ rfc = input('Introduce el RFC: ').strip().upper()
|
|
# ~ if not rfc:
|
|
# ~ msg = 'El RFC es requerido'
|
|
# ~ log.error(msg)
|
|
# ~ return
|
|
|
|
# ~ args = util.get_con(rfc)
|
|
# ~ if not args:
|
|
# ~ return
|
|
|
|
# ~ conectar(args)
|
|
# ~ log.info('Importando productos...')
|
|
|
|
# ~ fields = (
|
|
# ~ 'clave',
|
|
# ~ 'clave_sat',
|
|
# ~ 'unidad',
|
|
# ~ 'categoria',
|
|
# ~ 'descripcion',
|
|
# ~ 'valor_unitario',
|
|
# ~ 'existencia',
|
|
# ~ 'inventario',
|
|
# ~ 'codigo_barras',
|
|
# ~ 'cuenta_predial',
|
|
# ~ 'ultimo_precio',
|
|
# ~ 'minimo',
|
|
# ~ )
|
|
|
|
# ~ rows = util.read_file(archivo, 'r').split('\n')
|
|
# ~ for i, row in enumerate(rows):
|
|
# ~ if i == 0:
|
|
# ~ continue
|
|
# ~ data = row.split('|')
|
|
# ~ print (data)
|
|
# ~ new = {}
|
|
# ~ for i, f in enumerate(fields):
|
|
# ~ if not len(data[0]):
|
|
# ~ continue
|
|
|
|
# ~ if i in (2, 3):
|
|
# ~ try:
|
|
# ~ new[f] = int(data[i])
|
|
# ~ except ValueError:
|
|
# ~ continue
|
|
# ~ elif i in (5, 6, 10, 11):
|
|
# ~ new[f] = float(data[i])
|
|
# ~ elif i == 7:
|
|
# ~ new[f] = bool(data[i])
|
|
# ~ else:
|
|
# ~ new[f] = data[i]
|
|
|
|
# ~ impuestos = data[i + 1:-1]
|
|
# ~ if not impuestos:
|
|
# ~ taxes = [SATImpuestos.select().where(SATImpuestos.id==6)]
|
|
# ~ else:
|
|
# ~ taxes = []
|
|
# ~ try:
|
|
# ~ for i in range(0, len(impuestos), 4):
|
|
# ~ w = {
|
|
# ~ 'key': impuestos[i],
|
|
# ~ 'name': impuestos[i+1],
|
|
# ~ 'tipo': impuestos[i+2],
|
|
# ~ 'tasa': float(impuestos[i+3]),
|
|
# ~ }
|
|
# ~ taxes.append(SATImpuestos.get_o_crea(w))
|
|
# ~ except IndexError:
|
|
# ~ print ('IE', data)
|
|
# ~ continue
|
|
|
|
# ~ with database_proxy.transaction():
|
|
# ~ try:
|
|
# ~ obj = Productos.create(**new)
|
|
# ~ obj.impuestos = taxes
|
|
# ~ except IntegrityError as e:
|
|
# ~ 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 _export_cert():
|
|
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...')
|
|
|
|
obj = Certificado.get(Certificado.es_fiel==False)
|
|
cert = utils.SATCertificate
|
|
cert.save_cert(obj)
|
|
|
|
desconectar()
|
|
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('-ec', '--export-cert', dest='export_cert',
|
|
action='store_true', default=False, required=False)
|
|
|
|
parser.add_argument('-r', '--rfc', dest='rfc', 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.export_cert:
|
|
_export_cert()
|
|
return
|
|
|
|
return
|
|
|
|
|
|
if __name__ == '__main__':
|
|
args = _process_command_line_arguments()
|
|
main(args)
|
|
|
|
|