Add migrate certificates
This commit is contained in:
commit
17fa99e45b
|
@ -17,12 +17,14 @@ con su respectiva contraseña, te quedarás sin poder timbrar.
|
|||
menú "Emisor" ficha "Otros Datos", usuario y token de timbrado.
|
||||
1. Agregar nuevo requerimiento `pip install xmlsec`
|
||||
1. Actualizar `git pull origin master`
|
||||
1. Entrar a `source/app/controllers/pacs` y copiar `conf.py.example` a `conf.py`
|
||||
1. Entrar a `source/app/controllers/pacs/comerciodigital` y copiar `conf.py.example` a `conf.py`
|
||||
1. Entrar a `source/app/controllers/pacs/finkok` y copiar `conf.py.example` a `conf.py`
|
||||
1. Agregar la variable `TOKEN = ''` al archivo `source/app/conf.py` mira el archivo de ejemplo en el mismo directorio.
|
||||
1. Reiniciar el servicio: `sudo systemctl restart empresalibre`
|
||||
1. Sube de nuevo tus certificados en el menú "Emisor" ficha "Certificado".
|
||||
1. Ve al menú "Opciones", ficha "Otros".
|
||||
1. Selecciona tu PAC, si tu usuario es un correo electrónico, invariablemente
|
||||
debes seleccionar Finkok.
|
||||
debes seleccionar Finkok. Si tienes duda ponte en contacto con nosotros.
|
||||
1. Establece las credenciales del punto 1.
|
||||
1. Guarda los datos.
|
||||
|
||||
|
|
|
@ -13,13 +13,9 @@ TITLE_APP = 'Empresa Libre'
|
|||
#~ Establece una ruta accesible para el servidor web
|
||||
LOG_PATH = '/var/log/empresalibre/empresa-libre.log'
|
||||
|
||||
# ~ Establece los valores para sincronizar los backups de la base de datos
|
||||
# ~ por ejemplo
|
||||
# ~ SEAFILE_SERVER = {
|
||||
# ~ 'URL': 'https://tu_servidor_seafile',
|
||||
# ~ 'USER': 'tu_usuario',
|
||||
# ~ 'PASS': 'tu_contraseña',
|
||||
# ~ 'REPO': 'id_repo',
|
||||
# ~ }
|
||||
|
||||
SEAFILE_SERVER = {}
|
||||
# ~ Establece un token personalizado para encriptar las claves
|
||||
# ~ from secrets import token_hex
|
||||
# ~ token_hex(32)
|
||||
# ~ IMPORTANTE: Cada vez que cambies este valor, debes de subir de nuevo
|
||||
# ~ tus certificados
|
||||
TOKEN = ''
|
||||
|
|
|
@ -4,6 +4,7 @@ import argparse
|
|||
import base64
|
||||
import datetime
|
||||
import getpass
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import xmlsec
|
||||
|
@ -211,6 +212,40 @@ class SATCertificate(object):
|
|||
def error(self):
|
||||
return self._error
|
||||
|
||||
@classmethod
|
||||
def save_cert(cls, obj):
|
||||
import hashlib
|
||||
|
||||
cer = x509.load_pem_x509_certificate(obj.cer_pem.encode(), default_backend())
|
||||
cer_der = cer.public_bytes(serialization.Encoding.DER)
|
||||
token = hashlib.md5(obj.rfc.encode()).hexdigest()
|
||||
path = Path(f'/tmp/{obj.rfc}.key')
|
||||
path.write_text(obj.key_enc)
|
||||
args = f'openssl rsa -inform PEM -outform PEM -in "{str(path)}" -passin pass:{token}'
|
||||
pem = subprocess.check_output(args, shell=True).decode()
|
||||
key = serialization.load_pem_private_key(pem.encode(), password=None)
|
||||
|
||||
digest = hashes.Hash(hashes.SHA512(), default_backend())
|
||||
digest.update(obj.rfc.encode())
|
||||
digest.update(obj.serie.encode())
|
||||
digest.update(TOKEN.encode())
|
||||
p = digest.finalize()
|
||||
|
||||
key_enc = key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.BestAvailableEncryption(p)
|
||||
)
|
||||
|
||||
cert = SATCertificate(cer_der, key_enc)
|
||||
obj.key_enc = cert.key_enc
|
||||
obj.cer = cert.cer
|
||||
obj.serie = cert.serial_number
|
||||
obj.desde = cert.not_before
|
||||
obj.hasta = cert.not_after
|
||||
obj.save()
|
||||
return
|
||||
|
||||
|
||||
def main(args):
|
||||
# ~ contra = getpass.getpass('Introduce la contraseña del archivo KEY: ')
|
||||
|
|
|
@ -78,8 +78,6 @@ class PACComercioDigital(object):
|
|||
|
||||
def __init__(self):
|
||||
self.error = ''
|
||||
# ~ self.cfdi_uuid = ''
|
||||
# ~ self.date_stamped = ''
|
||||
|
||||
def _error(self, msg):
|
||||
self.error = str(msg)
|
||||
|
|
|
@ -397,36 +397,6 @@ def to_slug(string):
|
|||
return value.replace(' ', '_')
|
||||
|
||||
|
||||
# ~ def make_xml(data, certificado):
|
||||
# ~ from .cfdi_xml import CFDI
|
||||
|
||||
# ~ cert = SATCertificate(certificado.cer, certificado.key_enc.encode())
|
||||
# ~ if DEBUG:
|
||||
# ~ data['emisor']['Rfc'] = certificado.rfc
|
||||
# ~ data['emisor']['RegimenFiscal'] = '603'
|
||||
|
||||
# ~ cfdi = CFDI()
|
||||
# ~ xml = cfdi.get_xml(data)
|
||||
|
||||
# ~ data = {
|
||||
# ~ 'xsltproc': PATH_XSLTPROC,
|
||||
# ~ 'xslt': _join(PATH_XSLT, 'cadena.xslt'),
|
||||
# ~ 'xml': save_temp(xml, 'w'),
|
||||
# ~ 'openssl': PATH_OPENSSL,
|
||||
# ~ 'key': save_temp(certificado.key_enc, 'w'),
|
||||
# ~ 'pass': token,
|
||||
# ~ }
|
||||
# ~ args = '"{xsltproc}" "{xslt}" "{xml}" | ' \
|
||||
# ~ '"{openssl}" dgst -sha256 -sign "{key}" -passin pass:"{pass}" | ' \
|
||||
# ~ '"{openssl}" enc -base64 -A'.format(**data)
|
||||
# ~ sello = _call(args)
|
||||
|
||||
# ~ _kill(data['xml'])
|
||||
# ~ _kill(data['key'])
|
||||
|
||||
# ~ return cfdi.add_sello(sello)
|
||||
|
||||
|
||||
def timbra_xml(xml, auth):
|
||||
from .pac import Finkok as PAC
|
||||
|
||||
|
@ -460,26 +430,6 @@ def _get_uuid_fecha(xml):
|
|||
return node.attrib['UUID'], node.attrib['FechaTimbrado']
|
||||
|
||||
|
||||
def _ecodex_timbra_xml(xml):
|
||||
from .pac import Ecodex as PAC
|
||||
from .configpac import ecodex
|
||||
|
||||
auth, url = ecodex(DEBUG)
|
||||
|
||||
pac = PAC(auth, url)
|
||||
xml = pac.timbra_xml(xml)
|
||||
if xml:
|
||||
data = {'ok': True, 'error': ''}
|
||||
data['xml'] = xml
|
||||
uuid, fecha = _get_uuid_fecha(xml)
|
||||
data['uuid'] = uuid
|
||||
data['fecha'] = fecha
|
||||
return data
|
||||
|
||||
msg = pac.error
|
||||
return {'ok': False, 'error': msg}
|
||||
|
||||
|
||||
def get_sat(xml):
|
||||
from .pac import get_status_sat
|
||||
return get_status_sat(xml)
|
||||
|
@ -2616,373 +2566,6 @@ def sync_cfdi(files):
|
|||
return
|
||||
|
||||
|
||||
class ImportFacturaLibre(object):
|
||||
|
||||
def __init__(self, path, rfc):
|
||||
self._rfc = rfc
|
||||
self._con = None
|
||||
self._cursor = None
|
||||
self._error = ''
|
||||
self._is_connect = self._connect(path)
|
||||
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, path):
|
||||
try:
|
||||
self._con = sqlite3.connect(path)
|
||||
self._con.row_factory = sqlite3.Row
|
||||
self._cursor = self._con.cursor()
|
||||
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'),
|
||||
('cfdfacturas', 'Facturas'),
|
||||
('categorias', 'Categorias'),
|
||||
)
|
||||
for source, target in tables:
|
||||
data[target] = self._get_table(source)
|
||||
|
||||
data['Socios'] += self._clientes
|
||||
|
||||
return data
|
||||
|
||||
def _get_table(self, table):
|
||||
return getattr(self, '_{}'.format(table))()
|
||||
|
||||
def import_productos(self):
|
||||
sql = "SELECT * FROM productos"
|
||||
self._cursor.execute(sql)
|
||||
rows = self._cursor.fetchall()
|
||||
|
||||
fields = (
|
||||
('id_categoria', 'categoria'),
|
||||
('noIdentificacion', 'clave'),
|
||||
('descripcion', 'descripcion'),
|
||||
('unidad', 'unidad'),
|
||||
('valorUnitario', 'valor_unitario'),
|
||||
('existencia', 'existencia'),
|
||||
('inventario', 'inventario'),
|
||||
('codigobarras', 'codigo_barras'),
|
||||
('CuentaPredial', 'cuenta_predial'),
|
||||
('precio_compra', 'ultimo_precio'),
|
||||
('minimo', 'minimo'),
|
||||
)
|
||||
data = []
|
||||
|
||||
sql = """
|
||||
SELECT nombre, tasa, tipo
|
||||
FROM impuestos, productos, productosimpuestos
|
||||
WHERE productos.id=productosimpuestos.id_producto
|
||||
AND productosimpuestos.id_impuesto=impuestos.id
|
||||
AND productos.id = ?
|
||||
"""
|
||||
for row in rows:
|
||||
new = {t: row[s] for s, t in fields}
|
||||
if new['categoria'] == 0:
|
||||
new['categoria'] = None
|
||||
new['descripcion'] = ' '.join(new['descripcion'].split())
|
||||
new['clave_sat'] = DEFAULT_SAT_PRODUCTO
|
||||
self._cursor.execute(sql, (row['id'],))
|
||||
impuestos = self._cursor.fetchall()
|
||||
new['impuestos'] = tuple(impuestos)
|
||||
data.append(new)
|
||||
|
||||
return data
|
||||
|
||||
def _categorias(self):
|
||||
sql = "SELECT * FROM categorias ORDER BY id_padre"
|
||||
self._cursor.execute(sql)
|
||||
rows = self._cursor.fetchall()
|
||||
|
||||
fields = (
|
||||
('id', 'id'),
|
||||
('categoria', 'categoria'),
|
||||
('id_padre', 'padre'),
|
||||
)
|
||||
data = []
|
||||
|
||||
for row in rows:
|
||||
new = {t: row[s] for s, t in fields}
|
||||
if new['padre'] == 0:
|
||||
new['padre'] = None
|
||||
data.append(new)
|
||||
|
||||
return data
|
||||
|
||||
def _get_cliente(self, invoice):
|
||||
sql = "SELECT rfc, nombre FROM receptores WHERE id=?"
|
||||
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']
|
||||
|
||||
# ~ Validaciones especiales
|
||||
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 cfddetalle WHERE id_cfd=?"
|
||||
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'),
|
||||
('alumno', 'alumno'),
|
||||
('curp', 'curp'),
|
||||
('nivel', 'nivel'),
|
||||
('autorizacion', 'autorizacion'),
|
||||
)
|
||||
|
||||
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 cfdimpuestos WHERE id_cfd=?"
|
||||
self._cursor.execute(sql, [id])
|
||||
rows = self._cursor.fetchall()
|
||||
|
||||
tasas = {
|
||||
'0': 0.0,
|
||||
'16': 0.16,
|
||||
'16.00': 0.16,
|
||||
'-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,
|
||||
'-10.67': 0.106667,
|
||||
'-10.66666666666667': 0.106667,
|
||||
'-4': 0.04,
|
||||
'1': 0.01,
|
||||
'25': 0.25,
|
||||
'26.5': 0.265,
|
||||
'30': 0.30,
|
||||
'8': 0.08,
|
||||
}
|
||||
|
||||
data = []
|
||||
for row in rows:
|
||||
# ~ print (id, dict(row))
|
||||
filtro = {
|
||||
'name': row['nombre'],
|
||||
'tasa': tasas[row['tasa']],
|
||||
'tipo': row['tipo'][0],
|
||||
}
|
||||
new = {
|
||||
'importe': row['importe'],
|
||||
'filtro': filtro
|
||||
}
|
||||
data.append(new)
|
||||
|
||||
return data
|
||||
|
||||
def _cfdfacturas(self):
|
||||
sql = "SELECT * FROM cfdfacturas"
|
||||
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'),
|
||||
('donativo', 'donativo'),
|
||||
('estatus', 'estatus'),
|
||||
('regimen', 'regimen_fiscal'),
|
||||
('xml_acuse', 'acuse'),
|
||||
)
|
||||
data = []
|
||||
for row in rows:
|
||||
row = dict(row)
|
||||
if not 'xml_acuse'in row:
|
||||
row['xml_acuse'] = ''
|
||||
|
||||
new = {t: row[s] for s, t in fields}
|
||||
if not 'uuid' in new or not new['uuid']:
|
||||
new['uuid'] = None
|
||||
if not 'xml' in new or new['xml'] is None:
|
||||
new['xml'] = ''
|
||||
if row['estatus'] == 'Pagada':
|
||||
new['pagada'] = True
|
||||
elif row['estatus'] in ('Cancelada', 'Validada'):
|
||||
new['cancelada'] = True
|
||||
|
||||
if new['fecha'] is None:
|
||||
new['fecha'] = str(now())
|
||||
|
||||
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'),
|
||||
('cuentaCliente', 'cuenta_cliente'),
|
||||
('cuentaProveedor', 'cuenta_proveedor'),
|
||||
('saldoCliente', 'saldo_cliente'),
|
||||
('saldoProveedor', 'saldo_proveedor'),
|
||||
('esCliente', 'es_cliente'),
|
||||
('esProveedor', 'es_proveedor'),
|
||||
)
|
||||
data = []
|
||||
|
||||
sql1 = "SELECT correo FROM correos WHERE id_cliente=?"
|
||||
sql2 = "SELECT telefono FROM telefonos WHERE id_cliente=?"
|
||||
for row in rows:
|
||||
new = {t: row[s] for s, t in fields}
|
||||
new['slug'] = to_slug(new['nombre'])
|
||||
new['fecha_alta'] = str(parser.parse(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 ImportCFDI(object):
|
||||
|
||||
def __init__(self, xml):
|
||||
|
|
|
@ -10492,6 +10492,29 @@ def _change_pass(rfc):
|
|||
return
|
||||
|
||||
|
||||
def _migrate_cert():
|
||||
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('Exportando certificados...')
|
||||
|
||||
obj = Certificado.get(Certificado.es_fiel==False)
|
||||
cert = utils.SATCertificate
|
||||
cert.save_cert(obj)
|
||||
|
||||
desconectar()
|
||||
log.info('Proceso terminado correctamente...')
|
||||
return
|
||||
|
||||
|
||||
def _process_command_line_arguments():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Empresa Libre')
|
||||
|
@ -10516,6 +10539,9 @@ def _process_command_line_arguments():
|
|||
parser.add_argument('-ed', '--export-documents', dest='export_documents',
|
||||
action='store_true', default=False, required=False)
|
||||
|
||||
parser.add_argument('-mc' , '--migrate-cert', dest='migrate_cert',
|
||||
action='store_true', default=False, required=False)
|
||||
|
||||
parser.add_argument('-r', '--rfc', dest='rfc', default='')
|
||||
return parser.parse_args()
|
||||
|
||||
|
@ -10555,6 +10581,11 @@ def main(args):
|
|||
|
||||
if args.export_documents:
|
||||
_export_documents()
|
||||
return
|
||||
|
||||
if args.export_cert:
|
||||
_export_cert()
|
||||
return
|
||||
|
||||
return
|
||||
|
||||
|
|
Loading…
Reference in New Issue