FEATURE: Novo Cammpo adicionado!
- Campo de Justificativa adicionado para as requisições
This commit is contained in:
parent
e84647a2ba
commit
a4f6797c8f
@ -39,6 +39,8 @@ GLPI_DB_CHARSET=utf8mb4
|
||||
|
||||
# ID do usuario padrao do GLPI para criacao de tickets e comentarios
|
||||
GLPI_DEFAULT_USER_ID=11111
|
||||
GLPI_REQ_DEFAULT_JUSTIFICATIVA=Chamado aberto por colaborador CAOA sem Justificativa
|
||||
GLPI_REQ_DEFAULT_DESCRIPTION=Chamado aberto por colaborador CAOA sem Descricao
|
||||
|
||||
# Banco intermediario (PostgreSQL)
|
||||
SNGLPI_DB_HOST=10.0.120.75
|
||||
|
||||
@ -44,6 +44,8 @@ GLPI_DB_CHARSET=utf8mb4
|
||||
|
||||
# ID do usuario padrao do GLPI para criacao de tickets e comentarios
|
||||
GLPI_DEFAULT_USER_ID=1118
|
||||
GLPI_REQ_DEFAULT_JUSTIFICATIVA=Chamado aberto por colaborador CAOA sem Justificativa
|
||||
GLPI_REQ_DEFAULT_DESCRIPTION=Chamado aberto por colaborador CAOA sem Descricao
|
||||
|
||||
# --- Configuracao do Banco de Dados Intermediario (PostgreSQL) ---
|
||||
SNGLPI_DB_HOST=localhost
|
||||
|
||||
@ -30,28 +30,28 @@ const processAndSaveTickets = async (tickets, type) => {
|
||||
let latestUpdateTimestamp = new Date(0);
|
||||
for (const ticket of tickets) {
|
||||
try {
|
||||
if (type === 'requisicao' && (!ticket.description || ticket.description.trim() === '')) {
|
||||
logInfo(`Descricao vazia para requisicao ${ticket.number}. Buscando em sc_item_option...`);
|
||||
if (type === 'requisicao') {
|
||||
const ticketNumber = typeof ticket.number === 'object' ? ticket.number?.value : ticket.number;
|
||||
logInfo(`Buscando variaveis de catalogo para requisicao ${ticketNumber}.`);
|
||||
const extraData = await fetchScItemOptionValue(ticket.sys_id, 'justificativa');
|
||||
const vars = {};
|
||||
(extraData || []).forEach(item => {
|
||||
const question = item['sc_item_option.item_option_new.question_text'];
|
||||
const value = item['sc_item_option.value'];
|
||||
|
||||
if (question === 'Select what you want') vars.whatYouWant = value;
|
||||
if (question === 'Please justify the need') vars.justification = value;
|
||||
if (question === 'Telephone') vars.telephone = value;
|
||||
(extraData || []).forEach((item) => {
|
||||
const question = item.question_text;
|
||||
const name = item.variable_name;
|
||||
const value = item.value;
|
||||
|
||||
if (name === 'select_what_you_want' || question === 'Select what you want') vars.whatYouWant = value;
|
||||
if (name === 'please_justify_the_need' || question === 'Please justify the need') vars.justification = value;
|
||||
if (name === 'telephone_favorecido_ti' || question === 'Telephone') vars.telephone = value;
|
||||
});
|
||||
|
||||
|
||||
if (vars.justification) {
|
||||
ticket.description = vars.justification;
|
||||
logInfo(`Descricao da requisicao ${ticket.number} atualizada com valor de sc_item_option.`);
|
||||
}
|
||||
if (vars.telephone){
|
||||
ticket.justificativa = vars.justification || null;
|
||||
if (vars.telephone) {
|
||||
ticket.telefone = vars.telephone;
|
||||
logInfo(`Telefone da requisicao ${ticket.number} atualizado com valor de sc_item_option.`);
|
||||
}
|
||||
|
||||
logInfo(`Variaveis da requisicao ${ticketNumber}: justificativa=${ticket.justificativa ? 'ok' : 'vazia'}, telefone=${ticket.telefone ? 'ok' : 'vazio'}.`);
|
||||
}
|
||||
|
||||
const { id: idTableTicketSn, wasInserted } = await TicketSnModel.saveTicket(ticket, type);
|
||||
|
||||
@ -4,6 +4,8 @@ const LocationMappingModel = require('./locationMappingModel');
|
||||
|
||||
// Carrega o ID do usuário padrão do GLPI a partir das variáveis de ambiente
|
||||
const GLPI_DEFAULT_USER_ID = parseInt(process.env.GLPI_DEFAULT_USER_ID, 10) || 1118;
|
||||
const GLPI_REQ_DEFAULT_JUSTIFICATIVA = process.env.GLPI_REQ_DEFAULT_JUSTIFICATIVA || 'Chamado aberto por colaborador CAOA sem Justificativa';
|
||||
const GLPI_REQ_DEFAULT_DESCRIPTION = process.env.GLPI_REQ_DEFAULT_DESCRIPTION || 'Chamado aberto por colaborador CAOA sem Descricao';
|
||||
|
||||
class TicketGlpiModel {
|
||||
|
||||
@ -118,9 +120,31 @@ class TicketGlpiModel {
|
||||
return extractInfo(/(\d{3,4})/i, description);
|
||||
};
|
||||
|
||||
const email = ticketData.caller_email || extractEmailFromDescription(ticketData.description) || 'N/A';
|
||||
const phone = ticketData.caller_phone || extractPhoneFromDescription(ticketData.description) || 'N/A';
|
||||
const ramal = ticketData.caller_ramal || extractRamalFromDescription(ticketData.description) || 'N/A';
|
||||
const rawDescription = ticketData.description;
|
||||
const isRequest = ticketData.type === 'requisicao';
|
||||
const justificationForDisplay = isRequest
|
||||
? (ticketData.justificativa && ticketData.justificativa.trim()
|
||||
? ticketData.justificativa
|
||||
: GLPI_REQ_DEFAULT_JUSTIFICATIVA)
|
||||
: null;
|
||||
const descriptionForDisplay = isRequest
|
||||
? (rawDescription && rawDescription.trim()
|
||||
? rawDescription
|
||||
: GLPI_REQ_DEFAULT_DESCRIPTION)
|
||||
: (rawDescription || 'N/A');
|
||||
|
||||
const email = ticketData.caller_email || extractEmailFromDescription(rawDescription) || 'N/A';
|
||||
const phone = ticketData.caller_phone || extractPhoneFromDescription(rawDescription) || 'N/A';
|
||||
const ramal = ticketData.caller_ramal || extractRamalFromDescription(rawDescription) || 'N/A';
|
||||
|
||||
const requestExtraHtml = isRequest
|
||||
? `
|
||||
<div style="margin-top: 12px; padding: 12px; border: 1px solid #d9e2ec; border-left: 4px solid #2b6cb0; background-color: #f8fbff;">
|
||||
<div style="font-weight: 700; margin-bottom: 8px;">Justificativa</div>
|
||||
<div style="white-space: pre-wrap;">${justificationForDisplay}</div>
|
||||
</div>
|
||||
`
|
||||
: '';
|
||||
|
||||
let htmlDescription = `
|
||||
<table style="width:100%; border-collapse: collapse;">
|
||||
@ -149,10 +173,11 @@ class TicketGlpiModel {
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">${ticketData.ticket_number || 'N/A'}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
<strong>Descrição:</strong>
|
||||
<hr>
|
||||
${ticketData.description || 'N/A'}
|
||||
${requestExtraHtml}
|
||||
<div style="margin-top: 12px; padding: 12px; border: 1px solid #e2e8f0; border-left: 4px solid #4a5568; background-color: #fcfcfc;">
|
||||
<div style="font-weight: 700; margin-bottom: 8px;">Descricao</div>
|
||||
<div style="white-space: pre-wrap;">${descriptionForDisplay}</div>
|
||||
</div>
|
||||
`;
|
||||
return htmlDescription;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
const pool = require('../data/database');
|
||||
const { logInfo, logError, logWarning } = require('../utils/logger');
|
||||
const { logInfo, logError } = require('../utils/logger');
|
||||
|
||||
class TicketSnModel {
|
||||
|
||||
@ -7,8 +7,8 @@ class TicketSnModel {
|
||||
const query = `
|
||||
INSERT INTO tickets_sn
|
||||
(ticket_number, sys_id, short_description, status, description, caller_id, caller_email,
|
||||
location_id, location_name, ramal, telefone, opened_at, tipo)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
||||
location_id, location_name, ramal, telefone, justificativa, opened_at, tipo)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
||||
ON CONFLICT (ticket_number)
|
||||
DO UPDATE SET
|
||||
sys_id = EXCLUDED.sys_id,
|
||||
@ -21,12 +21,13 @@ class TicketSnModel {
|
||||
location_name = EXCLUDED.location_name,
|
||||
ramal = EXCLUDED.ramal,
|
||||
telefone = EXCLUDED.telefone,
|
||||
justificativa = EXCLUDED.justificativa,
|
||||
opened_at = EXCLUDED.opened_at,
|
||||
tipo = EXCLUDED.tipo
|
||||
RETURNING id, (xmax = 0) AS "wasInserted"`;
|
||||
|
||||
const ticketNumber = typeof ticketData.number === 'object' ? ticketData.number?.value : ticketData.number;
|
||||
let callerName, callerEmail, ramal, telefone, sysId, shortDescription, state, description, locationId, locationName, openedAt;
|
||||
let callerName, callerEmail, ramal, telefone, justificativa, sysId, shortDescription, state, description, locationId, locationName, openedAt;
|
||||
|
||||
|
||||
if (typeTicket === 'incidente') {
|
||||
@ -39,6 +40,7 @@ class TicketSnModel {
|
||||
shortDescription = ticketData.short_description || null;
|
||||
state = ticketData.state || null;
|
||||
description = ticketData.description || null;
|
||||
justificativa = null;
|
||||
const locationLink = ticketData.location?.link;
|
||||
locationId = locationLink ? locationLink.substring(locationLink.lastIndexOf('/') + 1) : null;
|
||||
locationName = ticketData.location?.display_value || null;
|
||||
@ -47,11 +49,12 @@ class TicketSnModel {
|
||||
callerName = ticketData.opened_by?.display_value || null;
|
||||
callerEmail = ticketData['opened_by.email'] || null;
|
||||
ramal = ticketData['variables.numero_ramal'] || null;
|
||||
telefone = ticketData['variables.telephone_favorecido_rh'] || ticketData.telefone || null;
|
||||
telefone = ticketData['variables.telephone_favorecido_rh'] || ticketData['variables.telephone_favorecido_ti'] || ticketData.telefone || null;
|
||||
sysId = ticketData.sys_id || null;
|
||||
shortDescription = ticketData.short_description || null;
|
||||
state = ticketData.state || null;
|
||||
description = ticketData.description || null;
|
||||
justificativa = ticketData.justificativa || null;
|
||||
const locationLink = ticketData.location?.link;
|
||||
locationId = locationLink ? locationLink.substring(locationLink.lastIndexOf('/') + 1) : null;
|
||||
locationName = ticketData.location?.display_value || null;
|
||||
@ -70,6 +73,7 @@ class TicketSnModel {
|
||||
locationName,
|
||||
ramal,
|
||||
telefone,
|
||||
justificativa,
|
||||
openedAt
|
||||
? (typeof openedAt === 'string' && openedAt.includes('/')
|
||||
? new Date(openedAt.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$2-$1'))
|
||||
@ -119,6 +123,7 @@ class TicketSnModel {
|
||||
ts.location_name,
|
||||
ts.ramal,
|
||||
ts.telefone,
|
||||
ts.justificativa,
|
||||
ts.opened_at,
|
||||
ts.tipo,
|
||||
ts.created_at,
|
||||
|
||||
@ -34,6 +34,7 @@ CREATE TABLE tickets_sn (
|
||||
location_name VARCHAR(255), -- Nome da localização no ServiceNow
|
||||
ramal VARCHAR(100), -- Ramal do solicitante
|
||||
telefone VARCHAR(100), -- Telefone do solicitante
|
||||
justificativa TEXT, -- Justificativa da requisicao (catalog variable)
|
||||
opened_at TIMESTAMP, -- Data e hora de abertura no ServiceNow
|
||||
created_at TIMESTAMP DEFAULT NOW(), -- Data e hora de criação do registro
|
||||
updated_at TIMESTAMP DEFAULT NOW() -- Data e hora da última atualização do registro
|
||||
@ -225,6 +226,7 @@ COMMENT ON COLUMN tickets_sn.location_id IS 'ID da localização no ServiceNow';
|
||||
COMMENT ON COLUMN tickets_sn.location_name IS 'Nome da localização no ServiceNow';
|
||||
COMMENT ON COLUMN tickets_sn.ramal IS 'Ramal do solicitante';
|
||||
COMMENT ON COLUMN tickets_sn.telefone IS 'Telefone do solicitante';
|
||||
COMMENT ON COLUMN tickets_sn.justificativa IS 'Justificativa da requisicao vinda das variaveis de catalogo';
|
||||
COMMENT ON COLUMN tickets_sn.opened_at IS 'Data e hora de abertura no ServiceNow';
|
||||
COMMENT ON COLUMN tickets_sn.created_at IS 'Data e hora de criação do registro';
|
||||
COMMENT ON COLUMN tickets_sn.updated_at IS 'Data e hora da última atualização do registro';
|
||||
@ -293,3 +295,8 @@ DELETE FROM tickets_sn WHERE status != 'Encerrado' AND status != 'Encerrado - Om
|
||||
|
||||
UPDATE sync_control
|
||||
SET last_watermark = '1970-01-01 00:00:00+00';
|
||||
|
||||
-- Migracao para bases existentes:
|
||||
ALTER TABLE tickets_sn ADD COLUMN IF NOT EXISTS justificativa TEXT;
|
||||
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ const TicketSnModel = require('../models/ticketSnModel');
|
||||
const TicketGlpiModel = require('../models/ticketGlpiModel');
|
||||
const TicketUpdateModel = require('../models/ticketUpdateModel');
|
||||
const TicketSyncModel = require('../models/ticketSyncModel');
|
||||
const { logInfo, logError, logWarning } = require('../utils/logger');
|
||||
const { logInfo, logError } = require('../utils/logger');
|
||||
const { addWorkNoteToServiceNow } = require('./servicenowService');
|
||||
|
||||
const syncTicketsToGlpi = async () => {
|
||||
@ -61,6 +61,7 @@ const processSingleTicket = async (ticket) => {
|
||||
ticket_number: ticket.ticket_number,
|
||||
short_description: ticket.short_description,
|
||||
description: ticket.description,
|
||||
justificativa: ticket.justificativa,
|
||||
caller_id: ticket.caller_id,
|
||||
caller_email: ticket.caller_email,
|
||||
caller_phone: ticket.telefone,
|
||||
@ -73,18 +74,6 @@ const processSingleTicket = async (ticket) => {
|
||||
opened_at: ticket.opened_at,
|
||||
updated_at: ticket.updated_at
|
||||
};
|
||||
|
||||
if (glpiData.type === 'requisicao' && (!glpiData.description || glpiData.description.trim() === '')) {
|
||||
logInfo(`Descrição vazia para requisição ${ticket.ticket_number}. Tentando buscar de sc_item_option.`);
|
||||
const scItemOptionDescription = await require('./servicenowService').fetchScItemOptionValue(ticket.sys_id, 'justificativa');
|
||||
if (scItemOptionDescription) {
|
||||
glpiData.description = scItemOptionDescription;
|
||||
logInfo(`Descrição atualizada para requisição ${ticket.ticket_number} a partir de sc_item_option.`);
|
||||
} else {
|
||||
logWarning(`Não foi possível obter descrição de sc_item_option para requisição ${ticket.ticket_number}.`);
|
||||
}
|
||||
}
|
||||
|
||||
const glpiTicketId = await TicketGlpiModel.createTicket(glpiData);
|
||||
await TicketSyncModel.updateLastSync(ticket.id, 'glpi');
|
||||
await TicketSyncModel.updateStatusAndGlpiTicket(ticket.id, 'synced', 'synced', glpiTicketId);
|
||||
|
||||
@ -495,14 +495,29 @@ const fetchScItemOptionValue = async (ritmSysId, variableName) => {
|
||||
try {
|
||||
|
||||
const baseUrl = apiConfig.snScItemOptionConfig.baseUrl.replace(/\/$/, "");
|
||||
const url = `${baseUrl}?sysparm_query=request_item=${ritmSysId}^sc_item_option.item_option_new.question_textINPlease justify the need,Telephone,Select what you want&sysparm_fields=sc_item_option.item_option_new.question_text,sc_item_option.value`;
|
||||
const url = `${baseUrl}?sysparm_query=request_item=${ritmSysId}^sc_item_option.item_option_new.question_textINPlease justify the need,Telephone,Select what you want&sysparm_fields=sc_item_option.item_option_new.question_text,sc_item_option.item_option_new.name,sc_item_option.value`;
|
||||
|
||||
const response = await axios.get(url, {
|
||||
auth: apiConfig.servicenowAuthentication.auth
|
||||
});
|
||||
|
||||
const result = response.data.result;
|
||||
return (result && result.length > 0) ? result : null;
|
||||
const result = response.data.result || [];
|
||||
if (result.length === 0) return null;
|
||||
|
||||
const normalize = (value) => {
|
||||
if (value === null || value === undefined) return '';
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'object') {
|
||||
return value.value || value.display_value || '';
|
||||
}
|
||||
return String(value);
|
||||
};
|
||||
|
||||
return result.map((item) => ({
|
||||
question_text: normalize(item['sc_item_option.item_option_new.question_text']),
|
||||
variable_name: normalize(item['sc_item_option.item_option_new.name']),
|
||||
value: normalize(item['sc_item_option.value'])
|
||||
}));
|
||||
} catch (error) {
|
||||
logError(error, `ERRO: Falha ao buscar valor da variavel '${variableName}' para RITM ${ritmSysId}`);
|
||||
return null;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user