7. Relatório de Execução do Batch
A API oferece um endpoint dedicado para monitorar o progresso e resultado de operações em lote.
7.1 Endpoint de Relatório
Definição
URL: GET /erp-data/batch-job-report/{jobId}
Autenticação: X-API-Key obrigatória
Resposta: JSON com status de processamento
Exemplo Requisição:
curl --request GET \
--url https://api.retailportal.com/erp-data/batch-job-report/hangfire-job-1234567890 \
--header 'X-API-Key: ERP-RETAILPORTAL-2024-SECURE-KEY-ABC123'
7.2 Campo "state" — Estados Possíveis
O campo state indica o status atual do processamento:
| Estado | Significado | isComplete | Ação |
|---|---|---|---|
| Enqueued | Aguardando processamento na fila | false | Continue consultando |
| Processing | Processando ativamente | false | Continue consultando |
| Finished | Concluído (sucesso ou parcial) | true | Analise resultados |
| Failed | Falha total do servidor | true | Aguarde e reenvie |
7.2.1 Estado: Enqueued
Significado: O job está na fila do Hangfire, aguardando processamento.
Exemplo:
{
"jobId": "hangfire-job-1234567890",
"entityType": "ErpProduct",
"state": "Enqueued",
"isComplete": false,
"createdAt": "2024-12-06T10:30:00Z",
"startedAt": null,
"completedAt": null,
"duration": null,
"totalItems": 100,
"successCount": 0,
"failureCount": 0,
"retryAttempt": 0,
"hasPartialData": false,
"errorMessage": null,
"exceptionDetails": null
}
Interpretação:
- Nenhum processamento começou ainda
- Não há contadores de sucesso/falha (ambos 0)
- startedAt é null (não iniciou)
O que fazer: - Aguarde 5-10 segundos - Consulte novamente - Sistemas com pouca carga: geralmente passa rápido - Sistemas com muita carga: pode ficar neste estado por minutos
7.2.2 Estado: Processing
Significado: O sistema está processando ativamente os itens.
Exemplo:
{
"jobId": "hangfire-job-1234567890",
"entityType": "ErpProduct",
"state": "Processing",
"isComplete": false,
"createdAt": "2024-12-06T10:30:00Z",
"startedAt": "2024-12-06T10:30:02Z",
"completedAt": null,
"duration": null,
"totalItems": 100,
"successCount": 47,
"failureCount": 0,
"retryAttempt": 0,
"hasPartialData": false,
"errorMessage": null,
"exceptionDetails": null
}
Interpretação: - Processamento iniciou em 10:30:02 - 47 itens já foram processados com sucesso - 53 itens ainda estão sendo processados - Progresso: 47%
O que fazer: - Continue consultando a cada 5-10 segundos - Você pode dar feedback de progresso ao usuário - Não reenvie ainda
Cálculo de ETA (Tempo Estimado):
Tempo decorrido: 10:30:02 a 10:30:15 = 13 segundos
Itens processados: 47
Taxa: 47 / 13 = 3.6 itens/segundo
Itens faltantes: 100 - 47 = 53
ETA: 53 / 3.6 = ~15 segundos
Conclusão esperada: ~10:30:30
7.2.3 Estado: Finished
Significado: Processamento concluído. Pode ser sucesso total, parcial ou falha de validação.
Caso A: Sucesso Total
{
"jobId": "hangfire-job-1234567890",
"entityType": "ErpProduct",
"state": "Finished",
"isComplete": true,
"createdAt": "2024-12-06T10:30:00Z",
"startedAt": "2024-12-06T10:30:02Z",
"completedAt": "2024-12-06T10:35:15Z",
"duration": "00:05:13",
"totalItems": 100,
"successCount": 100,
"failureCount": 0,
"retryAttempt": 0,
"hasPartialData": false,
"errorMessage": null,
"exceptionDetails": null
}
Interpretação:
- successCount: 100 — Todos os itens foram salvos ✓
- failureCount: 0 — Nenhum erro
- hasPartialData: false — 100% dos dados processados
- duration: "00:05:13" — Levou 5 minutos e 13 segundos
Resultado: ✓ SUCESSO TOTAL — Nada a fazer
Caso B: Sucesso Parcial (Alguns Itens Falharam)
{
"jobId": "hangfire-job-1234567891",
"entityType": "ErpProduct",
"state": "Finished",
"isComplete": true,
"createdAt": "2024-12-06T10:40:00Z",
"startedAt": "2024-12-06T10:40:02Z",
"completedAt": "2024-12-06T10:45:30Z",
"duration": "00:05:28",
"totalItems": 100,
"successCount": 95,
"failureCount": 5,
"retryAttempt": 0,
"hasPartialData": true,
"errorMessage": "5 items failed validation during processing",
"exceptionDetails": null
}
Interpretação:
- successCount: 95 — 95 itens foram salvos ✓
- failureCount: 5 — 5 itens falharam ✗
- hasPartialData: true — Dados processados parcialmente
- errorMessage — Resumo do problema
Resultado: ⚠️ SUCESSO PARCIAL — Investigar os 5 itens falhados
O que fazer: 1. Consulte o histórico de integração para identificar quais itens falharam 2. Revise os dados desses itens (validação, formato, etc.) 3. Corrija e reenvie apenas esses 5 itens 4. Pode usar um novo batch com apenas os itens com erro
Caso C: Falha de Validação (Pré-processamento)
{
"jobId": "hangfire-job-1234567892",
"entityType": "ErpProduct",
"state": "Finished",
"isComplete": true,
"createdAt": "2024-12-06T11:00:00Z",
"startedAt": null,
"completedAt": "2024-12-06T11:00:00Z",
"duration": null,
"totalItems": 100,
"successCount": 0,
"failureCount": 100,
"retryAttempt": 0,
"hasPartialData": false,
"errorMessage": "Batch validation failed: Item at index 15 has invalid data",
"exceptionDetails": null
}
Interpretação:
- startedAt: null — Falha aconteceu antes do processamento
- successCount: 0 — Nenhum item foi salvo
- errorMessage — Especifica qual item tem problema
- Pode ser erro de validação ou dados duplicados
Resultado: ✗ FALHA DE VALIDAÇÃO — Dados precisam de correção
O que fazer: 1. Leia a mensagem de erro 2. Corrija o item indicado (índice 15) 3. Reenvie o batch
7.2.4 Estado: Failed
Significado: Falha total no servidor, NÃO é erro de dados.
{
"jobId": "hangfire-job-1234567893",
"entityType": "ErpProduct",
"state": "Failed",
"isComplete": true,
"createdAt": "2024-12-06T12:00:00Z",
"startedAt": "2024-12-06T12:00:02Z",
"completedAt": "2024-12-06T12:00:45Z",
"duration": "00:00:43",
"totalItems": 100,
"successCount": 0,
"failureCount": 100,
"retryAttempt": 5,
"hasPartialData": false,
"errorMessage": "Database connection pool exhausted",
"exceptionDetails": "PostgreSQL: Connection timeout after 5 retry attempts. Max pool size: 25"
}
Interpretação:
- state: "Failed" — Falha do servidor
- successCount: 0 — Nenhum dado foi salvo
- retryAttempt: 5 — Sistema tentou automaticamente 5 vezes
- errorMessage — Problema técnico do servidor
- exceptionDetails — Stack trace técnico
Resultado: ✗ FALHA DO SISTEMA — Não é erro de dados
O que fazer: 1. Não é erro de dados — Seus dados estão corretos 2. É um problema do servidor Portal Retail 3. Aguarde 5-10 minutos 4. Reenvie a mesma requisição (o sistema tentará novamente) 5. Se persistir, contacte o suporte
7.3 Propriedade "isComplete" — Trabalho Finalizado?
A propriedade isComplete facilita validação booleana:
isComplete = true → state é "Finished" ou "Failed"
isComplete = false → state é "Enqueued" ou "Processing"
Uso prático:
response = get_batch_status(job_id)
if not response['isComplete']:
# Job ainda está processando
esperar(5)
tentar_novamente()
else:
# Job terminou (pode ser sucesso ou falha)
if response['state'] == 'Finished':
analisar_resultados()
elif response['state'] == 'Failed':
reenviar_apos_esperar()
7.4 Propriedade "hasPartialData" — Dados Parciais
hasPartialData indica se ALGUNS itens falharam:
hasPartialData: true → Alguns itens falharam, outros sucesso
hasPartialData: false → Ou todos sucedem, ou todos falham
Exemplo:
- 100 itens, 95 sucesso, 5 falha → hasPartialData: true
- 100 itens, 100 sucesso, 0 falha → hasPartialData: false
- 100 itens, 0 sucesso, 100 falha → hasPartialData: false
7.5 Campo "duration" — Tempo de Processamento
Formato: HH:mm:ss (horas, minutos, segundos)
Exemplos:
- "duration": "00:05:13" — 5 minutos e 13 segundos
- "duration": "00:00:45" — 45 segundos
- "duration": "01:23:47" — 1 hora, 23 minutos, 47 segundos
- "duration": null — Job ainda não processou ou validação falhou
7.6 Histórico de Tentativas — "retryAttempt"
O Hangfire reprocessa jobs que falharam automaticamente:
| retryAttempt | Significado |
|---|---|
| 0 | Primeira tentativa (ou sucesso na primeira) |
| 1 | Job falhou, Hangfire retentou 1x |
| 2 | Job falhou, Hangfire retentou 2x |
| ... | ... |
| 5 | Job falhou 5 vezes (limite máximo) |
Politica de Retry: - Máximo: 5 tentativas - Intervalo: Exponencial (aumenta a cada falha) - Tipo: Automático (não você, o Hangfire)
Se retryAttempt: 5:
- Sistema tentou 5 vezes e falhou
- Está fora do pool de retry
- Você deve reenviar manualmente após aguardar
7.7 Fluxo de Polling Recomendado
PASSO 1: Enviar batch
POST /product/batch → 202 + jobId
jobId: "hangfire-1234567890"
PASSO 2: Consultar status (LOOP)
GET /batch-job-report/{jobId}
INTERVALO: 5-10 segundos
TIMEOUT: 5 minutos máximo
CONDIÇÃO PARADA: isComplete = true
PASSO 3: Analisar resultado
Se successCount = totalItems → ✓ Sucesso
Se failureCount > 0 → Investigar erros
Se state = "Failed" → Aguardar 5 min e reenviar
FLUXO COMPLETO:
─────────────────
00:00 → POST batch
00:05 → GET report → Enqueued
00:10 → GET report → Processing (25%)
00:15 → GET report → Processing (50%)
00:20 → GET report → Processing (75%)
00:25 → GET report → Finished (100%)
00:26 → Análise de resultado
7.8 Implementação em Código
Python com Polling
import requests
import time
import json
def aguardar_batch(job_id, timeout_segundos=300):
"""
Aguarda conclusão de um batch job com polling
Args:
job_id: Identificador do job (recebido em 202 Accepted)
timeout_segundos: Tempo máximo de espera (padrão: 5 minutos)
Returns:
Dicionário com resultado do batch
"""
api_key = "SUA-CHAVE-API"
headers = {'X-API-Key': api_key}
url = f"https://api.retailportal.com/erp-data/batch-job-report/{job_id}"
inicio = time.time()
while True:
# Verificar timeout
tempo_decorrido = time.time() - inicio
if tempo_decorrido > timeout_segundos:
print(f"❌ Timeout: Job não completou em {timeout_segundos} segundos")
return None
# Consultar status
response = requests.get(url, headers=headers)
if response.status_code != 200:
print(f"❌ Erro ao consultar status: {response.status_code}")
return None
data = response.json()
# Mostrar progresso
if data['state'] == 'Processing':
progresso = (data['successCount'] / data['totalItems']) * 100
print(f"⏳ Processando... {data['successCount']}/{data['totalItems']} ({progresso:.0f}%)")
# Verificar conclusão
if data['isComplete']:
print(f"✓ Job concluído: {data['state']}")
return data
# Aguardar antes de próxima consulta
print("Consultando novamente em 5 segundos...")
time.sleep(5)
def criar_batch_e_aguardar(produtos):
"""Cria batch e aguarda conclusão"""
api_key = "SUA-CHAVE-API"
headers = {
'X-API-Key': api_key,
'Content-Type': 'application/json'
}
# PASSO 1: Enviar batch
print("Enviando batch...")
response = requests.post(
'https://api.retailportal.com/erp-data/product/batch',
headers=headers,
json={'products': produtos}
)
if response.status_code != 202:
print(f"❌ Erro ao enviar batch: {response.status_code}")
print(response.json())
return None
batch_response = response.json()
job_id = batch_response['jobId']
print(f"✓ Batch enfileirado: {job_id}")
# PASSO 2: Aguardar conclusão
print("\nAguardando processamento...")
resultado = aguardar_batch(job_id)
# PASSO 3: Analisar resultado
if resultado:
print("\n=== RESULTADO FINAL ===")
print(f"Total: {resultado['totalItems']}")
print(f"Sucesso: {resultado['successCount']}")
print(f"Erro: {resultado['failureCount']}")
print(f"Duração: {resultado['duration']}")
if resultado['failureCount'] > 0:
print(f"\n⚠️ {resultado['failureCount']} itens falharam")
if resultado['errorMessage']:
print(f"Erro: {resultado['errorMessage']}")
return resultado
# Usar
produtos = [
{
"erpId": 12345,
"ncmCode": "84733090",
"status": "Active",
"description": "Dipirona 500mg"
},
# ... mais produtos
]
resultado = criar_batch_e_aguardar(produtos)
Bash com cURL
#!/bin/bash
API_KEY="SUA-CHAVE-API"
URL_BATCH="https://api.retailportal.com/erp-data/product/batch"
URL_REPORT="https://api.retailportal.com/erp-data/batch-job-report"
# Função para enviar batch
enviar_batch() {
local json_file=$1
curl -s -X POST \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d @"$json_file" \
"$URL_BATCH"
}
# Função para consultar status
consultar_status() {
local job_id=$1
curl -s -X GET \
-H "X-API-Key: $API_KEY" \
"$URL_REPORT/$job_id"
}
# Função para aguardar conclusão
aguardar_conclusao() {
local job_id=$1
local timeout=300
local inicio=$(date +%s)
while true; do
agora=$(date +%s)
decorrido=$((agora - inicio))
if [ $decorrido -gt $timeout ]; then
echo "❌ Timeout após $timeout segundos"
return 1
fi
status=$(consultar_status "$job_id")
is_complete=$(echo "$status" | jq '.isComplete')
state=$(echo "$status" | jq -r '.state')
echo "Estado: $state"
if [ "$is_complete" = "true" ]; then
echo "✓ Job concluído"
echo "$status" | jq '.'
return 0
fi
echo "Aguardando 5 segundos..."
sleep 5
done
}
# Executar
echo "Enviando batch..."
resposta=$(enviar_batch "produtos.json")
job_id=$(echo "$resposta" | jq -r '.jobId')
echo "Job ID: $job_id"
echo "Aguardando conclusão..."
aguardar_conclusao "$job_id"
7.9 Logging Agregado de Batch
A API implementa logging condicional para reduzir ruído e evitar duplicação:
O que é registrado no histórico de integração (_Erp_IntegrationHistory):
- ✅ UM log por batch contendo:
- Total de itens processados
- Número de sucessos
- Número de falhas
- Resumo de erros
- Duração total
- Status final (sucesso/falha)
- ❌ NENHUM log para itens individuais do batch
- Mesmo para itens que falharam (evita duplicação)
- Mesmo para itens que tiveram sucesso
Razão: - Um batch de 1.000 itens geraria 1.000+ logs individuais - Estratégia condicional reduz para 1 log agregado - Mantém rastreabilidade sem criar ruído excessivo
Exemplo de Relatório:
Batch Job (jobId: hangfire-123):
- Data: 2024-12-06 10:30:00
- Tipo: ErpProduct
- Operação: CreateBatch
- Status: Finished
- Total: 100 itens
- Sucessos: 95
- Falhas: 5
- Duração: 5 minutos 13 segundos
- Histórico: ✓ UM registro contendo tudo acima
Se você precisa de detalhes de itens individuais:
- Consulte o endpoint /batch-job-report/{jobId} que retorna progresso em tempo real
- Implemente logging no lado cliente que correlacione jobId com items originais
- Mantenha mapeamento local de qual item foi enviado em qual batch
7.10 Checklist de Relatório
- [ ] Consultar status com jobId correto?
- [ ] Verificar campo
state(Enqueued, Processing, Finished, Failed)? - [ ] Verificar
isComplete(false = ainda processando)? - [ ] Se
isComplete: true: AnalisarsuccessCountvsfailureCount? - [ ] Se
failureCount > 0: ConsultarerrorMessage? - [ ] Se
state: "Failed": Aguardar e reenviar após 5 minutos? - [ ] Se
hasPartialData: true: Implementar logging cliente para rastrear itens? - [ ] Verificar
durationpara otimizar tamanho de batch? - [ ] Entender que histórico mostra UM log agregado por batch (não items individuais)?