Add migrate certificates

This commit is contained in:
Mauricio Baeza 2021-01-06 22:15:30 -06:00
commit 17fa99e45b
6 changed files with 76 additions and 431 deletions

View File

@ -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.

View File

@ -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 = ''

View File

@ -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: ')

View File

@ -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)

View File

@ -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):

View File

@ -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