fix(): Otimização do envio de multiplos registros
This commit is contained in:
parent
cb2fed6202
commit
ba2851d6e4
3 changed files with 1795 additions and 146 deletions
1410
Orius Mirror.postman_collection.json
Normal file
1410
Orius Mirror.postman_collection.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,8 +1,11 @@
|
||||||
from typing import List, Optional
|
import hashlib
|
||||||
|
import logging
|
||||||
|
from typing import List, Optional, Dict, Any
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
import traceback
|
from sqlalchemy import text
|
||||||
from sqlalchemy import func
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy.orm import Session # Importação para tipagem da session
|
from sqlalchemy.exc import IntegrityError, OperationalError, SQLAlchemyError
|
||||||
|
|
||||||
from database.mysql import SessionLocal, get_database_settings
|
from database.mysql import SessionLocal, get_database_settings
|
||||||
from packages.v1.administrativo.models.ato_principal_model import AtoPrincipal
|
from packages.v1.administrativo.models.ato_principal_model import AtoPrincipal
|
||||||
from packages.v1.administrativo.models.ato_parte_model import AtoParte
|
from packages.v1.administrativo.models.ato_parte_model import AtoParte
|
||||||
|
|
@ -11,6 +14,12 @@ from packages.v1.administrativo.schemas.ato_principal_schema import (
|
||||||
AtoPrincipalSaveSchema,
|
AtoPrincipalSaveSchema,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Configuração de logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Configuração da Chave AES
|
# Configuração da Chave AES
|
||||||
DB_SETTINGS = get_database_settings()
|
DB_SETTINGS = get_database_settings()
|
||||||
AES_KEY = getattr(DB_SETTINGS, "aeskey", None)
|
AES_KEY = getattr(DB_SETTINGS, "aeskey", None)
|
||||||
|
|
@ -18,11 +27,168 @@ AES_KEY = getattr(DB_SETTINGS, "aeskey", None)
|
||||||
|
|
||||||
class SaveMultipleRepository:
|
class SaveMultipleRepository:
|
||||||
"""
|
"""
|
||||||
Repositório para salvar múltiplos atos principais com suas partes e documentos,
|
Repositório otimizado para salvar múltiplos atos principais com suas partes
|
||||||
usando criptografia nativa do MySQL via AES_ENCRYPT.
|
e documentos, usando criptografia nativa do MySQL via AES_ENCRYPT.
|
||||||
Implementa lógica recursiva para atos vinculados.
|
|
||||||
|
Melhorias implementadas:
|
||||||
|
- Criptografia correta via execução SQL
|
||||||
|
- Hash SHA256 para busca rápida de duplicidades
|
||||||
|
- Gerenciamento adequado de sessões (uma por transação)
|
||||||
|
- Logging profissional estruturado
|
||||||
|
- Tratamento de exceções específicas
|
||||||
|
- Validação robusta da chave AES
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Inicializa o repositório e valida a configuração da chave AES"""
|
||||||
|
if not AES_KEY:
|
||||||
|
raise ValueError(
|
||||||
|
"A chave AES (aeskey) não está configurada. "
|
||||||
|
"Verifique as configurações do banco de dados."
|
||||||
|
)
|
||||||
|
logger.info("SaveMultipleRepository inicializado com sucesso")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate_hash(value: str) -> str:
|
||||||
|
"""
|
||||||
|
Gera hash SHA256 para busca rápida e verificação de duplicidade.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: Valor a ser hasheado
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Hash SHA256 em formato hexadecimal
|
||||||
|
"""
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
def _encrypt_field(self, db: Session, value: str) -> Optional[bytes]:
|
||||||
|
"""
|
||||||
|
Criptografa um campo usando AES_ENCRYPT do MySQL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: Sessão do banco de dados
|
||||||
|
value: Valor a ser criptografado
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Valor criptografado em bytes ou None se valor vazio
|
||||||
|
"""
|
||||||
|
if not value or (isinstance(value, str) and not value.strip()):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = db.execute(
|
||||||
|
text("SELECT AES_ENCRYPT(:val, :key) as encrypted"),
|
||||||
|
{"val": value.strip(), "key": AES_KEY},
|
||||||
|
).scalar()
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Erro ao criptografar campo: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _check_duplicate_codigo_selo(self, db: Session, codigo_selo: str) -> bool:
|
||||||
|
"""
|
||||||
|
Verifica se o código do selo já existe usando hash para busca rápida.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: Sessão do banco de dados
|
||||||
|
codigo_selo: Código do selo a verificar
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True se já existe, False caso contrário
|
||||||
|
"""
|
||||||
|
codigo_hash = self._generate_hash(codigo_selo)
|
||||||
|
|
||||||
|
existing = (
|
||||||
|
db.query(AtoPrincipal)
|
||||||
|
.filter(AtoPrincipal.codigo_selo_hash == codigo_hash)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
return existing is not None
|
||||||
|
|
||||||
|
def _prepare_encrypted_data(
|
||||||
|
self, db: Session, data: Dict[str, Any], fields_to_encrypt: List[str]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Prepara os dados criptografando os campos especificados.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: Sessão do banco de dados
|
||||||
|
data: Dicionário com os dados
|
||||||
|
fields_to_encrypt: Lista de campos a criptografar
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dicionário com campos criptografados
|
||||||
|
"""
|
||||||
|
processed_data = data.copy()
|
||||||
|
|
||||||
|
for field in fields_to_encrypt:
|
||||||
|
value = data.get(field)
|
||||||
|
if value is not None and str(value).strip():
|
||||||
|
processed_data[field] = self._encrypt_field(db, str(value))
|
||||||
|
else:
|
||||||
|
processed_data[field] = None
|
||||||
|
|
||||||
|
return processed_data
|
||||||
|
|
||||||
|
def _save_ato_partes(
|
||||||
|
self, db: Session, partes: List, ato_principal_id: int
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Salva as partes de um ato principal.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: Sessão do banco de dados
|
||||||
|
partes: Lista de schemas de partes
|
||||||
|
ato_principal_id: ID do ato principal
|
||||||
|
"""
|
||||||
|
campos_criptografar = ["nome", "telefone", "cpf_cnpj"]
|
||||||
|
|
||||||
|
for parte in partes:
|
||||||
|
parte_data = parte.model_dump(exclude_unset=True)
|
||||||
|
|
||||||
|
# Criptografa campos sensíveis
|
||||||
|
parte_data = self._prepare_encrypted_data(
|
||||||
|
db, parte_data, campos_criptografar
|
||||||
|
)
|
||||||
|
|
||||||
|
# Adiciona relacionamento
|
||||||
|
parte_data["ato_principal_id"] = ato_principal_id
|
||||||
|
|
||||||
|
new_parte = AtoParte(**parte_data)
|
||||||
|
db.add(new_parte)
|
||||||
|
|
||||||
|
logger.debug(f"Salvas {len(partes)} partes para ato {ato_principal_id}")
|
||||||
|
|
||||||
|
def _save_ato_documentos(
|
||||||
|
self, db: Session, documentos: List, ato_principal_id: int
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Salva os documentos de um ato principal.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db: Sessão do banco de dados
|
||||||
|
documentos: Lista de schemas de documentos
|
||||||
|
ato_principal_id: ID do ato principal
|
||||||
|
"""
|
||||||
|
campos_criptografar = ["url", "nome_documento", "tipo_documento"]
|
||||||
|
|
||||||
|
for doc in documentos:
|
||||||
|
doc_data = doc.model_dump(exclude_unset=True)
|
||||||
|
|
||||||
|
# Criptografa campos sensíveis
|
||||||
|
doc_data = self._prepare_encrypted_data(db, doc_data, campos_criptografar)
|
||||||
|
|
||||||
|
# Adiciona relacionamento
|
||||||
|
doc_data["ato_principal_id"] = ato_principal_id
|
||||||
|
|
||||||
|
new_documento = AtoDocumento(**doc_data)
|
||||||
|
db.add(new_documento)
|
||||||
|
|
||||||
|
logger.debug(f"Salvos {len(documentos)} documentos para ato {ato_principal_id}")
|
||||||
|
|
||||||
def _save_single_recursive_transaction(
|
def _save_single_recursive_transaction(
|
||||||
self,
|
self,
|
||||||
db: Session,
|
db: Session,
|
||||||
|
|
@ -30,95 +196,76 @@ class SaveMultipleRepository:
|
||||||
parent_ato_principal_id: Optional[int] = None,
|
parent_ato_principal_id: Optional[int] = None,
|
||||||
) -> AtoPrincipal:
|
) -> AtoPrincipal:
|
||||||
"""
|
"""
|
||||||
Salva um único Ato Principal, seus filhos (partes/documentos) e
|
Salva recursivamente um ato principal com todas suas dependências.
|
||||||
chama recursivamente o salvamento dos atos vinculados.
|
|
||||||
|
Args:
|
||||||
|
db: Sessão do banco de dados
|
||||||
|
ato_schema: Schema do ato a salvar
|
||||||
|
parent_ato_principal_id: ID do ato pai (para atos vinculados)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Instância do AtoPrincipal salvo
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: Se código do selo já existe
|
||||||
"""
|
"""
|
||||||
codigo_selo = ato_schema.codigo_selo
|
codigo_selo = ato_schema.codigo_selo
|
||||||
|
|
||||||
# 1. Pré-processamento e Criptografia
|
# 1. Verificação de duplicidade usando hash
|
||||||
ato_data = ato_schema.model_dump(
|
if self._check_duplicate_codigo_selo(db, codigo_selo):
|
||||||
exclude_unset=True,
|
logger.warning(f"Tentativa de cadastro duplicado: {codigo_selo}")
|
||||||
exclude={"ato_partes", "ato_documentos", "atos_vinculados"},
|
|
||||||
)
|
|
||||||
|
|
||||||
# Define o ID de origem se for um ato vinculado
|
|
||||||
if parent_ato_principal_id is not None:
|
|
||||||
ato_data["origem_ato_principal_id"] = parent_ato_principal_id
|
|
||||||
elif ato_data.get("origem_ato_principal_id") is None:
|
|
||||||
ato_data["origem_ato_principal_id"] = None
|
|
||||||
|
|
||||||
# Verifica duplicidade usando descriptografia
|
|
||||||
existing_ato = (
|
|
||||||
db.query(AtoPrincipal)
|
|
||||||
.filter(func.aes_decrypt(AtoPrincipal.codigo_selo, AES_KEY) == codigo_selo)
|
|
||||||
.first()
|
|
||||||
)
|
|
||||||
|
|
||||||
if existing_ato:
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT,
|
status_code=status.HTTP_409_CONFLICT,
|
||||||
detail=f"O Código do Selo '{codigo_selo}' já está cadastrado.",
|
detail=f"O Código do Selo '{codigo_selo}' já está cadastrado.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 2. Preparação dos dados do ato principal
|
||||||
|
ato_data = ato_schema.model_dump(
|
||||||
|
exclude_unset=True,
|
||||||
|
exclude={"ato_partes", "ato_documentos", "atos_vinculados"},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define relacionamento com ato pai
|
||||||
|
if parent_ato_principal_id is not None:
|
||||||
|
ato_data["origem_ato_principal_id"] = parent_ato_principal_id
|
||||||
|
elif "origem_ato_principal_id" not in ato_data:
|
||||||
|
ato_data["origem_ato_principal_id"] = None
|
||||||
|
|
||||||
|
# 3. Criptografia dos campos sensíveis
|
||||||
campos_criptografar = [
|
campos_criptografar = [
|
||||||
"nome_civil_ato",
|
"nome_civil_ato",
|
||||||
"nome_serventuario_praticou_ato",
|
"nome_serventuario_praticou_ato",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Criptografa os campos de texto necessários
|
ato_data = self._prepare_encrypted_data(db, ato_data, campos_criptografar)
|
||||||
for campo in campos_criptografar:
|
|
||||||
valor = ato_data.get(campo)
|
|
||||||
if isinstance(valor, str) and valor.strip():
|
|
||||||
ato_data[campo] = func.aes_encrypt(valor, AES_KEY)
|
|
||||||
elif campo not in ato_data or valor is None:
|
|
||||||
ato_data[campo] = None
|
|
||||||
|
|
||||||
# 2. Criação e Persistência do Ato Principal
|
# Adiciona hash do código do selo para busca rápida
|
||||||
|
ato_data["codigo_selo_hash"] = self._generate_hash(codigo_selo)
|
||||||
|
|
||||||
|
# 4. Criação e persistência do ato principal
|
||||||
new_ato = AtoPrincipal(**ato_data)
|
new_ato = AtoPrincipal(**ato_data)
|
||||||
db.add(new_ato)
|
db.add(new_ato)
|
||||||
db.flush()
|
db.flush() # Obtém o ID sem fazer commit
|
||||||
|
|
||||||
new_ato_id = new_ato.ato_principal_id
|
new_ato_id = new_ato.ato_principal_id
|
||||||
|
logger.info(f"Ato principal criado: ID={new_ato_id}, Selo={codigo_selo}")
|
||||||
|
|
||||||
# 3. Salva os Filhos Diretos: Ato Partes
|
# 5. Salva partes do ato
|
||||||
for parte in ato_schema.ato_partes:
|
if ato_schema.ato_partes:
|
||||||
parte_data = parte.model_dump(exclude_unset=True)
|
self._save_ato_partes(db, ato_schema.ato_partes, new_ato_id)
|
||||||
|
|
||||||
parte_campos_criptografar = ["nome", "telefone", "cpf_cnpj"]
|
# 6. Salva documentos do ato
|
||||||
|
if ato_schema.ato_documentos:
|
||||||
|
self._save_ato_documentos(db, ato_schema.ato_documentos, new_ato_id)
|
||||||
|
|
||||||
for campo in parte_campos_criptografar:
|
# 7. Salvamento recursivo de atos vinculados
|
||||||
valor = parte_data.get(campo)
|
|
||||||
# A validação/conversão para string já foi feita pelo Pydantic,
|
|
||||||
# mas mantemos a checagem de tipo e strip para segurança antes da criptografia
|
|
||||||
if isinstance(valor, str) and valor.strip():
|
|
||||||
parte_data[campo] = func.aes_encrypt(valor, AES_KEY)
|
|
||||||
else:
|
|
||||||
parte_data[campo] = None
|
|
||||||
|
|
||||||
new_parte = AtoParte(**parte_data, ato_principal_id=new_ato_id)
|
|
||||||
db.add(new_parte)
|
|
||||||
|
|
||||||
# 4. Salva os Filhos Diretos: Ato Documentos
|
|
||||||
for doc in ato_schema.ato_documentos:
|
|
||||||
doc_data = doc.model_dump(exclude_unset=True)
|
|
||||||
|
|
||||||
doc_campos_criptografar = ["url", "nome_documento", "tipo_documento"]
|
|
||||||
|
|
||||||
for campo in doc_campos_criptografar:
|
|
||||||
valor = doc_data.get(campo)
|
|
||||||
if isinstance(valor, str) and valor.strip():
|
|
||||||
doc_data[campo] = func.aes_encrypt(valor, AES_KEY)
|
|
||||||
else:
|
|
||||||
doc_data[campo] = None
|
|
||||||
|
|
||||||
new_documento = AtoDocumento(**doc_data, ato_principal_id=new_ato_id)
|
|
||||||
db.add(new_documento)
|
|
||||||
|
|
||||||
# 5. Lógica Recursiva para Atos Vinculados (Estrutura de loop idêntica)
|
|
||||||
if ato_schema.atos_vinculados:
|
if ato_schema.atos_vinculados:
|
||||||
|
logger.info(
|
||||||
|
f"Processando {len(ato_schema.atos_vinculados)} atos vinculados "
|
||||||
|
f"para selo {codigo_selo}"
|
||||||
|
)
|
||||||
|
|
||||||
# A iteração é semelhante, mas o que é executado é a chamada recursiva
|
|
||||||
for linked_ato_schema in ato_schema.atos_vinculados:
|
for linked_ato_schema in ato_schema.atos_vinculados:
|
||||||
# O ato vinculado é persistido chamando a rotina de salvamento completa
|
|
||||||
self._save_single_recursive_transaction(
|
self._save_single_recursive_transaction(
|
||||||
db,
|
db,
|
||||||
linked_ato_schema,
|
linked_ato_schema,
|
||||||
|
|
@ -127,70 +274,138 @@ class SaveMultipleRepository:
|
||||||
|
|
||||||
return new_ato
|
return new_ato
|
||||||
|
|
||||||
# Método principal (chamado pela Action)
|
def _process_single_ato(self, ato_schema: AtoPrincipalSaveSchema) -> Dict[str, Any]:
|
||||||
def execute(self, atos_principais: List[AtoPrincipalSaveSchema]):
|
"""
|
||||||
|
Processa um único ato em uma transação isolada.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ato_schema: Schema do ato a processar
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dicionário com resultado da operação
|
||||||
|
"""
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
results = []
|
codigo_selo = getattr(ato_schema, "codigo_selo", "CÓDIGO_INDISPONÍVEL")
|
||||||
|
|
||||||
# 1. Checa a chave AES
|
try:
|
||||||
if not AES_KEY:
|
logger.info(f"Iniciando processamento do selo: {codigo_selo}")
|
||||||
|
|
||||||
|
# Salva o ato com toda sua hierarquia
|
||||||
|
saved_ato = self._save_single_recursive_transaction(
|
||||||
|
db, ato_schema, parent_ato_principal_id=None
|
||||||
|
)
|
||||||
|
|
||||||
|
# Commit da transação completa
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Ato salvo com sucesso: ID={saved_ato.ato_principal_id}, "
|
||||||
|
f"Selo={codigo_selo}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "Ato Principal e vinculados salvos com sucesso",
|
||||||
|
"data": {
|
||||||
|
"ato_principal_id": saved_ato.ato_principal_id,
|
||||||
|
"codigo_selo": codigo_selo,
|
||||||
|
"tipo_ato": saved_ato.tipo_ato,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
except HTTPException as he:
|
||||||
|
db.rollback()
|
||||||
|
logger.warning(f"Erro HTTP ao processar selo {codigo_selo}: {he.detail}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": he.detail,
|
||||||
|
"data": {"codigo_selo": codigo_selo},
|
||||||
|
}
|
||||||
|
|
||||||
|
except IntegrityError as ie:
|
||||||
|
db.rollback()
|
||||||
|
logger.error(
|
||||||
|
f"Erro de integridade ao processar selo {codigo_selo}: {str(ie)}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Erro de integridade: violação de constraint no banco de dados",
|
||||||
|
"data": {"codigo_selo": codigo_selo},
|
||||||
|
}
|
||||||
|
|
||||||
|
except OperationalError as oe:
|
||||||
|
db.rollback()
|
||||||
|
logger.error(
|
||||||
|
f"Erro operacional ao processar selo {codigo_selo}: {str(oe)}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Erro de conexão ou operação no banco de dados",
|
||||||
|
"data": {"codigo_selo": codigo_selo},
|
||||||
|
}
|
||||||
|
|
||||||
|
except SQLAlchemyError as se:
|
||||||
|
db.rollback()
|
||||||
|
logger.error(
|
||||||
|
f"Erro SQLAlchemy ao processar selo {codigo_selo}: {str(se)}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Erro ao interagir com o banco de dados",
|
||||||
|
"data": {"codigo_selo": codigo_selo},
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
logger.error(
|
||||||
|
f"Erro inesperado ao processar selo {codigo_selo}: {str(e)}",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"Erro inesperado: {str(e)}",
|
||||||
|
"data": {"codigo_selo": codigo_selo},
|
||||||
|
}
|
||||||
|
|
||||||
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
raise Exception("A chave AES (aeskey) não está configurada.")
|
|
||||||
|
|
||||||
# 2. Loop principal: Cada iteração é uma transação completa (incluindo recursão)
|
def execute(
|
||||||
for ato_schema in atos_principais:
|
self, atos_principais: List[AtoPrincipalSaveSchema]
|
||||||
codigo_selo_log = getattr(ato_schema, "codigo_selo", "SELO_INDISPONÍVEL")
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Método principal para salvar múltiplos atos principais.
|
||||||
|
Cada ato é processado em uma transação independente.
|
||||||
|
|
||||||
try:
|
Args:
|
||||||
# A rotina completa de salvamento (incluindo filhos e recursão) é encapsulada
|
atos_principais: Lista de schemas de atos a salvar
|
||||||
saved_ato = self._save_single_recursive_transaction(
|
|
||||||
db,
|
|
||||||
ato_schema,
|
|
||||||
parent_ato_principal_id=None, # Ato de nível superior
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---------- COMMIT final para a transação completa (Ato Principal + Filhos + Atos Vinculados) ----------
|
Returns:
|
||||||
db.commit()
|
Lista de dicionários com resultado de cada operação
|
||||||
|
"""
|
||||||
|
logger.info(f"Iniciando processamento de {len(atos_principais)} atos")
|
||||||
|
|
||||||
# Retorno de sucesso
|
results = []
|
||||||
ato_result = {
|
successful_count = 0
|
||||||
"success": True,
|
failed_count = 0
|
||||||
"message": "Ato Principal e vinculados salvos com sucesso",
|
|
||||||
"data": {
|
|
||||||
"ato_principal_id": saved_ato.ato_principal_id,
|
|
||||||
"codigo_selo": codigo_selo_log,
|
|
||||||
"tipo_ato": saved_ato.tipo_ato,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
results.append(ato_result)
|
|
||||||
|
|
||||||
# Tratamento de erro específico para duplicidade ou HTTP
|
for idx, ato_schema in enumerate(atos_principais, 1):
|
||||||
except HTTPException as he:
|
logger.info(f"Processando ato {idx}/{len(atos_principais)}")
|
||||||
db.rollback()
|
|
||||||
results.append(
|
|
||||||
{
|
|
||||||
"success": False,
|
|
||||||
"error": he.detail,
|
|
||||||
"data": {"codigo_selo": codigo_selo_log},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Tratamento de erro genérico
|
result = self._process_single_ato(ato_schema)
|
||||||
except Exception as e:
|
results.append(result)
|
||||||
db.rollback()
|
|
||||||
|
|
||||||
# Log completo do erro
|
if result["success"]:
|
||||||
print(f"ERRO DE PERSISTÊNCIA NO SELO: {codigo_selo_log}")
|
successful_count += 1
|
||||||
traceback.print_exc()
|
else:
|
||||||
print("--------------")
|
failed_count += 1
|
||||||
|
|
||||||
results.append(
|
logger.info(
|
||||||
{
|
f"Processamento concluído: {successful_count} sucessos, "
|
||||||
"success": False,
|
f"{failed_count} falhas"
|
||||||
"error": "Erro ao salvar o registro. Verifique o formato dos dados ou os logs do servidor.",
|
)
|
||||||
"data": {"codigo_selo": codigo_selo_log},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
db.close()
|
|
||||||
return results
|
return results
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,60 @@
|
||||||
annotated-types==0.7.0
|
annotated-types==0.7.0
|
||||||
anyio==4.10.0
|
anyio==4.9.0
|
||||||
bcrypt==3.1.7
|
asyncpg==0.30.0
|
||||||
cffi==2.0.0
|
attrs==25.3.0
|
||||||
|
bcrypt==3.2.0
|
||||||
|
certifi==2025.4.26
|
||||||
|
cffi==1.17.1
|
||||||
click==8.2.1
|
click==8.2.1
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
cryptography==46.0.3
|
cryptography==45.0.2
|
||||||
|
distlib==0.3.9
|
||||||
dnspython==2.7.0
|
dnspython==2.7.0
|
||||||
ecdsa==0.19.1
|
ecdsa==0.19.1
|
||||||
email_validator==2.2.0
|
email_validator==2.2.0
|
||||||
fastapi==0.116.1
|
fastapi==0.115.12
|
||||||
firebird-base==2.0.2
|
filelock==3.18.0
|
||||||
firebird-driver==2.0.2
|
future==1.0.0
|
||||||
greenlet==3.2.4
|
greenlet==3.2.2
|
||||||
h11==0.16.0
|
h11==0.16.0
|
||||||
idna==3.10
|
idna==3.10
|
||||||
|
mysql-connector-python==9.3.0
|
||||||
|
outcome==1.3.0.post0
|
||||||
packaging==25.0
|
packaging==25.0
|
||||||
passlib==1.7.4
|
passlib==1.7.4
|
||||||
protobuf==5.29.5
|
pbr==6.1.1
|
||||||
pyasn1==0.6.1
|
platformdirs==4.3.8
|
||||||
|
protobuf==5.29.4
|
||||||
|
psycopg2==2.9.11
|
||||||
|
pyasn1==0.4.8
|
||||||
pycparser==2.22
|
pycparser==2.22
|
||||||
pydantic==2.11.7
|
pydantic==2.11.4
|
||||||
|
pydantic-settings==2.9.1
|
||||||
pydantic_core==2.33.2
|
pydantic_core==2.33.2
|
||||||
PyMySQL==1.1.2
|
PyMySQL==1.1.1
|
||||||
|
PySocks==1.7.1
|
||||||
python-dateutil==2.9.0.post0
|
python-dateutil==2.9.0.post0
|
||||||
python-jose==3.5.0
|
python-dotenv==1.1.0
|
||||||
|
python-jose==3.4.0
|
||||||
|
python-multipart==0.0.20
|
||||||
pytz==2025.2
|
pytz==2025.2
|
||||||
rsa==4.9.1
|
rsa==4.9.1
|
||||||
|
setuptools==80.8.0
|
||||||
six==1.17.0
|
six==1.17.0
|
||||||
sniffio==1.3.1
|
sniffio==1.3.1
|
||||||
SQLAlchemy==2.0.42
|
sortedcontainers==2.4.0
|
||||||
sqlalchemy-firebird==2.1
|
SQLAlchemy==2.0.41
|
||||||
starlette==0.47.2
|
starlette==0.46.2
|
||||||
typing-inspection==0.4.1
|
stevedore==5.4.1
|
||||||
typing_extensions==4.14.1
|
trio==0.30.0
|
||||||
uvicorn==0.35.0
|
trio-websocket==0.12.2
|
||||||
|
typing-inspection==0.4.0
|
||||||
|
typing_extensions==4.13.2
|
||||||
|
urllib3==2.4.0
|
||||||
|
uvicorn==0.34.2
|
||||||
|
virtualenv==20.31.2
|
||||||
|
virtualenv-clone==0.5.7
|
||||||
|
virtualenvwrapper==6.1.1
|
||||||
|
virtualenvwrapper-win==1.2.7
|
||||||
|
websocket-client==1.8.0
|
||||||
|
wsproto==1.2.0
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue