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;