Files
microservice/api/api_v2/modules/edocs/services.py

271 lines
9.8 KiB
Python

import base64
import re
import logging
import os
from typing import Any, Dict, Optional
import xml.etree.ElementTree as ET
from fastapi import HTTPException
from utils.helpers import soap_error
from .controllers import edocs_rest_controller, edocs_vu_controller
from ..common import create_service_response, create_error_response
# Logger para el módulo
logger = logging.getLogger("app.api")
# --- FUNCIONES AUXILIARES ---
def _get_file_name(**kwargs) -> str:
pedimento = kwargs.get('pedimento', {})
pedimento_app = pedimento.get('pedimento_app', 'N/A')
idEDocument = kwargs['edoc'].get('numero_edocument', 'N/A')
return f"vu_ED_{pedimento_app}_{idEDocument}.pdf"
# --- FUNCIONES DE SERVICIO ---
async def obtener_edoc(**kwargs):
credencial = kwargs.get('credencial', {})
usuario = credencial.get('user', '')
password = credencial.get('password', '')
doc = kwargs.get('edoc', {})
numero_documento = kwargs['edoc'].get('numero_edocument', '')
soap_headers = {
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': 'http://tempuri.org/IServicioEdocument/GetDocumento'
}
soap_xml = edocs_vu_controller.generate_edocument_template(username=usuario, password=password, idEDocument=numero_documento)
response = await edocs_vu_controller.make_request_async(
"Ventanilla-HA/ServicioEdocument/ServicioEdocument.svc",
data=soap_xml,
headers=soap_headers
)
# Validar respuesta del servicio SOAP
if response is None:
logger.error("No se obtuvo respuesta del servicio SOAP")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error al contactar el servicio SOAP",
errors=["No se obtuvo respuesta del servicio"],
metadata={
"edoc_number": numero_documento,
"username": usuario
}
)
)
if response.status_code != 200:
logger.error(f"Error en la solicitud SOAP: {response.status_code}")
raise HTTPException(
status_code=response.status_code,
detail=create_error_response(
message="Error en la solicitud SOAP",
errors=[f"Código de estado: {response.status_code}"],
data={"soap_response": response.text[:500]},
metadata={
"status_code": response.status_code,
"edoc_number": numero_documento
}
)
)
if soap_error(response):
logger.error("Respuesta SOAP contiene error de VUCEM")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error en la respuesta del servicio SOAP",
errors=["La respuesta contiene un error de VUCEM"],
data={"soap_response": response.text[:500]},
metadata={"edoc_number": numero_documento}
)
)
try:
edoc_base64 = extract_pdf_bytes_from_xml_content(response.text)
except ValueError as ve:
logger.error(f"Error extrayendo contenido del XML: {ve}")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error al procesar la respuesta SOAP",
errors=[str(ve)],
metadata={"edoc_number": numero_documento}
)
)
if edoc_base64 is None:
logger.error("No se pudo extraer el documento de la respuesta SOAP")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error al extraer el documento",
errors=["No se pudo encontrar el documento en la respuesta SOAP"],
metadata={"edoc_number": numero_documento}
)
)
pdf_bytes = edoc_base64['pdf_bytes']
if not pdf_bytes:
logger.error("No se pudo decodificar el documento PDF")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error al decodificar el documento",
errors=["El contenido del documento está vacío o es inválido"],
metadata={
"edoc_number": numero_documento,
"has_cadena_original": bool(edoc_base64.get('cadena_original')),
"has_sello_digital": bool(edoc_base64.get('sello_digital'))
}
)
)
# Validar formato PDF
if not pdf_bytes.startswith(b'%PDF'):
logger.error("El contenido decodificado no es un PDF válido")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="El documento recibido no es un PDF válido",
errors=["El contenido no tiene el formato PDF esperado"],
metadata={
"edoc_number": numero_documento,
"content_start": str(pdf_bytes[:20])
}
)
)
pedimento = kwargs.get('pedimento', {})
numero_documento = kwargs['edoc'].get('numero_edocument', '')
_file_name = _get_file_name(**kwargs)
organizacion = pedimento.get("organizacion", None)
pedimento_id = pedimento.get("id", None)
# No guardaremos el archivo localmente por seguridad
logger.debug(f"Procesando documento {numero_documento} para pedimento {pedimento_id}")
rest_response = await edocs_rest_controller.post_document(
binary_content=pdf_bytes,
organizacion=organizacion,
pedimento=pedimento_id,
file_name=_file_name,
document_type=5
)
if rest_response is None:
logger.error("Error al enviar el documento a la API interna")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error al guardar el documento en el sistema",
errors=["No se pudo enviar el documento a la API interna"],
metadata={
"file_name": _file_name,
"edoc_number": numero_documento
}
)
)
if rest_response.get("id") is None:
logger.error("Respuesta de API interna sin ID válido")
raise HTTPException(
status_code=500,
detail=create_error_response(
message="Error al procesar la respuesta del sistema",
errors=["La respuesta de la API no contiene un ID válido"],
data={"api_response": rest_response}
)
)
logger.info("Documento enviado, actualizando status de Edoc")
try:
edoc_status_response = await change_edocument_status(
edoc=doc,
status=True,
pedimento=pedimento
)
except Exception as e:
logger.warning(f"Error al actualizar estado del documento: {e}")
# No fallamos aquí porque el documento ya se guardó exitosamente
logger.info(f"E-document {numero_documento} procesado exitosamente")
return create_service_response(
message=f"E-document {numero_documento} procesado exitosamente",
data={
"document_response": rest_response,
"file_name": _file_name,
"numero_documento": numero_documento,
"edoc_update_response": edoc_status_response if edoc_status_response else None
},
metadata={
"document_type": 5,
"pedimento_app": pedimento.get('pedimento_app'),
"organizacion": organizacion,
"content_type": "application/pdf",
"has_cadena_original": bool(edoc_base64.get('cadena_original')),
"has_sello_digital": bool(edoc_base64.get('sello_digital'))
}
)
async def change_edocument_status(edoc: dict, status: bool, pedimento: dict):
data = {
"id": edoc.get("id"),
"acuse_descargado": status,
"numero_edocument": edoc.get("numero_edocument"),
"pedimento": pedimento.get("id"),
"organizacion": pedimento.get("organizacion"),
}
response = await edocs_rest_controller.put_edocument(edocument_id=edoc.get("id"), data=data)
return response
def extract_pdf_bytes_from_xml_content(xml_content: str):
"""
Extrae el PDF y metadatos desde un string XML.
"""
root = ET.fromstring(xml_content)
file_elem = root.find('.//File')
if file_elem is None:
for elem in root.iter():
if elem.tag.endswith('File') and elem.text:
file_elem = elem
break
if file_elem is not None and file_elem.text and file_elem.text.strip():
base64_data = file_elem.text.strip().replace('\n', '').replace('\r', '')
pdf_bytes = base64.b64decode(base64_data)
cadena_original = None
sello_digital = None
cadena_elem = root.find('.//CadenaOriginal')
if cadena_elem is None:
for elem in root.iter():
if elem.tag.endswith('CadenaOriginal') and elem.text:
cadena_elem = elem
break
if cadena_elem is not None and cadena_elem.text:
cadena_original = cadena_elem.text.strip()
sello_elem = root.find('.//SelloDigital')
if sello_elem is None:
for elem in root.iter():
if elem.tag.endswith('SelloDigital') and elem.text:
sello_elem = elem
break
if sello_elem is not None and sello_elem.text:
sello_digital = sello_elem.text.strip()
return {
"pdf_bytes": pdf_bytes,
"cadena_original": cadena_original,
"sello_digital": sello_digital
}
else:
raise ValueError("No se encontró el tag <File> con contenido válido. Verifique que el XML contiene el tag <File> con datos base64.")