diff --git a/.env.development b/.env.development index f5dfbcc..4fa1039 100644 --- a/.env.development +++ b/.env.development @@ -27,6 +27,7 @@ SERVICENOW_SC_ITEM_OPTION_URL=https://caoadev.service-now.com/api/now/table/sc_i SERVICENOW_DEFAULT_USER=SOTHIS.CAOA SERVICENOW_DEFAULT_USER_SYSID=b1312fda977a1250501634a6f053af98 SERVICENOW_RESOLVED_BY_SYSID=b131d6ed1b2ba510c5e163923b4bcb77 +SERVICENOW_IGNORE_DEFAULT_USER_JOURNAL=false # GLPI (banco real) GLPI_DB_TYPE=mysql diff --git a/README.md b/README.md index 3eeb87f..e541fb6 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ Middleware em Node.js para sincronizacao bidirecional de tickets, comentarios e - Sincroniza comentarios em duas direcoes: - ServiceNow -> GLPI - GLPI -> ServiceNow +- No fluxo ServiceNow -> GLPI: + - `comments` viram comentarios (followups) no GLPI. + - `work_notes` viram tarefas no GLPI. - Sincroniza status em duas direcoes com controle de origem (`source_last`: `SNOW`/`GLPI`). - Trata regras de negocio especificas de fechamento/reabertura. - Reprocessa tickets em estado de erro no inicio de cada ciclo. @@ -44,6 +47,11 @@ Executado em ordem pelo `main()`: ## Regras de negocio importantes - Controle de precedencia por `source_last` para reduzir conflito de atualizacao. +- Ao detectar divergencia de status, o bastao nao e invertido para `GLPI` quando a ultima origem valida ja e `SNOW`. +- Encerramento/Resolucao vindo do ServiceNow: + - Nao fecha ticket no GLPI automaticamente. + - Insere nota formatada no GLPI. + - Marca sync como `closed/closed` para remover do monitoramento. - Ticket resolvido no GLPI com `solutiontypes_id = 27` (fora do escopo): - Nao resolve no SN. - Adiciona `work_note` no SN. @@ -66,7 +74,10 @@ Executado em ordem pelo `main()`: - Regra fora do escopo (`solutiontypes_id = 27`) fecha fluxo local sem resolver no SN e registra `work_note`. 3. Status ServiceNow -> GLPI -- Mudancas de estado no SN sao refletidas no GLPI respeitando o estado final de fechado. +- Mudancas de estado no SN sao refletidas no GLPI para tickets ativos. +- Para `Resolvido`, `Encerrado` e `Encerrado - Omitido` no SN: + - GLPI recebe nota de encerramento/resolucao. + - Integracao remove ticket do monitoramento (`closed/closed`), sem fechar ticket no GLPI. - Fechamento permanente no GLPI bloqueia reabertura automatica por sincronizacao. 4. Comentarios GLPI -> SN @@ -75,6 +86,9 @@ Executado em ordem pelo `main()`: 5. Comentarios SN -> GLPI - Comentarios novos detectados no SN sao persistidos no banco intermediario e enviados ao GLPI. +- `work_notes` do SN sao persistidas como `update_type = task` e enviadas como tarefa no GLPI. +- Campo `author` no banco intermediario usa `sys_created_by` do SN. +- Conteudo enviado ao GLPI e formatado com card visual e autor no cabecalho. - IDs de origem/destino ficam registrados em `ticket_updates` para rastreabilidade. 6. Reprocessamento de erro @@ -114,6 +128,8 @@ Crie `.env.development` e `.env.production` com base em `.env.example` e inclua - `SERVICENOW_TABLE_REQUEST_URL` - `SERVICENOW_TABLE_JOURNAL_URL` - `SERVICENOW_SC_ITEM_OPTION_URL` +- `SERVICENOW_DEFAULT_USER` +- `SERVICENOW_IGNORE_DEFAULT_USER_JOURNAL` (`true` por padrao; use `false` para testes locais) ### GLPI (MySQL/MariaDB) @@ -191,7 +207,6 @@ Definicao base em `src/scripts/database/scriptBD.sql`: ## Limitacoes conhecidas (estado atual) - Nao ha suite de testes automatizados (`npm test` e placeholder). -- Ha regras hardcoded que deveriam vir de configuracao (ex.: `resolved_by` em fechamento no SN). - Parte do fluxo depende de acesso direto ao banco do GLPI (acoplamento operacional alto). - Existe logica sensivel de timezone/watermark que merece revisao para evitar reprocesso/perda de evento. diff --git a/src/models/ticketGlpiModel.js b/src/models/ticketGlpiModel.js index 6d1d1df..3c13625 100644 --- a/src/models/ticketGlpiModel.js +++ b/src/models/ticketGlpiModel.js @@ -313,6 +313,33 @@ class TicketGlpiModel { } } + static async insertTask(task, items_id) { + const connection = await glpiPool.getConnection(); + try { + // Registra work_notes do ServiceNow como tarefa no GLPI. + const query = ` + INSERT INTO glpi_tickettasks + (tickets_id, users_id, state, content, date, date_mod, actiontime) + VALUES (?, ?, ?, ?, NOW(), NOW(), ?) + `; + const values = [ + items_id, + GLPI_DEFAULT_USER_ID, + 2, + task.content, + 0 + ]; + + const [result] = await connection.execute(query, values); + return [{ id: result.insertId }]; + } catch(error){ + logError(`Erro ao inserir tarefa no GLPI: ${error}`); + throw error; + } finally { + if (connection) connection.release(); + } + } + static async getClosedTickets(watermark, limit = 100) { const connection = await glpiPool.getConnection(); try { diff --git a/src/services/glpiCommentService.js b/src/services/glpiCommentService.js index 82b5553..79400b7 100644 --- a/src/services/glpiCommentService.js +++ b/src/services/glpiCommentService.js @@ -7,6 +7,24 @@ const { existingCommentInServiceNow, syncCommentToServiceNow } = require('./serv const { sanitizeGLPIComment } = require('../utils/commentSanitizer'); const { log } = require('winston'); +const formatSNUpdateForGlpi = (update) => { + const escapeHtml = (value) => String(value || '') + .replace(/&/g, '&') + .replace(//g, '>'); + + const author = escapeHtml(update.author || 'ServiceNow'); + const content = escapeHtml(update.content || ''); + + return ` +