diff --git a/CHANGELOG.md b/CHANGELOG.md index 3848322..9491439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +v 1.41.0 [10-Feb-2021] +---------------------- + - Importar clientes desde archivo CSV + + v 1.40.1 [09-Feb-2021] ---------------------- - Fix #422 diff --git a/VERSION b/VERSION index 53a714f..7d47e59 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.40.1 +1.41.0 diff --git a/source/app/controllers/utils.py b/source/app/controllers/utils.py index 2e35df8..e8f3996 100644 --- a/source/app/controllers/utils.py +++ b/source/app/controllers/utils.py @@ -18,6 +18,7 @@ import base64 import collections +import csv import datetime import getpass import json @@ -30,6 +31,7 @@ import smtplib import sqlite3 import subprocess import threading +import unicodedata import zipfile from pathlib import Path from xml.sax.saxutils import escape @@ -759,3 +761,20 @@ def get_status_sat(xml): return node.text +def spaces(value): + return '\n'.join([' '.join(l.split()) for l in value.split('\n')]) + + +def to_slug(string): + value = (unicodedata.normalize('NFKD', string) + .encode('ascii', 'ignore') + .decode('ascii').lower()) + return value.replace(' ', '_') + + +def read_csv(path, args={'delimiter': '|'}): + with open(path) as f: + reader = csv.DictReader(f, **args) + rows = [r for r in reader] + return rows + diff --git a/source/app/models/main.py b/source/app/models/main.py index 908b16d..8f82824 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -9797,389 +9797,6 @@ def _importar_valores(archivo='', rfc=''): return -# ~ def _importar_socios(rows): - # ~ log.info('\tImportando Clientes...') - # ~ totals = len(rows) - # ~ for i, row in enumerate(rows): - # ~ msg = '\tGuardando cliente {} de {}'.format(i+1, totals) - # ~ log.info(msg) - # ~ try: - # ~ with database_proxy.atomic() as txn: - # ~ Socios.create(**row) - # ~ except IntegrityError: - # ~ msg = '\tSocio existente: {}'.format(row['nombre']) - # ~ log.info(msg) - # ~ log.info('\tClientes importados...') - # ~ return - - -# ~ def _existe_factura(row): - # ~ filtro = (Facturas.uuid==row['uuid']) - # ~ if row['uuid'] is None: - # ~ filtro = ( - # ~ (Facturas.serie==row['serie']) & - # ~ (Facturas.folio==row['folio']) - # ~ ) - # ~ return Facturas.select().where(filtro).exists() - - -# ~ def _importar_facturas(rows): - # ~ log.info('\tImportando Facturas...') - # ~ totals = len(rows) - # ~ for i, row in enumerate(rows): - # ~ msg = '\tGuardando factura {} de {}'.format(i+1, totals) - # ~ log.info(msg) - - # ~ try: - # ~ detalles = row.pop('detalles') - # ~ impuestos = row.pop('impuestos') - # ~ cliente = row.pop('cliente') - # ~ row['cliente'] = Socios.get(**cliente) - # ~ with database_proxy.atomic() as txn: - # ~ if _existe_factura(row): - # ~ msg = '\tFactura existente: {}{}'.format( - # ~ row['serie'], row['folio']) - # ~ log.info(msg) - # ~ continue - # ~ obj = Facturas.create(**row) - # ~ for detalle in detalles: - # ~ detalle['factura'] = obj - # ~ FacturasDetalle.create(**detalle) - # ~ for impuesto in impuestos: - # ~ imp = SATImpuestos.get(**impuesto['filtro']) - # ~ new = { - # ~ 'factura': obj, - # ~ 'impuesto': imp, - # ~ 'importe': impuesto['importe'], - # ~ } - # ~ try: - # ~ with database_proxy.atomic() as txn: - # ~ FacturasImpuestos.create(**new) - # ~ except IntegrityError as e: - # ~ pass - - # ~ except IntegrityError as e: - # ~ print (e) - # ~ msg = '\tFactura: id: {}'.format(row['serie'] + str(row['folio'])) - # ~ log.error(msg) - # ~ break - - # ~ log.info('\tFacturas importadas...') - # ~ return - - -# ~ def _importar_categorias(rows): - # ~ log.info('\tImportando Categorías...') - # ~ for row in rows: - # ~ with database_proxy.atomic() as txn: - # ~ try: - # ~ Categorias.create(**row) - # ~ except IntegrityError: - # ~ msg = '\tCategoria: ({}) {}'.format(row['padre'], row['categoria']) - # ~ log.error(msg) - - # ~ log.info('\tCategorías importadas...') - # ~ return - - -# ~ def _get_id_unidad(unidad): - # ~ try: - # ~ if 'pieza' in unidad.lower(): - # ~ unidad = 'pieza' - # ~ if 'metros' in unidad.lower(): - # ~ unidad = 'metro' - # ~ if 'tramo' in unidad.lower(): - # ~ unidad = 'paquete' - # ~ if 'juego' in unidad.lower(): - # ~ unidad = 'par' - # ~ if 'bolsa' in unidad.lower(): - # ~ unidad = 'globo' - # ~ if unidad.lower() == 'no aplica': - # ~ unidad = 'servicio' - - # ~ obj = SATUnidades.get(SATUnidades.name.contains(unidad)) - # ~ except SATUnidades.DoesNotExist: - # ~ msg = '\tNo se encontró la unidad: {}'.format(unidad) - # ~ return unidad - - # ~ return str(obj.id) - - -# ~ def _get_impuestos(impuestos): - # ~ lines = '|' - # ~ for impuesto in impuestos: - # ~ if impuesto['tasa'] == '-2/3': - # ~ tasa = str(round(2/3, 6)) - # ~ else: - # ~ if impuesto['tasa'] == 'EXENTO': - # ~ tasa = '0.00' - # ~ else: - # ~ tasa = str(round(float(impuesto['tasa']) / 100.0, 6)) - - # ~ info = ( - # ~ IMPUESTOS.get(impuesto['nombre']), - # ~ impuesto['nombre'], - # ~ impuesto['tipo'][0], - # ~ tasa, - # ~ ) - # ~ lines += '|'.join(info) + '|' - # ~ return lines - - -# ~ def _generar_archivo_productos(archivo): - # ~ rfc = input('Introduce el RFC: ').strip().upper() - # ~ if not rfc: - # ~ msg = 'El RFC es requerido' - # ~ log.error(msg) - # ~ return - - # ~ args = util.get_con(rfc) - # ~ if not args: - # ~ return - - # ~ conectar(args) - - # ~ log.info('Importando datos...') - # ~ app = util.ImportFacturaLibre(archivo, rfc) - # ~ if not app.is_connect: - # ~ log.error('\t{}'.format(app._error)) - # ~ return - - # ~ rows = app.import_productos() - - # ~ p, _, _, _ = util.get_path_info(archivo) - # ~ path_txt = util._join(p, 'productos_{}.txt'.format(rfc)) - # ~ log.info('\tGenerando archivo: {}'.format(path_txt)) - - # ~ fields = ( - # ~ 'clave', - # ~ 'clave_sat', - # ~ 'unidad', - # ~ 'categoria', - # ~ 'descripcion', - # ~ 'valor_unitario', - # ~ 'existencia', - # ~ 'inventario', - # ~ 'codigo_barras', - # ~ 'cuenta_predial', - # ~ 'ultimo_precio', - # ~ 'minimo', - # ~ ) - - # ~ data = ['|'.join(fields)] - # ~ not_units = [] - # ~ for row in rows: - # ~ impuestos = row.pop('impuestos', ()) - # ~ line = [str(row[r]) for r in fields] - # ~ if line[10] == 'None': - # ~ line[10] = '0.0' - # ~ line[2] = _get_id_unidad(line[2]) - # ~ try: - # ~ int(line[2]) - # ~ except ValueError: - # ~ if not line[2] in not_units: - # ~ not_units.append(line[2]) - # ~ msg = 'No se encontró la unidad: {}'.format(line[2]) - # ~ log.error(msg) - # ~ continue - # ~ line = '|'.join(line) + _get_impuestos(impuestos) - # ~ data.append(line) - - # ~ with open(path_txt, 'w') as fh: - # ~ fh.write('\n'.join(data)) - - # ~ log.info('\tArchivo generado: {}'.format(path_txt)) - # ~ return - - -# ~ def importar_bdfl(): - # ~ try: - # ~ emisor = Emisor.select()[0] - # ~ except IndexError: - # ~ msg = 'Configura primero al emisor' - # ~ return {'ok': False, 'msg': msg} - - # ~ name = '{}.sqlite'.format(emisor.rfc.lower()) - # ~ path = util._join('/tmp', name) - - # ~ log.info('Importando datos...') - # ~ app = util.ImportFacturaLibre(path, emisor.rfc) - # ~ if not app.is_connect: - # ~ msg = app._error - # ~ log.error('\t{}'.format(msg)) - # ~ return {'ok': False, 'msg': msg} - - # ~ data = app.import_data() - - # ~ _importar_socios(data['Socios']) - # ~ _importar_facturas(data['Facturas']) - # ~ _importar_categorias(data['Categorias']) - - # ~ msg = 'Importación terminada...' - # ~ log.info(msg) - - # ~ return {'ok': True, 'msg': msg} - - -# ~ def _importar_factura_libre(archivo): - # ~ rfc = input('Introduce el RFC: ').strip().upper() - # ~ if not rfc: - # ~ msg = 'El RFC es requerido' - # ~ log.error(msg) - # ~ return - - # ~ args = util.get_con(rfc) - # ~ if not args: - # ~ return - - # ~ conectar(args) - - # ~ log.info('Importando datos...') - # ~ app = util.ImportFacturaLibre(archivo, rfc) - # ~ if not app.is_connect: - # ~ log.error('\t{}'.format(app._error)) - # ~ return - - # ~ data = app.import_data() - - # ~ _importar_socios(data['Socios']) - # ~ _importar_facturas(data['Facturas']) - # ~ _importar_categorias(data['Categorias']) - - # ~ log.info('Importación terminada...') - # ~ return - - -# ~ def _exist_ticket(row): - # ~ filters = ( - # ~ (Tickets.serie==row['serie']) & - # ~ (Tickets.folio==row['folio']) - # ~ ) - # ~ return Tickets.select().where(filters).exists() - - -# ~ def _import_tickets(rows): - # ~ log.info('\tImportando Tickets...') - # ~ for row in rows: - # ~ try: - # ~ details = row.pop('details') - # ~ taxes = row.pop('taxes') - # ~ with database_proxy.atomic() as txn: - # ~ if _exist_ticket(row): - # ~ msg = '\tTicket existente: {}{}'.format( - # ~ row['serie'], row['folio']) - # ~ log.info(msg) - # ~ continue - - # ~ if not row['factura'] is None and row['factura']: - # ~ row['factura'] = Facturas.get( - # ~ Facturas.serie==row['factura']['serie'], - # ~ Facturas.folio==row['factura']['folio']) - # ~ else: - # ~ row['factura'] = None - - # ~ obj = Tickets.create(**row) - # ~ for detail in details: - # ~ detail['ticket'] = obj - # ~ TicketsDetalle.create(**detail) - # ~ for tax in taxes: - # ~ imp = SATImpuestos.get(**tax['filter']) - # ~ new = { - # ~ 'ticket': obj, - # ~ 'impuesto': imp, - # ~ 'importe': tax['import'], - # ~ } - # ~ TicketsImpuestos.create(**new) - # ~ except IntegrityError as e: - # ~ print (e) - # ~ msg = '\tTicket: id: {}'.format(row['serie'] + str(row['folio'])) - # ~ log.error(msg) - - # ~ log.info('\tTickets importadas...') - # ~ return - - -# ~ def _importar_productos(archivo): - # ~ rfc = input('Introduce el RFC: ').strip().upper() - # ~ if not rfc: - # ~ msg = 'El RFC es requerido' - # ~ log.error(msg) - # ~ return - - # ~ args = util.get_con(rfc) - # ~ if not args: - # ~ return - - # ~ conectar(args) - # ~ log.info('Importando productos...') - - # ~ fields = ( - # ~ 'clave', - # ~ 'clave_sat', - # ~ 'unidad', - # ~ 'categoria', - # ~ 'descripcion', - # ~ 'valor_unitario', - # ~ 'existencia', - # ~ 'inventario', - # ~ 'codigo_barras', - # ~ 'cuenta_predial', - # ~ 'ultimo_precio', - # ~ 'minimo', - # ~ ) - - # ~ rows = util.read_file(archivo, 'r').split('\n') - # ~ for i, row in enumerate(rows): - # ~ if i == 0: - # ~ continue - # ~ data = row.split('|') - # ~ print (data) - # ~ new = {} - # ~ for i, f in enumerate(fields): - # ~ if not len(data[0]): - # ~ continue - - # ~ if i in (2, 3): - # ~ try: - # ~ new[f] = int(data[i]) - # ~ except ValueError: - # ~ continue - # ~ elif i in (5, 6, 10, 11): - # ~ new[f] = float(data[i]) - # ~ elif i == 7: - # ~ new[f] = bool(data[i]) - # ~ else: - # ~ new[f] = data[i] - - # ~ impuestos = data[i + 1:-1] - # ~ if not impuestos: - # ~ taxes = [SATImpuestos.select().where(SATImpuestos.id==6)] - # ~ else: - # ~ taxes = [] - # ~ try: - # ~ for i in range(0, len(impuestos), 4): - # ~ w = { - # ~ 'key': impuestos[i], - # ~ 'name': impuestos[i+1], - # ~ 'tipo': impuestos[i+2], - # ~ 'tasa': float(impuestos[i+3]), - # ~ } - # ~ taxes.append(SATImpuestos.get_o_crea(w)) - # ~ except IndexError: - # ~ print ('IE', data) - # ~ continue - - # ~ with database_proxy.transaction(): - # ~ try: - # ~ obj = Productos.create(**new) - # ~ obj.impuestos = taxes - # ~ except IntegrityError as e: - # ~ pass - - # ~ log.info('Importación terminada...') - # ~ return - - def _import_from_folder(path): files = util.get_files(path, 'json') if not files: @@ -10505,32 +10122,59 @@ def _migrate_cert(rfc): return -def _test(rfc): - if not rfc: - rfc = input('Introduce el RFC: ').strip().upper() - +def _import_clients(rfc, path): if not rfc: msg = 'El RFC es requerido' log.error(msg) return + if not path: + msg = 'El archivo CSV es necesario' + log.error(msg) + return + args = util.get_con(rfc) if not args: return conectar(args) - log.info('Test con...') + log.info('Importando clientes...') - query = Socios.select(Socios.id, Socios.nombre, Socios.rfc).where(Socios.nombre.contains('Lopez')) - print(query) + data = utils.read_csv(path) + t = len(data) + ok = 0 + for i, row in enumerate(data): + msg = f'\tImportando cliente {i+1} de {t}' + log.info(msg) + row['rfc'] = row['rfc'].upper() + row['nombre'] = utils.spaces(row['nombre']) + row['slug'] = utils.to_slug(row['nombre']) + + w = ((Socios.rfc==row['rfc']) & (Socios.slug==row['slug'])) + if Socios.select().where(w).exists(): + msg = '\tYa existe el RFC y Razón Social' + log.info(msg) + continue + + row['es_cliente'] = True + row['forma_pago'] = SATFormaPago.get(SATFormaPago.id==row['forma_pago']) + + try: + obj = Socios.create(**row) + ok += 1 + except Exception as e: + log.error(e) + break desconectar() - log.info('End test...') + msg = f'Total de clientes: {t}' + log.info(msg) + msg = f'Clientes nuevos: {ok}' + log.info(msg) + log.info('Proceso terminado correctamente...') return - - def _process_command_line_arguments(): parser = argparse.ArgumentParser( description='Empresa Libre') @@ -10554,11 +10198,13 @@ def _process_command_line_arguments(): action='store_true', default=False, required=False) parser.add_argument('-ed', '--export-documents', dest='export_documents', action='store_true', default=False, required=False) - + parser.add_argument('-ic', '--import-clients', dest='import_clients', + action='store_true', default=False, required=False) parser.add_argument('-mc' , '--migrate-cert', dest='migrate_cert', action='store_true', default=False, required=False) parser.add_argument('-r', '--rfc', dest='rfc', default='') + parser.add_argument('-f', '--file', dest='file', default='') return parser.parse_args() @@ -10603,7 +10249,9 @@ def main(args): _migrate_cert(args.rfc) return - # ~ _test(args.rfc) + if args.import_clients: + _import_clients(args.rfc, args.file) + return return diff --git a/source/app/settings.py b/source/app/settings.py index a23ca65..1fd9946 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -42,7 +42,7 @@ except ImportError: DEBUG = DEBUG -VERSION = '1.40.1' +VERSION = '1.41.0' EMAIL_SUPPORT = ('soporte@empresalibre.mx',) TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)