296 lines
11 KiB
JavaScript
296 lines
11 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { db, logAttivita } = require('../activityDb');
|
|
const pool = require('../db');
|
|
|
|
// Helper to resolve ticket info from PostgreSQL given ID or TN
|
|
async function resolveTicketInfo(ticketId, ticketTn) {
|
|
try {
|
|
if (ticketId) {
|
|
const res = await pool.query('SELECT id, tn, title FROM ticket WHERE id = $1', [ticketId]);
|
|
if (res.rows.length > 0) return res.rows[0];
|
|
} else if (ticketTn) {
|
|
const res = await pool.query('SELECT id, tn, title FROM ticket WHERE tn = $1', [ticketTn.trim()]);
|
|
if (res.rows.length > 0) return res.rows[0];
|
|
}
|
|
} catch (err) {
|
|
console.warn('[Notes API] Error resolving ticket info:', err.message);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// GET /api/notes - List all draft notes for current agent
|
|
router.get('/', (req, res) => {
|
|
try {
|
|
const agentId = parseInt(req.headers['x-agent-id'] || '1', 10);
|
|
const notes = db.prepare(`
|
|
SELECT * FROM ticket_draft_notes
|
|
WHERE agent_id = ?
|
|
ORDER BY updated_at DESC
|
|
`).all(agentId);
|
|
|
|
res.json(notes);
|
|
} catch (err) {
|
|
console.error('[Notes API] GET / failed:', err);
|
|
res.status(500).json({ error: 'Errore nel caricamento delle note temporanee', message: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/notes/:id - Get single draft note
|
|
router.get('/:id', (req, res) => {
|
|
try {
|
|
const note = db.prepare('SELECT * FROM ticket_draft_notes WHERE id = ?').get(req.params.id);
|
|
if (!note) {
|
|
return res.status(404).json({ error: 'Nota temporanea non trovata' });
|
|
}
|
|
res.json(note);
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Errore nel recupero della nota', message: err.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/notes - Create or update a temporary draft note
|
|
router.post('/', async (req, res) => {
|
|
try {
|
|
const agentId = parseInt(req.headers['x-agent-id'] || '1', 10);
|
|
let { id, ticket_id, ticket_tn, ticket_title, subject, body, time_units, note_type } = req.body;
|
|
|
|
if (!body || body.trim() === '') {
|
|
return res.status(400).json({ error: 'Il corpo della nota non può essere vuoto' });
|
|
}
|
|
|
|
// Resolve ticket info if missing TN or ID
|
|
const resolved = await resolveTicketInfo(ticket_id, ticket_tn);
|
|
if (resolved) {
|
|
ticket_id = resolved.id;
|
|
ticket_tn = resolved.tn;
|
|
ticket_title = resolved.title;
|
|
}
|
|
|
|
const now = new Date().toISOString();
|
|
|
|
if (id) {
|
|
// Update existing note
|
|
db.prepare(`
|
|
UPDATE ticket_draft_notes
|
|
SET ticket_id = ?, ticket_tn = ?, ticket_title = ?, subject = ?, body = ?, time_units = ?, note_type = ?, updated_at = ?
|
|
WHERE id = ? AND agent_id = ?
|
|
`).run(ticket_id || null, ticket_tn || null, ticket_title || null, subject || '', body, parseFloat(time_units) || 0, note_type || 'note-internal', now, id, agentId);
|
|
|
|
const updated = db.prepare('SELECT * FROM ticket_draft_notes WHERE id = ?').get(id);
|
|
return res.json(updated);
|
|
} else {
|
|
// Insert new note
|
|
const info = db.prepare(`
|
|
INSERT INTO ticket_draft_notes (agent_id, ticket_id, ticket_tn, ticket_title, subject, body, time_units, note_type, created_at, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(agentId, ticket_id || null, ticket_tn || null, ticket_title || null, subject || '', body, parseFloat(time_units) || 0, note_type || 'note-internal', now, now);
|
|
|
|
const inserted = db.prepare('SELECT * FROM ticket_draft_notes WHERE id = ?').get(info.lastInsertRowid);
|
|
return res.status(201).json(inserted);
|
|
}
|
|
} catch (err) {
|
|
console.error('[Notes API] POST / failed:', err);
|
|
res.status(500).json({ error: 'Errore nel salvataggio della nota temporanea', message: err.message });
|
|
}
|
|
});
|
|
|
|
// DELETE /api/notes/:id - Delete a draft note permanently
|
|
router.delete('/:id', (req, res) => {
|
|
try {
|
|
const info = db.prepare('DELETE FROM ticket_draft_notes WHERE id = ?').run(req.params.id);
|
|
if (info.changes === 0) {
|
|
return res.status(404).json({ error: 'Nota temporanea non trovata' });
|
|
}
|
|
res.json({ success: true, id: req.params.id });
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Errore nell\'eliminazione della nota', message: err.message });
|
|
}
|
|
});
|
|
|
|
// DELETE /api/notes/by-ticket/:identifier - Delete notes tied to ticket ID or TN
|
|
router.delete('/by-ticket/:identifier', async (req, res) => {
|
|
try {
|
|
const identifier = req.params.identifier;
|
|
const resolved = await resolveTicketInfo(parseInt(identifier, 10) || null, identifier);
|
|
|
|
if (resolved) {
|
|
db.prepare('DELETE FROM ticket_draft_notes WHERE ticket_id = ? OR ticket_tn = ?').run(resolved.id, resolved.tn);
|
|
} else {
|
|
db.prepare('DELETE FROM ticket_draft_notes WHERE ticket_id = ? OR ticket_tn = ?').run(identifier, identifier);
|
|
}
|
|
res.json({ success: true });
|
|
} catch (err) {
|
|
res.status(500).json({ error: 'Errore nel ripristino/pulizia della nota', message: err.message });
|
|
}
|
|
});
|
|
|
|
// POST /api/notes/:id/submit - Submit note directly to OTRS and remove from SQLite upon success
|
|
router.post('/:id/submit', async (req, res) => {
|
|
try {
|
|
const agentId = parseInt(req.headers['x-agent-id'] || '1', 10);
|
|
const note = db.prepare('SELECT * FROM ticket_draft_notes WHERE id = ? AND agent_id = ?').get(req.params.id, agentId);
|
|
|
|
if (!note) {
|
|
return res.status(404).json({ error: 'Nota temporanea non trovata' });
|
|
}
|
|
|
|
let resolved = null;
|
|
if (note.ticket_id || note.ticket_tn) {
|
|
resolved = await resolveTicketInfo(note.ticket_id, note.ticket_tn);
|
|
if (!resolved) {
|
|
return res.status(400).json({ error: `Ticket non trovato nel sistema OTRS (TN: ${note.ticket_tn || ''}, ID: ${note.ticket_id || ''})` });
|
|
}
|
|
}
|
|
|
|
// Get agent login user
|
|
let userLogin = process.env.OTRS_API_USER || 'root@localhost';
|
|
try {
|
|
const prefRes = await pool.query(
|
|
`SELECT preferences_value FROM user_preferences WHERE user_id = $1 AND preferences_key = 'UserEmail'`,
|
|
[agentId]
|
|
);
|
|
if (prefRes.rows.length > 0 && prefRes.rows[0].preferences_value) {
|
|
userLogin = prefRes.rows[0].preferences_value;
|
|
}
|
|
} catch (_) {}
|
|
|
|
// If no ticket is associated, create a new ticket from this note
|
|
if (!resolved) {
|
|
// Default queue (first queue or 1)
|
|
let queueId = 1;
|
|
const qRes = await pool.query('SELECT id FROM queue ORDER BY id ASC LIMIT 1');
|
|
if (qRes.rows.length > 0) queueId = qRes.rows[0].id;
|
|
|
|
// Default state ('new' / 1)
|
|
let stateId = 1;
|
|
const sRes = await pool.query(`SELECT ts.id FROM ticket_state ts JOIN ticket_state_type tst ON ts.type_id = tst.id WHERE tst.name = 'new' LIMIT 1`);
|
|
if (sRes.rows.length > 0) stateId = sRes.rows[0].id;
|
|
|
|
const ticketTitle = note.subject || 'Nuova Nota Temporanea';
|
|
|
|
// Generate TN
|
|
const now = new Date();
|
|
const systemId = process.env.OTRS_SYSTEM_ID || String(agentId);
|
|
const datePrefix = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}${systemId}`;
|
|
|
|
const maxTicketResult = await pool.query(
|
|
`SELECT tn FROM ticket WHERE tn LIKE $1`,
|
|
[`${datePrefix}%`]
|
|
);
|
|
let maxCounter = 0;
|
|
for (const row of maxTicketResult.rows) {
|
|
const counterPart = row.tn.substring(datePrefix.length);
|
|
const parsed = parseInt(counterPart, 10);
|
|
if (!isNaN(parsed) && parsed > maxCounter) {
|
|
maxCounter = parsed;
|
|
}
|
|
}
|
|
const counterPadding = parseInt(process.env.OTRS_COUNTER_PADDING, 10) || 6;
|
|
const newTn = `${datePrefix}${String(maxCounter + 1).padStart(counterPadding, '0')}`;
|
|
|
|
// Insert new ticket into DB
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
const nowTs = new Date().toISOString().replace('T', ' ').substring(0, 19);
|
|
|
|
const newTicketRes = await client.query(
|
|
`INSERT INTO ticket (
|
|
tn, title, queue_id, ticket_lock_id, type_id,
|
|
user_id, responsible_user_id, ticket_priority_id, ticket_state_id,
|
|
create_time, create_by, change_time, change_by
|
|
) VALUES (
|
|
$1, $2, $3, 1, 1,
|
|
$4, $4, 3, $5,
|
|
$6, $4, $6, $4
|
|
) RETURNING id, tn`,
|
|
[newTn, ticketTitle, queueId, agentId, stateId, nowTs]
|
|
);
|
|
|
|
const newTicketId = newTicketRes.rows[0].id;
|
|
resolved = { id: newTicketId, tn: newTn, title: ticketTitle };
|
|
|
|
// Insert NewTicket history
|
|
const htRes = await client.query(`SELECT id FROM ticket_history_type WHERE name = 'NewTicket'`);
|
|
const htId = htRes.rows.length > 0 ? htRes.rows[0].id : 1;
|
|
await client.query(
|
|
`INSERT INTO ticket_history (
|
|
name, history_type_id, ticket_id, type_id, queue_id, owner_id, priority_id, state_id, create_time, create_by, change_time, change_by
|
|
) VALUES ('%%', $1, $2, 1, $3, $4, 3, $5, $6, $4, $6, $4)`,
|
|
[htId, newTicketId, queueId, agentId, stateId, nowTs]
|
|
);
|
|
|
|
await client.query('COMMIT');
|
|
} catch (createErr) {
|
|
await client.query('ROLLBACK');
|
|
throw createErr;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
// Prepare Article for OTRS REST API
|
|
const isInternal = note.note_type === 'note-internal';
|
|
const isEmail = note.note_type === 'email-external';
|
|
|
|
let articleType = isInternal ? 'note-internal' : (isEmail ? 'email-external' : 'note-external');
|
|
let senderType = isInternal ? 'agent' : (isEmail ? 'agent' : 'agent');
|
|
|
|
const articlePayload = {
|
|
ArticleType: articleType,
|
|
SenderType: senderType,
|
|
Subject: note.subject || `Nota per ticket ${resolved.tn}`,
|
|
Body: note.body,
|
|
ContentType: 'text/html; charset=UTF-8',
|
|
TimeUnit: parseFloat(note.time_units) || 0
|
|
};
|
|
|
|
const otrsUrl = process.env.OTRS_API_URL || 'http://localhost/otrs/nph-genericinterface.pl/Webservice/GenericTicketConnectorREST';
|
|
const otrsPassword = process.env.OTRS_API_PASSWORD || '';
|
|
|
|
const payload = {
|
|
UserLogin: userLogin,
|
|
Password: otrsPassword,
|
|
TicketID: resolved.id,
|
|
Article: articlePayload
|
|
};
|
|
|
|
const otrsRes = await fetch(`${otrsUrl}/TicketUpdate`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
const otrsData = await otrsRes.json();
|
|
|
|
if (otrsData && otrsData.Error) {
|
|
throw new Error(otrsData.Error.ErrorMessage || 'Errore durante l\'invio a OTRS');
|
|
}
|
|
|
|
// Log action
|
|
logAttivita({
|
|
agente_id: agentId,
|
|
agente_nome: userLogin,
|
|
titolo_azione: `Aggiunta nota a Ticket #${resolved.tn}`,
|
|
azione: { ticket_id: resolved.id, ticket_tn: resolved.tn, subject: note.subject, time_units: note.time_units },
|
|
esito: 'successo'
|
|
});
|
|
|
|
// Delete note from local SQLite DB upon successful OTRS upload
|
|
db.prepare('DELETE FROM ticket_draft_notes WHERE id = ?').run(note.id);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `Nota inviata con successo a OTRS per il ticket #${resolved.tn} e rimossa dal database locale.`,
|
|
ticket_id: resolved.id,
|
|
ticket_tn: resolved.tn
|
|
});
|
|
} catch (err) {
|
|
console.error('[Notes API] POST /:id/submit failed:', err);
|
|
res.status(500).json({ error: 'Errore durante l\'invio della nota a OTRS', message: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|