#!/usr/bin/env python from decimal import Decimal import sqlite3 import click from peewee import * from playhouse.fields import PasswordField, ManyToManyField from playhouse.shortcuts import case, SQL, cast if __name__ == '__main__': import os, sys parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, parent_dir) from controllers import util from settings import log, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \ INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \ CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET FORMAT = '{0:.2f}' database_proxy = Proxy() class BaseModel(Model): class Meta: database = database_proxy def conectar(opt): db = { 'sqlite': SqliteDatabase, 'postgres': PostgresqlDatabase, 'mysql': MySQLDatabase, } db_type = opt.pop('type') db_name = opt.pop('name') if not db_type in db: log.error('Tipo de base de datos no soportado') return False #~ print ('DB NAME', db_name) database = db[db_type](db_name, **opt) try: database_proxy.initialize(database) database_proxy.connect() log.info('Conectado a la BD...') return True except OperationalError as e: log.error('Error al intentar conectar a la base de datos') log.error(e) return False def desconectar(): if database_proxy.obj is None: return if not database_proxy.is_closed(): database_proxy.close() log.info('Desconectado a la BD...') return def upload_file(rfc, opt, file_obj): result = util.upload_file(rfc, opt, file_obj) if result['ok']: if opt != 'bdfl': Configuracion.add({opt: file_obj.filename}) return result def validar_timbrar(): try: obj = Emisor.select()[0] except IndexError: msg = 'Es necesario agregar los datos del emisor' return {'ok': False, 'msg': msg} try: obj = Folios.select()[0] except IndexError: msg = 'Es necesaria al menos una serie de folios' return {'ok': False, 'msg': msg} msg = 'Es necesario configurar un certificado de sellos' try: obj = Certificado.select()[0] except IndexError: return {'ok': False, 'msg': msg} if not obj.serie: return {'ok': False, 'msg': msg} dias = obj.hasta - util.now() if dias.days < 0: msg = 'El certificado ha vencido, es necesario cargar uno nuevo' return {'ok': False, 'msg': msg} msg = '' if dias.days < 15: msg = 'El certificado vence en: {} días.'.format(dias.days) return {'ok': True, 'msg': msg} def get_doc(type_doc, id, rfc): types = { 'xml': 'application/xml', 'zip': 'application/octet-stream', } 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 == '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) return data, file_name, content_type def config_main(): try: obj = Emisor.select()[0] except IndexError: obj = None punto_de_venta = util.get_bool(Configuracion.get_('chk_usar_punto_de_venta')) data = { 'empresa': 'Empresa Libre', 'punto_de_venta': punto_de_venta } if not obj is None: titulo = 'Empresa Libre - {}' data['empresa'] = titulo.format(obj.nombre) return data def config_timbrar(): try: obj = Emisor.select()[0] except IndexError: return {'cfdi_donativo': False} conf = { 'cfdi_donativo': obj.es_ong, 'cfdi_anticipo': Configuracion.get_('chk_config_anticipo'), 'cfdi_ine': Configuracion.get_bool('chk_config_ine'), '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'), } 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='') def __str__(self): return '{} = {}'.format(self.clave, self.valor) @classmethod def get_bool(cls, key): data = (Configuracion .select(Configuracion.valor) .where(Configuracion.clave == key) ) if data: return util.get_bool(data[0].valor) return False @classmethod def get_(cls, keys): if isinstance(keys, str): data = (Configuracion .select(Configuracion.valor) .where(Configuracion.clave == keys) ) if data: return data[0].valor return '' if keys['fields'] == 'productos': fields = ( 'chk_config_cuenta_predial', 'chk_config_codigo_barras', 'chk_config_precio_con_impuestos', 'chk_llevar_inventario', ) 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 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_anticipo', 'chk_config_cuenta_predial', 'chk_config_codigo_barras', 'chk_config_precio_con_impuestos', 'chk_llevar_inventario', 'chk_config_ine', 'chk_config_edu', 'chk_usar_punto_de_venta', 'chk_ticket_pdf_show', 'chk_ticket_direct_print', 'chk_ticket_edit_cant', 'chk_ticket_total_up', ) data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) values = {r.clave: util.get_bool(r.valor) for r in data} tp = 'txt_ticket_printer' values[tp] = Configuracion.get_(tp) return values if keys['fields'] == 'correo': fields = ('correo_servidor', 'correo_puerto', 'correo_ssl', 'correo_usuario', 'correo_contra', 'correo_copia', 'correo_asunto', 'correo_mensaje', 'correo_directo', 'correo_confirmacion') data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) elif keys['fields'] == 'path_cer': fields = ('path_key', 'path_cer') data = (Configuracion .select() .where(Configuracion.clave.in_(fields)) ) elif keys['fields'] == 'templates': fields = ( 'txt_plantilla_factura_32', 'txt_plantilla_factura_33', 'txt_plantilla_factura_33j', 'txt_plantilla_ticket', 'txt_plantilla_donataria', ) 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 @classmethod def add(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 Meta: order_by = ('clave',) indexes = ( (('clave', 'valor'), True), ) class Tags(BaseModel): tag = TextField(index=True, unique=True) class Meta: order_by = ('tag',) class TipoDireccion(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoTitulo(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoTelefono(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoCorreo(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoPariente(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoResponsable(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoMovimientoAlumno(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class TipoMovimientoAlmacen(BaseModel): nombre = TextField(unique=True) class Meta: order_by = ('nombre',) def __str__(self): return self.nombre class Sucursales(BaseModel): nombre = TextField(default='') direccion = TextField(default='') serie_facturas = TextField(default='') serie_tickets = TextField(default='') class Meta: order_by = ('nombre',) class Usuarios(BaseModel): usuario = TextField(unique=True) nombre = TextField(default='') apellidos = TextField(default='') correo = TextField(default='') contraseña = PasswordField() es_superusuario = BooleanField(default=False) es_admin = BooleanField(default=False) es_activo = BooleanField(default=True) fecha_ingreso = DateTimeField(default=util.now) ultimo_ingreso = DateTimeField(null=True) sucursal = ForeignKeyField(Sucursales, null=True) def __str__(self): t = '{} {} ({})' return t.format(self.nombre, self.apellidos, self.usuario) class Meta: order_by = ('nombre', 'apellidos') @classmethod def add(cls, values): values['contraseña'] = values.pop('contra') try: Usuarios.create(**values) return {'ok': True} except Exception as e: log.error(e) msg = 'Ocurrio un error, consulta a soporte técnico' return {'ok': False, 'msg': msg} @classmethod def remove(cls, id): q = Usuarios.delete().where(Usuarios.id==int(id)) return bool(q.execute()) @classmethod def edit(self, values): # ~ print (values) id = int(values.pop('id')) try: if 'contra' in values: values['contraseña'] = values.pop('contra') q = Usuarios.update(**values).where(Usuarios.id==id) result = {'ok': bool(q.execute())} except IntegrityError: msg = 'El usuario ya existe' result = {'ok': False, 'msg': msg} return result @classmethod def actualizar(self, values, user): id = int(values['id']) v = {'0': False, '1': True} if values['field'] == 'es_superusuario' and not user.es_superusuario: msg = 'Solo un super usuario puede hacer este cambio' return {'ok': False, 'msg': msg} if values['field'] == 'es_activo': q = (Usuarios .update(**{'es_activo': v[values['value']]}) .where(Usuarios.id==id)) result = bool(q.execute()) elif values['field'] == 'es_admin': q = (Usuarios .update(**{'es_admin': v[values['value']]}) .where(Usuarios.id==id)) result = bool(q.execute()) elif values['field'] == 'es_superusuario': q = (Usuarios .update(**{'es_superusuario': v[values['value']]}) .where(Usuarios.id==id)) result = bool(q.execute()) return {'ok': result} @classmethod def get_(cls, user): if user.es_superusuario: rows = Usuarios.select().dicts() else: filters = (Usuarios.es_superusuario == False) rows = Usuarios.select().where(filters).dicts() for row in rows: del row['contraseña'] return tuple(rows) class Registro(BaseModel): usuario = TextField() accion = TextField(default='') tabla = TextField(default='') fecha = DateTimeField(default=util.now) def __str__(self): t = '{} {}-{} ({})' return t.format(self.usuario, self.accion, self.tabla, self.fecha) class Meta: order_by = ('usuario', 'fecha') @classmethod def add(cls, values): try: Registro.create(**values) return except: return class SATRegimenes(BaseModel): key = TextField(index=True, unique=True) name = TextField(index=True) activo = BooleanField(default=False) default = BooleanField(default=False) fisica = BooleanField(default=False) moral = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return '{} ({})'.format(self.name, self.key) @classmethod def get_(cls, ids): if isinstance(ids, int): ids = [ids] return SATRegimenes.select().where(SATRegimenes.id.in_(ids)) @classmethod def get_activos(cls, rfc): where = ((SATRegimenes.activo==True) & (SATRegimenes.fisica==True)) if (len(rfc) == 12): where = ((SATRegimenes.activo==True) & (SATRegimenes.moral==True)) rows = (SATRegimenes .select( SATRegimenes.id, SATRegimenes.name.alias('value')) .where(where) .dicts() ) return tuple(rows) class Emisor(BaseModel): rfc = TextField(unique=True) nombre = TextField(default='') nombre_comercial = TextField(default='') calle = TextField(default='') no_exterior = TextField(default='') no_interior = TextField(default='') colonia = TextField(default='') municipio = TextField(default='') estado = TextField(default='') pais = TextField(default='México') codigo_postal = TextField(default='') cp_expedicion = TextField(default='') es_moral = BooleanField(default=False) es_ong = BooleanField(default=False) es_escuela = BooleanField(default=False) autorizacion = TextField(default='') fecha_autorizacion = DateField(null=True) fecha_dof = DateField(null=True) telefono = TextField(default='') correo = TextField(default='') web = TextField(default='') curp = TextField(default='') correo_timbrado = TextField(default='') token_timbrado = TextField(default='') token_soporte = TextField(default='') logo = TextField(default='') regimenes = ManyToManyField(SATRegimenes, related_name='emisores') def __str__(self): t = '{} ({})' return t.format(self.nombre, self.rfc) class Meta: order_by = ('nombre',) @classmethod def get_(cls, rfc): regimenes = SATRegimenes.get_activos(rfc) row = {'regimenes': regimenes} obj = Emisor.select().where(Emisor.rfc==rfc) if bool(obj): obj = obj[0] row['emisor'] = { 'emisor_rfc': obj.rfc, 'emisor_nombre': obj.nombre, 'emisor_cp': obj.codigo_postal, 'emisor_cp2': obj.cp_expedicion, 'emisor_calle': obj.calle, 'emisor_no_exterior': obj.no_exterior, 'emisor_no_interior': obj.no_interior, 'emisor_colonia': obj.colonia, 'emisor_municipio': obj.municipio, 'emisor_estado': obj.estado, 'emisor_pais': obj.pais, 'emisor_logo': obj.logo, 'emisor_nombre_comercial': obj.nombre_comercial, 'emisor_telefono': obj.telefono, 'emisor_correo': obj.correo, 'emisor_web': obj.web, 'es_escuela': obj.es_escuela, 'es_ong': obj.es_ong, 'ong_autorizacion': obj.autorizacion, 'ong_fecha': obj.fecha_autorizacion, 'ong_fecha_dof': obj.fecha_dof, 'correo_timbrado': obj.correo_timbrado, 'token_timbrado': obj.token_timbrado, 'token_soporte': obj.token_soporte, 'regimenes': [row.id for row in obj.regimenes] } else: row['emisor'] = {'emisor_rfc': rfc} return {'ok': True, 'row': row} @classmethod def get_auth(cls): try: obj = Emisor.select()[0] data = { 'RFC': obj.rfc, 'USER': obj.correo_timbrado, 'PASS': obj.token_timbrado, 'REPO': obj.token_soporte, } return data except: return {} @classmethod def get_regimenes(cls): try: obj = Emisor.select()[0] except IndexError: return () rows = [{'id': row.key, 'value': row.name} for row in obj.regimenes] return tuple(rows) def _clean(self, values): fields = util.clean(values) fields['rfc'] = fields.pop('emisor_rfc') fields['nombre'] = fields.pop('emisor_nombre') fields['codigo_postal'] = fields.pop('emisor_cp') fields['cp_expedicion'] = fields.pop('emisor_cp2', '') or fields['codigo_postal'] fields['calle'] = fields.pop('emisor_calle', '') fields['no_exterior'] = fields.pop('emisor_no_exterior', '') fields['no_interior'] = fields.pop('emisor_no_interior', '') fields['colonia'] = fields.pop('emisor_colonia', '') fields['municipio'] = fields.pop('emisor_municipio', '') fields['estado'] = fields.pop('emisor_estado', '') fields['pais'] = fields.pop('emisor_pais', 'México') fields['logo'] = fields.pop('emisor_logo', '') fields['nombre_comercial'] = fields.pop('emisor_nombre_comercial', '') fields['telefono'] = fields.pop('emisor_telefono', '') fields['correo'] = fields.pop('emisor_correo', '') fields['web'] = fields.pop('emisor_web', '') fields['es_escuela'] = bool(fields['es_escuela'].replace('0', '')) fields['es_ong'] = bool(fields['es_ong'].replace('0', '')) fields['autorizacion'] = fields.pop('ong_autorizacion', '') fields['fecha_autorizacion'] = fields.pop('ong_fecha', None) fields['fecha_dof'] = fields.pop('ong_fecha_dof', None) if len(fields['rfc']) == 12: fields['es_moral'] = True fields['regimenes'] = SATRegimenes.get_( util.loads(fields['regimenes'])) return fields @classmethod def add(cls, values): fields = cls._clean(cls, values) obj, created = Emisor.get_or_create(rfc=fields['rfc']) obj.regimenes = fields.pop('regimenes') q = Emisor.update(**fields).where(Emisor.id==obj.id) return {'ok': bool(q.execute())} class Certificado(BaseModel): key = BlobField(null=True) key_enc = TextField(default='') cer = BlobField(null=True) cer_pem = TextField(default='') cer_txt = TextField(default='') p12 = BlobField(null=True) serie = TextField(default='') rfc = TextField(default='') desde = DateTimeField(null=True) hasta = DateTimeField(null=True) def __str__(self): return self.serie @classmethod def get_data(cls): obj = cls.get_(cls) row = { 'cert_rfc': obj.rfc, 'cert_serie': obj.serie, 'cert_desde': obj.desde, 'cert_hasta': obj.hasta, } return row def get_(cls): return Certificado.select()[0] @classmethod def add(cls, file_obj): if file_obj.filename.endswith('key'): path_key = util.save_temp(file_obj.file.read()) Configuracion.add({'path_key': path_key}) elif file_obj.filename.endswith('cer'): path_cer = util.save_temp(file_obj.file.read()) Configuracion.add({'path_cer': path_cer}) return {'status': 'server'} @classmethod def validate(cls, values, session): row = {} result = False obj = cls.get_(cls) paths = Configuracion.get_({'fields': 'path_cer'}) cert = util.Certificado(paths) auth = Emisor.get_auth() data = cert.validate(values['contra'], session['rfc'], auth) if data: msg = 'Certificado guardado correctamente' q = Certificado.update(**data).where(Certificado.id==obj.id) if q.execute(): result = True row = { 'cert_rfc': data['rfc'], 'cert_serie': data['serie'], 'cert_desde': data['desde'], 'cert_hasta': data['hasta'], } else: msg = cert.error Configuracion.add({'path_key': ''}) Configuracion.add({'path_cer': ''}) return {'ok': result, 'msg': msg, 'data': row} class Folios(BaseModel): serie = TextField(unique=True) inicio = IntegerField(default=1) default = BooleanField(default=False) usarcon = TextField(default='') class Meta: order_by = ('-default', 'serie', 'inicio') indexes = ( (('serie', 'inicio'), True), ) @classmethod def get_default(cls): folio = Folios.select()[0] return folio.serie @classmethod def get_all(cls): rows = (Folios .select( Folios.id, Folios.serie.alias('value'), Folios.usarcon, ) .dicts() ) return tuple(rows) @classmethod def get_(cls): rows = (Folios .select( Folios.id, SQL(" '-' AS delete"), Folios.serie, Folios.inicio, case(Folios.usarcon, ( ('I', 'Ingreso'), ('E', 'Egreso'), ('T', 'Traslado'), ), 'Todos').alias('usarcon'), case(Folios.default, ( (True, 'Si'), (False, 'No'), )).alias('pre') ) .dicts() ) return tuple(rows) @classmethod def add(cls, values): uc = { '': 'Todos', 'I': 'Ingreso', 'E': 'Egreso', 'T': 'Traslado', } pre = { True: 'Si', False: 'No', } if 'default' in values: values['default'] = True try: obj = Folios.create(**values) except IntegrityError: msg = 'Serie ya existe' return {'ok': False, 'msg': msg} row = { 'id': obj.id, 'delete' : '-', 'serie' : obj.serie, 'inicio' : obj.inicio, 'usarcon' : uc[obj.usarcon], 'pre' : pre[obj.default], } return {'ok': True, 'row': row} @classmethod def remove(cls, id): q = Folios.delete().where(Folios.id==id) return bool(q.execute()) class Categorias(BaseModel): categoria = TextField() padre = ForeignKeyField('self', null=True, related_name='hijos') class Meta: order_by = ('categoria',) indexes = ( (('categoria', 'padre'), True), ) @classmethod def exists(cls, filters): return Categorias.select().where(filters).exists() @classmethod def get_all(cls): rows = (Categorias.select( Categorias.id, Categorias.categoria.alias('value'), Categorias.padre.alias('parent_id')) ).dicts() for row in rows: if row['parent_id'] is None: row['parent_id'] = 0 return tuple(rows) class CondicionesPago(BaseModel): condicion = TextField(unique=True) class Meta: order_by = ('condicion',) def __str__(self): return self.condicion @classmethod def get_(cls): q = CondicionesPago.select(CondicionesPago.condicion).tuples() data = [r[0] for r in q] return data @classmethod def get_or(cls, value): if value is None: return value obj, _ = CondicionesPago.get_or_create(condicion=value) return obj class SATUnidades(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name') indexes = ( (('key', 'name'), True), ) def __str__(self): return '{} ({})'.format(self.name, self.key) @classmethod def get_(self): rows = SATUnidades.select().dicts() return tuple(rows) @classmethod def add(self, values): try: SATUnidades.create(**values) return {'ok': True} except: return {'ok': False} @classmethod def actualizar(self, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} q = (SATUnidades .update(**{'activo': v[values['value']]}) .where(SATUnidades.id==id)) result = bool(q.execute()) elif values['field'] == 'default': q = SATUnidades.update(**{'default': False}) q.execute() v = {'false': False, 'true': True} q = (SATUnidades .update(**{'default': v[values['value']]}) .where(SATUnidades.id==id)) result = bool(q.execute()) return {'ok': result} @classmethod def get_default(cls): obj = SATUnidades.select()[0] if obj.default: return obj.id return 0 @classmethod def get_activos(cls): rows = (SATUnidades .select( SATUnidades.id, SATUnidades.name.alias('value')) .where(SATUnidades.activo==True) .dicts() ) return tuple(rows) @classmethod def remove(cls, id): with database_proxy.transaction(): try: q = SATUnidades.delete().where(SATUnidades.id==id) return bool(q.execute()) except IntegrityError: return False class SATFormaPago(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Forma de pago: ({}) {}'.format(self.key, self.name) @classmethod def get_(self): rows = SATFormaPago.select().dicts() return tuple(rows) @classmethod def get_by_key(cls, key): return SATFormaPago.get(SATFormaPago.key==key) @classmethod def get_activos(cls, values): field = SATFormaPago.id if values: field = SATFormaPago.key.alias('id') rows = (SATFormaPago .select(field, SATFormaPago.name.alias('value')) .where(SATFormaPago.activo==True) .dicts() ) return tuple(rows) @classmethod def actualizar(self, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} q = (SATFormaPago .update(**{'activo': v[values['value']]}) .where(SATFormaPago.id==id)) result = bool(q.execute()) elif values['field'] == 'default': q = SATFormaPago.update(**{'default': False}) q.execute() v = {'false': False, 'true': True} q = (SATFormaPago .update(**{'default': v[values['value']]}) .where(SATFormaPago.id==id)) result = bool(q.execute()) return {'ok': result} class SATAduanas(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) class SATMonedas(BaseModel): key = TextField(unique=True, index=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Moneda: ({}) {}'.format(self.key, self.name) @classmethod def add(self, values): try: SATMonedas.create(**values) return {'ok': True} except: return {'ok': False} @classmethod def get_(self): rows = SATMonedas.select().dicts() return tuple(rows) @classmethod def get_activos(cls): rows = (SATMonedas .select( SATMonedas.key.alias('id'), SATMonedas.name.alias('value')) .where(SATMonedas.activo==True) .dicts() ) return tuple(rows) @classmethod def get_activos_by_id(cls): rows = (SATMonedas .select( SATMonedas.id, SATMonedas.name.alias('value')) .where(SATMonedas.activo==True) .dicts() ) return tuple(rows) @classmethod def actualizar(self, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} q = (SATMonedas .update(**{'activo': v[values['value']]}) .where(SATMonedas.id==id)) result = bool(q.execute()) elif values['field'] == 'default': q = SATMonedas.update(**{'default': False}) q.execute() v = {'false': False, 'true': True} q = (SATMonedas .update(**{'default': v[values['value']]}) .where(SATMonedas.id==id)) result = bool(q.execute()) return {'ok': result} class SATImpuestos(BaseModel): key = TextField(index=True) name = TextField(default='', index=True) factor = TextField(default='T') tipo = TextField(default='T') tasa = DecimalField(default=0.0, decimal_places=6, auto_round=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'factor', 'tipo', 'tasa'), True), ) @classmethod def get_o_crea(self, values): obj, _ = SATImpuestos.get_or_create(**values) return obj @classmethod def add(self, values): tasa = float(values['tasa']) tipo = 'T' if tasa < 0: tipo = 'R' row = { 'key': IMPUESTOS.get(values['impuesto']), 'name': values['impuesto'], 'tipo': tipo, 'tasa': abs(tasa), } try: obj = SATImpuestos.create(**row) row['id'] = obj.id row['delete'] = '-' return {'ok': True, 'row': row} except IntegrityError: return {'ok': False, 'msg': 'El impuesto ya existe'} @classmethod def remove(cls, id): with database_proxy.transaction(): try: q = SATImpuestos.delete().where(SATImpuestos.id==id) return bool(q.execute()) except IntegrityError: return False @classmethod def get_(self): rows = (SATImpuestos.select( SATImpuestos.id, SQL(" '-' AS delete"), SATImpuestos.name, SATImpuestos.tipo, SATImpuestos.tasa, SATImpuestos.activo, SATImpuestos.default) .dicts() ) return tuple(rows) @classmethod def get_activos(self): rows = SATImpuestos.select().where(SATImpuestos.activo==True).dicts() return tuple(rows) @classmethod def actualizar(self, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} q = (SATImpuestos .update(**{'activo': v[values['value']]}) .where(SATImpuestos.id==id)) result = bool(q.execute()) elif values['field'] == 'default': q = SATImpuestos.update(**{'default': False}) q.execute() v = {'false': False, 'true': True} q = (SATImpuestos .update(**{'default': v[values['value']]}) .where(SATImpuestos.id==id)) result = bool(q.execute()) return {'ok': result} class SATTipoRelacion(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Tipo de relación: ({}) {}'.format(self.key, self.name) @classmethod def get_activos(cls, values): field = SATTipoRelacion.id if values: field = SATTipoRelacion.key.alias('id') rows = (SATTipoRelacion .select(field, SATTipoRelacion.name.alias('value')) .where(SATTipoRelacion.activo==True) .dicts() ) return ({'id': '-', 'value': ''},) + tuple(rows) class SATBancos(BaseModel): key = TextField(index=True, unique=True) name = TextField(index=True) razon_social = TextField(default='') rfc = TextField(default='') activo = BooleanField(default=False) class Meta: order_by = ('-activo', 'name') indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Banco: {} ({})'.format(self.name, self.key) @classmethod def get_(cls): rows = SATBancos.select().dicts() return tuple(rows) @classmethod def get_activos_by_id(cls): rows = (SATBancos .select( SATBancos.id, SATBancos.name.alias('value')) .where(SATBancos.activo==True) .dicts() ) return tuple(rows) @classmethod def actualizar(cls, values): id = int(values['id']) if values['field'] == 'activo': v = {'0': False, '1': True} q = (SATBancos .update(**{'activo': v[values['value']]}) .where(SATBancos.id==id)) result = bool(q.execute()) return {'ok': result} class SATNivelesEducativos(BaseModel): name = TextField(index=True) class Meta: order_by = ('name',) def __str__(self): return self.name class NivelesEducativos(BaseModel): nombre = TextField() autorizacion = TextField(default='') class Meta: order_by = ('nombre',) indexes = ( (('nombre', 'autorizacion'), True), ) def __str__(self): return '{} ({})'.format(self.nombre, self.autorizacion) class Grupos(BaseModel): nivel = ForeignKeyField(NivelesEducativos) grado = TextField(default='') nombre = TextField(default='') class Meta: order_by = ('nivel', 'grado', 'nombre') indexes = ( (('nivel', 'grado', 'nombre'), True), ) def __str__(self): return '{} {} {}'.format(self.nivel.nombre, self.grado, self.nombre) class CuentasBanco(BaseModel): de_emisor = BooleanField(default=False) activa = BooleanField(default=True) nombre = TextField() banco = ForeignKeyField(SATBancos) fecha_apertura = DateField(null=True) cuenta = TextField(default='') clabe = TextField(default='') moneda = ForeignKeyField(SATMonedas) saldo_inicial = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class Meta: order_by = ('nombre',) indexes = ( (('banco', 'cuenta'), True), ) def __str__(self): return '{} ({})'.format(self.banco.name, self.cuenta[-4:]) @classmethod def actualizar_saldo(cls, id, saldo): fields = {'saldo': saldo} q = CuentasBanco.update(**fields).where(CuentasBanco.id==id) return bool(q.execute()) @classmethod def get_saldo(cls, id): try: obj = CuentasBanco.get(CuentasBanco.id==id) return obj.saldo except CuentasBanco.DoesNotExist: return 0 @classmethod def remove(cls, id): try: with database_proxy.atomic() as txn: q = MovimientosBanco.delete().where(MovimientosBanco.cuenta==id) q.execute() q = CuentasBanco.delete().where(CuentasBanco.id==id) q.execute() return True except: return False @classmethod def get_years(cls): data = [{'id': -1, 'value': 'Todos'}] year1 = (CuentasBanco .select(fn.Min(CuentasBanco.fecha_apertura.year)) .where(CuentasBanco.de_emisor==True, CuentasBanco.activa==True) .group_by(CuentasBanco.fecha_apertura.year) .order_by(CuentasBanco.fecha_apertura.year) .scalar() ) if year1: year2 = util.now().year + 1 data += [{'id': y, 'value': y} for y in range(int(year1), year2)] return data @classmethod def get_(cls, values): if values['tipo'] == '1': rows = (CuentasBanco .select() .where(CuentasBanco.de_emisor==True, CuentasBanco.activa==True) ) if not (len(rows)): return {'ok': False} first = rows[0] rows = [{'id': r.id, 'value': '{} ({})'.format( r.banco.name, r.cuenta[-4:])} for r in rows] data = { 'ok': True, 'rows': tuple(rows), 'moneda': first.moneda.name, 'saldo': first.saldo, } return data return @classmethod def emisor(cls): rows = (CuentasBanco .select( CuentasBanco.id, CuentasBanco.activa, CuentasBanco.nombre, SATBancos.name.alias('banco'), CuentasBanco.fecha_apertura, CuentasBanco.cuenta, CuentasBanco.clabe, SATMonedas.name.alias('moneda'), CuentasBanco.saldo ) .join(SATBancos).switch(CuentasBanco) .join(SATMonedas).switch(CuentasBanco) .where(CuentasBanco.de_emisor==True).dicts() ) return tuple(rows) @classmethod def add(cls, values): w = '37137137137137137' dv = str( (10 - sum([(int(v) * int(values['clabe'][i])) % 10 for i, v in enumerate(w)]) % 10) % 10) if dv != values['clabe'][-1]: msg = 'Digito de control de la CLABE es incorrecto' return {'ok': False, 'msg': msg} fecha_deposito = values.pop('fecha_deposito', None) with database_proxy.transaction(): try: obj = CuentasBanco.create(**values) except IntegrityError: msg = 'Esta cuenta ya existe' return {'ok': False, 'msg': msg} nuevo_mov= { 'cuenta': obj.id, 'fecha': fecha_deposito, 'descripcion': 'Saldo inicial', 'forma_pago': SATFormaPago.get_by_key('99'), 'deposito': values['saldo'], 'saldo': values['saldo'], } MovimientosBanco.add(nuevo_mov) rows = (CuentasBanco .select( CuentasBanco.id, CuentasBanco.activa, CuentasBanco.nombre, SATBancos.name.alias('banco'), CuentasBanco.fecha_apertura, CuentasBanco.cuenta, CuentasBanco.clabe, SATMonedas.name.alias('moneda'), CuentasBanco.saldo ) .join(SATBancos).switch(CuentasBanco) .join(SATMonedas).switch(CuentasBanco) .where(CuentasBanco.id==obj.id).dicts() ) data = {'ok': True, 'row': rows[0]} return data class MovimientosBanco(BaseModel): cuenta = ForeignKeyField(CuentasBanco) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) descripcion = TextField(default='') forma_pago = ForeignKeyField(SATFormaPago) retiro = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) deposito = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) cancelado = BooleanField(default=False) conciliado = BooleanField(default=False) moneda = TextField(default='MXN') # Complemento de pagos tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6, auto_round=True) numero_operacion = TextField(default='') origen_rfc = TextField(default='') origen_nombre = TextField(default='') origen_cuenta = TextField(default='') destino_rfc = TextField(default='') destino_cuenta = TextField(default='') tipo_cadena_pago = TextField(default='') certificado_pago = TextField(default='') cadena_pago = TextField(default='') sello_pago = TextField(default='') class Meta: order_by = ('fecha',) def _ultimo_saldo(self, cuenta, fecha): query = (MovimientosBanco .select() .where( (MovimientosBanco.cuenta==cuenta) & (MovimientosBanco.fecharow.fecha) & (MovimientosBanco.cancelado==False)) ) saldo = round(Decimal(row.saldo), DECIMALES) for mov in query: mov.saldo = saldo + mov.deposito - mov.retiro mov.save() saldo = mov.saldo CuentasBanco.actualizar_saldo(row.cuenta, saldo) return saldo @classmethod def add(cls, values): ids = values.pop('ids', '') actualizar = False if 'saldo' in values: saldo = values['saldo'] else: actualizar = True hora = values.pop('hora') values['fecha'] = '{}T{}'.format(values['fecha'][:10], hora) values['cuenta'] = int(values['cuenta']) values['retiro'] = util.get_float(values['retiro']) values['deposito'] = util.get_float(values['deposito']) values['forma_pago'] = int(values['forma_pago']) ultimo_saldo = cls._ultimo_saldo( cls, values['cuenta'], values['fecha']) values['saldo'] = \ ultimo_saldo - values['retiro'] + values['deposito'] with database_proxy.transaction(): try: obj = MovimientosBanco.create(**values) except IntegrityError: msg = 'Este movimiento ya existe' return {'ok': False, 'msg': msg} if actualizar: saldo = cls._actualizar_saldos(cls, obj) if ids: FacturasPagos.add(obj, util.loads(ids)) return {'ok': True, 'saldo': saldo} @classmethod def remove(cls, id): try: obj = MovimientosBanco.get(MovimientosBanco.id==id) except MovimientosBanco.DoesNotExist: return False if obj.conciliado or obj.cancelado: return False with database_proxy.transaction(): obj.cancelado = True obj.save() FacturasPagos.cancelar(obj) obj = cls._movimiento_anterior(cls, obj.cuenta, obj.fecha) cls._actualizar_saldos(cls, obj) return True @classmethod def con(cls, id): cant = (MovimientosBanco .select(MovimientosBanco.id) .where(MovimientosBanco.cuenta==id) .count() ) if cant > 2: return {'ok': True} return {'ok': False} @classmethod def get_(cls, values): cuenta = int(values['cuenta']) if 'fechas' in values: rango = values['fechas'] fd = (MovimientosBanco.fecha.between( util.get_date(rango['start']), util.get_date(rango['end'], True))) filtros = (fd & (MovimientosBanco.cuenta==cuenta) & (MovimientosBanco.cancelado==False) ) else: year = int(values['year']) mes = int(values['mes']) if year == -1: fy = (MovimientosBanco.fecha.year > 0) else: fy = (MovimientosBanco.fecha.year == year) if mes == -1: fm = (MovimientosBanco.fecha.month > 0) else: fm = (MovimientosBanco.fecha.month == mes) filtros = (fy & fm & (MovimientosBanco.cuenta==cuenta) & (MovimientosBanco.cancelado==False) ) rows = tuple(MovimientosBanco .select( MovimientosBanco.id, MovimientosBanco.fecha, MovimientosBanco.numero_operacion, MovimientosBanco.descripcion, MovimientosBanco.retiro, MovimientosBanco.deposito, MovimientosBanco.saldo) .where(filtros) .dicts() ) return {'ok': True, 'rows': rows} class CfdiPagos(BaseModel): movimiento = ForeignKeyField(MovimientosBanco) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) fecha_timbrado = DateTimeField(null=True) xml = TextField(default='') uuid = UUIDField(null=True) estatus = TextField(default='Guardado') estatus_sat = TextField(default='') notas = TextField(default='') cancelado = BooleanField(default=False) class Meta: order_by = ('movimiento',) class SATUsoCfdi(BaseModel): key = TextField(index=True, unique=True) name = TextField(default='', index=True) activo = BooleanField(default=False) default = BooleanField(default=False) fisica = BooleanField(default=True) moral = BooleanField(default=False) class Meta: order_by = ('-default', 'name',) indexes = ( (('key', 'name'), True), ) def __str__(self): return 'Uso del CFDI: {} ({})'.format(self.name, self.key) @classmethod def get_id(self, key): if key is None: return return SATUsoCfdi.get(SATUsoCfdi.key==key).id @classmethod def get_key(self, id): if id is None: return return SATUsoCfdi.get(SATUsoCfdi.id==id).key @classmethod def get_activos(cls): rows = (SATUsoCfdi .select( SATUsoCfdi.key.alias('id'), SATUsoCfdi.name.alias('value'), SATUsoCfdi.fisica, SATUsoCfdi.moral, ) .where(SATUsoCfdi.activo==True) .dicts() ) return tuple(rows) class TipoCambio(BaseModel): dia = DateField(default=util.now) moneda = ForeignKeyField(SATMonedas) tipo_cambio = DecimalField(max_digits=15, decimal_places=6, auto_round=True) class Meta: order_by = ('-dia',) class Addendas(BaseModel): nombre = TextField(unique=True) addenda = TextField() class Meta: order_by = ('nombre',) class Socios(BaseModel): tipo_persona = IntegerField(default=1) rfc = TextField(index=True) nombre = TextField(index=True) slug = TextField(default='') nombre_comercial = TextField(index=True, default='') calle = TextField(default='') no_exterior = TextField(default='') no_interior = TextField(default='') colonia = TextField(default='') municipio = TextField(default='') estado = TextField(default='') pais = TextField(default='') codigo_postal = TextField(default='') notas = TextField(default='') telefonos = TextField(default='') es_activo = BooleanField(default=True) es_ong = BooleanField(default=False) fecha_alta = DateField(default=util.now) dias_pago = IntegerField(default=0) dias_habiles = BooleanField(default=False) es_cliente = BooleanField(default=False) es_proveedor = BooleanField(default=False) cuenta_cliente = TextField(default='') cuenta_proveedor = TextField(default='') saldo_cliente = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) saldo_proveedor = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) web = TextField(default='') correo_facturas = TextField(default='') forma_pago = ForeignKeyField(SATFormaPago, null=True) condicion_pago = ForeignKeyField(CondicionesPago, null=True) addenda = ForeignKeyField(Addendas, null=True) uso_cfdi = ForeignKeyField(SATUsoCfdi, null=True) tags = ManyToManyField(Tags, related_name='socios_tags') def __str__(self): t = '{} ({})' return t.format(self.nombre, self.rfc) class Meta: order_by = ('nombre',) indexes = ( (('rfc', 'slug'), True), ) def _clean(self, values): fields = util.clean(values) fields['rfc'] = fields['rfc'].upper() fields['nombre'] = util.spaces(fields['nombre']) fields['slug'] = util.to_slug(fields['nombre']) uso_cfdi = fields.pop('uso_cfdi_socio', None) fields['uso_cfdi'] = SATUsoCfdi.get_id(uso_cfdi) fields['condicion_pago'] = \ CondicionesPago.get_or(fields.get('condicion_pago', None)) fb = ('dias_habiles', 'es_activo', 'es_cliente', 'es_proveedor', 'es_ong') for name in fb: fields[name] = bool(fields[name].replace('0', '')) return fields @classmethod def get_(cls, values): #~ print ('values', values) id = values.get('id', 0) if id: id = int(values['id']) row = Socios.select().where(Socios.id==id).dicts()[0] row['uso_cfdi_socio'] = SATUsoCfdi.get_key(row.pop('uso_cfdi')) if not row['condicion_pago'] is None: row['condicion_pago'] = \ str(CondicionesPago.get(id=row['condicion_pago'])) return row #~ return {'data': data['rows'][:100], 'pos':0, 'total_count': 1300} #~ start = 0 #~ count = 0 #~ end = 100 #~ if values: #~ {'start': '100', 'count': '100', 'continue': 'true'} #~ start = int(values['start']) #~ cont = int(values['count']) #~ end = start + count total = Socios.select().count() rows = (Socios .select( Socios.id, Socios.rfc, Socios.nombre, Socios.saldo_cliente) .dicts() ) return {'pos': 0, 'total_count': total, 'data': tuple(rows)} @classmethod def get_by_client(cls, values): id = int(values.get('id', 0)) if id: row = (Socios .select( Socios.id, Socios.nombre, Socios.rfc, SATFormaPago.key.alias('forma_pago'), SATUsoCfdi.key.alias('uso_cfdi')) .join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios) .join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios) .where((Socios.id==id) & (Socios.es_cliente==True)) .dicts() ) if len(row): return {'ok': True, 'row': row[0]} return {'ok': False} name = values.get('name', '') if name: rows = (Socios .select(Socios.id, Socios.nombre, Socios.rfc, SATFormaPago.key.alias('forma_pago'), SATUsoCfdi.key.alias('uso_cfdi')) .join(SATFormaPago, JOIN.LEFT_OUTER).switch(Socios) .join(SATUsoCfdi, JOIN.LEFT_OUTER).switch(Socios) .where((Socios.es_cliente==True) & (Socios.rfc.contains(name) | Socios.nombre.contains(name))) .dicts()) return tuple(rows) return {'ok': False} @classmethod def add(cls, values): fields = cls._clean(cls, values) try: obj = Socios.create(**fields) except IntegrityError as e: msg = 'Ya existe el RFC y Razón Social' data = {'ok': False, 'row': {}, 'new': True, 'msg': msg} return data #~ ToDo Agregar tags row = { 'id': obj.id, 'rfc': obj.rfc, 'nombre': obj.nombre, 'saldo_cliente': obj.saldo_cliente, } data = {'ok': True, 'row': row, 'new': True} return data @classmethod def actualizar(cls, values, id): fields = cls._clean(cls, values) try: q = Socios.update(**fields).where(Socios.id==id) q.execute() except IntegrityError: msg = 'Ya existe el RFC y Razón Social' data = {'ok': False, 'row': {}, 'new': True, 'msg': msg} return data row = { 'id': id, 'rfc': fields['rfc'], 'nombre': fields['nombre'], } data = {'ok': True, 'row': row, 'new': False} return data @classmethod def remove(cls, id): count = (Facturas .select(fn.COUNT(Facturas.id)).join(Socios) .where(Socios.id==id) .count()) if count: return False q = Socios.delete().where(Socios.id==id) return bool(q.execute()) class Contactos(BaseModel): socio = ForeignKeyField(Socios) titulo = ForeignKeyField(TipoTitulo) foto = TextField(default='') nombre = TextField(index=True) paterno = TextField(index=True) materno = TextField(default='') fecha_nacimiento = DateField(null=True) notas = TextField(default='') class Meta: order_by = ('socio', 'nombre') indexes = ( (('socio', 'nombre', 'paterno', 'materno'), True), ) class ContactoDirecciones(BaseModel): contacto = ForeignKeyField(Contactos) tipo = ForeignKeyField(TipoDireccion) direccion = TextField() class Meta: order_by = ('contacto',) indexes = ( (('contacto', 'tipo', 'direccion'), True), ) class ContactoTelefonos(BaseModel): contacto = ForeignKeyField(Contactos) tipo = ForeignKeyField(TipoTelefono) telefono = TextField() class Meta: order_by = ('contacto',) indexes = ( (('contacto', 'tipo', 'telefono'), True), ) class ContactoCorreos(BaseModel): contacto = ForeignKeyField(Contactos) tipo = ForeignKeyField(TipoCorreo) correo = TextField() class Meta: order_by = ('contacto',) indexes = ( (('contacto', 'tipo', 'correo'), True), ) class Alumnos(BaseModel): rfc = TextField(null=True) curp = TextField(index=True, unique=True) foto = TextField(default='') nombre = TextField(index=True) paterno = TextField(index=True) materno = TextField(default='') calle = TextField(default='') no_exterior = TextField(default='') no_interior = TextField(default='') colonia = TextField(default='') municipio = TextField(default='') estado = TextField(default='') pais = TextField(default='') codigo_postal = TextField(default='') notas = TextField(default='') telefonos = TextField(default='') correos = TextField(default='') es_activo = BooleanField(default=True) fecha_alta = DateField(default=util.now) fecha_nacimiento = DateField(null=True) factura = ForeignKeyField(Socios, null=True) grupo = ForeignKeyField(Grupos, null=True) def __str__(self): t = '{} {} {}' return t.format(self.nombre, self.paterno, self.materno) class Meta: order_by = ('nombre', 'paterno') class AlumnosParientes(BaseModel): alumno = ForeignKeyField(Alumnos) tipo_pariente = ForeignKeyField(TipoPariente) foto = TextField(default='') nombre = TextField(index=True) paterno = TextField(index=True) materno = TextField(default='') fecha_nacimiento = DateField(null=True) puede_recoger = BooleanField(default=False) class Meta: order_by = ('alumno',) class ParienteDirecciones(BaseModel): pariente = ForeignKeyField(AlumnosParientes) tipo = ForeignKeyField(TipoDireccion) direccion = TextField() class Meta: order_by = ('pariente',) class ParienteTelefonos(BaseModel): pariente = ForeignKeyField(AlumnosParientes) tipo = ForeignKeyField(TipoTelefono) telefono = TextField() class Meta: order_by = ('pariente',) class ParienteCorreos(BaseModel): pariente = ForeignKeyField(AlumnosParientes) tipo = ForeignKeyField(TipoCorreo) correo = TextField() class Meta: order_by = ('pariente',) class Almacenes(BaseModel): nombre = TextField(default='') ubicacion = TextField(default='') class Meta: order_by = ('nombre',) class Productos(BaseModel): almacen = ForeignKeyField(Almacenes, null=True) categoria = ForeignKeyField(Categorias, null=True) clave = TextField(unique=True, index=True) clave_sat = TextField(default='') descripcion = TextField(index=True) unidad = ForeignKeyField(SATUnidades) valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) ultimo_costo = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) inventario = BooleanField(default=False) existencia = DecimalField(default=0.0, max_digits=18, decimal_places=2, auto_round=True) minimo = DecimalField(default=0.0, max_digits=18, decimal_places=2, auto_round=True) codigo_barras = TextField(default='') cuenta_predial = TextField(default='') es_activo = BooleanField(default=True) impuestos = ManyToManyField(SATImpuestos, related_name='productos') tags = ManyToManyField(Tags, related_name='productos_tags') class Meta: order_by = ('descripcion',) @classmethod def next_key(cls): value = (Productos .select(fn.Max(Productos.id).alias('fm')) .order_by(SQL('fm')) .scalar()) if value is None: value = 1 else: value += 1 return {'value': value} @classmethod def get_by_key(cls, values): clave = values.get('key', '') 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.clave==clave) | (Productos.codigo_barras==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} @classmethod def get_by(cls, values): clave = values.get('id', '') if clave: row = (Productos .select( Productos.id, Productos.clave, Productos.clave_sat, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario, Productos.descuento) .join(SATUnidades).switch(Productos) .where((Productos.id==clave) | (Productos.clave==clave)) .dicts()) if len(row): id = row[0]['id'] model_pt = Productos.impuestos.get_through_model() taxes = tuple(model_pt .select( model_pt.productos_id.alias('product'), model_pt.satimpuestos_id.alias('tax')) .where(model_pt.productos_id==id).dicts()) return {'ok': True, 'row': row[0], 'taxes': taxes} return {'ok': False} name = values.get('name', '') if name: rows = (Productos .select( Productos.id, Productos.clave, Productos.clave_sat, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario) .join(SATUnidades) .switch(Productos) .where((Productos.descripcion.contains(name)) | (Productos.clave.contains(name))) .dicts() ) return tuple(rows) return {'ok': False} @classmethod def get_(cls, values): if values: id = int(values['id']) row = (Productos .select( Productos.id, Productos.es_activo.alias('es_activo_producto'), Productos.categoria, Productos.clave, Productos.clave_sat, Productos.descripcion, Productos.unidad, Productos.valor_unitario, Productos.cuenta_predial, Productos.inventario, Productos.existencia, Productos.minimo, ) .where(Productos.id==id).dicts()[0] ) obj = Productos.get(Productos.id==id) taxes = [row.id for row in obj.impuestos] return {'row': row, 'taxes': taxes} rows = (Productos .select( Productos.id, Productos.clave_sat, Productos.clave, Productos.descripcion, SATUnidades.name.alias('unidad'), Productos.valor_unitario) .join(SATUnidades) .dicts() ) return {'ok': True, 'rows': tuple(rows)} def _clean(self, values): taxes = util.loads(values.pop('taxes')) descripcion = util.spaces(values.pop('descripcion')) fields = util.clean(values) 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, 'clave': obj.clave, 'descripcion': obj.descripcion, 'unidad': obj.unidad.name, 'valor_unitario': obj.valor_unitario, } data = {'ok': True, 'row': row, 'new': True} return data @classmethod def actualizar(cls, values, id): if not 'cuenta_predial' in values: values['cuenta_predial'] = '' fields, taxes = cls._clean(cls, values) obj_taxes = SATImpuestos.select().where(SATImpuestos.id.in_(taxes)) with database_proxy.transaction(): q = Productos.update(**fields).where(Productos.id==id) try: q.execute() except IntegrityError: msg = 'Ya existe un producto con esta clave' data = {'ok': False, 'row': {}, 'new': False, 'msg': msg} return data obj = Productos.get(Productos.id==id) obj.impuestos = obj_taxes row = { 'id': obj.id, 'clave': obj.clave, 'descripcion': obj.descripcion, 'unidad': obj.unidad.name, 'valor_unitario': obj.valor_unitario, } data = {'ok': True, 'row': row, 'new': False} return data @classmethod def remove(cls, id): count = (FacturasDetalle .select(fn.COUNT(FacturasDetalle.id)).join(Productos) .where(Productos.id==id) .count() ) if count: return False with database_proxy.transaction(): obj = Productos.get(Productos.id==id) obj.impuestos.clear() obj.tags.clear() q = Productos.delete().where(Productos.id==id) return bool(q.execute()) class RangosPrecios(BaseModel): producto = ForeignKeyField(Productos) descripcion = TextField(default='') desde = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) hasta = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) descuento = IntegerField(default=0) class Meta: order_by = ('producto',) class Facturas(BaseModel): cliente = ForeignKeyField(Socios) version = TextField(default=CURRENT_CFDI) serie = TextField(default='') folio = IntegerField(default=0) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) fecha_timbrado = DateTimeField(null=True) forma_pago = TextField(default='') condiciones_pago = TextField(default='') subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) moneda = TextField(default='MXN') tipo_cambio = DecimalField(default=1.0, max_digits=15, decimal_places=6, auto_round=True) total = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) tipo_comprobante = TextField(default='I') metodo_pago = TextField(default='PUE') lugar_expedicion = TextField(default='') confirmacion = TextField(default='') uso_cfdi = TextField(default='') total_retenciones = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) total_trasladados = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) xml = TextField(default='') uuid = UUIDField(null=True) estatus = TextField(default='Guardada') estatus_sat = TextField(default='Vigente') regimen_fiscal = TextField(default='') notas = TextField(default='') saldo = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) pagada = BooleanField(default=False) cancelada = BooleanField(default=False) fecha_cancelacion = DateTimeField(null=True) acuse = TextField(default='') donativo = BooleanField(default=False) anticipo = BooleanField(default=False) egreso_anticipo = BooleanField(default=False) tipo_relacion = TextField(default='') error = TextField(default='') class Meta: order_by = ('fecha',) @classmethod def cancel(cls, id): if CANCEL_SIGNATURE: return cls._cancel_signature(cls, id) return cls._cancel_xml(cls, id) def _cancel_xml(self, id): msg = 'Factura cancelada correctamente' auth = Emisor.get_auth() certificado = Certificado.select()[0] obj = Facturas.get(Facturas.id==id) 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['Fecha'] obj.acuse = result['Acuse'] self._actualizar_saldo_cliente(self, obj, True) else: obj.error = data['msg'] obj.save() return data def _cancel_signature(self, id): msg = 'Factura cancelada correctamente' auth = Emisor.get_auth() certificado = Certificado.select()[0] obj = Facturas.get(Facturas.id==id) data, result = util.cancel_signature( obj.uuid, certificado.p12, certificado.rfc, auth) if data['ok']: obj.estatus = 'Cancelada' obj.error = '' obj.cancelada = True obj.fecha_cancelacion = result['Fecha'] obj.acuse = result['Acuse'] self._actualizar_saldo_cliente(self, obj, True) else: obj.error = data['msg'] obj.save() return data @classmethod def filter_years(cls): data = [{'id': -1, 'value': 'Todos'}] rows = (Facturas .select(Facturas.fecha.year.alias('year')) .group_by(Facturas.fecha.year) .order_by(Facturas.fecha.year) ) if not rows is None: data += [{'id': int(r.year), 'value': int(r.year)} for r in rows] return tuple(data) @classmethod def get_xml(cls, id): obj = Facturas.get(Facturas.id==id) name = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc) cls._sync_xml(cls, obj) return obj.xml, name #~ Revisar def _get_data_cfdi_to_pdf(self, xml, cancel, version): pre_nomina = PRE['NOMINA'][version] data['nomina'] = {} node = doc.find('{}Complemento/{}Nomina'.format(pre, pre_nomina)) if not node is None: data['nomina']['nomina'] = node.attrib.copy() subnode = node.find('{}Emisor'.format(pre_nomina)) if not subnode is None: data['emisor'].update(subnode.attrib.copy()) subnode = node.find('{}Receptor'.format(pre_nomina)) data['receptor'].update(subnode.attrib.copy()) subnode = node.find('{}Percepciones'.format(pre_nomina)) data['nomina']['percepciones'] = subnode.attrib.copy() detalle = [] for n in subnode.getchildren(): if 'SeparacionIndemnizacion' in n.tag: continue detalle.append(n.attrib.copy()) data['nomina']['percepciones']['detalle'] = detalle data['nomina']['deducciones'] = None subnode = node.find('{}Deducciones'.format(pre_nomina)) if not subnode is None: data['nomina']['deducciones'] = subnode.attrib.copy() detalle = [] for n in subnode.getchildren(): detalle.append(n.attrib.copy()) data['nomina']['deducciones']['detalle'] = detalle data['nomina']['incapacidades'] = None subnode = node.find('{}Incapacidades'.format(pre_nomina)) if not subnode is None: detalle = [] for n in subnode.getchildren(): detalle.append(n.attrib.copy()) data['nomina']['incapacidades'] = detalle data['nomina']['otrospagos'] = None subnode = node.find('{}OtrosPagos'.format(pre_nomina)) if not subnode is None: data['nomina']['otrospagos'] = subnode.attrib.copy() detalle = [] for n in subnode.getchildren(): detalle.append(n.attrib.copy()) ns = n.find('{}SubsidioAlEmpleo'.format(pre_nomina)) if not ns is None: data['nomina']['otrospagos']['SubsidioCausado'] = ns.attrib['SubsidioCausado'] data['nomina']['otrospagos']['detalle'] = detalle return data def _get_not_in_xml(self, invoice, emisor): values = {} values['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', } values['metododepago'] = 'Método de Pago: ({}) {}'.format( invoice.metodo_pago, mp[invoice.metodo_pago]) obj = SATFormaPago.get(SATFormaPago.key==invoice.forma_pago) values['formadepago'] = str(obj) obj = SATMonedas.get(SATMonedas.key==invoice.moneda) values['moneda'] = str(obj) if invoice.tipo_relacion: obj = SATTipoRelacion.get(SATTipoRelacion.key==invoice.tipo_relacion) values['tiporelacion'] = str(obj) return values @classmethod def get_pdf(cls, id, rfc, sync=True): try: emisor = Emisor.select()[0] except IndexError: return b'', 'sin_datos_de_emisor.pdf' obj = Facturas.get(Facturas.id==id) name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc) if obj.uuid is None: return b'', name values = cls._get_not_in_xml(cls, obj, emisor) data = util.get_data_from_xml(obj, values) doc = util.to_pdf(data, emisor.rfc) if sync: target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/') cls._sync_pdf(cls, doc, name, target) return doc, name @classmethod def get_zip(cls, id, rfc): obj = Facturas.get(Facturas.id==id) name_zip = '{}{}_{}.zip'.format(obj.serie, obj.folio, obj.cliente.rfc) if obj.uuid is None: return b'', name_zip file_xml = cls.get_xml(id) if not file_xml[0]: return b'', name_zip file_pdf = cls.get_pdf(id, rfc) if not file_pdf[0]: return b'', name_zip file_zip = util.to_zip(file_xml, file_pdf) return file_zip, name_zip @util.run_in_thread def _send(self, id, rfc): return Facturas.send(id, rfc) @util.run_in_thread def _sync(self, id, auth): return Facturas.sync(id, auth) @util.run_in_thread def _sync_pdf(self, pdf, name_pdf, target): auth = Emisor.get_auth() files = ( (pdf, name_pdf, target), ) util.sync_cfdi(auth, files) return @util.run_in_thread def _sync_xml(self, obj): emisor = Emisor.select()[0] auth = Emisor.get_auth() name_xml = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc) target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/') files = ( (obj.xml, name_xml, target), ) util.sync_cfdi(auth, files) return @util.run_in_thread def _actualizar_saldo_cliente(self, invoice, cancel=False): if invoice.tipo_comprobante == 'T': return if invoice.donativo and invoice.forma_pago == '12': return importe = invoice.total_mn if invoice.tipo_comprobante == 'E': importe *= -1 if cancel: importe *= -1 q = (Socios .update(saldo_cliente=Socios.saldo_cliente + importe) .where(Socios.id==invoice.cliente.id) ) return bool(q.execute()) @classmethod def send(cls, id, rfc): values = Configuracion.get_({'fields': 'correo'}) in_zip = Configuracion.get_bool('chk_config_send_zip') if not values: msg = 'No esta configurado el servidor de correo de salida' return {'ok': False, 'msg': msg} obj = Facturas.get(Facturas.id==id) if obj.uuid is None: msg = 'La factura no esta timbrada' return {'ok': False, 'msg': msg} if not obj.cliente.correo_facturas: msg = 'El cliente no tiene configurado el correo para facturas' return {'ok': False, 'msg': msg} if in_zip: files = (cls.get_zip(id, rfc),) else: files = (cls.get_pdf(id, rfc), cls.get_xml(id)) fields = util.make_fields(obj.xml) server = { 'servidor': values['correo_servidor'], 'puerto': values['correo_puerto'], 'ssl': bool(int(values['correo_ssl'])), 'usuario': values['correo_usuario'], 'contra': values['correo_contra'], } options = { 'para': obj.cliente.correo_facturas, 'copia': values['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, auth): obj = Facturas.get(Facturas.id==id) if obj.uuid is None: msg = 'La factura no esta timbrada' return emisor = Emisor.select()[0] pdf, name_pdf = cls.get_pdf(id, auth['RFC'], False) name_xml = '{}{}_{}.xml'.format(obj.serie, obj.folio, obj.cliente.rfc) target = emisor.rfc + '/' + str(obj.fecha)[:7].replace('-', '/') files = ( (obj.xml, name_xml, target), (pdf, name_pdf, target), ) util.sync_cfdi(auth, files) return def _get_filter_folios(self, values): if not 'folio' in values: return '' folios = values['folio'].split('-') if len(folios) == 1: try: folio1 = int(folios[0]) except ValueError: return '' folio2 = folio1 else: try: folio1 = int(folios[0]) folio2 = int(folios[1]) except ValueError: return '' return (Facturas.folio.between(folio1, folio2)) def _get_por_pagar(self, ids): filtros = ( (Facturas.cancelada==False) & (Facturas.uuid.is_null(False)) & (Facturas.tipo_comprobante=='I') & (Facturas.saldo>0) ) if ids: filtros &= (Facturas.id.not_in(ids)) rows = tuple(Facturas .select( Facturas.id, Facturas.serie, Facturas.folio, Facturas.uuid, Facturas.fecha, Facturas.tipo_comprobante, Facturas.estatus, Socios.nombre.alias('cliente'), Facturas.total, Facturas.saldo, ) .where(filtros) .join(Socios) .switch(Facturas) .dicts() ) return {'ok': True, 'rows': rows} return def _get_opt(self, values): if values['opt'] == 'porpagar': return self._get_por_pagar(self, util.loads(values['ids'])) cfdis = util.loads(values['cfdis']) if values['year'] == '-1': fy = (Facturas.fecha.year > 0) else: fy = (Facturas.fecha.year == int(values['year'])) if values['month'] == '-1': fm = (Facturas.fecha.month > 0) else: fm = (Facturas.fecha.month == int(values['month'])) if values['opt'] == 'relacionados': folios = self._get_filter_folios(self, values) uuid = values.get('uuid', '') if uuid: f_uuid = (cast(Facturas.uuid, 'text').contains(uuid)) cliente = (Facturas.cliente == int(values['id_cliente'])) if cfdis: f_ids = (Facturas.id.not_in(cfdis)) else: f_ids = (Facturas.id > 0) filters = (fy & fm & cliente & f_ids) if folios: filters = filters & folios elif uuid: filters = filters & f_uuid if values['anticipo'] == '1': filters = filters & (Facturas.anticipo == True) rows = tuple(Facturas .select(Facturas.id, Facturas.serie, Facturas.folio, Facturas.uuid, Facturas.fecha, Facturas.tipo_comprobante, Facturas.estatus, Facturas.total_mn) .where(filters).dicts() ) return {'ok': True, 'rows': rows} @classmethod def get_(cls, values): opt = values.get('opt', '') if opt: return cls._get_opt(cls, values) if 'start' in values: filters = Facturas.fecha.between( util.get_date(values['start']), util.get_date(values['end'], True) ) else: if values['year'] == '-1': fy = (Facturas.fecha.year > 0) else: fy = (Facturas.fecha.year == int(values['year'])) if values['month'] == '-1': fm = (Facturas.fecha.month > 0) else: fm = (Facturas.fecha.month == int(values['month'])) filters = (fy & fm) rows = tuple(Facturas .select(Facturas.id, Facturas.serie, Facturas.folio, Facturas.uuid, Facturas.fecha, Facturas.tipo_comprobante, Facturas.estatus, Facturas.total_mn, Socios.nombre.alias('cliente')) .where(filters) .join(Socios) .switch(Facturas).dicts() ) return {'ok': True, 'rows': rows} @classmethod def remove(cls, id): obj = Facturas.get(Facturas.id==id) if obj.uuid: return False q = FacturasDetalle.delete().where(FacturasDetalle.factura==obj) q.execute() q = FacturasImpuestos.delete().where(FacturasImpuestos.factura==obj) q.execute() q = FacturasRelacionadas.delete().where(FacturasRelacionadas.factura==obj) q.execute() return bool(obj.delete_instance()) def _get_folio(self, serie): inicio_serie = Folios.select( Folios.inicio).where(Folios.serie==serie).scalar() inicio = (Facturas .select(fn.Max(Facturas.folio).alias('mf')) .where(Facturas.serie==serie) .order_by(SQL('mf')) .scalar()) if inicio is None: inicio = inicio_serie else: inicio += 1 return inicio def _calculate_totals(self, invoice, products): subtotal = 0 descuento_cfdi = 0 totals_tax = {} total_trasladados = None total_retenciones = None # ~ total_iva = 0 locales_traslados = 0 locales_retenciones = 0 for product in products: # ~ print ('\n', product['descripcion']) id_product = product.pop('id') p = Productos.get(Productos.id==id_product) product['unidad'] = p.unidad.key product['clave'] = p.clave product['clave_sat'] = p.clave_sat product['cuenta_predial'] = p.cuenta_predial product['factura'] = invoice.id product['producto'] = id_product cantidad = float(product['cantidad']) valor_unitario = float(product['valor_unitario']) descuento = float(product['descuento']) precio_final = valor_unitario - descuento importe = round(cantidad * precio_final, DECIMALES) product['cantidad'] = cantidad product['valor_unitario'] = valor_unitario product['descuento'] = round(descuento * cantidad, DECIMALES) product['precio_final'] = precio_final product['importe'] = round(cantidad * valor_unitario, DECIMALES) descuento_cfdi += product['descuento'] subtotal += product['importe'] FacturasDetalle.create(**product) base = product['importe'] - product['descuento'] for tax in p.impuestos: impuesto_producto = round(float(tax.tasa) * base, DECIMALES) if tax.tipo == 'T' and tax.key != '000': total_trasladados = (total_trasladados or 0) + impuesto_producto elif tax.tipo == 'R' and tax.key != '000': total_retenciones = (total_retenciones or 0) + impuesto_producto elif tax.tipo == 'T' and tax.key == '000': locales_traslados += impuesto_producto elif tax.tipo == 'R' and tax.key == '000': locales_retenciones += impuesto_producto if tax.id in totals_tax: totals_tax[tax.id].base += base totals_tax[tax.id].suma_impuestos += impuesto_producto else: tax.base = base tax.suma_impuestos = impuesto_producto totals_tax[tax.id] = tax for tax in totals_tax.values(): if tax.tipo == 'E': 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) \ + locales_traslados - locales_retenciones total_mn = round(total * invoice.tipo_cambio, DECIMALES) data = { 'subtotal': subtotal, 'descuento': descuento_cfdi, 'total': total, 'total_mn': total_mn, 'total_trasladados': total_trasladados, 'total_retenciones': total_retenciones, } return data def _guardar_relacionados(self, invoice, relacionados): for cfdi in relacionados: data = { 'factura': invoice, 'factura_origen': cfdi, } FacturasRelacionadas.create(**data) return def _guardar_ine(self, invoice, valores): if not valores: return data = { 'factura': invoice, 'nombre': 'ine', 'valores': valores, } FacturasComplementos.create(**data) return 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', {}) emisor = Emisor.select()[0] values['serie'] = cls._get_serie(cls, user, values['serie']) values['folio'] = cls._get_folio(cls, values['serie']) values['tipo_cambio'] = float(values['tipo_cambio']) values['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal values['anticipo'] = util.get_bool(values['anticipo']) values['donativo'] = util.get_bool(values['donativo']) with database_proxy.atomic() as txn: obj = Facturas.create(**values) totals = cls._calculate_totals(cls, obj, productos) cls._guardar_relacionados(cls, obj, relacionados) cls._guardar_ine(cls, obj, ine) obj.subtotal = totals['subtotal'] obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] obj.total_retenciones = totals['total_retenciones'] obj.total = totals['total'] obj.saldo = totals['total'] obj.total_mn = totals['total_mn'] obj.save() msg = 'Factura guardada correctamente. Enviando a timbrar' row = { 'id': obj.id, 'serie': obj.serie, 'folio': obj.folio, 'uuid': obj.uuid, 'fecha': obj.fecha, 'tipo_comprobante': obj.tipo_comprobante, 'estatus': obj.estatus, 'total_mn': obj.total_mn, 'cliente': obj.cliente.nombre, } data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg} return data def _make_xml(self, invoice, auth): emisor = Emisor.select()[0] certificado = Certificado.select()[0] comprobante = {} relacionados = {} donativo = {} complementos = FacturasComplementos.get_(invoice) if invoice.donativo: donativo['noAutorizacion'] = emisor.autorizacion donativo['fechaAutorizacion'] = str(emisor.fecha_autorizacion) if invoice.serie: comprobante['Serie'] = invoice.serie if invoice.condiciones_pago: comprobante['CondicionesDePago'] = invoice.condiciones_pago if invoice.descuento: comprobante['Descuento'] = invoice.descuento comprobante['Folio'] = str(invoice.folio) comprobante['Fecha'] = invoice.fecha.isoformat()[:19] comprobante['FormaPago'] = invoice.forma_pago comprobante['NoCertificado'] = certificado.serie comprobante['Certificado'] = certificado.cer_txt comprobante['SubTotal'] = FORMAT.format(invoice.subtotal) comprobante['Moneda'] = invoice.moneda comprobante['TipoCambio'] = '1' if comprobante['Moneda'] != 'MXN': comprobante['TipoCambio'] = FORMAT.format(invoice.tipo_cambio) comprobante['Total'] = FORMAT.format(invoice.total) comprobante['TipoDeComprobante'] = invoice.tipo_comprobante comprobante['MetodoPago'] = invoice.metodo_pago comprobante['LugarExpedicion'] = invoice.lugar_expedicion if invoice.descuento: comprobante['Descuento'] = FORMAT.format(invoice.descuento) if invoice.tipo_relacion: relacionados = { 'tipo': invoice.tipo_relacion, 'cfdis': FacturasRelacionadas.get_(invoice), } emisor = { 'Rfc': emisor.rfc, 'Nombre': emisor.nombre, 'RegimenFiscal': invoice.regimen_fiscal, } receptor = { 'Rfc': invoice.cliente.rfc, 'Nombre': invoice.cliente.nombre, 'UsoCFDI': invoice.uso_cfdi, } conceptos = [] rows = FacturasDetalle.select().where(FacturasDetalle.factura==invoice) for row in rows: concepto = { 'ClaveProdServ': row.producto.clave_sat, 'NoIdentificacion': row.producto.clave, 'Cantidad': FORMAT.format(row.cantidad), 'ClaveUnidad': row.producto.unidad.key, 'Unidad': row.producto.unidad.name, 'Descripcion': row.descripcion, 'ValorUnitario': FORMAT.format(row.valor_unitario), 'Importe': FORMAT.format(row.importe), } if row.descuento: concepto['Descuento'] = FORMAT.format(row.descuento) if row.cuenta_predial: concepto['CuentaPredial'] = row.cuenta_predial if row.pedimento: concepto['Pedimento'] = row.pedimento taxes = {} traslados = [] retenciones = [] for impuesto in row.producto.impuestos: if impuesto.tipo == 'E': continue if impuesto.key == '000': continue base = row.importe - row.descuento import_tax = round(impuesto.tasa * base, DECIMALES) tipo_factor = 'Tasa' if impuesto.factor != 'T': tipo_factor = 'Cuota' tax = { "Base": FORMAT.format(base), "Impuesto": impuesto.key, "TipoFactor": tipo_factor, "TasaOCuota": str(impuesto.tasa), "Importe": FORMAT.format(import_tax), } if impuesto.tipo == 'T': traslados.append(tax) else: retenciones.append(tax) if traslados: taxes['traslados'] = traslados if retenciones: taxes['retenciones'] = retenciones concepto['impuestos'] = taxes conceptos.append(concepto) impuestos = {} traslados = [] retenciones = [] total_locales_trasladados = 0 total_locales_retenciones = 0 locales_trasladados = [] locales_retenciones = [] if not invoice.total_trasladados is None: impuestos['TotalImpuestosTrasladados'] = \ FORMAT.format(invoice.total_trasladados) if not invoice.total_retenciones is None: impuestos['TotalImpuestosRetenidos'] = \ FORMAT.format(invoice.total_retenciones) taxes = (FacturasImpuestos .select() .where(FacturasImpuestos.factura==invoice)) for tax in taxes: if tax.impuesto.key == '000': tasa = str(round(tax.impuesto.tasa * 100, 2)) simporte = FORMAT.format(tax.importe) if tax.impuesto.tipo == 'T': traslado = { 'ImpLocTrasladado': tax.impuesto.name, 'TasadeTraslado': tasa, 'Importe': simporte, } locales_trasladados.append(traslado) total_locales_trasladados += tax.importe else: retencion = { 'ImpLocRetenido': tax.impuesto.name, 'TasadeRetencion': tasa, 'Importe': simporte, } locales_retenciones.append(retencion) total_locales_retenciones += tax.importe continue tipo_factor = 'Tasa' if tax.impuesto.factor != 'T': tipo_factor = 'Cuota' if tax.impuesto.tipo == 'T': traslado = { "Impuesto": tax.impuesto.key, "TipoFactor": tipo_factor, "TasaOCuota": str(tax.impuesto.tasa), "Importe": FORMAT.format(tax.importe), } traslados.append(traslado) else: retencion = { "Impuesto": tax.impuesto.key, "Importe": FORMAT.format(tax.importe), } retenciones.append(retencion) impuestos['traslados'] = traslados impuestos['retenciones'] = retenciones impuestos['total_locales_trasladados'] = '' if total_locales_trasladados: impuestos['total_locales_trasladados'] = \ FORMAT.format(total_locales_trasladados) impuestos['total_locales_retenciones'] = '' if total_locales_retenciones: impuestos['total_locales_retenciones'] = \ FORMAT.format(total_locales_retenciones) impuestos['locales_trasladados'] = locales_trasladados impuestos['locales_retenciones'] = locales_retenciones data = { 'comprobante': comprobante, 'relacionados': relacionados, 'emisor': emisor, 'receptor': receptor, 'conceptos': conceptos, 'impuestos': impuestos, 'donativo': donativo, 'complementos': complementos, } return util.make_xml(data, certificado, auth) @classmethod def get_status_sat(cls, id): obj = Facturas.get(Facturas.id == id) obj.estatus_sat = util.get_sat(obj.xml) obj.save() return obj.estatus_sat @classmethod def anticipo_egreso(cls, id): origen = Facturas.get(Facturas.id == id) relacionadas = (FacturasRelacionadas .select(FacturasRelacionadas.factura_origen) .where(FacturasRelacionadas.factura==origen)) conceptos = (FacturasDetalle .select() .where(FacturasDetalle.factura==origen)) impuestos = (FacturasImpuestos .select() .where(FacturasImpuestos.factura==origen)) #~ egreso_anticipo = BooleanField(default=False) serie = Folios.get_egreso(origen.serie) nueva = { 'cliente': origen.cliente, 'tipo_comprobante': 'E', 'forma_pago': '30', 'serie': serie, 'folio': cls._get_folio(cls, serie), 'tipo_relacion': '07', 'pagada': True, 'lugar_expedicion': origen.lugar_expedicion, 'uso_cfdi': origen.uso_cfdi, 'moneda': origen.moneda, 'tipo_cambio': origen.tipo_cambio, 'regimen_fiscal': origen.regimen_fiscal, 'subtotal': origen.subtotal, 'total': origen.total, 'total_trasladados': origen.total_trasladados, 'total_retenciones': origen.total_retenciones, } return @classmethod def timbrar(cls, id): auth = Emisor.get_auth() obj = Facturas.get(Facturas.id == id) obj.xml = cls._make_xml(cls, obj, auth) obj.estatus = 'Generada' obj.save() enviar_correo = util.get_bool(Configuracion.get_('correo_directo')) anticipo = False msg = 'Factura timbrada correctamente' result = util.timbra_xml(obj.xml, auth) # ~ print (result) if result['ok']: obj.xml = result['xml'] obj.uuid = result['uuid'] obj.fecha_timbrado = result['fecha'] obj.estatus = 'Timbrada' obj.error = '' obj.save() row = {'uuid': obj.uuid, 'estatus': 'Timbrada'} if enviar_correo: cls._send(cls, id, auth['RFC']) if obj.tipo_comprobante == 'I' and obj.tipo_relacion == '07': anticipo = True cls._actualizar_saldo_cliente(cls, obj) cls._sync(cls, id, auth) else: msg = result['error'] obj.estatus = 'Error' obj.error = msg obj.save() row = {'estatus': 'Error'} result = { 'ok': result['ok'], 'msg': msg, 'row': row, 'anticipo': anticipo } return result class PreFacturas(BaseModel): cliente = ForeignKeyField(Socios) serie = TextField(default='PRE') folio = IntegerField(default=0) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) forma_pago = TextField(default='') condiciones_pago = TextField(default='') subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) moneda = TextField(default='MXN') tipo_cambio = DecimalField(default=1.0, decimal_places=6, auto_round=True) total = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_mn = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) tipo_comprobante = TextField(default='I') metodo_pago = TextField(default='PUE') lugar_expedicion = TextField(default='') uso_cfdi = TextField(default='') total_retenciones = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) total_trasladados = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) estatus = TextField(default='Generada') regimen_fiscal = TextField(default='') notas = TextField(default='') donativo = BooleanField(default=False) tipo_relacion = TextField(default='') class Meta: order_by = ('fecha',) @classmethod def enviar(cls, id): values = Configuracion.get_({'fields': 'correo'}) if not values: msg = 'No esta configurado el servidor de correo de salida' return {'ok': False, 'msg': msg} obj = PreFacturas.get(PreFacturas.id==id) if not obj.cliente.correo_facturas: msg = 'El cliente no tiene configurado el correo para facturas' return {'ok': False, 'msg': msg} files = (cls.get_pdf(id),) invoice = PreFacturas.select().where(PreFacturas.id==id).dicts()[0] fields = { 'receptor_nombre': obj.cliente.nombre, 'receptor_rfc': obj.cliente.rfc, } fields.update(invoice) asunto = 'Enviamos la prefactura: PRE-{}'.format(obj.folio) server = { 'servidor': values['correo_servidor'], 'puerto': values['correo_puerto'], 'ssl': bool(int(values['correo_ssl'])), 'usuario': values['correo_usuario'], 'contra': values['correo_contra'], } options = { 'para': obj.cliente.correo_facturas, 'copia': values['correo_copia'], '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 @classmethod def get_pdf(cls, id): obj = PreFacturas.get(PreFacturas.id==id) name = '{}{}_{}.pdf'.format(obj.serie, obj.folio, obj.cliente.rfc) data = cls._get_info_to_pdf(cls, id) doc = util.to_pdf(data, data['emisor']['rfc']) return doc, name @classmethod def remove(cls, id): obj = PreFacturas.get(PreFacturas.id==id) q = PreFacturasDetalle.delete().where( PreFacturasDetalle.factura==obj) q.execute() q = PreFacturasImpuestos.delete().where( PreFacturasImpuestos.factura==obj) q.execute() q = PreFacturasRelacionadas.delete().where( PreFacturasRelacionadas.factura==obj) q.execute() return bool(obj.delete_instance()) @classmethod def filter_years(cls): data = [{'id': -1, 'value': 'Todos'}] rows = (PreFacturas .select(PreFacturas.fecha.year) .group_by(PreFacturas.fecha.year) .order_by(PreFacturas.fecha.year) .scalar(as_tuple=True) ) if not rows is None: data += [{'id': int(row), 'value': int(row)} for row in rows] return tuple(data) @classmethod def get_(cls, values): if values['year'] == '-1': fy = (PreFacturas.fecha.year > 0) else: fy = (PreFacturas.fecha.year == int(values['year'])) if values['month'] == '-1': fm = (PreFacturas.fecha.month > 0) else: fm = (PreFacturas.fecha.month == int(values['month'])) filters = (fy & fm) rows = tuple(PreFacturas .select( PreFacturas.id, PreFacturas.folio, PreFacturas.fecha, PreFacturas.tipo_comprobante, PreFacturas.total_mn, Socios.nombre.alias('cliente')) .where(filters) .join(Socios) .switch(PreFacturas).dicts() ) return {'ok': True, 'rows': rows} def _get_folio(self, serie): inicio = (PreFacturas .select(fn.Max(PreFacturas.folio).alias('mf')) .where(PreFacturas.serie==serie) .order_by(SQL('mf')) .scalar()) if inicio is None: inicio = 1 else: inicio += 1 return inicio def _calculate_totals(self, invoice, products): subtotal = 0 descuento_cfdi = 0 totals_tax = {} total_trasladados = None total_retenciones = None total_iva = 0 for product in products: id_product = product.pop('id') p = Productos.get(Productos.id==id_product) product['unidad'] = p.unidad.key product['clave'] = p.clave product['clave_sat'] = p.clave_sat product['cuenta_predial'] = p.cuenta_predial product['factura'] = invoice.id product['producto'] = id_product cantidad = float(product['cantidad']) valor_unitario = float(product['valor_unitario']) descuento = float(product['descuento']) precio_final = valor_unitario - descuento importe = round(cantidad * precio_final, DECIMALES) product['cantidad'] = cantidad product['valor_unitario'] = valor_unitario product['descuento'] = descuento product['precio_final'] = precio_final product['importe'] = round(cantidad * valor_unitario, DECIMALES) descuento_cfdi += descuento subtotal += importe PreFacturasDetalle.create(**product) for tax in p.impuestos: if tax.id in totals_tax: totals_tax[tax.id].importe += importe else: tax.importe = importe totals_tax[tax.id] = tax for tax in totals_tax.values(): if tax.tipo == 'E' or tax.tipo == 'R': continue import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) total_trasladados = (total_trasladados or 0) + import_tax if tax.name == 'IVA': total_iva += import_tax invoice_tax = { 'factura': invoice.id, 'impuesto': tax.id, 'base': tax.importe, 'importe': import_tax, } PreFacturasImpuestos.create(**invoice_tax) for tax in totals_tax.values(): if tax.tipo == 'E' or tax.tipo == 'T': continue if tax.tasa == round(Decimal(2/3), 6): import_tax = round(float(tax.tasa) * total_iva, DECIMALES) else: import_tax = round(float(tax.tasa) * tax.importe, DECIMALES) total_retenciones = (total_retenciones or 0) + import_tax invoice_tax = { 'factura': invoice.id, 'impuesto': tax.id, 'base': tax.importe, 'importe': import_tax, } PreFacturasImpuestos.create(**invoice_tax) total = subtotal + (total_trasladados or 0) - (total_retenciones or 0) total_mn = round(total * invoice.tipo_cambio, DECIMALES) data = { 'subtotal': subtotal + descuento, 'descuento': descuento_cfdi, 'total': total, 'total_mn': total_mn, 'total_trasladados': total_trasladados, 'total_retenciones': total_retenciones, } return data @classmethod def add(cls, values): productos = util.loads(values.pop('productos')) emisor = Emisor.select()[0] values['serie'] = 'PRE' values['folio'] = cls._get_folio(cls, values['serie']) values['tipo_cambio'] = float(values['tipo_cambio']) values['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal with database_proxy.atomic() as txn: obj = PreFacturas.create(**values) totals = cls._calculate_totals(cls, obj, productos) obj.subtotal = totals['subtotal'] obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] obj.total_retenciones = totals['total_retenciones'] obj.total = totals['total'] obj.total_mn = totals['total_mn'] obj.save() msg = 'Factura guardada correctamente' row = { 'id': obj.id, 'folio': obj.folio, 'fecha': obj.fecha, 'tipo_comprobante': obj.tipo_comprobante, 'total_mn': obj.total_mn, 'cliente': obj.cliente.nombre, } data = {'ok': True, 'row': row, 'new': True, 'error': False, 'msg': msg} return data class FacturasRelacionadas(BaseModel): factura = ForeignKeyField(Facturas, related_name='original') factura_origen = ForeignKeyField(Facturas, related_name='relacion') class Meta: order_by = ('factura',) @classmethod def get_(cls, invoice): query = (FacturasRelacionadas .select() .where(FacturasRelacionadas.factura==invoice) ) return [str(r.factura_origen.uuid) for r in query] class FacturasComplementos(BaseModel): factura = ForeignKeyField(Facturas) nombre = TextField(default='') valores = TextField(default='') class Meta: order_by = ('factura',) @classmethod def get_(cls, factura): query = (FacturasComplementos .select() .where(FacturasComplementos.factura==factura) ) return {r.nombre: util.loads(r.valores) for r in query} class PreFacturasRelacionadas(BaseModel): factura = ForeignKeyField(PreFacturas, related_name='original') factura_origen = ForeignKeyField(PreFacturas, related_name='relacion') class Meta: order_by = ('factura',) class FacturasDetalle(BaseModel): factura = ForeignKeyField(Facturas) producto = ForeignKeyField(Productos, null=True) cantidad = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) precio_final = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) descripcion = TextField(default='') unidad = TextField(default='') clave = TextField(default='') clave_sat = TextField(default='') categoria = TextField(default='') aduana = TextField(default='') pedimento = TextField(default='') fecha_pedimento = DateField(null=True) alumno = TextField(default='') curp = TextField(default='') nivel = TextField(default='') autorizacion = TextField(default='') cuenta_predial = TextField(default='') class Meta: order_by = ('factura',) class PreFacturasDetalle(BaseModel): factura = ForeignKeyField(PreFacturas) producto = ForeignKeyField(Productos, null=True) descripcion = TextField(default='') cantidad = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) precio_final = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) aduana = TextField(default='') pedimento = TextField(default='') fecha_pedimento = DateField(null=True) alumno = TextField(default='') curp = TextField(default='') nivel = TextField(default='') autorizacion = TextField(default='') cuenta_predial = TextField(default='') class Meta: order_by = ('factura',) def _get_impuestos(self, id): model_pt = Productos.impuestos.get_through_model() impuestos = tuple(model_pt .select( model_pt.productos_id.alias('product'), model_pt.satimpuestos_id.alias('tax')) .where(model_pt.productos_id==id).dicts()) return impuestos @classmethod def facturar(cls, id): data = [] q = PreFacturas.select().where(PreFacturas.id==id)[0] if q.cliente.forma_pago is None: forma_pago = '' else: forma_pago = q.cliente.forma_pago.key if q.cliente.uso_cfdi is None: uso_cfdi = '' else: uso_cfdi = q.cliente.uso_cfdi.key receptor = { 'id': q.cliente.id, 'nombre': q.cliente.nombre, 'rfc': q.cliente.rfc, 'forma_pago': forma_pago, 'uso_cfdi': uso_cfdi, 'notas': q.notas, } productos = PreFacturasDetalle.select().where( PreFacturasDetalle.factura==id) for p in productos: row = {'id': p.producto.id} row['clave'] = p.producto.clave row['descripcion'] = p.descripcion row['unidad'] = p.producto.unidad.name row['cantidad'] = p.cantidad row['valor_unitario'] = p.valor_unitario row['descuento'] = p.descuento pf = p.valor_unitario - p.descuento row['importe'] = round(pf * p.cantidad, DECIMALES) impuestos = cls._get_impuestos(cls, row['id']) data.append({'row': row, 'taxes': impuestos}) return {'rows': data, 'receptor': receptor} @classmethod def get_(cls, id): data = [] productos = PreFacturasDetalle.select().where( PreFacturasDetalle.factura==id) for p in productos: producto = {} producto['noidentificacion'] = '{}\n(SAT {})'.format( p.producto.clave, p.producto.clave_sat) producto['descripcion'] = p.descripcion if p.cuenta_predial: info = '\nCuenta Predial Número: {}'.format(p.cuenta_predial) producto['descripcion'] += info producto['unidad'] = '{}\n({})'.format( p.producto.unidad.name, p.producto.unidad.key) producto['cantidad'] = str(p.cantidad) producto['valorunitario'] = str(p.valor_unitario) producto['importe'] = str(p.importe) data.append(producto) return data class FacturasImpuestos(BaseModel): factura = ForeignKeyField(Facturas) impuesto = ForeignKeyField(SATImpuestos) base = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) class Meta: order_by = ('factura',) indexes = ( (('factura', 'impuesto'), True), ) class FacturasPagos(BaseModel): movimiento = ForeignKeyField(MovimientosBanco) factura = ForeignKeyField(Facturas) numero = IntegerField(default=1) saldo_anterior = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) saldo = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) class Meta: order_by = ('factura',) indexes = ( (('movimiento', 'factura', 'numero'), True), ) def _movimiento_anterior(self, mov, id): query = (FacturasPagos .select() .where(FacturasPagos.factura==id) ) if len(query): return query[-1], len(query) + 1 else: return None, 1 def _actualizar_saldo_cliente(self, cliente, importe): q = (Socios .update(saldo_cliente=Socios.saldo_cliente + importe) .where(Socios.id==cliente.id) ) return bool(q.execute()) def _actualizar_saldos(self, factura, saldo_anterior): query = (FacturasPagos .select() .where(FacturasPagos.factura==factura) ) saldo = saldo_anterior for i, row in enumerate(query): if not saldo_anterior: saldo_anterior = row.saldo_anterior row.numero = i + 1 row.saldo_anterior = saldo_anterior row.saldo = saldo_anterior - row.importe row.save() saldo_anterior = row.saldo saldo = row.saldo factura.saldo = saldo factura.pagada = False factura.save() return @classmethod def cancelar(cls, mov): query = (FacturasPagos .select() .where(FacturasPagos.movimiento==mov) ) for row in query: cls._actualizar_saldo_cliente(cls, row.factura.cliente, row.importe) factura = row.factura saldo_anterior = 0 if row.numero == 1: saldo_anterior = row.saldo_anterior row.delete_instance() cls._actualizar_saldos(cls, factura, saldo_anterior) return @classmethod def add(cls, mov, ids): for i, importe in ids.items(): fac = Facturas.get(Facturas.id==int(i)) mov_ant, numero = cls._movimiento_anterior(cls, mov, fac) nuevo = { 'movimiento': mov, 'factura': fac, 'numero': numero, 'importe': importe, } if mov_ant is None: nuevo['saldo_anterior'] = float(fac.saldo) else: nuevo['saldo_anterior'] = float(mov_ant.saldo) nuevo['saldo'] = nuevo['saldo_anterior'] - importe FacturasPagos.create(**nuevo) fac.saldo = nuevo['saldo'] if nuevo['saldo'] == 0: fac.pagada = True fac.save() cls._actualizar_saldo_cliente(cls, fac.cliente, importe * -1) return class PreFacturasImpuestos(BaseModel): factura = ForeignKeyField(PreFacturas) impuesto = ForeignKeyField(SATImpuestos) base = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) class Meta: order_by = ('factura',) indexes = ( (('factura', 'impuesto'), True), ) @classmethod def get_(cls, id): data = { 'traslados': [], 'retenciones': [], 'taxlocales': [], } taxes = PreFacturasImpuestos.select().where( PreFacturasImpuestos.factura==id) for tax in taxes: if tax.impuesto.tipo == 'T': title = 'Traslado {} {}'.format( tax.impuesto.name, str(tax.impuesto.tasa)) data['traslados'].append((title, str(tax.importe))) elif tax.impuesto.tipo == 'R': title = 'Retención {} {}'.format( tax.impuesto.name, str(tax.impuesto.tasa)) data['retenciones'].append((title, str(tax.importe))) return data class CamposPersonalizados(BaseModel): nombre = TextField() slug = TextField(unique=True) class Meta: order_by = ('nombre',) class FacturasPersonalizados(BaseModel): factura = ForeignKeyField(Facturas) campo = TextField() valor = TextField() class Meta: order_by = ('factura',) class Tickets(BaseModel): cliente = ForeignKeyField(Socios, null=True) serie = TextField(default='') folio = IntegerField(default=0) fecha = DateTimeField(default=util.now, formats=['%Y-%m-%d %H:%M:%S']) forma_pago = TextField(default='') subtotal = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) total_trasladados = DecimalField( max_digits=20, decimal_places=6, auto_round=True, null=True) estatus = TextField(default='Generado') notas = TextField(default='') factura = ForeignKeyField(Facturas, null=True) cancelado = BooleanField(default=False) vendedor = TextField(default='') comision = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) cambio = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class Meta: order_by = ('fecha',) def _get_folio(self, serie): inicio = (Tickets .select(fn.Max(Tickets.folio).alias('mf')) .where(Tickets.serie==serie) .order_by(SQL('mf')) .scalar()) if inicio is None: inicio = 1 else: inicio += 1 return inicio def _without_tax(self, importe, obj): # ~ Por ahora se asume que solo tiene IVA trasladado 0.16 for tax in obj.impuestos: tasa = 1.0 + float(tax.tasa) base = round(importe / tasa, DECIMALES) return base def _calcular_totales(self, ticket, productos): subtotal = 0 descuento_cfdi = 0 totals_tax = {} total_trasladados = None for producto in productos: id_producto = producto.pop('id') p = Productos.get(Productos.id==id_producto) producto['descripcion'] = p.descripcion producto['ticket'] = ticket.id producto['producto'] = id_producto cantidad = float(producto['cantidad']) valor_unitario = self._without_tax(self, producto['valor_unitario'], p) descuento = float(producto['descuento']) precio_final = valor_unitario - descuento importe = round(cantidad * precio_final, DECIMALES) producto['cantidad'] = cantidad producto['valor_unitario'] = valor_unitario producto['descuento'] = descuento producto['precio_final'] = precio_final producto['importe'] = importe descuento_cfdi += descuento subtotal += importe TicketsDetalle.create(**producto) if p.inventario: p.existencia -= Decimal(cantidad) p.save() base = producto['importe'] for tax in p.impuestos: impuesto_producto = round(float(tax.tasa) * base, DECIMALES) if tax.tipo == 'T' and tax.key != '000': total_trasladados = (total_trasladados or 0) + impuesto_producto if tax.id in totals_tax: totals_tax[tax.id].base += base totals_tax[tax.id].importe += impuesto_producto else: tax.base = base tax.importe = impuesto_producto totals_tax[tax.id] = tax for tax in totals_tax.values(): if tax.tipo == 'E': continue ticket_tax = { 'ticket': ticket.id, 'impuesto': tax.id, 'base': tax.base, 'importe': tax.importe, } TicketsImpuestos.create(**ticket_tax) total = subtotal + (total_trasladados or 0) data = { 'subtotal': subtotal + descuento, 'descuento': descuento_cfdi, 'total': total, 'total_trasladados': total_trasladados, } return data def _get_serie(self, user, invoice=False): default_serie = DEFAULT_SERIE_TICKET if invoice: default_serie = Folios.get_default() if user.sucursal is None: return default_serie if invoice: return user.sucursal.serie_facturas or default_serie else: return user.sucursal.serie_tickets or default_serie @classmethod def get_notes(cls, tickets): rows = Tickets.select(Tickets.notas).where(Tickets.id.in_(tickets)) return '\n'.join([r.notas for r in rows]) @classmethod def add(cls, values, user): productos = util.loads(values.pop('productos')) values['serie'] = cls._get_serie(cls, user) values['folio'] = cls._get_folio(cls, values['serie']) with database_proxy.atomic() as txn: obj = Tickets.create(**values) totals = cls._calcular_totales(cls, obj, productos) obj.subtotal = totals['subtotal'] obj.descuento = totals['descuento'] obj.total_trasladados = totals['total_trasladados'] obj.total = totals['total'] obj.save() row = { 'id': obj.id, 'serie': obj.serie, 'folio': obj.folio, 'fecha': obj.fecha, 'estatus': obj.estatus, 'total': obj.total, } data = {'ok': True, 'row': row} return data def _get_folio_invoice(self, serie): inicio = (Facturas .select(fn.Max(Facturas.folio).alias('mf')) .where(Facturas.serie==serie) .order_by(SQL('mf')) .scalar()) if inicio is None: inicio = 1 else: inicio += 1 return inicio 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 @classmethod def invoice(cls, values, user): is_invoice_day = util.get_bool(values['is_invoice_day']) id_client = int(values['client']) tickets = util.loads(values['tickets']) if is_invoice_day: filters = ( Socios.rfc == 'XAXX010101000' and Socios.slug == 'publico_en_general') try: client = Socios.get(filters) except Socios.DoesNotExist: msg = 'No existe el cliente Público en General. Agregalo primero.' data = {'ok': False, 'msg': msg} return data else: client = Socios.get(Socios.id==id_client) if client.forma_pago is None: msg = 'La Forma de Pago del cliente, no esta asignada' data = {'ok': False, 'msg': msg} return data emisor = Emisor.select()[0] data = {} data['cliente'] = client data['serie'] = cls._get_serie(cls, user, True) data['folio'] = cls._get_folio_invoice(cls, data['serie']) data['forma_pago'] = client.forma_pago.key data['tipo_cambio'] = 1.00 data['lugar_expedicion'] = emisor.cp_expedicion or emisor.codigo_postal if client.uso_cfdi is None: data['uso_cfdi'] = 'P01' else: data['uso_cfdi'] = client.uso_cfdi.key data['regimen_fiscal'] = emisor.regimenes[0].key with database_proxy.atomic() as txn: obj = Facturas.create(**data) 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 _update_inventory_if_cancel(self, id): products = TicketsDetalle.select().where(TicketsDetalle.ticket==id) for p in products: if p.producto.inventario: p.producto.existencia += p.cantidad p.producto.save() return @classmethod def cancel(cls, values): id = int(values['id']) msg = 'Ticket cancelado correctamente' u = {'cancelado': True, 'estatus': 'Cancelado'} with database_proxy.atomic() as txn: obj = Tickets.update(**u).where(Tickets.id==id) result = bool(obj.execute()) if result: cls._update_inventory_if_cancel(cls, id) row = {'estatus': 'Cancelado'} return {'ok': result, 'row': row, 'msg': msg} def _get_filters(self, values): opt = values.get('opt', '') if not opt: return if opt == 'active': filters = (Tickets.cancelado==False) return filters if opt == 'today': t = util.today() filters = ( (Tickets.fecha.day == t.day) & (Tickets.fecha.month == t.month) & (Tickets.fecha.year == t.year) ) return filters if opt == 'yearmonth': if values['year'] == '-1': fy = (Tickets.fecha.year > 0) else: fy = (Tickets.fecha.year == int(values['year'])) if values['month'] == '-1': fm = (Tickets.fecha.month > 0) else: fm = (Tickets.fecha.month == int(values['month'])) filters = (fy & fm) return filters if opt == 'dates': dates = util.loads(values['range']) filters = Tickets.fecha.between( util.get_date(dates['start']), util.get_date(dates['end'], True) ) return filters return @classmethod def get_by(cls, values): filters = cls._get_filters(cls, values) 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'] = '' 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']['lugarexpedicion'] = \ # ~ 'C.P. de Expedición: {}'.format( # ~ data['comprobante']['lugar_expedicion']) 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'] = {} # ~ if obj['descuento']: # ~ data['totales']['descuento'] = float(obj['descuento']) return data @classmethod def get_pdf(cls, id): obj = Tickets.get(Tickets.id==id) name = '{}{}.pdf'.format(obj.serie, obj.folio) data = cls._get_info_to_pdf(cls, id) doc = util.to_pdf(data, data['emisor']['rfc']) return doc, name def _format_ticket(self, id): emisor = util.get_dict(Emisor.select().dicts()[0]) regimen = Emisor.select()[0].regimenes[0].name ticket = Tickets.select().where(Tickets.id==id).dicts()[0] products = TicketsDetalle.get_by_print(id) emisor['name'] = '{}\n'.format(emisor['nombre']) emisor['rfc'] = 'RFC: {}\n'.format(emisor['rfc']) emisor['regimen'] = 'Regimen: {}\n'.format(regimen) interior = '' if emisor['no_interior']: interior = ', {}'.format(emisor['no_interior']) colonia = '' if emisor['colonia']: colonia = ', Col. {}'.format(emisor['colonia']) municipio = '' if emisor['municipio']: municipio = ', {}'.format(emisor['municipio']) estado = '' if emisor['estado']: estado = ', {}'.format(emisor['estado']) cp = '' if emisor['codigo_postal']: cp = ', C.P. {}'.format(emisor['codigo_postal']) pais = '' if emisor['pais']: pais = ', {}'.format(emisor['pais']) direccion = '{} {}{}{}{}{}{}{}\n\n'.format(emisor['calle'], emisor['no_exterior'], interior, colonia, municipio, estado, cp, pais) emisor['address'] = direccion ticket['title'] = 'Ticket: {}{}\n\n'.format(ticket['serie'], ticket['folio']) ticket['date'] = 'Fecha y hora: {}\n'.format(ticket['fecha']) ticket['letters'] = '{}\n\n'.format( util.to_letters(ticket['total'], 'peso')) ticket['total'] = 'TOTAL: $ {:>12,.2f}\n\n'.format(ticket['total']) data = { 'emisor': emisor, 'receptor': {'name': '{}\n\n'.format(PUBLIC)}, 'ticket': ticket, 'products': products, } return data def _get_info_printer(self): info = {} value = Configuracion.get_('txt_ticket_printer') if not value: return info values = value.split(':') if len(values) == 1: info = {'ip': values[0], 'usb': ()} elif len(values) == 2: info = {'ip': '', 'usb': (int(values[0], 16), int(values[1], 16))} elif len(values) == 5: info = {'ip': '', 'usb': ( int(values[0], 16), int(values[1], 16), int(values[2]), int(values[3], 16), int(values[4], 16), ) } return info @classmethod def printer(cls, values): id = int(values['id']) info_printer = cls._get_info_printer(cls) if not info_printer: msg = 'Es necesario configurar una impresora.' result = {'ok': False, 'msg': msg} return result data = cls._format_ticket(cls, id) result = util.print_ticket(data, info_printer) msg = 'Ticket impreso correctamente' if not result: msg = 'Asegurate de que la impresora este conectada y funcionando.' result = {'ok': result, 'msg': msg} return result class TicketsDetalle(BaseModel): ticket = ForeignKeyField(Tickets) producto = ForeignKeyField(Productos, null=True) descripcion = TextField(default='') cantidad = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) valor_unitario = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) descuento = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) precio_final = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) class Meta: order_by = ('ticket',) def _with_tax(self, product): precio_final = float(product.precio_final) base = float(product.precio_final) for tax in product.producto.impuestos: impuesto_producto = round(float(tax.tasa) * base, DECIMALES) precio_final += impuesto_producto return precio_final @classmethod def get_by_print(cls, id): products = TicketsDetalle.select().where(TicketsDetalle.ticket==id) lines = [] for p in products: price_with_tax = cls._with_tax(cls, p) importe = round(price_with_tax * float(p.cantidad), DECIMALES) l = '{:>6,.2f} {:<4} {:<14} {:>9,.2f} {:>10,.2f}\n'.format( p.cantidad, p.producto.unidad.name[:5], p.descripcion[:14], price_with_tax, importe ) lines.append(l) return lines @classmethod def get_by_ticket(cls, id): data = [] productos = TicketsDetalle.select().where(TicketsDetalle.ticket==id) for p in productos: producto = {} producto['noidentificacion'] = p.producto.clave producto['descripcion'] = p.descripcion producto['unidad'] = p.producto.unidad.name producto['cantidad'] = str(p.cantidad) price_with_tax = cls._with_tax(cls, p) producto['valorunitario'] = str(price_with_tax) importe = round(price_with_tax * float(p.cantidad), DECIMALES) producto['importe'] = str(importe) data.append(producto) return data class TicketsImpuestos(BaseModel): ticket = ForeignKeyField(Tickets) impuesto = ForeignKeyField(SATImpuestos) base = DecimalField(default=0.0, max_digits=20, decimal_places=6, auto_round=True) importe = DecimalField(default=0.0, max_digits=18, decimal_places=6, auto_round=True) class Meta: order_by = ('ticket',) indexes = ( (('ticket', 'impuesto'), True), ) class SeriesProductos(BaseModel): factura = ForeignKeyField(FacturasDetalle, null=True) ticket = ForeignKeyField(TicketsDetalle, null=True) serie = TextField(default='') class Meta: order_by = ('serie',) def authenticate(args): respuesta = {'login': False, 'msg': 'No Autorizado', 'user': ''} values = util.get_con(args['rfc']) if not values: return respuesta, None conectar(values) try: obj = Usuarios.get(usuario=args['usuario'], es_activo=True) except Usuarios.DoesNotExist: return respuesta, None except ProgrammingError as e: log.error(e) return respuesta, None if not obj.contraseña.check_password(args['contra']): return respuesta, None obj.ultimo_ingreso = util.now() obj.save() respuesta['msg'] = '' respuesta['login'] = True respuesta['user'] = str(obj) respuesta['super'] = obj.es_superusuario #~ respuesta['admin'] = obj.es_superusuario or obj.es_admin return respuesta, obj def get_cp(cp): con = sqlite3.connect(PATH_CP) cursor = con.cursor() sql = """ SELECT colonia, municipio, estado FROM colonias, municipios, estados WHERE colonias.id_municipio=municipios.id AND municipios.id_estado=estados.id AND cp=? ORDER BY colonia""" cursor.execute(sql, (cp,)) rows = cursor.fetchall() cursor.close() con.close() data = {} if rows: data = { 'estado': rows[0][2], 'municipio': rows[0][1], } if len(rows) == 1: data['colonia'] = rows[0][0] else: data['colonia'] = [r[0] for r in rows] return data def get_sat_key(key): return util.get_sat_key('productos', key) def get_sat_monedas(key): return util.get_sat_monedas(key) def get_sat_unidades(key): return util.get_sat_unidades(key) def get_sat_productos(key): return util.get_sat_productos(key) def test_correo(values): server = { 'servidor': values['correo_servidor'], 'puerto': values['correo_puerto'], 'ssl': util.get_bool(values['correo_ssl']), 'usuario': values['correo_usuario'], 'contra': values['correo_contra'], } ccp = values.get('correo_copia', '') options = { 'para': values['correo_usuario'], 'copia': ccp, 'confirmar': util.get_bool(values['correo_confirmacion']), 'asunto': values['correo_asunto'], 'mensaje': values['correo_mensaje'].replace('\n', '
'), 'files': [], } data= { 'server': server, 'options': options, } return util.send_mail(data) def _init_values(rfc): data = ( {'clave': 'version', 'valor': VERSION}, {'clave': 'migracion', 'valor': '0'}, {'clave': 'rfc_publico', 'valor': 'XAXX010101000'}, {'clave': 'rfc_extranjero', 'valor': 'XEXX010101000'}, {'clave': 'decimales', 'valor': '2'}, {'clave': 'path_key', 'valor': ''}, {'clave': 'path_cer', 'valor': ''}, ) for row in data: try: with database_proxy.atomic() as txn: Configuracion.create(**row) except IntegrityError: pass if not Certificado.select().count(): Certificado.create(rfc=rfc) log.info('Valores iniciales insertados...') return def _crear_tablas(rfc): tablas = [Addendas, Categorias, Certificado, CondicionesPago, Configuracion, Folios, Registro, CamposPersonalizados, Emisor, Facturas, FacturasDetalle, FacturasImpuestos, FacturasPagos, FacturasRelacionadas, FacturasComplementos, FacturasPersonalizados, SeriesProductos, Almacenes, Productos, RangosPrecios, Sucursales, PreFacturas, PreFacturasDetalle, PreFacturasImpuestos, PreFacturasRelacionadas, Tickets, TicketsDetalle, TicketsImpuestos, SATAduanas, SATFormaPago, SATImpuestos, SATMonedas, SATRegimenes, SATTipoRelacion, SATUnidades, SATUsoCfdi, SATBancos, SATNivelesEducativos, Socios, Contactos, ContactoCorreos, ContactoDirecciones, ContactoTelefonos, Tags, Usuarios, CuentasBanco, TipoCambio, MovimientosBanco, TipoCorreo, TipoDireccion, TipoPariente, TipoResponsable, TipoTelefono, TipoTitulo, TipoMovimientoAlumno, TipoMovimientoAlmacen, CfdiPagos, NivelesEducativos, Alumnos, AlumnosParientes, Grupos, ParienteDirecciones, ParienteTelefonos, ParienteCorreos, Emisor.regimenes.get_through_model(), Socios.tags.get_through_model(), Productos.impuestos.get_through_model(), Productos.tags.get_through_model(), ] log.info('Creando tablas...') database_proxy.create_tables(tablas, True) log.info('Tablas creadas correctamente...') usuarios = ( {'usuario': 'superadmin', 'contraseña': DEFAULT_PASSWORD, 'es_superusuario': True}, {'usuario': 'admin', 'contraseña': DEFAULT_PASSWORD, 'es_admin': True}, ) for usuario in usuarios: try: with database_proxy.atomic() as txn: Usuarios.create(**usuario) log.info('Usuario creado correctamente...') except IntegrityError: log.info('Usuario ya existe...') _init_values(rfc) _importar_valores('', rfc) return True def _migrate_tables(): from playhouse.migrate import PostgresqlMigrator, migrate 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] log.info('Creando nuevas tablas...') database_proxy.create_tables(tablas, True) log.info('Tablas creadas correctamente...') log.info('Iniciando migración de tablas...') migrations = [] migrator = PostgresqlMigrator(database_proxy) sucursal = ForeignKeyField(Sucursales, null=True, to_field=Sucursales.id) columns = [c.name for c in database_proxy.get_columns('usuarios')] if not 'sucursal_id' in columns: migrations.append( migrator.add_column('usuarios', 'sucursal_id', sucursal)) if migrations: with database_proxy.atomic() as txn: migrate(*migrations) log.info('Tablas migradas correctamente...') return def _agregar_superusuario(): args = util.get_con() if not args: return conectar(args) usuario = input('Introduce el nuevo nombre para el superusuario: ').strip() if not usuario: msg = 'El nombre de usuario es requerido' log.erro(msg) return ok, contraseña = util.get_pass() if not ok: log.error(contraseña) return try: obj = Usuarios.create( usuario=usuario, contraseña=contraseña, es_superusuario=True) except IntegrityError: msg = 'El usuario ya existe' log.error(msg) return log.info('SuperUsuario creado correctamente...') return def _cambiar_contraseña(): args = util.get_con() if not args: return conectar(args) usuario = input('Introduce el nombre de usuario: ').strip() if not usuario: msg = 'El nombre de usuario es requerido' log.error(msg) return try: obj = Usuarios.get(usuario=usuario) except Usuarios.DoesNotExist: msg = 'El usuario no existe' log.error(msg) return ok, contraseña = util.get_pass() if not ok: log.error(contraseña) return obj.contraseña = contraseña obj.save() log.info('Contraseña cambiada correctamente...') return def _add_emisor(rfc, args): util._valid_db_companies() con = sqlite3.connect(COMPANIES) cursor = con.cursor() sql = """ INSERT INTO names VALUES (?, ?)""" try: cursor.execute(sql, (rfc, args)) except sqlite3.IntegrityError as e: log.error(e) return False con.commit() cursor.close() con.close() return True def _delete_emisor(rfc): util._valid_db_companies() con = sqlite3.connect(COMPANIES) cursor = con.cursor() sql = """ DELETE FROM names WHERE rfc = ?""" try: cursor.execute(sql, (rfc,)) except Exception as e: log.error(e) return False con.commit() cursor.close() con.close() return True def _iniciar_bd(): rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) if _crear_tablas(rfc): return log.error('No se pudieron crear las tablas') return def _agregar_rfc(no_bd): rfc = input('Introduce el nuevo RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return datos = input('Introduce los datos de conexión: ').strip() if not datos: msg = 'Los datos de conexión son requeridos' log.error(msg) return opt = util.parse_con(datos) if not opt: log.error('Datos de conexión incompletos') return args = opt.copy() if conectar(args): if _add_emisor(rfc, util.dumps(opt)): if no_bd: log.info('RFC agregado correctamente...') return _crear_tablas(rfc) log.info('RFC agregado correctamente...') return log.error('No se pudo agregar el RFC') return def _borrar_rfc(): rfc = input('Introduce el RFC a borrar: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return confirm = input('¿Estás seguro de borrar el RFC?') if _delete_emisor(rfc): log.info('RFC borrado correctamente...') return log.error('No se pudo borrar el RFC') return def _listar_rfc(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): rfc = rfc.upper() if util.get_con(rfc): msg = 'El RFC ya esta dado de alta' return {'ok': False, 'msg': msg} user = rfc.replace('&', '').lower() if not util.crear_rol(user): msg = 'No se pudo crear el usuario, es probable que ya exista' return {'ok': False, 'msg': msg} if not util.crear_db(user): msg = 'No se pudo crear la base de datos' return {'ok': False, 'msg': msg} args = { "type": "postgres", "name": user, "user": user, "password": user, } if not conectar(args.copy()): msg = 'No se pudo conectar a la base de datos' return {'ok': False, 'msg': msg} if not _add_emisor(rfc, util.dumps(args)): msg = 'No se pudo guardar el nuevo emisor' return {'ok': False, 'msg': msg} if not _crear_tablas(rfc): msg = 'No se pudo crear las tablas' return {'ok': False, 'msg': msg} msg = 'Emisor dado de alta correctamente' row = {'delete': '-', 'rfc': rfc} return {'ok': True, 'msg': msg, 'row': row} def empresa_borrar(rfc): return _delete_emisor(rfc) def _importar_valores(archivo='', rfc=''): if not rfc: rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) if not archivo: archivo = INIT_VALUES log.info('Importando datos...') regimen = '' rows = util.loads(open(archivo, 'r').read()) for row in rows: log.info('\tImportando tabla: {}'.format(row['tabla'])) table = globals()[row['tabla']] for r in row['datos']: try: with database_proxy.atomic() as txn: table.create(**r) except IntegrityError: pass log.info('Importación terminada...') return def _importar_socios(rows): log.info('\tImportando Clientes...') totals = len(rows) for i, row in enumerate(rows): msg = '\tGuardando cliente {} de {}'.format(i+1, totals) log.info(msg) try: with database_proxy.atomic() as txn: Socios.create(**row) except IntegrityError: msg = '\tSocio existente: {}'.format(row['nombre']) log.info(msg) log.info('\tClientes importados...') return def _existe_factura(row): filtro = (Facturas.uuid==row['uuid']) if row['uuid'] is None: filtro = ( (Facturas.serie==row['serie']) & (Facturas.folio==row['folio']) ) return Facturas.select().where(filtro).exists() def _importar_facturas(rows): log.info('\tImportando Facturas...') totals = len(rows) for i, row in enumerate(rows): msg = '\tGuardando factura {} de {}'.format(i+1, totals) log.info(msg) try: detalles = row.pop('detalles') impuestos = row.pop('impuestos') cliente = row.pop('cliente') if cliente['rfc'] == 'GTR0610314Z0': cliente['slug'] = 'grupo_torque_sa_de_cv' row['cliente'] = Socios.get(**cliente) with database_proxy.atomic() as txn: if _existe_factura(row): msg = '\tFactura existente: {}{}'.format( row['serie'], row['folio']) log.info(msg) continue obj = Facturas.create(**row) for detalle in detalles: detalle['factura'] = obj FacturasDetalle.create(**detalle) for impuesto in impuestos: imp = SATImpuestos.get(**impuesto['filtro']) new = { 'factura': obj, 'impuesto': imp, 'importe': impuesto['importe'], } try: with database_proxy.atomic() as txn: FacturasImpuestos.create(**new) except IntegrityError as e: pass except IntegrityError as e: print (e) msg = '\tFactura: id: {}'.format(row['serie'] + str(row['folio'])) log.error(msg) break log.info('\tFacturas importadas...') return def _importar_categorias(rows): log.info('\tImportando Categorías...') for row in rows: with database_proxy.atomic() as txn: try: Categorias.create(**row) except IntegrityError: msg = '\tCategoria: ({}) {}'.format(row['padre'], row['categoria']) log.error(msg) log.info('\tCategorías importadas...') return def _get_id_unidad(unidad): try: if 'pieza' in unidad.lower(): unidad = 'pieza' if 'metros' in unidad.lower(): unidad = 'metro' if 'tramo' in unidad.lower(): unidad = 'paquete' if 'juego' in unidad.lower(): unidad = 'par' if 'bolsa' in unidad.lower(): unidad = 'globo' if unidad.lower() == 'no aplica': unidad = 'servicio' obj = SATUnidades.get(SATUnidades.name.contains(unidad)) except SATUnidades.DoesNotExist: msg = '\tNo se encontró la unidad: {}'.format(unidad) # ~ log.error(msg) return unidad return str(obj.id) def _get_impuestos(impuestos): lines = '|' for impuesto in impuestos: if impuesto['tasa'] == '-2/3': tasa = str(round(2/3, 6)) else: if impuesto['tasa'] == 'EXENTO': tasa = '0.00' else: tasa = str(round(float(impuesto['tasa']) / 100.0, 6)) info = ( IMPUESTOS.get(impuesto['nombre']), impuesto['nombre'], impuesto['tipo'][0], tasa, ) lines += '|'.join(info) + '|' return lines def _generar_archivo_productos(archivo): rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) log.info('Importando datos...') app = util.ImportFacturaLibre(archivo, rfc) if not app.is_connect: log.error('\t{}'.format(app._error)) return rows = app.import_productos() p, _, _, _ = util.get_path_info(archivo) path_txt = util._join(p, 'productos_{}.txt'.format(rfc)) log.info('\tGenerando archivo: {}'.format(path_txt)) fields = ( 'clave', 'clave_sat', 'unidad', 'categoria', 'descripcion', 'valor_unitario', 'existencia', 'inventario', 'codigo_barras', 'cuenta_predial', 'ultimo_precio', 'minimo', ) data = ['|'.join(fields)] not_units = [] for row in rows: impuestos = row.pop('impuestos', ()) line = [str(row[r]) for r in fields] if line[10] == 'None': line[10] = '0.0' line[2] = _get_id_unidad(line[2]) try: int(line[2]) except ValueError: if not line[2] in not_units: not_units.append(line[2]) msg = 'No se encontró la unidad: {}'.format(line[2]) log.error(msg) continue line = '|'.join(line) + _get_impuestos(impuestos) data.append(line) with open(path_txt, 'w') as fh: fh.write('\n'.join(data)) log.info('\tArchivo generado: {}'.format(path_txt)) return def importar_bdfl(): try: emisor = Emisor.select()[0] except IndexError: msg = 'Configura primero al emisor' return {'ok': False, 'msg': msg} name = '{}.sqlite'.format(emisor.rfc.lower()) path = util._join('/tmp', name) log.info('Importando datos...') app = util.ImportFacturaLibre(path, emisor.rfc) if not app.is_connect: msg = app._error log.error('\t{}'.format(msg)) return {'ok': False, 'msg': msg} data = app.import_data() _importar_socios(data['Socios']) _importar_facturas(data['Facturas']) _importar_categorias(data['Categorias']) msg = 'Importación terminada...' log.info(msg) return {'ok': True, 'msg': msg} def _importar_factura_libre(archivo): rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) log.info('Importando datos...') app = util.ImportFacturaLibre(archivo, rfc) if not app.is_connect: log.error('\t{}'.format(app._error)) return data = app.import_data() _importar_socios(data['Socios']) _importar_facturas(data['Facturas']) _importar_categorias(data['Categorias']) log.info('Importación terminada...') return def _importar_factura_libre_gambas(conexion): rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) log.info('Importando datos...') app = util.ImportFacturaLibreGambas(conexion, rfc) if not app.is_connect: log.error('\t{}'.format(app._error)) return data = app.import_data() _importar_socios(data['Socios']) _importar_facturas(data['Facturas']) _importar_categorias(data['Categorias']) # ~ _importar_productos_gambas(data['Productos']) _import_tickets(data['Tickets']) log.info('Importación terminada...') return def _exist_ticket(row): filters = ( (Tickets.serie==row['serie']) & (Tickets.folio==row['folio']) ) return Tickets.select().where(filters).exists() def _import_tickets(rows): log.info('\tImportando Tickets...') for row in rows: try: details = row.pop('details') taxes = row.pop('taxes') with database_proxy.atomic() as txn: if _exist_ticket(row): msg = '\tTicket existente: {}{}'.format( row['serie'], row['folio']) log.info(msg) continue if not row['factura'] is None and row['factura']: row['factura'] = Facturas.get( Facturas.serie==row['factura']['serie'], Facturas.folio==row['factura']['folio']) else: row['factura'] = None obj = Tickets.create(**row) for detail in details: detail['ticket'] = obj TicketsDetalle.create(**detail) for tax in taxes: imp = SATImpuestos.get(**tax['filter']) new = { 'ticket': obj, 'impuesto': imp, 'importe': tax['import'], } TicketsImpuestos.create(**new) except IntegrityError as e: # ~ print (e) msg = '\tTicket: id: {}'.format(row['serie'] + str(row['folio'])) log.error(msg) log.info('\tTickets importadas...') return def _importar_productos_gambas(rows): log.info('Importando productos...') KEYS = { 'Exento': '000', 'ISR': '001', 'IVA': '002', } totals = len(rows) for i, row in enumerate(rows): msg = '\tGuardando producto {} de {}'.format(i+1, totals) log.info(msg) source_taxes = row.pop('impuestos') row['unidad'] = SATUnidades.get(SATUnidades.key==row['unidad']) taxes = [] for tax in source_taxes: w = { 'key': KEYS[tax[0]], 'name': tax[0], 'tasa': float(tax[1]), 'tipo': tax[2][0], } taxes.append(SATImpuestos.get_o_crea(w)) with database_proxy.transaction(): try: obj = Productos.create(**row) obj.impuestos = taxes except IntegrityError as e: msg = '\tProducto ya existe' log.info(msg) log.info('Importación terminada...') return def _importar_productos(archivo): rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) log.info('Importando productos...') fields = ( 'clave', 'clave_sat', 'unidad', 'categoria', 'descripcion', 'valor_unitario', 'existencia', 'inventario', 'codigo_barras', 'cuenta_predial', 'ultimo_precio', 'minimo', ) rows = util.read_file(archivo, 'r').split('\n') for i, row in enumerate(rows): if i == 0: continue data = row.split('|') # ~ print (data) new = {} for i, f in enumerate(fields): if not len(data[0]): continue if i in (2, 3): try: new[f] = int(data[i]) except ValueError: continue elif i in (5, 6, 10, 11): new[f] = float(data[i]) elif i == 7: new[f] = bool(data[i]) else: new[f] = data[i] impuestos = data[i + 1:-1] if not impuestos: taxes = [SATImpuestos.select().where(SATImpuestos.id==6)] else: taxes = [] try: for i in range(0, len(impuestos), 4): w = { 'key': impuestos[i], 'name': impuestos[i+1], 'tipo': impuestos[i+2], 'tasa': float(impuestos[i+3]), } taxes.append(SATImpuestos.get_o_crea(w)) except IndexError: print ('IE', data) continue with database_proxy.transaction(): try: obj = Productos.create(**new) obj.impuestos = taxes except IntegrityError as e: pass log.info('Importación terminada...') return def _test(): rfc = input('Introduce el RFC: ').strip().upper() if not rfc: msg = 'El RFC es requerido' log.error(msg) return args = util.get_con(rfc) if not args: return conectar(args) return CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) help_create_tables = 'Crea las tablas en la base de datos' help_migrate_db = 'Migra las tablas en la base de datos' help_superuser = 'Crea un nuevo super usuario' help_change_pass = 'Cambia la contraseña a un usuario' help_rfc = 'Agrega un nuevo RFC' help_br = 'Elimina un RFC' help_lr = 'Listar RFCs' @click.command(context_settings=CONTEXT_SETTINGS) @click.option('-bd', '--iniciar-bd',help=help_create_tables, is_flag=True, default=False) @click.option('-m', '--migrar-bd', help=help_migrate_db, is_flag=True, default=False) @click.option('-ns', '--nuevo-superusuario', help=help_superuser, is_flag=True, default=False) @click.option('-cc', '--cambiar-contraseña', help=help_change_pass, is_flag=True, default=False) @click.option('-ar', '--agregar-rfc', help=help_rfc, is_flag=True, default=False) @click.option('-br', '--borrar-rfc', help=help_br, is_flag=True, default=False) @click.option('-lr', '--listar-rfc', help=help_lr, is_flag=True, default=False) @click.option('-i', '--importar-valores', is_flag=True, default=False) @click.option('-f', '--archivo') @click.option('-c', '--conexion') @click.option('-fl', '--factura-libre', is_flag=True, default=False) @click.option('-flg', '--factura-libre-gambas', is_flag=True, default=False) @click.option('-t', '--test', is_flag=True, default=False) @click.option('-gap', '--generar-archivo-productos', is_flag=True, default=False) @click.option('-ip', '--importar-productos', is_flag=True, default=False) @click.option('-bk', '--backup-dbs', is_flag=True, default=False) @click.option('-n', '--no-bd', is_flag=True, default=False) @click.option('-a', '--alta', is_flag=True, default=False) @click.option('-r', '--rfc') @click.option('-d', '--detalle', is_flag=True, default=False) def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, agregar_rfc, borrar_rfc, listar_rfc, importar_valores, archivo, conexion, factura_libre, factura_libre_gambas, test, generar_archivo_productos, importar_productos, backup_dbs, no_bd, alta, rfc, detalle): opt = locals() if opt['test']: _test() sys.exit(0) if opt['alta']: if not opt['rfc']: msg = 'Falta el RFC' raise click.ClickException(msg) empresa_agregar(opt['rfc']) sys.exit(0) if opt['iniciar_bd']: _iniciar_bd() sys.exit(0) if opt['migrar_bd']: _migrate_tables() sys.exit(0) if opt['nuevo_superusuario']: _agregar_superusuario() sys.exit(0) if opt['cambiar_contraseña']: _cambiar_contraseña() sys.exit(0) if opt['agregar_rfc']: _agregar_rfc(no_bd) sys.exit(0) if opt['borrar_rfc']: _borrar_rfc() sys.exit(0) if opt['listar_rfc']: _listar_rfc(opt['detalle']) sys.exit(0) if opt['importar_valores']: if not opt['archivo']: msg = 'Falta la ruta del archivo importar' raise click.ClickException(msg) if not util.is_file(opt['archivo']): msg = 'No es un archivo' raise click.ClickException(msg) _importar_valores(opt['archivo']) sys.exit(0) if opt['factura_libre']: if not opt['archivo']: msg = 'Falta la ruta de la base de datos' raise click.ClickException(msg) if not util.is_file(opt['archivo']): msg = 'No es un archivo' raise click.ClickException(msg) _, _, _, ext = util.get_path_info(opt['archivo']) if ext != '.sqlite': msg = 'No es una base de datos' raise click.ClickException(msg) _importar_factura_libre(opt['archivo']) sys.exit(0) if opt['factura_libre_gambas']: if not opt['conexion']: msg = 'Falta los datos de conexión' raise click.ClickException(msg) _importar_factura_libre_gambas(opt['conexion']) sys.exit(0) if opt['generar_archivo_productos']: if not opt['archivo']: msg = 'Falta la ruta de la base de datos' raise click.ClickException(msg) if not util.is_file(opt['archivo']): msg = 'No es un archivo' raise click.ClickException(msg) _, _, _, ext = util.get_path_info(opt['archivo']) if ext != '.sqlite': msg = 'No es una base de datos' raise click.ClickException(msg) _generar_archivo_productos(opt['archivo']) sys.exit(0) if opt['importar_productos']: if not opt['archivo']: msg = 'Falta la ruta del archivo' raise click.ClickException(msg) if not util.is_file(opt['archivo']): msg = 'No es un archivo' raise click.ClickException(msg) _, _, _, ext = util.get_path_info(opt['archivo']) if ext != '.txt': msg = 'No es un archivo de texto' raise click.ClickException(msg) _importar_productos(opt['archivo']) sys.exit(0) if opt['backup_dbs']: util.backup_dbs() return if __name__ == '__main__': main()