#!/usr/bin/env python3 # ~ Empresa Libre # ~ Copyright (C) 2016-2021 Mauricio Baeza Servin (public@correolibre.net) # ~ # ~ This program is free software: you can redistribute it and/or modify # ~ it under the terms of the GNU General Public License as published by # ~ the Free Software Foundation, either version 3 of the License, or # ~ (at your option) any later version. # ~ # ~ This program is distributed in the hope that it will be useful, # ~ but WITHOUT ANY WARRANTY; without even the implied warranty of # ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # ~ GNU General Public License for more details. # ~ # ~ You should have received a copy of the GNU General Public License # ~ along with this program. If not, see . import argparse from decimal import Decimal import sqlite3 from peewee import * from playhouse.fields import PasswordField, ManyToManyField from playhouse.shortcuts import case, SQL, cast if __name__ == '__main__': import os, sys parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, parent_dir) from controllers import util from settings import log, COMPANIES, VERSION, PATH_CP, PRE, CURRENT_CFDI, \ INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \ CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET, CURRENT_CFDI_NOMINA, \ DEFAULT_SAT_NOMINA, DECIMALES_TAX, TITLE_APP, MV, DECIMALES_PRECIOS, \ DEFAULT_CFDIPAY, CURRENCY_MN # ~ v2 from controllers import utils from settings import ( DEBUG, CANCEL_VERSION, CARTA_PORTE, DEFAULT_GLOBAL, DB_COMPANIES, EXT, IS_MV, MXN, PATHS, URL, VALUES_PDF, VERSION as VERSION_EMPRESA_LIBRE, RFCS, ) FORMAT = '{0:.2f}' FORMAT3 = '{0:.3f}' FORMAT4 = '{0:.4f}' FORMAT6 = '{0:.6f}' FORMAT_TAX = FORMAT4 FORMAT_PRECIO = FORMAT4 # ~ RFC_PUBLICO = 'XAXX010101000' RFC_EXTRANJERO = 'XEXX010101000' database_proxy = Proxy() class BaseModel(Model): class Meta: database = database_proxy def conectar(opt): db = { 'sqlite': SqliteDatabase, 'postgres': PostgresqlDatabase, 'mysql': MySQLDatabase, } db_type = opt.pop('type') db_name = opt.pop('name') if not db_type in db: log.error('Tipo de base de datos no soportado') return False opt['host'] = opt.get('host', 'localhost') database = db[db_type](db_name, **opt) try: database_proxy.initialize(database) database_proxy.connect() log.info('Conectado a la BD...') return True except OperationalError as e: log.error('Error al intentar conectar a la base de datos') log.error(e) return False def desconectar(): if database_proxy.obj is None: return if not database_proxy.is_closed(): database_proxy.close() log.info('Desconectado a la BD...') return class UploadFile(object): def _read_productsadd(self, result): emisor = result['data']['emisor'] rfc = emisor['rfc'] where = ((Socios.rfc == rfc) & (Socios.es_proveedor == True)) try: partner = Socios.select(Socios.id).where(where).get() emisor['id'] = partner.id except Socios.DoesNotExist: emisor['id'] = 0 result['data']['emisor'] = emisor return result @classmethod def read(cls, rfc, opt, file_obj): result = utils.upload_file(rfc, opt, file_obj) method = f'_read_{opt}' if hasattr(cls, method): return getattr(cls, method)(cls, result) return result def upload_file(rfc, opt, file_obj): if opt == 'cfdixml': sxml = file_obj.file.read().decode() xml = util.parse_xml(sxml) # ~ sxml = util.to_pretty_xml(data) if xml is None: return {'status': 'error'} else: return Facturas.import_cfdi(xml, sxml) if opt == 'emisorlogo': return Emisor.save_logo(file_obj) # ~ v2 names = ('productsadd', '_3.3_cp_2.0.ods') if opt in names: result = UploadFile.read(rfc, opt, file_obj) return result result = util.upload_file(rfc, opt, file_obj) if result['ok']: names = ('bdfl', 'employees', 'nomina', 'products', 'invoiceods') if not opt in names: Configuracion.add({opt: file_obj.filename}) return result def validar_timbrar(): try: obj = Emisor.select()[0] except IndexError: msg = 'Es necesario agregar los datos del emisor' return {'ok': False, 'msg': msg} try: obj = Folios.select()[0] except IndexError: msg = 'Es necesaria al menos una serie de folios' return {'ok': False, 'msg': msg} msg = 'Es necesario configurar un certificado de sellos' try: obj = Certificado.get(Certificado.es_fiel==False) except Exception as e: return {'ok': False, 'msg': msg} if not obj.serie: return {'ok': False, 'msg': msg} diff = obj.hasta - utils.now() if diff.days < 0: msg = 'El certificado ha vencido, es necesario cargar uno nuevo' return {'ok': False, 'msg': msg} auth = Configuracion.get_({'fields': 'pac_auth'}) if not auth: msg = 'Es necesario configurar los datos de timbrado del PAC' return {'ok': False, 'msg': msg} msg = '' if diff.days < 15: msg = 'El certificado vence en: {} días.'.format(diff.days) return {'ok': True, 'msg': msg} def _get_taxes_product(id): model_pt = Productos.impuestos.get_through_model() impuestos = tuple(model_pt .select( model_pt.productos_id.alias('product'), model_pt.satimpuestos_id.alias('tax')) .where(model_pt.productos_id==id).dicts()) return impuestos def import_invoice(): log.info('Importando factura...') emisor = Emisor.select()[0] rows, msg = util.import_invoice(emisor.rfc) if not rows: return {'ok': False, 'msg': msg} products = [] for i, row in enumerate(rows): try: if not isinstance(row[0], str): msg = 'Fila: {} - La clave debe ser TEXTO'.format(i+1) return {'ok': False, 'msg': msg} obj = Productos.get(Productos.clave==row[0]) vu = round(row[2], 2) if isinstance(row[3], str): msg = 'El descuento debe ser un número, debe ser 0.00, si no tiene' return {'ok': False, 'msg': msg} descuento = round(row[3], 2) cant = round(row[4], 2) description = row[1].strip() pedimento = row[5].strip() pf = vu - descuento p = { 'id_product': obj.id, 'delete': '-', 'clave': obj.clave, 'descripcion': description or obj.descripcion, 'pedimento': pedimento, 'unidad': obj.unidad.id, 'cantidad': cant, 'valor_unitario': vu, 'descuento': descuento, 'importe': round(pf * cant, DECIMALES), 'taxes': _get_taxes_product(obj.id), } products.append(p) except Productos.DoesNotExist: pass log.info('Factura importada...') return {'ok': True, 'rows': tuple(products)} def get_doc(type_doc, id, rfc): types = { 'xml': 'application/xml', 'xmlpago': 'application/xml', 'nomxml': 'application/xml', 'ods': 'application/octet-stream', 'zip': 'application/octet-stream', 'nomlog': 'application/txt', 'html': 'text/html', } content_type = types.get(type_doc, 'application/pdf') if type_doc == 'xml': data, file_name = Facturas.get_xml(id) elif type_doc == 'pdf': data, file_name = Facturas.get_pdf(id, rfc) elif type_doc == 'html': data, file_name = Facturas.get_html(id) elif type_doc == 'ods': data, file_name = Facturas.get_ods(id, rfc) elif type_doc == 'zip': data, file_name = Facturas.get_zip(id, rfc) elif type_doc == 'pre': data, file_name = PreFacturas.get_pdf(id) elif type_doc == 'tpdf': data, file_name = Tickets.get_pdf(id) elif type_doc == 'nomxml': data, file_name = CfdiNomina.get_xml(id) elif type_doc == 'nomlog': data, file_name = util.get_log('nomina') elif type_doc == 'nompdf': data, file_name = CfdiNomina.get_pdf(id, rfc) elif type_doc == 'xmlpago': data, file_name = CfdiPagos.get_file_xml(id) elif type_doc == 'pdfpago': data, file_name = CfdiPagos.get_file_pdf(id) return data, file_name, content_type def config_main(user): try: obj = Emisor.select()[0] except IndexError: obj = None punto_de_venta = Configuracion.get_bool('chk_usar_punto_de_venta') nomina = Configuracion.get_bool('chk_usar_nomina') if not user.es_admin: nomina = False data = { 'empresa': get_title_app(3), 'punto_de_venta': punto_de_venta, 'escuela': False, 'nomina': nomina, 'timbres': 0, 'decimales_precios': DECIMALES, 'pagos': Configuracion.get_bool('chk_config_pagos'), 'pays_data_bank': Configuracion.get_bool('chk_cfg_pays_data_bank') } dp = Configuracion.get_bool('chk_config_decimales_precios') if dp: data['decimales_precios'] = DECIMALES_PRECIOS if not obj is None: titulo = '{} - {}' data['empresa'] = titulo.format(data['empresa'], obj.nombre) data['escuela'] = obj.es_escuela data['timbres'] = Emisor.get_timbres() data['multi_currency'] = SATMonedas.get_multi_currency() return data def config_timbrar(): try: obj = Emisor.select()[0] except IndexError: return {'cfdi_donativo': False} conf = { 'cfdi_donativo': obj.es_ong, 'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'), 'cfdi_ine': Configuracion.get_bool('chk_config_ine'), 'cfdi_edu': Configuracion.get_bool('chk_config_edu'), 'cfdi_carta_porte': Configuracion.get_bool('chk_config_carta_porte'), 'cfdi_comercioe': Configuracion.get_bool('chk_config_comercio_exterior'), 'cfdi_divisas': Configuracion.get_bool('chk_config_divisas'), 'cfdi_metodo_pago': Configuracion.get_bool('chk_config_ocultar_metodo_pago'), 'cfdi_condicion_pago': Configuracion.get_bool('chk_config_ocultar_condiciones_pago'), 'cfdi_open_pdf': Configuracion.get_bool('chk_config_open_pdf'), 'cfdi_show_pedimento': Configuracion.get_bool('chk_config_show_pedimento'), 'cfdi_tax_locales': Configuracion.get_bool('chk_config_tax_locales'), 'cfdi_tax_decimals': Configuracion.get_bool('chk_config_tax_decimals'), 'cfdi_with_taxes': Configuracion.get_bool('chk_config_price_with_taxes_in_invoice'), 'cfdi_add_same_product': Configuracion.get_bool('chk_config_add_same_product'), 'cfdi_tax_locales_truncate': Configuracion.get_bool('chk_config_tax_locales_truncate'), 'cfdi_folio_custom': Configuracion.get_bool('chk_folio_custom'), 'cfdi_leyendasfiscales': Configuracion.get_bool('chk_config_leyendas_fiscales'), 'cfdi_show_total_cant': Configuracion.get_bool('chk_config_show_total_cant'), } return conf def config_ticket(): conf = { 'open_pdf': Configuracion.get_bool('chk_ticket_pdf_show'), 'direct_print': Configuracion.get_bool('chk_ticket_direct_print'), 'edit_cant': Configuracion.get_bool('chk_ticket_edit_cant'), 'total_up': Configuracion.get_bool('chk_ticket_total_up'), } return conf class Configuracion(BaseModel): clave = TextField(unique=True) valor = TextField(default='') class Meta: order_by = ('clave',) indexes = ( (('clave', 'valor'), True), ) def __str__(self): return '{} = {}'.format(self.clave, self.valor) def _clean_email(self, values={}): fields = ( 'correo_asunto', 'correo_confirmacion', 'correo_contra', 'correo_directo', 'correo_mensaje', 'correo_puerto', 'correo_servidor', 'correo_ssl', 'correo_usuario', ) q = (Configuracion .update(**{'valor': ''}) .where(Configuracion.clave.in_(fields)) ) result = q.execute() msg = 'Configuración guardada correctamente' if not result: msg = 'No se pudo guardar la configuración' return {'ok': result, 'msg': msg} def _save_mail(self, values): rfc = Emisor.select()[0].rfc values['correo_contra'] = utils.encrypt(values['correo_contra'], rfc) for k, v in values.items(): obj, created = Configuracion.get_or_create(clave=k) obj.valor = v obj.save() return {'ok': True} @classmethod def get_bool(cls, key): data = (Configuracion .select(Configuracion.valor) .where(Configuracion.clave == key) ) if data and data[0].valor == '1': return True return False def _get_partners(self, args={}): fields = ( 'chk_config_change_balance_partner', ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) values = {r.clave: util.get_bool(r.valor) for r in data} return values def _get_admin_products(self, args={}): fields = ( 'chk_config_cuenta_predial', 'chk_config_codigo_barras', 'chk_config_precio_con_impuestos', 'chk_llevar_inventario', 'chk_multi_stock', # ~ 'chk_use_packing', ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) values = {r.clave: util.get_bool(r.valor) for r in data} if not 'chk_multi_stock' in values: values['chk_multi_stock'] = False return values def _get_main_products(self, args={}): fields = ( 'chk_config_cuenta_predial', 'chk_config_codigo_barras', 'chk_config_precio_con_impuestos', 'chk_llevar_inventario', # ~ 'chk_use_packing', 'chk_multi_stock', ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) values = {r.clave: r.valor for r in data} values['default_tax'] = SATImpuestos.select()[0].id values['default_unidad'] = SATUnidades.get_default() return values def _get_complements(self, args={}): fields = ( 'chk_config_ine', 'chk_config_edu', 'chk_config_carta_porte', 'chk_config_comercio_exterior', 'chk_config_pagos', 'chk_config_divisas', 'chk_cfg_pays_data_bank', 'chk_usar_nomina', 'chk_config_leyendas_fiscales', ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) values = {r.clave: util.get_bool(r.valor) for r in data} fields = ( 'txt_ticket_printer', 'txt_config_nomina_serie', 'txt_config_nomina_folio', 'txt_config_cfdipay_serie', 'txt_config_cfdipay_folio', ) for f in fields: values[f] = Configuracion.get_(f) return values def _get_folios(self, args={}): fields = ( 'chk_folio_custom', ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) values = {r.clave: util.get_bool(r.valor) for r in data} return values def _get_correo(self, args={}): fields = ('correo_servidor', 'correo_puerto', 'correo_ssl', 'correo_starttls', 'correo_usuario', 'correo_copia', 'correo_asunto', 'correo_mensaje', 'correo_directo', 'correo_confirmacion') data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) values = {r.clave: r.valor for r in data} return values def _get_admin_config_users(self, args={}): fields = ( 'chk_users_notify_access', ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) values = {r.clave: util.get_bool(r.valor) for r in data} return values @classmethod def get_value(cls, key, default=''): value = default data = (Configuracion .select(Configuracion.valor) .where(Configuracion.clave == key) ) if data: value = data[0].valor return value def _get_pac(cls, pac): user_field = f'user_timbrado_{pac}' token_field = f'token_timbrado_{pac}' fields = (user_field, token_field) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) data = {r.clave: r.valor for r in data} values = { 'user_timbrado': data.get(user_field, ''), 'token_timbrado': data.get(token_field, ''), } return values def _get_pac_auth(cls, args={}): pac = cls.get_('lst_pac').lower() user = cls.get_(f'user_timbrado_{pac}') token = cls.get_(f'token_timbrado_{pac}') data = {} if pac and user and token: data['pac'] = pac data['user'] = user data['pass'] = token return data def _get_auth_by_pac(cls, args): pac = args['pac'] user = cls.get_(f'user_timbrado_{pac}') token = cls.get_(f'token_timbrado_{pac}') data = {} if pac and user and token: data['pac'] = pac data['user'] = user data['pass'] = token return data def _get_admin_sucursales(cls, args): values = { 'folios': Folios.get_for_sucursales(), 'warehouse': Almacenes.get_for_sucursales(), 'multi_stock': Configuracion.get_bool('chk_multi_stock') } return values @classmethod def get_(cls, keys): if isinstance(keys, str): data = (Configuracion .select(Configuracion.valor) .where(Configuracion.clave == keys) ) if data: return data[0].valor return '' options = ('partners', 'admin_products', 'main_products', 'complements', 'folios', 'correo', 'admin_config_users', 'pac_auth', 'auth_by_pac', 'admin_sucursales', ) opt = keys['fields'] if opt in options: return getattr(cls, f'_get_{opt}')(cls, keys) if opt == 'pac': return cls._get_pac(cls, keys['pac']) if keys['fields'] == 'configtemplates': try: emisor = Emisor.select()[0] is_ong = emisor.es_ong except IndexError: is_ong = False values = {'txt_plantilla_donataria': is_ong} fields = ( ('chk_usar_punto_de_venta', 'txt_plantilla_ticket'), ) for s, key in fields: value = util.get_bool(Configuracion.get_(s)) values[key] = value return values if keys['fields'] == 'configotros': fields = ( 'chk_config_ocultar_metodo_pago', 'chk_config_ocultar_condiciones_pago', 'chk_config_send_zip', 'chk_config_open_pdf', 'chk_config_show_pedimento', 'chk_config_tax_locales', 'chk_config_tax_decimals', 'chk_config_price_with_taxes_in_invoice', 'chk_config_add_same_product', 'chk_config_tax_locales_truncate', 'chk_config_decimales_precios', 'chk_config_user_show_doc', 'chk_config_anticipo', 'chk_usar_punto_de_venta', 'chk_ticket_pdf_show', 'chk_ticket_direct_print', 'chk_ticket_edit_cant', 'chk_ticket_total_up', 'chk_ticket_user_show_doc', 'chk_config_invoice_by_ticket', 'chk_config_show_total_cant', 'chk_cancel_invoices_by_admin', 'chk_cancel_tickets_by_admin', ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) values = {r.clave: util.get_bool(r.valor) for r in data} fields = ( ('lst_pac', 'comercio'), ) for k, d in fields: values[k] = Configuracion.get_value(k, d) return values if keys['fields'] == 'path_cer': fields = ('path_key', 'path_cer') data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) elif keys['fields'] == 'templates': fields = ( 'txt_plantilla_factura_32', 'txt_plantilla_factura_33', 'txt_plantilla_factura_html', 'txt_plantilla_factura_css', 'txt_plantilla_factura_json', 'txt_plantilla_nomina1233', 'txt_plantilla_pagos10', 'txt_plantilla_ticket', 'txt_plantilla_donataria', 'make_pdf_from', ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) elif keys['fields'] == 'timbrar': fields = ( 'chk_config_ocultar_metodo_pago', 'chk_config_ocultar_condiciones_pago', 'chk_config_anticipo', 'chk_config_ine', 'chk_config_open_pdf', ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) values = {r.clave: r.valor for r in data} return values def _save_pac(cls, values): pac = values['lst_pac'] user = values['user_timbrado'] token = values['token_timbrado'] data = { 'lst_pac': pac, f'user_timbrado_{pac}': user, f'token_timbrado_{pac}': token, } for k, v in data.items(): obj, _ = Configuracion.get_or_create(clave=k) obj.valor = v obj.save() return {'ok': True} @classmethod def add(cls, values): opt = values.pop('opt', '') if opt: return getattr(cls, f'_{opt}')(cls, values) # ~ print (values) try: for k, v in values.items(): #~ print (k, v) obj, created = Configuracion.get_or_create(clave=k) obj.valor = v obj.save() return {'ok': True} except Exception as e: log.error(str(e)) return {'ok': False, 'msg': str(e)} @classmethod def remove(cls, key): q = Configuracion.delete().where(Configuracion.clave==key) return bool(q.execute()) class Tags(BaseModel): tag = TextField(index=True, unique=True) class Meta: order_by = ('tag',) class TipoDireccion(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoTitulo(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoTelefono(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoCorreo(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoPariente(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoResponsable(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoMovimientoAlumno(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoMovimientoAlmacen(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class Roles(BaseModel): nombre = TextField(default='') class Meta: order_by = ('nombre',) class Permisos(BaseModel): rol = ForeignKeyField(Roles) modulo = TextField(default='') ver = BooleanField(default=False) agregar = BooleanField(default=False) editar = BooleanField(default=False) eliminar = BooleanField(default=False) especiales = TextField(default='') class Meta: order_by = ('rol', 'modulo') indexes = ( (('rol', 'modulo'), True), ) class Registro(BaseModel): usuario = TextField() accion = TextField(default='') tabla = TextField(default='') fecha = DateTimeField(default=util.now) def __str__(self): t = '{} {}-{} ({})' return t.format(self.usuario, self.accion, self.tabla, self.fecha) class Meta: order_by = ('usuario', 'fecha') @classmethod def add(cls, values): try: Registro.create(**values) return except: return class SATRegimenes(BaseModel): key = TextField(index=True, unique=True) name = TextField(index=True) activo = BooleanField(default=False) default = BooleanField(default=False) fisica = BooleanField(default=False) moral = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return '{} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): return SATRegimenes.get(SATRegimenes.key==key) @classmethod def get_(cls, ids): if isinstance(ids, int): ids = [ids] return SATRegimenes.select().where(SATRegimenes.id.in_(ids)) @classmethod def get_activos(cls, rfc): where = ((SATRegimenes.activo==True) & (SATRegimenes.fisica==True)) if (len(rfc) == 12): where = ((SATRegimenes.activo==True) & (SATRegimenes.moral==True)) rows = (SATRegimenes .select( SATRegimenes.id, SATRegimenes.name.alias('value')) .where(where) .dicts() ) return tuple(rows) @classmethod def _get_actives(cls, filters, user): where = ((SATRegimenes.activo==True) & (SATRegimenes.fisica==True)) if (filters['morales']=='true'): where = ((SATRegimenes.activo==True) & (SATRegimenes.moral==True)) rows = (SATRegimenes .select( SATRegimenes.id, SATRegimenes.name.alias('value')) .where(where) .dicts() ) return tuple(rows) @classmethod def get_data(cls, filters, user): opt = filters['opt'] return getattr(cls, f'_get_{opt}')(filters, user) class Emisor(BaseModel): rfc = TextField(unique=True) nombre = TextField(default='') nombre_comercial = TextField(default='') calle = TextField(default='') no_exterior = TextField(default='') no_interior = TextField(default='') colonia = TextField(default='') municipio = TextField(default='') estado = TextField(default='') pais = TextField(default='México') codigo_postal = TextField(default='') cp_expedicion = TextField(default='') es_moral = BooleanField(default=False) es_ong = BooleanField(default=False) es_escuela = BooleanField(default=False) autorizacion = TextField(default='') fecha_autorizacion = DateField(null=True) fecha_dof = DateField(null=True) telefono = TextField(default='') correo = TextField(default='') web = TextField(default='') curp = TextField(default='') correo_timbrado = TextField(default='') token_timbrado = TextField(default='') token_soporte = TextField(default='') logo = TextField(default='') registro_patronal = TextField(default='') regimenes = ManyToManyField(SATRegimenes, related_name='emisores') class Meta: order_by = ('nombre',) def __str__(self): t = '{} ({})' return t.format(self.nombre, self.rfc) @classmethod def save_logo(cls, file_obj): result = {'status': 'error', 'ok': False} name = file_obj.filename.split('.') if name[-1].lower() != EXT['PNG']: return result emisor = Emisor.select()[0] rfc = emisor.rfc.lower() name = f'{rfc}.png' path = util._join(PATHS['LOGOS'], name) if util.save_file(path, file_obj.file.read()): emisor.logo = file_obj.filename emisor.save() result = {'status': 'server', 'name': file_obj.filename, 'ok': True} return result @classmethod def get_(cls, rfc): regimenes = SATRegimenes.get_activos(rfc) row = {'regimenes': regimenes} obj = Emisor.select().where(Emisor.rfc==rfc) if bool(obj): obj = obj[0] row['emisor'] = { 'emisor_rfc': obj.rfc, 'emisor_curp': obj.curp, 'emisor_nombre': obj.nombre, 'emisor_cp': obj.codigo_postal, 'emisor_cp2': obj.cp_expedicion, 'emisor_calle': obj.calle, 'emisor_no_exterior': obj.no_exterior, 'emisor_no_interior': obj.no_interior, 'emisor_colonia': obj.colonia, 'emisor_municipio': obj.municipio, 'emisor_estado': obj.estado, 'emisor_pais': obj.pais, 'emisor_logo': obj.logo, 'emisor_nombre_comercial': obj.nombre_comercial, 'emisor_telefono': obj.telefono, 'emisor_correo': obj.correo, 'emisor_web': obj.web, 'emisor_logo': obj.logo, 'es_escuela': obj.es_escuela, 'es_ong': obj.es_ong, 'ong_autorizacion': obj.autorizacion, 'ong_fecha': obj.fecha_autorizacion, 'ong_fecha_dof': obj.fecha_dof, 'token_soporte': obj.token_soporte, 'emisor_registro_patronal': obj.registro_patronal, 'regimenes': [row.id for row in obj.regimenes] } else: row['emisor'] = {'emisor_rfc': rfc} return {'ok': True, 'row': row} @classmethod def get_auth(cls): try: obj = Emisor.select()[0] data = { 'RFC': obj.rfc, 'USER': obj.correo_timbrado, 'PASS': obj.token_timbrado, 'REPO': obj.token_soporte, } return data except: return {} @classmethod def get_timbres(cls): try: obj = Emisor.select()[0] except IndexError: return 's/e' auth = Configuracion.get_({'fields': 'pac_auth'}) result = utils.get_client_balance(auth, obj.rfc) return result @classmethod def get_regimenes(cls): try: obj = Emisor.select()[0] except IndexError: return () rows = [{'id': row.key, 'value': row.name} for row in obj.regimenes] return tuple(rows) def _clean(self, values): fields = util.clean(values) fields['rfc'] = fields.pop('emisor_rfc') fields['curp'] = fields.pop('emisor_curp', '') fields['nombre'] = fields.pop('emisor_nombre') fields['codigo_postal'] = fields.pop('emisor_cp') fields['cp_expedicion'] = fields.pop('emisor_cp2', '') or fields['codigo_postal'] fields['calle'] = fields.pop('emisor_calle', '') fields['no_exterior'] = fields.pop('emisor_no_exterior', '') fields['no_interior'] = fields.pop('emisor_no_interior', '') fields['colonia'] = fields.pop('emisor_colonia', '') fields['municipio'] = fields.pop('emisor_municipio', '') fields['estado'] = fields.pop('emisor_estado', '') fields['pais'] = fields.pop('emisor_pais', 'México') fields['logo'] = fields.pop('emisor_logo', '') fields['nombre_comercial'] = fields.pop('emisor_nombre_comercial', '') fields['telefono'] = fields.pop('emisor_telefono', '') fields['correo'] = fields.pop('emisor_correo', '') fields['web'] = fields.pop('emisor_web', '') fields['es_escuela'] = bool(fields['es_escuela'].replace('0', '')) fields['es_ong'] = bool(fields['es_ong'].replace('0', '')) ong_auth = fields.pop('ong_autorizacion', '') ong_date = fields.pop('ong_fecha', None) ong_date_dof = fields.pop('ong_fecha_dof', None) fields['autorizacion'] = ong_auth fields['fecha_autorizacion'] = ong_date fields['fecha_dof'] = ong_date_dof fields['correo_timbrado'] = fields.pop('correo_timbrado', '') fields['token_timbrado'] = fields.pop('token_timbrado', '') fields['token_soporte'] = fields.pop('token_soporte', '') if len(fields['rfc']) == 12: fields['es_moral'] = True fields['registro_patronal'] = fields.pop('emisor_registro_patronal', '') fields['regimenes'] = SATRegimenes.get_( util.loads(fields['regimenes'])) return fields @classmethod def add(cls, values): fields = cls._clean(cls, values) obj, created = Emisor.get_or_create(rfc=fields['rfc']) obj.regimenes = fields.pop('regimenes') q = Emisor.update(**fields).where(Emisor.id==obj.id) return {'ok': bool(q.execute())} class Certificado(BaseModel): key = BlobField(null=True) key_enc = TextField(default='') cer = BlobField(null=True) cer_pem = TextField(default='') cer_txt = TextField(default='') p12 = BlobField(null=True) serie = TextField(default='') rfc = TextField(default='') desde = DateTimeField(null=True) hasta = DateTimeField(null=True) es_fiel = BooleanField(default=False) def __str__(self): return self.serie @classmethod def _get_cert(cls, args): obj = Certificado.get(Certificado.es_fiel==False) data = { 'cert_rfc': obj.rfc, 'cert_serie': obj.serie, 'cert_desde': obj.desde, 'cert_hasta': obj.hasta, } return data @classmethod def get_data(cls, values): opt = values['opt'] return getattr(cls, f'_get_{opt}')(values) @classmethod def _validate_cert(cls, args): msg = 'Certificado guardado correctamente' result = {'ok': True, 'msg': msg, 'data': {}} cert = utils.get_cert(args) if not cert.is_valid: result['ok'] = False result['msg'] = cert.error return result obj = Certificado.get(Certificado.es_fiel==False) if obj.rfc != cert.rfc: result['ok'] = False result['msg'] = 'El RFC del certificado no corresponde.' return result obj.key = cert._key obj.key_enc = cert.key_enc obj.cer = cert.cer obj.serie = cert.serial_number obj.cer_txt = cert.cer_txt obj.p12 = utils.encrypt(cert._keyp, cert.serial_number) obj.desde = cert.not_before obj.hasta = cert.not_after obj.save() data = { 'cert_rfc': obj.rfc, 'cert_serie': obj.serie, 'cert_desde': obj.desde, 'cert_hasta': obj.hasta, } result['data'] = data return result @classmethod def post(cls, values): opt = values['opt'] return getattr(cls, f'_{opt}')(values) class Folios(BaseModel): serie = TextField(unique=True) inicio = IntegerField(default=1) default = BooleanField(default=False) usarcon = TextField(default='') plantilla = TextField(default='') class Meta: order_by = ('-default', 'serie', 'inicio') indexes = ( (('serie', 'inicio'), True), ) @classmethod def get_default(cls): folio = Folios.select()[0] return folio.serie @classmethod def get_id(cls, serie): try: obj = Folios.get(Folios.serie==serie) return obj.id except: return Folios.select()[0].id @classmethod def get_all(cls): rows = (Folios .select( Folios.id, Folios.serie.alias('value'), Folios.usarcon, ) .dicts() ) return tuple(rows) @classmethod def get_for_sucursales(cls): rows = (Folios .select( Folios.serie.alias('id'), Folios.serie.alias('value'), ) # ~ .where(Folios.usarcon == '') .dicts() ) return tuple(rows) @classmethod def get_(cls): rows = (Folios .select( Folios.id, SQL(" '-' AS delete"), Folios.serie, Folios.inicio, case(Folios.usarcon, ( ('I', 'Ingreso'), ('E', 'Egreso'), ('T', 'Traslado'), ), 'Todos').alias('usarcon'), case(Folios.default, ( (True, 'Si'), (False, 'No'), )).alias('pre') ) .dicts() ) return tuple(rows) @classmethod def add(cls, values): uc = { '': 'Todos', 'I': 'Ingreso', 'E': 'Egreso', 'T': 'Traslado', } pre = { True: 'Si', False: 'No', } if 'default' in values: values['default'] = True try: obj = Folios.create(**values) except IntegrityError: msg = 'Serie ya existe' return {'ok': False, 'msg': msg} row = { 'id': obj.id, 'delete' : '-', 'serie' : obj.serie, 'inicio' : obj.inicio, 'usarcon' : uc[obj.usarcon], 'pre' : pre[obj.default], } return {'ok': True, 'row': row} @classmethod def remove(cls, id): q = Folios.delete().where(Folios.id==id) return bool(q.execute()) class Categorias(BaseModel): categoria = TextField() padre = ForeignKeyField('self', null=True, related_name='hijos') class Meta: order_by = ('categoria',) indexes = ( (('categoria', 'padre'), True), ) @classmethod def exists(cls, filters): return Categorias.select().where(filters).exists() @classmethod def get_by_id(cls, id): try: return Categorias.get(Categorias.id==id) except: return None @classmethod def _get_by_name(cls, name): try: return Categorias.get(Categorias.categoria==name) except: return None @classmethod def get_all(cls): rows = (Categorias.select( Categorias.id, Categorias.categoria.alias('value'), Categorias.padre.alias('parent_id')) ).dicts() for row in rows: if row['parent_id'] is None: row['parent_id'] = 0 return tuple(rows) class CondicionesPago(BaseModel): condicion = TextField(unique=True) class Meta: order_by = ('condicion',) def __str__(self): return self.condicion @classmethod def get_(cls): q = CondicionesPago.select(CondicionesPago.condicion).tuples() data = [r[0] for r in q] return data @classmethod def get_or(cls, value): if value is None: return value obj, _ = CondicionesPago.get_or_create(condicion=value) return obj class SATUnidadesPeso(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return '{} ({})'.format(self.name, self.key) @classmethod def _get_all(cls, values, user): rows = tuple(SATUnidadesPeso.select().dicts()) return rows @classmethod def _get_active(cls, values, user): fields = ( SATUnidadesPeso.key.alias('id'), SATUnidadesPeso.name.alias('value'), ) where = (SATUnidadesPeso.activo==True) rows = tuple( SATUnidadesPeso.select(*fields).where(where).dicts() ) return rows @classmethod def get_data(cls, values, user): opt = values.pop('opt') return getattr(cls, f'_get_{opt}')(values, user) @classmethod def _add(cls, values, user): result = True try: SATUnidadesPeso.create(**values) except: result = False return {'ok': result} @classmethod def _delete(cls, values, user): id = values['id'] q = SATUnidadesPeso.delete().where(SATUnidadesPeso.id==id) result = bool(q.execute()) response = {'ok': result} return response @classmethod def _update_active(cls, values, user): id = values['id'] update = {'activo': bool(values['activo'])} where = (SATUnidadesPeso.id==id) q = SATUnidadesPeso.update(**update).where(where) result = bool(q.execute()) response = {'ok': result} return response @classmethod def post(cls, values, user): opt = values['opt'] args = utils.loads(values['values']) return getattr(cls, f'_{opt}')(args, user) class SATUnidades(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name') indexes = ( (('key', 'name'), True), ) def __str__(self): return '{} ({})'.format(self.name, self.key) @classmethod def get_by_name(self, name): try: return SATUnidades.get(SATUnidades.name==name) except: return None @classmethod def get_key_by_id(self, id): obj = SATUnidades.get(SATUnidades.id==id) return obj.key @classmethod def get_(self): rows = SATUnidades.select().dicts() return tuple(rows) @classmethod def add(self, values): try: SATUnidades.create(**values) return {'ok': True} except: return {'ok': False} @classmethod def actualizar(self, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} q = (SATUnidades .update(**{'activo': v[values['value']]}) .where(SATUnidades.id==id)) result = bool(q.execute()) elif values['field'] == 'default': q = SATUnidades.update(**{'default': False}) q.execute() v = {'false': False, 'true': True} q = (SATUnidades .update(**{'default': v[values['value']]}) .where(SATUnidades.id==id)) result = bool(q.execute()) return {'ok': result} @classmethod def get_default(cls): obj = SATUnidades.select()[0] if obj.default: return obj.id return 0 @classmethod def get_activos(cls): rows = (SATUnidades .select( SATUnidades.id, SATUnidades.name.alias('value')) .where(SATUnidades.activo==True) .dicts() ) return tuple(rows) @classmethod def get_activos_by_key(cls): rows = (SATUnidades .select( SATUnidades.key.alias('id'), SATUnidades.name.alias('value')) .where(SATUnidades.activo==True) .dicts() ) return tuple(rows) @classmethod def remove(cls, id): with database_proxy.transaction(): try: q = SATUnidades.delete().where(SATUnidades.id==id) return bool(q.execute()) except IntegrityError: return False class SATFormaPago(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Forma de pago: ({}) {}'.format(self.key, self.name) @classmethod def get_values(cls, values): opt = values.pop('opt') return getattr(cls, '_get_{}'.format(opt))(cls, values) def _get_active_by_id(self, values): rows = (SATFormaPago .select( SATFormaPago.id, SATFormaPago.name.alias('value')) .where(SATFormaPago.activo==True) .dicts() ) return tuple(rows) def _get_active_by_key(self, values): rows = (SATFormaPago .select( SATFormaPago.key.alias('id'), SATFormaPago.name.alias('value')) .where(SATFormaPago.activo==True) .dicts() ) return tuple(rows) @classmethod def get_(self): rows = SATFormaPago.select().dicts() return tuple(rows) @classmethod def get_by_key(cls, key): return SATFormaPago.get(SATFormaPago.key==key) @classmethod def get_activos(cls, values): field = SATFormaPago.id if values: field = SATFormaPago.key.alias('id') rows = (SATFormaPago .select(field, SATFormaPago.name.alias('value')) .where(SATFormaPago.activo==True) .dicts() ) return tuple(rows) @classmethod def actualizar(self, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} q = (SATFormaPago .update(**{'activo': v[values['value']]}) .where(SATFormaPago.id==id)) result = bool(q.execute()) elif values['field'] == 'default': q = SATFormaPago.update(**{'default': False}) q.execute() v = {'false': False, 'true': True} q = (SATFormaPago .update(**{'default': v[values['value']]}) .where(SATFormaPago.id==id)) result = bool(q.execute()) return {'ok': result} class SATAduanas(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) class SATMonedas(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Moneda: ({}) {}'.format(self.key, self.name) @classmethod def get_by_key(cls, key): return SATMonedas.get(SATMonedas.key==key) @classmethod def get_multi_currency(cls): count_currencies = len(cls.get_activos()) multi_currency = False if count_currencies > 1: multi_currency = True return multi_currency @classmethod def add(self, values): try: SATMonedas.create(**values) return {'ok': True} except: return {'ok': False} @classmethod def get_(self): rows = SATMonedas.select().dicts() return tuple(rows) @classmethod def get_activos(cls): rows = (SATMonedas .select( SATMonedas.key.alias('id'), SATMonedas.name.alias('value')) .where(SATMonedas.activo==True) .dicts() ) return tuple(rows) @classmethod def get_activos_by_id(cls): rows = (SATMonedas .select( SATMonedas.id, SATMonedas.name.alias('value')) .where(SATMonedas.activo==True) .dicts() ) return tuple(rows) @classmethod def actualizar(self, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} q = (SATMonedas .update(**{'activo': v[values['value']]}) .where(SATMonedas.id==id)) result = bool(q.execute()) elif values['field'] == 'default': q = SATMonedas.update(**{'default': False}) q.execute() v = {'false': False, 'true': True} q = (SATMonedas .update(**{'default': v[values['value']]}) .where(SATMonedas.id==id)) result = bool(q.execute()) return {'ok': result} class SATImpuestos(BaseModel): key = TextField(index=True) name = TextField(default='', index=True) factor = TextField(default='T') tipo = TextField(default='T') tasa = DecimalField(default=0.0, decimal_places=6, auto_round=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'factor', 'tipo', 'tasa'), True), ) @classmethod def get_o_crea(self, values): obj, _ = SATImpuestos.get_or_create(**values) return obj @classmethod def add(self, values): tasa = float(values['tasa']) tipo = 'T' if tasa < 0: tipo = 'R' row = { 'key': IMPUESTOS.get(values['impuesto']), 'name': values['impuesto'], 'tipo': tipo, 'tasa': abs(tasa), } try: obj = SATImpuestos.create(**row) row['id'] = obj.id row['delete'] = '-' return {'ok': True, 'row': row} except IntegrityError: return {'ok': False, 'msg': 'El impuesto ya existe'} @classmethod def remove(cls, id): with database_proxy.transaction(): try: q = SATImpuestos.delete().where(SATImpuestos.id==id) return bool(q.execute()) except IntegrityError: return False @classmethod def get_(self): rows = (SATImpuestos.select( SATImpuestos.id, SQL(" '-' AS delete"), SATImpuestos.name, SATImpuestos.tipo, SATImpuestos.tasa, SATImpuestos.activo, SATImpuestos.default) .dicts() ) return tuple(rows) @classmethod def get_activos(self): rows = SATImpuestos.select().where(SATImpuestos.activo==True).dicts() return tuple(rows) @classmethod def actualizar(self, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} q = (SATImpuestos .update(**{'activo': v[values['value']]}) .where(SATImpuestos.id==id)) result = bool(q.execute()) elif values['field'] == 'default': q = SATImpuestos.update(**{'default': False}) q.execute() v = {'false': False, 'true': True} q = (SATImpuestos .update(**{'default': v[values['value']]}) .where(SATImpuestos.id==id)) result = bool(q.execute()) return {'ok': result} class SATTipoRelacion(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Tipo de relación: ({}) {}'.format(self.key, self.name) @classmethod def get_activos(cls, values): field = SATTipoRelacion.id if values: field = SATTipoRelacion.key.alias('id') rows = (SATTipoRelacion .select(field, SATTipoRelacion.name.alias('value')) .where(SATTipoRelacion.activo==True) .dicts() ) return ({'id': '-', 'value': ''},) + tuple(rows) class SATBancos(BaseModel): key = TextField(index=True, unique=True) name = TextField(index=True) razon_social = TextField(default='') rfc = TextField(default='') activo = BooleanField(default=False) class Meta: order_by = ('-activo', 'name') indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Banco: {} ({})'.format(self.name, self.key) def _updaterfc(self, args): id = int(args['id']) values = util.loads(args['values']) msg = util.validate_rfc(values['rfc']) if msg: return {'ok': False, 'msg': msg} q = (SATBancos .update(**values) .where(SATBancos.id==id)) result = bool(q.execute()) msg = 'RFC actualizado correctamente' return {'ok': result, 'msg': msg} @classmethod def post(cls, values): opt = values.pop('opt') return getattr(cls, '_{}'.format(opt))(cls, values) @classmethod def get_values(cls, values): opt = values.pop('opt') return getattr(cls, '_get_{}'.format(opt))(cls, values) def _get_active(cls, values): rows = (SATBancos .select( SATBancos.id, SATBancos.name.alias('value')) .where(SATBancos.activo==True) .dicts() ) return tuple(rows) @classmethod def get_(cls): rows = SATBancos.select().dicts() return tuple(rows) @classmethod def get_activos_by_id(cls): rows = (SATBancos .select( SATBancos.id, SATBancos.name.alias('value')) .where(SATBancos.activo==True) .dicts() ) return tuple(rows) @classmethod def actualizar(cls, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} q = (SATBancos .update(**{'activo': v[values['value']]}) .where(SATBancos.id==id)) result = bool(q.execute()) return {'ok': result} @classmethod def get_by_key(cls, key): if not key: return try: obj = SATBancos.get(SATBancos.key==key) return obj except SATBancos.DoesNotExist: msg = 'SATBancos no existe: {}'.format(key) log.error(msg) return @classmethod def get_by_name(cls, name): try: obj = SATBancos.get(SATBancos.name==name) return obj except SATBancos.DoesNotExist: msg = 'SATBancos no existe: {}'.format(key) log.error(msg) return class SATNivelesEducativos(BaseModel): name = TextField(index=True) class Meta: order_by = ('name',) def __str__(self): return self.name @classmethod def get_by(cls): rows = SATNivelesEducativos.select( SATNivelesEducativos.name).tuples() return tuple([r[0] for r in rows]) class NivelesEducativos(BaseModel): nombre = TextField() autorizacion = TextField(default='') class Meta: order_by = ('nombre',) indexes = ( (('nombre', 'autorizacion'), True), ) def __str__(self): return '{} ({})'.format(self.nombre, self.autorizacion) @classmethod def get_all(cls): rows = NivelesEducativos.select().dicts() return tuple(rows) def _add_group(self, obj): Grupos.get_or_create(**{'nivel': obj}) return @classmethod def add(cls, values): try: obj = NivelesEducativos.create(**values) # Revisar cls._add_group(cls, obj) result = {'ok': True} except IntegrityError: msg = 'Nivel Educativo existente' result = {'ok': False, 'msg': msg} return result @classmethod def remove(cls, id): obj = NivelesEducativos.get(NivelesEducativos.id==int(id)) q = Grupos.delete().where(Grupos.nivel==obj) try: q.execute() except IntegrityError: return False q = NivelesEducativos.delete().where(NivelesEducativos.id==int(id)) return bool(q.execute()) class Grupos(BaseModel): nivel = ForeignKeyField(NivelesEducativos) grado = TextField(default='') nombre = TextField(default='') class Meta: order_by = ('nivel', 'grado', 'nombre') indexes = ( (('nivel', 'grado', 'nombre'), True), ) def __str__(self): return '{} {} {}'.format(self.nivel.nombre, self.grado, self.nombre) @classmethod def get_by(cls, values): rows = (Grupos.select( Grupos.id.alias('id'), NivelesEducativos.nombre.alias('value')) .join(NivelesEducativos) .switch(Grupos) .dicts() ) return tuple(rows) class CuentasBanco(BaseModel): de_emisor = BooleanField(default=False) activa = BooleanField(default=True) nombre = TextField() banco = ForeignKeyField(SATBancos) fecha_apertura = DateField(null=True) cuenta = TextField(default='') clabe = TextField(default='') moneda = ForeignKeyField(SATMonedas) saldo_inicial = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class Meta: order_by = ('nombre',) indexes = ( (('banco', 'cuenta'), True), ) def __str__(self): return '{} ({})'.format(self.nombre, self.cuenta[-4:]) @classmethod def activate(cls, values): result = False id = int(values['id']) if values['field'] == 'activa': v = {'0': False, '1': True} q = (CuentasBanco .update(**{'activa': v[values['value']]}) .where(CuentasBanco.id==id)) result = bool(q.execute()) return {'ok': result} @classmethod def actualizar_saldo(cls, id, saldo): fields = {'saldo': saldo} q = CuentasBanco.update(**fields).where(CuentasBanco.id==id) return bool(q.execute()) @classmethod def get_saldo(cls, id): try: obj = CuentasBanco.get(CuentasBanco.id==id) return obj.saldo except CuentasBanco.DoesNotExist: return 0 @classmethod def remove(cls, id): try: with database_proxy.atomic() as txn: q = MovimientosBanco.delete().where(MovimientosBanco.cuenta==id) q.execute() q = CuentasBanco.delete().where(CuentasBanco.id==id) q.execute() return True except: return False @classmethod def get_years(cls): data = [{'id': -1, 'value': 'Todos'}] year1 = (CuentasBanco .select(fn.Min(CuentasBanco.fecha_apertura.year)) .where(CuentasBanco.de_emisor==True, CuentasBanco.activa==True) .group_by(CuentasBanco.fecha_apertura.year) .order_by(CuentasBanco.fecha_apertura.year) .scalar() ) if year1: year2 = util.now().year + 1 data += [{'id': y, 'value': y} for y in range(int(year1), year2)] return data @classmethod def get_(cls, values): opt = values.pop('opt', '') if opt: return getattr(cls, '_get_{}'.format(opt))(cls, values) if values['tipo'] == '1': rows = (CuentasBanco .select() .where(CuentasBanco.de_emisor==True, CuentasBanco.activa==True) .order_by(CuentasBanco.id) ) if not (len(rows)): return {'ok': False} first = rows[0] rows = [{'id': r.id, 'value': '{} ({})'.format( r.nombre, r.cuenta[-4:])} for r in rows] data = { 'ok': True, 'rows': tuple(rows), 'moneda': '{} ({})'.format(first.moneda.name, first.moneda.key), 'key_currency': first.moneda.key, 'saldo': first.saldo, } return data return {'ok': False} def _get_currency(self, values): id = int(values['id']) account = CuentasBanco.get(CuentasBanco.id==id) data = { 'ok': True, 'currency': '{} ({})'.format(account.moneda.name, account.moneda.key), 'key_currency': account.moneda.key, } return data @classmethod def emisor(cls): rows = (CuentasBanco .select( CuentasBanco.id, CuentasBanco.activa, CuentasBanco.nombre, SATBancos.name.alias('banco'), CuentasBanco.fecha_apertura, CuentasBanco.cuenta, CuentasBanco.clabe, SATMonedas.name.alias('moneda'), CuentasBanco.saldo ) .join(SATBancos).switch(CuentasBanco) .join(SATMonedas).switch(CuentasBanco) .where(CuentasBanco.de_emisor==True).dicts() ) return tuple(rows) @classmethod def add(cls, values): w = '37137137137137137' dv = str( (10 - sum([(int(v) * int(values['clabe'][i])) % 10 for i, v in enumerate(w)]) % 10) % 10) if dv != values['clabe'][-1]: msg = 'Digito de control de la CLABE es incorrecto' return {'ok': False, 'msg': msg} fecha_deposito = values.pop('fecha_deposito', None) with database_proxy.transaction(): try: obj = CuentasBanco.create(**values) except IntegrityError: msg = 'Esta cuenta ya existe' return {'ok': False, 'msg': msg} nuevo_mov= { 'opt': 'add', 'cuenta': obj.id, 'fecha': fecha_deposito, 'descripcion': 'Saldo inicial', 'forma_pago': SATFormaPago.get_by_key('99'), 'deposito': values['saldo'], 'saldo': values['saldo'], } MovimientosBanco.post(nuevo_mov) rows = (CuentasBanco .select( CuentasBanco.id, CuentasBanco.activa, CuentasBanco.nombre, SATBancos.name.alias('banco'), CuentasBanco.fecha_apertura, CuentasBanco.cuenta, CuentasBanco.clabe, SATMonedas.name.alias('moneda'), CuentasBanco.saldo ) .join(SATBancos).switch(CuentasBanco) .join(SATMonedas).switch(CuentasBanco) .where(CuentasBanco.id==obj.id).dicts() ) data = {'ok': True, 'row': rows[0]} return data class SATUsoCfdi(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) fisica = BooleanField(default=True) moral = BooleanField(default=False) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Uso del CFDI: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): return SATUsoCfdi.get(SATUsoCfdi.key==key) @classmethod def actualizar(self, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} q = (SATUsoCfdi .update(**{'activo': v[values['value']]}) .where(SATUsoCfdi.id==id)) result = bool(q.execute()) return {'ok': result} @classmethod def get_all(self): rows = SATUsoCfdi.select().dicts() return tuple(rows) @classmethod def get_id(self, key): if key is None: return return SATUsoCfdi.get(SATUsoCfdi.key==key).id @classmethod def get_key(self, id): if id is None: return return SATUsoCfdi.get(SATUsoCfdi.id==id).key @classmethod def get_activos(cls): rows = (SATUsoCfdi .select( SATUsoCfdi.key.alias('id'), SATUsoCfdi.name.alias('value'), SATUsoCfdi.fisica, SATUsoCfdi.moral, ) .where(SATUsoCfdi.activo==True) .dicts() ) return tuple(rows) class SATEstados(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) pais = TextField(default='') activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name', 'pais'), True), ) def __str__(self): return 'Estado: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATEstados.get(SATEstados.key==key) return obj except SATEstados.DoesNotExist: msg = 'SATEstados no existe: {}'.format(key) log.error(msg) return class SATOrigenRecurso(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Origen Recurso: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATOrigenRecurso.get(SATOrigenRecurso.key==key) return obj except SATOrigenRecurso.DoesNotExist: msg = 'SATOrigenRecurso no existe: {}'.format(key) log.error(msg) return class SATPeriodicidadPago(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Periodicidad de Pago: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATPeriodicidadPago.get(SATPeriodicidadPago.key==key) return obj except SATPeriodicidadPago.DoesNotExist: msg = 'SATPeriodicidadPago no existe: {}'.format(key) log.error(msg) return class SATTipoContrato(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Tipo de Contrato: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATTipoContrato.get(SATTipoContrato.key==key) return obj except SATTipoContrato.DoesNotExist: msg = 'SATTipoContrato no existe: {}'.format(key) log.error(msg) return class SATTipoDeduccion(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) clave = TextField(default='') nombre = TextField(default='') activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Tipo de Deducción: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATTipoDeduccion.get(SATTipoDeduccion.key==key) return obj except SATTipoDeduccion.DoesNotExist: msg = 'SATTipoDeduccion no existe: {}'.format(key) log.error(msg) return class SATTipoHoras(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Tipo de Horas: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATTipoHoras.get(SATTipoHoras.key==key) return obj except SATTipoHoras.DoesNotExist: msg = 'SATTipoHoras no existe: {}'.format(key) log.error(msg) return class SATTipoIncapacidad(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Tipo de Incapacidad: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATTipoIncapacidad.get(SATTipoIncapacidad.key==key) return obj except SATTipoIncapacidad.DoesNotExist: msg = 'SATTipoIncapacidad no existe: {}'.format(key) log.error(msg) return class SATTipoJornada(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Tipo de Jornada: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATTipoJornada.get(SATTipoJornada.key==key) return obj except SATTipoJornada.DoesNotExist: msg = 'SATTipoJornada no existe: {}'.format(key) log.error(msg) return class SATTipoNomina(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Tipo de Nómina: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATTipoNomina.get(SATTipoNomina.key==key) return obj except SATTipoNomina.DoesNotExist: msg = 'SATTipoNomina no existe: {}'.format(key) log.error(msg) return class SATTipoOtroPago(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) clave = TextField(default='') nombre = TextField(default='') activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Tipo de Otro Pago: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATTipoOtroPago.get(SATTipoOtroPago.key==key) return obj except SATTipoOtroPago.DoesNotExist: msg = 'SATTipoOtroPago no existe: {}'.format(key) log.error(msg) return class SATTipoPercepcion(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) clave = TextField(default='') nombre = TextField(default='') activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Tipo de Percepción: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATTipoPercepcion.get(SATTipoPercepcion.key==key) return obj except SATTipoPercepcion.DoesNotExist: msg = 'SATTipoPercepcion no existe: {}'.format(key) log.error(msg) return class SATTipoRegimen(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Regimen de contratación: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATTipoRegimen.get(SATTipoRegimen.key==key) return obj except SATTipoRegimen.DoesNotExist: msg = 'SATTipoRegimen no existe: {}'.format(key) log.error(msg) return class SATRiesgoPuesto(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=True) class Meta: order_by = ('name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Riesgo Puesto: {} ({})'.format(self.name, self.key) @classmethod def get_by_key(cls, key): try: obj = SATRiesgoPuesto.get(SATRiesgoPuesto.key==key) return obj except SATRiesgoPuesto.DoesNotExist: msg = 'SATRiesgoPuesto no existe: {}'.format(key) log.error(msg) return class SATLeyendasFiscales(BaseModel): texto_leyenda = TextField(index=True) norma = TextField(default='') disposicion_fiscal = TextField(default='') active = BooleanField(default=True) default = BooleanField(default=False) class Meta: order_by = ('texto_leyenda',) indexes = ( (('texto_leyenda', 'norma', 'disposicion_fiscal'), True), ) def _get_all(self, values): rows = (SATLeyendasFiscales.select( SATLeyendasFiscales.id, SQL(" '-' AS delete"), SATLeyendasFiscales.texto_leyenda, SATLeyendasFiscales.norma, SATLeyendasFiscales.disposicion_fiscal) .dicts() ) return tuple(rows) def _get_active(self, values): rows = (SATLeyendasFiscales.select( SATLeyendasFiscales.id, SATLeyendasFiscales.texto_leyenda, SATLeyendasFiscales.norma, SATLeyendasFiscales.disposicion_fiscal) .where(SATLeyendasFiscales.active==True) .dicts() ) return tuple(rows) @classmethod def get_by_id(cls, ids): rows = (SATLeyendasFiscales.select( SATLeyendasFiscales.texto_leyenda.alias('textoLeyenda'), SATLeyendasFiscales.norma, SATLeyendasFiscales.disposicion_fiscal.alias('disposicionFiscal')) .where(SATLeyendasFiscales.id.in_(ids)) .dicts() ) for row in rows: if not row['norma']: del row['norma'] if not row['disposicionFiscal']: del row['disposicionFiscal'] return tuple(rows) @classmethod def get_values(cls, values): opt = values.pop('opt') return getattr(cls, '_get_{}'.format(opt))(cls, values) def _new(self, values): try: obj = SATLeyendasFiscales.create(**values) values['id'] = obj.id values['delete'] = '-' result = {'ok': True, 'row': values} except Exception as e: result = {'ok': False, 'msg': 'La Leyenda Fiscal ya existe'} return result @classmethod def post(cls, values): opt = values.pop('opt') return getattr(cls, '_{}'.format(opt))(cls, values) @classmethod def remove(cls, id): q = SATLeyendasFiscales.delete().where(SATLeyendasFiscales.id==id) return bool(q.execute()) class TipoCambio(BaseModel): dia = DateField(default=util.now) moneda = ForeignKeyField(SATMonedas) tipo_cambio = DecimalField(max_digits=15, decimal_places=6, auto_round=True) class Meta: order_by = ('-dia',) class Addendas(BaseModel): nombre = TextField(unique=True) addenda = TextField() class Meta: order_by = ('nombre',) class Socios(BaseModel): tipo_persona = IntegerField(default=1) rfc = TextField(index=True) id_fiscal = TextField(default='') nombre = TextField(index=True) slug = TextField(default='') nombre_comercial = TextField(index=True, default='') calle = TextField(default='') no_exterior = TextField(default='') no_interior = TextField(default='') colonia = TextField(default='') municipio = TextField(default='') estado = TextField(default='') pais = TextField(default='') codigo_postal = TextField(default='') notas = TextField(default='') telefonos = TextField(default='') es_activo = BooleanField(default=True) es_ong = BooleanField(default=False) fecha_alta = DateField(default=util.now) dias_pago = IntegerField(default=0) dias_habiles = BooleanField(default=False) es_cliente = BooleanField(default=False) es_proveedor = BooleanField(default=False) cuenta_cliente = TextField(default='') cuenta_proveedor = TextField(default='') saldo_cliente = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) saldo_proveedor = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) web = TextField(default='') correo_facturas = TextField(default='') correo_facturasp = TextField(default='') forma_pago = ForeignKeyField(SATFormaPago, null=True) condicion_pago = ForeignKeyField(CondicionesPago, null=True) addenda = ForeignKeyField(Addendas, null=True) uso_cfdi = ForeignKeyField(SATUsoCfdi, null=True) tags = ManyToManyField(Tags, related_name='socios_tags') plantilla = TextField(default='') regimen_fiscal = TextField(default='') def __str__(self): t = '{} ({})' return t.format(self.nombre, self.rfc) class Meta: order_by = ('nombre',) indexes = ( (('rfc', 'slug'), True), ) def _clean(self, values): fields = util.clean(values) change_balance = Configuracion.get_bool('chk_config_change_balance_partner') balance = fields.pop('partner_balance', '').replace('$', '').replace(',', '') if change_balance: fields['saldo_cliente'] = round(float(balance), 2) fields['rfc'] = fields['rfc'].upper() fields['nombre'] = util.spaces(fields['nombre']) fields['slug'] = util.to_slug(fields['nombre']) uso_cfdi = fields.pop('uso_cfdi_socio', None) fields['uso_cfdi'] = SATUsoCfdi.get_id(uso_cfdi) fields['condicion_pago'] = \ CondicionesPago.get_or(fields.get('condicion_pago', None)) fb = ('dias_habiles', 'es_activo', 'es_cliente', 'es_proveedor', 'es_ong') for name in fb: fields[name] = bool(fields[name].replace('0', '')) ft = ('nombre_comercial', 'calle', 'no_exterior', 'no_interior', 'colonia', 'municipio', 'estado', 'pais', 'codigo_postal', 'notas', 'telefonos', 'cuenta_cliente', 'cuenta_proveedor', 'web', 'correo_facturas', 'plantilla', 'id_fiscal') for name in ft: fields[name] = values.get(name, '') fields['correo_facturasp'] = values.pop('partner_email_fp', '') if fields['pais'] != 'México': fields['pais'] = fields['pais'].upper() if 'regimenes' in fields: fields['regimenes'] = utils.loads(fields['regimenes']) if isinstance(fields['regimenes'], list): fields['regimenes'] = tuple(map(int, fields['regimenes'])) else: fields['regimenes'] = (fields['regimenes'],) return fields @classmethod def get_(cls, values): #~ print ('values', values) id = values.get('id', 0) if id: id = int(values['id']) row = Socios.select().where(Socios.id==id).dicts()[0] row['uso_cfdi_socio'] = SATUsoCfdi.get_key(row.pop('uso_cfdi')) if not row['condicion_pago'] is None: row['condicion_pago'] = \ str(CondicionesPago.get(id=row['condicion_pago'])) row['partner_balance'] = row.pop('saldo_cliente') row['partner_email_fp'] = row.pop('correo_facturasp') row['regimenes'] = SociosRegimenes.get_by_socio(row['id']) return row total = Socios.select().count() rows = (Socios .select( Socios.id, Socios.rfc, Socios.nombre, Socios.saldo_cliente) .dicts() ) return {'pos': 0, 'total_count': total, 'data': tuple(rows)} @classmethod def get_by_client(cls, values): id = int(values.get('id', 0)) if id: row = (Socios .select( Socios.id, Socios.nombre, Socios.rfc, SATFormaPago.key.alias('forma_pago'), SATUsoCfdi.key.alias('uso_cfdi'), Socios.codigo_postal) .join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios) .join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios) .where((Socios.id==id) & (Socios.es_cliente==True)) .dicts() ) if len(row): client = row[0] client['regimenes'] = SociosRegimenes.get_by_key(client['id']) return {'ok': True, 'row': client} return {'ok': False} name = values.get('name', '') if name: rows = (Socios .select(Socios.id, Socios.nombre, Socios.rfc, SATFormaPago.key.alias('forma_pago'), SATUsoCfdi.key.alias('uso_cfdi'), Socios.codigo_postal) .join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios) .join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios) .where((Socios.es_cliente==True & Socios.es_activo==True) & (Socios.rfc.contains(name) | Socios.nombre.contains(name))) .dicts()) return tuple(rows) return {'ok': False} @classmethod def add(cls, values): accounts = util.loads(values.pop('accounts', '[]')) fields = cls._clean(cls, values) regimenes = fields.pop('regimenes', ()) w = ((Socios.rfc==fields['rfc']) & (Socios.slug==fields['slug'])) if Socios.select().where(w).exists(): msg = 'Ya existe el RFC y Razón Social' data = {'ok': False, 'row': {}, 'new': True, 'msg': msg} return data try: obj = Socios.create(**fields) except IntegrityError as e: msg = 'Ocurrio un error, al dar de alta el emisor' data = {'ok': False, 'row': {}, 'new': True, 'msg': msg} return data #~ ToDo Agregar tags for account in accounts: try: bank = SATBancos.get_by_name(account['banco']) fields = { 'socio': obj, 'banco': bank, 'cuenta': account['cuenta'], 'clabe': account['clabe'], } o = SociosCuentasBanco.create(**fields) except IntegrityError: pass for regimen in regimenes: try: fields = { 'socio': obj, 'regimen': regimen, } SociosRegimenes.create(**fields) except IntegrityError: pass row = { 'id': obj.id, 'rfc': obj.rfc, 'nombre': obj.nombre, 'saldo_cliente': obj.saldo_cliente, } data = {'ok': True, 'row': row, 'new': True} return data @classmethod def actualizar(cls, values, id): fields = cls._clean(cls, values) fields.pop('accounts', '') regimenes = fields.pop('regimenes', ()) try: q = Socios.update(**fields).where(Socios.id==id) q.execute() except IntegrityError: msg = 'Ya existe el RFC y Razón Social' data = {'ok': False, 'row': {}, 'new': True, 'msg': msg} return data obj = Socios.get(Socios.id==id) q = SociosRegimenes.delete().where(SociosRegimenes.socio==id) q.execute() for regimen in regimenes: try: fields = { 'socio': obj, 'regimen': regimen, } SociosRegimenes.create(**fields) except IntegrityError: pass obj = Socios.get(Socios.id==id) row = { 'id': id, 'rfc': obj.rfc, 'nombre': obj.nombre, 'saldo_cliente': obj.saldo_cliente, } data = {'ok': True, 'row': row, 'new': False} return data @classmethod def remove(cls, id): count = (Facturas .select(fn.COUNT(Facturas.id)).join(Socios) .where(Socios.id==id) .count()) if count: return False q = SociosCuentasBanco.delete().where(SociosCuentasBanco.socio==id) q.execute() q = SociosRegimenes.delete().where(SociosRegimenes.socio==id) q.execute() q = Socios.delete().where(Socios.id==id) return bool(q.execute()) @classmethod def opt(cls, args): opt = args.get('opt', '') if opt == 'reset': return cls._reset_saldo(cls, args['id']) return {'ok': False} # ~ v2 @classmethod def post(cls, values): opt = values['opt'] args = values['values'] return getattr(cls, f'_{opt}')(cls, args) class SociosCuentasBanco(BaseModel): socio = ForeignKeyField(Socios) activa = BooleanField(default=True) banco = ForeignKeyField(SATBancos) cuenta = TextField(default='') clabe = TextField(default='') moneda = ForeignKeyField(SATMonedas, null=True) class Meta: order_by = ('socio', 'banco', 'cuenta') indexes = ( (('socio', 'banco', 'cuenta'), True), ) def __str__(self): return '{} ({})'.format(self.banco.name, self.cuenta[-4:]) def _get_by_name(self, values): name = values['name'] query = (SociosCuentasBanco .select() .where(SociosCuentasBanco.activa==True, Socios.nombre==name) .join(Socios).switch(SociosCuentasBanco) ) rows = tuple([{'id': q.id, 'value': str(q)} for q in query]) return {'ok': True, 'values': rows} def _get_by_partner(self, values): id = int(values['id_partner']) rows = (SociosCuentasBanco.select( SociosCuentasBanco.id, SQL(" '-' AS delete"), SATBancos.name.alias('banco'), SociosCuentasBanco.cuenta, SociosCuentasBanco.clabe) .join(SATBancos).switch(SociosCuentasBanco) .where(SociosCuentasBanco.socio==id) .dicts()) return tuple(rows) @classmethod def get_values(cls, values): opt = values.pop('opt') return getattr(cls, '_get_{}'.format(opt))(cls, values) @classmethod def post(cls, values): opt = values.pop('opt') return getattr(cls, '_{}'.format(opt))(cls, values) def _new(self, values): values = util.loads(values['values']) bank = SATBancos.get_by_name(values['banco']) fields = { 'socio': values['id_partner'], 'banco': bank, 'cuenta': values['cuenta'], 'clabe': values['clabe'], } try: obj = SociosCuentasBanco.create(**fields) except IntegrityError as e: msg = 'Ya existe esta cuenta' data = {'ok': False, 'msg': msg} return data msg = 'Cuenta de banco agregada correctamente' data = {'ok': True, 'id': obj.id, 'msg': msg} return data def _delete(self, values): values = util.loads(values['values']) id = int(values['id']) q = SociosCuentasBanco.delete().where(SociosCuentasBanco.id==id) result = bool(q.execute()) msg = 'Cuenta borrada correctamente' return {'ok': result, 'msg': msg} @classmethod def validate_partner_account(cls, id, invoices): id_invoice = int(tuple(invoices.keys())[0]) account = SociosCuentasBanco.get(SociosCuentasBanco.id==id) invoice = Facturas.get(Facturas.id==id_invoice) return account.socio == invoice.cliente class SociosRegimenes(BaseModel): socio = ForeignKeyField(Socios) regimen = ForeignKeyField(SATRegimenes) class Meta: indexes = ( (('socio', 'regimen'), True), ) @classmethod def get_by_key(cls, socio, user=None): fields = (SATRegimenes.key.alias('id'), SATRegimenes.name.alias('value')) where = (SociosRegimenes.socio == socio) regimenes = (SociosRegimenes .select(*fields) .where(where) .join(SATRegimenes).switch(SociosRegimenes) .dicts() ) return tuple(regimenes) @classmethod def get_by_socio(cls, socio, user=None): fields = (SATRegimenes.id,) where = (SociosRegimenes.socio == socio) regimenes = (SociosRegimenes .select(*fields) .where(where) .join(SATRegimenes).switch(SociosRegimenes) .tuples() ) regimenes = [r[0] for r in regimenes] return regimenes @classmethod def _get_by_id(cls, filters, user): id = int(filters['id']) return cls.get_by_key(id) @classmethod def get_data(cls, filters, user): # ~ print('FILERS', filters) opt = filters['opt'] return getattr(cls, f'_get_{opt}')(filters, user) class Contactos(BaseModel): socio = ForeignKeyField(Socios) titulo = ForeignKeyField(TipoTitulo) foto = TextField(default='') nombre = TextField(index=True) paterno = TextField(index=True) materno = TextField(default='') fecha_nacimiento = DateField(null=True) notas = TextField(default='') class Meta: order_by = ('socio', 'nombre') indexes = ( (('socio', 'nombre', 'paterno', 'materno'), True), ) class ContactoDirecciones(BaseModel): contacto = ForeignKeyField(Contactos) tipo = ForeignKeyField(TipoDireccion) direccion = TextField() class Meta: order_by = ('contacto',) indexes = ( (('contacto', 'tipo', 'direccion'), True), ) class ContactoTelefonos(BaseModel): contacto = ForeignKeyField(Contactos) tipo = ForeignKeyField(TipoTelefono) telefono = TextField() class Meta: order_by = ('contacto',) indexes = ( (('contacto', 'tipo', 'telefono'), True), ) class ContactoCorreos(BaseModel): contacto = ForeignKeyField(Contactos) tipo = ForeignKeyField(TipoCorreo) correo = TextField() class Meta: order_by = ('contacto',) indexes = ( (('contacto', 'tipo', 'correo'), True), ) class MovimientosBanco(BaseModel): cuenta = ForeignKeyField(CuentasBanco) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) descripcion = TextField(default='') forma_pago = ForeignKeyField(SATFormaPago) retiro = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) deposito = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) cancelado = BooleanField(default=False) conciliado = BooleanField(default=False) moneda = TextField(default='MXN') # Complemento de pagos tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6, auto_round=True) numero_operacion = TextField(default='') cuenta_socio = ForeignKeyField(SociosCuentasBanco, null=True) tipo_cadena_pago = TextField(default='') certificado_pago = TextField(default='') cadena_pago = TextField(default='') sello_pago = TextField(default='') class Meta: order_by = ('fecha', 'id') def _ultimo_saldo(self, cuenta, fecha): query = (MovimientosBanco .select() .where( (MovimientosBanco.cuenta==cuenta) & (MovimientosBanco.fecha<=fecha) & (MovimientosBanco.cancelado==False))[-1] ) return round(float(query.saldo), DECIMALES) def _movimiento_anterior(self, cuenta, fecha): query = (MovimientosBanco .select() .where( (MovimientosBanco.cuenta==cuenta) & (MovimientosBanco.fecharow.fecha) & (MovimientosBanco.cancelado==False)) ) saldo = round(Decimal(row.saldo), DECIMALES) for mov in query: mov.saldo = saldo + mov.deposito - mov.retiro mov.save() saldo = mov.saldo CuentasBanco.actualizar_saldo(row.cuenta, saldo) return saldo def _update_description(self, values): try: obj = MovimientosBanco.get(MovimientosBanco.id==values['id']) except MovimientosBanco.DoesNotExist: msg = 'No se encontró el movimiento' return {'ok': False, 'msg': msg} obj.descripcion = values['description'] obj.save() msg = 'Descripción actualizada correctamente' return {'ok': True, 'msg': msg} @classmethod def post(cls, values): opt = values.pop('opt') return getattr(cls, '_{}'.format(opt))(cls, values) def _add(self, values): ids = values.pop('ids', '') cuenta_socio = values.get('cuenta_socio', None) if not ids: cuenta_socio = None if ids and cuenta_socio and \ not SociosCuentasBanco.validate_partner_account(cuenta_socio, util.loads(ids)): msg = 'La cuenta seleccionada no corresponde al cliente' data = {'ok': False, 'msg': msg} return data if ids and not Facturas.validate_count_partners(util.loads(ids)): msg = 'Facturas relacionadas a diferentes clientes' data = {'ok': False, 'msg': msg} return data actualizar = False if 'saldo' in values: saldo = values['saldo'] else: actualizar = True hora = values.pop('hora') account = CuentasBanco.get(CuentasBanco.id==int(values['cuenta'])) values['fecha'] = '{}T{}'.format(values['fecha'][:10], hora) values['cuenta'] = account values['cuenta_socio'] = cuenta_socio values['moneda'] = account.moneda.key values['retiro'] = util.get_float(values['retiro']) values['deposito'] = util.get_float(values['deposito']) values['tipo_cambio'] = util.get_float( values.get('tipo_cambio', 1.00), True) values['forma_pago'] = int(values['forma_pago']) ultimo_saldo = self._ultimo_saldo( self, values['cuenta'], values['fecha']) values['saldo'] = \ ultimo_saldo - values['retiro'] + values['deposito'] with database_proxy.transaction(): try: obj = MovimientosBanco.create(**values) except IntegrityError: msg = 'Error al agregar el movimiento' return {'ok': False, 'msg': msg} if actualizar: saldo = self._actualizar_saldos(self, obj) if ids: FacturasPagos.add(obj, util.loads(ids)) return {'ok': True, 'saldo': saldo} def _cancel(self, values): id = int(values['id']) try: obj = MovimientosBanco.get(MovimientosBanco.id==id) except MovimientosBanco.DoesNotExist: msg = 'No se encontró el movimiento' return {'ok': False, 'msg': msg} if obj.cancelado: msg = 'El movimiento ya esta cancelado' return {'ok': False, 'msg': msg} if obj.conciliado: msg = 'El movimiento esta conciliado, no se puede cancelar' return {'ok': False, 'msg': msg} # ~ filters = (CfdiPagos.movimiento==obj) # ~ cp = CfdiPagos.select().where(filters).count() # ~ if cp > 0: # ~ msg = 'El movimiento tiene Factura de Pago, no se puede cancelar' # ~ return {'ok': False, 'msg': msg} with database_proxy.transaction(): obj.cancelado = True obj.save() FacturasPagos.cancelar(obj) obj = self._movimiento_anterior(self, obj.cuenta, obj.fecha) self._actualizar_saldos(self, obj) balance = CuentasBanco.get_saldo(obj.cuenta.id) msg = 'Movimiento cancelado correctamente' return {'ok': True, 'msg': msg, 'balance': balance} @classmethod def con(cls, id): cant = (MovimientosBanco .select(MovimientosBanco.id) .where(MovimientosBanco.cuenta==id) .count() ) if cant > 2: return {'ok': True} return {'ok': False} @classmethod def get_(cls, values): cuenta = int(values['cuenta']) if 'fechas' in values: rango = util.loads(values['fechas']) fd = (MovimientosBanco.fecha.between( util.get_date(rango['start']), util.get_date(rango['end'], True))) filtros = (fd & (MovimientosBanco.cuenta==cuenta) & (MovimientosBanco.cancelado==False) ) else: year = int(values['year']) mes = int(values['mes']) if year == -1: fy = (MovimientosBanco.fecha.year > 0) else: fy = (MovimientosBanco.fecha.year == year) if mes == -1: fm = (MovimientosBanco.fecha.month > 0) else: fm = (MovimientosBanco.fecha.month == mes) filtros = (fy & fm & (MovimientosBanco.cuenta==cuenta) & (MovimientosBanco.cancelado==False) ) rows = tuple(MovimientosBanco.select( MovimientosBanco.id, MovimientosBanco.fecha, MovimientosBanco.numero_operacion, SATFormaPago.name.alias('way_payment'), MovimientosBanco.descripcion, MovimientosBanco.retiro, MovimientosBanco.deposito, MovimientosBanco.saldo, fn.COUNT(CfdiPagos.id).alias('invoice') ) .join(SATFormaPago).switch(MovimientosBanco) .join(CfdiPagos, JOIN.LEFT_OUTER).switch(MovimientosBanco) .where(filtros) .group_by(MovimientosBanco.id, SATFormaPago.name) .dicts() ) return {'ok': True, 'rows': rows} class Alumnos(BaseModel): rfc = TextField(null=True) curp = TextField(index=True, unique=True) foto = TextField(default='') nombre = TextField(index=True) paterno = TextField(index=True) materno = TextField(default='') calle = TextField(default='') no_exterior = TextField(default='') no_interior = TextField(default='') colonia = TextField(default='') municipio = TextField(default='') estado = TextField(default='') pais = TextField(default='') codigo_postal = TextField(default='') notas = TextField(default='') telefonos = TextField(default='') correos = TextField(default='') es_activo = BooleanField(default=True) fecha_alta = DateField(default=util.now) fecha_nacimiento = DateField(null=True) factura = ForeignKeyField(Socios, null=True) grupo = ForeignKeyField(Grupos, null=True) def __str__(self): t = '{} {} {}' return t.format(self.nombre, self.paterno, self.materno) class Meta: order_by = ('nombre', 'paterno') def _clean(self, values): fields = util.clean(util.loads(values)) fields['rfc'] = fields['rfc'].upper().strip() fields['curp'] = fields['curp'].upper() fields['nombre'] = util.spaces(fields['nombre']) fields['paterno'] = util.spaces(fields['paterno']) fields['materno'] = util.spaces(fields['materno']) return fields def _get(self, where): rows = (Alumnos .select() .where(where) .dicts() ) return tuple(rows) @classmethod def get_by_name(cls, values): rows = () name = values.get('name', '') if name: rows = (Alumnos .select( Alumnos.id, Alumnos.nombre, Alumnos.paterno, Alumnos.materno, Alumnos.rfc) .where((Alumnos.es_activo==True) & (Alumnos.nombre.contains(name) | Alumnos.paterno.contains(name) | Alumnos.materno.contains(name) | Alumnos.rfc.contains(name))) .dicts()) rows = tuple(rows) return rows @classmethod def get_by(cls, values): if 'id' in values: id = int(values['id']) w = (Alumnos.id==id) rows = cls._get(cls, w) return rows[0] if not values: w = None return cls._get(cls, w) @classmethod def add(cls, values): fields = cls._clean(cls, values) try: obj = Alumnos.create(**fields) except IntegrityError as e: msg = 'Ya existe un alumno con este CURP' data = {'ok': False, 'msg': msg} return data data = {'ok': True} return data @classmethod def actualizar(cls, values): fields = cls._clean(cls, values) id = int(fields.pop('id')) try: q = Alumnos.update(**fields).where(Alumnos.id==id) q.execute() except IntegrityError: msg = 'Ya existe un Alumno con este CURP' data = {'ok': False, 'msg': msg} return data data = {'ok': True} return data @classmethod def remove(cls, id): q = Alumnos.delete().where(Alumnos.id==id) return bool(q.execute()) class AlumnosParientes(BaseModel): alumno = ForeignKeyField(Alumnos) tipo_pariente = ForeignKeyField(TipoPariente) foto = TextField(default='') nombre = TextField(index=True) paterno = TextField(index=True) materno = TextField(default='') fecha_nacimiento = DateField(null=True) puede_recoger = BooleanField(default=False) class Meta: order_by = ('alumno',) class ParienteDirecciones(BaseModel): pariente = ForeignKeyField(AlumnosParientes) tipo = ForeignKeyField(TipoDireccion) direccion = TextField() class Meta: order_by = ('pariente',) class ParienteTelefonos(BaseModel): pariente = ForeignKeyField(AlumnosParientes) tipo = ForeignKeyField(TipoTelefono) telefono = TextField() class Meta: order_by = ('pariente',) class ParienteCorreos(BaseModel): pariente = ForeignKeyField(AlumnosParientes) tipo = ForeignKeyField(TipoCorreo) correo = TextField() class Meta: order_by = ('pariente',) class Almacenes(BaseModel): name = TextField(unique=True) ubicacion = TextField(default='') class Meta: order_by = ('name',) @classmethod def get_for_sucursales(cls): rows = (Almacenes .select( Almacenes.id, Almacenes.name.alias('value'), ) .dicts() ) return tuple(rows) @classmethod def _get_for_select(cls, values={}): rows = (Almacenes .select( Almacenes.id, Almacenes.name.alias('value'), ) .dicts() ) return tuple(rows) @classmethod def _get_all(cls, values): rows = (Almacenes .select( Almacenes.id, Almacenes.name, ) .dicts() ) return tuple(rows) @classmethod def get_data(cls, values): opt = values['opt'] return getattr(cls, f'_get_{opt}')(values) @classmethod def _create(cls, args, user): try: obj, created = Almacenes.get_or_create(**args) except Exception as e: log.error(e) msg = 'Ocurrio un error al agregar el almacen' result = {'ok': False, 'msg': msg} return result if created: rows = Almacenes._get_all({}) result = {'ok': True, 'rows': rows} else: msg = 'El almacen ya existe' result = {'ok': True, 'msg': msg} return result @classmethod def _delete(cls, args, user): id = args['id'] try: q = Almacenes.delete().where(Almacenes.id==id) r = bool(q.execute()) result = {'ok': r} except Exception as e: log.error(e) result = {'ok': False, 'msg': 'Ocurrio un error al intentar eliminar'} return result @classmethod def post(cls, values, user): opt = values['opt'] args = utils.loads(values['values']) return getattr(cls, f'_{opt}')(args, user) class Sucursales(BaseModel): warehouse = ForeignKeyField(Almacenes, null=True) nombre = TextField(default='') direccion = TextField(default='') serie_facturas = TextField(default='') serie_tickets = TextField(default='') class Meta: order_by = ('nombre',) @classmethod def _get_for_admin(cls, args): rows = (Sucursales .select( Sucursales.id, Sucursales.nombre.alias('name'), Sucursales.serie_facturas.alias('serie_invoice'), Sucursales.serie_tickets, Almacenes.name.alias('warehouse'), ) .join(Almacenes, JOIN.LEFT_OUTER).switch(Sucursales) .dicts() ) return tuple(rows) @classmethod def _get_for_select(cls, args): fields = ( Sucursales.id, Sucursales.nombre.alias('value') ) rows = Sucursales.select(*fields).dicts() return tuple(rows) @classmethod def _get_there_are_branchs(cls, args): result = bool(Sucursales.select().count()) return result @classmethod def get_data(cls, values): opt = values['opt'] return getattr(cls, f'_get_{opt}')(values) @classmethod def _create(cls, args): try: values = utils.loads(args) Sucursales.create(**values) result = {'ok': True} except Exception as e: log.error(e) msg = 'Ocurrio un error al agregar la sucursal' result = {'ok': False, 'msg': msg} return result @classmethod def _delete(cls, args): values = utils.loads(args) id = values['id'] try: q = Sucursales.delete().where(Sucursales.id==id) r = bool(q.execute()) result = {'ok': r} except Exception as e: log.error(e) result = {'ok': False, 'msg': 'Ocurrio un error al intentar eliminar'} return result @classmethod def post(cls, values): opt = values['opt'] args = values['values'] return getattr(cls, f'_{opt}')(args) class Usuarios(BaseModel): usuario = TextField(unique=True) nombre = TextField(default='') apellidos = TextField(default='') correo = TextField(default='') contraseña = PasswordField() es_superusuario = BooleanField(default=False) es_admin = BooleanField(default=False) es_activo = BooleanField(default=True) fecha_ingreso = DateTimeField(default=util.now) ultimo_ingreso = DateTimeField(null=True) sucursal = ForeignKeyField(Sucursales, null=True) rol = ForeignKeyField(Roles, null=True) def __str__(self): t = '{} {} ({})' return t.format(self.nombre, self.apellidos, self.usuario) class Meta: order_by = ('nombre', 'apellidos') def _get_is_admin(self, filters, user): return {'is_admin': user.es_admin} def _get_is_superadmin(self, filters, user): return {'is_superadmin': user.es_superusuario} @classmethod def get_data(cls, filters, user): method = f"_get_{filters['opt']}" return getattr(cls, method)(cls, filters, user) @classmethod def _set_branch_null(cls, args, user): id = args['id'] values = {'sucursal': None} where = (Usuarios.id == id) q = (Usuarios .update(**values) .where(where) ) result = bool(q.execute()) response = {'ok': result} return response @classmethod def _set_branch(cls, args, user): id = args['id_user'] values = {'sucursal': args['id_branch']} where = (Usuarios.id == id) q = (Usuarios .update(**values) .where(where) ) result = bool(q.execute()) response = {'ok': result} return response @classmethod def post(cls, values, user): opt = values['opt'] args = utils.loads(values['values']) return getattr(cls, f'_{opt}')(args, user) @classmethod def add(cls, values): values['contraseña'] = values.pop('contra') try: Usuarios.create(**values) return {'ok': True} except Exception as e: log.error(e) msg = 'Ocurrio un error, consulta a soporte técnico' return {'ok': False, 'msg': msg} @classmethod def remove(cls, id): q = Usuarios.delete().where(Usuarios.id==int(id)) return bool(q.execute()) @classmethod def edit(self, values): # ~ print (values) id = int(values.pop('id')) try: if 'contra' in values: values['contraseña'] = values.pop('contra') q = Usuarios.update(**values).where(Usuarios.id==id) result = {'ok': bool(q.execute())} except IntegrityError: msg = 'El usuario ya existe' result = {'ok': False, 'msg': msg} return result @classmethod def actualizar(self, values, user): id = int(values['id']) v = {'0': False, '1': True} if values['field'] == 'es_superusuario' and not user.es_superusuario: msg = 'Solo un super usuario puede hacer este cambio' return {'ok': False, 'msg': msg} if values['field'] == 'es_activo': q = (Usuarios .update(**{'es_activo': v[values['value']]}) .where(Usuarios.id==id)) result = bool(q.execute()) elif values['field'] == 'es_admin': q = (Usuarios .update(**{'es_admin': v[values['value']]}) .where(Usuarios.id==id)) result = bool(q.execute()) elif values['field'] == 'es_superusuario': q = (Usuarios .update(**{'es_superusuario': v[values['value']]}) .where(Usuarios.id==id)) result = bool(q.execute()) return {'ok': result} @classmethod def get_(cls, user): if user.es_superusuario: rows = Usuarios.select().dicts() else: filters = (Usuarios.es_superusuario == False) rows = Usuarios.select().where(filters).dicts() for row in rows: del row['contraseña'] return tuple(rows) class Productos(BaseModel): categoria = ForeignKeyField(Categorias, null=True) clave = TextField(unique=True, index=True) clave_sat = TextField(default='') descripcion = TextField(index=True) unidad = ForeignKeyField(SATUnidades) valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) ultimo_costo = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) inventario = BooleanField(default=False) existencia = DecimalField(default=0.0, max_digits=18, decimal_places=2, auto_round=True) minimo = DecimalField(default=0.0, max_digits=18, decimal_places=2, auto_round=True) codigo_barras = TextField(default='') cuenta_predial = TextField(default='') es_activo = BooleanField(default=True) impuestos = ManyToManyField(SATImpuestos, related_name='productos') tags = ManyToManyField(Tags, related_name='productos_tags') cantidad_empaque = DecimalField(default=0.0, max_digits=14, decimal_places=4, auto_round=True) is_discontinued = BooleanField(default=False) objeto_impuesto = TextField(default='02') class Meta: order_by = ('descripcion',) @classmethod def _get_by_key(cls, args, user): try: warehouse = user.sucursal.warehouse except: warehouse = None key = args['key'] select = ( Productos.id.alias('id_product'), Productos.clave, Productos.clave_sat, Productos.descripcion, SATUnidades.id.alias('unidad'), Productos.valor_unitario, Productos.descuento, Productos.inventario, Productos.existencia, Productos.objeto_impuesto, ) where = ( (Productos.es_activo==True) & ( (Productos.clave==key) | (Productos.codigo_barras==key) ) ) row = (Productos .select(*select) .join(SATUnidades).switch(Productos) .where(where) .limit(1) .dicts() ) if not len(row): return {'ok': False} obj = row[0] model_pt = Productos.impuestos.get_through_model() taxes = tuple( model_pt .select( model_pt.productos_id.alias('product'), model_pt.satimpuestos_id.alias('tax')) .where(model_pt.productos_id==obj['id_product']).dicts() ) if obj['inventario'] and not warehouse is None: obj['existencia'] = WareHouseProduct.get_exists_by_warehouse( obj['id_product'], warehouse) result = {'ok': True, 'row': obj, 'taxes': taxes} return result @classmethod def get_data(cls, values, user): opt = values.pop('opt') return getattr(cls, f'_get_{opt}')(values, user) @classmethod def next_key(cls): try: with database_proxy.transaction(): value = (Productos .select(fn.Max(cast(Productos.clave, 'int')).alias('fm')) .order_by(SQL('fm')) .scalar()) except Exception as e: values = (Productos .select(Productos.clave) .order_by(Productos.clave) .tuples() ) values = [int(v[0]) for v in values if v[0].isdigit()] value = 0 if values: value = max(values) value = value or 0 value += 1 return {'value': value} @classmethod def get_by_key(cls, values): clave = values.get('key', '') row = (Productos .select( Productos.id.alias('id_product'), Productos.clave, Productos.clave_sat, Productos.descripcion, SATUnidades.id.alias('unidad'), Productos.valor_unitario, Productos.descuento, Productos.inventario, Productos.existencia, Productos.objeto_impuesto) .join(SATUnidades).switch(Productos) .where((Productos.es_activo==True) & ((Productos.clave==clave) | (Productos.codigo_barras==clave))) .dicts() ) if len(row): id = row[0]['id_product'] model_pt = Productos.impuestos.get_through_model() taxes = tuple(model_pt .select( model_pt.productos_id.alias('product'), model_pt.satimpuestos_id.alias('tax')) .where(model_pt.productos_id==id).dicts()) product = row[0] return {'ok': True, 'row': product, 'taxes': taxes} return {'ok': False} def _validate_import(self, product): product['categoria'] = Categorias.get_by_id(int(product['categoria'])) product['unidad'] = SATUnidades.get_by_name(product['unidad']) product['inventario'] = bool(product['inventario']) if not product['inventario']: product['existencia'] = 0.0 impuestos = product['impuestos'].split('|') if not impuestos: taxes = [SATImpuestos.select().where(SATImpuestos.id==6)] else: taxes = [] for i in range(0, len(impuestos), 3): w = ( (SATImpuestos.key == impuestos[i]) & (SATImpuestos.name == impuestos[i+1]) & (SATImpuestos.tasa == float(impuestos[i+2])) ) try: taxes.append(SATImpuestos.get(w)) except: pass product['impuestos'] = taxes w = (Productos.clave==product['clave']) return product, w def _import(self): emisor = Emisor.select()[0] rows, msg = util.import_products(emisor.rfc) if not rows: return {'ok': False, 'msg': msg} cs = 0 np = 0 ap = 0 for p in rows: data, w = self._validate_import(self, p) if data['unidad'] is None: msg = 'Producto: {} - No se encontró la unidad'.format( data['clave']) log.error(msg) continue result = util.get_sat_key('productos', data['clave_sat']) if not result['ok']: msg = 'Producto: {} - Clave SAT incorrecta: {}'.format( data['clave'], data['clave_sat']) log.error(msg) cs += 1 continue # ~ print (data) taxes = data.pop('impuestos') try: with database_proxy.transaction(): if Productos.select().where(w).exists(): q = Productos.update(**data).where(w) q.execute() obj = Productos.get(w) obj.impuestos = taxes log.info('\tProducto actualizado: {}'.format(data['clave'])) ap += 1 else: obj = Productos.create(**data) obj.impuestos = taxes log.info('\tProducto agregado: {}'.format(data['clave'])) np += 1 except Exception as e: msg = 'Error al importar producto: {}'.format(data['clave']) log.error(msg) log.error(e) msg = 'Productos encontrados: {}
'.format(len(rows)) msg += 'Productos agregados: {}
'.format(np) msg += 'Productos actualizados: {}
'.format(ap) msg += 'Productos con problemas: {}
'.format(len(rows) - np - ap) msg += 'Productos con clave SAT erronea: {}'.format(cs) return {'ok': True, 'msg': msg} @classmethod def opt(cls, values): if values['opt'] == 'import': return cls._import(cls) return {'ok': False, 'msg': 'Sin opción'} @classmethod def get_by(cls, values): clave = values.get('id', '') if clave: row = (Productos .select( Productos.id, Productos.clave, Productos.clave_sat, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario, Productos.descuento) .join(SATUnidades).switch(Productos) .where((Productos.es_activo==True) & ((Productos.id==clave) | (Productos.clave==clave))) .dicts()) if len(row): id = row[0]['id'] model_pt = Productos.impuestos.get_through_model() taxes = tuple(model_pt .select( model_pt.productos_id.alias('product'), model_pt.satimpuestos_id.alias('tax')) .where(model_pt.productos_id==id).dicts()) return {'ok': True, 'row': row[0], 'taxes': taxes} return {'ok': False} name = values.get('name', '') if name: rows = (Productos .select( Productos.id, Productos.clave, Productos.clave_sat, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario) .join(SATUnidades).switch(Productos) .where((Productos.es_activo==True) & ((Productos.descripcion.contains(name)) | (Productos.clave.contains(name)))) .dicts() ) return tuple(rows) return {'ok': False} @classmethod def get_(cls, values): if values: id = int(values['id']) row = (Productos .select( Productos.id, Productos.es_activo.alias('es_activo_producto'), Productos.categoria, Productos.clave, Productos.clave_sat, Productos.descripcion, Productos.unidad, Productos.valor_unitario, Productos.codigo_barras, Productos.cuenta_predial, Productos.inventario, Productos.existencia, Productos.minimo, Productos.objeto_impuesto, # ~ Productos.cantidad_empaque.alias('cant_by_packing'), ) .where(Productos.id==id).dicts()[0] ) obj = Productos.get(Productos.id==id) taxes = [row.id for row in obj.impuestos] return {'row': row, 'taxes': taxes} rows = (Productos .select( Productos.id, Productos.es_activo, Productos.clave_sat, Productos.clave, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario, Productos.existencia) .join(SATUnidades) .dicts() ) return {'ok': True, 'rows': tuple(rows)} def _clean(self, values): taxes = util.loads(values.pop('taxes')) descripcion = util.spaces(values.pop('descripcion')) fields = util.clean(values) fields['cantidad_empaque'] = fields.pop('cant_by_packing', 0.0) fields.pop('precio_con_impuestos', '') fields['es_activo'] = fields.pop('es_activo_producto') fields['descripcion'] = descripcion fields['unidad'] = int(fields['unidad']) fields['valor_unitario'] = fields['valor_unitario'].replace( '$', '').replace(',', '') fb = ('es_activo', 'inventario') for name in fb: fields[name] = bool(fields[name].replace('0', '')) return fields, taxes @classmethod def add(cls, values): fields, taxes = cls._clean(cls, values) if Productos.select().where(Productos.clave==fields['clave']).exists(): msg = 'Clave ya existe' return {'ok': False, 'msg': msg} obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes)) with database_proxy.transaction(): obj = Productos.create(**fields) obj.impuestos = obj_taxes row = { 'id': obj.id, 'es_activo': obj.es_activo, 'clave': obj.clave, 'clave_sat': obj.clave_sat, 'descripcion': obj.descripcion, 'unidad': obj.unidad.name, 'valor_unitario': obj.valor_unitario, } data = {'ok': True, 'row': row, 'new': True} return data @classmethod def actualizar(cls, values, id): values['cuenta_predial'] = values.get('cuenta_predial', '') values['codigo_barras'] = values.get('codigo_barras', '') fields, taxes = cls._clean(cls, values) obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes)) with database_proxy.transaction(): q = Productos.update(**fields).where(Productos.id==id) try: q.execute() except IntegrityError: msg = 'Ya existe un producto con esta clave' data = {'ok': False, 'row': {}, 'new': False, 'msg': msg} return data except Exception as e: msg = str(e) if 'productos_clave' in msg: msg = 'Ya existe un producto con esta clave' data = {'ok': False, 'row': {}, 'new': False, 'msg': msg} return data obj = Productos.get(Productos.id==id) obj.impuestos = obj_taxes row = { 'id': obj.id, 'es_activo': obj.es_activo, 'clave': obj.clave, 'clave_sat': obj.clave_sat, 'descripcion': obj.descripcion, 'unidad': obj.unidad.name, 'valor_unitario': obj.valor_unitario, 'existencia': obj.existencia, } data = {'ok': True, 'row': row, 'new': False} return data @classmethod def remove(cls, id): count = (FacturasDetalle .select(fn.COUNT(FacturasDetalle.id)).join(Productos) .where(Productos.id==id) .count() ) if count: return False with database_proxy.transaction(): obj = Productos.get(Productos.id==id) obj.impuestos.clear() obj.tags.clear() q = Productos.delete().where(Productos.id==id) return bool(q.execute()) class WareHouseProduct(BaseModel): warehouse = ForeignKeyField(Almacenes) product = ForeignKeyField(Productos) exists = DecimalField(default=0.0, max_digits=18, decimal_places=2, auto_round=True) class Meta: indexes = ( (('warehouse', 'product'), True), ) @classmethod def _get_by_product(cls, args, user): id = int(args['id']) select = ( Almacenes.id, Almacenes.name.alias('warehouse'), WareHouseProduct.exists, ) where = (WareHouseProduct.product==id) rows = (WareHouseProduct .select(*select) .join(Almacenes) .switch(WareHouseProduct) .where(where) .dicts() ) return tuple(rows) @classmethod def get_exists_by_warehouse(cls, product, warehouse): where = ( (WareHouseProduct.product==product) & (WareHouseProduct.warehouse==warehouse) ) exists = (WareHouseProduct .select(WareHouseProduct.exists) .where(where) .limit(1) .scalar() ) or 0 return exists @classmethod def _get_exists(cls, args, user): id_product = int(args['id']) warehouse = None try: warehouse = user.sucursal.warehouse except: obj = Productos.get(id==id_product) return obj.existencia where = ( (WareHouseProduct.product==id) & (WareHouseProduct.warehouse==warehouse) ) exists = (WareHouseProduct .select(WareHouseProduct.exists) .where(where) .limit(1) .scalar() ) return exists @classmethod def get_data(cls, values, user): opt = values.pop('opt') return getattr(cls, f'_get_{opt}')(values, user) @classmethod def update_exists(cls, args): if args['storage'] is None: return values = dict( warehouse = args['storage'], product = args['product'], ) obj, created = WareHouseProduct.get_or_create(**values) cant = args['cant'] if created: cant = float(cant) obj.exists += cant obj.save() return @classmethod def _adjust_stock(cls, args, user): cant = Decimal(args['cant']) fields = dict( warehouse = args['storage'], product = args['id_product'], ) obj = WareHouseProduct.get(**fields) obj.exists += cant obj.save() total = (WareHouseProduct .select(fn.Sum(WareHouseProduct.exists)).alias('total') .where(WareHouseProduct.product==fields['product']) .limit(1) .scalar() ) query = (Productos .update(existencia=total) .where(Productos.id==fields['product']) ) query.execute() msg = 'ajustar stock' _save_log(user.usuario, msg, 'WHP') result = {'ok': True, 'row': {'existencia': total}} return result @classmethod def post(cls, values, user): opt = values['opt'] args = utils.loads(values['values']) return getattr(cls, f'_{opt}')(args, user) class RangosPrecios(BaseModel): producto = ForeignKeyField(Productos) descripcion = TextField(default='') desde = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) hasta = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) descuento = IntegerField(default=0) class Meta: order_by = ('producto',) class Facturas(BaseModel): cliente = ForeignKeyField(Socios) version = TextField(default=CURRENT_CFDI) serie = TextField(default='') folio = BigIntegerField(default=0) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) fecha_timbrado = DateTimeField(null=True) forma_pago = TextField(default='') condiciones_pago = TextField(default='') subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) moneda = TextField(default='MXN') tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6, auto_round=True) total = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) tipo_comprobante = TextField(default='I') metodo_pago = TextField(default='PUE') lugar_expedicion = TextField(default='') confirmacion = TextField(default='') uso_cfdi = TextField(default='') total_retenciones = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) total_trasladados = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) xml = TextField(default='') uuid = UUIDField(null=True) estatus = TextField(default='Guardada') estatus_sat = TextField(default='Vigente') regimen_fiscal = TextField(default='') divisas = TextField(default='') notas = TextField(default='') saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) pagada = BooleanField(default=False) cancelada = BooleanField(default=False) fecha_cancelacion = DateTimeField(null=True) acuse = TextField(default='') donativo = BooleanField(default=False) anticipo = BooleanField(default=False) egreso_anticipo = BooleanField(default=False) tipo_relacion = TextField(default='') error = TextField(default='') exportacion = TextField(default='01') receptor_regimen = TextField(default='') periodicidad = TextField(default='') class Meta: order_by = ('fecha',) @classmethod def validate_count_partners(cls, ids): """Validate if invoices IDs are of unique client""" filters = (Facturas.id.in_(tuple(ids.keys()))) partners = (Facturas.select(fn.COUNT(Socios.rfc)) .where(filters) .join(Socios).switch(Facturas) .group_by(Socios.rfc) .order_by(Socios.rfc).tuples()) if len(partners) > 1: return False return True @classmethod def cancel(cls, args): obj = Facturas.get(Facturas.id==args['id']) if obj.uuid is None: obj.estatus = 'Cancelada' obj.cancelada = True obj.fecha_cancelacion = util.now() obj.save() msg = 'Factura cancelada correctamente' return {'ok': True, 'msg': msg, 'row': {'estatus': obj.estatus}} return cls._cancel_xml_sign(obj, args) @classmethod def _cancel_xml_sign(cls, invoice, args): if not invoice.version in CANCEL_VERSION: msg = 'Solo es posible cancelar CFDI >= 3.3' return {'ok': False, 'msg': msg} pac = utils.get_pac_by_rfc(invoice.xml) auth = Configuracion.get_({'fields': 'auth_by_pac', 'pac': pac}) certificado = Certificado.get(Certificado.es_fiel==False) result = utils.cancel_xml_sign(invoice, args, auth, certificado) if result['ok']: invoice.estatus = 'Cancelada' invoice.error = '' invoice.cancelada = True invoice.fecha_cancelacion = result['date'] invoice.acuse = result['acuse'] or '' cls._actualizar_saldo_cliente(cls, invoice, True) cls._update_inventory(cls, invoice, True) cls._uncancel_tickets(cls, invoice) else: invoice.error = result['msg'] invoice.save() data = {'ok': result['ok'], 'msg': result['msg'], 'row': result['row']} return data def _cancel_xml(self, id): msg = 'Factura cancelada correctamente' auth = Emisor.get_auth() certificado = Certificado.select()[0] obj = Facturas.get(Facturas.id==id) if obj.version == '3.2': msg = 'No es posible cancelar CFDI 3.2' return {'ok': False, 'msg': msg} data, result = util.cancel_xml(auth, obj.uuid, certificado) if data['ok']: obj.estatus = 'Cancelada' obj.error = '' obj.cancelada = True obj.fecha_cancelacion = result['date'] obj.acuse = result['acuse'] or '' self._actualizar_saldo_cliente(self, obj, True) self._update_inventory(self, obj, True) self._uncancel_tickets(self, obj) else: obj.error = data['msg'] obj.save() return data @utils.run_in_thread def _uncancel_tickets(self, invoice): query = (Tickets .update(estatus='Generado', cancelado=False, factura=None) .where(Tickets.factura==invoice) ) query.execute() return def _get_filters(self, values): if 'start' in values and 'end' in values: filters = Facturas.fecha.between( utils.parse_date(values['start']), utils.parse_date(values['end'], True) ) else: if values['year'] == '-1': fy = (Facturas.fecha.year > 0) else: fy = (Facturas.fecha.year == int(values['year'])) if values['month'] == '-1': fm = (Facturas.fecha.month > 0) else: fm = (Facturas.fecha.month == int(values['month'])) filters = (fy & fm) if 'client' in values: filters &= (Socios.nombre==values['client']) return filters def _get_invoices(self, filters, user): filter_serie = False filter_by_user = Configuracion.get_bool('chk_config_user_show_doc') if not filter_by_user: if not user.sucursal is None and not user.sucursal.serie_facturas is None: filter_serie = (Facturas.serie==user.sucursal.serie_facturas) if filter_serie: filters &= filter_serie rows = tuple(Facturas.select( Facturas.id, Facturas.serie, Facturas.folio, Facturas.uuid, Facturas.fecha, Facturas.tipo_comprobante, Facturas.estatus, case(Facturas.pagada, ( (True, 'Si'), (False, 'No'), )).alias('paid'), Facturas.metodo_pago, Facturas.total, Facturas.moneda.alias('currency'), Facturas.total_mn, Socios.nombre.alias('cliente')) .where(filters) .join(Socios) .switch(Facturas).dicts() ) return {'ok': True, 'rows': rows} def _get_by_dates(self, filters, user): filters = self._get_filters(self, filters) return self._get_invoices(self, filters, user) def _get_by_notes(self, filters, user): notes = filters['notes'] filters = self._get_filters(self, filters) filters &= (Facturas.notas.contains(notes)) return self._get_invoices(self, filters, user) @classmethod def get_by(cls, filters, user): return getattr(cls, f"_get_by_{filters['by']}")(cls, filters, user) @classmethod def filter_years(cls): data = [{'id': -1, 'value': 'Todos'}] rows = (Facturas .select(Facturas.fecha.year.alias('year')) .group_by(Facturas.fecha.year) .order_by(Facturas.fecha.year) ) if not rows is None: data += [{'id': int(r.year), 'value': int(r.year)} for r in rows] return tuple(data) @classmethod def get_xml(cls, id): obj = Facturas.get(Facturas.id==id) name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc) cls._sync_xml(cls, obj) return obj.xml, name @classmethod def get_notes(cls, id): obj = Facturas.get(Facturas.id==int(id)) return {'notes': obj.notas} @classmethod def save_notes(cls, values): obj = Facturas.get(Facturas.id==int(values['id'])) obj.notas = values.get('notes', '') obj.save() return {'ok': True, 'msg': 'Notas guardadas correctamente'} def _get_not_in_xml(self, invoice, emisor): pdf_from = Configuracion.get_('make_pdf_from') or '1' values = {'el.version': VERSION_EMPRESA_LIBRE} values['notas'] = invoice.notas values['fechadof'] = str(emisor.fecha_dof) if invoice.version == '3.2': return values obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal) values['regimenfiscal'] = str(obj) obj = SATUsoCfdi.get(SATUsoCfdi.key==invoice.uso_cfdi) values['usocfdi'] = str(obj) mp = { 'PUE': 'Pago en una sola exhibición', 'PPD': 'Pago en parcialidades o diferido', } if invoice.metodo_pago: values['metododepago'] = 'Método de Pago: ({}) {}'.format( invoice.metodo_pago, mp[invoice.metodo_pago]) if invoice.forma_pago: obj = SATFormaPago.get(SATFormaPago.key==invoice.forma_pago) values['formadepago'] = str(obj) obj = SATMonedas.get(SATMonedas.key==invoice.moneda) values['moneda'] = str(obj) if invoice.tipo_relacion: obj = SATTipoRelacion.get(SATTipoRelacion.key==invoice.tipo_relacion) values['tiporelacion'] = str(obj) receptor = Socios.select().where(Socios.id==invoice.cliente.id).dicts()[0] values['receptor'] = {} for k, v in receptor.items(): values['receptor'][k] = v # ~ use_packing = Configuracion.get_bool('chk_use_packing') # ~ if use_packing: # ~ w = FacturasDetalle.factura == invoice # ~ q = (FacturasDetalle # ~ .select(FacturasDetalle.empaques) # ~ .where(w) # ~ .order_by(FacturasDetalle.id.asc()) # ~ .tuples()) # ~ values['pakings'] = [str(int(r[0])) for r in q] return values @classmethod def get_pdf(cls, id, rfc, sync=True): try: emisor = Emisor.select()[0] except IndexError: return b'', 'sin_datos_de_emisor.pdf' obj = Facturas.get(Facturas.id==id) name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc) if obj.uuid is None: return b'', name pdf_from = Configuracion.get_('make_pdf_from') or '1' values = cls._get_not_in_xml(cls, obj, emisor) #Tmp to v2 data = util.get_data_from_xml(obj, values, pdf_from) data.update(utils.CfdiToDict(obj.xml).values) doc = util.to_pdf(data, emisor.rfc, pdf_from=pdf_from) if sync: target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/') cls._sync_pdf(cls, doc, name, target) return doc, name def _get_concepto(self, node, currency): d = util.get_dict(node.attrib) concepto = { 'clave': f"{d['noidentificacion']}
({d['claveprodserv']})", 'descripcion': self._get_description(self, node, d), 'unidad': f"{d['unidad']}
({d['claveunidad']})", 'cantidad': d['cantidad'], 'valorunitario': util.format_currency(d['valorunitario'], currency), 'importe': util.format_currency(d['importe'], currency), } return concepto def _get_description(self, node, data): return data['descripcion'] def _get_tax(self, node, data, currency): type_tax = 'Traslado' if 'Retenciones' in node.tag: type_tax = 'Retención' for n in node: d = util.get_dict(n.attrib) name = VALUES_PDF['TAX'].get(d['impuesto']) tasa = '' if 'tasaocuota' in d: tasa = f"{float(d['tasaocuota']):.2f}" title = f"{type_tax} {name} {tasa}" importe = util.format_currency(d['importe'], currency) data['totales'].append((title, importe)) return def _get_others_values(self, invoice, emisor): data = { 'rfc': emisor.rfc.lower(), 'version': invoice.version, 'cancelada': VALUES_PDF['CANCEL'].get(invoice.cancelada), 'cfdi_notas': invoice.notas, } xml = util.parse_xml2(invoice.xml) d = util.get_dict(xml.attrib) d.pop('Certificado', '') currency = d.get('Moneda', 'MXN') data.update({f'cfdi_{k.lower()}': v for k, v in d.items()}) data['cfdi_tipodecomprobante'] = \ VALUES_PDF['TYPE'][data['cfdi_tipodecomprobante']] if data.get('cfdi_formapago', ''): data['cfdi_formapago'] = str( SATFormaPago.get_by_key(data['cfdi_formapago'])) if data.get('cfdi_metodopago', ''): data['cfdi_metodopago'] = 'Método de Pago: ({}) {}'.format( data['cfdi_metodopago'], VALUES_PDF['METHOD'][data['cfdi_metodopago']]) data['cfdi_moneda'] = str(SATMonedas.get_by_key(currency)) data['cfdi_tipocambio'] = util.format_currency( data['cfdi_tipocambio'], currency, 4) data['cfdi_totalenletras'] = util.to_letters( float(data['cfdi_total']), currency) data['totales'] = [( 'Subtotal', util.format_currency(data['cfdi_subtotal'], currency))] for node in xml: if 'Emisor' in node.tag: d = {f'emisor_{k.lower()}': v for k, v in node.attrib.items()} data.update(d) elif 'Receptor' in node.tag: d = {f'receptor_{k.lower()}': v for k, v in node.attrib.items()} data.update(d) elif 'Conceptos' in node.tag: data['conceptos'] = [] for subnode in node: concepto = self._get_concepto(self, subnode, currency) data['conceptos'].append(concepto) elif 'Impuestos' in node.tag: for subnode in node: self._get_tax(self, subnode, data, currency) elif 'Complemento' in node.tag: for sn in node: if 'TimbreFiscalDigital' in sn.tag: d = {f'timbre_{k.lower()}': v for k, v in sn.attrib.items()} data.update(d) data['emisor_logo'] = data['emisor_rfc'].lower() data['emisor_regimenfiscal'] = str( SATRegimenes.get_by_key(data['emisor_regimenfiscal'])) data['receptor_usocfdi'] = str( SATUsoCfdi.get_by_key(data['receptor_usocfdi'])) data['totales'].append(( 'Total', util.format_currency(data['cfdi_total'], currency))) data['timbre_cadenaoriginal'] = ( f"||{data['timbre_version']}|" f"{data['timbre_uuid']}|" f"{data['timbre_fechatimbrado']}|" f"{data['timbre_sellocfd']}|" f"{data['timbre_nocertificadosat']}||" ) qr_data = ( f"https://verificacfdi.facturaelectronica.sat.gob.mx/" f"default.aspx?&id={data['timbre_uuid']}&re={data['emisor_rfc']}" f"&rr={data['receptor_rfc']}&tt={data['cfdi_total']}" f"&fe={data['cfdi_sello'][-8:]}" ) cbb = utils.get_qr(qr_data, 'png', True) data['cbb'] = f'data:image/png;base64,{cbb}' return data @classmethod def get_html(cls, id): try: emisor = Emisor.select()[0] except IndexError: return '', 'sin_datos_de_emisor.html' obj = Facturas.get(Facturas.id==id) name = '{}{}_{}.html'.format(obj.serie, obj.folio, obj.cliente.rfc) if obj.uuid is None: return '', name data = cls._get_others_values(cls, obj, emisor) doc = util.to_html(data) # ~ util.html_to_pdf(doc) return doc, name @classmethod def get_ods(cls, id, rfc): try: emisor = Emisor.select()[0] except IndexError: return b'', 'sin_datos_de_emisor.pdf' obj = Facturas.get(Facturas.id==id) name = '{}{}_{}.ods'.format(obj.serie, obj.folio, obj.cliente.rfc) if obj.uuid is None: return b'', name values = cls._get_not_in_xml(cls, obj, emisor) data = util.get_data_from_xml(obj, values) data.update(utils.CfdiToDict(obj.xml).values) doc = util.to_pdf(data, emisor.rfc, True) return doc, name @classmethod def get_zip(cls, id, rfc): obj = Facturas.get(Facturas.id==id) name_zip = '{}{}_{}.zip'.format(obj.serie, obj.folio, obj.cliente.rfc) if obj.uuid is None: return b'', name_zip file_xml = cls.get_xml(id) if not file_xml[0]: return b'', name_zip file_pdf = cls.get_pdf(id, rfc) if not file_pdf[0]: return b'', name_zip file_zip = util.to_zip(file_xml, file_pdf) return file_zip, name_zip @util.run_in_thread def _send(self, id, rfc): return Facturas.send(id, rfc) @util.run_in_thread def _sync(self, id, rfc): return Facturas.sync(id, rfc) @util.run_in_thread def _sync_pdf(self, pdf, name_pdf, target): # ~ auth = Emisor.get_auth() files = ( (pdf, name_pdf, target), ) util.sync_cfdi(files) return @util.run_in_thread def _sync_xml(self, obj): emisor = Emisor.select()[0] name_xml = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc) target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/') files = ( (obj.xml, name_xml, target), ) util.sync_cfdi(files) return @util.run_in_thread def _actualizar_saldo_cliente(self, invoice, cancel=False): if invoice.tipo_comprobante == 'T': return if invoice.donativo and invoice.forma_pago == '12': return if invoice.cliente.rfc == RFCS['PUBLIC']: return importe = invoice.total_mn if invoice.tipo_comprobante == 'E': importe *= -1 if cancel: importe *= -1 q = (Socios .update(saldo_cliente=Socios.saldo_cliente + importe) .where(Socios.id==invoice.cliente.id) ) return bool(q.execute()) @classmethod def send(cls, id, rfc): values = Configuracion.get_({'fields': 'correo'}) contra = Configuracion.get_('correo_contra') in_zip = Configuracion.get_bool('chk_config_send_zip') if not values: msg = 'No esta configurado el servidor de correo de salida' return {'ok': False, 'msg': msg} obj = Facturas.get(Facturas.id==id) if obj.uuid is None: msg = 'La factura no esta timbrada' return {'ok': False, 'msg': msg} if not obj.cliente.correo_facturas: msg = 'El cliente no tiene configurado el correo para facturas' return {'ok': False, 'msg': msg} if in_zip: files = (cls.get_zip(id, rfc),) else: files = (cls.get_pdf(id, rfc), cls.get_xml(id)) fields = util.make_fields(obj.xml) starttls = bool(int(values.get('correo_starttls', '0'))) server = { 'servidor': values['correo_servidor'], 'puerto': values['correo_puerto'], 'ssl': bool(int(values['correo_ssl'])), 'starttls': starttls, 'usuario': values['correo_usuario'], 'contra': utils.decrypt(contra, rfc), } options = { 'para': obj.cliente.correo_facturas, 'copia': values.get('correo_copia', ''), 'confirmar': util.get_bool(values.get('correo_confirmacion', '0')), 'asunto': util.make_info_mail(values['correo_asunto'], fields), 'mensaje': util.make_info_mail(values['correo_mensaje'], fields), 'files': files, } data= { 'server': server, 'options': options, } result = util.send_mail(data) if not result['ok'] or result['msg']: return {'ok': False, 'msg': result['msg']} msg = 'Factura enviada correctamente' return {'ok': True, 'msg': msg} @classmethod def sync(cls, id, rfc): obj = Facturas.get(Facturas.id==id) if obj.uuid is None: msg = 'La factura no esta timbrada' return # ~ emisor = Emisor.select()[0] pdf, name_pdf = cls.get_pdf(id, rfc, False) name_xml = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc) target = rfc + '/' + str(obj.fecha)[:7].replace('-', '/') files = ( (obj.xml, name_xml, target), (pdf, name_pdf, target), ) util.sync_cfdi(files) return def _get_filter_folios(self, values): if not 'folio' in values: return '' folios = values['folio'].split('-') if len(folios) == 1: try: folio1 = int(folios[0]) except ValueError: return '' folio2 = folio1 else: try: folio1 = int(folios[0]) folio2 = int(folios[1]) except ValueError: return '' return (Facturas.folio.between(folio1, folio2)) def _get_por_pagar(self, ids): filtros = ( (Facturas.cancelada==False) & (Facturas.uuid.is_null(False)) & (Facturas.tipo_comprobante.in_(('I', 'ingreso'))) & (Facturas.saldo>0) ) if ids: filtros &= (Facturas.id.not_in(ids)) currency = case(Facturas.moneda, ( ('peso', 'MXN'), ), Facturas.moneda) rows = tuple(Facturas .select( Facturas.id, Facturas.serie, Facturas.folio, Facturas.uuid, Facturas.fecha, Facturas.tipo_comprobante, Facturas.estatus, Socios.nombre.alias('cliente'), Facturas.total, currency.alias('currency'), Facturas.total_mn, Facturas.saldo, ) .where(filtros) .join(Socios) .switch(Facturas) .dicts() ) return {'ok': True, 'rows': rows} def _get_reinvoice(self, id): obj = Facturas.get(Facturas.id==id) receptor = { 'id': obj.cliente.id, 'nombre': obj.cliente.nombre, 'rfc': obj.cliente.rfc, 'codigo_postal': obj.cliente.codigo_postal, 'notas': obj.notas, 'regimenes': SociosRegimenes.get_by_key(obj.cliente.id) } invoice = { 'tipo_comprobante': obj.tipo_comprobante, 'serie': Folios.get_id(obj.serie), 'uso_cfdi': obj.uso_cfdi, 'metodo_pago': obj.metodo_pago, 'forma_pago': obj.forma_pago, 'condicion_pago': obj.condiciones_pago, } products = FacturasDetalle.reinvoice(id) data = {'receptor': receptor, 'invoice': invoice, 'products': products} return data def _get_opt(self, values): if values['opt'] == 'foliocustom': return self._get_folio_custom(self, values) if values['opt'] == 'porpagar': return self._get_por_pagar(self, util.loads(values['ids'])) if values['opt'] == 'reinvoice': return self._get_reinvoice(self, int(values['id'])) if values['opt'] == 'detalle': return FacturasDetalle.get_detalle(int(values['id'])) if values['opt'] == 'statussat': return self.get_status_sat(int(values['id'])) cfdis = util.loads(values['cfdis']) if values['year'] == '-1': fy = (Facturas.fecha.year > 0) else: fy = (Facturas.fecha.year == int(values['year'])) if values['month'] == '-1': fm = (Facturas.fecha.month > 0) else: fm = (Facturas.fecha.month == int(values['month'])) if values['opt'] == 'relacionados': folios = self._get_filter_folios(self, values) uuid = values.get('uuid', '') if uuid: f_uuid = (cast(Facturas.uuid, 'text').contains(uuid)) cliente = (Facturas.cliente == int(values['id_cliente'])) if cfdis: f_ids = (Facturas.id.not_in(cfdis)) else: f_ids = (Facturas.id > 0) filters = (fy & fm & cliente & f_ids) if folios: filters = filters & folios elif uuid: filters = filters & f_uuid if values['anticipo'] == '1': filters = filters & (Facturas.anticipo == True) rows = tuple(Facturas .select(Facturas.id, Facturas.serie, Facturas.folio, Facturas.uuid, Facturas.fecha, Facturas.tipo_comprobante, Facturas.estatus, Facturas.total_mn) .where(filters).dicts() ) return {'ok': True, 'rows': rows} @classmethod def get_(cls, values): opt = values.get('opt', '') if opt: return cls._get_opt(cls, values) if 'start' in values: filters = Facturas.fecha.between( util.get_date(values['start']), util.get_date(values['end'], True) ) else: if values['year'] == '-1': fy = (Facturas.fecha.year > 0) else: fy = (Facturas.fecha.year == int(values['year'])) if values['month'] == '-1': fm = (Facturas.fecha.month > 0) else: fm = (Facturas.fecha.month == int(values['month'])) filters = (fy & fm) rows = tuple(Facturas.select( Facturas.id, Facturas.serie, Facturas.folio, Facturas.uuid, Facturas.fecha, Facturas.tipo_comprobante, Facturas.estatus, case(Facturas.pagada, ( (True, 'Si'), (False, 'No'), )).alias('paid'), Facturas.total, Facturas.moneda.alias('currency'), Facturas.total_mn, Socios.nombre.alias('cliente')) .where(filters) .join(Socios) .switch(Facturas).dicts() ) return {'ok': True, 'rows': rows} @classmethod def remove(cls, id, user): obj = Facturas.get(Facturas.id==id) if obj.uuid: return False q = FacturasDetalle.delete().where(FacturasDetalle.factura==obj) q.execute() q = FacturasImpuestos.delete().where(FacturasImpuestos.factura==obj) q.execute() q = FacturasRelacionadas.delete().where(FacturasRelacionadas.factura==obj) q.execute() q = FacturasComplementos.delete().where(FacturasComplementos.factura==obj) q.execute() Tickets.uncancel(obj) m = 'B {}'.format(obj.id) _save_log(user.usuario, m, 'F') return bool(obj.delete_instance()) def _get_folio(self, serie): inicio_serie = Folios.select( Folios.inicio).where(Folios.serie==serie).scalar() inicio = (Facturas .select(fn.Max(Facturas.folio).alias('mf')) .where(Facturas.serie==serie) .order_by(SQL('mf')) .scalar()) if inicio is None or inicio_serie > inicio: inicio = inicio_serie else: inicio += 1 return inicio def _get_folio_custom(self, values): result = {'ok': False, 'msg': 'Folio libre para facturar'} try: folio = int(values['folio']) except ValueError: result['ok'] = True result['msg'] = 'Valor de folio inválido' return result serie = values['serie'] result['ok'] = (Facturas .select() .where((Facturas.serie==serie) & (Facturas.folio==folio)) .exists()) if result['ok']: result['msg'] = 'Folio previamente usado' return result def _calculate_totals(self, invoice, products, tipo_comprobante, user): tax_locales = Configuracion.get_bool('chk_config_tax_locales') tax_locales_truncate = Configuracion.get_bool('chk_config_tax_locales_truncate') tax_decimals = Configuracion.get_bool('chk_config_tax_decimals') # ~ use_packing = Configuracion.get_bool('chk_use_packing') subtotal = 0 descuento_cfdi = 0 totals_tax = {} total_trasladados = None total_retenciones = None locales_traslados = 0 locales_retenciones = 0 warehouse = None try: warehouse = user.sucursal.warehouse except: pass for product in products: id_product = product.pop('id_product') id_student = product.pop('id_student', 0) p = Productos.get(Productos.id==id_product) product['unidad'] = SATUnidades.get(SATUnidades.id==product['unidad']).key product['clave'] = p.clave product['clave_sat'] = p.clave_sat product['cuenta_predial'] = p.cuenta_predial product['factura'] = invoice.id product['producto'] = id_product cantidad = float(product['cantidad']) valor_unitario = float(product['valor_unitario']) descuento = float(product['descuento']) precio_final = valor_unitario - descuento importe = round(cantidad * precio_final, DECIMALES) # ~ if use_packing and p.cantidad_empaque: # ~ product['empaques'] = utils.round_up( # ~ cantidad / float(p.cantidad_empaque)) product['cantidad'] = cantidad product['valor_unitario'] = valor_unitario product['descuento'] = round(descuento * cantidad, DECIMALES) product['precio_final'] = precio_final product['importe'] = round(cantidad * valor_unitario, DECIMALES) descuento_cfdi += product['descuento'] subtotal += product['importe'] if id_student: student = Alumnos.get(Alumnos.id==id_student) product['alumno'] = str(student) product['curp'] = student.curp product['nivel'] = student.grupo.nivel.nombre product['autorizacion'] = student.grupo.nivel.autorizacion.strip() product['warehouse'] = warehouse FacturasDetalle.create(**product) if tipo_comprobante == 'T': continue base = product['importe'] - product['descuento'] for tax in p.impuestos: if tax_locales and tax.tipo == 'R' and tax.key == '000': base = product['importe'] if tax_decimals: impuesto_producto = round(float(tax.tasa) * base, DECIMALES_TAX) else: impuesto_producto = round(float(tax.tasa) * base, DECIMALES) if tax.key == '000' and tax_locales_truncate: impuesto_producto = util.truncate(float(tax.tasa) * base) if tax.tipo == 'T' and tax.key != '000': total_trasladados = (total_trasladados or 0) + impuesto_producto elif tax.tipo == 'R' and tax.key != '000': total_retenciones = (total_retenciones or 0) + impuesto_producto elif tax.tipo == 'T' and tax.key == '000': locales_traslados += impuesto_producto elif tax.tipo == 'R' and tax.key == '000': locales_retenciones += impuesto_producto if tax.id in totals_tax: totals_tax[tax.id].base += base totals_tax[tax.id].suma_impuestos += impuesto_producto else: tax.base = base tax.suma_impuestos = impuesto_producto totals_tax[tax.id] = tax if tipo_comprobante == 'T': subtotal = 0.0 descuento = 0.0 else: for tax in totals_tax.values(): if tax.tipo == 'E': continue invoice_tax = { 'factura': invoice.id, 'impuesto': tax.id, 'base': tax.base, 'importe': round(tax.suma_impuestos, DECIMALES), } FacturasImpuestos.create(**invoice_tax) if not total_trasladados is None: total_trasladados = round(total_trasladados, DECIMALES) if not total_retenciones is None: total_retenciones = round(total_retenciones, DECIMALES) total = subtotal - descuento_cfdi + \ (total_trasladados or 0) - (total_retenciones or 0) \ + locales_traslados - locales_retenciones total = round(total, DECIMALES) # ~ exchange_rate = float(invoice.tipo_cambio) total_mn = round(total * invoice.tipo_cambio, DECIMALES) # ~ total_mn = round(total * exchange_rate, DECIMALES) data = { 'subtotal': subtotal, 'descuento': descuento_cfdi, 'total': total, 'total_mn': total_mn, 'total_trasladados': total_trasladados, 'total_retenciones': total_retenciones, } return data def _guardar_relacionados(self, invoice, relacionados): for cfdi in relacionados: data = { 'factura': invoice, 'factura_origen': cfdi, } FacturasRelacionadas.create(**data) return def _guardar_ine(self, invoice, valores): if not valores: return data = { 'factura': invoice, 'nombre': 'ine', 'valores': valores, } FacturasComplementos.create(**data) return def _save_cartaporte(self, invoice, valores): if not valores: return values = utils.loads(valores) total_distance = 0.00 total_weight = 0.00 mercancias = values['mercancias'] for mercancia in mercancias['mercancias']: total_weight += float(mercancia['PesoEnKg']) if isinstance(mercancia['PesoEnKg'], (int, float)): mercancia['PesoEnKg'] = f"{mercancia['PesoEnKg']:.2f}" mercancias['PesoBrutoTotal'] = f"{total_weight:.2f}" ubicaciones = values['ubicaciones'] for ubicacion in ubicaciones: if 'DistanciaRecorrida' in ubicacion: if ubicacion['TipoUbicacion'] == 'Origen': del ubicacion['DistanciaRecorrida'] elif ubicacion['DistanciaRecorrida']: total_distance += float(ubicacion['DistanciaRecorrida']) if isinstance(ubicacion['DistanciaRecorrida'], (int, float)): ubicacion['DistanciaRecorrida'] = f"{ubicacion['DistanciaRecorrida']:.2f}" municipio = ubicacion.pop('Municipio') estado = ubicacion.pop('Estado') pais = ubicacion.pop('Pais') cp = ubicacion.pop('CodigoPostal') if municipio and estado and pais and cp: ubicacion['domicilio'] = { 'Municipio': municipio, 'Estado': estado, 'Pais': pais, 'CodigoPostal': cp, } values['TotalDistRec'] = f"{total_distance:.2f}" # ~ print(2, values) data = { 'factura': invoice, 'nombre': 'cartaporte', 'valores': utils.dumps(values), } FacturasComplementos.create(**data) return def _save_comercioe(self, invoice, valores): if not valores: return # ~ values = utils.loads(valores) data = { 'factura': invoice, 'nombre': 'comercioe', # ~ 'valores': utils.dumps(values), 'valores': valores, } FacturasComplementos.create(**data) return def _get_serie(self, user, default_serie): if user.sucursal is None: return default_serie return user.sucursal.serie_facturas or default_serie @classmethod def add(cls, values, user): productos = util.loads(values.pop('productos')) relacionados = util.loads(values.pop('relacionados')) ine = values.pop('ine', {}) cartaporte = values.pop('cartaporte', {}) comercioe = values.pop('comercioe', {}) tipo_comprobante = values['tipo_comprobante'] folio_custom = values.pop('folio_custom', '') divisas = values.pop('divisas', '') if Configuracion.get_bool('chk_config_divisas'): divisas = divisas.lower() if divisas == 'ninguna': divisas = '' values['divisas'] = divisas leyendas_fiscales = utils.loads(values.pop('leyendas_fiscales', '[]')) emisor = Emisor.select()[0] values['serie'] = cls._get_serie(cls, user, values['serie']) if Configuracion.get_bool('chk_folio_custom') and folio_custom: fc = {'folio': folio_custom, 'serie': values['serie']} result = cls._get_folio_custom(cls, fc) if result['ok']: data = {'ok': False, 'row': {}, 'new': True, 'msg': result['msg']} return data values['folio'] = int(folio_custom) else: values['folio'] = cls._get_folio(cls, values['serie']) values['tipo_cambio'] = round(float(values['tipo_cambio']), 4) values['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal values['anticipo'] = util.get_bool(values['anticipo']) values['donativo'] = util.get_bool(values['donativo']) if tipo_comprobante == 'T': values['metodo_pago'] = '' values['forma_pago'] = '' values['moneda'] = CARTA_PORTE['MONEDA'] self_client = Socios.get(Socios.rfc==emisor.rfc) if values['cliente'] != self_client.id: values['cliente'] = self_client with database_proxy.atomic() as txn: # ~ print('VALUES\n\n', values) obj = Facturas.create(**values) totals = cls._calculate_totals( cls, obj, productos, tipo_comprobante, user) cls._guardar_relacionados(cls, obj, relacionados) cls._guardar_ine(cls, obj, ine) cls._save_cartaporte(cls, obj, cartaporte) cls._save_comercioe(cls, obj, comercioe) cls._save_leyendas_fiscales(cls, obj, leyendas_fiscales) obj.subtotal = totals['subtotal'] obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] obj.total_retenciones = totals['total_retenciones'] obj.total = totals['total'] obj.saldo = totals['total'] obj.total_mn = totals['total_mn'] obj.save() msg = 'G {}'.format(obj.id) _save_log(user.usuario, msg, 'F') msg = 'Factura guardada correctamente. Enviando a timbrar' row = { 'id': obj.id, 'serie': obj.serie, 'folio': obj.folio, 'uuid': obj.uuid, 'fecha': obj.fecha, 'tipo_comprobante': obj.tipo_comprobante, 'estatus': obj.estatus, 'paid': 'No', 'total': obj.total, 'currency': obj.moneda, 'total_mn': obj.total_mn, 'cliente': obj.cliente.nombre, } data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg} return data def _save_leyendas_fiscales(self, invoice, valores): if not valores: return data = { 'factura': invoice, 'nombre': 'leyendas', 'valores': utils.dumps(valores), } FacturasComplementos.create(**data) return def _make_xml(self, invoice): tax_decimals = Configuracion.get_bool('chk_config_tax_decimals') decimales_precios = Configuracion.get_bool('chk_config_decimales_precios') invoice_by_ticket = Configuracion.get_bool('chk_config_invoice_by_ticket') # ~ is_global = (invoice.cliente.rfc == RFCS['PUBLIC']) and invoice_by_ticket is_global = bool(invoice.periodicidad) data_global = {} if is_global: now = utils.now() data_global['Periodicidad'] = invoice.periodicidad data_global['Meses'] = now.strftime('%m') data_global['Año'] = now.strftime('%Y') frm_vu = FORMAT if decimales_precios: frm_vu = FORMAT_PRECIO tmp = 0 emisor = Emisor.select()[0] certificado = Certificado.get(Certificado.es_fiel==False) is_edu = False comprobante = {} relacionados = {} donativo = {} complementos = FacturasComplementos.get_by_invoice(invoice) comprobante['divisas'] = invoice.divisas if invoice.divisas: complementos['divisas'] = True if 'leyendas' in complementos: ids = complementos['leyendas'] complementos['leyendas'] = SATLeyendasFiscales.get_by_id(ids) if invoice.donativo: donativo['noAutorizacion'] = emisor.autorizacion donativo['fechaAutorizacion'] = str(emisor.fecha_autorizacion) if invoice.serie: comprobante['Serie'] = invoice.serie if invoice.condiciones_pago: comprobante['CondicionesDePago'] = invoice.condiciones_pago if invoice.descuento: comprobante['Descuento'] = invoice.descuento comprobante['Folio'] = str(invoice.folio) comprobante['Fecha'] = invoice.fecha.isoformat()[:19] comprobante['FormaPago'] = invoice.forma_pago comprobante['NoCertificado'] = certificado.serie comprobante['SubTotal'] = FORMAT.format(invoice.subtotal) comprobante['Moneda'] = invoice.moneda comprobante['TipoCambio'] = '1' if comprobante['Moneda'] != 'MXN': comprobante['TipoCambio'] = FORMAT_TAX.format(invoice.tipo_cambio) comprobante['Total'] = FORMAT.format(invoice.total) comprobante['TipoDeComprobante'] = invoice.tipo_comprobante if invoice.metodo_pago: comprobante['MetodoPago'] = invoice.metodo_pago comprobante['LugarExpedicion'] = invoice.lugar_expedicion if invoice.descuento: comprobante['Descuento'] = FORMAT.format(invoice.descuento) if invoice.tipo_comprobante == 'T': comprobante['SubTotal'] = '0' comprobante['Total'] = '0' del comprobante['FormaPago'] del comprobante['TipoCambio'] if invoice.tipo_relacion: relacionados = { 'tipo': invoice.tipo_relacion, 'cfdis': FacturasRelacionadas.get_(invoice), } emisor = { 'Rfc': emisor.rfc, 'Nombre': emisor.nombre, 'RegimenFiscal': invoice.regimen_fiscal, } receptor = { 'Rfc': invoice.cliente.rfc, 'Nombre': invoice.cliente.nombre, 'UsoCFDI': invoice.uso_cfdi, 'DomicilioFiscalReceptor': invoice.cliente.codigo_postal, 'RegimenFiscalReceptor': invoice.receptor_regimen } if invoice.cliente.tipo_persona == 4: if invoice.cliente.pais: receptor['ResidenciaFiscal'] = invoice.cliente.pais if invoice.cliente.id_fiscal: receptor['NumRegIdTrib'] = invoice.cliente.id_fiscal conceptos = [] rows = FacturasDetalle.select().where(FacturasDetalle.factura==invoice) for row in rows: if is_global: key_sat = row.clave_sat key = row.clave else: key_sat = row.producto.clave_sat key = row.producto.clave concepto = { 'ClaveProdServ': key_sat, 'NoIdentificacion': key, 'Cantidad': FORMAT.format(row.cantidad), 'ClaveUnidad': row.unidad, 'Unidad': SATUnidades.get(SATUnidades.key==row.unidad).name[:20], 'Descripcion': row.descripcion, 'ValorUnitario': frm_vu.format(row.valor_unitario), 'Importe': FORMAT.format(row.importe), } if row.descuento: concepto['Descuento'] = FORMAT.format(row.descuento) if row.cuenta_predial: concepto['CuentaPredial'] = row.cuenta_predial if row.pedimento: concepto['Pedimento'] = row.pedimento if row.autorizacion: is_edu = True concepto['student'] = { 'version': '1.0', 'nombreAlumno': row.alumno, 'CURP': row.curp, 'nivelEducativo': row.nivel, 'autRVOE': row.autorizacion, } taxes = {} traslados = [] retenciones = [] if invoice.tipo_comprobante != 'T': if is_global: ticket = (Tickets .get(fn.Concat(Tickets.serie, Tickets.folio)==row.clave) ) product_taxes = (TicketsImpuestos .select() .where(TicketsImpuestos.ticket==ticket) ) else: product_taxes = row.producto.impuestos for impuesto in product_taxes: base = float(row.importe - row.descuento) if is_global: base = float(impuesto.base) impuesto = impuesto.impuesto if impuesto.tipo == 'E': tax = { 'Base': FORMAT.format(base), 'Impuesto': '002', 'TipoFactor': 'Exento', } traslados.append(tax) continue if impuesto.key == '000': continue tasa = float(impuesto.tasa) if tax_decimals: import_tax = round(tasa * base, DECIMALES_TAX) tmp += import_tax xml_importe = FORMAT_TAX.format(import_tax) else: import_tax = round(tasa * base, DECIMALES) xml_importe = FORMAT.format(import_tax) tipo_factor = 'Tasa' if impuesto.factor != 'T': tipo_factor = 'Cuota' tax = { "Base": FORMAT.format(base), "Impuesto": impuesto.key, "TipoFactor": tipo_factor, "TasaOCuota": str(impuesto.tasa), "Importe": xml_importe, } if impuesto.tipo == 'T': traslados.append(tax) else: retenciones.append(tax) if traslados: taxes['traslados'] = traslados if retenciones: taxes['retenciones'] = retenciones concepto['impuestos'] = taxes # cfdi4 if row.producto.objeto_impuesto: concepto['ObjetoImp'] = row.producto.objeto_impuesto else: if taxes: concepto['ObjetoImp'] = '02' else: concepto['ObjetoImp'] = '01' conceptos.append(concepto) impuestos = {} traslados = [] retenciones = [] total_locales_trasladados = 0 total_locales_retenciones = 0 locales_trasladados = [] locales_retenciones = [] if not invoice.total_trasladados is None: impuestos['TotalImpuestosTrasladados'] = \ FORMAT.format(invoice.total_trasladados) if not invoice.total_retenciones is None: impuestos['TotalImpuestosRetenidos'] = \ FORMAT.format(invoice.total_retenciones) taxes = (FacturasImpuestos .select() .where(FacturasImpuestos.factura==invoice)) for tax in taxes: if tax.impuesto.key == '000': tasa = str(round(tax.impuesto.tasa * 100, 2)) simporte = FORMAT.format(tax.importe) if tax.impuesto.tipo == 'T': traslado = { 'ImpLocTrasladado': tax.impuesto.name, 'TasadeTraslado': tasa, 'Importe': simporte, } locales_trasladados.append(traslado) total_locales_trasladados += tax.importe else: retencion = { 'ImpLocRetenido': tax.impuesto.name, 'TasadeRetencion': tasa, 'Importe': simporte, } locales_retenciones.append(retencion) total_locales_retenciones += tax.importe continue tipo_factor = 'Tasa' if tax.impuesto.factor != 'T': tipo_factor = 'Cuota' if tax_decimals: xml_importe = FORMAT_TAX.format(tax.importe) xml_tax_base = FORMAT_TAX.format(tax.base) else: xml_importe = FORMAT.format(tax.importe) xml_tax_base = FORMAT.format(tax.base) if tax.impuesto.tipo == 'T': traslado = { "Base": xml_tax_base, "Impuesto": tax.impuesto.key, "TipoFactor": tipo_factor, "TasaOCuota": str(tax.impuesto.tasa), "Importe": xml_importe, } traslados.append(traslado) else: retencion = { "Impuesto": tax.impuesto.key, "Importe": FORMAT.format(tax.importe), } retenciones.append(retencion) impuestos['traslados'] = traslados impuestos['retenciones'] = retenciones impuestos['total_locales_trasladados'] = '' if total_locales_trasladados: impuestos['total_locales_trasladados'] = \ FORMAT.format(total_locales_trasladados) impuestos['total_locales_retenciones'] = '' if total_locales_retenciones: impuestos['total_locales_retenciones'] = \ FORMAT.format(total_locales_retenciones) impuestos['locales_trasladados'] = locales_trasladados impuestos['locales_retenciones'] = locales_retenciones if invoice.tipo_comprobante == 'T': impuestos = {} data = { 'comprobante': comprobante, 'relacionados': relacionados, 'emisor': emisor, 'receptor': receptor, 'conceptos': conceptos, 'impuestos': impuestos, 'donativo': donativo, 'edu': is_edu, 'complementos': complementos, 'global': data_global, } return utils.make_xml(data, certificado) @classmethod def get_status_sat(cls, id): obj = Facturas.get(Facturas.id == id) estatus_sat = utils.get_status_sat(obj.xml) if obj.estatus_sat != estatus_sat: obj.estatus_sat = estatus_sat obj.save() if obj.estatus_sat == 'Vigente' and obj.estatus == 'Cancelada': days = utils.get_days(obj.fecha_cancelacion) if days > 3: estatus_sat = 'uncancel' return estatus_sat @classmethod def get_verify_sat(cls, id): emisor = Emisor.select()[0].rfc obj = Facturas.get(Facturas.id == id) xml = util.parse_xml(obj.xml) sello = xml.attrib.get('sello', xml.attrib.get('Sello'))[-8:] data = { 'url': 'https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx?', 'uuid': '&id={}'.format(obj.uuid), 'emisor': '&re={}'.format(emisor), 'receptor': '&rr={}'.format(obj.cliente.rfc), 'total': '&tt={}'.format(str(obj.total)), 'sello': '&fe={}'.format(sello), } result = { 'url': '{url}{uuid}{emisor}{receptor}{total}{sello}'.format(**data) } return result @classmethod def anticipo_egreso(cls, id): origen = Facturas.get(Facturas.id == id) relacionadas = (FacturasRelacionadas .select(FacturasRelacionadas.factura_origen) .where(FacturasRelacionadas.factura==origen)) conceptos = (FacturasDetalle .select() .where(FacturasDetalle.factura==origen)) impuestos = (FacturasImpuestos .select() .where(FacturasImpuestos.factura==origen)) #~ egreso_anticipo = BooleanField(default=False) serie = Folios.get_egreso(origen.serie) nueva = { 'cliente': origen.cliente, 'tipo_comprobante': 'E', 'forma_pago': '30', 'serie': serie, 'folio': cls._get_folio(cls, serie), 'tipo_relacion': '07', 'pagada': True, 'lugar_expedicion': origen.lugar_expedicion, 'uso_cfdi': origen.uso_cfdi, 'moneda': origen.moneda, 'tipo_cambio': origen.tipo_cambio, 'regimen_fiscal': origen.regimen_fiscal, 'subtotal': origen.subtotal, 'total': origen.total, 'total_trasladados': origen.total_trasladados, 'total_retenciones': origen.total_retenciones, } return @classmethod def timbrar(cls, values, user): id = int(values['id']) update = util.loads(values.get('update', 'true')) rfc = Emisor.select()[0].rfc obj = Facturas.get(Facturas.id == id) xml = cls._make_xml(cls, obj) if obj.cliente.rfc == RFCS['PUBLIC']: update = False enviar_correo = util.get_bool(Configuracion.get_('correo_directo')) auth = Configuracion.get_({'fields': 'pac_auth'}) anticipo = False msg = 'Factura timbrada correctamente' result = utils.xml_stamp(xml, auth) if result['ok']: cfdi_uuid = result['uuid'] values = dict( xml = result['xml'], uuid = cfdi_uuid, fecha_timbrado = result['date'], estatus = 'Timbrada', error = '', ) q = (Facturas .update(**values) .where(Facturas.id == id) ) q.execute() row = {'uuid': cfdi_uuid, 'estatus': 'Timbrada'} if enviar_correo: cls._send(cls, id, rfc) if obj.tipo_comprobante == 'I' and obj.tipo_relacion == '07': anticipo = True cls._actualizar_saldo_cliente(cls, obj) if update: cls._update_inventory(cls, obj, user=user) cls._sync(cls, id, rfc) m = 'T {}'.format(obj.id) _save_log(user.usuario, m, 'F') else: msg = result['error'] obj.estatus = 'Error' obj.xml = xml obj.error = msg obj.save() row = {'estatus': 'Error'} result = { 'ok': result['ok'], 'msg': msg, 'row': row, 'anticipo': anticipo } return result def _validate_import_32(self, data, sxml): currencies = { 'Peso Mexicano': 'MXN', } regimenes = { 'REGIMEN GENERAL DE LEY PERSONAS MORALES': '601', } # ~ print(data) invoice = data['invoice'] receptor = data['receptor'] name = receptor.get('nombre', '') tipo_persona = 1 if receptor['rfc'] == 'XEXX010101000': tipo_persona = 4 elif receptor['rfc'] == RFCS['PUBLIC']: tipo_persona = 3 elif len(receptor['rfc']) == 12: tipo_persona = 2 new = { 'tipo_persona': tipo_persona, 'rfc': receptor['rfc'], 'slug': util.to_slug(name), 'nombre': name, 'es_cliente': True, } cliente, _ = Socios.get_or_create(**new) tipo_cambio = float(invoice.get('TipoCambio', '1.0')) total = float(invoice['Total']) invoice = { 'cliente': cliente, 'version': invoice['version'], 'serie': invoice.get('serie', ''), 'folio': int(invoice.get('folio', '0')), 'fecha': invoice['fecha'], 'fecha_timbrado': invoice['FechaTimbrado'], 'forma_pago': invoice['formaDePago'], 'condiciones_pago': invoice.get('CondicionesDePago', ''), 'subtotal': float(invoice['SubTotal']), 'descuento': float(invoice.get('Descuento', '0.0')), 'moneda': currencies[invoice['Moneda']], 'tipo_cambio': tipo_cambio, 'total': total, 'saldo': total, 'total_mn': round(float(total * tipo_cambio), DECIMALES), 'tipo_comprobante': invoice['TipoDeComprobante'], 'metodo_pago': invoice['metodoDePago'], 'lugar_expedicion': invoice['LugarExpedicion'], # ~ 'uso_cfdi': invoice['UsoCFDI'], 'total_retenciones': float(invoice.get('TotalImpuestosRetenidos', '0.0')), 'total_trasladados': float(invoice.get('TotalImpuestosTrasladados', '0.0')), 'xml': sxml, 'uuid': invoice['uuid'], 'estatus': 'Importada', 'regimen_fiscal': regimenes[invoice['regimen_fiscal']], 'pagada': False, 'tipo_relacion': invoice.get('TipoRelacion', '') } # ~ donativo = BooleanField(default=False) conceptos = [] for concepto in data['conceptos']: valor_unitario = float(concepto['ValorUnitario']) descuento = float(concepto.get('Descuento', '0.0')) c = { 'cantidad': float(concepto['Cantidad']), 'valor_unitario': valor_unitario, 'descuento': descuento, 'precio_final': round(valor_unitario - descuento, DECIMALES), 'importe': float(concepto['Importe']), 'descripcion': concepto['Descripcion'], 'unidad': concepto.get('Unidad', ''), 'clave': concepto.get('NoIdentificacion', ''), 'clave_sat': '01010101', } conceptos.append(c) new = { 'invoice': invoice, 'conceptos': conceptos, } return True, new def _validate_import(self, data, sxml): try: emisor = Emisor.select()[0] except IndexError: msg = 'Falta Emisor' log.error(msg) return False, {} if not DEBUG: if emisor.rfc != data['emisor']['rfc']: msg = 'El CFDI no es del Emisor' log.error(msg) return False, {} invoice = data['invoice'] w = (Facturas.uuid==invoice['uuid']) if Facturas.select().where(w).exists(): msg = 'Factura ya existe: {}'.format(invoice['uuid']) log.error(msg) return False, {} if invoice['version'] != '3.3': if invoice['version'] != '3.2': msg = 'CFDI no soportado' log.error(msg) return False, {} return self._validate_import_32(self, data, sxml) receptor = data['receptor'] name = receptor.get('nombre', '') tipo_persona = 1 if receptor['rfc'] == 'XEXX010101000': tipo_persona = 4 elif receptor['rfc'] == RFCS['PUBLIC']: tipo_persona = 3 elif len(receptor['rfc']) == 12: tipo_persona = 2 new = { 'tipo_persona': tipo_persona, 'rfc': receptor['rfc'], 'slug': util.to_slug(name), 'nombre': name, 'es_cliente': True, } cliente, _ = Socios.get_or_create(**new) tipo_cambio = float(invoice.get('TipoCambio', '1.0')) total = float(invoice['Total']) invoice = { 'cliente': cliente, 'version': invoice['version'], 'serie': invoice.get('serie', ''), 'folio': int(invoice.get('folio', '0')), 'fecha': invoice['fecha'], 'fecha_timbrado': invoice['FechaTimbrado'], 'forma_pago': invoice['FormaPago'], 'condiciones_pago': invoice.get('CondicionesDePago', ''), 'subtotal': float(invoice['SubTotal']), 'descuento': float(invoice.get('Descuento', '0.0')), 'moneda': invoice['Moneda'], 'tipo_cambio': tipo_cambio, 'total': total, 'saldo': total, 'total_mn': round(float(total * tipo_cambio), DECIMALES), 'tipo_comprobante': invoice['TipoDeComprobante'], 'metodo_pago': invoice['MetodoPago'], 'lugar_expedicion': invoice['LugarExpedicion'], 'uso_cfdi': invoice['UsoCFDI'], 'total_retenciones': float(invoice.get('TotalImpuestosRetenidos', '0.0')), 'total_trasladados': float(invoice.get('TotalImpuestosTrasladados', '0.0')), 'xml': sxml, 'uuid': invoice['uuid'], 'estatus': 'Importada', 'regimen_fiscal': invoice['RegimenFiscal'], 'pagada': False, 'tipo_relacion': invoice.get('TipoRelacion', '') } # ~ donativo = BooleanField(default=False) conceptos = [] for concepto in data['conceptos']: valor_unitario = float(concepto['ValorUnitario']) descuento = float(concepto.get('Descuento', '0.0')) clave = concepto.get('NoIdentificacion', '') producto = None if Productos.select().where(Productos.clave==clave).exists(): producto = Productos.get(Productos.clave==clave) c = { 'producto': producto, 'cantidad': float(concepto['Cantidad']), 'valor_unitario': valor_unitario, 'descuento': descuento, 'precio_final': round(valor_unitario - descuento, DECIMALES), 'importe': float(concepto['Importe']), 'descripcion': concepto['Descripcion'], 'unidad': concepto.get('Unidad', ''), 'clave': clave, 'clave_sat': concepto['ClaveProdServ'], } conceptos.append(c) new = { 'invoice': invoice, 'conceptos': conceptos, } return True, new @classmethod def import_cfdi(cls, xml, sxml): result = {'status': 'error'} data = util.ImportCFDI(xml).get_data() ok, new = cls._validate_import(cls, data, sxml) if not ok: return result with database_proxy.atomic() as txn: obj = Facturas.create(**new['invoice']) for product in new['conceptos']: product['factura'] = obj FacturasDetalle.create(**product) return {'status': 'server'} def _set_invoices_payed(self, ids, user): ids = util.loads(ids) ok_ids = [] for id in ids: with database_proxy.atomic() as txn: obj = Facturas.get(Facturas.id==id) obj.pagada = True saldo = obj.saldo obj.saldo = 0.00 obj.save() q = (Socios .update(saldo_cliente=Socios.saldo_cliente - saldo) .where(Socios.id==obj.cliente.id) ) q.execute() ok_ids.append(id) if ok_ids: return {'ok': True, 'rows': ok_ids} return {'ok': False} @classmethod def opt(cls, args, user): if args['opt'] == 'invoicepayed': return cls._set_invoices_payed(cls, args['ids'], user) if args['opt'] == 'timbrar': return cls.timbrar(args, user) if args['opt'] == 'cancel': admin_cancel = Configuracion.get_bool('chk_cancel_invoices_by_admin') if admin_cancel and not user.es_admin: msg = 'Solo un admin puede cancelar' result = {'ok': False, 'msg': msg, 'row': {}} return result values = utils.loads(args['values']) result = cls.cancel(values) if result['ok']: m = 'C {}'.format(values['id']) _save_log(user.usuario, m, 'F') return result return {'ok': False} # ~ v2 @utils.run_in_thread def _update_inventory(self, invoice, cancel=False, user=None): if invoice.tipo_comprobante != 'I': return if invoice.cliente.rfc == RFCS['PUBLIC']: return warehouse = None try: warehouse = user.sucursal.warehouse except: pass products = FacturasDetalle.get_by_invoice(invoice.id) for p in products: if p.producto is None: continue if p.producto.inventario: cant = Decimal(p.cantidad) if cancel: cant *= -1 warehouse = p.warehouse p.producto.existencia -= cant p.producto.save() if warehouse is None: continue fields = ( WareHouseProduct.warehouse==warehouse, WareHouseProduct.product==p.producto, ) obj = WareHouseProduct.get(*fields) obj.exists -= cant obj.save() return @utils.run_in_thread def _update_client_balance(self, invoice, cancel=False): if invoice.tipo_comprobante == 'T': return if invoice.donativo and invoice.forma_pago == '12': return if invoice.cliente.rfc == RFCS['PUBLIC']: return importe = invoice.total_mn if invoice.tipo_comprobante == 'E': importe *= -1 if cancel: importe *= -1 q = (Socios .update(saldo_cliente=Socios.saldo_cliente + importe) .where(Socios.id==invoice.cliente.id) ) return bool(q.execute()) def _put_uncancel(self, args, user): id = int(args['id']) obj = Facturas.get(Facturas.id==id) obj.estatus = 'Timbrada' obj.error = '' obj.cancelada = False obj.fecha_cancelacion = None obj.acuse = '' self._update_client_balance(self, obj) self._update_inventory(self, obj) obj.save() msg = 'Factura actualizada correctamente' result = {'result': True, 'msg': msg, 'values': {'estatus': 'Timbrada'}} return result @classmethod def put(cls, args, user): return getattr(cls, f"_put_{args['opt']}")(cls, args, user) class PreFacturas(BaseModel): cliente = ForeignKeyField(Socios) serie = TextField(default='PRE') folio = IntegerField(default=0) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) forma_pago = TextField(default='') condiciones_pago = TextField(default='') subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) moneda = TextField(default='MXN') tipo_cambio = DecimalField(default=1.0, decimal_places=6, auto_round=True) total = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) tipo_comprobante = TextField(default='I') metodo_pago = TextField(default='PUE') lugar_expedicion = TextField(default='') uso_cfdi = TextField(default='') total_retenciones = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) total_trasladados = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) estatus = TextField(default='Generada') regimen_fiscal = TextField(default='') notas = TextField(default='') donativo = BooleanField(default=False) tipo_relacion = TextField(default='') class Meta: order_by = ('fecha',) @util.run_in_thread def _send_in_thread(self, id, obj, values): log.info('Generando PDF...') files = (self.get_pdf(id),) log.info('PDF Generado...') invoice = PreFacturas.select().where(PreFacturas.id==id).dicts()[0] fields = { 'receptor_nombre': obj.cliente.nombre, 'receptor_rfc': obj.cliente.rfc, } fields.update(invoice) asunto = 'Enviamos la prefactura: PRE-{}'.format(obj.folio) server = { 'servidor': values['correo_servidor'], 'puerto': values['correo_puerto'], 'ssl': bool(int(values['correo_ssl'])), 'usuario': values['correo_usuario'], 'contra': values['correo_contra'], } options = { 'para': obj.cliente.correo_facturas, 'copia': values['correo_copia'], 'confirmar': util.get_bool(values.get('correo_confirmacion', '0')), 'asunto': asunto, 'mensaje': util.make_info_mail(values['correo_mensaje'], fields), 'files': files, } data= { 'server': server, 'options': options, } log.info('Enviando prefactura...') result = util.send_mail(data) log.info('Prefactura enviada...') return @classmethod def enviar(cls, id): values = Configuracion.get_({'fields': 'correo'}) if not values: msg = 'No esta configurado el servidor de correo de salida' return {'ok': False, 'msg': msg} obj = PreFacturas.get(PreFacturas.id==id) if not obj.cliente.correo_facturas: msg = 'El cliente no tiene configurado el correo para facturas' return {'ok': False, 'msg': msg} rows = PreFacturasDetalle.count(id) if rows > 300: cls._send_in_thread(cls, id, obj, values) msg = 'Enviando correo...' return {'ok': True, 'msg': msg} files = (cls.get_pdf(id),) invoice = PreFacturas.select().where(PreFacturas.id==id).dicts()[0] fields = { 'receptor_nombre': obj.cliente.nombre, 'receptor_rfc': obj.cliente.rfc, } fields.update(invoice) contra = Configuracion.get_('correo_contra') rfc = Emisor.select()[0].rfc asunto = 'Enviamos la prefactura: PRE-{}'.format(obj.folio) starttls = bool(int(values.get('correo_starttls', '0'))) server = { 'servidor': values['correo_servidor'], 'puerto': values['correo_puerto'], 'ssl': bool(int(values['correo_ssl'])), 'starttls': starttls, 'usuario': values['correo_usuario'], 'contra': utils.decrypt(contra, rfc), } options = { 'para': obj.cliente.correo_facturas, 'copia': values['correo_copia'], 'confirmar': util.get_bool(values.get('correo_confirmacion', '0')), 'asunto': asunto, 'mensaje': util.make_info_mail(values['correo_mensaje'], fields), 'files': files, } data= { 'server': server, 'options': options, } result = util.send_mail(data) if not result['ok'] or result['msg']: return {'ok': False, 'msg': result['msg']} msg = 'Pre Factura enviada correctamente' return {'ok': True, 'msg': msg} def _get_info_to_pdf(self, id): data = {} obj = PreFacturas.select().where(PreFacturas.id==id).dicts()[0] regimen = SATRegimenes.get(SATRegimenes.key==obj['regimen_fiscal']) usocfdi = SATUsoCfdi.get(SATUsoCfdi.key==obj['uso_cfdi']) formapago = SATFormaPago.get(SATFormaPago.key==obj['forma_pago']) moneda = SATMonedas.get(SATMonedas.key==obj['moneda']) emisor = util.get_dict(Emisor.select().dicts()[0]) emisor['nointerior'] = emisor['no_interior'] emisor['noexterior'] = emisor['no_exterior'] emisor['codigopostal'] = emisor['codigo_postal'] emisor['regimenfiscal'] = str(regimen) receptor = Socios.select().where(Socios.id==obj['cliente']).dicts()[0] receptor['usocfdi'] = str(usocfdi) receptor['nointerior'] = receptor['no_interior'] receptor['noexterior'] = receptor['no_exterior'] receptor['codigopostal'] = receptor['codigo_postal'] data['es_pre'] = True data['cancelada'] = False data['donativo'] = obj['donativo'] tipos = { 'I': 'ingreso', 'E': 'egreso', 'T': 'traslado', } mp = { 'PUE': 'Pago en una sola exhibición', 'PPD': 'Pago en parcialidades o diferido', } data['comprobante'] = obj data['comprobante']['version'] = CURRENT_CFDI data['comprobante']['folio'] = str(data['comprobante']['folio']) data['comprobante']['seriefolio'] = '{}-{}'.format( data['comprobante']['serie'], data['comprobante']['folio']) data['comprobante']['fecha'] = str(data['comprobante']['fecha']) data['comprobante']['tipodecomprobante'] = tipos.get( data['comprobante']['tipo_comprobante']) data['comprobante']['lugarexpedicion'] = \ 'C.P. de Expedición: {}'.format( data['comprobante']['lugar_expedicion']) data['comprobante']['metododepago'] = 'Método de Pago: ({}) {}'.format( obj['metodo_pago'], mp[obj['metodo_pago']]) data['comprobante']['formadepago'] = str(formapago) data['comprobante']['condicionesdepago'] = \ data['comprobante']['condiciones_pago'] data['comprobante']['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format( data['comprobante']['tipo_cambio']) data['comprobante']['totalenletras'] = util.to_letters( data['comprobante']['total'], data['comprobante']['moneda']) data['comprobante']['moneda'] = str(moneda) data['emisor'] = emisor data['receptor'] = receptor data['conceptos'] = PreFacturasDetalle.get_(id) data['totales'] = {} data['totales']['moneda'] = data['comprobante']['moneda'] data['totales']['subtotal'] = str(data['comprobante']['subtotal']) data['totales']['total'] = str(data['comprobante']['total']) if obj['descuento']: data['totales']['descuento'] = float(obj['descuento']) taxes = PreFacturasImpuestos.get_(id) data['totales']['traslados'] = taxes['traslados'] data['totales']['retenciones'] = taxes['retenciones'] data['totales']['taxlocales'] = taxes['taxlocales'] data['timbre'] = {} data['donataria'] = {} data['ine'] = {} return data @util.run_in_thread def _get_pdf_in_thread(self, id): obj = PreFacturas.get(PreFacturas.id==id) name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc) data = self._get_info_to_pdf(self, id) doc = util.to_pdf(data, data['emisor']['rfc']) emisor = Emisor.select()[0] target = emisor.rfc + '/Prefacturas/' files = ( (doc, name, target), ) util.sync_cfdi(files) return @classmethod def get_pdf_in_thread(cls, id): return cls._get_pdf_in_thread(cls, id) @classmethod def get_pdf(cls, id): obj = PreFacturas.get(PreFacturas.id==id) name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc) data = cls._get_info_to_pdf(cls, id) doc = util.to_pdf(data, data['emisor']['rfc']) return doc, name @classmethod def remove(cls, id): obj = PreFacturas.get(PreFacturas.id==id) q = PreFacturasDetalle.delete().where( PreFacturasDetalle.factura==obj) q.execute() q = PreFacturasImpuestos.delete().where( PreFacturasImpuestos.factura==obj) q.execute() q = PreFacturasRelacionadas.delete().where( PreFacturasRelacionadas.factura==obj) q.execute() return bool(obj.delete_instance()) @classmethod def filter_years(cls): data = [{'id': -1, 'value': 'Todos'}] rows = (PreFacturas .select(PreFacturas.fecha.year.alias('year')) .group_by(PreFacturas.fecha.year) .order_by(PreFacturas.fecha.year) ) if not rows is None: data += [{'id': int(r.year), 'value': int(r.year)} for r in rows] return tuple(data) @classmethod def get_(cls, values): if values['year'] == '-1': fy = (PreFacturas.fecha.year > 0) else: fy = (PreFacturas.fecha.year == int(values['year'])) if values['month'] == '-1': fm = (PreFacturas.fecha.month > 0) else: fm = (PreFacturas.fecha.month == int(values['month'])) filters = (fy & fm) rows = tuple(PreFacturas .select( PreFacturas.id, PreFacturas.folio, PreFacturas.fecha, PreFacturas.tipo_comprobante, PreFacturas.total_mn, Socios.nombre.alias('cliente')) .where(filters) .join(Socios) .switch(PreFacturas).dicts() ) return {'ok': True, 'rows': rows} def _get_folio(self, serie): inicio = (PreFacturas .select(fn.Max(PreFacturas.folio).alias('mf')) .where(PreFacturas.serie==serie) .order_by(SQL('mf')) .scalar()) if inicio is None: inicio = 1 else: inicio += 1 return inicio def _calculate_totals(self, invoice, products): tax_locales = Configuracion.get_bool('chk_config_tax_locales') tax_decimals = Configuracion.get_bool('chk_config_tax_decimals') tax_locales_truncate = Configuracion.get_bool('chk_config_tax_locales_truncate') subtotal = 0 descuento_cfdi = 0 totals_tax = {} total_trasladados = None total_retenciones = None locales_traslados = 0 locales_retenciones = 0 for product in products: id_product = product.pop('id_product') p = Productos.get(Productos.id==id_product) product['unidad'] = SATUnidades.get(SATUnidades.id==product['unidad']).key product['clave'] = p.clave product['clave_sat'] = p.clave_sat product['cuenta_predial'] = p.cuenta_predial product['factura'] = invoice.id product['producto'] = id_product cantidad = float(product['cantidad']) valor_unitario = float(product['valor_unitario']) descuento = float(product['descuento']) precio_final = valor_unitario - descuento importe = round(cantidad * precio_final, DECIMALES) product['cantidad'] = cantidad product['valor_unitario'] = valor_unitario product['descuento'] = round(descuento * cantidad, DECIMALES) product['precio_final'] = precio_final product['importe'] = round(cantidad * valor_unitario, DECIMALES) descuento_cfdi += product['descuento'] subtotal += product['importe'] PreFacturasDetalle.create(**product) if invoice.tipo_comprobante == 'T': continue base = product['importe'] - product['descuento'] for tax in p.impuestos: if tax_locales and tax.tipo == 'R' and tax.key == '000': base = product['importe'] if tax_decimals: impuesto_producto = round(float(tax.tasa) * base, DECIMALES_TAX) else: impuesto_producto = round(float(tax.tasa) * base, DECIMALES) if tax.key == '000' and tax_locales_truncate: impuesto_producto = util.truncate(float(tax.tasa) * base) if tax.tipo == 'T' and tax.key != '000': total_trasladados = (total_trasladados or 0) + impuesto_producto elif tax.tipo == 'R' and tax.key != '000': total_retenciones = (total_retenciones or 0) + impuesto_producto elif tax.tipo == 'T' and tax.key == '000': locales_traslados += impuesto_producto elif tax.tipo == 'R' and tax.key == '000': locales_retenciones += impuesto_producto if tax.id in totals_tax: totals_tax[tax.id].base += base totals_tax[tax.id].suma_impuestos += impuesto_producto else: tax.base = base tax.suma_impuestos = impuesto_producto totals_tax[tax.id] = tax if invoice.tipo_comprobante == 'T': subtotal = 0.0 descuento = 0.0 else: for tax in totals_tax.values(): if tax.tipo == 'E': continue invoice_tax = { 'factura': invoice.id, 'impuesto': tax.id, 'base': tax.base, 'importe': tax.suma_impuestos, } PreFacturasImpuestos.create(**invoice_tax) total = subtotal - descuento_cfdi + \ (total_trasladados or 0) - (total_retenciones or 0) \ + locales_traslados - locales_retenciones total_mn = round(total * invoice.tipo_cambio, DECIMALES) data = { 'subtotal': subtotal, 'descuento': descuento_cfdi, 'total': total, 'total_mn': total_mn, 'total_trasladados': total_trasladados, 'total_retenciones': total_retenciones, } return data @classmethod def add(cls, values): productos = util.loads(values.pop('productos')) emisor = Emisor.select()[0] values['serie'] = 'PRE' values['folio'] = cls._get_folio(cls, values['serie']) values['tipo_cambio'] = float(values['tipo_cambio']) values['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal with database_proxy.atomic() as txn: obj = PreFacturas.create(**values) totals = cls._calculate_totals(cls, obj, productos) obj.subtotal = totals['subtotal'] obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] obj.total_retenciones = totals['total_retenciones'] obj.total = totals['total'] obj.total_mn = totals['total_mn'] obj.save() msg = 'Factura guardada correctamente' row = { 'id': obj.id, 'folio': obj.folio, 'fecha': obj.fecha, 'tipo_comprobante': obj.tipo_comprobante, 'total_mn': obj.total_mn, 'cliente': obj.cliente.nombre, } data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg} return data class FacturasRelacionadas(BaseModel): factura = ForeignKeyField(Facturas, related_name='original') factura_origen = ForeignKeyField(Facturas, related_name='relacion') class Meta: order_by = ('factura',) @classmethod def get_(cls, invoice): query = (FacturasRelacionadas .select() .where(FacturasRelacionadas.factura==invoice) ) return [str(r.factura_origen.uuid) for r in query] class FacturasComplementos(BaseModel): factura = ForeignKeyField(Facturas) nombre = TextField(default='') valores = TextField(default='') class Meta: order_by = ('factura',) @classmethod def get_(cls, factura): query = (FacturasComplementos .select() .where(FacturasComplementos.factura==factura) ) return {r.nombre: utils.loads(r.valores) for r in query} @classmethod def get_by_invoice(cls, invoice): query = (FacturasComplementos .select() .where(FacturasComplementos.factura==invoice) ) return {r.nombre: utils.loads(r.valores) for r in query} class PreFacturasRelacionadas(BaseModel): factura = ForeignKeyField(PreFacturas, related_name='original') factura_origen = ForeignKeyField(PreFacturas, related_name='relacion') class Meta: order_by = ('factura',) class FacturasDetalle(BaseModel): factura = ForeignKeyField(Facturas) producto = ForeignKeyField(Productos, null=True) cantidad = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) precio_final = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) descripcion = TextField(default='') unidad = TextField(default='') clave = TextField(default='') clave_sat = TextField(default='') categoria = TextField(default='') aduana = TextField(default='') pedimento = TextField(default='') fecha_pedimento = DateField(null=True) alumno = TextField(default='') curp = TextField(default='') nivel = TextField(default='') autorizacion = TextField(default='') cuenta_predial = TextField(default='') empaques = DecimalField(default=0.0, max_digits=14, decimal_places=4, auto_round=True) warehouse = ForeignKeyField(Almacenes, null=True) class Meta: order_by = ('factura',) def _get_impuestos(self, id): model_pt = Productos.impuestos.get_through_model() impuestos = tuple(model_pt .select( model_pt.productos_id.alias('product'), model_pt.satimpuestos_id.alias('tax')) .where(model_pt.productos_id==id).dicts()) return impuestos @classmethod def get_by_invoice(cls, id): return FacturasDetalle.select().where(FacturasDetalle.factura==id) @classmethod def reinvoice(cls, id): data = [] products = FacturasDetalle.select().where(FacturasDetalle.factura==id) for p in products: row = {'delete': '-', 'id_product': p.producto.id} row['clave'] = p.clave row['clave_sat'] = p.clave_sat row['descripcion'] = p.descripcion row['pedimento'] = p.pedimento row['unidad'] = p.producto.unidad.id row['cantidad'] = p.cantidad row['valor_unitario'] = p.valor_unitario descuento = p.valor_unitario - p.precio_final row['descuento'] = descuento pf = p.valor_unitario - descuento row['importe'] = round(pf * p.cantidad, DECIMALES) impuestos = cls._get_impuestos(cls, row['id_product']) data.append({'row': row, 'taxes': impuestos}) return data @classmethod def get_detalle(cls, id): data = [] products = FacturasDetalle.select().where(FacturasDetalle.factura==id) for p in products: row = {'id_product': p.producto.id} row['clave'] = p.clave row['clave_sat'] = p.clave_sat row['descripcion'] = p.descripcion row['unidad'] = p.producto.unidad.name row['cantidad'] = p.cantidad row['valor_unitario'] = p.valor_unitario descuento = p.valor_unitario - p.precio_final row['descuento'] = descuento pf = p.valor_unitario - descuento row['importe'] = round(pf * p.cantidad, DECIMALES) data.append(row) return data class PreFacturasDetalle(BaseModel): factura = ForeignKeyField(PreFacturas) producto = ForeignKeyField(Productos, null=True) descripcion = TextField(default='') cantidad = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) precio_final = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) aduana = TextField(default='') pedimento = TextField(default='') fecha_pedimento = DateField(null=True) alumno = TextField(default='') curp = TextField(default='') nivel = TextField(default='') autorizacion = TextField(default='') cuenta_predial = TextField(default='') unidad = TextField(default='') class Meta: order_by = ('factura',) def _get_impuestos(self, id): model_pt = Productos.impuestos.get_through_model() impuestos = tuple(model_pt .select( model_pt.productos_id.alias('product'), model_pt.satimpuestos_id.alias('tax')) .where(model_pt.productos_id==id).dicts()) return impuestos @classmethod def facturar(cls, id): data = [] q = PreFacturas.select().where(PreFacturas.id==id)[0] receptor = { 'id': q.cliente.id, 'nombre': q.cliente.nombre, 'rfc': q.cliente.rfc, 'forma_pago': q.forma_pago, 'uso_cfdi': q.uso_cfdi, 'codigo_postal': q.cliente.codigo_postal, 'regimenes': SociosRegimenes.get_by_key(q.cliente.id), 'notas': q.notas, } productos = PreFacturasDetalle.select().where( PreFacturasDetalle.factura==id) for p in productos: row = {'id_product': p.producto.id} row['clave'] = p.producto.clave row['descripcion'] = p.descripcion row['pedimento'] = p.pedimento row['unidad'] = p.producto.unidad.id row['cantidad'] = p.cantidad row['valor_unitario'] = p.valor_unitario descuento = p.valor_unitario - p.precio_final row['descuento'] = descuento pf = p.valor_unitario - descuento row['importe'] = round(pf * p.cantidad, DECIMALES) impuestos = cls._get_impuestos(cls, row['id_product']) data.append({'row': row, 'taxes': impuestos}) return {'rows': data, 'receptor': receptor} @classmethod def count(cls, id): c = PreFacturasDetalle.select().where( PreFacturasDetalle.factura==id).count() return c @classmethod def can_open(cls, id): c = cls.count(id) PreFacturas.get_pdf_in_thread(id) return c < 300 @classmethod def get_(cls, id): data = [] productos = PreFacturasDetalle.select().where( PreFacturasDetalle.factura==id) for p in productos: producto = {} producto['noidentificacion'] = '{}\n(SAT {})'.format( p.producto.clave, p.producto.clave_sat) producto['descripcion'] = p.descripcion if p.cuenta_predial: info = '\nCuenta Predial Número: {}'.format(p.cuenta_predial) producto['descripcion'] += info if p.pedimento: info = '\nNúmero Pedimento: {}'.format(p.pedimento) producto['descripcion'] += info producto['unidad'] = '{}\n({})'.format( p.producto.unidad.name, p.producto.unidad.key) producto['cantidad'] = str(p.cantidad) producto['valorunitario'] = str(p.valor_unitario) producto['importe'] = str(p.importe) data.append(producto) return data class FacturasImpuestos(BaseModel): factura = ForeignKeyField(Facturas) impuesto = ForeignKeyField(SATImpuestos) base = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) class Meta: order_by = ('factura',) indexes = ( (('factura', 'impuesto'), True), ) class FacturasPagos(BaseModel): movimiento = ForeignKeyField(MovimientosBanco) factura = ForeignKeyField(Facturas) numero = IntegerField(default=1) saldo_anterior = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) saldo = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6, auto_round=True) class Meta: order_by = ('factura',) indexes = ( (('movimiento', 'factura', 'numero'), True), ) def _movimiento_anterior(self, mov, id): query = (FacturasPagos .select() .where(FacturasPagos.factura==id) ) if len(query): return query[-1], len(query) + 1 else: return None, 1 def _actualizar_saldo_cliente(self, cliente, importe): q = (Socios .update(saldo_cliente=Socios.saldo_cliente + importe) .where(Socios.id==cliente.id) ) return bool(q.execute()) def _actualizar_saldos(self, factura, saldo_anterior): query = (FacturasPagos .select() .where(FacturasPagos.factura==factura) ) saldo = saldo_anterior for i, row in enumerate(query): if not saldo_anterior: saldo_anterior = row.saldo_anterior row.numero = i + 1 row.saldo_anterior = saldo_anterior row.saldo = saldo_anterior - row.importe row.save() saldo_anterior = row.saldo saldo = row.saldo factura.saldo = saldo factura.pagada = False factura.save() return @classmethod def cancelar(cls, mov): query = (FacturasPagos .select() .where(FacturasPagos.movimiento==mov) ) for row in query: cls._actualizar_saldo_cliente(cls, row.factura.cliente, row.importe) factura = row.factura saldo_anterior = 0 if row.numero == 1: saldo_anterior = row.saldo_anterior row.delete_instance() cls._actualizar_saldos(cls, factura, saldo_anterior) return @classmethod def add(cls, mov, ids): for i, values in ids.items(): fac = Facturas.get(Facturas.id==int(i)) this_pay = values['this_pay'] importe = values['importe'] type_change = round(1 / values['type_change'], 6) validate = round(this_pay / type_change, 2) while validate > importe: type_change += 0.000001 validate = round(this_pay / type_change, 2) mov_ant, numero = cls._movimiento_anterior(cls, mov, fac) nuevo = { 'movimiento': mov, 'factura': fac, 'numero': numero, 'importe': importe, 'tipo_cambio': type_change, } if mov_ant is None: nuevo['saldo_anterior'] = float(fac.saldo) else: nuevo['saldo_anterior'] = float(mov_ant.saldo) if(fac.moneda in ('MXN', 'peso')): nuevo['saldo'] = nuevo['saldo_anterior'] - importe else: nuevo['importe'] = this_pay nuevo['saldo'] = nuevo['saldo_anterior'] - this_pay FacturasPagos.create(**nuevo) fac.saldo = nuevo['saldo'] if nuevo['saldo'] == 0: fac.pagada = True fac.save() cls._actualizar_saldo_cliente(cls, fac.cliente, importe * -1) return def _get_related(self, values): id = int(values['id']) filters = (FacturasPagos.movimiento==id) rows = tuple(FacturasPagos .select( Facturas.id, Facturas.serie, Facturas.folio, Facturas.uuid, Facturas.fecha, Facturas.tipo_comprobante, Facturas.estatus, Socios.nombre.alias('cliente'), Facturas.total, FacturasPagos.saldo, FacturasPagos.importe, ) .join(Facturas).switch(FacturasPagos) .join(Socios, on=(Facturas.cliente==Socios.id)) .where(filters) .dicts() ) return {'ok': True, 'rows': rows} @classmethod def get_values(cls, values): opt = values.pop('opt') return getattr(cls, '_get_{}'.format(opt))(cls, values) class CfdiPagos(BaseModel): movimiento = ForeignKeyField(MovimientosBanco) socio = ForeignKeyField(Socios) serie = TextField(default='') folio = IntegerField(default=0) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) fecha_timbrado = DateTimeField(null=True) tipo_comprobante = TextField(default=DEFAULT_CFDIPAY['TYPE']) lugar_expedicion = TextField(default='') regimen_fiscal = TextField(default='') tipo_relacion = TextField(default='') uuid_relacionado = UUIDField(null=True) xml = TextField(default='') uuid = UUIDField(null=True) estatus = TextField(default='Guardada') estatus_sat = TextField(default='') notas = TextField(default='') error = TextField(default='') cancelada = BooleanField(default=False) fecha_cancelacion = DateTimeField(null=True) receptor_regimen = TextField(default='') class Meta: order_by = ('movimiento',) @classmethod def with_invoice(cls, id): if CfdiPagos.select().where(CfdiPagos.movimiento==id).count(): return 'Si' return '' @classmethod def post(cls, values): opt = values.pop('opt') return getattr(cls, '_{}'.format(opt))(cls, values) def _delete(self, values): id_mov = int(values['id_mov']) filters = ( (CfdiPagos.movimiento==id_mov) ) last = CfdiPagos.select().where(filters) if not last: msg = 'El depósito no tiene facturas de pago' data = {'ok': False, 'msg': msg} return data if len(last) > 1: msg = 'Hay más de una factura activa para este depósito' data = {'ok': False, 'msg': msg} return data last = last[0] if last.uuid: msg = 'La factura esta timbrada, no se puede eliminar' data = {'ok': False, 'msg': msg} return data q = CfdiPagos.delete().where(CfdiPagos.id==last.id) q.execute() msg = 'Factura de pago eliminada correctamente' data = {'ok': True, 'msg': msg} return data def _cancel(self, values): id_mov = int(values['id_mov']) args = utils.loads(values['args']) filters = ( (CfdiPagos.movimiento==id_mov) & (CfdiPagos.cancelada==False) ) last = CfdiPagos.select().where(filters) if not last: msg = 'El depósito no tiene facturas de pago activas' data = {'ok': False, 'msg': msg} return data if len(last) > 1: msg = 'Hay más de una factura activa para este depósito' data = {'ok': False, 'msg': msg} return data last = last[0] if not last.uuid: msg = 'La factura no esta timbrada' data = {'ok': False, 'msg': msg} return data pac = utils.get_pac_by_rfc(last.xml) auth = Configuracion.get_({'fields': 'auth_by_pac', 'pac': pac}) certificado = Certificado.get(Certificado.es_fiel==False) result = utils.cancel_xml_sign(last, args, auth, certificado) if result['ok']: last.estatus = 'Cancelada' last.error = '' last.cancelada = True last.fecha_cancelacion = result['date'] else: last.error = result['msg'] last.save() return {'ok': result['ok'], 'msg': result['msg'], 'id': last.id} def _get_folio(self, serie): folio = int(Configuracion.get_('txt_config_cfdipay_folio') or '0') start = (CfdiPagos .select(fn.Max(CfdiPagos.folio).alias('mf')) .where(CfdiPagos.serie==serie) .order_by(SQL('mf')) .scalar()) if start is None: next_folio = 1 else: next_folio = start + 1 if folio > next_folio: next_folio = folio return next_folio def _new(self, values): id_mov = int(values['id_mov']) filters = (FacturasPagos.movimiento==id_mov) related = FacturasPagos.select().where(filters) if not related: msg = 'El pago no tiene facturas relacionadas' data = {'ok': False, 'msg': msg} return data partner = tuple(set([f.factura.cliente.rfc for f in related])) if len(partner) > 1: msg = 'Facturas relacionadas a diferentes clientes' data = {'ok': False, 'msg': msg} return data partner = related[0].factura.cliente partner_name = related[0].factura.cliente.nombre receptor_regimen = related[0].factura.receptor_regimen emisor = Emisor.select()[0] # ~ regimen_fiscal = related[0].factura.regimen_fiscal regimen_fiscal = emisor.regimenes[0].key filters = ( (CfdiPagos.movimiento==id_mov) & (CfdiPagos.cancelada==False) ) previous = CfdiPagos.select().where(filters) if previous: previous = previous[0] if previous.uuid: msg = 'Hay una factura activa, es necesario cancelarla primero' data = {'ok': False, 'msg': msg} return data else: data = {'ok': True, 'new': False} return data fields = {} filters = ( (CfdiPagos.movimiento==id_mov) & (CfdiPagos.cancelada==True) ) previous = CfdiPagos.select().where(filters).order_by(CfdiPagos.id.desc()) if previous: previous = previous[0] fields['tipo_relacion'] = DEFAULT_CFDIPAY['TYPE_RELATION'] fields['uuid_relacionado'] = previous.uuid emisor = Emisor.select()[0] serie = Configuracion.get_('txt_config_cfdipay_serie') or DEFAULT_CFDIPAY['SERIE'] fields['movimiento'] = id_mov fields['socio'] = partner fields['serie'] = serie fields['folio'] = self._get_folio(self, serie) fields['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal fields['regimen_fiscal'] = regimen_fiscal fields['receptor_regimen'] = receptor_regimen with database_proxy.atomic() as txn: obj = CfdiPagos.create(**fields) row = { 'id': obj.id, 'serie': obj.serie, 'folio': obj.folio, 'uuid': obj.uuid, 'fecha': obj.fecha, 'tipo_comprobante': obj.tipo_comprobante, 'estatus': obj.estatus, 'cliente': partner_name, } data = {'ok': True, 'row': row, 'new': True} return data def _get_taxes_by_pay(self, pay, taxes_pay): # ~ print(pay['ImpPagado'] invoice = Facturas.get(Facturas.uuid==pay['IdDocumento']) impuestos = {} traslados = [] retenciones = [] where = (FacturasImpuestos.factura==invoice) taxes = FacturasImpuestos.select().where(where) for tax in taxes: if tax.impuesto.key == '000': # ~ tasa = str(round(tax.impuesto.tasa * 100, 2)) # ~ simporte = FORMAT.format(tax.importe) # ~ if tax.impuesto.tipo == 'T': # ~ traslado = { # ~ 'ImpLocTrasladado': tax.impuesto.name, # ~ 'TasadeTraslado': tasa, # ~ 'Importe': simporte, # ~ } # ~ locales_trasladados.append(traslado) # ~ total_locales_trasladados += tax.importe # ~ else: # ~ retencion = { # ~ 'ImpLocRetenido': tax.impuesto.name, # ~ 'TasadeRetencion': tasa, # ~ 'Importe': simporte, # ~ } # ~ locales_retenciones.append(retencion) # ~ total_locales_retenciones += tax.importe continue tipo_factor = 'Tasa' if tax.impuesto.factor != 'T': tipo_factor = 'Cuota' # ~ if tax_decimals: # ~ xml_importe = FORMAT_TAX.format(tax.importe) # ~ xml_tax_base = FORMAT_TAX.format(tax.base) # ~ else: xml_importe = FORMAT.format(tax.importe) xml_tax_base = FORMAT.format(tax.base) values = { "BaseDR": xml_tax_base, "ImpuestoDR": tax.impuesto.key, "TipoFactorDR": tipo_factor, "TasaOCuotaDR": str(tax.impuesto.tasa), "ImporteDR": xml_importe, } tax_key = tax.impuesto.key if tax.impuesto.tipo == 'T': traslados.append(values) if tax_key in taxes_pay['traslados']: taxes_pay['traslados'][tax_key]['ImporteP'] += tax.importe else: values = { "BaseP": tax.base, "ImpuestoP": tax.impuesto.key, "TipoFactorP": tipo_factor, "TasaOCuotaP": str(tax.impuesto.tasa), "ImporteP": tax.importe, } taxes_pay['traslados'][tax_key] = values else: retenciones.append(values) if tax_key in taxes_pay['retenciones']: taxes_pay['retenciones'][tax_key] += tax.importe else: taxes_pay['retenciones'][tax_key] = tax.importe impuestos['traslados'] = traslados impuestos['retenciones'] = retenciones return impuestos def _get_related_xml(self, id_mov, currency): TAX_IVA_16 = '002|0.160000' filters = (FacturasPagos.movimiento==id_mov) related = tuple(FacturasPagos.select( Facturas.uuid.alias('IdDocumento'), Facturas.serie.alias('Serie'), Facturas.folio.alias('Folio'), Facturas.moneda.alias('MonedaDR'), FacturasPagos.tipo_cambio.alias('TipoCambioDR'), # ~ Facturas.metodo_pago.alias('MetodoDePagoDR'), FacturasPagos.numero.alias('NumParcialidad'), FacturasPagos.saldo_anterior.alias('ImpSaldoAnt'), FacturasPagos.importe.alias('ImpPagado'), FacturasPagos.saldo.alias('ImpSaldoInsoluto'), ).join(Facturas).switch(FacturasPagos) .where(filters) .dicts()) taxes_pay = {'retenciones': {}, 'traslados': {}, 'totales': {}} for r in related: r['taxes'] = self._get_taxes_by_pay(self, r, taxes_pay) # ~ print('\n\nMONEDA', currency, r['MonedaDR']) r['IdDocumento'] = str(r['IdDocumento']) r['Folio'] = str(r['Folio']) r['NumParcialidad'] = str(r['NumParcialidad']) r['TipoCambioDR'] = FORMAT6.format(r['TipoCambioDR']) # ~ r['MetodoDePagoDR'] = DEFAULT_CFDIPAY['WAYPAY'] # REVISAR r['EquivalenciaDR'] = '1' r['ObjetoImpDR'] = '02' r['ImpSaldoAnt'] = FORMAT.format(r['ImpSaldoAnt']) r['ImpPagado'] = FORMAT.format(r['ImpPagado']) if round(r['ImpSaldoInsoluto'], 2) == 0.0: r['ImpSaldoInsoluto'] = '0.00' else: r['ImpSaldoInsoluto'] = FORMAT.format(r['ImpSaldoInsoluto']) if currency == r['MonedaDR']: del r['TipoCambioDR'] if not r['Serie']: del r['Serie'] total_tax_iva_16_base = 0 total_tax_iva_16_importe = 0 for key, importe in taxes_pay['retenciones'].items(): taxes_pay['retenciones'][key] = FORMAT.format(importe) for k, tax in taxes_pay['traslados'].items(): tax_type = taxes_pay['traslados'][k]['ImpuestoP'] tax_tasa = taxes_pay['traslados'][k]['TasaOCuotaP'] tax_base = taxes_pay['traslados'][k]['BaseP'] importe = taxes_pay['traslados'][k]['ImporteP'] if f'{tax_type}|{tax_tasa}' == TAX_IVA_16: total_tax_iva_16_base += tax_base total_tax_iva_16_importe += importe taxes_pay['traslados'][k]['BaseP'] = FORMAT.format(tax_base) taxes_pay['traslados'][k]['ImporteP'] = FORMAT.format(importe) taxes_pay['totales'] = { 'TotalTrasladosBaseIVA16': FORMAT.format(total_tax_iva_16_base), 'TotalTrasladosImpuestoIVA16': FORMAT.format(total_tax_iva_16_importe), } return related, taxes_pay def _generate_xml(self, invoice): emisor = Emisor.select()[0] certificado = Certificado.get(Certificado.es_fiel==False) used_data_bank = Configuracion.get_bool('chk_cfg_pays_data_bank') cfdi = {} related = {} cfdi['Serie'] = invoice.serie cfdi['Folio'] = str(invoice.folio) cfdi['Fecha'] = invoice.fecha.isoformat()[:19] cfdi['NoCertificado'] = certificado.serie cfdi['SubTotal'] = '0' cfdi['Moneda'] = DEFAULT_CFDIPAY['CURRENCY'] # ~ cfdi['TipoCambio'] = DEFAULT_CFDIPAY['TC'] cfdi['Total'] = '0' cfdi['TipoDeComprobante'] = invoice.tipo_comprobante cfdi['LugarExpedicion'] = invoice.lugar_expedicion if invoice.tipo_relacion: related = { 'tipo': invoice.tipo_relacion, 'cfdis': (str(invoice.uuid_relacionado),), } emisor = { 'Rfc': emisor.rfc, 'Nombre': emisor.nombre, 'RegimenFiscal': invoice.regimen_fiscal, } receptor = { 'Rfc': invoice.socio.rfc, 'Nombre': invoice.socio.nombre, 'UsoCFDI': DEFAULT_CFDIPAY['USED'], 'DomicilioFiscalReceptor': invoice.socio.codigo_postal, 'RegimenFiscalReceptor': invoice.receptor_regimen } if invoice.socio.tipo_persona == 4: if invoice.socio.pais: receptor['ResidenciaFiscal'] = invoice.socio.pais if invoice.socio.id_fiscal: receptor['NumRegIdTrib'] = invoice.socio.id_fiscal conceptos = ({ 'ClaveProdServ': DEFAULT_CFDIPAY['KEYSAT'], 'Cantidad': '1', 'ClaveUnidad': DEFAULT_CFDIPAY['UNITKEY'], 'Descripcion': DEFAULT_CFDIPAY['DESCRIPTION'], 'ValorUnitario': '0', 'Importe': '0', 'ObjetoImp': '01', },) impuestos = {} mov = invoice.movimiento currency = mov.moneda related_docs, taxes_pay = self._get_related_xml(self, invoice.movimiento, currency) totales = taxes_pay.pop('totales') pagos = { 'FechaPago': mov.fecha.isoformat()[:19], 'FormaDePagoP': mov.forma_pago.key, 'MonedaP': currency, 'TipoCambioP': '1', 'Monto': FORMAT.format(mov.deposito), 'relacionados': related_docs, 'taxes_pay': taxes_pay, } if mov.numero_operacion: pagos['NumOperacion'] = mov.numero_operacion if used_data_bank and mov.cuenta_socio: if mov.cuenta_socio.banco.rfc: pagos['RfcEmisorCtaOrd'] = mov.cuenta_socio.banco.rfc if mov.cuenta_socio.banco.rfc == RFC_EXTRANJERO: pagos['NomBancoOrdExt'] = mov.cuenta_socio.banco.name pagos['CtaOrdenante'] = mov.cuenta_socio.cuenta if mov.cuenta.banco.rfc: pagos['RfcEmisorCtaBen'] = mov.cuenta.banco.rfc pagos['CtaBeneficiario'] = mov.cuenta.cuenta if currency != CURRENCY_MN: pagos['TipoCambioP'] = FORMAT_TAX.format(mov.tipo_cambio) totales['MontoTotalPagos'] = pagos['Monto'] pagos['totales'] = totales complementos = {'pagos': pagos} data = { 'comprobante': cfdi, 'relacionados': related, 'emisor': emisor, 'receptor': receptor, 'conceptos': conceptos, 'impuestos': impuestos, 'donativo': {}, 'edu': False, 'complementos': complementos, } return utils.make_xml(data, certificado) def _stamp(self, values): id_mov = int(values['id_mov']) send_email = Configuracion.get_bool('correo_directo') auth = Configuracion.get_({'fields': 'pac_auth'}) filters = ( (CfdiPagos.movimiento==id_mov) & (CfdiPagos.uuid.is_null(True)) ) obj = CfdiPagos.get(filters) obj.xml = self._generate_xml(self, obj) obj.estatus = 'Generada' obj.save() msg = 'Factura timbrada correctamente' result = utils.xml_stamp(obj.xml, auth) if result['ok']: obj.xml = result['xml'] obj.uuid = result['uuid'] obj.fecha_timbrado = result['date'] obj.estatus = 'Timbrada' obj.error = '' row = {'uuid': obj.uuid, 'estatus': 'Timbrada'} else: msg = result['error'] obj.estatus = 'Error' obj.error = msg row = {'estatus': 'Error'} obj.save() result = { 'ok': result['ok'], 'msg': msg, 'id': obj.id, 'row': row, } if result['ok']: self._sync(self, obj.id, send_email) return result def _get_related(self, values): id_mov = int(values['id_mov']) filters = ( (CfdiPagos.movimiento==id_mov) ) rows = tuple(CfdiPagos.select( CfdiPagos.id, CfdiPagos.serie, CfdiPagos.folio, CfdiPagos.uuid, CfdiPagos.fecha, CfdiPagos.tipo_comprobante, CfdiPagos.estatus, Socios.nombre.alias('cliente'), ).join(Socios).switch(CfdiPagos) .where(filters).dicts()) return {'ok': True, 'rows': rows} @classmethod def get_file_xml(cls, id): obj = CfdiPagos.get(CfdiPagos.id==id) folio = str(obj.folio).zfill(6) name = '{}{}_{}.xml'.format(obj.serie, folio, obj.socio.rfc) emisor = Emisor.select()[0] target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/') files = ( (obj.xml, name, target), ) cls._sync_files(cls, files) return obj.xml, name def _get_not_in_xml(self, invoice, emisor): values = {'el.version': VERSION_EMPRESA_LIBRE} values['notas'] = invoice.notas values['fechadof'] = None obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal) values['regimenfiscal'] = str(obj) obj = SATUsoCfdi.get(SATUsoCfdi.key=='P01') values['usocfdi'] = str(obj) values['moneda'] = 'XXX' if invoice.tipo_relacion: obj = SATTipoRelacion.get(SATTipoRelacion.key==invoice.tipo_relacion) values['tiporelacion'] = str(obj) receptor = Socios.select().where(Socios.id==invoice.socio.id).dicts()[0] values['receptor'] = {} for k, v in receptor.items(): values['receptor'][k] = v values['pagos'] = True return values @classmethod def get_file_pdf(cls, id): try: emisor = Emisor.select()[0] except IndexError: return b'', 'sin_datos_de_emisor.pdf' obj = CfdiPagos.get(CfdiPagos.id==id) folio = str(obj.folio).zfill(6) name = '{}{}_{}.pdf'.format(obj.serie, folio, obj.socio.rfc) if obj.uuid is None: return b'', name target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/') values = cls._get_not_in_xml(cls, obj, emisor) data = util.get_data_from_xml(obj, values) obj = SATFormaPago.get(SATFormaPago.key==data['pays']['FormaDePagoP']) data['pays']['formadepago'] = '{} ({})'.format(obj.name, obj.key) doc = util.to_pdf(data, emisor.rfc) files = ( (doc, name, target), ) cls._sync_files(cls, files) return doc, name @util.run_in_thread def _sync(self, id, send_email=False): files = (CfdiPagos.get_file_xml(id), CfdiPagos.get_file_pdf(id)) if send_email: self._send(self, {'id': id}, files) return @util.run_in_thread def _sync_files(self, files): util.sync_files(files) return @classmethod def get_values(cls, values): opt = values.pop('opt') return getattr(cls, '_get_{}'.format(opt))(cls, values) def _get_status_sat(self, values): id = int(values['id']) obj = CfdiPagos.get(CfdiPagos.id == id) estatus_sat = utils.get_status_sat(obj.xml) if obj.estatus_sat != estatus_sat: obj.estatus_sat = estatus_sat obj.save() # ~ if obj.estatus_sat == 'Vigente' and obj.estatus == 'Cancelada': # ~ days = utils.get_days(obj.fecha_cancelacion) # ~ if days > 3: # ~ estatus_sat = 'uncancel' return estatus_sat def _get_table(self, values): # ~ print('\n\n', values, '\n') if 'start' in values: filters = CfdiPagos.fecha.between( util.get_date(values['start']), util.get_date(values['end'], True)) else: if values['year'] == '-1': fy = (CfdiPagos.fecha.year > 0) else: fy = (CfdiPagos.fecha.year == int(values['year'])) if values['month'] == '-1': fm = (CfdiPagos.fecha.month > 0) else: fm = (CfdiPagos.fecha.month == int(values['month'])) filters = (fy & fm) rows = tuple(CfdiPagos.select( CfdiPagos.id, CfdiPagos.serie, CfdiPagos.folio, CfdiPagos.uuid, CfdiPagos.fecha, CfdiPagos.tipo_comprobante, CfdiPagos.estatus, case(MovimientosBanco.cancelado, ( (True, 'Cancelado'), (False, 'Activo'), )).alias('movimiento'), MovimientosBanco.deposito.alias('total'), MovimientosBanco.moneda.alias('currency'), (MovimientosBanco.deposito * MovimientosBanco.tipo_cambio).alias('total_mn'), Socios.nombre.alias('cliente')) .join(MovimientosBanco).switch(CfdiPagos) .join(Socios).switch(CfdiPagos) .where(filters).dicts()) return {'ok': True, 'rows': rows} def _send(self, values, files=()): id = int(values['id']) values = Configuracion.get_({'fields': 'correo'}) contra = Configuracion.get_('correo_contra') rfc = Emisor.select()[0].rfc if not values: msg = 'No esta configurado el servidor de correo de salida' return {'ok': False, 'msg': msg} obj = CfdiPagos.get(CfdiPagos.id==id) if obj.uuid is None: msg = 'La factura no esta timbrada' return {'ok': False, 'msg': msg} to = obj.socio.correo_facturasp or obj.socio.correo_facturas if not to: msg = 'El cliente no tiene configurado el correo para facturas' return {'ok': False, 'msg': msg} if not files: files = (self.get_file_pdf(id), self.get_file_xml(id)) fields = util.make_fields(obj.xml) starttls = bool(int(values.get('correo_starttls', '0'))) server = { 'servidor': values['correo_servidor'], 'puerto': values['correo_puerto'], 'ssl': bool(int(values['correo_ssl'])), 'starttls': starttls, 'usuario': values['correo_usuario'], 'contra': utils.decrypt(contra, rfc), } options = { 'para': to, 'copia': values.get('correo_copia', ''), 'confirmar': util.get_bool(values.get('correo_confirmacion', '0')), 'asunto': util.make_info_mail(values['correo_asunto'], fields), 'mensaje': util.make_info_mail(values['correo_mensaje'], fields), 'files': files, } data= { 'server': server, 'options': options, } result = util.send_mail(data) if not result['ok'] or result['msg']: return {'ok': False, 'msg': result['msg']} msg = 'Factura enviada correctamente' return {'ok': True, 'msg': msg} class PreFacturasImpuestos(BaseModel): factura = ForeignKeyField(PreFacturas) impuesto = ForeignKeyField(SATImpuestos) base = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) class Meta: order_by = ('factura',) indexes = ( (('factura', 'impuesto'), True), ) @classmethod def get_(cls, id): data = { 'traslados': [], 'retenciones': [], 'taxlocales': [], } taxes = PreFacturasImpuestos.select().where( PreFacturasImpuestos.factura==id) for tax in taxes: if tax.impuesto.tipo == 'T': title = 'Traslado {} {}'.format( tax.impuesto.name, str(tax.impuesto.tasa)) data['traslados'].append((title, str(tax.importe))) elif tax.impuesto.tipo == 'R': title = 'Retención {} {}'.format( tax.impuesto.name, str(tax.impuesto.tasa)) data['retenciones'].append((title, str(tax.importe))) return data class CamposPersonalizados(BaseModel): nombre = TextField() slug = TextField(unique=True) class Meta: order_by = ('nombre',) class FacturasPersonalizados(BaseModel): factura = ForeignKeyField(Facturas) campo = TextField() valor = TextField() class Meta: order_by = ('factura',) class Tickets(BaseModel): cliente = ForeignKeyField(Socios, null=True) serie = TextField(default='') folio = IntegerField(default=0) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) forma_pago = TextField(default='') subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_trasladados = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) estatus = TextField(default='Generado') notas = TextField(default='') factura = ForeignKeyField(Facturas, null=True) cancelado = BooleanField(default=False) vendedor = TextField(default='') comision = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) cambio = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class Meta: order_by = ('fecha',) def _get_folio(self, serie): inicio = (Tickets .select(fn.Max(Tickets.folio).alias('mf')) .where(Tickets.serie==serie) .order_by(SQL('mf')) .scalar()) if inicio is None: inicio = 1 else: inicio += 1 return inicio def _without_tax(self, importe, obj): # ~ Por ahora se asume que solo tiene IVA trasladado 0.16 for tax in obj.impuestos: tasa = 1.0 + float(tax.tasa) base = round(importe / tasa, DECIMALES) return base def _calcular_totales(self, ticket, productos, user=None): subtotal = 0 descuento_cfdi = 0 totals_tax = {} total_trasladados = None warehouse = None try: warehouse = user.sucursal.warehouse except: pass for producto in productos: id_producto = producto.pop('id_product') p = Productos.get(Productos.id==id_producto) producto['descripcion'] = p.descripcion producto['ticket'] = ticket.id producto['producto'] = id_producto cantidad = float(producto['cantidad']) valor_unitario = self._without_tax(self, producto['valor_unitario'], p) descuento = float(producto['descuento'] or 0) precio_final = valor_unitario - descuento importe = round(cantidad * precio_final, DECIMALES) producto['cantidad'] = cantidad producto['valor_unitario'] = valor_unitario producto['descuento'] = descuento producto['precio_final'] = precio_final producto['importe'] = importe producto['warehouse'] = warehouse descuento_cfdi += descuento subtotal += importe TicketsDetalle.create(**producto) # ~ if p.inventario: # ~ p.existencia -= Decimal(cantidad) # ~ p.save() base = producto['importe'] for tax in p.impuestos: impuesto_producto = round(float(tax.tasa) * base, DECIMALES) if tax.tipo == 'T' and tax.key != '000': total_trasladados = (total_trasladados or 0) + impuesto_producto if tax.id in totals_tax: totals_tax[tax.id].base += base totals_tax[tax.id].importe += impuesto_producto else: tax.base = base tax.importe = impuesto_producto totals_tax[tax.id] = tax for tax in totals_tax.values(): if tax.tipo == 'E': continue ticket_tax = { 'ticket': ticket.id, 'impuesto': tax.id, 'base': tax.base, 'importe': tax.importe, } TicketsImpuestos.create(**ticket_tax) total = subtotal + (total_trasladados or 0) data = { 'subtotal': subtotal + descuento, 'descuento': descuento_cfdi, 'total': total, 'total_trasladados': total_trasladados, } return data def _get_serie(self, user, invoice=False): default_serie = DEFAULT_SERIE_TICKET if invoice: default_serie = Folios.get_default() if user.sucursal is None: return default_serie if invoice: return user.sucursal.serie_facturas or default_serie else: return user.sucursal.serie_tickets or default_serie @classmethod def get_notes(cls, tickets): rows = Tickets.select(Tickets.notas).where(Tickets.id.in_(tickets)) return '\n'.join([r.notas for r in rows]) @utils.run_in_thread def _update_inventory(cls, id_ticket, cancel=False, user=None): warehouse = None try: warehouse = user.sucursal.warehouse except: pass products = (TicketsDetalle .select() .where(TicketsDetalle.ticket==id_ticket) ) for p in products: if p.producto.inventario: cant = Decimal(p.cantidad) if cancel: cant *= -1 warehouse = p.warehouse p.producto.existencia -= cant p.producto.save() if warehouse is None: continue where = ( WareHouseProduct.warehouse==warehouse, WareHouseProduct.product==p.producto, ) obj = WareHouseProduct.get(*where) obj.exists -= cant obj.save() return @classmethod def add(cls, values, user): productos = util.loads(values.pop('productos')) values['serie'] = cls._get_serie(cls, user) values['folio'] = cls._get_folio(cls, values['serie']) with database_proxy.atomic() as txn: obj = Tickets.create(**values) totals = cls._calcular_totales(cls, obj, productos, user) obj.subtotal = totals['subtotal'] obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] obj.total = totals['total'] obj.save() cls._update_inventory(cls, obj.id, user=user) row = { 'id': obj.id, 'serie': obj.serie, 'folio': obj.folio, 'fecha': obj.fecha, 'estatus': obj.estatus, 'total': obj.total, } data = {'ok': True, 'row': row} return data def _get_folio_invoice(self, serie): inicio = (Facturas .select(fn.Max(Facturas.folio).alias('mf')) .where(Facturas.serie==serie) .order_by(SQL('mf')) .scalar()) if inicio is None: inicio = 1 else: inicio += 1 return inicio @classmethod def uncancel(cls, invoice): query = (Tickets .update(estatus='Generado', cancelado=False, factura=None) .where(Tickets.factura==invoice) ) result = query.execute() return result def _cancel_tickets(self, invoice, tickets): query = (Tickets .update(estatus='Facturado', cancelado=True, factura=invoice) .where(Tickets.id.in_(tickets)) ) result = query.execute() return result def _calculate_totals_invoice(self, invoice, tickets): subtotal = 0 descuento_cfdi = 0 totals_tax = {} total_trasladados = None total_retenciones = None notes = Tickets.get_notes(tickets) details = TicketsDetalle.select().where(TicketsDetalle.ticket.in_(tickets)) for detail in details: product = {} p = detail.producto product['unidad'] = p.unidad.key product['clave'] = p.clave product['clave_sat'] = p.clave_sat product['factura'] = invoice.id product['producto'] = p.id product['descripcion'] = detail.descripcion cantidad = float(detail.cantidad) valor_unitario = float(detail.valor_unitario) descuento = float(detail.descuento) precio_final = valor_unitario - descuento importe = round(cantidad * precio_final, DECIMALES) product['cantidad'] = cantidad product['valor_unitario'] = valor_unitario product['descuento'] = round(descuento * cantidad, DECIMALES) product['precio_final'] = precio_final product['importe'] = round(cantidad * valor_unitario, DECIMALES) descuento_cfdi += product['descuento'] subtotal += product['importe'] FacturasDetalle.create(**product) base = product['importe'] - product['descuento'] for tax in p.impuestos: impuesto_producto = round(float(tax.tasa) * base, DECIMALES) if tax.tipo == 'T' and tax.key != '000': total_trasladados = (total_trasladados or 0) + impuesto_producto elif tax.tipo == 'R' and tax.key != '000': total_retenciones = (total_retenciones or 0) + impuesto_producto elif tax.tipo == 'T' and tax.key == '000': locales_traslados += impuesto_producto elif tax.tipo == 'R' and tax.key == '000': locales_retenciones += impuesto_producto if tax.id in totals_tax: totals_tax[tax.id].base += base totals_tax[tax.id].suma_impuestos += impuesto_producto else: tax.base = base tax.suma_impuestos = impuesto_producto totals_tax[tax.id] = tax for tax in totals_tax.values(): if tax.tipo == 'E': continue invoice_tax = { 'factura': invoice.id, 'impuesto': tax.id, 'base': tax.base, 'importe': tax.suma_impuestos, } FacturasImpuestos.create(**invoice_tax) total = subtotal - descuento_cfdi + \ (total_trasladados or 0) - (total_retenciones or 0) total_mn = round(total * invoice.tipo_cambio, DECIMALES) data = { 'subtotal': subtotal, 'descuento': descuento_cfdi, 'total': total, 'total_mn': total_mn, 'total_trasladados': total_trasladados, 'total_retenciones': total_retenciones, 'notas': notes, } return data def _get_totals_invoice_by_ticket(self, invoice, ids): subtotal = 0 descuento_cfdi = 0 totals_tax = {} total_trasladados = None total_retenciones = None notes = Tickets.get_notes(ids) rows = Tickets.select().where(Tickets.id.in_(ids)) tax_sum = {} for row in rows: details = DEFAULT_GLOBAL.copy() details['clave'] = row.serie + str(row.folio) details['factura'] = invoice.id unit_value = row.subtotal discount = row.descuento final_price = unit_value - discount importe = final_price details['valor_unitario'] = unit_value details['descuento'] = discount details['precio_final'] = final_price details['importe'] = importe descuento_cfdi += details['descuento'] subtotal += details['importe'] FacturasDetalle.create(**details) taxes = (TicketsImpuestos .select( TicketsImpuestos.impuesto, TicketsImpuestos.base, TicketsImpuestos.importe) .where(TicketsImpuestos.ticket == row.id) ) for r in taxes: tax_id = r.impuesto.id tasa = r.impuesto.tasa tax_importe = round(tasa * r.base, DECIMALES) if tax_id in tax_sum: tax_sum[tax_id]['base'] += r.base tax_sum[tax_id]['importe'] += tax_importe else: values = { 'tipo': r.impuesto.tipo, 'key': r.impuesto.key, 'base': r.base, 'importe': tax_importe} tax_sum[tax_id] = values for i, tax in tax_sum.items(): tax_importe = round(tax['importe'], DECIMALES) invoice_tax = { 'factura': invoice.id, 'impuesto': i, 'base': tax['base'], 'importe': tax_importe } FacturasImpuestos.create(**invoice_tax) if tax['tipo'] == 'T' and tax['key'] != '000': total_trasladados = (total_trasladados or 0) + tax_importe elif tax['tipo'] == 'R' and tax['key'] != '000': total_retenciones = (total_retenciones or 0) + tax_importe total = subtotal - descuento_cfdi + \ (total_trasladados or 0) - (total_retenciones or 0) type_change = Decimal(invoice.tipo_cambio) total_mn = round(total * type_change, DECIMALES) data = { 'subtotal': subtotal, 'descuento': descuento_cfdi, 'total': total, 'total_mn': total_mn, 'total_trasladados': total_trasladados, 'total_retenciones': total_retenciones, 'notas': notes, } return data @classmethod def invoice(cls, values, user): is_invoice_day = util.get_bool(values['is_invoice_day']) periodicidad = values['periodicidad'] id_client = int(values['client']) tickets = util.loads(values['tickets']) invoice_by_ticket = Configuracion.get_bool('chk_config_invoice_by_ticket') if is_invoice_day: filters = ( Socios.rfc == RFCS['PUBLIC'] and Socios.slug == 'publico_en_general') try: client = Socios.get(filters) except Socios.DoesNotExist: msg = 'No existe el cliente Público en General. Agregalo primero.' data = {'ok': False, 'msg': msg} return data else: client = Socios.get(Socios.id==id_client) periodicidad = '' if client.forma_pago is None: msg = 'La Forma de Pago del cliente, no esta asignada' data = {'ok': False, 'msg': msg} return data try: receptor_regimen = SociosRegimenes.get_by_key(client)[0]['id'] except Exception as e: log.error(e) msg = 'Error al obtener el Regimen Fiscal del receptor' data = {'ok': False, 'msg': msg} return data payment_type = cls._get_payment_type(cls, tickets) emisor = Emisor.select()[0] data = {} data['cliente'] = client data['serie'] = cls._get_serie(cls, user, True) data['folio'] = cls._get_folio_invoice(cls, data['serie']) data['forma_pago'] = payment_type data['tipo_cambio'] = 1.00 data['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal data['uso_cfdi'] = client.uso_cfdi.key data['regimen_fiscal'] = emisor.regimenes[0].key data['receptor_regimen'] = receptor_regimen data['periodicidad'] = periodicidad with database_proxy.atomic() as txn: obj = Facturas.create(**data) if is_invoice_day and invoice_by_ticket: totals = cls._get_totals_invoice_by_ticket(cls, obj, tickets) else: totals = cls._calculate_totals_invoice(cls, obj, tickets) obj.subtotal = totals['subtotal'] obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] obj.total_retenciones = totals['total_retenciones'] obj.total = totals['total'] obj.saldo = totals['total'] obj.total_mn = totals['total_mn'] obj.notas = totals['notas'] obj.save() cls._cancel_tickets(cls, obj, tickets) msg = 'Factura generada correctamente.

Enviando a timbrar' data = {'ok': True, 'msg': msg, 'id': obj.id} return data def _get_payment_type(self, ids): """Get max of payment type""" query = (Tickets .select( Tickets.forma_pago, fn.Sum(Tickets.subtotal).alias('total')) .where(Tickets.id.in_(ids)) .group_by(Tickets.forma_pago) .order_by(SQL('total').desc()) .limit(1) .scalar() ) return query # ~ def _update_inventory_if_cancel(self, id): # ~ products = TicketsDetalle.select().where(TicketsDetalle.ticket==id) # ~ for p in products: # ~ if p.producto.inventario: # ~ p.producto.existencia += p.cantidad # ~ p.producto.save() # ~ return @classmethod def cancel(cls, values, user): admin_cancel = Configuracion.get_bool('chk_cancel_tickets_by_admin') if admin_cancel and not user.es_admin: msg = 'Solo un admin puede cancelar tickets' result = {'ok': False, 'msg': msg, 'row': {}} return result id = int(values['id']) msg = 'Ticket cancelado correctamente' fields = {'cancelado': True, 'estatus': 'Cancelado'} with database_proxy.atomic() as txn: obj = Tickets.update(**fields).where(Tickets.id==id) result = bool(obj.execute()) if result: cls._update_inventory(cls, id, True, user) row = {'estatus': 'Cancelado'} return {'ok': result, 'row': row, 'msg': msg} def _get_filters(self, values): opt = values.get('opt', '') if not opt: return if opt == 'active': filters = (Tickets.cancelado==False) return filters if opt == 'today': t = util.today() filters = ( (Tickets.fecha.day == t.day) & (Tickets.fecha.month == t.month) & (Tickets.fecha.year == t.year) ) return filters if opt == 'yearmonth': if values['year'] == '-1': fy = (Tickets.fecha.year > 0) else: fy = (Tickets.fecha.year == int(values['year'])) if values['month'] == '-1': fm = (Tickets.fecha.month > 0) else: fm = (Tickets.fecha.month == int(values['month'])) filters = (fy & fm) return filters if opt == 'dates': dates = util.loads(values['range']) filters = Tickets.fecha.between( util.get_date(dates['start']), util.get_date(dates['end'], True) ) return filters return @classmethod def get_by(cls, values, user): filter_serie = False filter_by_user = Configuracion.get_bool('chk_ticket_user_show_doc') if not filter_by_user: if not user.sucursal is None and not user.sucursal.serie_tickets is None: filter_serie = (Tickets.serie==user.sucursal.serie_tickets) filters = cls._get_filters(cls, values) if filter_serie: filters = filters & filter_serie rows = tuple(Tickets .select( Tickets.id, Tickets.serie, Tickets.folio, Tickets.fecha, Tickets.estatus, Tickets.total) .where(filters) .dicts() ) return {'ok': True, 'rows': rows} @classmethod def filter_years(cls): data = [{'id': -1, 'value': 'Todos'}] rows = (Tickets .select(Tickets.fecha.year.alias('year')) .group_by(Tickets.fecha.year) .order_by(Tickets.fecha.year) ) if not rows is None: data += [{'id': int(r.year), 'value': int(r.year)} for r in rows] return tuple(data) def _get_info_to_pdf(self, id): data = {} obj = Tickets.select().where(Tickets.id==id).dicts()[0] formapago = SATFormaPago.get(SATFormaPago.key==obj['forma_pago']) emisor = util.get_dict(Emisor.select().dicts()[0]) emisor['nointerior'] = emisor['no_interior'] emisor['noexterior'] = emisor['no_exterior'] emisor['codigopostal'] = emisor['codigo_postal'] data['is_ticket'] = True data['cancelada'] = obj['cancelado'] data['donativo'] = '' if obj['estatus'] == 'Facturado': data['cancelada'] = False data['comprobante'] = obj data['comprobante']['version'] = 'ticket' data['comprobante']['folio'] = str(data['comprobante']['folio']) data['comprobante']['seriefolio'] = '{}-{}'.format( data['comprobante']['serie'], data['comprobante']['folio']) data['comprobante']['fecha'] = str(data['comprobante']['fecha']) data['comprobante']['formadepago'] = str(formapago) data['comprobante']['totalenletras'] = util.to_letters( data['comprobante']['total'], 'peso') data['emisor'] = emisor data['receptor'] = {'nombre': 'Público en general'} data['conceptos'] = TicketsDetalle.get_by_ticket(id) data['totales'] = {} data['totales']['total'] = str(data['comprobante']['total']) data['totales']['subtotal'] = str(data['comprobante']['subtotal']) data['totales']['moneda'] = 'peso' data['totales']['traslados'] = () data['totales']['retenciones'] = () data['totales']['taxlocales'] = () data['timbre'] = {} data['donataria'] = {} data['ine'] = {} data['leyendas'] = () return data @classmethod def get_pdf(cls, id): obj = Tickets.get(Tickets.id==id) name = '{}{}.pdf'.format(obj.serie, obj.folio) data = cls._get_info_to_pdf(cls, id) doc = util.to_pdf(data, data['emisor']['rfc']) return doc, name def _format_ticket(self, id): emisor = util.get_dict(Emisor.select().dicts()[0]) regimen = Emisor.select()[0].regimenes[0].name ticket = Tickets.select().where(Tickets.id==id).dicts()[0] products = TicketsDetalle.get_by_print(id) emisor['name'] = '{}\n'.format(emisor['nombre']) emisor['rfc'] = 'RFC: {}\n'.format(emisor['rfc']) emisor['regimen'] = 'Regimen: {}\n'.format(regimen) interior = '' if emisor['no_interior']: interior = ', {}'.format(emisor['no_interior']) colonia = '' if emisor['colonia']: colonia = ', Col. {}'.format(emisor['colonia']) municipio = '' if emisor['municipio']: municipio = ', {}'.format(emisor['municipio']) estado = '' if emisor['estado']: estado = ', {}'.format(emisor['estado']) cp = '' if emisor['codigo_postal']: cp = ', C.P. {}'.format(emisor['codigo_postal']) pais = '' if emisor['pais']: pais = ', {}'.format(emisor['pais']) direccion = '{} {}{}{}{}{}{}{}\n\n'.format(emisor['calle'], emisor['no_exterior'], interior, colonia, municipio, estado, cp, pais) emisor['address'] = direccion ticket['title'] = 'Ticket: {}{}\n\n'.format(ticket['serie'], ticket['folio']) ticket['date'] = 'Fecha y hora: {}\n'.format(ticket['fecha']) ticket['letters'] = '{}\n\n'.format( util.to_letters(ticket['total'], 'peso')) ticket['total'] = 'TOTAL: $ {:>12,.2f}\n\n'.format(ticket['total']) data = { 'emisor': emisor, 'receptor': {'name': '{}\n\n'.format(PUBLIC)}, 'ticket': ticket, 'products': products, } return data def _get_info_printer(self): info = {} value = Configuracion.get_('txt_ticket_printer') if not value: return info values = value.split(':') if len(values) == 1: info = {'ip': values[0], 'usb': ()} elif len(values) == 2: info = {'ip': '', 'usb': (int(values[0], 16), int(values[1], 16))} elif len(values) == 5: info = {'ip': '', 'usb': ( int(values[0], 16), int(values[1], 16), int(values[2]), int(values[3], 16), int(values[4], 16), ) } return info @classmethod def printer(cls, values): id = int(values['id']) info_printer = cls._get_info_printer(cls) if not info_printer: msg = 'Es necesario configurar una impresora.' result = {'ok': False, 'msg': msg} return result data = cls._format_ticket(cls, id) result = util.print_ticket(data, info_printer) msg = 'Ticket impreso correctamente' if not result: msg = 'Asegurate de que la impresora este conectada y funcionando.' result = {'ok': result, 'msg': msg} return result class TicketsDetalle(BaseModel): ticket = ForeignKeyField(Tickets) producto = ForeignKeyField(Productos, null=True) descripcion = TextField(default='') cantidad = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) precio_final = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) warehouse = ForeignKeyField(Almacenes, null=True) class Meta: order_by = ('ticket',) def _with_tax(self, product): precio_final = float(product.precio_final) base = float(product.precio_final) for tax in product.producto.impuestos: impuesto_producto = round(float(tax.tasa) * base, DECIMALES) precio_final += impuesto_producto return precio_final @classmethod def get_by_print(cls, id): products = TicketsDetalle.select().where(TicketsDetalle.ticket==id) lines = [] for p in products: price_with_tax = cls._with_tax(cls, p) importe = round(price_with_tax * float(p.cantidad), DECIMALES) l = '{:>6,.2f} {:<4} {:<14} {:>9,.2f} {:>10,.2f}\n'.format( p.cantidad, p.producto.unidad.name[:5], p.descripcion[:14], price_with_tax, importe ) lines.append(l) return lines @classmethod def get_by_ticket(cls, id): data = [] productos = TicketsDetalle.select().where(TicketsDetalle.ticket==id) for p in productos: producto = {} producto['noidentificacion'] = p.producto.clave producto['descripcion'] = p.descripcion producto['unidad'] = p.producto.unidad.name producto['cantidad'] = str(p.cantidad) price_with_tax = cls._with_tax(cls, p) producto['valorunitario'] = str(price_with_tax) importe = round(price_with_tax * float(p.cantidad), DECIMALES) producto['importe'] = str(importe) data.append(producto) return data def _get_by_ticket_id(cls, args, user): products = [] id = int(args['id']) rows = TicketsDetalle.select().where(TicketsDetalle.ticket==id) for row in rows: # ~ descuento # ~ precio_final # ~ inventario # ~ existencia product = dict( id_product = row.producto.id, clave = row.producto.clave, clave_sat = row.producto.clave_sat, descripcion = row.descripcion, unidad = row.producto.unidad.id, cantidad = float(row.cantidad), valor_unitario = cls._with_tax(cls, row), ) product['importe'] = round( product['valor_unitario'] * product['cantidad'], DECIMALES) products.append(product) return tuple(products) @classmethod def get_data(cls, values, user): opt = values.pop('opt') method = f'_get_{opt}' return getattr(cls, method)(cls, values, user) class TicketsImpuestos(BaseModel): ticket = ForeignKeyField(Tickets) impuesto = ForeignKeyField(SATImpuestos) base = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) class Meta: order_by = ('ticket',) indexes = ( (('ticket', 'impuesto'), True), ) class SeriesProductos(BaseModel): factura = ForeignKeyField(FacturasDetalle, null=True) ticket = ForeignKeyField(TicketsDetalle, null=True) serie = TextField(default='') class Meta: order_by = ('serie',) class Departamentos(BaseModel): nombre = TextField(default='', unique=True) descripcion = TextField(default='') class Meta: order_by = ('nombre',) class Puestos(BaseModel): departamento = ForeignKeyField(Departamentos, null=True) nombre = TextField(default='') descripcion = TextField(default='') class Meta: order_by = ('nombre',) indexes = ( (('departamento', 'nombre'), True), ) @classmethod def get_by_depto(cls, puesto, depto): departamento = None if depto: with database_proxy.transaction(): departamento, _ = Departamentos.get_or_create(nombre=depto) data = {'departamento': departamento, 'nombre': puesto} obj, _ = Puestos.get_or_create(**data) return obj class Empleados(BaseModel): num_empleado = TextField(default='') rfc = TextField(default='', unique=True) curp = TextField(default='', unique=True) nombre = TextField(default='') paterno = TextField(default='') materno = TextField(default='') nombre_completo = TextField(default='') es_activo = BooleanField(default=True) es_extranjero = BooleanField(default=False) fecha_alta = DateField(default=util.now) fecha_ingreso = DateField(null=True) imss = TextField(default='') tipo_contrato = ForeignKeyField(SATTipoContrato) es_sindicalizado = BooleanField(default=False) tipo_jornada = ForeignKeyField(SATTipoJornada, null=True) tipo_regimen = ForeignKeyField(SATTipoRegimen) puesto = ForeignKeyField(Puestos, null=True) riesgo_puesto = ForeignKeyField(SATRiesgoPuesto, null=True) periodicidad_pago = ForeignKeyField(SATPeriodicidadPago) banco = ForeignKeyField(SATBancos, null=True) cuenta_bancaria = TextField(default='') clabe = TextField(default='') salario_base = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) salario_diario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) estado = ForeignKeyField(SATEstados) codigo_postal = TextField(default='') notas = TextField(default='') correo = TextField(default='') class Meta: order_by = ('nombre_completo',) indexes = ( (('num_empleado', 'rfc'), True), ) def _validate_import(self, row): sn = {'si': True, 'no': False} data = row.copy() data['nombre_completo'] = '{} {} {}'.format( row['nombre'], row['paterno'], row['materno']).strip() data['fecha_ingreso'] = None if row['fecha_ingreso']: data['fecha_ingreso'] = util.calc_to_date(row['fecha_ingreso']) data['tipo_contrato'] = SATTipoContrato.get_by_key(row['tipo_contrato']) data['es_sindicalizado'] = sn.get(row['es_sindicalizado'].lower(), False) data['tipo_jornada'] = SATTipoJornada.get_by_key(row['tipo_jornada']) data['tipo_regimen'] = SATTipoRegimen.get_by_key(row['tipo_regimen']) data['puesto'] = Puestos.get_by_depto(row['puesto'], row['departamento']) data['riesgo_puesto'] = SATRiesgoPuesto.get_by_key(row['riesgo_puesto']) data['periodicidad_pago'] = SATPeriodicidadPago.get_by_key(row['periodicidad_pago']) data['banco'] = SATBancos.get_by_key(row['banco']) data['estado'] = SATEstados.get_by_key(row['estado']) del data['departamento'] return data def _import(self): emisor = Emisor.select()[0] rows, msg = util.import_employees(emisor.rfc) if not rows: return {'ok': False, 'msg': msg} en = 0 ea = 0 for row in rows: if not DEBUG and row['rfc'] == 'BASM740115RW0': continue data = self._validate_import(self, row) w = (Empleados.rfc==row['rfc']) with database_proxy.transaction(): if Empleados.select().where(w).exists(): q = Empleados.update(**data).where(w) q.execute() ea += 1 else: obj = Empleados.create(**data) en += 1 msg = 'Empleados encontrados: {}
'.format(len(rows)) msg += 'Empleados nuevos: {}
'.format(en) msg += 'Empleados actualizados: {}
'.format(ea) msg += 'Empleados no importados: {}'.format(len(rows) - en - ea) return {'ok': True, 'msg': msg} def _get(self): rows = (Empleados .select( Empleados.id, Empleados.num_empleado, Empleados.rfc, Empleados.curp, Empleados.nombre_completo, Empleados.imss, Empleados.salario_base, Empleados.salario_diario, Empleados.fecha_ingreso) .dicts() ) return {'ok': True, 'rows': tuple(rows)} @classmethod def get_by(cls, values): if not 'opt' in values: return cls._get(cls) if values['opt'] == 'import': return cls._import(cls) @classmethod def remove(cls, id): try: q = Empleados.delete().where(Empleados.id==id) return bool(q.execute()) except IntegrityError: return False class CfdiNomina(BaseModel): empleado = ForeignKeyField(Empleados) version = TextField(default=CURRENT_CFDI) serie = TextField(default='N') folio = IntegerField(default=0) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) fecha_timbrado = DateTimeField(null=True) forma_pago = TextField(default='') condiciones_pago = TextField(default='') subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) moneda = TextField(default='MXN') tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6, auto_round=True) total = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) tipo_comprobante = TextField(default='N') metodo_pago = TextField(default='PUE') lugar_expedicion = TextField(default='') confirmacion = TextField(default='') uso_cfdi = TextField(default='') total_retenciones = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) total_trasladados = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) xml = TextField(default='') uuid = UUIDField(null=True) estatus = TextField(default='Guardado') estatus_sat = TextField(default='Vigente') regimen_fiscal = TextField(default='') notas = TextField(default='') saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) pagada = BooleanField(default=False) cancelada = BooleanField(default=False) fecha_cancelacion = DateTimeField(null=True) acuse = TextField(default='') tipo_relacion = TextField(default='') error = TextField(default='') version_nomina = TextField(default=CURRENT_CFDI_NOMINA) registro_patronal = TextField(default='') rfc_patron_origen = TextField(default='') tipo_nomina = ForeignKeyField(SATTipoNomina) fecha_pago = DateField() fecha_inicial_pago = DateField() fecha_final_pago = DateField() dias_pagados = DecimalField(default=0.0, max_digits=12, decimal_places=2, auto_round=True) origen_recurso = ForeignKeyField(SATOrigenRecurso, null=True) monto_recurso_propio = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class Meta: order_by = ('fecha',) @classmethod def _get_status_sat(cls, filters, user): id = filters['id'] obj = CfdiNomina.get(CfdiNomina.id == id) estatus_sat = utils.get_status_sat(obj.xml) if obj.estatus_sat != estatus_sat: obj.estatus_sat = estatus_sat obj.save() if obj.estatus_sat == 'Vigente' and obj.estatus == 'Cancelada': days = utils.get_days(obj.fecha_cancelacion) if days > 3: estatus_sat = 'uncancel' return estatus_sat @classmethod def _get_yearmonth(cls, filters, user): if filters['year'] == '-1': fy = (CfdiNomina.fecha.year > 0) else: fy = (CfdiNomina.fecha.year == int(filters['year'])) if filters['month'] == '-1': fm = (CfdiNomina.fecha.month > 0) else: fm = (CfdiNomina.fecha.month == int(filters['month'])) filters = (fy & fm) return cls._get(cls, filters) @classmethod def _get_dates(cls, filters, user): dates = util.loads(filters['range']) filters = CfdiNomina.fecha.between( util.get_date(dates['start']), util.get_date(dates['end'], True) ) return cls._get(cls, filters) @classmethod def _get_import(cls, filters, user): return cls._import(cls) @classmethod def _get_stamp(cls, filters, user): return cls._stamp(cls) @classmethod def get_data(cls, values, user): opt = values.pop('opt') return getattr(cls, f'_get_{opt}')(values, user) def _cancel(self, values, user): id = int(values['id']) args = utils.loads(values['args']) obj = CfdiNomina.get(CfdiNomina.id==id) if obj.uuid is None: msg = 'Solo se pueden cancelar recibos timbrados' return {'ok': False, 'msg': msg} auth = Configuracion.get_({'fields': 'pac_auth'}) certificado = Certificado.get(Certificado.es_fiel==False) result = utils.cancel_xml_sign(obj, args, auth, certificado) if result['ok']: obj.estatus = 'Cancelado' obj.error = '' obj.cancelada = True obj.fecha_cancelacion = result['date'] obj.acuse = result['acuse'] or '' else: obj.error = result['msg'] obj.save() return result def _send_mail(self, values, user): id = int(values['id']) msg = 'Recibo de Nómina enviado correctamente' result = {'ok': True, 'msg': msg} config = Configuracion.get_({'fields': 'correo'}) contra = Configuracion.get_('correo_contra') if not config: msg = 'Envío de correo no configurado.' result['ok'] = False result['msg'] = msg return result emisor = Emisor.select()[0] obj = CfdiNomina.get(CfdiNomina.id==id) to = obj.empleado.correo if not to: msg = 'El empleado no tiene correo.' result['ok'] = False result['msg'] = msg return result name = '{}{}_{}'.format(obj.serie, obj.folio, obj.empleado.rfc) values = self._get_not_in_xml(self, obj, emisor) data = util.get_data_from_xml(obj, values) doc = util.to_pdf(data, emisor.rfc) files = ( (obj.xml, f'{name}.xml'), (doc, f'{name}.pdf'), ) message = subject = f"Enviamos tu recibo de nómina" starttls = bool(int(values.get('correo_starttls', '0'))) server = { 'server': config['correo_servidor'], 'port': config['correo_puerto'], 'ssl': utils.to_bool(config['correo_ssl']), 'starttls': starttls, 'user': config['correo_usuario'], 'pass': utils.decrypt(contra, emisor.rfc), } mail = { 'to': to, 'copy': config.get('correo_copia', ''), 'subject': subject, 'message': message, 'files': files, } data= { 'server': server, 'mail': mail, } r = utils.send_mail(data) if not r['ok']: result['ok'] = False result['msg'] = r['msg'] return result @classmethod def post(cls, values, user): opt = values.pop('opt') return getattr(cls, '_{}'.format(opt))(cls, values, user) def _get_serie(self): serie = Configuracion.get_('txt_config_nomina_serie') if not serie: serie = DEFAULT_SAT_NOMINA['SERIE'] return serie def _get_folio(self, serie): folio = int(Configuracion.get_('txt_config_nomina_folio') or '0') inicio = (CfdiNomina .select(fn.Max(CfdiNomina.folio).alias('mf')) .where(CfdiNomina.serie==serie) .order_by(SQL('mf')) .scalar()) if inicio is None: new = 1 else: new = inicio + 1 if folio > new: new = folio return new def _get_relacionados(values): rows = values.split('|') msg = '' uuids = [] for row in rows: sf = row.split('-') if len(sf) == 1: msg = 'Formato incorrecto en CFDI relacionado: SERIE-FOLIO' return [], msg try: obj = CfdiNomina.get( CfdiNomina.serie==sf[0], CfdiNomina.folio==int(sf[1])) except CfdiNomina.DoesNotExist: msg = 'No existe el CFDI relacionado: {}'.format(row) return [], msg uuids.append({'cfdi_origen': obj.id}) return uuids, msg def _validate_nomina(self, row): sn = {'si': True, 'no': False} data = row.copy() rfc = data.pop('rfc') days_pay = data.pop('dias_pagados') try: data['empleado'] = Empleados.get(Empleados.rfc==rfc) except Empleados.DoesNotExist: msg = 'No existe el Empleado con RFC: {}'.format(rfc) return {}, msg tipo_nomina = SATTipoNomina.get_by_key(row['tipo_nomina']) if tipo_nomina is None: msg = 'RFC: {}, Tipo de Nómina no existe: {}'.format(row['tipo_nomina']) return {}, msg if data['relacionados']: data['relacionados'], msg = self._get_relacionados(data['relacionados']) if msg: return {}, msg data['serie'] = self._get_serie(self) data['folio'] = self._get_folio(self, data['serie']) data['forma_pago'] = DEFAULT_SAT_NOMINA['FORMA_PAGO'] data['uso_cfdi'] = DEFAULT_SAT_NOMINA['USO_CFDI'] data['tipo_nomina'] = tipo_nomina data['fecha_pago'] = util.calc_to_date(row['fecha_pago']) data['fecha_inicial_pago'] = util.calc_to_date(row['fecha_inicial_pago']) data['fecha_final_pago'] = util.calc_to_date(row['fecha_final_pago']) data['dias_pagados'] = days_pay return data, '' def _validate_percepciones(self, headers, row): total_gravado = 0.0 total_exento = 0.0 total_jubilacion = 0.0 total_separacion = 0.0 data = [] for i, key in enumerate(headers[::2]): gravado = 0.0 exento = 0.0 if isinstance(row[i * 2], float): gravado = round(row[i * 2], DECIMALES) if isinstance(row[i * 2 + 1], float): exento = round(row[i * 2 + 1], DECIMALES) if not gravado and not exento: continue tp = SATTipoPercepcion.get_by_key(key) if tp is None: continue total_gravado += gravado total_exento += exento if key in ('039', '044'): total_jubilacion += gravado + exento elif key in ('022', '023', '025'): total_separacion += gravado + exento new = { 'tipo_percepcion': tp, 'importe_gravado': gravado, 'importe_exento': exento, } data.append(new) total_percepciones = round(total_gravado + total_exento, DECIMALES) total_sueldos = round(total_percepciones - total_jubilacion - total_separacion, DECIMALES) totals = { 'total_gravado': total_gravado, 'total_exento': total_exento, 'total_jubilacion': total_jubilacion, 'total_separacion': total_separacion, 'total_sueldos': total_sueldos, 'total_percepciones': total_percepciones } return data, totals, '' def _validate_deducciones(self, headers, row, new_titles): total_retenciones = 0.0 total_otras_deducciones = 0.0 data = [] for i, value in enumerate(row): key = headers[0][i] importe = 0.0 if isinstance(value, float): importe = round(value, DECIMALES) if not importe: continue td = SATTipoDeduccion.get_by_key(key) if td is None: continue if key == '002': total_retenciones += importe else: total_otras_deducciones += importe new = { 'tipo_deduccion': td, 'importe': importe, 'concepto': new_titles.get(i, ''), } data.append(new) totals = { 'total_retenciones': total_retenciones, 'total_otras_deducciones': total_otras_deducciones, 'total_deducciones': round( total_retenciones + total_otras_deducciones, DECIMALES) } return data, totals, '' def _validate_otros_pagos(self, headers, row): total_otros_pagos = 0.0 data = [] subsidio_causado = round(row[0], DECIMALES) for i, value in enumerate(row): if not i: continue key = headers[0][i] importe = 0.0 if isinstance(value, float): importe = round(value, DECIMALES) # ~ if not importe: # ~ continue td = SATTipoOtroPago.get_by_key(key) if td is None: continue total_otros_pagos += importe new = { 'tipo_otro_pago': td, 'importe': importe, } if key == '002': new['subsidio_causado'] = subsidio_causado data.append(new) totals = {'total_otros_pagos': total_otros_pagos} return data, totals, '' def _validate_separacion(self, row): if not row[0]: return {}, '' if not row[2] or isinstance(row[2], str): msg = 'El campo Ultimo Sueldo debe ser un importe' return {}, msg if isinstance(row[3], str): msg = 'El campo Ingreso Acumulable debe ser un importe' return {}, msg if isinstance(row[4], str): msg = 'El campo Ingreso No Acumulable debe ser un importe' return {}, msg new = { 'total_pagado': round(row[0], DECIMALES), 'years_servicio': int(row[1]), 'ultimo_sueldo': round(row[2], DECIMALES), 'ingreso_acumulable': round(row[3], DECIMALES), 'ingreso_no_acumulable': round(row[4], DECIMALES), } return new, '' def _validate_horas_extras(self, row): data = [] for i, key in enumerate(row[::4]): days = 0 if isinstance(row[i * 4], float): days = int(row[i * 4]) key = row[i * 4 + 1] the = SATTipoHoras.get_by_key(key) if the is None: continue hours = 0 if isinstance(row[i * 4 + 2], float): hours = int(row[i * 4 + 2]) importe = 0.0 if isinstance(row[i * 4 + 3], float): importe = round(row[i * 4 + 3], DECIMALES) if not hours or not importe: continue new = { 'dias': days, 'tipos_horas': the, 'horas_extra': hours, 'importe_pagado': importe, } data.append(new) return data, '' def _validate_incapacidades(self, row): data = [] for i, key in enumerate(row[::3]): msg = '' key = row[i * 3] ti = SATTipoIncapacidad.get_by_key(key) if ti is None: msg = 'No se encontrón el tipo de incapacidad' break if isinstance(row[i * 3 + 1], str): msg = 'Los dias de incapacidad debe ser un número' break if isinstance(row[i * 3 + 2], str): msg = 'El importe de la incapacidad debe ser un número' break days = int(row[i * 3 + 1]) importe = round(row[i * 3 + 2], DECIMALES) if not days or not importe: break new = { 'dias': days, 'tipo': ti, 'importe': importe, } data.append(new) return data, msg def _validate_exists(self, values): result = (CfdiNomina .select() .where( (CfdiNomina.empleado==values['empleado']) & (CfdiNomina.fecha_pago==values['fecha_pago']) & (CfdiNomina.fecha_inicial_pago==values['fecha_inicial_pago']) & (CfdiNomina.fecha_final_pago==values['fecha_final_pago']) & (CfdiNomina.total==values['total']) & (CfdiNomina.cancelada==False) ) .exists()) return result def _import(self): util.log_file('nomina', kill=True) emisor = Emisor.select()[0] data, msg = util.import_nomina(emisor.rfc) if not data: return {'ok': False, 'msg': msg} hp = data['percepciones'][0] percepciones = data['percepciones'][2:] hd = data['deducciones'][:1] deducciones = data['deducciones'][2:] ho = data['otros_pagos'][:1] otros_pagos = data['otros_pagos'][2:] separacion = data['separacion'][2:] horas_extras = data['horas_extras'][2:] incapacidades = data['incapacidades'][2:] new_titles = data['new_titles'] for i, row in enumerate(data['nomina']): row['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal row['regimen_fiscal'] = emisor.regimenes[0].key row['registro_patronal'] = emisor.registro_patronal new_nomina, msg = self._validate_nomina(self, row) if msg: util.log_file('nomina', msg) continue relacionados = new_nomina.pop('relacionados', '') if relacionados: new_nomina['tipo_relacion'] = '04' new_percepciones, total_percepciones, msg = \ self._validate_percepciones(self, hp, percepciones[i]) if msg: util.log_file('nomina', msg) continue new_deducciones, total_deducciones, msg = \ self._validate_deducciones(self, hd, deducciones[i], new_titles) if msg: util.log_file('nomina', msg) continue new_otros_pagos, total_otros_pagos, msg = \ self._validate_otros_pagos(self, ho, otros_pagos[i]) if msg: util.log_file('nomina', msg) continue new_separacion, msg = self._validate_separacion(self, separacion[i]) if msg: util.log_file('nomina', msg) continue new_horas_extras, msg = self._validate_horas_extras(self, horas_extras[i]) if msg: util.log_file('nomina', msg) continue new_incapacidades, msg = self._validate_incapacidades(self, incapacidades[i]) if msg: util.log_file('nomina', msg) continue totals = total_percepciones.copy() totals.update(total_deducciones) totals.update(total_otros_pagos) totals['subtotal'] = round(totals['total_percepciones'] + totals['total_otros_pagos'], DECIMALES) totals['descuento'] = totals['total_deducciones'] totals['total'] = round(totals['subtotal'] - totals['descuento'], DECIMALES) new_nomina['subtotal'] = totals['subtotal'] new_nomina['descuento'] = totals['descuento'] new_nomina['total'] = totals['total'] new_nomina['total_mn'] = totals['total'] if self._validate_exists(self, new_nomina): info = '{}'.format(new_nomina['empleado'].nombre_completo) msg = 'Nomina existente: {}'.format(info) util.log_file('nomina', msg) continue try: with database_proxy.transaction(): obj = CfdiNomina.create(**new_nomina) for row in new_percepciones: row['cfdi'] = obj CfdiNominaPercepciones.create(**row) for row in new_deducciones: row['cfdi'] = obj CfdiNominaDeducciones.create(**row) for row in new_otros_pagos: row['cfdi'] = obj CfdiNominaOtroPago.create(**row) for row in new_horas_extras: row['cfdi'] = obj CfdiNominaHorasExtra.create(**row) for row in new_incapacidades: row['cfdi'] = obj CfdiNominaIncapacidad.create(**row) for row in relacionados: row['cfdi'] = obj CfdiNominaRelacionados.create(**row) if new_separacion: new_separacion['cfdi'] = obj CfdiNominaSeparacion.create(**new_separacion) concepto = { 'cfdi': obj, 'valor_unitario': totals['subtotal'], 'importe': totals['subtotal'], 'descuento': totals['total_deducciones'], } CfdiNominaDetalle.create(**concepto) totals['cfdi'] = obj CfdiNominaTotales.create(**totals) except Exception as e: msg = 'ERROR: {}-{}'.format(new_nomina['serie'], new_nomina['folio']) util.log_file('nomina', msg) util.log_file('nomina', str(e)) continue msg = 'Nómina importada correctamente' return {'ok': True, 'msg': msg} def _get(self, where=''): if not where: where = ((CfdiNomina.uuid.is_null(True)) & (CfdiNomina.cancelada==False)) rows = (CfdiNomina .select( CfdiNomina.id, CfdiNomina.serie, CfdiNomina.folio, CfdiNomina.uuid, CfdiNomina.fecha, CfdiNomina.estatus, CfdiNomina.fecha_pago, CfdiNomina.total, Empleados.nombre_completo.alias('empleado') ) .where(where) .join(Empleados) .switch(CfdiNomina) .order_by(CfdiNomina.id) .dicts() ) return {'ok': True, 'rows': tuple(rows)} def _validate_rules_nomina(self, data): receptor = data['receptor'] if receptor['TipoContrato'] in ('09', '10', '99'): data['emisor'].pop('RegistroPatronal', None) # ~ msg = 'Recibo: {}-{}'.format(row.serie, row.folio) # ~ util.log_file('nomina', msg) # ~ NOM 154 if data['nomina']['TipoNomina'] == 'E': data['receptor']['PeriodicidadPago'] = '99' # ~ NOM 162 # ~ Si el atributo TipoContrato está entre 01 al 08, el atributo # ~ Nomina.Emisor.RegistroPatronal debe existir. return def _make_xml(self, cfdi): emisor = Emisor.select()[0] empleado = cfdi.empleado certificado = Certificado.get(Certificado.es_fiel==False) totals = CfdiNominaTotales.select().where(CfdiNominaTotales.cfdi==cfdi)[0] comprobante = {} relacionados = {} complementos = None comprobante['Serie'] = cfdi.serie comprobante['Folio'] = str(cfdi.folio) comprobante['Fecha'] = cfdi.fecha.isoformat()[:19] comprobante['FormaPago'] = cfdi.forma_pago comprobante['NoCertificado'] = certificado.serie comprobante['Certificado'] = certificado.cer_txt comprobante['SubTotal'] = FORMAT.format(cfdi.subtotal) comprobante['Moneda'] = cfdi.moneda comprobante['Total'] = FORMAT.format(cfdi.total) comprobante['TipoDeComprobante'] = cfdi.tipo_comprobante comprobante['MetodoPago'] = cfdi.metodo_pago comprobante['LugarExpedicion'] = cfdi.lugar_expedicion if cfdi.descuento: comprobante['Descuento'] = FORMAT.format(cfdi.descuento) if cfdi.tipo_relacion: relacionados = { 'tipo': cfdi.tipo_relacion, 'cfdis': CfdiNominaRelacionados.get_(cfdi), } cfdi_emisor = { 'Rfc': emisor.rfc, 'Nombre': emisor.nombre, 'RegimenFiscal': cfdi.regimen_fiscal, } receptor = { 'Rfc': cfdi.empleado.rfc, 'Nombre': cfdi.empleado.nombre_completo, 'UsoCFDI': cfdi.uso_cfdi, } conceptos = [] rows = CfdiNominaDetalle.select().where(CfdiNominaDetalle.cfdi==cfdi) for row in rows: concepto = { 'ClaveProdServ': row.clave_sat, 'Cantidad': '1', 'ClaveUnidad': row.clave_unidad, 'Descripcion': row.descripcion, 'ValorUnitario': FORMAT.format(row.valor_unitario), 'Importe': FORMAT.format(row.importe), } if row.descuento: concepto['Descuento'] = FORMAT.format(row.descuento) conceptos.append(concepto) nomina = { 'Version': cfdi.version_nomina, 'TipoNomina': cfdi.tipo_nomina.key, 'FechaPago': str(cfdi.fecha_pago), 'FechaInicialPago': str(cfdi.fecha_inicial_pago), 'FechaFinalPago': str(cfdi.fecha_final_pago), 'NumDiasPagados': FORMAT3.format(cfdi.dias_pagados), } if totals.total_percepciones: nomina['TotalPercepciones'] = FORMAT.format(totals.total_percepciones) if totals.total_deducciones: nomina['TotalDeducciones'] = FORMAT.format(totals.total_deducciones) # ~ if totals.total_otros_pagos: nomina['TotalOtrosPagos'] = FORMAT.format(totals.total_otros_pagos) nomina_emisor = {} if emisor.curp: nomina_emisor['Curp'] = emisor.curp if emisor.registro_patronal: nomina_emisor['RegistroPatronal'] = emisor.registro_patronal nomina_receptor = { 'Curp': empleado.curp, 'TipoContrato': empleado.tipo_contrato.key, 'Sindicalizado': {True: 'Si', False: 'No'}.get(empleado.es_sindicalizado), 'TipoJornada': empleado.tipo_jornada.key, 'TipoRegimen': empleado.tipo_regimen.key, 'NumEmpleado': str(empleado.num_empleado), 'PeriodicidadPago': empleado.periodicidad_pago.key, 'ClaveEntFed': empleado.estado.key, } if empleado.riesgo_puesto.key != '99': nomina_receptor['RiesgoPuesto'] = empleado.riesgo_puesto.key if empleado.imss: nomina_receptor['NumSeguridadSocial'] = empleado.imss.replace('-', '') if empleado.fecha_ingreso: nomina_receptor['FechaInicioRelLaboral'] = str(empleado.fecha_ingreso) days = util.get_days(empleado.fecha_ingreso, cfdi.fecha_final_pago) weeks = days // 7 if weeks: ant = 'P{}W'.format(weeks) else: ant = 'P{}D'.format(days) nomina_receptor['Antigüedad'] = ant if empleado.puesto.nombre: if empleado.puesto.departamento.nombre: nomina_receptor['Departamento'] = empleado.puesto.departamento.nombre nomina_receptor['Puesto'] = empleado.puesto.nombre if empleado.clabe: nomina_receptor['CuentaBancaria'] = empleado.clabe.replace('-', '') elif empleado.cuenta_bancaria: nomina_receptor['CuentaBancaria'] = empleado.cuenta_bancaria.replace('-', '') nomina_receptor['Banco'] = empleado.banco.key if empleado.salario_base: nomina_receptor['SalarioBaseCotApor'] = FORMAT.format(empleado.salario_base) if empleado.salario_diario: nomina_receptor['SalarioDiarioIntegrado'] = FORMAT.format(empleado.salario_diario) percepciones = { 'TotalSueldos': FORMAT.format(totals.total_sueldos), 'TotalGravado': FORMAT.format(totals.total_gravado), 'TotalExento': FORMAT.format(totals.total_exento), } if totals.total_separacion: percepciones['TotalSeparacionIndemnizacion'] = FORMAT.format(totals.total_separacion) if totals.total_jubilacion: percepciones['TotalJubilacionPensionRetiro'] = FORMAT.format(totals.total_jubilacion) rows = CfdiNominaPercepciones.select().where( CfdiNominaPercepciones.cfdi==cfdi) details = [] for row in rows: concepto = row.concepto or row.tipo_percepcion.nombre or row.tipo_percepcion.name p = { 'TipoPercepcion': row.tipo_percepcion.key, 'Clave': row.tipo_percepcion.clave or row.tipo_percepcion.key, 'Concepto': concepto[:100], 'ImporteGravado': FORMAT.format(row.importe_gravado), 'ImporteExento': FORMAT.format(row.importe_exento), } details.append(p) percepciones['details'] = details rows = CfdiNominaSeparacion.select().where(CfdiNominaSeparacion.cfdi==cfdi) for row in rows: separacion = { 'TotalPagado': FORMAT.format(row.total_pagado), 'NumAñosServicio': str(row.years_servicio), 'UltimoSueldoMensOrd': FORMAT.format(row.ultimo_sueldo), 'IngresoAcumulable': FORMAT.format(row.ingreso_acumulable), 'IngresoNoAcumulable': FORMAT.format(row.ingreso_no_acumulable), } percepciones['separacion'] = separacion break rows = CfdiNominaHorasExtra.select().where(CfdiNominaHorasExtra.cfdi==cfdi) details = [] for row in rows: n = { 'Dias': str(row.dias), 'TipoHoras': row.tipos_horas.key, 'HorasExtra': str(row.horas_extra), 'ImportePagado': FORMAT.format(row.importe_pagado), } details.append(n) percepciones['hours_extra'] = details deducciones = { 'TotalOtrasDeducciones': FORMAT.format(totals.total_otras_deducciones), } if totals.total_retenciones: deducciones['TotalImpuestosRetenidos'] = \ FORMAT.format(totals.total_retenciones) rows = CfdiNominaDeducciones.select().where(CfdiNominaDeducciones.cfdi==cfdi) details = [] for row in rows: concepto = row.concepto or row.tipo_deduccion.nombre or row.tipo_deduccion.name p = { 'TipoDeduccion': row.tipo_deduccion.key, 'Clave': row.tipo_deduccion.clave or row.tipo_deduccion.key, 'Concepto': concepto[:100], 'Importe': FORMAT.format(row.importe), } details.append(p) deducciones['details'] = details rows = CfdiNominaOtroPago.select().where(CfdiNominaOtroPago.cfdi==cfdi) otros_pagos = [] for row in rows: concepto = row.concepto or row.tipo_otro_pago.nombre or row.tipo_otro_pago.name if nomina_receptor['TipoRegimen'] != '02' and \ row.tipo_otro_pago.key in ('002', '007', '008'): continue p = { 'TipoOtroPago': row.tipo_otro_pago.key, 'Clave': row.tipo_otro_pago.clave or row.tipo_otro_pago.key, 'Concepto': concepto[:100], 'Importe': FORMAT.format(row.importe), } if row.tipo_otro_pago.key == '002': p['subsidio'] = { 'SubsidioCausado': FORMAT.format(row.subsidio_causado) } otros_pagos.append(p) if not otros_pagos: del nomina['TotalOtrosPagos'] rows = CfdiNominaIncapacidad.select().where(CfdiNominaIncapacidad.cfdi==cfdi) incapacidades = [] for row in rows: n = { 'DiasIncapacidad': str(row.dias), 'TipoIncapacidad': row.tipo.key, 'ImporteMonetario': FORMAT.format(row.importe), } incapacidades.append(n) nomina = { 'nomina': nomina, 'emisor': nomina_emisor, 'receptor': nomina_receptor, 'percepciones': percepciones, 'deducciones': deducciones, 'otros_pagos': otros_pagos, 'incapacidades': incapacidades, } self._validate_rules_nomina(self, nomina) data = { 'comprobante': comprobante, 'relacionados': relacionados, 'emisor': cfdi_emisor, 'receptor': receptor, 'conceptos': conceptos, 'complementos': complementos, 'nomina': nomina, 'impuestos': {}, 'donativo': {}, } # ~ return util.make_xml(data, certificado, auth) return utils.make_xml(data, certificado) def _stamp_id(self, id): # ~ auth = Emisor.get_auth() auth = Configuracion.get_({'fields': 'pac_auth'}) obj = CfdiNomina.get(CfdiNomina.id==id) obj.xml = self._make_xml(self, obj) obj.estatus = 'Generado' obj.save() # ~ result = util.timbra_xml(obj.xml, auth) result = utils.xml_stamp(obj.xml, auth) # ~ print (result) if result['ok']: obj.xml = result['xml'] obj.uuid = result['uuid'] obj.fecha_timbrado = result['date'] obj.estatus = 'Timbrado' obj.error = '' obj.save() # ~ cls._sync(cls, id, auth) else: msg = result['error'] obj.estatus = 'Error' obj.error = msg obj.save() return result['ok'], obj.error def _stamp(self): msg = '' where = ((CfdiNomina.uuid.is_null(True)) & (CfdiNomina.cancelada==False)) rows = CfdiNomina.select().where(where).order_by(CfdiNomina.id) util.log_file('nomina', kill=True) if not len(rows): msg = 'Sin recibos por timbrar' return {'ok': True, 'msg_ok': msg} msg_error = '' ok_stamp = 0 for row in rows: msg = 'Timbrando el recibo: {}-{}'.format(row.serie, row.folio) util.log_file('nomina', msg) result, msg = self._stamp_id(self, row.id) if result: msg = 'Recibo: {}-{}, timbrado correctamente'.format(row.serie, row.folio) ok_stamp += 1 util.log_file('nomina', msg) else: msg = 'Error la timbrar: {}-{}, {}'.format(row.serie, row.folio, msg) util.log_file('nomina', msg) msg_error = msg break ok = False if ok_stamp: msg = 'Se timbraron {} recibos'.format(ok_stamp) ok = True error = False if msg_error: error = True return {'ok': ok, 'msg_ok': msg, 'error': error, 'msg_error': msg_error} def _get_by_download(self, filters): emisor = Emisor.select()[0] ids = util.loads(filters['ids']) w = CfdiNomina.id.in_(ids) rows = CfdiNomina.select().where(w) files = {} for row in rows: name = '{}{}_{}'.format(row.serie, row.folio, row.empleado.rfc) files[f'{name}.xml'] = row.xml values = self._get_not_in_xml(self, row, emisor) data = util.get_data_from_xml(row, values) doc = util.to_pdf(data, emisor.rfc) files[f'{name}.pdf'] = doc fz = utils.to_zip(files) return {'data': fz, 'name': name + 'zip'} @classmethod def get_by(cls, values): if not values: return cls._get(cls) if values.get('by', ''): return getattr(cls, f"_get_by_{values['by']}")(cls, values) @classmethod def remove(cls, ids): ids = util.loads(ids) for id in ids: obj = CfdiNomina.get(CfdiNomina.id==id) if obj.uuid: continue with database_proxy.transaction(): q = CfdiNominaDetalle.delete().where(CfdiNominaDetalle.cfdi==obj) q.execute() q = CfdiNominaTotales.delete().where(CfdiNominaTotales.cfdi==obj) q.execute() q = CfdiNominaPercepciones.delete().where(CfdiNominaPercepciones.cfdi==obj) q.execute() q = CfdiNominaDeducciones.delete().where(CfdiNominaDeducciones.cfdi==obj) q.execute() q = CfdiNominaOtroPago.delete().where(CfdiNominaOtroPago.cfdi==obj) q.execute() q = CfdiNominaSeparacion.delete().where(CfdiNominaSeparacion.cfdi==obj) q.execute() q = CfdiNominaHorasExtra.delete().where(CfdiNominaHorasExtra.cfdi==obj) q.execute() q = CfdiNominaIncapacidad.delete().where(CfdiNominaIncapacidad.cfdi==obj) q.execute() obj.delete_instance() return True @classmethod def get_xml(cls, id): obj = CfdiNomina.get(CfdiNomina.id==id) name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.empleado.rfc) # ~ cls._sync_xml(cls, obj) return obj.xml, name def _get_not_in_xml(self, invoice, emisor): values = {'is_nomina': True, 'el.version': VERSION_EMPRESA_LIBRE} if invoice.version == '3.2': return values obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal) values['regimenfiscal'] = str(obj) obj = SATFormaPago.get(SATFormaPago.key==invoice.forma_pago) values['formadepago'] = '{} ({})'.format(obj.name, obj.key) obj = SATPeriodicidadPago.get(SATPeriodicidadPago.id==invoice.empleado.periodicidad_pago) values['periodicidaddepago'] = '{} ({})'.format(obj.name, obj.key) values['tiporelacion'] = invoice.tipo_relacion values['usocfdi'] = invoice.uso_cfdi values['receptor'] = {} values['fechadof'] = None values['version'] = invoice.version_nomina return values @classmethod def get_pdf(cls, id, rfc, sync=True): try: emisor = Emisor.select()[0] except IndexError: return b'', 'sin_datos_de_emisor.pdf' obj = CfdiNomina.get(CfdiNomina.id==id) name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.empleado.rfc) if obj.uuid is None: return b'', name values = cls._get_not_in_xml(cls, obj, emisor) data = util.get_data_from_xml(obj, values) doc = util.to_pdf(data, emisor.rfc) # ~ if sync: # ~ target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/') # ~ cls._sync_pdf(cls, doc, name, target) return doc, name @classmethod def filter_years(cls): data = [{'id': -1, 'value': 'Todos'}] rows = (CfdiNomina .select(CfdiNomina.fecha.year.alias('year')) .group_by(CfdiNomina.fecha.year) .order_by(CfdiNomina.fecha.year) ) if not rows is None: data += [{'id': int(r.year), 'value': int(r.year)} for r in rows] return tuple(data) class CfdiNominaDetalle(BaseModel): cfdi = ForeignKeyField(CfdiNomina) clave_sat = TextField(default=DEFAULT_SAT_NOMINA['CLAVE']) cantidad = DecimalField(default=1.0, max_digits=18, decimal_places=6, auto_round=True) clave_unidad = TextField(default=DEFAULT_SAT_NOMINA['UNIDAD']) descripcion = TextField(default=DEFAULT_SAT_NOMINA['DESCRIPCION']) valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) class Meta: order_by = ('cfdi',) class CfdiNominaTotales(BaseModel): cfdi = ForeignKeyField(CfdiNomina) subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_percepciones = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_gravado = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_exento = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_deducciones = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_otros_pagos = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_sueldos = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_separacion = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_jubilacion = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_retenciones = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_otras_deducciones = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class CfdiNominaJubilacion(BaseModel): cfdi = ForeignKeyField(CfdiNomina) total_una_exhibicion = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_parcialidad = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) monto_diario = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) ingreso_acumulable = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) ingreso_no_acumulable = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class CfdiNominaSeparacion(BaseModel): cfdi = ForeignKeyField(CfdiNomina) total_pagado = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) years_servicio = IntegerField(default=0) ultimo_sueldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) ingreso_acumulable = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) ingreso_no_acumulable = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class CfdiNominaPercepciones(BaseModel): cfdi = ForeignKeyField(CfdiNomina) tipo_percepcion = ForeignKeyField(SATTipoPercepcion) concepto = TextField(default='') importe_gravado = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) importe_exento = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) valor_mercado = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) precio_al_ortorgarse = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class CfdiNominaDeducciones(BaseModel): cfdi = ForeignKeyField(CfdiNomina) tipo_deduccion = ForeignKeyField(SATTipoDeduccion) concepto = TextField(default='') importe = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class CfdiNominaOtroPago(BaseModel): cfdi = ForeignKeyField(CfdiNomina) tipo_otro_pago = ForeignKeyField(SATTipoOtroPago) concepto = TextField(default='') importe = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) subsidio_causado = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) saldo_a_favor = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) year = IntegerField(default=0) remanente_saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class CfdiNominaIncapacidad(BaseModel): cfdi = ForeignKeyField(CfdiNomina) dias = IntegerField(default=0) tipo = ForeignKeyField(SATTipoIncapacidad) importe = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class CfdiNominaHorasExtra(BaseModel): cfdi = ForeignKeyField(CfdiNomina) dias = IntegerField(default=0) tipos_horas = ForeignKeyField(SATTipoHoras) horas_extra = IntegerField(default=0) importe_pagado = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class CfdiNominaSubcontratos(BaseModel): cfdi = ForeignKeyField(CfdiNomina) rfc = TextField(default='') porcentaje = DecimalField(default=0.0, max_digits=12, decimal_places=2, auto_round=True) class Meta: order_by = ('cfdi',) class CfdiNominaOtros(BaseModel): cfdi = ForeignKeyField(CfdiNomina) node = TextField(default='') key = TextField(default='') value = TextField(default='') class CfdiNominaRelacionados(BaseModel): cfdi = ForeignKeyField(CfdiNomina, related_name='original') cfdi_origen = ForeignKeyField(CfdiNomina, related_name='relacion') class Meta: order_by = ('cfdi',) @classmethod def get_(cls, cfdi): query = (CfdiNominaRelacionados .select() .where(CfdiNominaRelacionados.cfdi==cfdi) ) return [str(r.cfdi_origen.uuid) for r in query] class PartnerProducts(BaseModel): partner = ForeignKeyField(Socios) key = TextField(default='') product = ForeignKeyField(Productos) class Meta: order_by = ('partner', 'key') indexes = ( (('partner', 'key', 'product'), True), ) def _get_product(self, filters): partner = utils.loads(filters['partner']) id = int(partner['id']) rfc = partner['rfc'] key = filters['product_key'] where = (Socios.rfc == rfc) if id: where = (Socios.id == id) where = (where & (PartnerProducts.key == key)) select = ( Productos.id.alias('id_product'), Productos.clave_sat.alias('key_sat1'), Productos.descripcion.alias('description1'), Productos.valor_unitario.alias('unit_value1'), ) try: obj = (PartnerProducts .select(*select) .join(Socios).switch(PartnerProducts) .join(Productos).switch(PartnerProducts) .where(where) .dicts()[0] ) except IndexError: msg = 'No se encontró un producto existente' data = {'ok': False, 'msg': msg} return data data = {'ok': True, 'row': obj} return data @classmethod def get_data(cls, filters): method = f"_get_{filters['opt']}" return getattr(cls, method)(cls, filters) class PartnerInvoices(BaseModel): partner = ForeignKeyField(Socios) serie = TextField(default='') folio = TextField(default='') cfdi_date = DateTimeField() cfdi_stamp = DateTimeField() pay_way = TextField(default='') pay_conditions = TextField(default='') pay_method = TextField(default='') subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) discount = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) currency = TextField(default='MXN') type_change = DecimalField(default=1.0, max_digits=15, decimal_places=6, auto_round=True) total = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) type_cfdi = TextField(default='') cfdi_use = TextField(default='') total_retenciones = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) total_trasladados = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) xml = TextField(default='') uuid = UUIDField() status = TextField(default='Guardada') status_sat = TextField(default='Vigente') notes = TextField(default='') balance = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) pagada = BooleanField(default=False) cancelada = BooleanField(default=False) date_cancel = DateTimeField(null=True) error = TextField(default='') class Meta: order_by = ('-cfdi_date',) class InventoryEntries(BaseModel): storage = ForeignKeyField(Almacenes, null=True) invoice = ForeignKeyField(PartnerInvoices, null=True) product = ForeignKeyField(Productos) date_add = DateTimeField(default=utils.now) cant = DecimalField(default=0.0, max_digits=18, decimal_places=2, auto_round=True) class Meta: order_by = ('-date_add',) indexes = ( (('storage', 'product', 'date_add', 'cant'), True), ) @classmethod def get_data(cls, filters): method = f"_get_{filters['opt']}" return getattr(cls, method)(cls, filters) def _get_or_create_partner(self, args): obj = None msg = '' id = int(args['id']) type_person = 1 if len(args['rfc']) == 12: type_person = 2 if args['rfc'] == RFCS['FOREIGN']: type_person = 4 fields = dict( tipo_persona = type_person, rfc = args['rfc'], nombre = args['nombre'], slug = utils.to_slug(args['nombre']), es_proveedor = True, ) try: if id: obj = Socios.get(Socios.id==id) else: obj = Socios.create(**fields) except IntegrityError as e: msg = 'Ocurrio un error, al dar de alta el emisor' return obj, msg def _get_or_create_product(self, args, cant): # ~ almacen = ForeignKeyField(Almacenes, null=True) id = int(args.get('id_product', '0')) if id: obj = Productos.get(Productos.id==id) else: category = Categorias._get_by_name('Productos') next_key = Productos.next_key()['value'] unit = SATUnidades.get_by_name(args['unit']) fields = dict( categoria = category, clave = next_key, clave_sat = args['key_sat'], descripcion = args['description'], unidad = unit, valor_unitario = Decimal(args['unit_value']), inventario = True, ) obj = Productos.create(**fields) where = (SATImpuestos.activo==True & SATImpuestos.default==True) taxes = SATImpuestos.select().where(where) obj.impuestos = taxes obj.save() res = (Productos .update(existencia=Productos.existencia + cant) .where(Productos.id==obj.id).execute() ) return obj def _add_entries(self, products, partner): msg = '' for p in products: cant = float(p['cant']) product = self._get_or_create_product(self, p, cant) values = dict( product = product, cant = cant, ) InventoryEntries.create(**values) values = dict( partner = partner, key = p['key'], product = product, ) PartnerProducts.get_or_create(**values) return msg def _create(self, args, user): # ~ print('ARGS', args) if not user.es_admin: msg = 'Solo un administrador puede usar esta función.' result = {'ok': False, 'msg': msg} return result partner, error = self._get_or_create_partner(self, args['partner']) if error: result = {'ok': False, 'msg': error} return result # ~ ToDo - Save invoice error = self._add_entries(self, args['products'], partner) if error: result = {'ok': False, 'msg': error} return result result = {'ok': True, 'msg': ''} return result def _create_manual(self, args, user): if not user.es_admin: msg = 'Solo un administrador puede usar esta función.' result = {'ok': False, 'msg': msg} return result product = Productos.get(Productos.id==args['product_id']) if not product.inventario: msg = 'Este producto no lleva inventario.' result = {'ok': False, 'msg': msg} return result storage = None if args['warehouse']: storage = args['warehouse'] cant = Decimal(args['cant']) values = dict( storage = storage, product = product, cant = cant, ) obj = InventoryEntries.create(**values) msg = 'a {}'.format(obj.id) _save_log(user.usuario, msg, 'IE') WareHouseProduct.update_exists(values) new_cant = product.existencia + cant q = (Productos .update(existencia=Productos.existencia + cant) .where(Productos.id==product.id) ) q.execute() result = {'ok': True, 'row': {'existencia': new_cant}} return result def _warehouse_movement(self, args, user): if not user.es_admin: msg = 'Solo un administrador puede usar esta función.' result = {'ok': False, 'msg': msg} return result product = Productos.get(Productos.id==args['id_product']) if not product.inventario: msg = 'Este producto no lleva inventario.' result = {'ok': False, 'msg': msg} return result storage = args['target'] cant = Decimal(args['cant']) values = dict( storage = storage, product = product, cant = cant, ) obj = InventoryEntries.create(**values) msg = 'a {}'.format(obj.id) _save_log(user.usuario, msg, 'IE') WareHouseProduct.update_exists(values) values['storage'] = args['source'] values['cant'] *= -1 obj = InventoryEntries.create(**values) msg = 'a {}'.format(obj.id) _save_log(user.usuario, msg, 'IE') WareHouseProduct.update_exists(values) result = {'ok': True} return result @classmethod def post(cls, values, user=None): opt = values['opt'] args = utils.loads(values['values']) return getattr(cls, f'_{opt}')(cls, args, user) @util.run_in_thread def _save_log(user, action, table): data = {'usuario': user, 'accion': action, 'tabla': table} Registro.add(data) return @util.run_in_thread def _send_notify_access(args): admins = (Usuarios .select(Usuarios.correo) .where(Usuarios.es_admin==True) .scalar(as_tuple=True)) if not admins: return config = Configuracion.get_({'fields': 'correo'}) contra = Configuracion.get_('correo_contra') if not config: return user = args['usuario'] rfc = args['rfc'] ip = args['ip'] url = f"http://ip-api.com/line/{ip}?fields=city" city = utils.get_url(url) message = f"Desde la IP: {ip} en: {city}" server = { 'server': config['correo_servidor'], 'port': config['correo_puerto'], 'ssl': utils.to_bool(config['correo_ssl']), 'starttls': utils.to_bool(config['correo_starttls']), 'user': config['correo_usuario'], 'pass': utils.decrypt(contra, rfc), } mail = { 'to': ','.join(admins), 'subject': f"Usuario {user} identificado", 'message': message, } data= { 'server': server, 'mail': mail, } result = utils.send_mail(data) return def authenticate(args): respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''} values = util.get_con(args['rfc']) if not values: return respuesta, None conectar(values) try: obj = Usuarios.get(usuario=args['usuario'], es_activo=True) except Usuarios.DoesNotExist: return respuesta, None except ProgrammingError as e: log.error(e) return respuesta, None if not obj.contraseña.check_password(args['contra']): return respuesta, None obj.ultimo_ingreso = util.now() obj.save() respuesta['msg'] = '' respuesta['login'] = True respuesta['user'] = str(obj) respuesta['super'] = obj.es_superusuario notify_access = Configuracion.get_bool('chk_users_notify_access') if notify_access: _send_notify_access(args) return respuesta, obj def get_cp(cp): con = sqlite3.connect(PATH_CP) cursor = con.cursor() sql = """ SELECT colonia, municipio, estado, municipios.id_municipio FROM colonias, municipios, estados WHERE colonias.id_municipio=municipios.id AND municipios.id_estado=estados.id AND cp=? ORDER BY colonia""" cursor.execute(sql, (cp,)) rows = cursor.fetchall() cursor.close() con.close() data = {} if rows: data = { 'estado': rows[0][2], 'municipio': rows[0][1], 'key_municipio': str(rows[0][3]).zfill(3), } if len(rows) == 1: data['colonia'] = rows[0][0] else: data['colonia'] = [r[0] for r in rows] return data def get_sat_key(key): return util.get_sat_key('productos', key) def get_sat_monedas(key): return util.get_sat_monedas(key) def get_sat_unidades(key): return util.get_sat_unidades(key) def get_sat_unidadespeso(key): return util.get_sat_unidadespeso(key) def get_sat_productos(key): return util.get_sat_productos(key) def get_title_app(by=1): html = { 1: '{}', 2: 'Bienvenido a {}', 3: '{}', } return html[by].format(TITLE_APP) def test_correo(values): server = { 'servidor': values['correo_servidor'], 'puerto': values['correo_puerto'], 'ssl': util.get_bool(values['correo_ssl']), 'starttls': util.get_bool(values['correo_starttls']), 'usuario': values['correo_usuario'], 'contra': values['correo_contra'], } ccp = values.get('correo_copia', '') options = { 'para': values['correo_usuario'], 'copia': ccp, 'confirmar': util.get_bool(values['correo_confirmacion']), 'asunto': values['correo_asunto'], 'mensaje': values['correo_mensaje'].replace('\n', '
'), 'files': [], } data= { 'server': server, 'options': options, } return util.send_mail(data) def _init_values(rfc): data = ( {'clave': 'version', 'valor': VERSION}, {'clave': 'migracion', 'valor': '0'}, {'clave': 'rfc_publico', 'valor': RFCS['PUBLIC']}, {'clave': 'rfc_extranjero', 'valor': 'XEXX010101000'}, {'clave': 'decimales', 'valor': '2'}, {'clave': 'path_key', 'valor': ''}, {'clave': 'path_cer', 'valor': ''}, ) for row in data: try: with database_proxy.atomic() as txn: Configuracion.create(**row) except IntegrityError: pass if not Certificado.select().count(): Certificado.create(rfc=rfc.upper()) log.info('Valores iniciales insertados...') return def _crear_tablas(rfc): tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion, Folios, Registro, CamposPersonalizados, Permisos, Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos, FacturasRelacionadas, FacturasComplementos, FacturasPersonalizados, SeriesProductos, Almacenes, Productos, RangosPrecios, Sucursales, PreFacturas, PreFacturasDetalle, PreFacturasImpuestos, PreFacturasRelacionadas, Tickets, TicketsDetalle, TicketsImpuestos, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos, SATNivelesEducativos, SATEstados, SATRiesgoPuesto, SATPeriodicidadPago, SATOrigenRecurso, SATTipoContrato, SATTipoDeduccion, SATTipoHoras, SATTipoIncapacidad, SATTipoJornada, SATTipoNomina, SATTipoOtroPago, SATTipoPercepcion, SATTipoRegimen, SATLeyendasFiscales, Socios, SociosCuentasBanco, Contactos, ContactoCorreos, ContactoDirecciones, Empleados, ContactoTelefonos, Departamentos, Puestos, Tags, Roles, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco, TipoCorreo, TipoDireccion, TipoPariente, TipoResponsable, TipoTelefono, TipoTitulo, TipoMovimientoAlumno, TipoMovimientoAlmacen, CfdiPagos, NivelesEducativos, Alumnos, AlumnosParientes, Grupos, ParienteDirecciones, ParienteTelefonos, ParienteCorreos, CfdiNomina, CfdiNominaDeducciones, CfdiNominaDetalle, CfdiNominaHorasExtra, CfdiNominaIncapacidad, CfdiNominaJubilacion, CfdiNominaOtroPago, CfdiNominaOtros, CfdiNominaPercepciones, CfdiNominaRelacionados, CfdiNominaSeparacion, CfdiNominaSubcontratos, CfdiNominaTotales, Emisor.regimenes.get_through_model(), Socios.tags.get_through_model(), Productos.impuestos.get_through_model(), Productos.tags.get_through_model(), PartnerProducts, InventoryEntries, PartnerInvoices, WareHouseProduct, SATUnidadesPeso, SociosRegimenes, ] log.info('Creando tablas...') database_proxy.create_tables(tablas, True) log.info('Tablas creadas correctamente...') usuarios = ( {'usuario': 'superadmin', 'contraseña': DEFAULT_PASSWORD, 'es_superusuario': True}, {'usuario': 'admin', 'contraseña': DEFAULT_PASSWORD, 'es_admin': True}, ) for usuario in usuarios: try: with database_proxy.atomic() as txn: Usuarios.create(**usuario) log.info('Usuario creado correctamente...') except IntegrityError: log.info('Usuario ya existe...') _init_values(rfc) _importar_valores('', rfc) return True def _migrate_tables(rfc=''): from playhouse.migrate import PostgresqlMigrator, migrate if rfc: rfc = rfc.strip().upper() else: rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) tablas = [Sucursales, SATEstados, SATRiesgoPuesto, SATPeriodicidadPago, SATOrigenRecurso, SATTipoContrato, SATTipoDeduccion, SATTipoHoras, SATTipoIncapacidad, SATTipoJornada, SATTipoNomina, SATTipoOtroPago, SATTipoPercepcion, SATTipoRegimen, Departamentos, Puestos, Empleados, CfdiNomina, CfdiNominaDeducciones, CfdiNominaDetalle, CfdiNominaHorasExtra, CfdiNominaIncapacidad, CfdiNominaJubilacion, CfdiNominaOtroPago, CfdiNominaOtros, CfdiNominaPercepciones, CfdiNominaRelacionados, CfdiNominaSeparacion, CfdiNominaSubcontratos, CfdiNominaTotales, SATNivelesEducativos, Roles, Permisos, SociosCuentasBanco, SATLeyendasFiscales, PartnerProducts, InventoryEntries, PartnerInvoices, WareHouseProduct, SATUnidadesPeso, SociosRegimenes, ] log.info('Creando tablas nuevas...') database_proxy.create_tables(tablas, True) log.info('Tablas creadas correctamente...') log.info('Iniciando migración de tablas...') migrations = [] migrator = PostgresqlMigrator(database_proxy) columns = [c.name for c in database_proxy.get_columns('usuarios')] if not 'sucursal_id' in columns: sucursal = ForeignKeyField(Sucursales, null=True, to_field=Sucursales.id) migrations.append( migrator.add_column('usuarios', 'sucursal_id', sucursal)) if not 'rol_id' in columns: rol = ForeignKeyField(Roles, null=True, to_field=Roles.id) migrations.append( migrator.add_column('usuarios', 'rol_id', rol)) columns = [c.name for c in database_proxy.get_columns('emisor')] if not 'registro_patronal' in columns: registro_patronal = TextField(default='') migrations.append( migrator.add_column( 'emisor', 'registro_patronal', registro_patronal)) if not 'curp' in columns: curp = TextField(default='') migrations.append( migrator.add_column('emisor', 'curp', curp)) columns = [c.name for c in database_proxy.get_columns('socios')] if not 'id_fiscal' in columns: id_fiscal = TextField(default='') migrations.append( migrator.add_column('socios', 'id_fiscal', id_fiscal)) if not 'plantilla' in columns: plantilla = TextField(default='') migrations.append( migrator.add_column('socios', 'plantilla', plantilla)) if not 'correo_facturasp' in columns: correo_facturasp = TextField(default='') migrations.append( migrator.add_column('socios', 'correo_facturasp', correo_facturasp)) if not 'regimen_fiscal' in columns: regimen_fiscal = TextField(default='') migrations.append( migrator.add_column('socios', 'regimen_fiscal', regimen_fiscal)) columns = [c.name for c in database_proxy.get_columns('folios')] if not 'plantilla' in columns: plantilla = TextField(default='') migrations.append( migrator.add_column('folios', 'plantilla', plantilla)) columns = [c.name for c in database_proxy.get_columns('prefacturasdetalle')] if not 'unidad' in columns: unidad = TextField(default='') migrations.append( migrator.add_column('prefacturasdetalle', 'unidad', unidad)) columns = [c.name for c in database_proxy.get_columns('facturaspagos')] if 'cancelado' in columns: migrations.append(migrator.drop_column('facturaspagos', 'cancelado')) if not 'tipo_cambio' in columns: tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6, auto_round=True) migrations.append(migrator.add_column('facturaspagos', 'tipo_cambio', tipo_cambio)) columns = [c.name for c in database_proxy.get_columns('certificado')] if not 'es_fiel' in columns: es_fiel = BooleanField(default=False) migrations.append(migrator.add_column('certificado', 'es_fiel', es_fiel)) columns = [c.name for c in database_proxy.get_columns('cfdipagos')] if not 'serie' in columns: socio = ForeignKeyField(Socios, null=True, to_field=Socios.id) serie = TextField(default='') folio = IntegerField(default=0) lugar_expedicion = TextField(default='') regimen_fiscal = TextField(default='') tipo_comprobante = TextField(default=DEFAULT_CFDIPAY['TYPE']) error = TextField(default='') tipo_relacion = TextField(default='') uuid_relacionado = UUIDField(null=True) cancelada = BooleanField(default=False) migrations.append(migrator.add_column('cfdipagos', 'serie', serie)) migrations.append(migrator.add_column('cfdipagos', 'folio', folio)) migrations.append(migrator.add_column('cfdipagos', 'lugar_expedicion', lugar_expedicion)) migrations.append(migrator.add_column('cfdipagos', 'regimen_fiscal', regimen_fiscal)) migrations.append(migrator.add_column('cfdipagos', 'tipo_comprobante', tipo_comprobante)) migrations.append(migrator.add_column('cfdipagos', 'error', error)) migrations.append(migrator.add_column('cfdipagos', 'tipo_relacion', tipo_relacion)) migrations.append(migrator.add_column('cfdipagos', 'uuid_relacionado', uuid_relacionado)) migrations.append(migrator.add_column('cfdipagos', 'socio_id', socio)) migrations.append(migrator.drop_column('cfdipagos', 'cancelado')) migrations.append(migrator.add_column('cfdipagos', 'cancelada', cancelada)) if not 'receptor_regimen' in columns: receptor_regimen = TextField(default='') migrations.append(migrator.add_column('cfdipagos', 'receptor_regimen', receptor_regimen)) if not 'fecha_cancelacion' in columns: fecha_cancelacion = DateTimeField(null=True) migrations.append(migrator.add_column('cfdipagos', 'fecha_cancelacion', fecha_cancelacion)) table = 'movimientosbanco' columns = [c.name for c in database_proxy.get_columns(table)] if not 'cuenta_socio_id' in columns: cuenta_socio = ForeignKeyField(SociosCuentasBanco, null=True, to_field=SociosCuentasBanco.id) migrations.append(migrator.add_column(table, 'cuenta_socio_id', cuenta_socio)) migrations.append(migrator.drop_column(table, 'origen_rfc')) migrations.append(migrator.drop_column(table, 'origen_nombre')) migrations.append(migrator.drop_column(table, 'origen_cuenta')) migrations.append(migrator.drop_column(table, 'destino_rfc')) migrations.append(migrator.drop_column(table, 'destino_cuenta')) table = 'socioscuentasbanco' columns = [c.name for c in database_proxy.get_columns(table)] if not 'activa' in columns: activa = BooleanField(default=True) migrations.append(migrator.add_column(table, 'activa', activa)) table = 'productos' columns = [c.name for c in database_proxy.get_columns(table)] if not 'cantidad_empaque' in columns: cantidad_empaque = DecimalField(default=0.0, max_digits=14, decimal_places=4, auto_round=True) migrations.append(migrator.add_column( table, 'cantidad_empaque', cantidad_empaque)) if not 'is_discontinued' in columns: is_discontinued = BooleanField(default=False) migrations.append(migrator.add_column( table, 'is_discontinued', is_discontinued)) if not 'objeto_impuesto' in columns: objeto_impuesto = TextField(default='02') migrations.append(migrator.add_column(table, 'objeto_impuesto', objeto_impuesto)) if 'almacen_id' in columns: migrations.append(migrator.drop_column(table, 'almacen_id')) table = 'facturasdetalle' columns = [c.name for c in database_proxy.get_columns(table)] if not 'empaques' in columns: empaques = DecimalField(default=0.0, max_digits=14, decimal_places=4, auto_round=True) migrations.append(migrator.add_column( table, 'empaques', empaques)) table = 'facturas' columns = [c.name for c in database_proxy.get_columns(table)] if not 'divisas' in columns: divisas = TextField(default='') migrations.append(migrator.add_column(table, 'divisas', divisas)) if not 'exportacion' in columns: new_field = TextField(default='01') migrations.append(migrator.add_column(table, 'exportacion', new_field)) if not 'receptor_regimen' in columns: receptor_regimen = TextField(default='') migrations.append(migrator.add_column(table, 'receptor_regimen', receptor_regimen)) if not 'periodicidad' in columns: periodicidad = TextField(default='') migrations.append(migrator.add_column(table, 'periodicidad', periodicidad)) table = 'almacenes' columns = [c.name for c in database_proxy.get_columns(table)] if 'nombre' in columns: name = TextField(unique=True, default='') migrations.append(migrator.drop_column(table, 'nombre')) migrations.append(migrator.add_column(table, 'name', name)) table = 'sucursales' columns = [c.name for c in database_proxy.get_columns(table)] if not 'warehouse_id' in columns: warehouse = ForeignKeyField(Almacenes, null=True, to_field=Almacenes.id) migrations.append(migrator.add_column(table, 'warehouse_id', warehouse)) table = 'facturasdetalle' columns = [c.name for c in database_proxy.get_columns(table)] if not 'warehouse_id' in columns: warehouse = ForeignKeyField(Almacenes, null=True, to_field=Almacenes.id) migrations.append(migrator.add_column(table, 'warehouse_id', warehouse)) table = 'ticketsdetalle' field = 'warehouse_id' columns = [c.name for c in database_proxy.get_columns(table)] if not field in columns: warehouse = ForeignKeyField(Almacenes, null=True, to_field=Almacenes.id) migrations.append(migrator.add_column(table, field, warehouse)) if migrations: with database_proxy.atomic() as txn: migrate(*migrations) Configuracion.add({'version': VERSION}) log.info('Tablas migradas correctamente...') _importar_valores('', rfc) log.info('Actualizando valores...') try: q = SATRegimenes.update(**{'activo': True}).where(SATRegimenes.key=='616') q.execute() except Exception as e: log.error(e) else: log.info('Valores actualizados...') return def _add_emisor(rfc, args): util._valid_db_companies() con = sqlite3.connect(COMPANIES) cursor = con.cursor() sql = """ INSERT INTO names VALUES (?, ?)""" try: cursor.execute(sql, (rfc, args)) except sqlite3.IntegrityError as e: log.error(e) return False con.commit() cursor.close() con.close() return True def _delete_emisor(rfc): util._valid_db_companies() con = sqlite3.connect(COMPANIES) cursor = con.cursor() sql = """ DELETE FROM names WHERE rfc = ?""" try: cursor.execute(sql, (rfc,)) except Exception as e: log.error(e) return False con.commit() cursor.close() con.close() return True def _iniciar_bd(): rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) if _crear_tablas(rfc): return log.error('No se pudieron crear las tablas') return def _borrar_rfc(): rfc = input('Introduce el RFC a borrar: ').strip().lower() if not rfc: msg = 'El RFC es requerido' log.error(msg) return confirm = input('¿Estás seguro de borrar el RFC? [si]') if confirm != 'si': log.info('Proceso cancelado...') return if _delete_emisor(rfc.upper()): utils.db_delete(rfc, PATHS['BK']) log.info('RFC borrado correctamente...') return log.error('No se pudo borrar el RFC') return def _listar_rfc(detalle): data = util.get_rfcs() for row in data: if detalle: msg = 'RFC: {}\n\t{}'.format(row[0], row[1]) else: msg = 'RFC: {}'.format(row[0]) log.info(msg) return def get_empresas(): data = util.get_rfcs() rows = [] for row in data: rows.append({'delete': '-', 'rfc': row[0]}) return tuple(rows) def empresa_agregar(rfc, no_bd): rfc = rfc.upper() if util.get_con(rfc): msg = 'El RFC ya esta dado de alta' return {'ok': False, 'msg': msg} user = rfc.replace('&', '').lower() if not no_bd: if not util.crear_rol(user): msg = 'No se pudo crear el usuario, es probable que ya exista' return {'ok': False, 'msg': msg} if not util.crear_db(user): msg = 'No se pudo crear la base de datos' return {'ok': False, 'msg': msg} args = { "type": "postgres", "name": user, "user": user, "password": user, } if not no_bd: if not conectar(args.copy()): msg = 'No se pudo conectar a la base de datos' return {'ok': False, 'msg': msg} if not _add_emisor(rfc, util.dumps(args)): msg = 'No se pudo guardar el nuevo emisor' return {'ok': False, 'msg': msg} if not no_bd: if not _crear_tablas(rfc): msg = 'No se pudo crear las tablas' return {'ok': False, 'msg': msg} msg = 'Emisor dado de alta correctamente' row = {'delete': '-', 'rfc': rfc} return {'ok': True, 'msg': msg, 'row': row} def empresa_borrar(rfc): utils.db_delete(rfc.lower(), PATHS['BK']) return True def respaldar_dbs(): if not MV: msg = 'Solo MV' return {'ok': False, 'msg': msg} result = utils.db_backup_local() return result def _importar_valores(archivo='', rfc=''): if not rfc: rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) if not archivo: archivo = INIT_VALUES log.info('Importando datos...') regimen = '' rows = util.loads(open(archivo, 'r').read()) for row in rows: log.info('\tImportando tabla: {}'.format(row['tabla'])) table = globals()[row['tabla']] for r in row['datos']: try: with database_proxy.atomic() as txn: table.create(**r) except: pass log.info('Importación terminada...') return def _import_from_folder(path): files = util.get_files(path, 'json') if not files: msg = 'No se encontraron archivos para importar' log.error(msg) return rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) log.info('Importando valores...') for p in files: msg = '\tImportando tabla: {}' data = util.import_json(p) log.info(msg.format(data['tabla'])) table = globals()[data['tabla']] for r in data['datos']: try: with database_proxy.atomic() as txn: table.create(**r) except IntegrityError: pass log.info('Valores importados...') return def _export_documents(): rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) log.info('Exportar documentos...') n = util.now() year = input('Introduce el año [{}]: '.format(n.year)).strip() if not year: year = str(n.year) month = input('Introduce el mes [{}]: '.format(n.month)).strip() if not month: month = str(n.month) without_stamp = [] with_error = [] filters = { 'year': year, 'month': month, } result = Facturas.get_(filters) if result['ok']: t = len(result['rows']) for i, row in enumerate(result['rows']): msg = 'Extrayendo factura {} de {}: {}-{}'.format( i+1, t, row['serie'], row['folio']) log.info(msg) _, name = Facturas.get_xml(row['id']) if not row['uuid']: without_stamp.append(f"{row['serie']}-{row['folio']}") msg = '\tXML extraido...' log.info(msg) name = name[:-3] + 'pdf' path = '/home/mau/facturas/{}/{}/{}/{}'.format( rfc, year, month.zfill(2), name) if util.exists(path): continue try: Facturas.get_pdf(row['id'], rfc, True) except Exception as e: with_error.append(f"{row['serie']}-{row['folio']}") print(e) msg = '\tPDF generado...' log.info(msg) log.info('Documentos exportados...') log.info('Sin sellar...') for i in without_stamp: log.info(i) log.info('Con error...') for i in with_error: log.info(i) return # ~ v2 def companies_get(): data = utils.rfc_get() rows = [] for row in data: rows.append({'delete': '-', 'rfc': row[0].upper()}) return tuple(rows) def _list_clients(): rows = utils.rfc_get() for row in rows: msg = f'RFC: {row[0].upper()}' print(msg) return def _new_client(rfc, no_database): rfc = rfc.lower() if not rfc: log.error('Falta el RFC') return if utils.rfc_exists(rfc): msg = 'El RFC ya esta dado de alta' log.error(msg) return {'ok': False, 'msg': msg} user = rfc.replace('&', '').lower() if not no_database: if not utils.db_create(user): msg = 'No se pudo crear la base de datos' log.error(msg) return {'ok': False, 'msg': msg} args = { 'type': 'postgres', 'name': user, 'user': user, 'password': user, } if not conectar(args.copy()): msg = 'No se pudo conectar a la base de datos' log.error(msg) return {'ok': False, 'msg': msg} if not utils.rfc_add(rfc, args): msg = 'No se pudo guardar el nuevo emisor' log.error(msg) return {'ok': False, 'msg': msg} if not no_database: if not _crear_tablas(rfc): msg = 'No se pudo crear las tablas' log.error(msg) return {'ok': False, 'msg': msg} desconectar() msg = 'Emisor dado de alta correctamente' row = {'delete': '-', 'rfc': rfc.upper()} result = {'ok': True, 'msg': msg, 'row': row} return result def _delete_client(rfc, no_database, ask=True): rfc = rfc.lower() if not rfc: log.error('Falta el RFC') return if not utils.rfc_exists(rfc): msg = 'El RFC no esta dado de alta' log.error(msg) return {'ok': False, 'msg': msg} if ask: confirm = input('¿Estás seguro de borrar el RFC? [si]') if confirm != 'si': log.info('Proceso cancelado...') return False if utils.db_delete(rfc, PATHS['BK'], no_database): log.info('RFC borrado correctamente...') result = True else: log.error('No se pudo borrar el RFC') result = False return result def _update_sat(): clients = utils.rfc_get() tables = utils.json_loads(PATHS['SAT']) for rfc, con in clients: log.info(f'Importando datos en: {rfc.upper()}') conectar(utils.loads(con)) for table in tables: t = globals()[table['tabla']] with database_proxy.atomic(): for r in table['datos']: try: t.get_or_create(**r) except: pass log.info(f"\tTabla importada: {table['tabla']}") desconectar() log.info('Importación terminada...') return def _new_superuser(rfc): rfc = rfc.lower() if not rfc: log.error('Falta el RFC') return if not utils.rfc_exists(rfc): msg = 'El RFC no esta dado de alta' log.error(msg) return args = utils.get_data_con(rfc) user = input('Introduce el nuevo nombre para el superusuario: ').strip() if not user: msg = 'El nombre de usuario es requerido' log.error(msg) return ok, password = utils.get_pass() if not ok: log.error(password) return conectar(args) try: obj = Usuarios.create( usuario=user, contraseña=password, es_superusuario=True) msg = 'SuperUsuario creado correctamente...' log.info(msg) except IntegrityError: msg = 'El usuario ya existe' log.error(msg) desconectar() return def _change_pass(rfc): rfc = rfc.lower() if not rfc: log.error('Falta el RFC') return if not utils.rfc_exists(rfc): msg = 'El RFC no esta dado de alta' log.error(msg) return args = utils.get_data_con(rfc) conectar(args) user = input('Introduce el nombre de usuario: ').strip() if not user: msg = 'El nombre de usuario es requerido' log.error(msg) desconectar() return try: obj = Usuarios.get(usuario=user) except Usuarios.DoesNotExist: msg = 'El usuario no existe' log.error(msg) desconectar() return ok, password = utils.get_pass() if not ok: log.error(password) desconectar() return obj.contraseña = password obj.save() desconectar() log.info('Contraseña cambiada correctamente...') return def _migrate_cert(rfc): if not rfc: rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) log.info('Exportando certificados...') auth = Emisor.get_auth() data = { 'lst_pac': 'finkok', 'user_timbrado_finkok': auth['USER'], 'token_timbrado_finkok': auth['PASS'], } Configuracion.add(data) obj = Certificado.get(Certificado.es_fiel==False) cert = utils.SATCertificate cert.save_cert(obj) desconectar() log.info('Proceso terminado correctamente...') return def _import_clients(rfc, path): if not rfc: msg = 'El RFC es requerido' log.error(msg) return if not path: msg = 'El archivo CSV es necesario' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) log.info('Importando clientes...') data = utils.read_csv(path) t = len(data) ok = 0 for i, row in enumerate(data): msg = f'\tImportando cliente {i+1} de {t}' log.info(msg) row['rfc'] = row['rfc'].upper() row['nombre'] = utils.spaces(row['nombre']) row['slug'] = utils.to_slug(row['nombre']) w = ((Socios.rfc==row['rfc']) & (Socios.slug==row['slug'])) if Socios.select().where(w).exists(): msg = '\tYa existe el RFC y Razón Social' log.info(msg) continue row['es_cliente'] = True row['forma_pago'] = SATFormaPago.get(SATFormaPago.id==row['forma_pago']) try: obj = Socios.create(**row) ok += 1 except Exception as e: log.error(e) break desconectar() msg = f'Total de clientes: {t}' log.info(msg) msg = f'Clientes nuevos: {ok}' log.info(msg) log.info('Proceso terminado correctamente...') return def _import_stock(rfc, path): if not rfc: msg = 'El RFC es requerido' log.error(msg) return if not path: msg = 'El archivo CSV es necesario' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) log.info('Importando existencias...') data = utils.read_csv(path) t = len(data) ok = 0 for i, row in enumerate(data): msg = f'\tImportando existencia {i+1} de {t}' log.info(msg) cant = Decimal(row['cantidad']) if cant <= 0: continue storage = int(row['almacen']) product = Productos.get(Productos.clave==row['clave']) if not product.inventario: msg = 'Este producto no lleva inventario.' log.debug(msg) continue values = dict( storage = storage, product = product, cant = cant, ) obj = InventoryEntries.create(**values) WareHouseProduct.update_exists(values) new_cant = product.existencia + cant q = (Productos .update(existencia=Productos.existencia + cant) .where(Productos.id==product.id) ) q.execute() desconectar() msg = f'Total de productos: {t}' log.info(msg) log.info('Proceso terminado correctamente...') return def _process_command_line_arguments(): parser = argparse.ArgumentParser( description='Empresa Libre') parser.add_argument('-lc', '--list-clients', dest='list_clients', action='store_true', default=False, required=False) parser.add_argument('-nc', '--new-client', dest='new_client', action='store_true', default=False, required=False) parser.add_argument('-dc', '--delete-client', dest='delete_client', action='store_true', default=False, required=False) parser.add_argument('-ndb', '--no-database', dest='no_database', action='store_true', default=False, required=False) parser.add_argument('-m', '--migrate', dest='migrate', action='store_true', default=False, required=False) parser.add_argument('-us', '--update-sat', dest='update_sat', action='store_true', default=False, required=False) parser.add_argument('-ns', '--new-superuser', dest='new_superuser', action='store_true', default=False, required=False) parser.add_argument('-cp', '--change-pass', dest='change_pass', action='store_true', default=False, required=False) parser.add_argument('-bk', '--backup', dest='backup', action='store_true', default=False, required=False) parser.add_argument('-ed', '--export-documents', dest='export_documents', action='store_true', default=False, required=False) parser.add_argument('-ic', '--import-clients', dest='import_clients', action='store_true', default=False, required=False) parser.add_argument('-is', '--import-stock', dest='import_stock', action='store_true', default=False, required=False) parser.add_argument('-mc' , '--migrate-cert', dest='migrate_cert', action='store_true', default=False, required=False) parser.add_argument('-r', '--rfc', dest='rfc', default='') parser.add_argument('-f', '--file', dest='file', default='') return parser.parse_args() def main(args): if args.list_clients: _list_clients() return if args.new_client: _new_client(args.rfc, args.no_database) return if args.delete_client: _delete_client(args.rfc, args.no_database) return if args.migrate: _migrate_tables(args.rfc) return if args.update_sat: _update_sat() return if args.new_superuser: _new_superuser(args.rfc) return if args.change_pass: _change_pass(args.rfc) return if args.backup: utils.db_backup(IS_MV, URL['SEAFILE']) return if args.export_documents: _export_documents() return if args.migrate_cert: _migrate_cert(args.rfc) return if args.import_clients: _import_clients(args.rfc, args.file) return if args.import_stock: _import_stock(args.rfc, args.file) return return if __name__ == '__main__': args = _process_command_line_arguments() main(args)