329 lines
12 KiB
JavaScript
329 lines
12 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const pool = require('../db');
|
|
const { db, logAttivita } = require('../activityDb');
|
|
|
|
// Helper to fetch details of a list of tickets from OTRS DB
|
|
async function fetchTicketsDetails(ticketIds) {
|
|
if (!ticketIds || ticketIds.length === 0) return [];
|
|
try {
|
|
const placeholders = ticketIds.map((_, i) => `$${i + 1}`).join(', ');
|
|
const query = `
|
|
SELECT
|
|
t.id, t.tn, t.title,
|
|
t.queue_id, q.name AS queue_name,
|
|
t.ticket_state_id, ts.name AS state_name, tst.name AS state_type,
|
|
t.ticket_priority_id, tp.name AS priority_name,
|
|
t.user_id, u.first_name AS owner_first, u.last_name AS owner_last, u.login AS owner_login
|
|
FROM ticket t
|
|
JOIN queue q ON t.queue_id = q.id
|
|
JOIN ticket_state ts ON t.ticket_state_id = ts.id
|
|
JOIN ticket_state_type tst ON ts.type_id = tst.id
|
|
JOIN ticket_priority tp ON t.ticket_priority_id = tp.id
|
|
LEFT JOIN users u ON t.user_id = u.id
|
|
WHERE t.id IN (${placeholders})
|
|
`;
|
|
const res = await pool.query(query, ticketIds);
|
|
return res.rows;
|
|
} catch (err) {
|
|
console.error('Error fetching ticket details from OTRS DB:', err);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// GET /api/groups - List all groups with member counts
|
|
router.get('/', (req, res) => {
|
|
try {
|
|
// Ensure default group exists
|
|
const defaultGroup = db.prepare('SELECT id FROM ticket_groups WHERE UPPER(nome) = ?').get('CHIUDI A FINE GIORNATA');
|
|
if (!defaultGroup) {
|
|
db.prepare(`
|
|
INSERT INTO ticket_groups (nome, descrizione)
|
|
VALUES (?, ?)
|
|
`).run('CHIUDI A FINE GIORNATA', 'I ticket in questo gruppo verranno chiusi automaticamente alla consuntivazione di fine giornata');
|
|
}
|
|
|
|
const groups = db.prepare(`
|
|
SELECT g.*,
|
|
(SELECT COUNT(*) FROM ticket_group_members WHERE group_id = g.id) AS member_count
|
|
FROM ticket_groups g
|
|
ORDER BY CASE WHEN UPPER(g.nome) = 'CHIUDI A FINE GIORNATA' THEN 1 ELSE 0 END ASC, g.nome ASC
|
|
`).all();
|
|
|
|
res.json(groups);
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Errore nel recupero dei gruppi', message: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/groups/by-ticket/:ticket_id - Get groups associated to a specific ticket
|
|
router.get('/by-ticket/:ticket_id', (req, res) => {
|
|
try {
|
|
const { ticket_id } = req.params;
|
|
const ticketIdNum = parseInt(ticket_id, 10);
|
|
if (isNaN(ticketIdNum)) {
|
|
return res.status(400).json({ error: 'ID ticket non valido' });
|
|
}
|
|
|
|
const asMaster = db.prepare('SELECT id, nome, descrizione FROM ticket_groups WHERE master_ticket_id = ?').all(ticketIdNum);
|
|
const asMember = db.prepare(`
|
|
SELECT g.id, g.nome, g.descrizione
|
|
FROM ticket_groups g
|
|
JOIN ticket_group_members m ON g.id = m.group_id
|
|
WHERE m.ticket_id = ?
|
|
`).all(ticketIdNum);
|
|
|
|
res.json({ asMaster, asMember });
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Errore nel recupero dei gruppi del ticket', message: err.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/groups - Create a new group
|
|
router.post('/', (req, res) => {
|
|
try {
|
|
const { nome, descrizione, master_ticket_id } = req.body;
|
|
if (!nome) {
|
|
return res.status(400).json({ error: 'Il nome del gruppo è obbligatorio' });
|
|
}
|
|
|
|
const masterId = master_ticket_id ? parseInt(master_ticket_id, 10) : null;
|
|
|
|
const info = db.prepare(`
|
|
INSERT INTO ticket_groups (nome, descrizione, master_ticket_id)
|
|
VALUES (?, ?, ?)
|
|
`).run(nome, descrizione || '', masterId);
|
|
|
|
res.json({ id: info.lastInsertRowid, nome, descrizione, master_ticket_id: masterId });
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Errore nella creazione del gruppo', message: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/groups/:id - Detail of a single group
|
|
router.get('/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const group = db.prepare('SELECT * FROM ticket_groups WHERE id = ?').get(id);
|
|
if (!group) {
|
|
return res.status(404).json({ error: 'Gruppo non trovato' });
|
|
}
|
|
|
|
// Get members list
|
|
const memberRows = db.prepare('SELECT ticket_id FROM ticket_group_members WHERE group_id = ?').all(id);
|
|
const memberIds = memberRows.map(r => r.ticket_id);
|
|
|
|
// Fetch details of master and member tickets from OTRS
|
|
let masterTicket = null;
|
|
if (group.master_ticket_id) {
|
|
const details = await fetchTicketsDetails([group.master_ticket_id]);
|
|
if (details.length > 0) {
|
|
masterTicket = details[0];
|
|
}
|
|
}
|
|
|
|
let memberTickets = [];
|
|
if (memberIds.length > 0) {
|
|
memberTickets = await fetchTicketsDetails(memberIds);
|
|
}
|
|
|
|
res.json({
|
|
group,
|
|
masterTicket,
|
|
memberTickets
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Errore nel recupero dei dettagli del gruppo', message: err.message });
|
|
}
|
|
});
|
|
|
|
// PUT /api/groups/:id - Update group info
|
|
router.put('/:id', (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { nome, descrizione, master_ticket_id } = req.body;
|
|
if (!nome) {
|
|
return res.status(400).json({ error: 'Il nome del gruppo è obbligatorio' });
|
|
}
|
|
|
|
const masterId = master_ticket_id ? parseInt(master_ticket_id, 10) : null;
|
|
|
|
const info = db.prepare(`
|
|
UPDATE ticket_groups
|
|
SET nome = ?, descrizione = ?, master_ticket_id = ?
|
|
WHERE id = ?
|
|
`).run(nome, descrizione || '', masterId, id);
|
|
|
|
if (info.changes === 0) {
|
|
return res.status(404).json({ error: 'Gruppo non trovato o nessuna modifica' });
|
|
}
|
|
|
|
res.json({ id: parseInt(id, 10), nome, descrizione, master_ticket_id: masterId });
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Errore nell\'aggiornamento del gruppo', message: err.message });
|
|
}
|
|
});
|
|
|
|
// DELETE /api/groups/:id - Delete group
|
|
router.delete('/:id', (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const info = db.prepare('DELETE FROM ticket_groups WHERE id = ?').run(id);
|
|
if (info.changes === 0) {
|
|
return res.status(404).json({ error: 'Gruppo non trovato' });
|
|
}
|
|
res.json({ success: true, message: 'Gruppo eliminato con successo' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Errore nella cancellazione del gruppo', message: err.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/groups/:id/tickets - Add ticket(s) to group
|
|
router.post('/:id/tickets', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { ticket_identifier } = req.body; // Can be ticket ID or ticket number (tn)
|
|
if (!ticket_identifier) {
|
|
return res.status(400).json({ error: 'Identificativo ticket obbligatorio' });
|
|
}
|
|
|
|
const cleanIdentifier = String(ticket_identifier).trim();
|
|
let queryTicketSql;
|
|
let queryParams;
|
|
|
|
if (/^\d+$/.test(cleanIdentifier)) {
|
|
// It's a number - check if it matches id or tn
|
|
queryTicketSql = 'SELECT id, tn, title FROM ticket WHERE id = $1 OR tn = $2';
|
|
queryParams = [parseInt(cleanIdentifier, 10), cleanIdentifier];
|
|
} else {
|
|
// Check tn
|
|
queryTicketSql = 'SELECT id, tn, title FROM ticket WHERE tn = $1';
|
|
queryParams = [cleanIdentifier];
|
|
}
|
|
|
|
const otrsRes = await pool.query(queryTicketSql, queryParams);
|
|
if (otrsRes.rows.length === 0) {
|
|
return res.status(404).json({ error: `Ticket con identificativo '${cleanIdentifier}' non trovato` });
|
|
}
|
|
|
|
const ticket = otrsRes.rows[0];
|
|
|
|
// Check if group exists
|
|
const group = db.prepare('SELECT id FROM ticket_groups WHERE id = ?').get(id);
|
|
if (!group) {
|
|
return res.status(404).json({ error: 'Gruppo non trovato' });
|
|
}
|
|
|
|
// Insert to membership
|
|
try {
|
|
db.prepare(`
|
|
INSERT INTO ticket_group_members (group_id, ticket_id)
|
|
VALUES (?, ?)
|
|
`).run(id, ticket.id);
|
|
} catch (dbErr) {
|
|
if (dbErr.code === 'SQLITE_CONSTRAINT_PRIMARYKEY') {
|
|
return res.status(409).json({ error: 'Il ticket appartiene già a questo gruppo' });
|
|
}
|
|
throw dbErr;
|
|
}
|
|
|
|
res.json({ success: true, ticket });
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Errore nell\'associazione del ticket', message: err.message });
|
|
}
|
|
});
|
|
|
|
// DELETE /api/groups/:id/tickets/:ticket_id - Remove ticket from group
|
|
router.delete('/:id/tickets/:ticket_id', (req, res) => {
|
|
try {
|
|
const { id, ticket_id } = req.params;
|
|
const info = db.prepare('DELETE FROM ticket_group_members WHERE group_id = ? AND ticket_id = ?').run(id, ticket_id);
|
|
if (info.changes === 0) {
|
|
return res.status(404).json({ error: 'Associazione non trovata' });
|
|
}
|
|
res.json({ success: true, message: 'Ticket rimosso dal gruppo' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Errore nella rimozione del ticket', message: err.message });
|
|
}
|
|
});
|
|
// POST /api/groups/close-end-of-day - Close all tickets in 'CHIUDI A FINE GIORNATA' group
|
|
router.post('/close-end-of-day', async (req, res) => {
|
|
try {
|
|
const operatorId = parseInt(req.headers['x-agent-id'] || '1', 10);
|
|
|
|
// Find default group 'CHIUDI A FINE GIORNATA'
|
|
const closeGroup = db.prepare('SELECT id FROM ticket_groups WHERE UPPER(nome) = ?').get('CHIUDI A FINE GIORNATA');
|
|
if (!closeGroup) {
|
|
return res.status(404).json({ error: 'Gruppo "CHIUDI A FINE GIORNATA" non trovato' });
|
|
}
|
|
|
|
const groupMembers = db.prepare('SELECT ticket_id FROM ticket_group_members WHERE group_id = ?').all(closeGroup.id);
|
|
if (groupMembers.length === 0) {
|
|
return res.json({ success: true, count: 0, message: 'Nessun ticket presente nel gruppo "CHIUDI A FINE GIORNATA"' });
|
|
}
|
|
|
|
const groupTicketIds = groupMembers.map(m => m.ticket_id);
|
|
const placeholders = groupTicketIds.map((_, i) => `$${i + 1}`).join(', ');
|
|
|
|
// Get open tickets in group (not already in a closed state)
|
|
const openTicketsQuery = `
|
|
SELECT t.id
|
|
FROM ticket t
|
|
JOIN ticket_state ts ON t.ticket_state_id = ts.id
|
|
JOIN ticket_state_type tst ON ts.type_id = tst.id
|
|
WHERE t.id IN (${placeholders})
|
|
AND LOWER(tst.name) NOT LIKE '%closed%'
|
|
`;
|
|
const openRes = await pool.query(openTicketsQuery, groupTicketIds);
|
|
const openTicketIds = openRes.rows.map(r => r.id);
|
|
|
|
if (openTicketIds.length === 0) {
|
|
return res.json({ success: true, count: 0, message: 'Tutti i ticket nel gruppo "CHIUDI A FINE GIORNATA" risultano già chiusi' });
|
|
}
|
|
|
|
// Resolve closed state ID (closed successful / closed fallback)
|
|
const closedStateRes = await pool.query(
|
|
`SELECT id FROM ticket_state WHERE name = 'closed successful' OR name = 'chiuso con successo' LIMIT 1`
|
|
);
|
|
let stateId;
|
|
if (closedStateRes.rows.length > 0) {
|
|
stateId = closedStateRes.rows[0].id;
|
|
} else {
|
|
const fallbackRes = await pool.query(
|
|
`SELECT ts.id FROM ticket_state ts JOIN ticket_state_type tst ON ts.type_id = tst.id WHERE tst.name = 'closed' LIMIT 1`
|
|
);
|
|
stateId = fallbackRes.rows[0]?.id || 2;
|
|
}
|
|
|
|
const updatePlaceholders = openTicketIds.map((_, i) => `$${i + 1}`).join(', ');
|
|
const updateQuery = `
|
|
UPDATE ticket
|
|
SET ticket_state_id = $${openTicketIds.length + 1},
|
|
ticket_lock_id = 1,
|
|
change_time = NOW(),
|
|
change_by = $${openTicketIds.length + 2}
|
|
WHERE id IN (${updatePlaceholders})
|
|
`;
|
|
await pool.query(updateQuery, [...openTicketIds, stateId, operatorId]);
|
|
|
|
// Log activity
|
|
logAttivita({
|
|
agente_id: operatorId,
|
|
titolo_azione: 'Chiusura ticket gruppo CHIUDI A FINE GIORNATA',
|
|
azione: { closed_count: openTicketIds.length, ticket_ids: openTicketIds },
|
|
esito: 'successo',
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
count: openTicketIds.length,
|
|
message: `${openTicketIds.length} ticket del gruppo "CHIUDI A FINE GIORNATA" chius${openTicketIds.length === 1 ? 'o' : 'i'} con successo!`
|
|
});
|
|
} catch (err) {
|
|
console.error('Errore nella chiusura ticket fine giornata:', err);
|
|
res.status(500).json({ error: 'Errore nella chiusura ticket', message: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|