feat: aggiunta possibilità di salvare delle note interne da caricare successivamente su OTRS.
This commit is contained in:
1 parent
146d4ed661
commit
3f3885130a
6 files changed
+837
No files matched your search
+221
@@ -0,0 +1,221 @@
|
||||
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' });
|
||||
}
|
||||
|
||||
if (!note.ticket_id && !note.ticket_tn) {
|
||||
return res.status(400).json({ error: 'Impossibile inviare a OTRS: nessun ticket (TN/ID) associato a questa nota' });
|
||||
}
|
||||
|
||||
const 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 (_) {}
|
||||
|
||||
// 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(
|
||||
agentId,
|
||||
userLogin,
|
||||
`Aggiunta nota a Ticket #${resolved.tn}`,
|
||||
{ ticket_id: resolved.id, ticket_tn: resolved.tn, subject: note.subject, time_units: note.time_units },
|
||||
'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;
|
||||
Reference in new issue
Block a user