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_USERNAME=SOTHIS.CAOA
|
||||||
SERVICENOW_PASSWORD=#Sothis20
|
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 access via DB (use DB connection instead of API)
|
||||||
GLPI_DB_TYPE=mysql # ou postgresql
|
GLPI_DB_TYPE=mysql # ou postgresql
|
||||||
GLPI_DB_HOST=localhost
|
GLPI_DB_HOST=177.73.177.32
|
||||||
GLPI_DB_PORT=3306
|
GLPI_DB_PORT=3306
|
||||||
GLPI_DB_USER=glpi_user
|
GLPI_DB_USER=snglpi
|
||||||
GLPI_DB_PASSWORD=your_glpi_db_password
|
GLPI_DB_PASSWORD=j2633669
|
||||||
GLPI_DB_NAME=glpi_database
|
GLPI_DB_NAME=glpi_data
|
||||||
GLPI_DB_CHARSET=utf8mb4
|
GLPI_DB_CHARSET=utf8mb4
|
||||||
GLPI_TABLE_PREFIX=glpi_ # deixe em branco se não houver prefixo
|
|
||||||
|
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
|
|||||||
126
package-lock.json
generated
126
package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"axios": "^0.21.4",
|
"axios": "^0.21.4",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"mysql2": "^3.14.5",
|
||||||
"pg": "^8.7.1",
|
"pg": "^8.7.1",
|
||||||
"winston": "^3.17.0"
|
"winston": "^3.17.0"
|
||||||
}
|
}
|
||||||
@ -61,6 +62,15 @@
|
|||||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/axios": {
|
||||||
"version": "0.21.4",
|
"version": "0.21.4",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
"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": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@ -445,6 +464,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||||
@ -582,6 +610,12 @@
|
|||||||
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/is-stream": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||||
@ -617,6 +651,36 @@
|
|||||||
"node": ">= 12.0.0"
|
"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": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@ -674,6 +738,54 @@
|
|||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/negotiator": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||||
@ -1011,6 +1123,11 @@
|
|||||||
"node": ">= 18"
|
"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": {
|
"node_modules/serve-static": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
||||||
@ -1122,6 +1239,15 @@
|
|||||||
"node": ">= 10.x"
|
"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": {
|
"node_modules/stack-trace": {
|
||||||
"version": "0.0.10",
|
"version": "0.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
"axios": "^0.21.4",
|
"axios": "^0.21.4",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
|
"mysql2": "^3.14.5",
|
||||||
"pg": "^8.7.1",
|
"pg": "^8.7.1",
|
||||||
"winston": "^3.17.0"
|
"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 { snServices } = require('./controllers/servicenowController');
|
||||||
|
const { glpiServices } = require('./controllers/glpiController'); // NOVO
|
||||||
const { logInfo } = require('./utils/logger');
|
const { logInfo } = require('./utils/logger');
|
||||||
|
|
||||||
logInfo('Aplicação iniciada', {
|
logInfo('Aplicação iniciada', {
|
||||||
@ -6,5 +8,14 @@ logInfo('Aplicação iniciada', {
|
|||||||
environment: process.env.NODE_ENV || 'development'
|
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 { logInfo, logError, logWarning } = require('../utils/logger');
|
||||||
|
|
||||||
const glpiPool = mysql.createPool({
|
const glpiPool = mysql.createPool({
|
||||||
host: '177.73.177.32',
|
host: process.env.GLPI_DB_HOST,
|
||||||
port: 3306,
|
port: process.env.GLPI_DB_PORT,
|
||||||
database: process.env.GLPI_DB_NAME,
|
database: process.env.GLPI_DB_NAME,
|
||||||
user: 'snglpi',
|
user: process.env.GLPI_DB_USER,
|
||||||
password: 'j2633669',
|
password: process.env.GLPI_DB_PASSWORD,
|
||||||
connectionLimit: 10,
|
|
||||||
acquireTimeout: 60000,
|
|
||||||
timeout: 60000
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Testar conexão
|
// Testar conexão
|
||||||
|
|||||||
@ -3,6 +3,7 @@ const glpiPool = require('../data/glpiDataBase');
|
|||||||
const { logInfo, logError } = require('../utils/logger');
|
const { logInfo, logError } = require('../utils/logger');
|
||||||
|
|
||||||
class TicketGlpiModel {
|
class TicketGlpiModel {
|
||||||
|
|
||||||
// Mapeamento de status ServiceNow → GLPI
|
// Mapeamento de status ServiceNow → GLPI
|
||||||
static mapStatus(snState) {
|
static mapStatus(snState) {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
@ -38,69 +39,71 @@ class TicketGlpiModel {
|
|||||||
return categoryMap[shortDescription] || 5717;
|
return categoryMap[shortDescription] || 5717;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Criar ticket no GLPI
|
// Criar ticket no GLPI
|
||||||
static async createTicket(snTicket, mapping) {
|
// src/models/ticketGlpiModel.js
|
||||||
|
|
||||||
|
static async createTicket(ticketData) {
|
||||||
const connection = await glpiPool.getConnection();
|
const connection = await glpiPool.getConnection();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await connection.beginTransaction();
|
await connection.beginTransaction();
|
||||||
|
|
||||||
// 1. Preparar dados para o GLPI
|
// 1. Preparar dados para o GLPI (usando valores fixos para teste)
|
||||||
const glpiTicketData = {
|
const glpiTicketData = {
|
||||||
entities_id: mapping.entityId || 1371,
|
entities_id: 1371, // Valor fixo para teste
|
||||||
name: snTicket.short_description?.value || 'Sem título',
|
name: ticketData.short_description || 'Sem título',
|
||||||
date: snTicket.sys_created_on?.value ? new Date(snTicket.sys_created_on.value) : new Date(),
|
date: ticketData.opened_at || new Date(),
|
||||||
date_mod: snTicket.sys_updated_on?.value ? new Date(snTicket.sys_updated_on.value) : new Date(),
|
date_mod: ticketData.updated_at || new Date(),
|
||||||
status: this.mapStatus(snTicket.state?.value),
|
status: TicketGlpiModel.mapStatus(ticketData.state || '1'),
|
||||||
users_id_recipient: mapping.userId || 917,
|
users_id_recipient: 917, // Valor fixo para teste
|
||||||
requesttypes_id: 1,
|
requesttypes_id: 1,
|
||||||
content: snTicket.description?.value || '',
|
content: ticketData.description || '',
|
||||||
urgency: this.mapUrgency(snTicket.urgency?.value),
|
urgency: 3, // Valor fixo
|
||||||
impact: this.mapImpact(snTicket.impact?.value),
|
impact: 3, // Valor fixo
|
||||||
priority: this.mapPriority(snTicket.priority?.value),
|
priority: TicketGlpiModel.mapPriority(ticketData.priority || '3'),
|
||||||
type: 1, // Incidente
|
type: 1, // Incidente
|
||||||
itilcategories_id: this.mapCategory(snTicket.short_description?.value),
|
itilcategories_id: TicketGlpiModel.mapCategory(ticketData.short_description),
|
||||||
global_validation: 1,
|
global_validation: 1,
|
||||||
date_creation: snTicket.sys_created_on?.value ? new Date(snTicket.sys_created_on.value) : new Date()
|
date_creation: ticketData.opened_at || new Date()
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. Inserir ticket principal
|
await connection.execute(requesterQuery, [ticketId, 917]); // Default user
|
||||||
const ticketQuery = `
|
// Construir a query manualmente
|
||||||
INSERT INTO glpi_tickets SET ?
|
const fields = Object.keys(glpiTicketData).join(', ');
|
||||||
`;
|
const placeholders = Object.keys(glpiTicketData).map(() => '?').join(', ');
|
||||||
|
const values = Object.values(glpiTicketData);
|
||||||
|
|
||||||
const [ticketResult] = await connection.execute(ticketQuery, [glpiTicketData]);
|
const ticketQuery = `INSERT INTO glpi_tickets (${fields}) VALUES (${placeholders})`;
|
||||||
|
|
||||||
|
// Inserir ticket principal
|
||||||
|
const [ticketResult] = await connection.execute(ticketQuery, values);
|
||||||
const ticketId = ticketResult.insertId;
|
const ticketId = ticketResult.insertId;
|
||||||
|
|
||||||
// 3. Adicionar requerente
|
// Adicionar requerente
|
||||||
const requesterQuery = `
|
await connection.execute(
|
||||||
INSERT INTO glpi_tickets_users
|
`INSERT INTO glpi_tickets_users
|
||||||
(tickets_id, users_id, type, use_notification, alternative_email)
|
(tickets_id, users_id, type, use_notification, alternative_email)
|
||||||
VALUES (?, ?, 1, 1, '')
|
VALUES (?, ?, 1, 1, '')`,
|
||||||
`;
|
[ticketId, 917]
|
||||||
await connection.execute(requesterQuery, [ticketId, mapping.userId || 917]);
|
);
|
||||||
|
|
||||||
// 4. Adicionar grupo
|
// Adicionar grupo
|
||||||
const groupQuery = `
|
await connection.execute(
|
||||||
INSERT INTO glpi_groups_tickets
|
`INSERT INTO glpi_groups_tickets
|
||||||
(tickets_id, groups_id, type)
|
(tickets_id, groups_id, type)
|
||||||
VALUES (?, ?, 2)
|
VALUES (?, ?, 2)`,
|
||||||
`;
|
[ticketId, 30]
|
||||||
await connection.execute(groupQuery, [ticketId, mapping.groupId || 30]);
|
);
|
||||||
|
|
||||||
await connection.commit();
|
await connection.commit();
|
||||||
|
|
||||||
logInfo(`✅ Ticket GLPI criado: ${ticketId}`, {
|
logInfo(`✅ Ticket GLPI criado: ${ticketId}`);
|
||||||
glpi_ticket_id: ticketId,
|
|
||||||
sn_ticket_number: snTicket.number?.value,
|
|
||||||
location: snTicket.location?.display_value
|
|
||||||
});
|
|
||||||
|
|
||||||
return ticketId;
|
return ticketId;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await connection.rollback();
|
await connection.rollback();
|
||||||
logError(error, `❌ Erro ao criar ticket GLPI para ${snTicket.number?.value}`);
|
logError(error, `❌ Erro ao criar ticket GLPI`);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
connection.release();
|
connection.release();
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
// src/models/ticketSnModel.js
|
||||||
const pool = require('../data/database');
|
const pool = require('../data/database');
|
||||||
const { logInfo, logError, logWarning } = require('../utils/logger');
|
const { logInfo, logError, logWarning } = require('../utils/logger');
|
||||||
|
|
||||||
@ -6,37 +7,40 @@ class TicketSnModel {
|
|||||||
static async saveTicket(ticketData, typeTicket) {
|
static async saveTicket(ticketData, typeTicket) {
|
||||||
const query = `
|
const query = `
|
||||||
INSERT INTO tickets_sn
|
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)
|
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)
|
ON CONFLICT (ticket_number)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
short_description = EXCLUDED.short_description,
|
short_description = EXCLUDED.short_description,
|
||||||
|
status = EXCLUDED.status,
|
||||||
description = EXCLUDED.description,
|
description = EXCLUDED.description,
|
||||||
location_id = EXCLUDED.location_id,
|
|
||||||
location_name = EXCLUDED.location_name,
|
|
||||||
caller_id = EXCLUDED.caller_id,
|
caller_id = EXCLUDED.caller_id,
|
||||||
caller_email = EXCLUDED.caller_email,
|
caller_email = EXCLUDED.caller_email,
|
||||||
|
location_id = EXCLUDED.location_id,
|
||||||
|
location_name = EXCLUDED.location_name,
|
||||||
ramal = EXCLUDED.ramal,
|
ramal = EXCLUDED.ramal,
|
||||||
telefone = EXCLUDED.telefone,
|
telefone = EXCLUDED.telefone,
|
||||||
|
opened_at = EXCLUDED.opened_at,
|
||||||
|
tipo = EXCLUDED.tipo,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
RETURNING id, xmax = 0 as inserted`;
|
RETURNING id, xmax = 0 as inserted`;
|
||||||
|
|
||||||
const values = [
|
const values = [
|
||||||
ticketData.number?.value || null,
|
ticketData.number?.value || null, // $1 - ticket_number
|
||||||
ticketData.short_description?.value || null,
|
ticketData.short_description?.value || null, // $2 - short_description
|
||||||
ticketData.description?.value || null,
|
ticketData.state?.display_value || null, // $3 - status
|
||||||
ticketData.opened_by?.display_value || null,
|
ticketData.description?.value || null, // $4 - description
|
||||||
ticketData['opened_by.email']?.value || null,
|
ticketData.opened_by?.display_value || null, // $5 - caller_id
|
||||||
ticketData.location?.value || null, // location_id (value)
|
ticketData['opened_by.email']?.value || null, // $6 - caller_email
|
||||||
ticketData.location?.display_value || null, // location_name (display_value)
|
ticketData.location?.value || null, // $7 - location_id
|
||||||
ticketData['variables.numero_ramal']?.value || null,
|
ticketData.location?.display_value || null, // $8 - location_name
|
||||||
ticketData['variables.telephone_favorecido_rh']?.value || null,
|
ticketData['variables.numero_ramal']?.value || null, // $9 - ramal
|
||||||
ticketData.opened_at?.value ? new Date(ticketData.opened_at.value) : null,
|
ticketData['variables.telephone_favorecido_rh']?.value || null, // $10 - telefone
|
||||||
typeTicket
|
ticketData.opened_at?.value ? new Date(ticketData.opened_at.value) : null, // $11 - opened_at
|
||||||
|
typeTicket // $12 - tipo
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(query, values);
|
const result = await pool.query(query, values);
|
||||||
const wasInserted = result.rows[0].inserted; // true = INSERT, false = UPDATE
|
const wasInserted = result.rows[0].inserted; // true = INSERT, false = UPDATE
|
||||||
@ -62,6 +66,116 @@ class TicketSnModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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;
|
module.exports = TicketSnModel;
|
||||||
@ -3,6 +3,7 @@ CREATE TABLE tickets_sn (
|
|||||||
ticket_number VARCHAR(32) NOT NULL UNIQUE, -- Número do ticket (INC0140378) - ÚNICO
|
ticket_number VARCHAR(32) NOT NULL UNIQUE, -- Número do ticket (INC0140378) - ÚNICO
|
||||||
short_description TEXT, -- Resumo do problema
|
short_description TEXT, -- Resumo do problema
|
||||||
tipo VARCHAR(10), -- Requisição ou incidente
|
tipo VARCHAR(10), -- Requisição ou incidente
|
||||||
|
status VARCHAR(55), -- Resumo do problema
|
||||||
description TEXT, -- Descrição completa
|
description TEXT, -- Descrição completa
|
||||||
caller_id VARCHAR(255), -- Nome de quem abriu o ticket
|
caller_id VARCHAR(255), -- Nome de quem abriu o ticket
|
||||||
caller_email VARCHAR(255), -- Email do solicitante
|
caller_email VARCHAR(255), -- Email do solicitante
|
||||||
@ -12,9 +13,14 @@ CREATE TABLE tickets_sn (
|
|||||||
telefone VARCHAR(100), -- Telefone do solicitante
|
telefone VARCHAR(100), -- Telefone do solicitante
|
||||||
opened_at TIMESTAMP, -- Quando o ticket foi aberto
|
opened_at TIMESTAMP, -- Quando o ticket foi aberto
|
||||||
created_at TIMESTAMP DEFAULT NOW(), -- Quando registramos no BD
|
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;
|
||||||
|
|
||||||
|
SELECT * FROM tickets_sn LIMIT 3;
|
||||||
|
|
||||||
|
|||||||
@ -1,57 +1,92 @@
|
|||||||
// src/services/glpiService.js
|
// 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');
|
class GlpiService {
|
||||||
const { GLPI_URL, GLPI_APP_TOKEN, GLPI_USER_TOKEN } = process.env;
|
|
||||||
|
|
||||||
// Function to create a ticket in GLPI
|
// Método principal que o controller vai chamar
|
||||||
const createTicketInGLPI = async (ticketData) => {
|
static async syncTicketsToGlpi() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(`${GLPI_URL}/ticket`, ticketData, {
|
logInfo('🔄 Iniciando sincronização para GLPI...');
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
// 1. Buscar tickets pendentes usando SEU MODEL
|
||||||
'App-Token': GLPI_APP_TOKEN,
|
const pendingTickets = await TicketSnModel.getPendingTickets();
|
||||||
'Authorization': `Bearer ${GLPI_USER_TOKEN}`
|
|
||||||
|
if (pendingTickets.length === 0) {
|
||||||
|
logInfo('✅ Nenhum ticket pendente para sincronizar');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
return response.data;
|
logInfo(`📋 Encontrados ${pendingTickets.length} tickets para sincronizar`);
|
||||||
|
|
||||||
|
// 2. Processar cada ticket
|
||||||
|
for (const ticket of pendingTickets) {
|
||||||
|
await this.processSingleTicket(ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo('🎉 Sincronização com GLPI concluída!');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error creating ticket in GLPI: ${error.message}`);
|
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
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to update a ticket in GLPI
|
// 3. Inserir no GLPI (O MODEL faz INSERT e mapeamento)
|
||||||
const updateTicketInGLPI = async (ticketId, ticketData) => {
|
const glpiTicketId = await TicketGlpiModel.createTicket(glpiData);
|
||||||
try {
|
|
||||||
const response = await axios.put(`${GLPI_URL}/ticket/${ticketId}`, ticketData, {
|
// 4. Atualizar status no PostgreSQL usando SEU MODEL
|
||||||
headers: {
|
await TicketSnModel.updateSyncStatus(ticket.id, 'completed', glpiTicketId);
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'App-Token': GLPI_APP_TOKEN,
|
logInfo(`✅ Ticket ${ticket.ticket_number} sincronizado! GLPI ID: ${glpiTicketId}`);
|
||||||
'Authorization': `Bearer ${GLPI_USER_TOKEN}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Error updating ticket in GLPI: ${error.message}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error fetching ticket from GLPI: ${error.message}`);
|
logError(error, `❌ Erro no ticket ${ticket.ticket_number}`);
|
||||||
|
await TicketSnModel.updateSyncStatus(ticket.id, 'error');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncTicketsToGlpi = async () => {
|
||||||
|
await GlpiService.syncTicketsToGlpi();
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createTicketInGLPI,
|
syncTicketsToGlpi,
|
||||||
updateTicketInGLPI,
|
GlpiService
|
||||||
getTicketFromGLPI
|
|
||||||
};
|
};
|
||||||
Loading…
Reference in New Issue
Block a user