diff --git a/requirements.txt b/requirements.txt
index a007210..389a78d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -13,6 +13,7 @@ pypng
reportlab
psycopg2-binary
cryptography
+xmlsec
# escpos
# pyusb
diff --git a/source/app/controllers/cfdi_cert.py b/source/app/controllers/cfdi_cert.py
new file mode 100644
index 0000000..12b19c4
--- /dev/null
+++ b/source/app/controllers/cfdi_cert.py
@@ -0,0 +1,256 @@
+#!/usr/bin/env python3
+
+import argparse
+import base64
+import datetime
+import getpass
+from pathlib import Path
+
+import xmlsec
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import serialization
+from cryptography import x509
+from cryptography.x509.oid import NameOID
+from cryptography.x509.oid import ExtensionOID
+from cryptography.hazmat.primitives import hashes
+from cryptography.hazmat.primitives.asymmetric import padding
+
+
+from .conf import TOKEN
+
+
+class SATCertificate(object):
+
+ def __init__(self, cer=b'', key=b'', password=''):
+ self._error = ''
+ self._init_values()
+ self._get_data_cer(cer)
+ self._get_data_key(key, password)
+
+ def _init_values(self):
+ self._rfc = ''
+ self._serial_number = ''
+ self._not_before = None
+ self._not_after = None
+ self._is_fiel = False
+ self._are_couple = False
+ self._is_valid_time = False
+ self._cer_pem = ''
+ self._cer_txt = ''
+ self._key_enc = b''
+ self._p12 = b''
+ self._cer_modulus = 0
+ self._key_modulus = 0
+ return
+
+ def __str__(self):
+ msg = '\tRFC: {}\n'.format(self.rfc)
+ msg += '\tNo de Serie: {}\n'.format(self.serial_number)
+ msg += '\tVálido desde: {}\n'.format(self.not_before)
+ msg += '\tVálido hasta: {}\n'.format(self.not_after)
+ msg += '\tEs vigente: {}\n'.format(self.is_valid_time)
+ msg += '\tSon pareja: {}\n'.format(self.are_couple)
+ msg += '\tEs FIEL: {}\n'.format(self.is_fiel)
+ return msg
+
+ def __bool__(self):
+ return self.is_valid
+
+ def _get_hash(self):
+ digest = hashes.Hash(hashes.SHA512(), default_backend())
+ digest.update(self._rfc.encode())
+ digest.update(self._serial_number.encode())
+ digest.update(TOKEN.encode())
+ return digest.finalize()
+
+ def _get_data_cer(self, cer):
+ obj = x509.load_der_x509_certificate(cer, default_backend())
+ self._rfc = obj.subject.get_attributes_for_oid(
+ NameOID.X500_UNIQUE_IDENTIFIER)[0].value.split(' ')[0]
+ self._serial_number = '{0:x}'.format(obj.serial_number)[1::2]
+ self._not_before = obj.not_valid_before
+ self._not_after = obj.not_valid_after
+ now = datetime.datetime.utcnow()
+ self._is_valid_time = (now > self.not_before) and (now < self.not_after)
+ if not self._is_valid_time:
+ msg = 'El certificado no es vigente'
+ self._error = msg
+
+ self._is_fiel = obj.extensions.get_extension_for_oid(
+ ExtensionOID.KEY_USAGE).value.key_agreement
+
+ self._cer_pem = obj.public_bytes(serialization.Encoding.PEM).decode()
+ self._cer_txt = ''.join(self._cer_pem.split('\n')[1:-2])
+ self._cer_modulus = obj.public_key().public_numbers().n
+ return
+
+ def _get_data_key(self, key, password):
+ self._key_enc = key
+ if not key or not password:
+ return
+
+ try:
+ obj = serialization.load_der_private_key(
+ key, password.encode(), default_backend())
+ except ValueError:
+ msg = 'La contraseña es incorrecta'
+ self._error = msg
+ return
+
+ p = self._get_hash()
+ self._key_enc = obj.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=serialization.BestAvailableEncryption(p)
+ )
+
+ self._key_modulus = obj.public_key().public_numbers().n
+ self._are_couple = self._cer_modulus == self._key_modulus
+ if not self._are_couple:
+ msg = 'El CER y el KEY no son pareja'
+ self._error = msg
+ return
+
+ def _get_key(self, password):
+ if not password:
+ password = self._get_hash()
+ private_key = serialization.load_pem_private_key(
+ self._key_enc, password=password, backend=default_backend())
+ return private_key
+
+ def _get_key_pem(self):
+ obj = self._get_key('')
+ key_pem = obj.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=serialization.NoEncryption()
+ )
+ return key_pem
+
+ # Not work
+ def _get_p12(self):
+ obj = serialization.pkcs12.serialize_key_and_certificates('test',
+ self.key_pem, self.cer_pem, None,
+ encryption_algorithm=serialization.NoEncryption()
+ )
+ return obj
+
+ def sign(self, data, password=''):
+ private_key = self._get_key(password)
+ firma = private_key.sign(data, padding.PKCS1v15(), hashes.SHA256())
+ return base64.b64encode(firma).decode()
+
+ def sign_xml(self, tree):
+ node = xmlsec.tree.find_node(tree, xmlsec.constants.NodeSignature)
+ ctx = xmlsec.SignatureContext()
+ key = xmlsec.Key.from_memory(self.key_pem, xmlsec.constants.KeyDataFormatPem)
+ ctx.key = key
+ ctx.sign(node)
+ node = xmlsec.tree.find_node(tree, 'X509Certificate')
+ node.text = self.cer_txt
+ return tree
+
+ @property
+ def rfc(self):
+ return self._rfc
+
+ @property
+ def serial_number(self):
+ return self._serial_number
+
+ @property
+ def not_before(self):
+ return self._not_before
+
+ @property
+ def not_after(self):
+ return self._not_after
+
+ @property
+ def is_fiel(self):
+ return self._is_fiel
+
+ @property
+ def are_couple(self):
+ return self._are_couple
+
+ @property
+ def is_valid(self):
+ return not bool(self.error)
+
+ @property
+ def is_valid_time(self):
+ return self._is_valid_time
+
+ @property
+ def cer_pem(self):
+ return self._cer_pem.encode()
+
+ @property
+ def cer_txt(self):
+ return self._cer_txt
+
+ @property
+ def key_pem(self):
+ return self._get_key_pem()
+
+ @property
+ def key_enc(self):
+ return self._key_enc
+
+ @property
+ def p12(self):
+ return self._get_p12()
+
+ @property
+ def error(self):
+ return self._error
+
+
+def main(args):
+ # ~ contra = getpass.getpass('Introduce la contraseña del archivo KEY: ')
+ contra = '12345678a'
+ if not contra.strip():
+ msg = 'La contraseña es requerida'
+ print(msg)
+ return
+
+ path_cer = Path(args.cer)
+ path_key = Path(args.key)
+
+ if not path_cer.is_file():
+ msg = 'El archivo CER es necesario'
+ print(msg)
+ return
+
+ if not path_key.is_file():
+ msg = 'El archivo KEY es necesario'
+ print(msg)
+ return
+
+ cer = path_cer.read_bytes()
+ key = path_key.read_bytes()
+ cert = SATCertificate(cer, key, contra)
+
+ if cert.error:
+ print(cert.error)
+ else:
+ print(cert)
+ return
+
+
+def _process_command_line_arguments():
+ parser = argparse.ArgumentParser(description='CFDI Certificados')
+
+ help = 'Archivo CER'
+ parser.add_argument('-c', '--cer', help=help, default='')
+ help = 'Archivo KEY'
+ parser.add_argument('-k', '--key', help=help, default='')
+
+ args = parser.parse_args()
+ return args
+
+
+if __name__ == '__main__':
+ args = _process_command_line_arguments()
+ main(args)
diff --git a/source/app/controllers/comercio/__init__.py b/source/app/controllers/comercio/__init__.py
deleted file mode 100644
index 195aadd..0000000
--- a/source/app/controllers/comercio/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env python3
-
-from .comercio import PACComercioDigital
diff --git a/source/app/controllers/comercio/comercio.py b/source/app/controllers/comercio/comercio.py
deleted file mode 100644
index 8836156..0000000
--- a/source/app/controllers/comercio/comercio.py
+++ /dev/null
@@ -1,342 +0,0 @@
-#!/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
-
-
-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)
-
-
-try:
- from .conf import DEBUG, AUTH
-except ImportError:
- DEBUG = False
- log.debug('Need make conf.py')
-
-
-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/comercio/conf.py.example b/source/app/controllers/comercio/conf.py.example
deleted file mode 100644
index de81efb..0000000
--- a/source/app/controllers/comercio/conf.py.example
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/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 = False
-
-
-AUTH = {
- 'user': '',
- 'pass': '',
-}
-
-
-if DEBUG:
- AUTH = {
- 'user': 'AAA010101AAA',
- 'pass': 'PWD',
- }
diff --git a/source/app/controllers/configpac.py b/source/app/controllers/configpac.py
index 814fefa..d4c70a0 100644
--- a/source/app/controllers/configpac.py
+++ b/source/app/controllers/configpac.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
-from .conf import DEBUG, ID_INTEGRADOR, FINKOK
+from .conf import DEBUG, FINKOK
DEBUG = DEBUG
TIMEOUT = 10
@@ -11,34 +11,6 @@ TIMEOUT = 10
PAC = 'finkok'
-def ecodex(debug):
- NEW_SERVER = True
- auth = {'ID': ID_INTEGRADOR}
- if debug:
- #~ No cambies este ID de pruebas
- auth = {'ID': '2b3a8764-d586-4543-9b7e-82834443f219'}
-
- base_url = 'https://servicios.ecodex.com.mx:4043/Servicio{}.svc?wsdl'
- if NEW_SERVER:
- base_url = 'https://serviciosnominas.ecodex.com.mx:4043/Servicio{}.svc?wsdl'
- base_api = 'https://api.ecodex.com.mx/{}'
- if debug:
- base_url = 'https://wsdev.ecodex.com.mx:2045/Servicio{}.svc?wsdl'
- base_api = 'https://pruebasapi.ecodex.com.mx/{}'
- url = {
- 'seguridad': base_url.format('Seguridad'),
- 'clients': base_url.format('Clientes'),
- 'timbra': base_url.format('Timbrado'),
- 'token': base_api.format('token?version=2'),
- 'docs': base_api.format('api/documentos'),
- 'hash': base_api.format('api/Documentos/{}'),
- 'codes': {
- 'HASH': 'DUPLICIDAD EN HASH',
- }
- }
- return auth, url
-
-
#~ IMPORTANTE: Si quieres hacer pruebas, con tu propio correo de usuario y
#~ contraseña, ponte en contacto con Finkok para que te asignen tus datos de
#~ acceso, consulta su documentación para ver las diferentes opciones de acceso.
diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py
index 3efcfc2..81441f1 100644
--- a/source/app/controllers/main.py
+++ b/source/app/controllers/main.py
@@ -632,3 +632,19 @@ class AppSociosCuentasBanco(object):
req.context['result'] = self._db.partners_accounts_bank(values)
resp.status = falcon.HTTP_200
+
+class AppCert(object):
+
+ def __init__(self, db):
+ self._db = db
+
+ def on_get(self, req, resp):
+ values = req.params
+ req.context['result'] = self._db.cert_get(values)
+ resp.status = falcon.HTTP_200
+
+ def on_post(self, req, resp):
+ values = req.params
+ req.context['result'] = self._db.cert_post(values)
+ resp.status = falcon.HTTP_200
+
diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py
index 26e24d0..4465cfa 100644
--- a/source/app/controllers/util.py
+++ b/source/app/controllers/util.py
@@ -68,7 +68,7 @@ from settings import DEBUG, MV, log, template_lookup, COMPANIES, DB_SAT, \
PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \
PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES, DIR_FACTURAS
-from settings import SEAFILE_SERVER, USAR_TOKEN, API, DECIMALES_TAX
+from settings import USAR_TOKEN, API, DECIMALES_TAX
from .configpac import AUTH
diff --git a/source/app/controllers/utils.py b/source/app/controllers/utils.py
index 0c498ab..6ba662b 100644
--- a/source/app/controllers/utils.py
+++ b/source/app/controllers/utils.py
@@ -48,9 +48,9 @@ from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from dateutil import parser
-import seafileapi
-
from settings import DEBUG, DB_COMPANIES, PATHS
+
+from .cfdi_cert import SATCertificate
from .pacs import PACComercioDigital
from .pac import Finkok as PACFinkok
# ~ from .finkok import PACFinkok
@@ -492,29 +492,6 @@ def _backup_db(rfc, is_mv, url_seafile):
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
-
- # ~ 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'
- # ~ uuid = 'cc42c591-cf66-499a-ae70-c09df5646be9'
-
- # ~ log.debug(url_seafile, email, _get_pass(rfc))
- # ~ client = seafileapi.connect(url_seafile, email, _get_pass(rfc))
- # ~ repo = client.repos.get_repo(uuid)
- # ~ print(repo)
-
return
@@ -629,14 +606,30 @@ def xml_cancel(xml, auth, cert, name):
return data, result
-def get_client_balance(auth, name):
+def get_client_balance(auth):
if DEBUG:
return '-d'
- pac = PACS[name]()
- auth = {'usr': auth['USER'], 'pwd': auth['PASS']}
+ pac = PACS[auth['pac']]()
balance = pac.client_balance(auth)
if pac.error:
balance = '-e'
return balance
+
+
+def get_cert(args):
+ p1 = '/home/mau/Desktop/Pruebas_EKU9003173C9/file.cer'
+ cer = args['cer']
+ # ~ cer = cer.encode()
+ # ~ cer = base64.b64decode(args['cer'].encode())
+ with open(p1, 'w') as f:
+ f.write(cer)
+ # ~ cer = base64.b64decode(args['cer'].encode())
+ print('TYPE', type(cer))
+ # ~ print(cer)
+ # ~ key = base64.b64decode(args['key'].encode())
+ # ~ cert = SATCertificate(cer, key, args['contra'])
+ return
+
+
diff --git a/source/app/main.py b/source/app/main.py
index c03b858..cb28630 100644
--- a/source/app/main.py
+++ b/source/app/main.py
@@ -17,7 +17,7 @@ from controllers.main import (AppEmpresas,
AppDocumentos, AppFiles, AppPreInvoices, AppCuentasBanco,
AppMovimientosBanco, AppTickets, AppStudents, AppEmployees, AppNomina,
AppInvoicePay, AppCfdiPay, AppSATBancos, AppSociosCuentasBanco,
- AppSATFormaPago, AppSATLeyendaFiscales
+ AppSATFormaPago, AppSATLeyendaFiscales, AppCert
)
@@ -62,6 +62,7 @@ api.add_route('/satbancos', AppSATBancos(db))
api.add_route('/satformapago', AppSATFormaPago(db))
api.add_route('/socioscb', AppSociosCuentasBanco(db))
api.add_route('/leyendasfiscales', AppSATLeyendaFiscales(db))
+api.add_route('/cert', AppCert(db))
session_options = {
diff --git a/source/app/models/db.py b/source/app/models/db.py
index 7d50926..2219a2d 100644
--- a/source/app/models/db.py
+++ b/source/app/models/db.py
@@ -471,6 +471,13 @@ class StorageEngine(object):
def sat_leyendas_fiscales_delete(self, values):
return main.SATLeyendasFiscales.remove(values)
+ # ~ v2
+ def cert_get(self, values):
+ return main.Certificado.get_data(values)
+
+ def cert_post(self, values):
+ return main.Certificado.post(values)
+
# 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 798dc7e..3fdcb54 100644
--- a/source/app/models/main.py
+++ b/source/app/models/main.py
@@ -1165,19 +1165,35 @@ class Certificado(BaseModel):
return self.serie
@classmethod
- def get_cert(cls, is_fiel=False):
- return Certificado.get(Certificado.es_fiel==is_fiel)
-
- @classmethod
- def get_data(cls):
- obj = cls.get_(cls)
- row = {
+ def _get_cert(cls, args):
+ obj = Certificado.get(Certificado.es_fiel==False)
+ data = {
'cert_rfc': obj.rfc,
'cert_serie': obj.serie,
'cert_desde': obj.desde,
'cert_hasta': obj.hasta,
}
- return row
+ return data
+
+ @classmethod
+ def get_data(cls, values):
+ opt = values['opt']
+ return getattr(cls, f'_get_{opt}')(values)
+
+ @classmethod
+ def _validate(cls, args):
+ cert = utils.get_cert(args)
+ print(cert)
+ return {'ok': False, 'msg': 'error', 'data': {}}
+
+ @classmethod
+ def post(cls, values):
+ opt = values['opt']
+ return getattr(cls, f'_{opt}')(values)
+
+ # ~ @classmethod
+ # ~ def get_cert(cls, is_fiel=False):
+ # ~ return Certificado.get(Certificado.es_fiel==is_fiel)
def get_(cls):
return Certificado.select()[0]
diff --git a/source/app/seafileapi/__init__.py b/source/app/seafileapi/__init__.py
deleted file mode 100644
index d6c3b8d..0000000
--- a/source/app/seafileapi/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from seafileapi.client import SeafileApiClient
-
-def connect(server, username, password):
- client = SeafileApiClient(server, username, password)
- return client
diff --git a/source/app/seafileapi/admin.py b/source/app/seafileapi/admin.py
deleted file mode 100644
index 08fc2c4..0000000
--- a/source/app/seafileapi/admin.py
+++ /dev/null
@@ -1,7 +0,0 @@
-
-class SeafileAdmin(object):
- def lists_users(self, maxcount=100):
- pass
-
- def list_user_repos(self, username):
- pass
diff --git a/source/app/seafileapi/client.py b/source/app/seafileapi/client.py
deleted file mode 100644
index 52a6ea6..0000000
--- a/source/app/seafileapi/client.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import requests
-from seafileapi.utils import urljoin
-from seafileapi.exceptions import ClientHttpError
-from seafileapi.repos import Repos
-
-class SeafileApiClient(object):
- """Wraps seafile web api"""
- def __init__(self, server, username=None, password=None, token=None):
- """Wraps various basic operations to interact with seahub http api.
- """
- self.server = server
- self.username = username
- self.password = password
- self._token = token
-
- self.repos = Repos(self)
- self.groups = Groups(self)
-
- if token is None:
- self._get_token()
-
- def _get_token(self):
- data = {
- 'username': self.username,
- 'password': self.password,
- }
- url = urljoin(self.server, '/api2/auth-token/')
- res = requests.post(url, data=data)
- if res.status_code != 200:
- raise ClientHttpError(res.status_code, res.content)
- token = res.json()['token']
- assert len(token) == 40, 'The length of seahub api auth token should be 40'
- self._token = token
-
- def __str__(self):
- return 'SeafileApiClient[server=%s, user=%s]' % (self.server, self.username)
-
- __repr__ = __str__
-
- def get(self, *args, **kwargs):
- return self._send_request('GET', *args, **kwargs)
-
- def post(self, *args, **kwargs):
- return self._send_request('POST', *args, **kwargs)
-
- def put(self, *args, **kwargs):
- return self._send_request('PUT', *args, **kwargs)
-
- def delete(self, *args, **kwargs):
- return self._send_request('delete', *args, **kwargs)
-
- def _send_request(self, method, url, *args, **kwargs):
- if not url.startswith('http'):
- url = urljoin(self.server, url)
-
- headers = kwargs.get('headers', {})
- headers.setdefault('Authorization', 'Token ' + self._token)
- kwargs['headers'] = headers
-
- expected = kwargs.pop('expected', 200)
- if not hasattr(expected, '__iter__'):
- expected = (expected, )
- resp = requests.request(method, url, *args, **kwargs)
- if resp.status_code not in expected:
- msg = 'Expected %s, but get %s' % \
- (' or '.join(map(str, expected)), resp.status_code)
- raise ClientHttpError(resp.status_code, msg)
-
- return resp
-
-
-class Groups(object):
- def __init__(self, client):
- pass
-
- def create_group(self, name):
- pass
diff --git a/source/app/seafileapi/exceptions.py b/source/app/seafileapi/exceptions.py
deleted file mode 100644
index b11498d..0000000
--- a/source/app/seafileapi/exceptions.py
+++ /dev/null
@@ -1,25 +0,0 @@
-
-class ClientHttpError(Exception):
- """This exception is raised if the returned http response is not as
- expected"""
- def __init__(self, code, message):
- super(ClientHttpError, self).__init__()
- self.code = code
- self.message = message
-
- def __str__(self):
- return 'ClientHttpError[%s: %s]' % (self.code, self.message)
-
-class OperationError(Exception):
- """Expcetion to raise when an opeartion is failed"""
- pass
-
-
-class DoesNotExist(Exception):
- """Raised when not matching resource can be found."""
- def __init__(self, msg):
- super(DoesNotExist, self).__init__()
- self.msg = msg
-
- def __str__(self):
- return 'DoesNotExist: %s' % self.msg
diff --git a/source/app/seafileapi/files.py b/source/app/seafileapi/files.py
deleted file mode 100644
index ed01e64..0000000
--- a/source/app/seafileapi/files.py
+++ /dev/null
@@ -1,250 +0,0 @@
-import io
-import os
-import posixpath
-import re
-from seafileapi.utils import querystr
-
-ZERO_OBJ_ID = '0000000000000000000000000000000000000000'
-
-class _SeafDirentBase(object):
- """Base class for :class:`SeafFile` and :class:`SeafDir`.
-
- It provides implementation of their common operations.
- """
- isdir = None
-
- def __init__(self, repo, path, object_id, size=0):
- """
- :param:`path` the full path of this entry within its repo, like
- "/documents/example.md"
-
- :param:`size` The size of a file. It should be zero for a dir.
- """
- self.client = repo.client
- self.repo = repo
- self.path = path
- self.id = object_id
- self.size = size
-
- @property
- def name(self):
- return posixpath.basename(self.path)
-
- def list_revisions(self):
- pass
-
- def delete(self):
- suffix = 'dir' if self.isdir else 'file'
- url = '/api2/repos/%s/%s/' % (self.repo.id, suffix) + querystr(p=self.path)
- resp = self.client.delete(url)
- return resp
-
- def rename(self, newname):
- """Change file/folder name to newname
- """
- suffix = 'dir' if self.isdir else 'file'
- url = '/api2/repos/%s/%s/' % (self.repo.id, suffix) + querystr(p=self.path, reloaddir='true')
- postdata = {'operation': 'rename', 'newname': newname}
- resp = self.client.post(url, data=postdata)
- succeeded = resp.status_code == 200
- if succeeded:
- if self.isdir:
- new_dirent = self.repo.get_dir(os.path.join(os.path.dirname(self.path), newname))
- else:
- new_dirent = self.repo.get_file(os.path.join(os.path.dirname(self.path), newname))
- for key in list(self.__dict__.keys()):
- self.__dict__[key] = new_dirent.__dict__[key]
- return succeeded
-
- def _copy_move_task(self, operation, dirent_type, dst_dir, dst_repo_id=None):
- url = '/api/v2.1/copy-move-task/'
- src_repo_id = self.repo.id
- src_parent_dir = os.path.dirname(self.path)
- src_dirent_name = os.path.basename(self.path)
- dst_repo_id = dst_repo_id
- dst_parent_dir = dst_dir
- operation = operation
- dirent_type = dirent_type
- postdata = {'src_repo_id': src_repo_id, 'src_parent_dir': src_parent_dir,
- 'src_dirent_name': src_dirent_name, 'dst_repo_id': dst_repo_id,
- 'dst_parent_dir': dst_parent_dir, 'operation': operation,
- 'dirent_type': dirent_type}
- return self.client.post(url, data=postdata)
-
- def copyTo(self, dst_dir, dst_repo_id=None):
- """Copy file/folder to other directory (also to a different repo)
- """
- if dst_repo_id is None:
- dst_repo_id = self.repo.id
-
- dirent_type = 'dir' if self.isdir else 'file'
- resp = self._copy_move_task('copy', dirent_type, dst_dir, dst_repo_id)
- return resp.status_code == 200
-
- def moveTo(self, dst_dir, dst_repo_id=None):
- """Move file/folder to other directory (also to a different repo)
- """
- if dst_repo_id is None:
- dst_repo_id = self.repo.id
-
- dirent_type = 'dir' if self.isdir else 'file'
- resp = self._copy_move_task('move', dirent_type, dst_dir, dst_repo_id)
- succeeded = resp.status_code == 200
- if succeeded:
- new_repo = self.client.repos.get_repo(dst_repo_id)
- dst_path = os.path.join(dst_dir, os.path.basename(self.path))
- if self.isdir:
- new_dirent = new_repo.get_dir(dst_path)
- else:
- new_dirent = new_repo.get_file(dst_path)
- for key in list(self.__dict__.keys()):
- self.__dict__[key] = new_dirent.__dict__[key]
- return succeeded
-
- def get_share_link(self):
- pass
-
-class SeafDir(_SeafDirentBase):
- isdir = True
-
- def __init__(self, *args, **kwargs):
- super(SeafDir, self).__init__(*args, **kwargs)
- self.entries = None
- self.entries = kwargs.pop('entries', None)
-
- def ls(self, force_refresh=False):
- """List the entries in this dir.
-
- Return a list of objects of class :class:`SeafFile` or :class:`SeafDir`.
- """
- if self.entries is None or force_refresh:
- self.load_entries()
-
- return self.entries
-
- def share_to_user(self, email, permission):
- url = '/api2/repos/%s/dir/shared_items/' % self.repo.id + querystr(p=self.path)
- putdata = {
- 'share_type': 'user',
- 'username': email,
- 'permission': permission
- }
- resp = self.client.put(url, data=putdata)
- return resp.status_code == 200
-
- def create_empty_file(self, name):
- """Create a new empty file in this dir.
- Return a :class:`SeafFile` object of the newly created file.
- """
- # TODO: file name validation
- path = posixpath.join(self.path, name)
- url = '/api2/repos/%s/file/' % self.repo.id + querystr(p=path, reloaddir='true')
- postdata = {'operation': 'create'}
- resp = self.client.post(url, data=postdata)
- self.id = resp.headers['oid']
- self.load_entries(resp.json())
- return SeafFile(self.repo, path, ZERO_OBJ_ID, 0)
-
- def mkdir(self, name):
- """Create a new sub folder right under this dir.
-
- Return a :class:`SeafDir` object of the newly created sub folder.
- """
- path = posixpath.join(self.path, name)
- url = '/api2/repos/%s/dir/' % self.repo.id + querystr(p=path, reloaddir='true')
- postdata = {'operation': 'mkdir'}
- resp = self.client.post(url, data=postdata)
- self.id = resp.headers['oid']
- self.load_entries(resp.json())
- return SeafDir(self.repo, path, ZERO_OBJ_ID)
-
- def upload(self, fileobj, filename):
- """Upload a file to this folder.
-
- :param:fileobj :class:`File` like object
- :param:filename The name of the file
-
- Return a :class:`SeafFile` object of the newly uploaded file.
- """
- if isinstance(fileobj, str):
- fileobj = io.BytesIO(fileobj)
- upload_url = self._get_upload_link()
- files = {
- 'file': (filename, fileobj),
- 'parent_dir': self.path,
- }
- self.client.post(upload_url, files=files)
- return self.repo.get_file(posixpath.join(self.path, filename))
-
- def upload_local_file(self, filepath, name=None):
- """Upload a file to this folder.
-
- :param:filepath The path to the local file
- :param:name The name of this new file. If None, the name of the local file would be used.
-
- Return a :class:`SeafFile` object of the newly uploaded file.
- """
- name = name or os.path.basename(filepath)
- with open(filepath, 'r') as fp:
- return self.upload(fp, name)
-
- def _get_upload_link(self):
- url = '/api2/repos/%s/upload-link/' % self.repo.id
- resp = self.client.get(url)
- return re.match(r'"(.*)"', resp.text).group(1)
-
- def get_uploadable_sharelink(self):
- """Generate a uploadable shared link to this dir.
-
- Return the url of this link.
- """
- pass
-
- def load_entries(self, dirents_json=None):
- if dirents_json is None:
- url = '/api2/repos/%s/dir/' % self.repo.id + querystr(p=self.path)
- dirents_json = self.client.get(url).json()
-
- self.entries = [self._load_dirent(entry_json) for entry_json in dirents_json]
-
- def _load_dirent(self, dirent_json):
- path = posixpath.join(self.path, dirent_json['name'])
- if dirent_json['type'] == 'file':
- return SeafFile(self.repo, path, dirent_json['id'], dirent_json['size'])
- else:
- return SeafDir(self.repo, path, dirent_json['id'], 0)
-
- @property
- def num_entries(self):
- if self.entries is None:
- self.load_entries()
- return len(self.entries) if self.entries is not None else 0
-
- def __str__(self):
- return 'SeafDir[repo=%s,path=%s,entries=%s]' % \
- (self.repo.id[:6], self.path, self.num_entries)
-
- __repr__ = __str__
-
-class SeafFile(_SeafDirentBase):
- isdir = False
-
- def update(self, fileobj):
- """Update the content of this file"""
- pass
-
- def __str__(self):
- return 'SeafFile[repo=%s,path=%s,size=%s]' % \
- (self.repo.id[:6], self.path, self.size)
-
- def _get_download_link(self):
- url = '/api2/repos/%s/file/' % self.repo.id + querystr(p=self.path)
- resp = self.client.get(url)
- return re.match(r'"(.*)"', resp.text).group(1)
-
- def get_content(self):
- """Get the content of the file"""
- url = self._get_download_link()
- return self.client.get(url).content
-
- __repr__ = __str__
diff --git a/source/app/seafileapi/group.py b/source/app/seafileapi/group.py
deleted file mode 100644
index 731d7ef..0000000
--- a/source/app/seafileapi/group.py
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-class Group(object):
- def __init__(self, client, group_id, group_name):
- self.client = client
- self.group_id = group_id
- self.group_name = group_name
-
- def list_memebers(self):
- pass
-
- def delete(self):
- pass
-
- def add_member(self, username):
- pass
-
- def remove_member(self, username):
- pass
-
- def list_group_repos(self):
- pass
diff --git a/source/app/seafileapi/repo.py b/source/app/seafileapi/repo.py
deleted file mode 100644
index 01811a2..0000000
--- a/source/app/seafileapi/repo.py
+++ /dev/null
@@ -1,99 +0,0 @@
-from urllib.parse import urlencode
-from seafileapi.files import SeafDir, SeafFile
-from seafileapi.utils import raise_does_not_exist
-
-class Repo(object):
- """
- A seafile library
- """
- def __init__(self, client, repo_id, repo_name,
- encrypted, owner, perm):
- self.client = client
- self.id = repo_id
- self.name = repo_name
- self.encrypted = encrypted
- self.owner = owner
- self.perm = perm
-
- @classmethod
- def from_json(cls, client, repo_json):
-
- repo_id = repo_json['id']
- repo_name = repo_json['name']
- encrypted = repo_json['encrypted']
- perm = repo_json['permission']
- owner = repo_json['owner']
-
- return cls(client, repo_id, repo_name, encrypted, owner, perm)
-
- def is_readonly(self):
- return 'w' not in self.perm
-
- @raise_does_not_exist('The requested file does not exist')
- def get_file(self, path):
- """Get the file object located in `path` in this repo.
-
- Return a :class:`SeafFile` object
- """
- assert path.startswith('/')
- url = '/api2/repos/%s/file/detail/' % self.id
- query = '?' + urlencode(dict(p=path))
- file_json = self.client.get(url + query).json()
-
- return SeafFile(self, path, file_json['id'], file_json['size'])
-
- @raise_does_not_exist('The requested dir does not exist')
- def get_dir(self, path):
- """Get the dir object located in `path` in this repo.
-
- Return a :class:`SeafDir` object
- """
- assert path.startswith('/')
- url = '/api2/repos/%s/dir/' % self.id
- query = '?' + urlencode(dict(p=path))
- resp = self.client.get(url + query)
- dir_id = resp.headers['oid']
- dir_json = resp.json()
- dir = SeafDir(self, path, dir_id)
- dir.load_entries(dir_json)
- return dir
-
- def delete(self):
- """Remove this repo. Only the repo owner can do this"""
- self.client.delete('/api2/repos/' + self.id)
-
- def list_history(self):
- """List the history of this repo
-
- Returns a list of :class:`RepoRevision` object.
- """
- pass
-
- ## Operations only the repo owner can do:
-
- def update(self, name=None):
- """Update the name of this repo. Only the repo owner can do
- this.
- """
- pass
-
- def get_settings(self):
- """Get the settings of this repo. Returns a dict containing the following
- keys:
-
- `history_limit`: How many days of repo history to keep.
- """
- pass
-
- def restore(self, commit_id):
- pass
-
-class RepoRevision(object):
- def __init__(self, client, repo, commit_id):
- self.client = client
- self.repo = repo
- self.commit_id = commit_id
-
- def restore(self):
- """Restore the repo to this revision"""
- self.repo.revert(self.commit_id)
diff --git a/source/app/seafileapi/repos.py b/source/app/seafileapi/repos.py
deleted file mode 100644
index 70a8fa7..0000000
--- a/source/app/seafileapi/repos.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from seafileapi.repo import Repo
-from seafileapi.utils import raise_does_not_exist
-
-class Repos(object):
- def __init__(self, client):
- self.client = client
-
- def create_repo(self, name, password=None):
- data = {'name': name}
- if password:
- data['passwd'] = password
- repo_json = self.client.post('/api2/repos/', data=data).json()
- return self.get_repo(repo_json['repo_id'])
-
- @raise_does_not_exist('The requested library does not exist')
- def get_repo(self, repo_id):
- """Get the repo which has the id `repo_id`.
-
- Raises :exc:`DoesNotExist` if no such repo exists.
- """
- repo_json = self.client.get('/api2/repos/' + repo_id).json()
- return Repo.from_json(self.client, repo_json)
-
- def list_repos(self):
- repos_json = self.client.get('/api2/repos/').json()
- return [Repo.from_json(self.client, j) for j in repos_json]
diff --git a/source/app/seafileapi/utils.py b/source/app/seafileapi/utils.py
deleted file mode 100644
index 7903414..0000000
--- a/source/app/seafileapi/utils.py
+++ /dev/null
@@ -1,57 +0,0 @@
-import string
-import random
-from functools import wraps
-from urllib.parse import urlencode
-from seafileapi.exceptions import ClientHttpError, DoesNotExist
-
-def randstring(length=0):
- if length == 0:
- length = random.randint(1, 30)
- return ''.join(random.choice(string.lowercase) for i in range(length))
-
-def urljoin(base, *args):
- url = base
- if url[-1] != '/':
- url += '/'
- for arg in args:
- arg = arg.strip('/')
- url += arg + '/'
- if '?' in url:
- url = url[:-1]
- return url
-
-def raise_does_not_exist(msg):
- """Decorator to turn a function that get a http 404 response to a
- :exc:`DoesNotExist` exception."""
- def decorator(func):
- @wraps(func)
- def wrapped(*args, **kwargs):
- try:
- return func(*args, **kwargs)
- except ClientHttpError as e:
- if e.code == 404:
- raise DoesNotExist(msg)
- else:
- raise
- return wrapped
- return decorator
-
-def to_utf8(obj):
- if isinstance(obj, str):
- return obj.encode('utf-8')
- return obj
-
-def querystr(**kwargs):
- return '?' + urlencode(kwargs)
-
-def utf8lize(obj):
- if isinstance(obj, dict):
- return {k: to_utf8(v) for k, v in obj.items()}
-
- if isinstance(obj, list):
- return [to_utf8(x) for x in ob]
-
- if instance(obj, str):
- return obj.encode('utf-8')
-
- return obj
diff --git a/source/app/settings.py b/source/app/settings.py
index 72ced22..ed389e4 100644
--- a/source/app/settings.py
+++ b/source/app/settings.py
@@ -30,11 +30,6 @@ try:
except ImportError:
DEFAULT_PASSWORD = 'salgueiro3.3'
-try:
- from conf import SEAFILE_SERVER
-except ImportError:
- SEAFILE_SERVER = {}
-
try:
from conf import TITLE_APP
except ImportError:
diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js
index 956faa4..80a3b82 100644
--- a/source/static/js/controller/admin.js
+++ b/source/static/js/controller/admin.js
@@ -19,6 +19,9 @@ var msg = ''
var tb_options = null
var tb_sat = null
+var file_cer = null
+var file_key = null
+
var controllers = {
init: function(){
@@ -32,7 +35,9 @@ var controllers = {
$$('chk_escuela').attachEvent('onChange', chk_escuela_change)
$$('chk_ong').attachEvent('onChange', chk_ong_change)
$$('cmd_subir_certificado').attachEvent('onItemClick', cmd_subir_certificado_click)
- $$('up_cert').attachEvent('onUploadComplete', up_cert_upload_complete)
+ //~ $$('up_cert').attachEvent('onUploadComplete', up_cert_upload_complete)
+ //~ $$('up_cert').attachEvent('onAfterFileAdd', up_cert_after_file_add)
+ $$('up_cert').attachEvent('onBeforeFileAdd', up_cert_before_file_add)
$$('cmd_agregar_serie').attachEvent('onItemClick', cmd_agregar_serie_click)
$$('grid_folios').attachEvent('onItemClick', grid_folios_click)
$$('chk_folio_custom').attachEvent('onItemClick', chk_config_item_click)
@@ -280,7 +285,7 @@ function get_emisor(){
function get_certificado(){
var form = $$('form_cert')
- webix.ajax().get("/values/cert", {}, {
+ webix.ajax().get("/cert", {'opt': 'cert'}, {
error: function(text, data, xhr) {
msg = 'Error al consultar'
msg_error(msg)
@@ -602,105 +607,6 @@ function chk_ong_change(new_value, old_value){
}
-function cmd_subir_certificado_click(){
- var form = $$('form_upload')
-
- if (!form.validate()){
- msg = 'Valores inválidos'
- msg_error(msg)
- return
- }
-
- var values = form.getValues()
-
- if(!values.contra.trim()){
- msg = 'La contraseña no puede estar vacía'
- msg_error(msg)
- return
- }
-
- if($$('lst_cert').count() < 2){
- msg = 'Selecciona al menos dos archivos: CER y KEY del certificado.'
- msg_error(msg)
- return
- }
-
- if($$('lst_cert').count() > 2){
- msg = 'Selecciona solo dos archivos: CER y KEY del certificado.'
- msg_error(msg)
- return
- }
-
- var fo1 = $$('up_cert').files.getItem($$('up_cert').files.getFirstId())
- var fo2 = $$('up_cert').files.getItem($$('up_cert').files.getLastId())
-
- var ext = ['key', 'cer']
- if(ext.indexOf(fo1.type.toLowerCase()) == -1 || ext.indexOf(fo2.type.toLowerCase()) == -1){
- msg = 'Archivos inválidos, se requiere un archivo CER y un KEY.'
- msg_error(msg)
- return
- }
-
- if(fo1.type == fo2.type && fo1.size == fo2.size){
- msg = 'Selecciona archivos diferentes: un archivo CER y un KEY.'
- msg_error(msg)
- return
- }
-
- var serie = $$('form_cert').getValues()['cert_serie']
-
- if(serie){
- msg = 'Ya existe un certificado guardado
¿Deseas reemplazarlo?'
- webix.confirm({
- title: 'Certificado Existente',
- ok: 'Si',
- cancel: 'No',
- type: 'confirm-error',
- text: msg,
- callback:function(result){
- if(result){
- $$('up_cert').send()
- }
- }
- })
- }else{
- $$('up_cert').send()
- }
-}
-
-
-function up_cert_upload_complete(response){
- if(response.status != 'server'){
- msg = 'Ocurrio un error al subir los archivos'
- msg_error(msg)
- return
- }
-
- msg = 'Archivos subidos correctamente. Esperando validación'
- msg_ok(msg)
-
- var values = $$('form_upload').getValues()
- $$('form_upload').setValues({})
- $$('up_cert').files.data.clearAll()
-
- webix.ajax().post('/values/cert', values, {
- error:function(text, data, XmlHttpRequest){
- msg = 'Ocurrio un error, consulta a soporte técnico'
- msg_error(msg)
- },
- success:function(text, data, XmlHttpRequest){
- var values = data.json()
- if(values.ok){
- $$('form_cert').setValues(values.data)
- msg_ok(values.msg)
- }else{
- msg_error(values.msg)
- }
- }
- })
-}
-
-
function cmd_agregar_serie_click(){
var form = $$('form_folios')
var grid = $$('grid_folios')
@@ -2726,3 +2632,175 @@ function cmd_save_pac_click(){
}
})
}
+
+
+function cmd_subir_certificado_click(){
+ var form = $$('form_upload')
+
+ if (!form.validate()){
+ msg = 'Valores inválidos'
+ msg_error(msg)
+ return
+ }
+
+ var values = form.getValues()
+
+ if(!values.contra.trim()){
+ msg = 'La contraseña no puede estar vacía'
+ msg_error(msg)
+ return
+ }
+
+ //~ if($$('lst_cert').count() < 2){
+ //~ msg = 'Selecciona al menos dos archivos: CER y KEY del certificado.'
+ //~ msg_error(msg)
+ //~ return
+ //~ }
+
+ //~ if($$('lst_cert').count() > 2){
+ //~ msg = 'Selecciona solo dos archivos: CER y KEY del certificado.'
+ //~ msg_error(msg)
+ //~ return
+ //~ }
+
+ //~ var fo1 = $$('up_cert').files.getItem($$('up_cert').files.getFirstId())
+ //~ var fo2 = $$('up_cert').files.getItem($$('up_cert').files.getLastId())
+
+ //~ var ext = ['key', 'cer']
+ //~ if(ext.indexOf(fo1.type.toLowerCase()) == -1 || ext.indexOf(fo2.type.toLowerCase()) == -1){
+ //~ msg = 'Archivos inválidos, se requiere un archivo CER y un KEY.'
+ //~ msg_error(msg)
+ //~ return
+ //~ }
+
+ //~ if(fo1.type == fo2.type && fo1.size == fo2.size){
+ //~ msg = 'Selecciona archivos diferentes: un archivo CER y un KEY.'
+ //~ msg_error(msg)
+ //~ return
+ //~ }
+
+ var serie = $$('form_cert').getValues()['cert_serie']
+
+ if(serie){
+ msg = 'Ya existe un certificado guardado
¿Deseas reemplazarlo?'
+ webix.confirm({
+ title: 'Certificado Existente',
+ ok: 'Si',
+ cancel: 'No',
+ type: 'confirm-error',
+ text: msg,
+ callback:function(result){
+ if(!result){
+ return
+ }
+ }
+ })
+ }
+
+ //~ if (fo1.type.toLowerCase()=='cer'){
+ //~ values['cer'] = fo1.file
+ //~ values['key'] = fo2.file
+ //~ } else {
+ //~ values['key'] = fo1.file
+ //~ values['cer'] = fo2.file
+ //~ }
+ $$('form_upload').setValues({})
+ $$('up_cert').files.data.clearAll()
+
+ values['cer'] = file_cer
+ values['key'] = file_key
+ validate_cert(values)
+}
+
+
+function up_cert_before_file_add(file){
+ if (file.type.toLowerCase() != 'cer' && file.type.toLowerCase() != 'key'){
+ msg_error('Selecciona un archivo CER o KEY')
+ return false
+ }
+
+ var count = $$('lst_cert').count()
+ if (count > 1){
+ msg = 'Selecciona solo dos archivos: CER y KEY del certificado.'
+ msg_error(msg)
+ return false
+ }
+
+ if (count > 0){
+ var f = $$('up_cert').files.getItem($$('up_cert').files.getFirstId())
+ if (f.type.toLowerCase() == file.type.toLowerCase()){
+ msg = 'Selecciona archivos diferentes: un archivo CER y un KEY.'
+ msg_error(msg)
+ return false
+ }
+ }
+
+ var reader = new FileReader();
+ if (file.type.toLowerCase() == 'cer'){
+ reader.addEventListener('load', (event) => {
+ file_cer = event.target.result;
+ });
+ reader.readAsBinaryString(file.file);
+ } else {
+ reader.addEventListener('load', (event) => {
+ file_key = event.target.result;
+ });
+ reader.readAsBinaryString(file.file);
+ }
+}
+
+
+function validate_cert(values){
+ msg = 'Archivos recibidos correctamente. Esperando validación'
+ msg_ok(msg)
+
+ values['opt'] = 'validate'
+ webix.ajax().post('/cert', values, {
+ error:function(text, data, XmlHttpRequest){
+ msg = 'Ocurrio un error, consulta a soporte técnico'
+ msg_error(msg)
+ },
+ success:function(text, data, XmlHttpRequest){
+ var values = data.json()
+ if(values.ok){
+ $$('form_cert').setValues(values.data)
+ msg_ok(values.msg)
+ }else{
+ msg_error(values.msg)
+ }
+ }
+ })
+}
+
+
+//~ function up_cert_upload_complete(response){
+ //~ if(response.status != 'server'){
+ //~ msg = 'Ocurrio un error al subir los archivos'
+ //~ msg_error(msg)
+ //~ return
+ //~ }
+
+ //~ msg = 'Archivos subidos correctamente. Esperando validación'
+ //~ msg_ok(msg)
+
+ //~ var values = $$('form_upload').getValues()
+ //~ $$('form_upload').setValues({})
+ //~ $$('up_cert').files.data.clearAll()
+ //~ values['opt'] = 'validate'
+
+ //~ webix.ajax().post('/cert', values, {
+ //~ error:function(text, data, XmlHttpRequest){
+ //~ msg = 'Ocurrio un error, consulta a soporte técnico'
+ //~ msg_error(msg)
+ //~ },
+ //~ success:function(text, data, XmlHttpRequest){
+ //~ var values = data.json()
+ //~ if(values.ok){
+ //~ $$('form_cert').setValues(values.data)
+ //~ msg_ok(values.msg)
+ //~ }else{
+ //~ msg_error(values.msg)
+ //~ }
+ //~ }
+ //~ })
+//~ }
diff --git a/source/static/js/ui/admin.js b/source/static/js/ui/admin.js
index 2805483..0e02fbd 100644
--- a/source/static/js/ui/admin.js
+++ b/source/static/js/ui/admin.js
@@ -278,18 +278,21 @@ var col_fiel = {rows: [
]}
+ //~ {view: 'uploader', id: 'up_cert', autosend: false, link: 'lst_cert',
+ //~ value: 'Seleccionar certificado', upload: '/values/files'}, {}]},
+
var emisor_certificado = [
{cols: [col_sello, col_fiel]},
{template: 'Cargar Certificado', type: 'section'},
{view: 'form', id: 'form_upload', rows: [
{cols: [{},
{view: 'uploader', id: 'up_cert', autosend: false, link: 'lst_cert',
- value: 'Seleccionar certificado', upload: '/values/files'}, {}]},
+ value: 'Seleccionar certificado'}, {}]},
{cols: [{},
{view: 'list', id: 'lst_cert', name: 'certificado',
type: 'uploader', autoheight:true, borderless: true}, {}]},
{cols: [{},
- {view: 'text', id: 'txt_contra', name: 'contra',
+ {view: 'text', id: 'txt_contra', name: 'contra', value: '12345678a',
label: 'Contraseña KEY', labelPosition: 'top',
labelAlign: 'center', type: 'password', required: true}, {}]},
{cols: [{}, {view: 'button', id: 'cmd_subir_certificado',