From aeb045e76ae3e1691102655c58cd8a8bef8819a8 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 11 Jan 2020 22:14:38 -0600 Subject: [PATCH 1/6] Check unique clients --- CHANGELOG.md | 12 + VERSION | 2 +- source/app/controllers/util.py | 515 -------------------------------- source/app/controllers/utils.py | 150 ++++++++-- source/app/models/db.py | 19 +- source/app/models/main.py | 263 +++++++++------- source/app/settings.py | 4 +- 7 files changed, 307 insertions(+), 658 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d58d3c..85e9105 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ + +v 1.33.0 [11-ene-2020] +---------------------- + - Mejora: Cambio del mensaje para cuando se intenta dar de alta un cliente ya existente. + - Mejora: Solo los admins pueden ver la nómina. + + +v 1.32.0 [05-ene-2020] +---------------------- + - Mejora: Recuperar facturas no aceptas para cancelación por el receptor + + v 1.31.2 [28-oct-2019] ---------------------- - Error: Al generar PDF con tags en las series diff --git a/VERSION b/VERSION index 359c410..7aa332e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.32.0 +1.33.0 diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index a5772b4..70a465c 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -2738,521 +2738,6 @@ 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'), - ('productos', 'Productos'), - ('tickets', 'Tickets'), - ) - 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 _tickets(self): - sql = "SELECT * FROM tickets" - self._cursor.execute(sql) - rows = self._cursor.fetchall() - - fields = ( - ('serie', 'serie'), - ('folio', 'folio'), - ('fecha', 'fecha'), - ('formadepago', 'forma_pago'), - ('subtotal', 'subtotal'), - ('descuento', 'descuento'), - ('total', 'total'), - ('notas', 'notas'), - ('factura', 'factura'), - ('cancelada', 'cancelado'), - ('vendedor', 'vendedor'), - ) - data = [] - totals = len(rows) - for i, row in enumerate(rows): - msg = '\tImportando ticket {} de {}'.format(i+1, totals) - log.info(msg) - - new = {t: row[s] for s, t in fields} - - new['notas'] = '' - new['fecha'] = new['fecha'].replace(microsecond=0) - new['estatus'] = 'Generado' - if new['cancelado']: - new['estatus'] = 'Cancelado' - new['factura'] = self._get_invoice_ticket(new['factura']) - - new['details'] = self._get_details_ticket(row['id']) - new['taxes'] = self._get_taxes_ticket(row['id']) - data.append(new) - - return data - - def _get_invoice_ticket(self, invoice): - if not invoice: - return None - - sql = "SELECT serie, folio FROM cfdifacturas WHERE id=%s" - self._cursor.execute(sql, [invoice]) - row = self._cursor.fetchone() - if row is None: - return {} - - return dict(row) - - def _get_details_ticket(self, id): - sql = "SELECT * FROM t_detalle WHERE id_cfdi=%s" - self._cursor.execute(sql, [id]) - rows = self._cursor.fetchall() - - fields = ( - ('descripcion', 'descripcion'), - ('cantidad', 'cantidad'), - ('valorunitario', 'valor_unitario'), - ('importe', 'importe'), - ('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_taxes_ticket(self, id): - sql = "SELECT * FROM t_impuestos WHERE id_cfdi=%s" - self._cursor.execute(sql, [id]) - rows = self._cursor.fetchall() - - tasas = { - '0': 0.0, - '16': 0.16, - '16.00': 0.16, - '0.16': 0.16, - '11': 0.11, - '-10': 0.10, - '-2': 0.02, - '-0.5': 0.005, - '-2/3': 0.106667, - '-10.6667': 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 = { - 'import': row['importe'], - 'filter': filtro - } - data.append(new) - - return data - - def _productos(self): - UNIDADES = { - 'k': 'KGM', - 'kg': 'KGM', - 'kg.': 'KGM', - 'pieza': 'H87', - 'pza': 'H87', - 'pz': 'H87', - 'bulto': 'H87', - 'b': 'H87', - 'exb': 'H87', - 'ex': 'H87', - 'caja': 'XBX', - 'c': 'XBX', - 'rollo': 'XRO', - 'tira': 'SR', - 't': 'SR', - 'cono': 'XAJ', - 'paquete': 'XPK', - 'pq': 'XPK', - } - sql = "SELECT * FROM productos" - self._cursor.execute(sql) - rows = self._cursor.fetchall() - - fields = ( - ('id_categoria', 'categoria'), - ('noidentificacion', 'clave'), - ('descripcion', 'descripcion'), - # ~ ('unidad', 'unidad'), - ('id_unidad', 'unidad'), - # ~ ('costo', 'ultimo_costo'), - ('valorunitario', 'valor_unitario'), - # ~ ('existencia', 'existencia'), - # ~ ('minimo', 'minimo'), - ('inventario', 'inventario'), - ('codigobarras', 'codigo_barras'), - ('cuentapredial', 'cuenta_predial'), - ) - data = [] - - sql = """ - SELECT nombre, tasa, tipo - FROM impuestos - WHERE id=%s - """ - totals = len(rows) - for i, row in enumerate(rows): - msg = '\tImportando producto {} de {}'.format(i+1, totals) - log.info(msg) - # ~ print (i, dict(row)) - new = {t: row[s] for s, t in fields} - - # ~ print (new['unidad']) - if new['unidad'] == 2: - new['unidad'] = 'servicio' - - u = new['unidad'].lower().strip() - if u in ('sin',): - continue - if not u: - u = 'pieza' - - if not new['categoria']: - new['categoria'] = None - new['codigo_barras'] = new['codigo_barras'] or '' - new['cuenta_predial'] = new['cuenta_predial'] or '' - new['descripcion'] = ' '.join(new['descripcion'].split()) - new['clave_sat'] = DEFAULT_SAT_PRODUCTO - - new['unidad'] = UNIDADES.get(u, new['unidad']) - self._cursor.execute(sql, [row['id_impuesto1']]) - 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, - '-4': 0.04, - } - - 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 = [] - totals = len(rows) - for i, row in enumerate(rows): - msg = '\tImportando factura {} de {}'.format(i+1, totals) - log.info(msg) - - new = {t: row[s] for s, t in fields} - - for _, f in fields: - new[f] = new[f] or '' - - - new['fecha'] = new['fecha'].replace(microsecond=0) - if new['fecha_timbrado']: - new['fecha_timbrado'] = new['fecha_timbrado'].replace(microsecond=0) - else: - new['fecha_timbrado'] = None - - new['estatus'] = 'Timbrada' - if new['cancelada']: - new['estatus'] = 'Cancelada' - - if not new['uuid']: - new['uuid'] = None - elif new['uuid'] in('ok', '123', '??', 'X'): - 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" - totals = len(rows) - for i, row in enumerate(rows): - msg = '\tImportando cliente {} de {}'.format(i+1, totals) - log.info(msg) - 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/controllers/utils.py b/source/app/controllers/utils.py index f653679..5baaf16 100644 --- a/source/app/controllers/utils.py +++ b/source/app/controllers/utils.py @@ -23,6 +23,7 @@ import json import logging import math import os +import shlex import shutil import smtplib import sqlite3 @@ -48,6 +49,8 @@ from dateutil import parser import seafileapi +from settings import DEBUG, DB_COMPANIES, PATHS + LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' LOG_DATE = '%d/%m/%Y %H:%M:%S' @@ -61,6 +64,11 @@ logging.getLogger('peewee').setLevel(logging.WARNING) TIMEOUT = 10 PATH_INVOICES = 'facturas' +PG_DUMP = 'pg_dump -U postgres' +PSQL = 'psql -U postgres' +if DEBUG: + PG_DUMP = 'pg_dump -h localhost -U postgres' + PSQL = 'psql -h localhost -U postgres' #~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37 @@ -259,6 +267,16 @@ def _call(args): return subprocess.check_output(args, shell=True).decode() +def _run(args, wait=False): + result = '' + cmd = shlex.split(args) + if wait: + result = subprocess.run(cmd, shell=True, check=True).stdout.decode() + else: + subprocess.run(cmd) + return result + + def _join(*paths): return os.path.join(*paths) @@ -332,29 +350,96 @@ def to_zip(files): return zip_buffer.getvalue() -def db_delete(user, path): - dt = datetime.datetime.now().strftime('%y%m%d_%H%M') - path_bk = _join(path, 'tmp', '{}_{}.bk'.format(user, dt)) - args = 'pg_dump -U postgres -Fc {} > "{}"'.format(user, path_bk) - try: - _call(args) - except: - pass +def dumps(data): + return json.dumps(data, default=str) - args = 'psql -U postgres -c "DROP DATABASE {0};"'.format(user) - try: - _call(args) - except: - pass - args = 'psql -U postgres -c "DROP ROLE {0};"'.format(user) - try: - _call(args) - except: - pass +def loads(data): + return json.loads(data) + + +def _validate_db_rfc(): + con = sqlite3.connect(DB_COMPANIES) + sql = """ + CREATE TABLE IF NOT EXISTS names( + rfc TEXT NOT NULL COLLATE NOCASE UNIQUE, + con TEXT NOT NULL + ); + """ + cursor = con.cursor() + cursor.executescript(sql) + cursor.close() + con.close() return +def _sql_companies(sql, args=()): + _validate_db_rfc() + con = sqlite3.connect(DB_COMPANIES) + cursor = con.cursor() + try: + cursor.execute(sql, args) + data = cursor.fetchall() + except sqlite3.IntegrityError as e: + log.error(e) + return False + + con.commit() + cursor.close() + con.close() + return data + + +def rfc_get(): + sql = "SELECT * FROM names" + data = _sql_companies(sql) + return data + + +def rfc_exists(rfc): + sql = "SELECT rfc FROM names WHERE rfc = ?" + data = _sql_companies(sql, (rfc,)) + if isinstance(data, bool): + return + return bool(data) + + +def rfc_add(rfc, con): + sql = "INSERT INTO names VALUES (?, ?)" + data = _sql_companies(sql, (rfc.upper(), dumps(con))) + return True + + +def db_create(user): + args = f'{PSQL} -c "CREATE ROLE {user} WITH LOGIN ENCRYPTED PASSWORD \'{user}\';"' + _run(args) + args = f'{PSQL} -c "CREATE DATABASE {user} WITH OWNER {user};"' + _run(args) + return True + + +def db_delete(user, path, no_database=False): + sql = "DELETE FROM names WHERE rfc = ?" + data = _sql_companies(sql, (user,)) + + if no_database: + return True + + user = user.replace('&', '').lower() + dt = now().strftime('%y%m%d_%H%M') + path_bk = _join(path, f'{user}_{dt}.bk') + + args = f'{PG_DUMP} -d {user} -Fc -f "{path_bk}"' + _run(args) + + args = f'{PSQL} -c "DROP DATABASE {user};"' + _run(args) + + args = f'{PSQL} -c "DROP ROLE {user};"' + _run(args) + return True + + def _get_pass(rfc): return rfc @@ -417,6 +502,35 @@ def db_backup(path_companies, path_bk, is_mv, url_seafile): return +def _validate_path_local(): + path_bk = _join(str(Path.home()), PATHS['LOCAL']) + if not os.path.isdir(path_bk): + path_bk = '' + return path_bk + + +def db_backup_local(): + path_bk = _validate_path_local() + if not path_bk: + msg = 'No existe la carpeta local' + return {'ok': False, 'msg': msg} + + data = rfc_get() + if not len(data): + msg = 'Sin bases de datos a respaldar' + return {'ok': False, 'msg': msg} + + for row in data: + user = row[0].lower() + db = loads(row[1])['name'] + path = _join(path_bk, '{}.bk'.format(user)) + args = f'{PG_DUMP} -d {user} -Fc -f "{path}"' + _run(args) + + msg = 'Bases de datos respaldadas correctamente' + result = {'ok': True, 'msg': msg} + return result + def now(): return datetime.datetime.now().replace(microsecond=0) diff --git a/source/app/models/db.py b/source/app/models/db.py index 78c04da..b302198 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -34,19 +34,18 @@ class StorageEngine(object): return main.CfdiNomina.get_by(values) def empresa_agregar(self, values): - return main.empresa_agregar(values['alta_rfc'], False) + # ~ return main.empresa_agregar(values['alta_rfc'], False) + return main._new_client(values['alta_rfc'], False) def empresa_borrar(self, values): - return main.empresa_borrar(values['rfc']) + # ~ return main.empresa_borrar(values['rfc']) + return main._delete_client(values['rfc'], False, False) def respaldar_dbs(self): return main.respaldar_dbs() - def _get_empresas(self, values): - return main.get_empresas() - def get_values(self, table, values=None, session=None): - if table in ('allusuarios', 'usuarioupdate'): + if table in ('allusuarios', 'usuarioupdate', 'main'): return getattr(self, '_get_{}'.format(table))(values, session) return getattr(self, '_get_{}'.format(table))(values) @@ -71,8 +70,8 @@ class StorageEngine(object): def _get_importinvoice(self, values): return main.import_invoice() - def _get_main(self, values): - return main.config_main() + def _get_main(self, values, session): + return main.config_main(session['userobj']) def _get_configtimbrar(self, values): return main.config_timbrar() @@ -463,3 +462,7 @@ class StorageEngine(object): def nomina(self, values, user): return main.CfdiNomina.post(values, user) + # Companies only in MV + def _get_empresas(self, values): + return main.companies_get() + diff --git a/source/app/models/main.py b/source/app/models/main.py index df26dba..0d60249 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -16,6 +16,7 @@ # ~ You should have received a copy of the GNU General Public License # ~ along with this program. If not, see . +import argparse from decimal import Decimal import sqlite3 import click @@ -31,7 +32,7 @@ if __name__ == '__main__': from controllers import util -from settings import log, DEBUG, COMPANIES, VERSION, PATH_CP, PRE, CURRENT_CFDI, \ +from settings import log, COMPANIES, VERSION, PATH_CP, PRE, CURRENT_CFDI, \ INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \ CANCEL_SIGNATURE, PUBLIC, DEFAULT_SERIE_TICKET, CURRENT_CFDI_NOMINA, \ DEFAULT_SAT_NOMINA, DECIMALES_TAX, TITLE_APP, MV, DECIMALES_PRECIOS, \ @@ -40,6 +41,7 @@ from settings import log, DEBUG, COMPANIES, VERSION, PATH_CP, PRE, CURRENT_CFDI, # ~ v2 from controllers import utils from settings import ( + DEBUG, DB_COMPANIES, EXT, IS_MV, @@ -249,14 +251,18 @@ def get_doc(type_doc, id, rfc): return data, file_name, content_type -def config_main(): +def config_main(user): try: obj = Emisor.select()[0] except IndexError: obj = None punto_de_venta = Configuracion.get_bool('chk_usar_punto_de_venta') + nomina = Configuracion.get_bool('chk_usar_nomina') + if not user.es_admin: + nomina = False + data = { 'empresa': get_title_app(3), 'punto_de_venta': punto_de_venta, @@ -2632,10 +2638,17 @@ class Socios(BaseModel): def add(cls, values): accounts = util.loads(values.pop('accounts', '[]')) fields = cls._clean(cls, values) + + w = ((Socios.rfc==fields['rfc']) & (Socios.slug==fields['slug'])) + if Socios.select().where(w).exists(): + msg = 'Ya existe el RFC y Razón Social' + data = {'ok': False, 'row': {}, 'new': True, 'msg': msg} + return data + try: obj = Socios.create(**fields) except IntegrityError as e: - msg = 'Ya existe el RFC y Razón Social' + msg = 'Ocurrio un error, al dar de alta el emisor' data = {'ok': False, 'row': {}, 'new': True, 'msg': msg} return data @@ -9349,49 +9362,20 @@ def _iniciar_bd(): 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() + rfc = input('Introduce el RFC a borrar: ').strip().lower() if not rfc: msg = 'El RFC es requerido' log.error(msg) return - confirm = input('¿Estás seguro de borrar el RFC?') + confirm = input('¿Estás seguro de borrar el RFC? [si]') + if confirm != 'si': + log.info('Proceso cancelado...') + return - if _delete_emisor(rfc): - util.delete_db(rfc.lower()) + if _delete_emisor(rfc.upper()): + utils.db_delete(rfc, PATHS['BK']) log.info('RFC borrado correctamente...') return @@ -9460,9 +9444,7 @@ def empresa_agregar(rfc, no_bd): def empresa_borrar(rfc): - if _delete_emisor(rfc): - # ~ util.delete_db(rfc.lower()) - utils.db_delete(rfc.lower(), PATHS['DOCS']) + utils.db_delete(rfc.lower(), PATHS['BK']) return True @@ -9471,21 +9453,8 @@ def respaldar_dbs(): msg = 'Solo MV' return {'ok': False, 'msg': msg} - result = util.validate_path_bk() - if not result['ok']: - return result - path_bk = result['msg'] - - data = util.get_rfcs() - if not len(data): - msg = 'Sin bases de datos a respaldar' - return {'ok': False, 'msg': msg} - - for row in data: - util.respaldar_db(row, path_bk) - - msg = 'Bases de datos respaldadas correctamente' - return {'ok': True, 'msg': msg} + result = utils.db_backup_local() + return result def _importar_valores(archivo='', rfc=''): @@ -9776,37 +9745,6 @@ 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']) - _importar_productos_gambas(data['Productos']) - _import_tickets(data['Tickets']) - - log.info('Importación terminada...') - return - - def _exist_ticket(row): filters = ( (Tickets.serie==row['serie']) & @@ -10074,18 +10012,126 @@ def _exportar_documentos(): return -def _test(): - rfc = input('Introduce el RFC: ').strip().upper() +# ~ v2 + +def companies_get(): + data = utils.rfc_get() + rows = [] + for row in data: + rows.append({'delete': '-', 'rfc': row[0].upper()}) + return tuple(rows) + + +def _list_clients(): + rows = utils.rfc_get() + for row in rows: + msg = f'RFC: {row[0].upper()}' + print(msg) + return + + +def _new_client(rfc, no_database): + rfc = rfc.lower() if not rfc: - msg = 'El RFC es requerido' + log.error('Falta el RFC') + return + + if utils.rfc_exists(rfc): + msg = 'El RFC ya esta dado de alta' log.error(msg) + return {'ok': False, 'msg': msg} + + user = rfc.replace('&', '').lower() + if not no_database: + if not utils.db_create(user): + msg = 'No se pudo crear la base de datos' + log.error(msg) + return {'ok': False, 'msg': msg} + + args = { + 'type': 'postgres', + 'name': user, + 'user': user, + 'password': user, + } + if not conectar(args.copy()): + msg = 'No se pudo conectar a la base de datos' + log.error(msg) + return {'ok': False, 'msg': msg} + + if not utils.rfc_add(rfc, args): + msg = 'No se pudo guardar el nuevo emisor' + log.error(msg) + return {'ok': False, 'msg': msg} + + if not no_database: + if not _crear_tablas(rfc): + msg = 'No se pudo crear las tablas' + log.error(msg) + return {'ok': False, 'msg': msg} + + desconectar() + msg = 'Emisor dado de alta correctamente' + row = {'delete': '-', 'rfc': rfc.upper()} + result = {'ok': True, 'msg': msg, 'row': row} + return result + + +def _delete_client(rfc, no_database, ask=True): + rfc = rfc.lower() + if not rfc: + log.error('Falta el RFC') return - args = util.get_con(rfc) - if not args: + if not utils.rfc_exists(rfc): + msg = 'El RFC no esta dado de alta' + log.error(msg) + return {'ok': False, 'msg': msg} + + if ask: + confirm = input('¿Estás seguro de borrar el RFC? [si]') + if confirm != 'si': + log.info('Proceso cancelado...') + return False + + if utils.db_delete(rfc, PATHS['BK'], no_database): + log.info('RFC borrado correctamente...') + result = True + else: + log.error('No se pudo borrar el RFC') + result = False + + return result + + +def _process_command_line_arguments(): + parser = argparse.ArgumentParser( + description='Empresa Libre') + parser.add_argument('-lc', '--list-clients', dest='list_clients', + action='store_true', default=False, required=False) + parser.add_argument('-nc', '--new-client', dest='new_client', + action='store_true', default=False, required=False) + parser.add_argument('-dc', '--delete-client', dest='delete_client', + action='store_true', default=False, required=False) + parser.add_argument('-ndb', '--no-database', dest='no_database', + action='store_true', default=False, required=False) + + parser.add_argument('-r', '--rfc', dest='rfc', default='') + return parser.parse_args() + + +def main2(args): + if args.list_clients: + _list_clients() return - conectar(args) + if args.new_client: + _new_client(args.rfc, args.no_database) + return + + if args.delete_client: + _delete_client(args.rfc, args.no_database) + return return @@ -10108,14 +10154,12 @@ help_lr = 'Listar RFCs' 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) @@ -10127,22 +10171,19 @@ help_lr = 'Listar RFCs' @click.option('-id', '--importar-directorio') @click.option('-ed', '--exportar_documentos', 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, + borrar_rfc, listar_rfc, importar_valores, archivo, conexion, + factura_libre, test, generar_archivo_productos, importar_productos, backup_dbs, no_bd, alta, rfc, detalle, importar_directorio, exportar_documentos): 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'], no_bd) + # ~ empresa_agregar(opt['rfc'], no_bd) + _new_client(opt['rfc'], no_bd) sys.exit(0) if opt['iniciar_bd']: @@ -10161,10 +10202,6 @@ def main(iniciar_bd, migrar_bd, nuevo_superusuario, 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) @@ -10199,13 +10236,6 @@ def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, _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' @@ -10252,5 +10282,8 @@ def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, if __name__ == '__main__': - main() + args = _process_command_line_arguments() + main2(args) + # ~ main() + diff --git a/source/app/settings.py b/source/app/settings.py index fcb3570..4b9a4ba 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -47,7 +47,7 @@ except ImportError: DEBUG = DEBUG -VERSION = '1.32.0' +VERSION = '1.33.0' EMAIL_SUPPORT = ('soporte@empresalibre.mx',) TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION) @@ -200,6 +200,7 @@ CURRENCY_MN = 'MXN' IS_MV = MV DB_COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db')) path_bk = os.path.join(path_docs, 'tmp') +path_local = 'facturas' EXT = { 'CSS': 'css', @@ -218,6 +219,7 @@ PATHS = { 'USER': path_user_template, 'LOGOS': path_user_logos, 'BK': path_bk, + 'LOCAL': path_local, } VALUES_PDF = { From fe09860922815f30a16fbef008f08533173612b0 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 12 Jan 2020 22:40:45 -0600 Subject: [PATCH 2/6] Update catalogos SAT --- source/app/controllers/utils.py | 4 + source/app/models/main.py | 35 +- source/app/settings.py | 2 + source/db/valores_iniciales.json | 989 ++++++++++++++++++++++--------- 4 files changed, 761 insertions(+), 269 deletions(-) diff --git a/source/app/controllers/utils.py b/source/app/controllers/utils.py index 5baaf16..7c58cb7 100644 --- a/source/app/controllers/utils.py +++ b/source/app/controllers/utils.py @@ -358,6 +358,10 @@ def loads(data): return json.loads(data) +def json_loads(path): + return json.loads(open(path, 'r').read()) + + def _validate_db_rfc(): con = sqlite3.connect(DB_COMPANIES) sql = """ diff --git a/source/app/models/main.py b/source/app/models/main.py index 0d60249..6695639 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -9009,7 +9009,7 @@ def _init_values(rfc): pass if not Certificado.select().count(): - Certificado.create(rfc=rfc) + Certificado.create(rfc=rfc.upper()) log.info('Valores iniciales insertados...') return @@ -10104,6 +10104,26 @@ def _delete_client(rfc, no_database, ask=True): return result +def _update_sat(): + clients = utils.rfc_get() + tables = utils.json_loads(PATHS['SAT']) + for rfc, con in clients: + log.info(f'Importando datos en: {rfc.upper()}') + conectar(utils.loads(con)) + for table in tables: + t = globals()[table['tabla']] + with database_proxy.atomic(): + for r in table['datos']: + try: + t.get_or_create(**r) + except: + pass + log.info(f"\tTabla importada: {table['tabla']}") + desconectar() + log.info('Importación terminada...') + return + + def _process_command_line_arguments(): parser = argparse.ArgumentParser( description='Empresa Libre') @@ -10115,12 +10135,14 @@ def _process_command_line_arguments(): action='store_true', default=False, required=False) parser.add_argument('-ndb', '--no-database', dest='no_database', action='store_true', default=False, required=False) + parser.add_argument('-us', '--update-sat', dest='update_sat', + action='store_true', default=False, required=False) parser.add_argument('-r', '--rfc', dest='rfc', default='') return parser.parse_args() -def main2(args): +def main(args): if args.list_clients: _list_clients() return @@ -10133,6 +10155,9 @@ def main2(args): _delete_client(args.rfc, args.no_database) return + if args.update_sat: + _update_sat() + return @@ -10170,7 +10195,7 @@ help_lr = 'Listar RFCs' @click.option('-d', '--detalle', is_flag=True, default=False) @click.option('-id', '--importar-directorio') @click.option('-ed', '--exportar_documentos', is_flag=True, default=False) -def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, +def main2(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, borrar_rfc, listar_rfc, importar_valores, archivo, conexion, factura_libre, test, generar_archivo_productos, importar_productos, backup_dbs, no_bd, alta, rfc, detalle, @@ -10283,7 +10308,7 @@ def main(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, if __name__ == '__main__': args = _process_command_line_arguments() - main2(args) - # ~ main() + main(args) + # ~ main2() diff --git a/source/app/settings.py b/source/app/settings.py index 4b9a4ba..91ae2c8 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -201,6 +201,7 @@ IS_MV = MV DB_COMPANIES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'rfc.db')) path_bk = os.path.join(path_docs, 'tmp') path_local = 'facturas' +path_sat = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'valores_iniciales.json')) EXT = { 'CSS': 'css', @@ -220,6 +221,7 @@ PATHS = { 'LOGOS': path_user_logos, 'BK': path_bk, 'LOCAL': path_local, + 'SAT': path_sat, } VALUES_PDF = { diff --git a/source/db/valores_iniciales.json b/source/db/valores_iniciales.json index d6083f2..5b63380 100644 --- a/source/db/valores_iniciales.json +++ b/source/db/valores_iniciales.json @@ -117,102 +117,543 @@ ] }, { - "tabla": "SATBancos", "datos": [ - {"key": "002", "name": "BANAMEX", "razon_social": "Banco Nacional de México, S.A., Institución de Banca Múltiple, Grupo Financiero Banamex"}, - {"key": "006", "name": "BANCOMEXT", "razon_social": "Banco Nacional de Comercio Exterior, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, - {"key": "009", "name": "BANOBRAS", "razon_social": "Banco Nacional de Obras y Servicios Públicos, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, - {"key": "012", "name": "BBVABANCOMER", "razon_social": "BBVA Bancomer, S.A., Institución de Banca Múltiple, Grupo Financiero BBVA Bancomer"}, - {"key": "014", "name": "SANTANDER", "razon_social": "Banco Santander (México), S.A., Institución de Banca Múltiple, Grupo Financiero Santander"}, - {"key": "019", "name": "BANJERCITO", "razon_social": "Banco Nacional del Ejército, Fuerza Aérea y Armada, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, - {"key": "021", "name": "HSBC", "razon_social": "HSBC México, S.A., institución De Banca Múltiple, Grupo Financiero HSBC"}, - {"key": "030", "name": "BAJIO", "razon_social": "Banco del Bajío, S.A., Institución de Banca Múltiple"}, - {"key": "106", "name": "BAMSA", "razon_social": "Bank of America México, S.A., Institución de Banca Múltiple, Grupo Financiero Bank of America"}, - {"key": "032", "name": "IXE", "razon_social": "IXE Banco, S.A., Institución de Banca Múltiple, IXE Grupo Financiero"}, - {"key": "036", "name": "INBURSA", "razon_social": "Banco Inbursa, S.A., Institución de Banca Múltiple, Grupo Financiero Inbursa"}, - {"key": "037", "name": "INTERACCIONES", "razon_social": "Banco Interacciones, S.A., Institución de Banca Múltiple"}, - {"key": "042", "name": "MIFEL", "razon_social": "Banca Mifel, S.A., Institución de Banca Múltiple, Grupo Financiero Mifel"}, - {"key": "044", "name": "SCOTIABANK", "razon_social": "Scotiabank Inverlat, S.A."}, - {"key": "059", "name": "INVEX", "razon_social": "Banco Invex, S.A., Institución de Banca Múltiple, Invex Grupo Financiero"}, - {"key": "058", "name": "BANREGIO", "razon_social": "Banco Regional de Monterrey, S.A., Institución de Banca Múltiple, Banregio Grupo Financiero"}, - {"key": "060", "name": "BANSI", "razon_social": "Bansi, S.A., Institución de Banca Múltiple"}, - {"key": "062", "name": "AFIRME", "razon_social": "Banca Afirme, S.A., Institución de Banca Múltiple"}, - {"key": "072", "name": "BANORTE", "razon_social": "Banco Mercantil del Norte, S.A., Institución de Banca Múltiple, Grupo Financiero Banorte"}, - {"key": "102", "name": "THE ROYAL BANK", "razon_social": "The Royal Bank of Scotland México, S.A., Institución de Banca Múltiple"}, - {"key": "103", "name": "AMERICAN EXPRESS", "razon_social": "American Express Bank (México), S.A., Institución de Banca Múltiple"}, - {"key": "108", "name": "TOKYO", "razon_social": "Bank of Tokyo-Mitsubishi UFJ (México), S.A."}, - {"key": "110", "name": "JP MORGAN", "razon_social": "Banco J.P. Morgan, S.A., Institución de Banca Múltiple, J.P. Morgan Grupo Financiero"}, - {"key": "112", "name": "BMONEX", "razon_social": "Banco Monex, S.A., Institución de Banca Múltiple"}, - {"key": "113", "name": "VE POR MAS", "razon_social": "Banco Ve Por Mas, S.A. Institución de Banca Múltiple"}, - {"key": "116", "name": "ING", "razon_social": "ING Bank (México), S.A., Institución de Banca Múltiple, ING Grupo Financiero"}, - {"key": "124", "name": "DEUTSCHE", "razon_social": "Deutsche Bank México, S.A., Institución de Banca Múltiple"}, - {"key": "126", "name": "CREDIT SUISSE", "razon_social": "Banco Credit Suisse (México), S.A. Institución de Banca Múltiple, Grupo Financiero Credit Suisse (México)"}, - {"key": "127", "name": "AZTECA", "razon_social": "Banco Azteca, S.A. Institución de Banca Múltiple."}, - {"key": "128", "name": "AUTOFIN", "razon_social": "Banco Autofin México, S.A. Institución de Banca Múltiple"}, - {"key": "129", "name": "BARCLAYS", "razon_social": "Barclays Bank México, S.A., Institución de Banca Múltiple, Grupo Financiero Barclays México"}, - {"key": "130", "name": "COMPARTAMOS", "razon_social": "Banco Compartamos, S.A., Institución de Banca Múltiple"}, - {"key": "131", "name": "BANCO FAMSA", "razon_social": "Banco Ahorro Famsa, S.A., Institución de Banca Múltiple"}, - {"key": "132", "name": "BMULTIVA", "razon_social": "Banco Multiva, S.A., Institución de Banca Múltiple, Multivalores Grupo Financiero"}, - {"key": "133", "name": "ACTINVER", "razon_social": "Banco Actinver, S.A. Institución de Banca Múltiple, Grupo Financiero Actinver"}, - {"key": "134", "name": "WAL-MART", "razon_social": "Banco Wal-Mart de México Adelante, S.A., Institución de Banca Múltiple"}, - {"key": "135", "name": "NAFIN", "razon_social": "Nacional Financiera, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, - {"key": "136", "name": "INTERBANCO", "razon_social": "Inter Banco, S.A. Institución de Banca Múltiple"}, - {"key": "137", "name": "BANCOPPEL", "razon_social": "BanCoppel, S.A., Institución de Banca Múltiple"}, - {"key": "138", "name": "ABC CAPITAL", "razon_social": "ABC Capital, S.A., Institución de Banca Múltiple"}, - {"key": "139", "name": "UBS BANK", "razon_social": "UBS Bank México, S.A., Institución de Banca Múltiple, UBS Grupo Financiero"}, - {"key": "140", "name": "CONSUBANCO", "razon_social": "Consubanco, S.A. Institución de Banca Múltiple"}, - {"key": "141", "name": "VOLKSWAGEN", "razon_social": "Volkswagen Bank, S.A., Institución de Banca Múltiple"}, - {"key": "143", "name": "CIBANCO", "razon_social": "CIBanco, S.A."}, - {"key": "145", "name": "BBASE", "razon_social": "Banco Base, S.A., Institución de Banca Múltiple"}, - {"key": "166", "name": "BANSEFI", "razon_social": "Banco del Ahorro Nacional y Servicios Financieros, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, - {"key": "168", "name": "HIPOTECARIA FEDERAL", "razon_social": "Sociedad Hipotecaria Federal, Sociedad Nacional de Crédito, Institución de Banca de Desarrollo"}, - {"key": "600", "name": "MONEXCB", "razon_social": "Monex Casa de Bolsa, S.A. de C.V. Monex Grupo Financiero"}, - {"key": "601", "name": "GBM", "razon_social": "GBM Grupo Bursátil Mexicano, S.A. de C.V. Casa de Bolsa"}, - {"key": "602", "name": "MASARI", "razon_social": "Masari Casa de Bolsa, S.A."}, - {"key": "605", "name": "VALUE", "razon_social": "Value, S.A. de C.V. Casa de Bolsa"}, - {"key": "606", "name": "ESTRUCTURADORES", "razon_social": "Estructuradores del Mercado de Valores Casa de Bolsa, S.A. de C.V."}, - {"key": "607", "name": "TIBER", "razon_social": "Casa de Cambio Tiber, S.A. de C.V."}, - {"key": "608", "name": "VECTOR", "razon_social": "Vector Casa de Bolsa, S.A. de C.V."}, - {"key": "610", "name": "B&B", "razon_social": "B y B, Casa de Cambio, S.A. de C.V."}, - {"key": "614", "name": "ACCIVAL", "razon_social": "Acciones y Valores Banamex, S.A. de C.V., Casa de Bolsa"}, - {"key": "615", "name": "MERRILL LYNCH", "razon_social": "Merrill Lynch México, S.A. de C.V. Casa de Bolsa"}, - {"key": "616", "name": "FINAMEX", "razon_social": "Casa de Bolsa Finamex, S.A. de C.V."}, - {"key": "617", "name": "VALMEX", "razon_social": "Valores Mexicanos Casa de Bolsa, S.A. de C.V."}, - {"key": "618", "name": "UNICA", "razon_social": "Unica Casa de Cambio, S.A. de C.V."}, - {"key": "619", "name": "MAPFRE", "razon_social": "MAPFRE Tepeyac, S.A."}, - {"key": "620", "name": "PROFUTURO", "razon_social": "Profuturo G.N.P., S.A. de C.V., Afore"}, - {"key": "621", "name": "CB ACTINVER", "razon_social": "Actinver Casa de Bolsa, S.A. de C.V."}, - {"key": "622", "name": "OACTIN", "razon_social": "OPERADORA ACTINVER, S.A. DE C.V."}, - {"key": "623", "name": "SKANDIA", "razon_social": "Skandia Vida, S.A. de C.V."}, - {"key": "626", "name": "CBDEUTSCHE", "razon_social": "Deutsche Securities, S.A. de C.V. CASA DE BOLSA"}, - {"key": "627", "name": "ZURICH", "razon_social": "Zurich Compañía de Seguros, S.A."}, - {"key": "628", "name": "ZURICHVI", "razon_social": "Zurich Vida, Compañía de Seguros, S.A."}, - {"key": "629", "name": "SU CASITA", "razon_social": "Hipotecaria Su Casita, S.A. de C.V. SOFOM ENR"}, - {"key": "630", "name": "CB INTERCAM", "razon_social": "Intercam Casa de Bolsa, S.A. de C.V."}, - {"key": "631", "name": "CI BOLSA", "razon_social": "CI Casa de Bolsa, S.A. de C.V."}, - {"key": "632", "name": "BULLTICK CB", "razon_social": "Bulltick Casa de Bolsa, S.A., de C.V."}, - {"key": "633", "name": "STERLING", "razon_social": "Sterling Casa de Cambio, S.A. de C.V."}, - {"key": "634", "name": "FINCOMUN", "razon_social": "Fincomún, Servicios Financieros Comunitarios, S.A. de C.V."}, - {"key": "636", "name": "HDI SEGUROS", "razon_social": "HDI Seguros, S.A. de C.V."}, - {"key": "637", "name": "ORDER", "razon_social": "Order Express Casa de Cambio, S.A. de C.V"}, - {"key": "638", "name": "AKALA", "razon_social": "Akala, S.A. de C.V., Sociedad Financiera Popular"}, - {"key": "640", "name": "CB JPMORGAN", "razon_social": "J.P. Morgan Casa de Bolsa, S.A. de C.V. J.P. Morgan Grupo Financiero"}, - {"key": "642", "name": "REFORMA", "razon_social": "Operadora de Recursos Reforma, S.A. de C.V., S.F.P."}, - {"key": "646", "name": "STP", "razon_social": "Sistema de Transferencias y Pagos STP, S.A. de C.V.SOFOM ENR"}, - {"key": "647", "name": "TELECOMM", "razon_social": "Telecomunicaciones de México"}, - {"key": "648", "name": "EVERCORE", "razon_social": "Evercore Casa de Bolsa, S.A. de C.V."}, - {"key": "649", "name": "SKANDIA", "razon_social": "Skandia Operadora de Fondos, S.A. de C.V."}, - {"key": "651", "name": "SEGMTY", "razon_social": "Seguros Monterrey New York Life, S.A de C.V"}, - {"key": "652", "name": "ASEA", "razon_social": "Solución Asea, S.A. de C.V., Sociedad Financiera Popular"}, - {"key": "653", "name": "KUSPIT", "razon_social": "Kuspit Casa de Bolsa, S.A. de C.V."}, - {"key": "655", "name": "SOFIEXPRESS", "razon_social": "J.P. SOFIEXPRESS, S.A. de C.V., S.F.P."}, - {"key": "656", "name": "UNAGRA", "razon_social": "UNAGRA, S.A. de C.V., S.F.P."}, - {"key": "659", "name": "OPCIONES EMPRESARIALES DEL NOROESTE", "razon_social": "OPCIONES EMPRESARIALES DEL NORESTE, S.A. DE C.V., S.F.P."}, - {"key": "670", "name": "LIBERTAD", "razon_social": "Libertad Servicios Financieros, S.A. De C.V."}, - {"key": "901", "name": "CLS", "razon_social": "Cls Bank International"}, - {"key": "902", "name": "INDEVAL", "razon_social": "SD. Indeval, S.A. de C.V."}, - {"key": "999", "name": "N/A"} - ] + { + "key": "002", + "name": "BANAMEX", + "razon_social": "Banco Nacional de M\u00e9xico, S.A., Instituci\u00f3n de Banca M\u00faltiple, Grupo Financiero Banamex" + }, + { + "key": "006", + "name": "BANCOMEXT", + "razon_social": "Banco Nacional de Comercio Exterior, Sociedad Nacional de Cr\u00e9dito, Instituci\u00f3n de Banca de Desarrollo" + }, + { + "key": "009", + "name": "BANOBRAS", + "razon_social": "Banco Nacional de Obras y Servicios P\u00fablicos, Sociedad Nacional de Cr\u00e9dito, Instituci\u00f3n de Banca de Desarrollo" + }, + { + "key": "012", + "name": "BBVA BANCOMER", + "razon_social": "BBVA Bancomer, S.A., Instituci\u00f3n de Banca M\u00faltiple, Grupo Financiero BBVA Bancomer" + }, + { + "key": "014", + "name": "SANTANDER", + "razon_social": "Banco Santander (M\u00e9xico), S.A., Instituci\u00f3n de Banca M\u00faltiple, Grupo Financiero Santander" + }, + { + "key": "019", + "name": "BANJERCITO", + "razon_social": "Banco Nacional del Ej\u00e9rcito, Fuerza A\u00e9rea y Armada, Sociedad Nacional de Cr\u00e9dito, Instituci\u00f3n de Banca de Desarrollo" + }, + { + "key": "021", + "name": "HSBC", + "razon_social": "HSBC M\u00e9xico, S.A., instituci\u00f3n De Banca M\u00faltiple, Grupo Financiero HSBC" + }, + { + "key": "030", + "name": "BAJIO", + "razon_social": "Banco del Baj\u00edo, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "032", + "name": "IXE", + "razon_social": "IXE Banco, S.A., Instituci\u00f3n de Banca M\u00faltiple, IXE Grupo Financiero" + }, + { + "key": "036", + "name": "INBURSA", + "razon_social": "Banco Inbursa, S.A., Instituci\u00f3n de Banca M\u00faltiple, Grupo Financiero Inbursa" + }, + { + "key": "037", + "name": "INTERACCIONES", + "razon_social": "Banco Interacciones, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "042", + "name": "MIFEL", + "razon_social": "Banca Mifel, S.A., Instituci\u00f3n de Banca M\u00faltiple, Grupo Financiero Mifel" + }, + { + "key": "044", + "name": "SCOTIABANK", + "razon_social": "Scotiabank Inverlat, S.A." + }, + { + "key": "058", + "name": "BANREGIO", + "razon_social": "Banco Regional de Monterrey, S.A., Instituci\u00f3n de Banca M\u00faltiple, Banregio Grupo Financiero" + }, + { + "key": "059", + "name": "INVEX", + "razon_social": "Banco Invex, S.A., Instituci\u00f3n de Banca M\u00faltiple, Invex Grupo Financiero" + }, + { + "key": "060", + "name": "BANSI", + "razon_social": "Bansi, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "062", + "name": "AFIRME", + "razon_social": "Banca Afirme, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "072", + "name": "BANORTE/IXE", + "razon_social": "Banco Mercantil del Norte, S.A., Instituci\u00f3n de Banca M\u00faltiple, Grupo Financiero Banorte" + }, + { + "key": "102", + "name": "THE ROYAL BANK", + "razon_social": "The Royal Bank of Scotland M\u00e9xico, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "103", + "name": "AMERICAN EXPRESS", + "razon_social": "American Express Bank (M\u00e9xico), S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "106", + "name": "BAMSA", + "razon_social": "Bank of America M\u00e9xico, S.A., Instituci\u00f3n de Banca M\u00faltiple, Grupo Financiero Bank of America" + }, + { + "key": "108", + "name": "TOKYO", + "razon_social": "Bank of Tokyo-Mitsubishi UFJ (M\u00e9xico), S.A." + }, + { + "key": "110", + "name": "JP MORGAN", + "razon_social": "Banco J.P. Morgan, S.A., Instituci\u00f3n de Banca M\u00faltiple, J.P. Morgan Grupo Financiero" + }, + { + "key": "112", + "name": "BMONEX", + "razon_social": "Banco Monex, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "113", + "name": "VE POR MAS", + "razon_social": "Banco Ve Por Mas, S.A. Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "116", + "name": "ING", + "razon_social": "ING Bank (M\u00e9xico), S.A., Instituci\u00f3n de Banca M\u00faltiple, ING Grupo Financiero" + }, + { + "key": "124", + "name": "DEUTSCHE", + "razon_social": "Deutsche Bank M\u00e9xico, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "126", + "name": "CREDIT SUISSE", + "razon_social": "Banco Credit Suisse (M\u00e9xico), S.A. Instituci\u00f3n de Banca M\u00faltiple, Grupo Financiero Credit Suisse (M\u00e9xico)" + }, + { + "key": "127", + "name": "AZTECA", + "razon_social": "Banco Azteca, S.A. Instituci\u00f3n de Banca M\u00faltiple." + }, + { + "key": "128", + "name": "AUTOFIN", + "razon_social": "Banco Autofin M\u00e9xico, S.A. Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "129", + "name": "BARCLAYS", + "razon_social": "Barclays Bank M\u00e9xico, S.A., Instituci\u00f3n de Banca M\u00faltiple, Grupo Financiero Barclays M\u00e9xico" + }, + { + "key": "130", + "name": "COMPARTAMOS", + "razon_social": "Banco Compartamos, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "131", + "name": "BANCO FAMSA", + "razon_social": "Banco Ahorro Famsa, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "132", + "name": "BMULTIVA", + "razon_social": "Banco Multiva, S.A., Instituci\u00f3n de Banca M\u00faltiple, Multivalores Grupo Financiero" + }, + { + "key": "133", + "name": "ACTINVER", + "razon_social": "Banco Actinver, S.A. Instituci\u00f3n de Banca M\u00faltiple, Grupo Financiero Actinver" + }, + { + "key": "134", + "name": "WAL-MART", + "razon_social": "Banco Wal-Mart de M\u00e9xico Adelante, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "135", + "name": "NAFIN", + "razon_social": "Nacional Financiera, Sociedad Nacional de Cr\u00e9dito, Instituci\u00f3n de Banca de Desarrollo" + }, + { + "key": "136", + "name": "INTERCAM BANCO", + "razon_social": "Intercam Banco, S.A., Instituci\u00f3n de Banca M\u00faltiple, Intercam Grupo Financiero" + }, + { + "key": "137", + "name": "BANCOPPEL", + "razon_social": "BanCoppel, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "138", + "name": "ABC CAPITAL", + "razon_social": "ABC Capital, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "139", + "name": "UBS BANK", + "razon_social": "UBS Bank M\u00e9xico, S.A., Instituci\u00f3n de Banca M\u00faltiple, UBS Grupo Financiero" + }, + { + "key": "140", + "name": "CONSUBANCO", + "razon_social": "Consubanco, S.A. Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "141", + "name": "VOLKSWAGEN", + "razon_social": "Volkswagen Bank, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "143", + "name": "CIBANCO", + "razon_social": "CIBanco, S.A." + }, + { + "key": "145", + "name": "BBASE", + "razon_social": "Banco Base, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "147", + "name": "BANKAOOL", + "razon_social": "Bankaool, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "148", + "name": "PAGATODO", + "razon_social": "Banco PagaTodo, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "149", + "name": "FORJADORES", + "razon_social": "Banco Forjadores, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "150", + "name": "INMOBILIARIO", + "razon_social": "Banco Inmobiliario Mexicano, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "151", + "name": "DOND\u00c9", + "razon_social": "Fundaci\u00f3n Dond\u00e9 Banco, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "152", + "name": "BANCREA", + "razon_social": "Banco Bancrea, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "153", + "name": "PROGRESO", + "razon_social": "Banco Progreso Chihuahua, S.A." + }, + { + "key": "154", + "name": "BANCO FINTERRA", + "razon_social": "Banco Finterra, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "155", + "name": "ICBC", + "razon_social": "Industrial and Commercial Bank of China M\u00e9xico, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "156", + "name": "SABADELL", + "razon_social": "Banco Sabadell, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "157", + "name": "SHINHAN", + "razon_social": "Banco Shinhan de M\u00e9xico, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "158", + "name": "MIZUHO BANK", + "razon_social": "Mizuho Bank M\u00e9xico, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "159", + "name": "BANK OF CHINA", + "razon_social": "Bank of China M\u00e9xico, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "160", + "name": "BANCO S3", + "razon_social": "Banco S3 M\u00e9xico, S.A., Instituci\u00f3n de Banca M\u00faltiple" + }, + { + "key": "166", + "name": "BANSEFI", + "razon_social": "Banco del Ahorro Nacional y Servicios Financieros, Sociedad Nacional de Cr\u00e9dito, Instituci\u00f3n de Banca de Desarrollo" + }, + { + "key": "168", + "name": "HIPOTECARIA FEDERAL", + "razon_social": "Sociedad Hipotecaria Federal, Sociedad Nacional de Cr\u00e9dito, Instituci\u00f3n de Banca de Desarrollo" + }, + { + "key": "600", + "name": "MONEXCB", + "razon_social": "Monex Casa de Bolsa, S.A. de C.V. Monex Grupo Financiero" + }, + { + "key": "601", + "name": "GBM", + "razon_social": "GBM Grupo Burs\u00e1til Mexicano, S.A. de C.V. Casa de Bolsa" + }, + { + "key": "602", + "name": "MASARI", + "razon_social": "Masari Casa de Bolsa, S.A." + }, + { + "key": "605", + "name": "VALUE", + "razon_social": "Value, S.A. de C.V. Casa de Bolsa" + }, + { + "key": "606", + "name": "ESTRUCTURADORES", + "razon_social": "Estructuradores del Mercado de Valores Casa de Bolsa, S.A. de C.V." + }, + { + "key": "607", + "name": "TIBER", + "razon_social": "Casa de Cambio Tiber, S.A. de C.V." + }, + { + "key": "608", + "name": "VECTOR", + "razon_social": "Vector Casa de Bolsa, S.A. de C.V." + }, + { + "key": "610", + "name": "B&B", + "razon_social": "B y B, Casa de Cambio, S.A. de C.V." + }, + { + "key": "614", + "name": "ACCIVAL", + "razon_social": "Acciones y Valores Banamex, S.A. de C.V., Casa de Bolsa" + }, + { + "key": "615", + "name": "MERRILL LYNCH", + "razon_social": "Merrill Lynch M\u00e9xico, S.A. de C.V. Casa de Bolsa" + }, + { + "key": "616", + "name": "FINAMEX", + "razon_social": "Casa de Bolsa Finamex, S.A. de C.V." + }, + { + "key": "617", + "name": "VALMEX", + "razon_social": "Valores Mexicanos Casa de Bolsa, S.A. de C.V." + }, + { + "key": "618", + "name": "UNICA", + "razon_social": "Unica Casa de Cambio, S.A. de C.V." + }, + { + "key": "619", + "name": "MAPFRE", + "razon_social": "MAPFRE Tepeyac, S.A." + }, + { + "key": "620", + "name": "PROFUTURO", + "razon_social": "Profuturo G.N.P., S.A. de C.V., Afore" + }, + { + "key": "621", + "name": "CB ACTINVER", + "razon_social": "Actinver Casa de Bolsa, S.A. de C.V." + }, + { + "key": "622", + "name": "OACTIN", + "razon_social": "OPERADORA ACTINVER, S.A. DE C.V." + }, + { + "key": "623", + "name": "SKANDIA", + "razon_social": "Skandia Vida, S.A. de C.V." + }, + { + "key": "626", + "name": "CBDEUTSCHE", + "razon_social": "Deutsche Securities, S.A. de C.V. CASA DE BOLSA" + }, + { + "key": "627", + "name": "ZURICH", + "razon_social": "Zurich Compa\u00f1\u00eda de Seguros, S.A." + }, + { + "key": "628", + "name": "ZURICHVI", + "razon_social": "Zurich Vida, Compa\u00f1\u00eda de Seguros, S.A." + }, + { + "key": "629", + "name": "SU CASITA", + "razon_social": "Hipotecaria Su Casita, S.A. de C.V. SOFOM ENR" + }, + { + "key": "630", + "name": "CB INTERCAM", + "razon_social": "Intercam Casa de Bolsa, S.A. de C.V." + }, + { + "key": "631", + "name": "CI BOLSA", + "razon_social": "CI Casa de Bolsa, S.A. de C.V." + }, + { + "key": "632", + "name": "BULLTICK CB", + "razon_social": "Bulltick Casa de Bolsa, S.A., de C.V." + }, + { + "key": "633", + "name": "STERLING", + "razon_social": "Sterling Casa de Cambio, S.A. de C.V." + }, + { + "key": "634", + "name": "FINCOMUN", + "razon_social": "Fincom\u00fan, Servicios Financieros Comunitarios, S.A. de C.V." + }, + { + "key": "636", + "name": "HDI SEGUROS", + "razon_social": "HDI Seguros, S.A. de C.V." + }, + { + "key": "637", + "name": "ORDER", + "razon_social": "Order Express Casa de Cambio, S.A. de C.V" + }, + { + "key": "638", + "name": "AKALA", + "razon_social": "Akala, S.A. de C.V., Sociedad Financiera Popular" + }, + { + "key": "640", + "name": "CB JPMORGAN", + "razon_social": "J.P. Morgan Casa de Bolsa, S.A. de C.V. J.P. Morgan Grupo Financiero" + }, + { + "key": "642", + "name": "REFORMA", + "razon_social": "Operadora de Recursos Reforma, S.A. de C.V., S.F.P." + }, + { + "key": "646", + "name": "STP", + "razon_social": "Sistema de Transferencias y Pagos STP, S.A. de C.V.SOFOM ENR" + }, + { + "key": "647", + "name": "TELECOMM", + "razon_social": "Telecomunicaciones de M\u00e9xico" + }, + { + "key": "648", + "name": "EVERCORE", + "razon_social": "Evercore Casa de Bolsa, S.A. de C.V." + }, + { + "key": "649", + "name": "SKANDIA", + "razon_social": "Skandia Operadora de Fondos, S.A. de C.V." + }, + { + "key": "651", + "name": "SEGMTY", + "razon_social": "Seguros Monterrey New York Life, S.A de C.V" + }, + { + "key": "652", + "name": "ASEA", + "razon_social": "Soluci\u00f3n Asea, S.A. de C.V., Sociedad Financiera Popular" + }, + { + "key": "653", + "name": "KUSPIT", + "razon_social": "Kuspit Casa de Bolsa, S.A. de C.V." + }, + { + "key": "655", + "name": "SOFIEXPRESS", + "razon_social": "J.P. SOFIEXPRESS, S.A. de C.V., S.F.P." + }, + { + "key": "656", + "name": "UNAGRA", + "razon_social": "UNAGRA, S.A. de C.V., S.F.P." + }, + { + "key": "659", + "name": "OPCIONES EMPRESARIALES DEL NOROESTE", + "razon_social": "OPCIONES EMPRESARIALES DEL NORESTE, S.A. DE C.V., S.F.P." + }, + { + "key": "670", + "name": "LIBERTAD", + "razon_social": "Libertad Servicios Financieros, S.A. De C.V." + }, + { + "key": "901", + "name": "CLS", + "razon_social": "Cls Bank International" + }, + { + "key": "902", + "name": "INDEVAL", + "razon_social": "SD. Indeval, S.A. de C.V." + }, + { + "key": "999", + "name": "N/A" + } + ], + "tabla": "SATBancos" }, { "tabla": "SATUsoCfdi", @@ -660,12 +1101,12 @@ }, { "key": "QC", - "name": "Quebec\u00a0", + "name": "\u00a0Quebec\u00a0", "pais": "CAN" }, { "key": "NS", - "name": "Nueva Escocia", + "name": "\u00a0Nueva Escocia", "pais": "CAN" }, { @@ -675,47 +1116,47 @@ }, { "key": "MB", - "name": "Manitoba", + "name": "\u00a0Manitoba", "pais": "CAN" }, { "key": "BC", - "name": "Columbia Brit\u00e1nica", + "name": "\u00a0Columbia Brit\u00e1nica", "pais": "CAN" }, { "key": "PE", - "name": "Isla del Pr\u00edncipe Eduardo", + "name": "\u00a0Isla del Pr\u00edncipe Eduardo", "pais": "CAN" }, { "key": "SK", - "name": "Saskatchewan", + "name": "\u00a0Saskatchewan", "pais": "CAN" }, { "key": "AB", - "name": "Alberta", + "name": "\u00a0Alberta", "pais": "CAN" }, { "key": "NL", - "name": "Terranova y Labrador", + "name": "\u00a0Terranova y Labrador", "pais": "CAN" }, { "key": "NT", - "name": "Territorios del Noroeste", + "name": "\u00a0Territorios del Noroeste", "pais": "CAN" }, { "key": "YT", - "name": "Yuk\u00f3n", + "name": "\u00a0Yuk\u00f3n", "pais": "CAN" }, { "key": "UN", - "name": "Nunavut", + "name": "\u00a0Nunavut", "pais": "CAN" } ], @@ -865,6 +1306,155 @@ ], "tabla": "SATTipoContrato" }, +{ + "datos": [ + { + "key": "01", + "name": "Dobles" + }, + { + "key": "02", + "name": "Triples" + }, + { + "key": "03", + "name": "Simples" + } + ], + "tabla": "SATTipoHoras" +}, +{ + "datos": [ + { + "key": "01", + "name": "Riesgo de trabajo." + }, + { + "key": "02", + "name": "Enfermedad en general." + }, + { + "key": "03", + "name": "Maternidad." + }, + { + "key": "04", + "name": "Licencia por cuidados m\u00e9dicos de hijos diagnosticados con c\u00e1ncer." + } + ], + "tabla": "SATTipoIncapacidad" +}, +{ + "datos": [ + { + "key": "01", + "name": "Diurna" + }, + { + "key": "02", + "name": "Nocturna" + }, + { + "key": "03", + "name": "Mixta" + }, + { + "key": "04", + "name": "Por hora" + }, + { + "key": "05", + "name": "Reducida" + }, + { + "key": "06", + "name": "Continuada" + }, + { + "key": "07", + "name": "Partida" + }, + { + "key": "08", + "name": "Por turnos" + }, + { + "key": "99", + "name": "Otra Jornada" + } + ], + "tabla": "SATTipoJornada" +}, +{ + "datos": [ + { + "key": "O", + "name": "N\u00f3mina ordinaria" + }, + { + "key": "E", + "name": "N\u00f3mina extraordinaria" + } + ], + "tabla": "SATTipoNomina" +}, +{ + "datos": [ + { + "key": "02", + "name": "Sueldos" + }, + { + "key": "03", + "name": "Jubilados" + }, + { + "key": "04", + "name": "Pensionados" + }, + { + "key": "05", + "name": "Asimilados Miembros Sociedades Cooperativas Produccion" + }, + { + "key": "06", + "name": "Asimilados Integrantes Sociedades Asociaciones Civiles" + }, + { + "key": "07", + "name": "Asimilados Miembros consejos" + }, + { + "key": "08", + "name": "Asimilados comisionistas" + }, + { + "key": "09", + "name": "Asimilados Honorarios" + }, + { + "key": "10", + "name": "Asimilados acciones" + }, + { + "key": "11", + "name": "Asimilados otros" + }, + { + "key": "12", + "name": "Jubilados o Pensionados" + }, + { + "key": "13", + "name": "Indemnización o Separación" + }, + { + "key": "99", + "name": "Otro Regimen" + } + ], + "tabla": "SATTipoRegimen" +}, { "datos": [ { @@ -1125,11 +1715,11 @@ }, { "key": "065", - "name": "Ajuste en Jubilaciones, pensiones o haberes de retiro Exento" + "name": "Ajuste en Jubilaciones, pensiones o haberes de retiro en una sola exhibici\u00f3n Exento " }, { "key": "066", - "name": "Ajuste en Jubilaciones, pensiones o haberes de retiro Gravado" + "name": "Ajuste en Jubilaciones, pensiones o haberes de retiro en una sola exhibici\u00f3n Gravado" }, { "key": "067", @@ -1141,11 +1731,11 @@ }, { "key": "069", - "name": "Ajuste en Jubilaciones, pensiones o haberes de retiro Acumulable" + "name": "Ajuste en Jubilaciones, pensiones o haberes de retiro en parcialidades Exento" }, { "key": "070", - "name": "Ajuste en Jubilaciones, pensiones o haberes de retiro No acumulable" + "name": "Ajuste en Jubilaciones, pensiones o haberes de retiro en parcialidades Gravado" }, { "key": "071", @@ -1273,115 +1863,31 @@ }, { "key": "102", - "name": "Ajuste a pagos por gratificaciones, primas, compensaciones, recompensas u otros a extrabajadores derivados de jubilación en parcialidades, gravados" + "name": "Ajuste a pagos por gratificaciones, primas, compensaciones, recompensas u otros a extrabajadores derivados de jubilaci\u00f3n en parcialidades, gravados" }, { "key": "103", - "name": "Ajuste a pagos que se realicen a extrabajadores que obtengan una jubilación en parcialidades derivados de la ejecución de una resolución judicial o de un laudo gravados" + "name": "Ajuste a pagos que se realicen a extrabajadores que obtengan una jubilaci\u00f3n en parcialidades derivados de la ejecuci\u00f3n de una resoluci\u00f3n judicial o de un laudo gravados" }, { "key": "104", - "name": "Ajuste a pagos que se realicen a extrabajadores que obtengan una jubilación en parcialidades derivados de la ejecución de una resolución judicial o de un laudo exentos" + "name": "Ajuste a pagos que se realicen a extrabajadores que obtengan una jubilaci\u00f3n en parcialidades derivados de la ejecuci\u00f3n de una resoluci\u00f3n judicial o de un laudo exentos" }, { "key": "105", - "name": "Ajuste a pagos que se realicen a extrabajadores que obtengan una jubilación en una sola exhibición derivados de la ejecución de una resolución judicial o de un laudo gravados" + "name": "Ajuste a pagos que se realicen a extrabajadores que obtengan una jubilaci\u00f3n en una sola exhibici\u00f3n derivados de la ejecuci\u00f3n de una resoluci\u00f3n judicial o de un laudo gravados" }, { "key": "106", - "name": "Ajuste a pagos que se realicen a extrabajadores que obtengan una jubilación en una sola exhibición derivados de la ejecución de una resolución judicial o de un laudo exentos" + "name": "Ajuste a pagos que se realicen a extrabajadores que obtengan una jubilaci\u00f3n en una sola exhibici\u00f3n derivados de la ejecuci\u00f3n de una resoluci\u00f3n judicial o de un laudo exentos" + }, + { + "key": "107", + "name": "Ajuste al Subsidio Causado " } ], "tabla": "SATTipoDeduccion" }, -{ - "datos": [ - { - "key": "01", - "name": "Dobles" - }, - { - "key": "02", - "name": "Triples" - }, - { - "key": "03", - "name": "Simples" - } - ], - "tabla": "SATTipoHoras" -}, -{ - "datos": [ - { - "key": "01", - "name": "Riesgo de trabajo." - }, - { - "key": "02", - "name": "Enfermedad en general." - }, - { - "key": "03", - "name": "Maternidad." - } - ], - "tabla": "SATTipoIncapacidad" -}, -{ - "datos": [ - { - "key": "01", - "name": "Diurna" - }, - { - "key": "02", - "name": "Nocturna" - }, - { - "key": "03", - "name": "Mixta" - }, - { - "key": "04", - "name": "Por hora" - }, - { - "key": "05", - "name": "Reducida" - }, - { - "key": "06", - "name": "Continuada" - }, - { - "key": "07", - "name": "Partida" - }, - { - "key": "08", - "name": "Por turnos" - }, - { - "key": "99", - "name": "Otra Jornada" - } - ], - "tabla": "SATTipoJornada" -}, -{ - "datos": [ - { - "key": "O", - "name": "N\u00f3mina ordinaria" - }, - { - "key": "E", - "name": "N\u00f3mina extraordinaria" - } - ], - "tabla": "SATTipoNomina" -}, { "datos": [ { @@ -1404,6 +1910,18 @@ "key": "005", "name": "Reintegro de ISR retenido en exceso de ejercicio anterior (siempre que no haya sido enterado al SAT)." }, + { + "key": "006", + "name": "Alimentos en bienes (Servicios de comedor y comida) Art 94 \u00faltimo p\u00e1rrafo LISR" + }, + { + "key": "007", + "name": "ISR ajustado por subsidio" + }, + { + "key": "008", + "name": "Subsidio efectivamente entregado que no correspond\u00eda (Aplica s\u00f3lo cuando haya ajuste al cierre de mes en relaci\u00f3n con el Ap\u00e9ndice 7 de la gu\u00eda de llenado de n\u00f3mina)" + }, { "key": "999", "name": "Pagos distintos a los listados y que no deben considerarse como ingreso por sueldos, salarios o ingresos asimilados." @@ -1563,7 +2081,7 @@ }, { "key": "047", - "name": "Alimentaci\u00f3n" + "name": "Alimentaci\u00f3n diferentes a los establecidos en el Art 94 \u00faltimo p\u00e1rrafo LISR" }, { "key": "048", @@ -1579,74 +2097,17 @@ }, { "key": "051", - "name": "Pagos por gratificaciones, primas, compensaciones, recompensas u otros a extrabajadores derivados de jubilación en parcialidades" + "name": "Pagos por gratificaciones, primas, compensaciones, recompensas u otros a extrabajadores derivados de jubilaci\u00f3n en parcialidades" }, { "key": "052", - "name": "Pagos que se realicen a extrabajadores que obtengan una jubilación en parcialidades derivados de la ejecución de resoluciones judicial o de un laudo" + "name": "Pagos que se realicen a extrabajadores que obtengan una jubilaci\u00f3n en parcialidades derivados de la ejecuci\u00f3n de resoluciones judicial o de un laudo" }, { "key": "053", - "name": "Pagos que se realicen a extrabajadores que obtengan una jubilación en una sola exhibición derivados de la ejecución de resoluciones judicial o de un laudo" + "name": "Pagos que se realicen a extrabajadores que obtengan una jubilaci\u00f3n en una sola exhibici\u00f3n derivados de la ejecuci\u00f3n de resoluciones judicial o de un laudo" } ], "tabla": "SATTipoPercepcion" -}, -{ - "datos": [ - { - "key": "02", - "name": "Sueldos" - }, - { - "key": "03", - "name": "Jubilados" - }, - { - "key": "04", - "name": "Pensionados" - }, - { - "key": "05", - "name": "Asimilados Miembros Sociedades Cooperativas Produccion" - }, - { - "key": "06", - "name": "Asimilados Integrantes Sociedades Asociaciones Civiles" - }, - { - "key": "07", - "name": "Asimilados Miembros consejos" - }, - { - "key": "08", - "name": "Asimilados comisionistas" - }, - { - "key": "09", - "name": "Asimilados Honorarios" - }, - { - "key": "10", - "name": "Asimilados acciones" - }, - { - "key": "11", - "name": "Asimilados otros" - }, - { - "key": "12", - "name": "Jubilados o Pensionados" - }, - { - "key": "13", - "name": "Indemnización o Separación" - }, - { - "key": "99", - "name": "Otro Regimen" - } - ], - "tabla": "SATTipoRegimen" } ] From 2cc6e7046ae2984b981ee33c32550cd7618f5929 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Tue, 21 Jan 2020 23:25:13 -0600 Subject: [PATCH 3/6] Add new PAC --- source/app/controllers/comercio/__init__.py | 3 + source/app/controllers/comercio/comercio.py | 338 +++++++++++++++ source/app/controllers/util.py | 2 +- source/app/controllers/utils.py | 124 ++++-- source/app/models/main.py | 454 ++++++++++---------- 5 files changed, 663 insertions(+), 258 deletions(-) create mode 100644 source/app/controllers/comercio/__init__.py create mode 100644 source/app/controllers/comercio/comercio.py diff --git a/source/app/controllers/comercio/__init__.py b/source/app/controllers/comercio/__init__.py new file mode 100644 index 0000000..195aadd --- /dev/null +++ b/source/app/controllers/comercio/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python3 + +from .comercio import PACComercioDigital diff --git a/source/app/controllers/comercio/comercio.py b/source/app/controllers/comercio/comercio.py new file mode 100644 index 0000000..a6e625b --- /dev/null +++ b/source/app/controllers/comercio/comercio.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python +# ~ +# ~ PAC +# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net +# ~ +# ~ This program is free software: you can redistribute it and/or modify +# ~ it under the terms of the GNU General Public License as published by +# ~ the Free Software Foundation, either version 3 of the License, or +# ~ (at your option) any later version. +# ~ +# ~ This program is distributed in the hope that it will be useful, +# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of +# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# ~ GNU General Public License for more details. +# ~ +# ~ You should have received a copy of the GNU General Public License +# ~ along with this program. If not, see . + + +import logging + +import lxml.etree as ET +import requests +from requests.exceptions import ConnectionError + +from .conf import DEBUG, AUTH + + +LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' +LOG_DATE = '%d/%m/%Y %H:%M:%S' +logging.addLevelName(logging.ERROR, '\033[1;41mERROR\033[1;0m') +logging.addLevelName(logging.DEBUG, '\x1b[33mDEBUG\033[1;0m') +logging.addLevelName(logging.INFO, '\x1b[32mINFO\033[1;0m') +logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE) +log = logging.getLogger(__name__) + +logging.getLogger('requests').setLevel(logging.ERROR) + + +TIMEOUT = 10 + + +class PACComercioDigital(object): + ws = 'https://{}.comercio-digital.mx/{}' + api = 'https://app2.comercio-digital.mx/{}' + URL = { + 'timbra': ws.format('ws', 'timbre/timbrarV5.aspx'), + 'cancel': ws.format('cancela', 'cancela3/cancelarUuid'), + 'cancelxml': ws.format('cancela', 'cancela3/cancelarXml'), + 'client': api.format('x3/altaEmpresa'), + 'saldo': api.format('x3/saldo'), + 'timbres': api.format('x3/altaTimbres'), + } + CODES = { + '000': '000 Exitoso', + '004': '004 RFC {} ya esta dado de alta con Estatus=A', + '704': '704 Usuario Invalido', + } + NS_CFDI = { + 'cfdi': 'http://www.sat.gob.mx/cfd/3', + 'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital', + } + + if DEBUG: + ws = 'https://pruebas.comercio-digital.mx/{}' + URL = { + 'timbra': ws.format('timbre/timbrarV5.aspx'), + 'cancel': ws.format('cancela3/cancelarUuid'), + 'cancelxml': ws.format('cancela3/cancelarXml'), + 'client': api.format('x3/altaEmpresa'), + 'saldo': api.format('x3/saldo'), + 'timbres': api.format('x3/altaTimbres'), + } + + def __init__(self): + self.error = '' + self.cfdi_uuid = '' + self.date_stamped = '' + + def _error(self, msg): + self.error = str(msg) + log.error(msg) + return + + def _post(self, url, data, headers={}): + result = None + headers['host'] = url.split('/')[2] + headers['Content-type'] = 'text/plain' + headers['Connection'] = 'Keep-Alive' + + try: + result = requests.post(url, data=data, headers=headers, timeout=TIMEOUT) + except ConnectionError as e: + self._error(e) + + return result + + def _validate_cfdi(self, xml): + """ + Comercio Digital solo soporta la declaración con doble comilla + """ + tree = ET.fromstring(xml.encode()) + xml = ET.tostring(tree, + pretty_print=True, doctype='') + return xml + + def stamp(self, cfdi, auth={}): + if DEBUG or not auth: + auth = AUTH + + url = self.URL['timbra'] + headers = { + 'usrws': auth['user'], + 'pwdws': auth['pass'], + 'tipo': 'XML', + } + cfdi = self._validate_cfdi(cfdi) + result = self._post(url, cfdi, headers) + + if result is None: + return '' + + if result.status_code != 200: + return '' + + if 'errmsg' in result.headers: + self._error(result.headers['errmsg']) + return '' + + xml = result.content + tree = ET.fromstring(xml) + self.cfdi_uuid = tree.xpath( + 'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)', + namespaces=self.NS_CFDI) + self.date_stamped = tree.xpath( + 'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@FechaTimbrado)', + namespaces=self.NS_CFDI) + + return xml.decode() + + def _get_data_cancel(self, cfdi, info, auth): + NS_CFDI = { + 'cfdi': 'http://www.sat.gob.mx/cfd/3', + 'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital', + } + tree = ET.fromstring(cfdi) + tipo = tree.xpath( + 'string(//cfdi:Comprobante/@TipoDeComprobante)', + namespaces=NS_CFDI) + total = tree.xpath( + 'string(//cfdi:Comprobante/@Total)', + namespaces=NS_CFDI) + rfc_emisor = tree.xpath( + 'string(//cfdi:Comprobante/cfdi:Emisor/@Rfc)', + namespaces=NS_CFDI) + rfc_receptor = tree.xpath( + 'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)', + namespaces=NS_CFDI) + uid = tree.xpath( + 'string(//cfdi:Complemento/tdf:TimbreFiscalDigital/@UUID)', + namespaces=NS_CFDI) + data = ( + f"USER={auth['user']}", + f"PWDW={auth['pass']}", + f"RFCE={rfc_emisor}", + f"UUID={uid}", + f"PWDK={info['pass']}", + f"KEYF={info['key']}", + f"CERT={info['cer']}", + f"TIPO={info['tipo']}", + f"ACUS=SI", + f"RFCR={rfc_receptor}", + f"TIPOC={tipo}", + f"TOTAL={total}", + ) + return '\n'.join(data) + + def cancel(self, cfdi, info, auth={}): + if not auth: + auth = AUTH + url = self.URL['cancel'] + data = self._get_data_cancel(cfdi, info, auth) + + result = self._post(url, data) + + if result is None: + return '' + + if result.status_code != 200: + return '' + + if result.headers['codigo'] != '000': + self._error(result.headers['errmsg']) + return '' + + return result.content + + def _get_headers_cancel_xml(self, cfdi, info, auth): + NS_CFDI = { + 'cfdi': 'http://www.sat.gob.mx/cfd/3', + 'tdf': 'http://www.sat.gob.mx/TimbreFiscalDigital', + } + tree = ET.fromstring(cfdi) + tipo = tree.xpath( + 'string(//cfdi:Comprobante/@TipoDeComprobante)', + namespaces=NS_CFDI) + total = tree.xpath( + 'string(//cfdi:Comprobante/@Total)', + namespaces=NS_CFDI) + rfc_receptor = tree.xpath( + 'string(//cfdi:Comprobante/cfdi:Receptor/@Rfc)', + namespaces=NS_CFDI) + + headers = { + 'usrws': auth['user'], + 'pwdws': auth['pass'], + 'rfcr': rfc_receptor, + 'total': total, + 'tipocfdi': tipo, + } + headers.update(info) + + return headers + + def cancel_xml(self, cfdi, xml, info, auth={}): + if not auth: + auth = AUTH + url = self.URL['cancelxml'] + headers = self._get_headers_cancel_xml(cfdi, info, auth) + result = self._post(url, xml, headers) + + if result is None: + return '' + + if result.status_code != 200: + return '' + + if result.headers['codigo'] != '000': + self._error(result.headers['errmsg']) + return '' + + return result.content + + def _get_data_client(self, auth, values): + data = [f"usr_ws={auth['user']}", f"pwd_ws={auth['pass']}"] + fields = ( + 'rfc_contribuyente', + 'nombre_contribuyente', + 'calle', + 'noExterior', + 'noInterior', + 'colonia', + 'localidad', + 'municipio', + 'estado', + 'pais', + 'cp', + 'contacto', + 'telefono', + 'email', + 'rep_nom', + 'rep_rfc', + 'email_fact', + 'pwd_asignado', + ) + data += [f"{k}={values[k]}" for k in fields] + + return '\n'.join(data) + + def client_add(self, data): + auth = AUTH + url = self.URL['client'] + data = self._get_data_client(auth, data) + + result = self._post(url, data) + + if result is None: + return False + + if result.status_code != 200: + self._error(f'Code: {result.status_code}') + return False + + if result.text != self.CODES['000']: + self._error(result.text) + return False + + return True + + def client_balance(self, data): + url = self.URL['saldo'] + host = url.split('/')[2] + headers = { + 'Content-type': 'text/plain', + 'Host': host, + 'Connection' : 'Keep-Alive', + } + try: + result = requests.get(url, params=data, headers=headers, timeout=TIMEOUT) + except ConnectionError as e: + self._error(e) + return '' + + if result.status_code != 200: + return '' + + if result.text == self.CODES['704']: + self._error(result.text) + return '' + + return result.text + + def client_add_timbres(self, data, auth={}): + if not auth: + auth = AUTH + url = self.URL['timbres'] + data = '\n'.join(( + f"usr_ws={auth['user']}", + f"pwd_ws={auth['pass']}", + f"rfc_recibir={data['rfc']}", + f"num_timbres={data['timbres']}" + )) + + result = self._post(url, data) + + if result is None: + return False + + if result.status_code != 200: + self._error(f'Code: {result.status_code}') + return False + + if result.text != self.CODES['000']: + self._error(result.text) + return False + + return True + diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 70a465c..3806ab9 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -589,7 +589,7 @@ def timbra_xml(xml, auth): if not DEBUG and not auth: msg = 'Sin datos para timbrar' - result = {'ok': True, 'error': msg} + result = {'ok': False, 'error': msg} return result result = {'ok': True, 'error': ''} diff --git a/source/app/controllers/utils.py b/source/app/controllers/utils.py index 7c58cb7..8b13b09 100644 --- a/source/app/controllers/utils.py +++ b/source/app/controllers/utils.py @@ -19,6 +19,7 @@ import base64 import collections import datetime +import getpass import json import logging import math @@ -50,6 +51,7 @@ from dateutil import parser import seafileapi from settings import DEBUG, DB_COMPANIES, PATHS +from .comercio import PACComercioDigital LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' @@ -70,6 +72,9 @@ if DEBUG: PG_DUMP = 'pg_dump -h localhost -U postgres' PSQL = 'psql -h localhost -U postgres' +PACS = { + 'comercio': PACComercioDigital +} #~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37 class CaseInsensitiveDict(collections.MutableMapping): @@ -394,9 +399,19 @@ def _sql_companies(sql, args=()): return data -def rfc_get(): - sql = "SELECT * FROM names" - data = _sql_companies(sql) +def get_data_con(rfc): + data = rfc_get(rfc)[0][0] + return loads(data) + + +def rfc_get(rfc=''): + if rfc: + sql = "SELECT con FROM names WHERE rfc = ?" + w = (rfc,) + else: + w = () + sql = "SELECT * FROM names" + data = _sql_companies(sql, w) return data @@ -448,35 +463,34 @@ def _get_pass(rfc): return rfc -def _backup_db(rfc, data, path_bk, is_mv, url_seafile): - if data['type'] != 'postgres': - return - - log.info('Generando backup de: {}'.format(rfc)) - bk_name = '{}.bk'.format(rfc.lower()) - path_db = _join(path_bk, bk_name) - args = 'pg_dump -U postgres -Fc {} > "{}"'.format(data['name'], path_db) - result = _call(args) +def _backup_db(rfc, is_mv, url_seafile): + log.info(f'Generando backup de: {rfc.upper()}') + bk_name = f'{rfc}.bk' + path = _join(PATHS['BK'], bk_name) + args = f'{PG_DUMP} -d {rfc} -Fc -f "{path}"' + _run(args) log.info('\tBackup local generado...') + if is_mv: - path_target = _join(Path.home(), PATH_INVOICES) - if Path(path_target).exists(): + path_target = _validate_path_local() + if path_target: path_target = _join(path_target, bk_name) - shutil.copy(path_db, path_target) + shutil.copy(path, path_target) else: log.error('\tNo existe la carpeta compartida...') - sql = 'select correo_timbrado, token_soporte from emisor;' - args = 'psql -U postgres -d {} -Atc "{}"'.format(data['name'], sql) - result = _call(args) - if not result: - log.error('\tSin datos para backup remoto') - return + # ~ sql = 'select correo_timbrado, token_soporte from emisor;' + # ~ args = 'psql -U postgres -d {} -Atc "{}"'.format(data['name'], sql) + # ~ result = _call(args) + # ~ if not result: + # ~ log.error('\tSin datos para backup remoto') + # ~ return + + # ~ data = result.strip().split('|') + # ~ if not data[1]: + # ~ log.error('\tSin token de soporte') + # ~ return - data = result.strip().split('|') - if not data[1]: - log.error('\tSin token de soporte') - return # ~ email = data[0] # ~ uuid = data[1] # ~ email = 'hola@elmau.net' @@ -490,19 +504,15 @@ def _backup_db(rfc, data, path_bk, is_mv, url_seafile): return -def db_backup(path_companies, path_bk, is_mv, url_seafile): - con = sqlite3.connect(path_companies) - cursor = con.cursor() - sql = "SELECT * FROM names" - cursor.execute(sql) - rows = cursor.fetchall() - if rows is None: +def db_backup(is_mv, url_seafile): + data = rfc_get() + if not len(data): + msg = 'Sin bases de datos a respaldar' + log.info(msg) return - cursor.close() - con.close() - for rfc, data in rows: - _backup_db(rfc, json.loads(data), path_bk, is_mv, url_seafile) + for rfc, _ in data: + _backup_db(rfc.lower(), is_mv, url_seafile) return @@ -542,3 +552,45 @@ def now(): def get_days(date): return (now() - date).days + + +def get_pass(): + pass1 = getpass.getpass('Introduce la contraseña: ') + pass2 = getpass.getpass('Confirma la contraseña: ') + + if pass1 != pass2: + msg = 'Las contraseñas son diferentes' + return False, msg + + password = pass1.strip() + if not password: + msg = 'La contraseña es necesaria' + return False, msg + + return True, password + + +def xml_stamp(xml, auth, name): + if not DEBUG and not auth: + msg = 'Sin datos para timbrar' + result = {'ok': False, 'error': msg} + return result + + result = {'ok': True, 'error': ''} + if name == 'comercio': + auth = {'user': auth['RFC'], 'pass': auth['PASS']} + else: + auth = {'user': auth['USER'], 'pass': auth['PASS']} + + pac = PACS[name]() + xml_stamped = pac.stamp(xml, auth) + + if not xml_stamped: + result['ok'] = False + result['error'] = pac.error + return result + + result['xml'] = xml_stamped + result['uuid'] = pac.cfdi_uuid + result['fecha'] = pac.date_stamped + return result diff --git a/source/app/models/main.py b/source/app/models/main.py index 6695639..1d43452 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -19,7 +19,7 @@ import argparse from decimal import Decimal import sqlite3 -import click +# ~ import click from peewee import * from playhouse.fields import PasswordField, ManyToManyField from playhouse.shortcuts import case, SQL, cast @@ -5000,10 +5000,15 @@ class Facturas(BaseModel): obj.save() enviar_correo = util.get_bool(Configuracion.get_('correo_directo')) + pac = Configuracion.get_('pac').lower() anticipo = False msg = 'Factura timbrada correctamente' - result = util.timbra_xml(obj.xml, auth) + if pac: + result = utils.xml_stamp(obj.xml, auth, pac) + else: + result = util.timbra_xml(obj.xml, auth) + if result['ok']: obj.xml = result['xml'] obj.uuid = result['uuid'] @@ -9247,64 +9252,6 @@ def _migrate_tables(rfc=''): 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) @@ -9484,7 +9431,7 @@ def _importar_valores(archivo='', rfc=''): try: with database_proxy.atomic() as txn: table.create(**r) - except IntegrityError: + except: pass log.info('Importación terminada...') @@ -9794,44 +9741,6 @@ def _import_tickets(rows): 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: @@ -10124,6 +10033,87 @@ def _update_sat(): return +def _new_superuser(rfc): + rfc = rfc.lower() + if not rfc: + log.error('Falta el RFC') + return + + if not utils.rfc_exists(rfc): + msg = 'El RFC no esta dado de alta' + log.error(msg) + return + + args = utils.get_data_con(rfc) + + user = input('Introduce el nuevo nombre para el superusuario: ').strip() + if not user: + msg = 'El nombre de usuario es requerido' + log.error(msg) + return + + ok, password = utils.get_pass() + if not ok: + log.error(password) + return + + conectar(args) + try: + obj = Usuarios.create( + usuario=user, contraseña=password, es_superusuario=True) + msg = 'SuperUsuario creado correctamente...' + log.info(msg) + except IntegrityError: + msg = 'El usuario ya existe' + log.error(msg) + + desconectar() + return + + +def _change_pass(rfc): + rfc = rfc.lower() + if not rfc: + log.error('Falta el RFC') + return + + if not utils.rfc_exists(rfc): + msg = 'El RFC no esta dado de alta' + log.error(msg) + return + + args = utils.get_data_con(rfc) + conectar(args) + + user = input('Introduce el nombre de usuario: ').strip() + if not user: + msg = 'El nombre de usuario es requerido' + log.error(msg) + desconectar() + return + + try: + obj = Usuarios.get(usuario=user) + except Usuarios.DoesNotExist: + msg = 'El usuario no existe' + log.error(msg) + desconectar() + return + + ok, password = utils.get_pass() + if not ok: + log.error(password) + desconectar() + return + + obj.contraseña = password + obj.save() + + desconectar() + log.info('Contraseña cambiada correctamente...') + return + + def _process_command_line_arguments(): parser = argparse.ArgumentParser( description='Empresa Libre') @@ -10135,8 +10125,16 @@ def _process_command_line_arguments(): action='store_true', default=False, required=False) parser.add_argument('-ndb', '--no-database', dest='no_database', action='store_true', default=False, required=False) + parser.add_argument('-m', '--migrate', dest='migrate', + action='store_true', default=False, required=False) parser.add_argument('-us', '--update-sat', dest='update_sat', action='store_true', default=False, required=False) + parser.add_argument('-ns', '--new-superuser', dest='new_superuser', + action='store_true', default=False, required=False) + parser.add_argument('-cp', '--change-pass', dest='change_pass', + action='store_true', default=False, required=False) + parser.add_argument('-bk', '--backup', dest='backup', + action='store_true', default=False, required=False) parser.add_argument('-r', '--rfc', dest='rfc', default='') return parser.parse_args() @@ -10155,155 +10153,169 @@ def main(args): _delete_client(args.rfc, args.no_database) return + if args.migrate: + _migrate_tables(args.rfc) + return + if args.update_sat: _update_sat() + return + + if args.new_superuser: + _new_superuser(args.rfc) + return + + if args.change_pass: + _change_pass(args.rfc) + return + + if args.backup: + utils.db_backup(IS_MV, URL['SEAFILE']) return -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' +# ~ 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('-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('-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) -@click.option('-id', '--importar-directorio') -@click.option('-ed', '--exportar_documentos', is_flag=True, default=False) -def main2(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, - borrar_rfc, listar_rfc, importar_valores, archivo, conexion, - factura_libre, test, generar_archivo_productos, - importar_productos, backup_dbs, no_bd, alta, rfc, detalle, - importar_directorio, exportar_documentos): +# ~ @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('-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('-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) +# ~ @click.option('-id', '--importar-directorio') +# ~ @click.option('-ed', '--exportar_documentos', is_flag=True, default=False) +# ~ def main2(iniciar_bd, migrar_bd, nuevo_superusuario, cambiar_contraseña, + # ~ borrar_rfc, listar_rfc, importar_valores, archivo, conexion, + # ~ factura_libre, test, generar_archivo_productos, + # ~ importar_productos, backup_dbs, no_bd, alta, rfc, detalle, + # ~ importar_directorio, exportar_documentos): - opt = locals() + # ~ opt = locals() - if opt['alta']: - if not opt['rfc']: - msg = 'Falta el RFC' - raise click.ClickException(msg) - # ~ empresa_agregar(opt['rfc'], no_bd) - _new_client(opt['rfc'], no_bd) - sys.exit(0) + # ~ if opt['alta']: + # ~ if not opt['rfc']: + # ~ msg = 'Falta el RFC' + # ~ raise click.ClickException(msg) + # ~ _new_client(opt['rfc'], no_bd) + # ~ sys.exit(0) - if opt['iniciar_bd']: - _iniciar_bd() - sys.exit(0) + # ~ if opt['iniciar_bd']: + # ~ _iniciar_bd() + # ~ sys.exit(0) - if opt['migrar_bd']: - _migrate_tables(rfc) - sys.exit(0) + # ~ if opt['migrar_bd']: + # ~ _migrate_tables(rfc) + # ~ sys.exit(0) - if opt['nuevo_superusuario']: - _agregar_superusuario() - 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['cambiar_contraseña']: + # ~ _cambiar_contraseña() + # ~ sys.exit(0) - if opt['borrar_rfc']: - _borrar_rfc() - 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['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) + # ~ 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) + # ~ _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) + # ~ 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) + # ~ _importar_factura_libre(opt['archivo']) + # ~ 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) + # ~ 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) + # ~ _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) + # ~ 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) + # ~ _importar_productos(opt['archivo']) + # ~ sys.exit(0) - if opt['importar_directorio']: - _import_from_folder(opt['importar_directorio']) - sys.exit(0) + # ~ if opt['importar_directorio']: + # ~ _import_from_folder(opt['importar_directorio']) + # ~ sys.exit(0) - if opt['backup_dbs']: - # ~ util.backup_dbs() - utils.db_backup(DB_COMPANIES, PATHS['BK'], IS_MV, URL['SEAFILE']) - sys.exit(0) + # ~ if opt['backup_dbs']: + # ~ utils.db_backup(DB_COMPANIES, PATHS['BK'], IS_MV, URL['SEAFILE']) + # ~ sys.exit(0) - if opt['exportar_documentos']: - _exportar_documentos() + # ~ if opt['exportar_documentos']: + # ~ _exportar_documentos() - return + # ~ return if __name__ == '__main__': From f9a73dfd66dceec5e2a7d156a76db0bd75a332be Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Tue, 21 Jan 2020 23:25:25 -0600 Subject: [PATCH 4/6] Add new PAC --- CHANGELOG.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85e9105..b3de097 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,24 @@ -v 1.33.0 [11-ene-2020] +v 1.33.0 [22-ene-2020] ---------------------- - Mejora: Cambio del mensaje para cuando se intenta dar de alta un cliente ya existente. - Mejora: Solo los admins pueden ver la nómina. + - Se agrega un segundo PAC + - Se actualizan los catalogos del SAT + +* IMPORTANTE: +Es necesario actualizar los catalogos del SAT +``` +git pull origin master +cd source/app/models +python main.py -bk +python main.py -us +``` v 1.32.0 [05-ene-2020] ---------------------- - - Mejora: Recuperar facturas no aceptas para cancelación por el receptor + - Mejora: Recuperar facturas no aceptadas para cancelación por el receptor v 1.31.2 [28-oct-2019] From a641024a86efb83a5b76983dee79e01f9a53425d Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Wed, 22 Jan 2020 12:48:15 -0600 Subject: [PATCH 5/6] Select Pac in admin --- CHANGELOG.md | 7 ++- .../app/controllers/comercio/conf.py.example | 40 ++++++++++++++++ source/app/models/main.py | 30 +++++++----- source/static/js/controller/admin.js | 36 +++++++++++++- source/static/js/controller/util.js | 9 ++++ source/static/js/ui/admin.js | 47 +++++++++++++------ 6 files changed, 141 insertions(+), 28 deletions(-) create mode 100644 source/app/controllers/comercio/conf.py.example diff --git a/CHANGELOG.md b/CHANGELOG.md index b3de097..f2c5570 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,17 @@ v 1.33.0 [22-ene-2020] - Mejora: Cambio del mensaje para cuando se intenta dar de alta un cliente ya existente. - Mejora: Solo los admins pueden ver la nómina. - Se agrega un segundo PAC - - Se actualizan los catalogos del SAT + - Se actualizan los catálogos del SAT * IMPORTANTE: -Es necesario actualizar los catalogos del SAT +Es necesario actualizar los catálogos del SAT ``` git pull origin master + cd source/app/models + python main.py -bk + python main.py -us ``` diff --git a/source/app/controllers/comercio/conf.py.example b/source/app/controllers/comercio/conf.py.example new file mode 100644 index 0000000..4e89d68 --- /dev/null +++ b/source/app/controllers/comercio/conf.py.example @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# ~ +# ~ PAC +# ~ Copyright (C) 2018-2019 Mauricio Baeza Servin - public [AT] elmau [DOT] net +# ~ +# ~ This program is free software: you can redistribute it and/or modify +# ~ it under the terms of the GNU General Public License as published by +# ~ the Free Software Foundation, either version 3 of the License, or +# ~ (at your option) any later version. +# ~ +# ~ This program is distributed in the hope that it will be useful, +# ~ but WITHOUT ANY WARRANTY; without even the implied warranty of +# ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# ~ GNU General Public License for more details. +# ~ +# ~ You should have received a copy of the GNU General Public License +# ~ along with this program. If not, see . + + +# ~ Siempre consulta la documentación de Finkok +# ~ AUTH = Puedes usar credenciales genericas para timbrar, o exclusivas para +# ~ cada emisor +# ~ RESELLER = Algunos procesos como agregar emisores, solo pueden ser usadas +# ~ con una cuenta de reseller + + +DEBUG = True + + +AUTH = { + 'user': '', + 'pass': '', +} + + +if DEBUG: + AUTH = { + 'user': 'AAA010101AAA', + 'pass': 'PWD', + } diff --git a/source/app/models/main.py b/source/app/models/main.py index 1d43452..7555088 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -19,7 +19,6 @@ import argparse from decimal import Decimal import sqlite3 -# ~ import click from peewee import * from playhouse.fields import PasswordField, ManyToManyField from playhouse.shortcuts import case, SQL, cast @@ -432,6 +431,7 @@ class Configuracion(BaseModel): 'chk_config_pagos', 'chk_config_divisas', 'chk_cfg_pays_data_bank', + 'chk_usar_nomina', ) data = (Configuracion .select() @@ -440,6 +440,9 @@ class Configuracion(BaseModel): values = {r.clave: util.get_bool(r.valor) for r in data} fields = ( + 'txt_ticket_printer', + 'txt_config_nomina_serie', + 'txt_config_nomina_folio', 'txt_config_cfdipay_serie', 'txt_config_cfdipay_folio', ) @@ -481,6 +484,17 @@ class Configuracion(BaseModel): values = {r.clave: util.get_bool(r.valor) for r in data} return values + @classmethod + def get_value(cls, key, default=''): + value = default + data = (Configuracion + .select(Configuracion.valor) + .where(Configuracion.clave == key) + ) + if data: + value = data[0].valor + return value + @classmethod def get_(cls, keys): if isinstance(keys, str): @@ -541,7 +555,6 @@ class Configuracion(BaseModel): 'chk_ticket_direct_print', 'chk_ticket_edit_cant', 'chk_ticket_total_up', - 'chk_usar_nomina', ) data = (Configuracion .select() @@ -549,15 +562,10 @@ class Configuracion(BaseModel): ) values = {r.clave: util.get_bool(r.valor) for r in data} fields = ( - 'txt_ticket_printer', - 'txt_config_nomina_serie', - 'txt_config_nomina_folio', - 'txt_config_cfdipay_serie', - 'txt_config_cfdipay_folio', + ('lst_pac', 'default'), ) - # ~ tp = 'txt_ticket_printer' - for f in fields: - values[f] = Configuracion.get_(f) + for k, d in fields: + values[k] = Configuracion.get_value(k, d) return values if keys['fields'] == 'path_cer': @@ -5000,7 +5008,7 @@ class Facturas(BaseModel): obj.save() enviar_correo = util.get_bool(Configuracion.get_('correo_directo')) - pac = Configuracion.get_('pac').lower() + pac = Configuracion.get_('lst_pac').lower() anticipo = False msg = 'Factura timbrada correctamente' diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 650b713..73597a4 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -120,6 +120,7 @@ var controllers = { $$('txt_config_cfdipay_serie').attachEvent('onKeyPress', txt_config_cfdipay_serie_press) $$('txt_config_cfdipay_folio').attachEvent('onKeyPress', txt_config_cfdipay_folio_press) $$('chk_usar_nomina').attachEvent('onItemClick', chk_config_item_click) + $$('lst_pac').attachEvent('onChange', lst_pac_on_change) $$('cmd_subir_bdfl').attachEvent('onItemClick', cmd_subir_bdfl_click) $$('cmd_subir_cfdixml').attachEvent('onItemClick', cmd_subir_cfdixml_click) @@ -464,7 +465,11 @@ function get_config_values(opt){ success: function(text, data, xhr) { var values = data.json() Object.keys(values).forEach(function(key){ - $$(key).setValue(values[key]) + if(key=='lst_pac'){ + set_value(key, values[key]) + }else{ + $$(key).setValue(values[key]) + } }) } }) @@ -2534,3 +2539,32 @@ function opt_make_pdf_from_on_change(new_value, old_value){ } +function lst_pac_on_change(nv, ov){ + if(nv=='default'){ + webix.ajax().del('/config', {id: 'lst_pac'}, function(text, xml, xhr){ + var msg = 'PAC predeterminado establecido correctamente' + if(xhr.status == 200){ + msg_ok(msg) + }else{ + msg = 'No se pudo eliminar' + msg_error(msg) + } + }) + }else{ + webix.ajax().post('/config', {'lst_pac': nv}, { + error: function(text, data, xhr) { + msg = 'Error al guardar la configuración' + msg_error(msg) + }, + success: function(text, data, xhr) { + var values = data.json(); + if (values.ok){ + msg = 'PAC establecido correctamente' + msg_ok(msg) + }else{ + msg_error(values.msg) + } + } + }) + } +} diff --git a/source/static/js/controller/util.js b/source/static/js/controller/util.js index e74b00e..9150608 100644 --- a/source/static/js/controller/util.js +++ b/source/static/js/controller/util.js @@ -579,3 +579,12 @@ function lst_clear(lst){ function lst_parse(lst, values){ lst.getList().parse(values) } + + +function set_value(control, value){ + obj = $$(control) + obj.blockEvent() + obj.setValue(value) + obj.unblockEvent() +} + diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js index da12a80..4affa44 100644 --- a/source/static/js/ui/admin.js +++ b/source/static/js/ui/admin.js @@ -643,6 +643,12 @@ var options_templates = [ {}] +var options_pac = [ + {id: 'default', value: 'Predeterminado'}, + {id: 'comercio', value: 'Comercio Digital'}, +] + + var options_admin_otros = [ {maxHeight: 15}, {template: 'Facturación', type: 'section'}, @@ -674,6 +680,13 @@ var options_admin_otros = [ {view: 'checkbox', id: 'chk_config_decimales_precios', labelWidth: 0, labelRight: 'Precios con 4 decimales'}, {}, ]}, + {maxHeight: 15}, + {cols: [{maxWidth: 15}, + {view: 'richselect', id: 'lst_pac', name: 'lst_pac', width: 300, + label: 'PAC: ', value: 'default', required: false, + options: options_pac}, + {}, + ]}, {maxHeight: 20}, {template: 'Ayudas varias', type: 'section'}, {cols: [{maxWidth: 15}, @@ -700,15 +713,6 @@ var options_admin_otros = [ labelRight: 'Mostrar total arriba'}, {}]}, {maxHeight: 20}, - {template: 'Nómina', type: 'section'}, - {cols: [{maxWidth: 15}, - {view: 'checkbox', id: 'chk_usar_nomina', labelWidth: 0, - labelRight: 'Usar timbrado de Nómina'}, - {view: 'text', id: 'txt_config_nomina_serie', name: 'config_nomina_serie', - label: 'Serie', labelWidth: 50, labelAlign: 'right'}, - {view: 'text', id: 'txt_config_nomina_folio', name: 'config_nomina_folio', - label: 'Folio', labelWidth: 50, labelAlign: 'right'}, - {}]}, {}] @@ -738,12 +742,15 @@ var options_admin_products = [ var options_admin_complements = [ {maxHeight: 20}, + {template: 'Complemento de Nómina', type: 'section'}, {cols: [{maxWidth: 15}, - {view: 'checkbox', id: 'chk_config_ine', labelWidth: 0, - labelRight: 'Usar el complemento INE'}, - {view: 'checkbox', id: 'chk_config_edu', labelWidth: 0, - labelRight: 'Usar el complemento EDU'}, - {}]}, + {view: 'checkbox', id: 'chk_usar_nomina', labelWidth: 0, + labelRight: 'Usar complemento de Nómina'}, + {view: 'text', id: 'txt_config_nomina_serie', name: 'config_nomina_serie', + label: 'Serie', labelWidth: 50, labelAlign: 'right'}, + {view: 'text', id: 'txt_config_nomina_folio', name: 'config_nomina_folio', + label: 'Folio', labelWidth: 50, labelAlign: 'right'}, + {maxWidth: 15}]}, {maxHeight: 20}, {template: 'Complemento de Pagos', type: 'section'}, {cols: [{maxWidth: 15}, @@ -762,6 +769,18 @@ var options_admin_complements = [ {view: 'checkbox', id: 'chk_config_divisas', labelWidth: 0, labelRight: 'Usar complemento de divisas'}, {maxWidth: 15}]}, + {maxHeight: 20}, + {template: 'Complemento INE', type: 'section'}, + {cols: [{maxWidth: 15}, + {view: 'checkbox', id: 'chk_config_ine', labelWidth: 0, + labelRight: 'Usar el complemento INE'}, + {maxWidth: 15}]}, + {maxHeight: 20}, + {template: 'Complemento para escuelas EDU', type: 'section'}, + {cols: [{maxWidth: 15}, + {view: 'checkbox', id: 'chk_config_edu', labelWidth: 0, + labelRight: 'Usar el complemento EDU'}, + {maxWidth: 15}]}, ] From 3f0daec6ad054899dd5c815abc327878bf9f5edc Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Wed, 22 Jan 2020 13:21:53 -0600 Subject: [PATCH 6/6] Change argument user for stamp --- source/app/controllers/utils.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/source/app/controllers/utils.py b/source/app/controllers/utils.py index 8b13b09..c955291 100644 --- a/source/app/controllers/utils.py +++ b/source/app/controllers/utils.py @@ -577,10 +577,7 @@ def xml_stamp(xml, auth, name): return result result = {'ok': True, 'error': ''} - if name == 'comercio': - auth = {'user': auth['RFC'], 'pass': auth['PASS']} - else: - auth = {'user': auth['USER'], 'pass': auth['PASS']} + auth = {'user': auth['USER'], 'pass': auth['PASS']} pac = PACS[name]() xml_stamped = pac.stamp(xml, auth)