HOTFIX: Corrgidigo bug em prod
This commit is contained in:
parent
a7f9f7f714
commit
50d5b77261
165
docs/RODAMP.MD
Normal file
165
docs/RODAMP.MD
Normal file
@ -0,0 +1,165 @@
|
||||
# Roadmap de Evolucao - SN <-> GLPI
|
||||
|
||||
## Visao Geral
|
||||
Objetivo: elevar confiabilidade, observabilidade e arquitetura da integracao sem interromper operacao.
|
||||
|
||||
Principios:
|
||||
- migracao incremental (sem Big Bang)
|
||||
- manter compatibilidade com fluxo atual
|
||||
- cada fase precisa ter criterio claro de aceite
|
||||
|
||||
## Fases (ordem recomendada)
|
||||
|
||||
### Fase 0 - Baseline e seguranca operacional (1 semana)
|
||||
Objetivo: criar base para evolucao segura.
|
||||
|
||||
Entregas:
|
||||
- checklist oficial de deploy/rollback em producao
|
||||
- backup e restore testado para banco intermediario
|
||||
- playbook de incidentes (falha SN, falha GLPI, falha DB)
|
||||
- padrao de versionamento e convencao de commits
|
||||
|
||||
Criterios de aceite:
|
||||
- rollback executado com sucesso em ambiente de teste
|
||||
- documentacao revisada e aprovada
|
||||
|
||||
Risco: baixo
|
||||
|
||||
---
|
||||
|
||||
### Fase 1 - Logs estruturados e rastreabilidade (1-2 semanas)
|
||||
Objetivo: saber exatamente onde e por que falhou.
|
||||
|
||||
Entregas:
|
||||
- padrao de log com contexto por camada e direcao:
|
||||
- `[USECASE][GLPI>SN]`
|
||||
- `[USECASE][SN>GLPI]`
|
||||
- `[SERVICE]`, `[REPOSITORY]`, `[INTEGRATION]`
|
||||
- `correlation_id` por ciclo e por ticket
|
||||
- campos padrao nos logs: `ticket_number`, `sn_ticket_id`, `glpi_ticket_id`, `source_last`, `step`
|
||||
- metricas minimas por ciclo: processados, sucesso, erro, duracao
|
||||
|
||||
Criterios de aceite:
|
||||
- conseguir rastrear 1 ticket de ponta a ponta por `correlation_id`
|
||||
- reduzir tempo de diagnostico de erro em pelo menos 50%
|
||||
|
||||
Risco: baixo
|
||||
|
||||
---
|
||||
|
||||
### Fase 2 - Estabilizacao final de fluxos (1 semana)
|
||||
Objetivo: fechar lacunas de negocio antes de refatoracao pesada.
|
||||
|
||||
Entregas:
|
||||
- bateria manual oficial para `incidente` e `requisicao`
|
||||
- validacao de regras finais:
|
||||
- resolucao GLPI -> SN
|
||||
- reabertura SN -> GLPI
|
||||
- fechamento permanente GLPI nao reabre
|
||||
- fora de escopo nao reabre
|
||||
- hardening de idempotencia em comentarios/tarefas
|
||||
|
||||
Criterios de aceite:
|
||||
- 100% do checklist de fluxos principais aprovado em DEV
|
||||
|
||||
Risco: baixo
|
||||
|
||||
---
|
||||
|
||||
### Fase 3 - Modularizacao progressiva (monolito modular, estilo clean) (3-5 semanas)
|
||||
Objetivo: organizar o sistema em modulos com fronteiras claras.
|
||||
|
||||
Entregas:
|
||||
- estrutura em camadas:
|
||||
- `domain` (regras e entidades)
|
||||
- `application` (casos de uso)
|
||||
- `infrastructure` (DB, APIs, logger)
|
||||
- `interfaces` (cron/controllers)
|
||||
- definicao de portas/adapters para SN, GLPI e repositorios
|
||||
- migracao por vertical (ticket -> comment -> status)
|
||||
|
||||
Criterios de aceite:
|
||||
- novos casos de uso entram sem acoplar controller direto a SQL/API
|
||||
- arquitetura documentada em diagrama simples
|
||||
|
||||
Risco: medio
|
||||
|
||||
---
|
||||
|
||||
### Fase 4 - GLPI via API (2-4 semanas)
|
||||
Objetivo: reduzir dependencia de escrita direta no banco GLPI.
|
||||
|
||||
Entregas:
|
||||
- cliente GLPI API para:
|
||||
- criar ticket
|
||||
- inserir comentario
|
||||
- inserir tarefa
|
||||
- atualizar status
|
||||
- fallback controlado para SQL apenas enquanto necessario
|
||||
- comparativo de comportamento API x SQL em DEV
|
||||
|
||||
Criterios de aceite:
|
||||
- ao menos 1 fluxo completo operando via API com sucesso
|
||||
- sem regressao de regras de negocio
|
||||
|
||||
Risco: medio
|
||||
|
||||
---
|
||||
|
||||
### Fase 5 - Sincronizacao de imagens/anexos (3-6 semanas)
|
||||
Objetivo: suportar anexos dos dois lados.
|
||||
|
||||
Entregas:
|
||||
- modelagem de rastreio de anexos (source_id, destiny_id, hash, status)
|
||||
- pipeline SN -> GLPI e GLPI -> SN:
|
||||
- listar anexos
|
||||
- baixar binario
|
||||
- validar tipo/tamanho
|
||||
- upload no destino
|
||||
- deduplicacao por hash
|
||||
- retries com backoff
|
||||
|
||||
Criterios de aceite:
|
||||
- anexos de teste sobem nos dois sentidos com rastreabilidade
|
||||
- sem duplicacao de anexos no reprocessamento
|
||||
|
||||
Risco: alto
|
||||
|
||||
---
|
||||
|
||||
### Fase 6 - Qualidade e teste automatizado (2-4 semanas, em paralelo)
|
||||
Objetivo: diminuir risco de regressao.
|
||||
|
||||
Entregas:
|
||||
- testes unitarios de mapeamento/status/regras
|
||||
- testes de integracao com mocks de SN e GLPI
|
||||
- smoke test automatizado por ciclo
|
||||
|
||||
Criterios de aceite:
|
||||
- cobertura minima em regras criticas
|
||||
- pipeline bloqueando merge em caso de regressao
|
||||
|
||||
Risco: medio
|
||||
|
||||
## Backlog Tecnico (prioridade)
|
||||
P1:
|
||||
- logs estruturados + correlation_id
|
||||
- checklist oficial de deploy/rollback
|
||||
- bateria de testes de `requisicao`
|
||||
|
||||
P2:
|
||||
- modularizacao por casos de uso
|
||||
- GLPI API para comentarios/tarefas
|
||||
|
||||
P3:
|
||||
- anexos/imagens bidirecional
|
||||
- observabilidade avancada (dashboards/alertas)
|
||||
|
||||
## Definicao de Pronto (DoD) por fase
|
||||
- codigo revisado
|
||||
- documentacao atualizada
|
||||
- evidencias de teste anexadas
|
||||
- plano de rollback definido
|
||||
|
||||
## Observacao
|
||||
Este roadmap prioriza estabilidade de operacao antes de expansao funcional. A transicao para arquitetura modular deve ser gradual e guiada por casos de uso reais.
|
||||
@ -7,6 +7,31 @@ const { closeTicketInServiceNow, updateStatusInServiceNow, addWorkNoteToServiceN
|
||||
const { addServiceNowClosureNoteToGlpi } = require('../services/glpiTicketService');
|
||||
|
||||
const outOfScopeSolutionTypeId = Number(process.env.GLPI_OOS_SOLUTION_TYPE_ID || 27);
|
||||
const snCloseRetryMax = Number(process.env.SN_CLOSE_RETRY_MAX || 2);
|
||||
const snCloseRetryDelayMs = Number(process.env.SN_CLOSE_RETRY_DELAY_MS || 1500);
|
||||
|
||||
const waitMs = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const attemptCloseInServiceNowWithRetry = async (ticketSnDetails, closeNotes, resolvedAt) => {
|
||||
for (let attempt = 1; attempt <= snCloseRetryMax + 1; attempt += 1) {
|
||||
const closeOk = await closeTicketInServiceNow(ticketSnDetails.id, closeNotes, resolvedAt);
|
||||
if (closeOk) {
|
||||
const liveStatus = await fetchTicketLiveStatusFromServiceNow(ticketSnDetails.sys_id, ticketSnDetails.tipo);
|
||||
if (liveStatus === 'Resolvido' || liveStatus === 'Encerrado' || liveStatus === 'Encerrado - Omitido') {
|
||||
return { ok: true, liveStatus };
|
||||
}
|
||||
logWarning(`Fechamento SN tentou atualizar, mas status ao vivo ficou '${liveStatus}'. Tentando novamente.`);
|
||||
} else {
|
||||
logWarning(`Tentativa ${attempt} de fechamento no SN falhou para ${ticketSnDetails.ticket_number}.`);
|
||||
}
|
||||
|
||||
if (attempt <= snCloseRetryMax) {
|
||||
await waitMs(snCloseRetryDelayMs);
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: false, liveStatus: null };
|
||||
};
|
||||
|
||||
const processStatusAndClosureController = async () => {
|
||||
logInfo('Iniciando processamento de status e fechamento de tickets...');
|
||||
@ -61,14 +86,9 @@ const processStatusAndClosureController = async () => {
|
||||
}
|
||||
|
||||
// Regra de precedencia de finalizacao:
|
||||
// quando GLPI ja esta finalizado e SN ainda nao, prioriza GLPI->SN
|
||||
// apenas se o bastao ainda estiver com GLPI.
|
||||
// Se o bastao estiver com SNOW, permitimos reabertura SN->GLPI.
|
||||
if (
|
||||
glpiIsFinalState &&
|
||||
!snIsFinalState &&
|
||||
(!glpiWasFinalLocally || ticket.source_last === 'GLPI')
|
||||
) {
|
||||
// quando GLPI ja esta finalizado e SN ainda nao, sempre prioriza GLPI->SN
|
||||
// para evitar reabertura indevida no GLPI.
|
||||
if (glpiIsFinalState && !snIsFinalState) {
|
||||
effectiveSourceLast = 'GLPI';
|
||||
}
|
||||
|
||||
@ -113,8 +133,8 @@ const processStatusAndClosureController = async () => {
|
||||
const glpiCurrentStatus = glpiLiveStatus; // Reutiliza o status que já buscamos
|
||||
if (glpiCurrentStatus !== null) {
|
||||
if (glpiCurrentStatus === 5) { // GLPI foi para 'Solucionado'
|
||||
// Apenas processa se o SN não estiver já resolvido/fechado
|
||||
if (ticket.sn_sync_status !== 'solved' && ticket.sn_sync_status !== 'closed') {
|
||||
// Apenas processa se o SN nao estiver resolvido/fechado de fato
|
||||
if (!snIsFinalState) {
|
||||
logInfo(`GLPI ${ticket.glpi_ticket_id} foi Solucionado. Sincronizando para SN...`);
|
||||
const solution = await TicketGlpiModel.getTicketSolution(ticket.glpi_ticket_id);
|
||||
|
||||
@ -136,8 +156,13 @@ const processStatusAndClosureController = async () => {
|
||||
// FLUXO NORMAL DE RESOLUÇÃO
|
||||
const closeNotes = solution?.content ? stripHTML(solution.content) : 'Resolvido via integração GLPI.';
|
||||
const resolvedAt = solution?.date_mod || new Date();
|
||||
await closeTicketInServiceNow(ticket.sn_ticket_id, closeNotes, resolvedAt);
|
||||
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'solved', 'solved');
|
||||
const closeResult = await attemptCloseInServiceNowWithRetry(ticketSnDetails, closeNotes, resolvedAt);
|
||||
if (closeResult.ok) {
|
||||
await TicketSyncModel.updateStatusAndSourceLast(ticket.sn_ticket_id, 'solved', 'solved', 'GLPI');
|
||||
} else {
|
||||
await TicketSyncModel.updateSourceLast(ticket.sn_ticket_id, 'GLPI');
|
||||
logWarning(`Falha ao confirmar fechamento no SN para ${ticketSnDetails.ticket_number}. Mantendo ticket para nova tentativa.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (glpiCurrentStatus === 6) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user