WIP: Model do GLPI em construção
Adicionados dados do banco ao .ENV Erros de sintaxe persistem
This commit is contained in:
parent
9ca4a27a81
commit
40d0b2f4c6
15
.env
15
.env
@ -8,20 +8,15 @@ SERVICENOW_REQUEST_URL="https://caoadev.service-now.com/api/now/table/sc_req_ite
|
||||
SERVICENOW_USERNAME=SOTHIS.CAOA
|
||||
SERVICENOW_PASSWORD=#Sothis20
|
||||
|
||||
# GLPI API Configuration
|
||||
GLPI_URL=https://your-glpi-instance/apirest.php/
|
||||
GLPI_APP_TOKEN=your_glpi_app_token
|
||||
GLPI_USER_TOKEN=your_glpi_user_token
|
||||
|
||||
# GLPI access via DB (use DB connection instead of API)
|
||||
GLPI_DB_TYPE=mysql # ou postgresql
|
||||
GLPI_DB_HOST=localhost
|
||||
GLPI_DB_HOST=177.73.177.32
|
||||
GLPI_DB_PORT=3306
|
||||
GLPI_DB_USER=glpi_user
|
||||
GLPI_DB_PASSWORD=your_glpi_db_password
|
||||
GLPI_DB_NAME=glpi_database
|
||||
GLPI_DB_USER=snglpi
|
||||
GLPI_DB_PASSWORD=j2633669
|
||||
GLPI_DB_NAME=glpi_data
|
||||
GLPI_DB_CHARSET=utf8mb4
|
||||
GLPI_TABLE_PREFIX=glpi_ # deixe em branco se não houver prefixo
|
||||
|
||||
|
||||
# Database Configuration
|
||||
DB_HOST=localhost
|
||||
|
||||
126
package-lock.json
generated
126
package-lock.json
generated
@ -12,6 +12,7 @@
|
||||
"axios": "^0.21.4",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "^5.1.0",
|
||||
"mysql2": "^3.14.5",
|
||||
"pg": "^8.7.1",
|
||||
"winston": "^3.17.0"
|
||||
}
|
||||
@ -61,6 +62,15 @@
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-ssl-profiles": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
||||
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
@ -229,6 +239,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@ -445,6 +464,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/generate-function": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-property": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@ -582,6 +610,12 @@
|
||||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-property": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
@ -617,6 +651,36 @@
|
||||
"node": ">= 12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "7.18.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/lru.min": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz",
|
||||
"integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"bun": ">=1.0.0",
|
||||
"deno": ">=1.30.0",
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wellwelwel"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@ -674,6 +738,54 @@
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mysql2": {
|
||||
"version": "3.14.5",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.14.5.tgz",
|
||||
"integrity": "sha512-40hDf8LPUsuuJ2hFq+UgOuPwt2IFLIRDvMv6ez9hKbXeYuZPxDDwiJW7KdknvOsQqKznaKczOT1kELgFkhDvFg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aws-ssl-profiles": "^1.1.1",
|
||||
"denque": "^2.1.0",
|
||||
"generate-function": "^2.3.1",
|
||||
"iconv-lite": "^0.7.0",
|
||||
"long": "^5.2.1",
|
||||
"lru.min": "^1.0.0",
|
||||
"named-placeholders": "^1.1.3",
|
||||
"seq-queue": "^0.0.5",
|
||||
"sqlstring": "^2.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mysql2/node_modules/iconv-lite": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
|
||||
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/named-placeholders": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
|
||||
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lru-cache": "^7.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||
@ -1011,6 +1123,11 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/seq-queue": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
|
||||
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
||||
@ -1122,6 +1239,15 @@
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/sqlstring": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
|
||||
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-trace": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
"axios": "^0.21.4",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "^5.1.0",
|
||||
"mysql2": "^3.14.5",
|
||||
"pg": "^8.7.1",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
|
||||
13
src/app.js
13
src/app.js
@ -1,4 +1,6 @@
|
||||
// src/app.js
|
||||
const { snServices } = require('./controllers/servicenowController');
|
||||
const { glpiServices } = require('./controllers/glpiController'); // NOVO
|
||||
const { logInfo } = require('./utils/logger');
|
||||
|
||||
logInfo('Aplicação iniciada', {
|
||||
@ -6,5 +8,14 @@ logInfo('Aplicação iniciada', {
|
||||
environment: process.env.NODE_ENV || 'development'
|
||||
});
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await snServices();
|
||||
//await glpiServices();
|
||||
|
||||
snServices();
|
||||
} catch (error) {
|
||||
logInfo('Erro na aplicação:', error);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@ -0,0 +1,10 @@
|
||||
// src/controllers/glpiController.js
|
||||
const { syncTicketsToGlpi } = require('../services/glpiService');
|
||||
|
||||
const glpiServices = async () => {
|
||||
await syncTicketsToGlpi();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
glpiServices
|
||||
}
|
||||
@ -4,14 +4,11 @@ const mysql = require('mysql2/promise');
|
||||
const { logInfo, logError, logWarning } = require('../utils/logger');
|
||||
|
||||
const glpiPool = mysql.createPool({
|
||||
host: '177.73.177.32',
|
||||
port: 3306,
|
||||
host: process.env.GLPI_DB_HOST,
|
||||
port: process.env.GLPI_DB_PORT,
|
||||
database: process.env.GLPI_DB_NAME,
|
||||
user: 'snglpi',
|
||||
password: 'j2633669',
|
||||
connectionLimit: 10,
|
||||
acquireTimeout: 60000,
|
||||
timeout: 60000
|
||||
user: process.env.GLPI_DB_USER,
|
||||
password: process.env.GLPI_DB_PASSWORD,
|
||||
});
|
||||
|
||||
// Testar conexão
|
||||
|
||||
@ -3,6 +3,7 @@ const glpiPool = require('../data/glpiDataBase');
|
||||
const { logInfo, logError } = require('../utils/logger');
|
||||
|
||||
class TicketGlpiModel {
|
||||
|
||||
// Mapeamento de status ServiceNow → GLPI
|
||||
static mapStatus(snState) {
|
||||
const statusMap = {
|
||||
@ -38,74 +39,76 @@ class TicketGlpiModel {
|
||||
return categoryMap[shortDescription] || 5717;
|
||||
}
|
||||
|
||||
|
||||
// Criar ticket no GLPI
|
||||
static async createTicket(snTicket, mapping) {
|
||||
const connection = await glpiPool.getConnection();
|
||||
// src/models/ticketGlpiModel.js
|
||||
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
static async createTicket(ticketData) {
|
||||
const connection = await glpiPool.getConnection();
|
||||
|
||||
// 1. Preparar dados para o GLPI
|
||||
const glpiTicketData = {
|
||||
entities_id: mapping.entityId || 1371,
|
||||
name: snTicket.short_description?.value || 'Sem título',
|
||||
date: snTicket.sys_created_on?.value ? new Date(snTicket.sys_created_on.value) : new Date(),
|
||||
date_mod: snTicket.sys_updated_on?.value ? new Date(snTicket.sys_updated_on.value) : new Date(),
|
||||
status: this.mapStatus(snTicket.state?.value),
|
||||
users_id_recipient: mapping.userId || 917,
|
||||
requesttypes_id: 1,
|
||||
content: snTicket.description?.value || '',
|
||||
urgency: this.mapUrgency(snTicket.urgency?.value),
|
||||
impact: this.mapImpact(snTicket.impact?.value),
|
||||
priority: this.mapPriority(snTicket.priority?.value),
|
||||
type: 1, // Incidente
|
||||
itilcategories_id: this.mapCategory(snTicket.short_description?.value),
|
||||
global_validation: 1,
|
||||
date_creation: snTicket.sys_created_on?.value ? new Date(snTicket.sys_created_on.value) : new Date()
|
||||
};
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
// 2. Inserir ticket principal
|
||||
const ticketQuery = `
|
||||
INSERT INTO glpi_tickets SET ?
|
||||
`;
|
||||
// 1. Preparar dados para o GLPI (usando valores fixos para teste)
|
||||
const glpiTicketData = {
|
||||
entities_id: 1371, // Valor fixo para teste
|
||||
name: ticketData.short_description || 'Sem título',
|
||||
date: ticketData.opened_at || new Date(),
|
||||
date_mod: ticketData.updated_at || new Date(),
|
||||
status: TicketGlpiModel.mapStatus(ticketData.state || '1'),
|
||||
users_id_recipient: 917, // Valor fixo para teste
|
||||
requesttypes_id: 1,
|
||||
content: ticketData.description || '',
|
||||
urgency: 3, // Valor fixo
|
||||
impact: 3, // Valor fixo
|
||||
priority: TicketGlpiModel.mapPriority(ticketData.priority || '3'),
|
||||
type: 1, // Incidente
|
||||
itilcategories_id: TicketGlpiModel.mapCategory(ticketData.short_description),
|
||||
global_validation: 1,
|
||||
date_creation: ticketData.opened_at || new Date()
|
||||
};
|
||||
|
||||
const [ticketResult] = await connection.execute(ticketQuery, [glpiTicketData]);
|
||||
const ticketId = ticketResult.insertId;
|
||||
await connection.execute(requesterQuery, [ticketId, 917]); // Default user
|
||||
// Construir a query manualmente
|
||||
const fields = Object.keys(glpiTicketData).join(', ');
|
||||
const placeholders = Object.keys(glpiTicketData).map(() => '?').join(', ');
|
||||
const values = Object.values(glpiTicketData);
|
||||
|
||||
// 3. Adicionar requerente
|
||||
const requesterQuery = `
|
||||
INSERT INTO glpi_tickets_users
|
||||
const ticketQuery = `INSERT INTO glpi_tickets (${fields}) VALUES (${placeholders})`;
|
||||
|
||||
// Inserir ticket principal
|
||||
const [ticketResult] = await connection.execute(ticketQuery, values);
|
||||
const ticketId = ticketResult.insertId;
|
||||
|
||||
// Adicionar requerente
|
||||
await connection.execute(
|
||||
`INSERT INTO glpi_tickets_users
|
||||
(tickets_id, users_id, type, use_notification, alternative_email)
|
||||
VALUES (?, ?, 1, 1, '')
|
||||
`;
|
||||
await connection.execute(requesterQuery, [ticketId, mapping.userId || 917]);
|
||||
VALUES (?, ?, 1, 1, '')`,
|
||||
[ticketId, 917]
|
||||
);
|
||||
|
||||
// 4. Adicionar grupo
|
||||
const groupQuery = `
|
||||
INSERT INTO glpi_groups_tickets
|
||||
// Adicionar grupo
|
||||
await connection.execute(
|
||||
`INSERT INTO glpi_groups_tickets
|
||||
(tickets_id, groups_id, type)
|
||||
VALUES (?, ?, 2)
|
||||
`;
|
||||
await connection.execute(groupQuery, [ticketId, mapping.groupId || 30]);
|
||||
VALUES (?, ?, 2)`,
|
||||
[ticketId, 30]
|
||||
);
|
||||
|
||||
await connection.commit();
|
||||
await connection.commit();
|
||||
|
||||
logInfo(`✅ Ticket GLPI criado: ${ticketId}`, {
|
||||
glpi_ticket_id: ticketId,
|
||||
sn_ticket_number: snTicket.number?.value,
|
||||
location: snTicket.location?.display_value
|
||||
});
|
||||
logInfo(`✅ Ticket GLPI criado: ${ticketId}`);
|
||||
return ticketId;
|
||||
|
||||
return ticketId;
|
||||
|
||||
} catch (error) {
|
||||
await connection.rollback();
|
||||
logError(error, `❌ Erro ao criar ticket GLPI para ${snTicket.number?.value}`);
|
||||
throw error;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
} catch (error) {
|
||||
await connection.rollback();
|
||||
logError(error, `❌ Erro ao criar ticket GLPI`);
|
||||
throw error;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar se ticket já existe no GLPI
|
||||
static async ticketExists(snTicketNumber) {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// src/models/ticketSnModel.js
|
||||
const pool = require('../data/database');
|
||||
const { logInfo, logError, logWarning } = require('../utils/logger');
|
||||
|
||||
@ -6,62 +7,175 @@ class TicketSnModel {
|
||||
static async saveTicket(ticketData, typeTicket) {
|
||||
const query = `
|
||||
INSERT INTO tickets_sn
|
||||
(ticket_number, short_description, description, caller_id, caller_email,
|
||||
(ticket_number, 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)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
ON CONFLICT (ticket_number)
|
||||
DO UPDATE SET
|
||||
short_description = EXCLUDED.short_description,
|
||||
status = EXCLUDED.status,
|
||||
description = EXCLUDED.description,
|
||||
location_id = EXCLUDED.location_id,
|
||||
location_name = EXCLUDED.location_name,
|
||||
caller_id = EXCLUDED.caller_id,
|
||||
caller_email = EXCLUDED.caller_email,
|
||||
location_id = EXCLUDED.location_id,
|
||||
location_name = EXCLUDED.location_name,
|
||||
ramal = EXCLUDED.ramal,
|
||||
telefone = EXCLUDED.telefone,
|
||||
opened_at = EXCLUDED.opened_at,
|
||||
tipo = EXCLUDED.tipo,
|
||||
updated_at = NOW()
|
||||
RETURNING id, xmax = 0 as inserted`;
|
||||
|
||||
const values = [
|
||||
ticketData.number?.value || null,
|
||||
ticketData.short_description?.value || null,
|
||||
ticketData.description?.value || null,
|
||||
ticketData.opened_by?.display_value || null,
|
||||
ticketData['opened_by.email']?.value || null,
|
||||
ticketData.location?.value || null, // location_id (value)
|
||||
ticketData.location?.display_value || null, // location_name (display_value)
|
||||
ticketData['variables.numero_ramal']?.value || null,
|
||||
ticketData['variables.telephone_favorecido_rh']?.value || null,
|
||||
ticketData.opened_at?.value ? new Date(ticketData.opened_at.value) : null,
|
||||
typeTicket
|
||||
ticketData.number?.value || null, // $1 - ticket_number
|
||||
ticketData.short_description?.value || null, // $2 - short_description
|
||||
ticketData.state?.display_value || null, // $3 - status
|
||||
ticketData.description?.value || null, // $4 - description
|
||||
ticketData.opened_by?.display_value || null, // $5 - caller_id
|
||||
ticketData['opened_by.email']?.value || null, // $6 - caller_email
|
||||
ticketData.location?.value || null, // $7 - location_id
|
||||
ticketData.location?.display_value || null, // $8 - location_name
|
||||
ticketData['variables.numero_ramal']?.value || null, // $9 - ramal
|
||||
ticketData['variables.telephone_favorecido_rh']?.value || null, // $10 - telefone
|
||||
ticketData.opened_at?.value ? new Date(ticketData.opened_at.value) : null, // $11 - opened_at
|
||||
typeTicket // $12 - tipo
|
||||
];
|
||||
|
||||
try {
|
||||
const result = await pool.query(query, values);
|
||||
const wasInserted = result.rows[0].inserted; // true = INSERT, false = UPDATE
|
||||
|
||||
try {
|
||||
const result = await pool.query(query, values);
|
||||
const wasInserted = result.rows[0].inserted; // true = INSERT, false = UPDATE
|
||||
if (wasInserted) {
|
||||
logInfo(`✅ NOVO ticket salvo! ID: ${result.rows[0].id}`, {
|
||||
ticket_id: result.rows[0].id,
|
||||
ticket_number: values[0],
|
||||
action: 'insert'
|
||||
});
|
||||
} else {
|
||||
logInfo(`🔄 Ticket atualizado! ID: ${result.rows[0].id}`, {
|
||||
ticket_id: result.rows[0].id,
|
||||
ticket_number: values[0],
|
||||
action: 'update'
|
||||
});
|
||||
}
|
||||
|
||||
if (wasInserted) {
|
||||
logInfo(`✅ NOVO ticket salvo! ID: ${result.rows[0].id}`, {
|
||||
ticket_id: result.rows[0].id,
|
||||
ticket_number: values[0],
|
||||
action: 'insert'
|
||||
});
|
||||
} else {
|
||||
logInfo(`🔄 Ticket atualizado! ID: ${result.rows[0].id}`, {
|
||||
ticket_id: result.rows[0].id,
|
||||
ticket_number: values[0],
|
||||
action: 'update'
|
||||
});
|
||||
return result.rows[0].id;
|
||||
} catch (error) {
|
||||
logError(error, `❌ Erro ao salvar ticket ${values[0]}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return result.rows[0].id;
|
||||
} catch (error) {
|
||||
logError(error, `❌ Erro ao salvar ticket ${values[0]}`);
|
||||
throw error;
|
||||
// NOVO: Buscar tickets pendentes para sincronização com GLPI
|
||||
static async getPendingTickets(limit = 3) {
|
||||
try {
|
||||
const query = `
|
||||
SELECT
|
||||
id,
|
||||
ticket_number,
|
||||
short_description,
|
||||
description,
|
||||
status,
|
||||
caller_id,
|
||||
caller_email,
|
||||
location_id,
|
||||
location_name,
|
||||
ramal,
|
||||
telefone,
|
||||
opened_at,
|
||||
tipo,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM tickets_sn
|
||||
WHERE glpi_sync_status IS NULL
|
||||
ORDER BY opened_at ASC
|
||||
LIMIT $1
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [limit]);
|
||||
logInfo(`📋 Tickets pendentes encontrados: ${result.rows.length}`);
|
||||
return result.rows;
|
||||
|
||||
} catch (error) {
|
||||
logError(error, '❌ Erro ao buscar tickets pendentes');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// NOVO: Atualizar status de sincronização com GLPI
|
||||
static async updateSyncStatus(ticketId, status, glpiTicketId = null) {
|
||||
try {
|
||||
const query = `
|
||||
UPDATE tickets_sn
|
||||
SET glpi_sync_status = $1,
|
||||
glpi_ticket_id = $2,
|
||||
glpi_sync_date = NOW()
|
||||
WHERE id = $3
|
||||
RETURNING id, ticket_number
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [status, glpiTicketId, ticketId]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
logInfo(`🔄 Status atualizado: ${status} para ticket ${result.rows[0].ticket_number}`, {
|
||||
ticket_id: result.rows[0].id,
|
||||
ticket_number: result.rows[0].ticket_number,
|
||||
glpi_ticket_id: glpiTicketId,
|
||||
status: status
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logError(error, `❌ Erro ao atualizar status de sync para ticket ${ticketId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// NOVO: Resetar status de sync para testes (opcional)
|
||||
static async resetSyncStatus() {
|
||||
try {
|
||||
const query = `
|
||||
UPDATE tickets_sn
|
||||
SET glpi_sync_status = NULL,
|
||||
glpi_ticket_id = NULL,
|
||||
glpi_sync_date = NULL
|
||||
WHERE id IN (
|
||||
SELECT id FROM tickets_sn
|
||||
ORDER BY opened_at DESC
|
||||
LIMIT $1
|
||||
)
|
||||
RETURNING id, ticket_number
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [limit]);
|
||||
logInfo(`🔄 Status resetado para ${result.rows.length} tickets`);
|
||||
return result.rows;
|
||||
|
||||
} catch (error) {
|
||||
logError(error, '❌ Erro ao resetar status de sync');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// NOVO: Verificar status de sincronização
|
||||
static async getSyncStatus() {
|
||||
try {
|
||||
const query = `
|
||||
SELECT
|
||||
glpi_sync_status,
|
||||
COUNT(*) as total,
|
||||
COUNT(glpi_ticket_id) as synchronized
|
||||
FROM tickets_sn
|
||||
GROUP BY glpi_sync_status
|
||||
`;
|
||||
|
||||
const result = await pool.query(query);
|
||||
return result.rows;
|
||||
|
||||
} catch (error) {
|
||||
logError(error, '❌ Erro ao verificar status de sync');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
module.exports = TicketSnModel;
|
||||
@ -3,6 +3,7 @@ CREATE TABLE tickets_sn (
|
||||
ticket_number VARCHAR(32) NOT NULL UNIQUE, -- Número do ticket (INC0140378) - ÚNICO
|
||||
short_description TEXT, -- Resumo do problema
|
||||
tipo VARCHAR(10), -- Requisição ou incidente
|
||||
status VARCHAR(55), -- Resumo do problema
|
||||
description TEXT, -- Descrição completa
|
||||
caller_id VARCHAR(255), -- Nome de quem abriu o ticket
|
||||
caller_email VARCHAR(255), -- Email do solicitante
|
||||
@ -12,9 +13,14 @@ CREATE TABLE tickets_sn (
|
||||
telefone VARCHAR(100), -- Telefone do solicitante
|
||||
opened_at TIMESTAMP, -- Quando o ticket foi aberto
|
||||
created_at TIMESTAMP DEFAULT NOW(), -- Quando registramos no BD
|
||||
updated_at TIMESTAMP DEFAULT NOW() -- Quando atualizamos pela última vez
|
||||
updated_at TIMESTAMP DEFAULT NOW(), -- Quando atualizamos pela última vez
|
||||
glpi_sync_status VARCHAR(20),
|
||||
glpi_ticket_id INT
|
||||
|
||||
);
|
||||
|
||||
|
||||
SELECT * FROM tickets_sn;
|
||||
|
||||
SELECT * FROM tickets_sn LIMIT 3;
|
||||
|
||||
|
||||
@ -1,57 +1,92 @@
|
||||
// src/services/glpiService.js
|
||||
const TicketSnModel = require('../models/ticketSnModel'); // SEU MODEL do PostgreSQL
|
||||
const TicketGlpiModel = require('../models/ticketGlpiModel'); // Model do GLPI
|
||||
const { logInfo, logError } = require('../utils/logger');
|
||||
|
||||
const axios = require('axios');
|
||||
const { GLPI_URL, GLPI_APP_TOKEN, GLPI_USER_TOKEN } = process.env;
|
||||
class GlpiService {
|
||||
|
||||
// Function to create a ticket in GLPI
|
||||
const createTicketInGLPI = async (ticketData) => {
|
||||
try {
|
||||
const response = await axios.post(`${GLPI_URL}/ticket`, ticketData, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'App-Token': GLPI_APP_TOKEN,
|
||||
'Authorization': `Bearer ${GLPI_USER_TOKEN}`
|
||||
// Método principal que o controller vai chamar
|
||||
static async syncTicketsToGlpi() {
|
||||
try {
|
||||
logInfo('🔄 Iniciando sincronização para GLPI...');
|
||||
|
||||
// 1. Buscar tickets pendentes usando SEU MODEL
|
||||
const pendingTickets = await TicketSnModel.getPendingTickets();
|
||||
|
||||
if (pendingTickets.length === 0) {
|
||||
logInfo('✅ Nenhum ticket pendente para sincronizar');
|
||||
return;
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(`Error creating ticket in GLPI: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to update a ticket in GLPI
|
||||
const updateTicketInGLPI = async (ticketId, ticketData) => {
|
||||
try {
|
||||
const response = await axios.put(`${GLPI_URL}/ticket/${ticketId}`, ticketData, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'App-Token': GLPI_APP_TOKEN,
|
||||
'Authorization': `Bearer ${GLPI_USER_TOKEN}`
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(`Error updating ticket in GLPI: ${error.message}`);
|
||||
}
|
||||
};
|
||||
logInfo(`📋 Encontrados ${pendingTickets.length} tickets para sincronizar`);
|
||||
|
||||
// Function to fetch ticket details from GLPI
|
||||
const getTicketFromGLPI = async (ticketId) => {
|
||||
try {
|
||||
const response = await axios.get(`${GLPI_URL}/ticket/${ticketId}`, {
|
||||
headers: {
|
||||
'App-Token': GLPI_APP_TOKEN,
|
||||
'Authorization': `Bearer ${GLPI_USER_TOKEN}`
|
||||
// 2. Processar cada ticket
|
||||
for (const ticket of pendingTickets) {
|
||||
await this.processSingleTicket(ticket);
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
throw new Error(`Error fetching ticket from GLPI: ${error.message}`);
|
||||
|
||||
logInfo('🎉 Sincronização com GLPI concluída!');
|
||||
|
||||
} catch (error) {
|
||||
logError(error, '❌ Erro na sincronização GLPI');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Processar um ticket individual
|
||||
static async processSingleTicket(ticket) {
|
||||
try {
|
||||
logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`);
|
||||
|
||||
// 1. Verificar se já existe no GLPI (evitar duplicatas)
|
||||
const existingTicket = await TicketGlpiModel.ticketExists(ticket.ticket_number);
|
||||
if (existingTicket) {
|
||||
logInfo(`⏭️ Ticket já existe no GLPI: ${existingTicket}`);
|
||||
await TicketSnModel.updateSyncStatus(ticket.id, 'duplicate', existingTicket);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Converter dados para formato GLPI (O MODEL vai fazer o mapeamento)
|
||||
const glpiData = {
|
||||
// Dados básicos do PostgreSQL
|
||||
ticket_number: ticket.ticket_number,
|
||||
short_description: ticket.short_description,
|
||||
description: ticket.description,
|
||||
|
||||
// Dados do ServiceNow originais (se necessário para mapeamento)
|
||||
state: ticket.status, // Já vem do ServiceNow
|
||||
urgency: '2', // Default - O MODEL vai mapear
|
||||
impact: '2', // Default - O MODEL vai mapear
|
||||
priority: '3', // Default - O MODEL vai mapear
|
||||
|
||||
// Localização para de/para
|
||||
location_name: ticket.location_name,
|
||||
|
||||
// Datas
|
||||
opened_at: ticket.opened_at,
|
||||
updated_at: ticket.updated_at
|
||||
};
|
||||
|
||||
// 3. Inserir no GLPI (O MODEL faz INSERT e mapeamento)
|
||||
const glpiTicketId = await TicketGlpiModel.createTicket(glpiData);
|
||||
|
||||
// 4. Atualizar status no PostgreSQL usando SEU MODEL
|
||||
await TicketSnModel.updateSyncStatus(ticket.id, 'completed', glpiTicketId);
|
||||
|
||||
logInfo(`✅ Ticket ${ticket.ticket_number} sincronizado! GLPI ID: ${glpiTicketId}`);
|
||||
|
||||
} catch (error) {
|
||||
logError(error, `❌ Erro no ticket ${ticket.ticket_number}`);
|
||||
await TicketSnModel.updateSyncStatus(ticket.id, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const syncTicketsToGlpi = async () => {
|
||||
await GlpiService.syncTicketsToGlpi();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createTicketInGLPI,
|
||||
updateTicketInGLPI,
|
||||
getTicketFromGLPI
|
||||
syncTicketsToGlpi,
|
||||
GlpiService
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user