From 4699c429fa7373b12a99ca8b11342e2110f85078 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Tue, 26 Dec 2017 20:30:22 -0600 Subject: [PATCH] Importar de Factura Libre Gambas --- source/app/controllers/helper.py | 7 +- source/app/controllers/util.py | 354 +++++++++++++++++++++++++++++++ source/app/models/main.py | 43 +++- 3 files changed, 401 insertions(+), 3 deletions(-) diff --git a/source/app/controllers/helper.py b/source/app/controllers/helper.py index d2fe530..0f1dbe8 100644 --- a/source/app/controllers/helper.py +++ b/source/app/controllers/helper.py @@ -3,6 +3,7 @@ #~ import falcon import re import smtplib +import ssl import collections from collections import OrderedDict @@ -252,9 +253,12 @@ class SendMail(object): def _login(self): try: if self._config['ssl']: - self._server = smtplib.SMTP_SSL( + self._server = smtplib.SMTP( self._config['servidor'], self._config['puerto'], timeout=10) + self._server.ehlo() + self._server.starttls() + self._server.ehlo() else: self._server = smtplib.SMTP( self._config['servidor'], @@ -265,6 +269,7 @@ class SendMail(object): if '535' in str(e): self._error = 'Nombre de usuario o contraseña inválidos' return False + print (e) if '534' in str(e) and 'gmail' in self._config['servidor']: self._error = 'Necesitas activar el acceso a otras ' \ 'aplicaciones en tu cuenta de GMail' diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index fec29c8..682136a 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -1594,6 +1594,360 @@ def sync_cfdi(auth, files): return +class ImportFacturaLibreGambas(object): + + def __init__(self, conexion, rfc): + self._rfc = rfc + self._con = None + self._cursor = None + self._error = '' + self._is_connect = self._connect(conexion) + self._clientes = [] + self._clientes_rfc = [] + + @property + def error(self): + return self._error + + @property + def is_connect(self): + return self._is_connect + + def _validate_rfc(self): + sql = "SELECT rfc FROM emisor LIMIT 1" + self._cursor.execute(sql) + obj = self._cursor.fetchone() + if obj is None: + self._error = 'No se encontró al emisor: {}'.format(self._rfc) + return False + + if not DEBUG: + if obj['rfc'] != self._rfc: + self._error = 'Los datos no corresponden al RFC: {}'.format(self._rfc) + return False + + return True + + def _connect(self, conexion): + import psycopg2 + import psycopg2.extras + try: + self._con = psycopg2.connect(conexion) + self._cursor = self._con.cursor(cursor_factory=psycopg2.extras.DictCursor) + return self._validate_rfc() + except Exception as e: + log.error(e) + self._error = 'No se pudo conectar a la base de datos' + return False + + def close(self): + try: + self._cursor.close() + self._con.close() + except: + pass + return + + def import_data(self): + data = {} + tables = ( + ('receptores', 'Socios'), + ('cfdifacturas', 'Facturas'), + ('categorias', 'Categorias'), + ) + for source, target in tables: + data[target] = self._get_table(source) + + data['Socios'] += self._clientes + + return data + + def _get_table(self, table): + return getattr(self, '_{}'.format(table))() + + def import_productos(self): + sql = "SELECT * FROM productos" + self._cursor.execute(sql) + rows = self._cursor.fetchall() + + fields = ( + ('id_categoria', 'categoria'), + ('noIdentificacion', 'clave'), + ('descripcion', 'descripcion'), + ('unidad', 'unidad'), + ('valorUnitario', 'valor_unitario'), + ('existencia', 'existencia'), + ('inventario', 'inventario'), + ('codigobarras', 'codigo_barras'), + ('CuentaPredial', 'cuenta_predial'), + ('precio_compra', 'ultimo_precio'), + ('minimo', 'minimo'), + ) + data = [] + + sql = """ + SELECT nombre, tasa, tipo + FROM impuestos, productos, productosimpuestos + WHERE productos.id=productosimpuestos.id_producto + AND productosimpuestos.id_impuesto=impuestos.id + AND productos.id = ? + """ + for row in rows: + new = {t: row[s] for s, t in fields} + new['descripcion'] = ' '.join(new['descripcion'].split()) + new['clave_sat'] = DEFAULT_SAT_PRODUCTO + self._cursor.execute(sql, (row['id'],)) + impuestos = self._cursor.fetchall() + new['impuestos'] = tuple(impuestos) + data.append(new) + + return data + + def _categorias(self): + sql = "SELECT * FROM categorias ORDER BY id_padre" + self._cursor.execute(sql) + rows = self._cursor.fetchall() + + fields = ( + ('id', 'id'), + ('categoria', 'categoria'), + ('id_padre', 'padre'), + ) + data = [] + + for row in rows: + new = {t: row[s] for s, t in fields} + if new['padre'] == 0: + new['padre'] = None + data.append(new) + + return data + + def _get_cliente(self, invoice): + sql = "SELECT rfc, nombre FROM receptores WHERE id=%s" + self._cursor.execute(sql, [invoice['id_cliente']]) + obj = self._cursor.fetchone() + if not obj is None: + data = { + 'rfc': obj['rfc'], + 'slug': to_slug(obj['nombre']), + } + return data + + if not invoice['xml']: + return {} + + doc = parse_xml(invoice['xml']) + version = doc.attrib['version'] + node = doc.find('{}Receptor'.format(PRE[version])) + rfc = node.attrib['rfc'] + nombre = node.attrib['nombre'] + + tipo_persona = 1 + if rfc == 'XEXX010101000': + tipo_persona = 4 + elif rfc == 'XAXX010101000': + tipo_persona = 3 + elif len(rfc) == 12: + tipo_persona = 2 + + data = { + 'tipo_persona': tipo_persona, + 'rfc': rfc, + 'nombre': nombre, + 'slug': to_slug(nombre), + 'es_cliente': True, + 'es_activo': False, + } + if not rfc in self._clientes_rfc: + self._clientes_rfc.append(rfc) + self._clientes.append(data) + + data = { + 'rfc': data['rfc'], + 'slug': data['slug'], + } + return data + + def _get_detalles(self, id): + sql = "SELECT * FROM cfdidetalle WHERE id_cfdi=%s" + self._cursor.execute(sql, [id]) + rows = self._cursor.fetchall() + + fields = ( + ('categoria', 'categoria'), + ('cantidad', 'cantidad'), + ('unidad', 'unidad'), + ('noidentificacion', 'clave'), + ('descripcion', 'descripcion'), + ('valorunitario', 'valor_unitario'), + ('importe', 'importe'), + ('numero', 'pedimento'), + ('fecha', 'fecha_pedimento'), + ('aduana', 'aduana'), + ('cuentapredial', 'cuenta_predial'), + ('descuento', 'descuento'), + ('precio', 'precio_final'), + ) + + data = [] + for row in rows: + new = {t: row[s] for s, t in fields if row[s]} + data.append(new) + + return data + + def _get_impuestos(self, id): + sql = "SELECT * FROM cfdiimpuestos WHERE id_cfdi=%s" + self._cursor.execute(sql, [id]) + rows = self._cursor.fetchall() + + tasas = { + '0': 0.0, + '16': 0.16, + '16.00': 0.16, + '11': 0.11, + '-10': 0.10, + '-2': 0.02, + '-0.5': 0.005, + '-2/3': 0.106667, + '-10.6666': 0.106667, + '-10.666666': 0.106667, + '-10.66660': 0.106667, + } + + data = [] + for row in rows: + filtro = { + 'name': row['impuesto'], + 'tasa': tasas[row['tasa']], + 'tipo': row['tipo'][0], + } + new = { + 'importe': row['importe'], + 'filtro': filtro + } + data.append(new) + + return data + + def _cfdifacturas(self): + sql = "SELECT * FROM cfdifacturas" + self._cursor.execute(sql) + rows = self._cursor.fetchall() + fields = ( + ('version', 'version'), + ('serie', 'serie'), + ('folio', 'folio'), + ('fecha', 'fecha'), + ('fecha_timbrado', 'fecha_timbrado'), + ('formadepago', 'forma_pago'), + ('condicionesdepago', 'condiciones_pago'), + ('subtotal', 'subtotal'), + ('descuento', 'descuento'), + ('tipocambio', 'tipo_cambio'), + ('moneda', 'moneda'), + ('total', 'total'), + ('tipodecomprobante', 'tipo_comprobante'), + ('metododepago', 'metodo_pago'), + ('lugarexpedicion', 'lugar_expedicion'), + ('totalimpuestosretenidos', 'total_retenciones'), + ('totalimpuestostrasladados', 'total_traslados'), + ('xml', 'xml'), + ('id_cliente', 'cliente'), + ('notas', 'notas'), + ('uuid', 'uuid'), + ('cancelada', 'cancelada'), + ) + data = [] + for row in rows: + new = {t: row[s] for s, t in fields} + + for _, f in fields: + new[f] = new[f] or '' + + if not new['fecha_timbrado']: + new['fecha_timbrado'] = None + + new['estatus'] = 'Timbrada' + if new['cancelada']: + new['estatus'] = 'Cancelada' + + if not new['uuid']: + new['uuid'] = None + elif new['uuid'] == 'ok': + new['uuid'] = None + new['estatus'] = 'Cancelada' + new['cancelada'] = True + + if new['xml'] is None: + new['xml'] = '' + + new['pagada'] = True + new['total_mn'] = round(row['tipocambio'] * row['total'], 2) + new['detalles'] = self._get_detalles(row['id']) + new['impuestos'] = self._get_impuestos(row['id']) + new['cliente'] = self._get_cliente(row) + data.append(new) + return data + + def _receptores(self): + sql = "SELECT * FROM receptores" + self._cursor.execute(sql) + rows = self._cursor.fetchall() + + fields = ( + ('rfc', 'rfc'), + ('nombre', 'nombre'), + ('calle', 'calle'), + ('noexterior', 'no_exterior'), + ('nointerior', 'no_interior'), + ('colonia', 'colonia'), + ('municipio', 'municipio'), + ('estado', 'estado'), + ('pais', 'pais'), + ('codigopostal', 'codigo_postal'), + ('extranjero', 'es_extranjero'), + ('activo', 'es_activo'), + ('fechaalta', 'fecha_alta'), + ('notas', 'notas'), + ) + data = [] + + sql1 = "SELECT correo FROM correos WHERE id_padre=%s" + sql2 = "SELECT telefono FROM telefonos WHERE id_padre=%s" + for row in rows: + new = {t: row[s] for s, t in fields} + new['slug'] = to_slug(new['nombre']) + new['es_cliente'] = True + if new['fecha_alta'] is None: + new['fecha_alta'] = str(now()) + else: + new['fecha_alta'] = str(new['fecha_alta']) + + for _, f in fields: + new[f] = new[f] or '' + if new['es_extranjero']: + new['tipo_persona'] = 4 + elif new['rfc'] == 'XAXX010101000': + new['tipo_persona'] = 3 + elif len(new['rfc']) == 12: + new['tipo_persona'] = 2 + + self._cursor.execute(sql1, (row['id'],)) + tmp = self._cursor.fetchall() + if tmp: + new['correo_facturas'] = ', '.join([r[0] for r in tmp]) + + self._cursor.execute(sql2, (row['id'],)) + tmp = self._cursor.fetchall() + if tmp: + new['telefonos'] = ', '.join([r[0] for r in tmp]) + + data.append(new) + return data + + class ImportFacturaLibre(object): def __init__(self, path, rfc): diff --git a/source/app/models/main.py b/source/app/models/main.py index 46a1683..febd764 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -4889,6 +4889,35 @@ def _importar_factura_libre(archivo): 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']) + + log.info('Importación terminada...') + return + + def _importar_productos(archivo): rfc = input('Introduce el RFC: ').strip().upper() if not rfc: @@ -5008,14 +5037,17 @@ help_lr = 'Listar RFCs' @click.option('-lr', '--listar-rfc', help=help_lr, is_flag=True, default=False) @click.option('-i', '--importar-valores', is_flag=True, default=False) @click.option('-a', '--archivo') +@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) def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc, - borrar_rfc, listar_rfc, importar_valores, archivo, factura_libre, test, - generar_archivo_productos, importar_productos, backup_dbs): + borrar_rfc, listar_rfc, importar_valores, archivo, conexion, factura_libre, + factura_libre_gambas, test, generar_archivo_productos, importar_productos, + backup_dbs): opt = locals() @@ -5077,6 +5109,13 @@ def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, rfc, _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'