const pool = require('../data/database'); const { logError, logInfo } = require('../utils/logger'); class TicketSyncModel { static async createSyncRecord(data) { try { const query = `INSERT INTO ticket_sync ( sn_ticket_id, glpi_ticket_id, last_sn_sync, last_glpi_sync, created_at, updated_at, sn_sync_status, glpi_sync_status, source_last ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id`; const values = [ data.sn_ticket_id, data.glpi_ticket_id, data.last_sn_sync, data.last_glpi_sync, data.created_at, data.updated_at, data.sn_sync_status, data.glpi_sync_status, data.source_last ] await pool.query(query, values); } catch (error) { logError(error, 'ERRO: Erro ao criar registro de sincronizacao'); throw error; } } static async getTicketsToSync(glpi_sync_status, sn_sync_status) { try { const query = 'SELECT id, glpi_ticket_id, sn_ticket_id FROM ticket_sync WHERE glpi_sync_status = $1 AND sn_sync_status = $2'; const values =[glpi_sync_status , sn_sync_status]; const { rows } = await pool.query(query, values); return rows; } catch (error) { logError(error, 'ERRO: Erro ao buscar tickets para sincronizar'); throw error; } } static async updateSourceLast(snTicketId, source) { try { const query = 'UPDATE ticket_sync SET source_last = $1 WHERE sn_ticket_id = $2'; await pool.query(query, [source, snTicketId]); } catch (error) { logError(error, `ERRO: Erro ao atualizar source_last para o ticket SN ID: ${snTicketId}`); throw error; } } static async updateStatus(ticketSyncId, glpiStatus, snStatus) { try { const query = 'UPDATE ticket_sync SET glpi_sync_status = $1, sn_sync_status = $2 WHERE sn_ticket_id = $3'; await pool.query(query, [glpiStatus, snStatus, ticketSyncId]); } catch (error) { logError(`${error} ERRO: Erro ao atualizar status de sincronizacao para o ID: ${ticketSyncId}`); throw error; } } static async updateStatusAndSourceLast(ticketSyncId, glpiStatus, snStatus, source_last) { try { const query = 'UPDATE ticket_sync SET glpi_sync_status = $1, sn_sync_status= $2, source_last = $4 WHERE sn_ticket_id = $3'; await pool.query(query, [glpiStatus, snStatus, ticketSyncId, source_last]); } catch (error) { logError(`${error} ERRO: Erro ao atualizar status de sincronizacao para o ID: ${ticketSyncId}`); throw error; } } static async updateStatusAndGlpiTicket(ticketSyncId, glpiStatus, snStatus, glpiTicketId) { try { const query = 'UPDATE ticket_sync SET glpi_sync_status = $1, sn_sync_status = $2, glpi_ticket_id = $4 WHERE sn_ticket_id = $3'; await pool.query(query, [glpiStatus, snStatus, ticketSyncId, glpiTicketId]); } catch (error) { logError(error, `ERRO: Erro ao atualizar status de sincronizacao para o ID: ${ticketSyncId}`); throw error; } } static async getIdSyncByGlpiId(glpiTicketId) { try { const query = 'SELECT id FROM ticket_sync WHERE glpi_ticket_id = $1'; const { rows } = await pool.query(query, [glpiTicketId]); return rows[0] ? rows[0].id : null; } catch (error) { logError(error, `ERRO: Erro ao buscar ID de sincronizacao por GLPI ID: ${glpiTicketId}`); throw error; } } static async getBySysId(sysId) { try { const query = ` SELECT tsync.* FROM ticket_sync tsync JOIN tickets_sn tsn ON tsync.sn_ticket_id = tsn.id WHERE tsn.sys_id = $1`; const { rows } = await pool.query(query, [sysId]); return rows[0] || null; } catch (error) { logError(error, `ERRO: Erro ao buscar registro de sincronizacao por sys_id: ${sysId}`); throw error; } } static async getIdSyncBySnId(snTicketId) { try { const query = 'SELECT id FROM ticket_sync WHERE sn_ticket_id = $1'; const { rows } = await pool.query(query, [snTicketId]); return rows[0] ? rows[0].id : null; } catch (error) { logError(error, `ERRO: Erro ao buscar ID de sincronizacao por SN ID: ${snTicketId}`); throw error; } } static async getByTicketNumber(ticketNumber) { try { const query = 'SELECT * FROM ticket_sync WHERE sn_ticket_id = $1'; const { rows } = await pool.query(query, [ticketNumber]); return rows[0] || null; } catch (error) { logError(`${error}, ERRO: Erro ao buscar ticket ${ticketNumber}}` ) } } static async getTicketsSynced() { try { const query = ` SELECT tsync.id, tsync.glpi_ticket_id, tsync.sn_ticket_id, tsn.sys_id, tsync.source_last FROM ticket_sync tsync JOIN tickets_sn tsn ON tsync.sn_ticket_id = tsn.id WHERE tsync.glpi_sync_status = 'synced' AND tsync.sn_sync_status = 'synced'`; const { rows } = await pool.query(query); return rows; } catch (error) { logError(error, 'ERRO: Erro ao buscar tickets para sincronizar'); throw error; } } static async getTicketsToMonitor() { try { const query = ` SELECT tsync.id, tsync.glpi_ticket_id, tsync.sn_ticket_id, tsn.sys_id, tsync.source_last, tsync.sn_sync_status, tsync.glpi_sync_status FROM ticket_sync tsync JOIN tickets_sn tsn ON tsync.sn_ticket_id = tsn.id WHERE (tsync.glpi_sync_status IN ('synced', 'solved') OR tsync.sn_sync_status IN ('synced', 'solved')) AND (tsync.glpi_sync_status != 'closed' AND tsync.sn_sync_status != 'closed')`; const { rows } = await pool.query(query); return rows; } catch (error) { logError(error, 'ERRO: Erro ao buscar tickets para monitorar'); throw error; } } static async getTicketsPendingSolve() { try { const query = ` SELECT tsync.id, tsync.glpi_ticket_id, tsync.sn_ticket_id, tsync.glpi_sync_status, tsync.sn_sync_status, tsn.sys_id, tsn.tipo FROM ticket_sync tsync JOIN tickets_sn tsn ON tsync.sn_ticket_id = tsn.id WHERE tsync.glpi_sync_status = 'pending_solve' OR tsync.sn_sync_status = 'pending_solve'`; const { rows } = await pool.query(query); return rows; } catch (error) { logError(error, 'ERRO: Erro ao buscar tickets pendentes de fechamento'); throw error; } } static async getTicketsInErrorState() { try { const query = ` SELECT id, sn_ticket_id, glpi_ticket_id, sn_sync_status, glpi_sync_status FROM ticket_sync WHERE sn_sync_status LIKE '%error%' OR glpi_sync_status LIKE '%error%' `; const { rows } = await pool.query(query); return rows; } catch (error) { logError(error, 'ERRO: Erro ao buscar tickets em estado de erro'); throw error; } } static async updateLastSync(snTicketId, system) { try { let fieldToUpdate; if (system === 'servicenow') { fieldToUpdate = 'last_sn_sync'; } else if (system === 'glpi') { fieldToUpdate = 'last_glpi_sync'; } else { return; } const query = `UPDATE ticket_sync SET ${fieldToUpdate} = NOW() WHERE sn_ticket_id = $1`; await pool.query(query, [snTicketId]); } catch (error) { logError(`${error} ERRO: Erro ao atualizar last_sync para o ID: ${snTicketId}`); } } static async updateStatusByGlpiId(glpiTicketId, snStatus, glpiStatus) { try { const query = 'UPDATE ticket_sync SET sn_sync_status = $1, glpi_sync_status = $2 WHERE glpi_ticket_id = $3'; await pool.query(query, [snStatus, glpiStatus, glpiTicketId]); } catch (error) { logError(`${error} ERRO: Erro ao atualizar status de sincronizacao para o GLPI ID: ${glpiTicketId}`); throw error; } } } /** * @module TicketSyncModel * @description Este modulo e a camada de acesso a tabela `ticket_sync`, que atua como o cerebro do processo de sincronizacao. * Ele armazena o estado de cada ticket em ambos os sistemas (GLPI e ServiceNow) e controla o fluxo de atualizacoes. * * Principais Funcionalidades: * - **Gerenciamento de Estado**: Metodos como `updateStatus`, `updateStatusAndSourceLast` e `updateStatusAndGlpiTicket` sao usados para modificar os status de sincronizacao (ex: 'pending_check', 'synced', 'solved', 'closed', 'error'). * - **Controle de Fluxo (`source_last`)**: A coluna `source_last` e gerenciada por este modelo para indicar qual sistema realizou a ultima modificacao, prevenindo atualizacoes conflitantes (race conditions). * - **Selecao de Tickets para Processamento**: Metodos como `getTicketsToMonitor`, `getTicketsInErrorState`, e `getTicketsSynced` fornecem listas de tickets para os diferentes controladores processarem, com base em seus status atuais. * - **Criacao e Consulta**: `createSyncRecord` cria o registro inicial de um ticket, e varias funcoes de busca (`getBySysId`, `getIdSyncByGlpiId`, etc.) permitem localizar registros de sincronizacao. * * A tabela `ticket_sync` e a fonte da verdade para o estado da integracao de cada ticket individualmente. */ module.exports = TicketSyncModel;