feat: aggiunta ricerca spudorata. progress: ripreso con il tentativo di invio delle mail che non creino nuovi ticket

This commit is contained in:
VVER440 committed 2026-08-25 23:24:48 +02:00
1 parent 3650bf5790
commit 8d24759a15
18 files changed
+1661 -98

No files matched your search

+147
View File
@@ -0,0 +1,147 @@
const express = require('express');
const router = express.Router();
const fs = require('fs');
const path = require('path');
const { db, logAttivita } = require('../activityDb');
const pool = require('../db');
const baseDir = process.pkg ? path.dirname(process.execPath) : path.resolve(__dirname, '..');
const envPath = fs.existsSync(path.resolve(baseDir, '.env'))
? path.resolve(baseDir, '.env')
: path.resolve(__dirname, '..', '.env');
// Helper to resolve agent name from DB
async function resolveAgentName(agentId) {
try {
const r = await pool.query(
`SELECT first_name, last_name, login FROM users WHERE id = $1`,
[agentId]
);
if (r.rows.length > 0) {
const u = r.rows[0];
return (u.first_name || '') + ' ' + (u.last_name || '') || u.login || `Agent #${agentId}`;
}
} catch (_) { /* ignore */ }
return `Agent #${agentId}`;
}
// GET /api/config/env — Read current .env file and history
router.get('/env', async (req, res) => {
try {
let content = '';
if (fs.existsSync(envPath)) {
content = fs.readFileSync(envPath, 'utf8');
}
const history = db.prepare(`
SELECT * FROM env_history ORDER BY id DESC LIMIT 100
`).all();
res.json({ content, history });
} catch (err) {
console.error('Error reading env config:', err);
res.status(500).json({ error: 'Errore durante la lettura del file .env', message: err.message });
}
});
// POST /api/config/env — Save updated .env content and create snapshot in SQLite
router.post('/env', async (req, res) => {
try {
const { content, note } = req.body;
const operatorId = parseInt(req.headers['x-agent-id'] || '1', 10);
const agentName = await resolveAgentName(operatorId);
if (typeof content !== 'string') {
return res.status(400).json({ error: 'Il contenuto del file .env è obbligatorio' });
}
// If history is empty and current .env exists, create an initial baseline snapshot
const countRow = db.prepare('SELECT COUNT(*) AS count FROM env_history').get();
if (countRow.count === 0 && fs.existsSync(envPath)) {
const initialContent = fs.readFileSync(envPath, 'utf8');
db.prepare(`
INSERT INTO env_history (agent_id, agent_name, content, note)
VALUES (?, ?, ?, ?)
`).run(operatorId, agentName, initialContent, 'Baseline iniziale preesistente');
}
// Write updated content to disk
fs.writeFileSync(envPath, content, 'utf8');
// Save snapshot in history table
const info = db.prepare(`
INSERT INTO env_history (agent_id, agent_name, content, note)
VALUES (?, ?, ?, ?)
`).run(operatorId, agentName, content, note || 'Modifica manuale .env');
logAttivita({
agente_id: operatorId,
agente_nome: agentName,
titolo_azione: 'Modifica .env',
azione: { note: note || 'Modifica manuale .env', history_id: info.lastInsertRowid },
esito: 'successo',
});
const history = db.prepare(`
SELECT * FROM env_history ORDER BY id DESC LIMIT 100
`).all();
res.json({
success: true,
message: 'File .env aggiornato e salvato nello storico locale con successo!',
history_id: info.lastInsertRowid,
history,
});
} catch (err) {
console.error('Error saving .env config:', err);
res.status(500).json({ error: 'Errore durante il salvataggio del file .env', message: err.message });
}
});
// POST /api/config/env/restore/:history_id — Restore a previous .env snapshot
router.post('/env/restore/:history_id', async (req, res) => {
try {
const { history_id } = req.params;
const operatorId = parseInt(req.headers['x-agent-id'] || '1', 10);
const agentName = await resolveAgentName(operatorId);
const record = db.prepare('SELECT * FROM env_history WHERE id = ?').get(history_id);
if (!record) {
return res.status(404).json({ error: 'Versione di storico non trovata' });
}
// Write snapshot content to disk
fs.writeFileSync(envPath, record.content, 'utf8');
// Insert new history record documenting the restore operation
const restoreNote = `Ripristinato alla versione #${record.id} del ${record.created_at}`;
const info = db.prepare(`
INSERT INTO env_history (agent_id, agent_name, content, note)
VALUES (?, ?, ?, ?)
`).run(operatorId, agentName, record.content, restoreNote);
logAttivita({
agente_id: operatorId,
agente_nome: agentName,
titolo_azione: 'Ripristino .env',
azione: { restored_from_id: record.id, new_history_id: info.lastInsertRowid },
esito: 'successo',
});
const history = db.prepare(`
SELECT * FROM env_history ORDER BY id DESC LIMIT 100
`).all();
res.json({
success: true,
message: `Versione #${record.id} ripristinata con successo!`,
content: record.content,
history,
});
} catch (err) {
console.error('Error restoring .env version:', err);
res.status(500).json({ error: 'Errore durante il ripristino della versione .env', message: err.message });
}
});
module.exports = router;
+100 -19
View File
@@ -193,6 +193,59 @@ router.delete('/address-groups/:id', (req, res) => {
}
});
// ─── SETTINGS ──────────────────────────────────────────────────────────────────
// GET /api/email/settings — Fetch agent sender preference
router.get('/settings', async (req, res) => {
try {
const agentId = parseInt(req.query.agent_id || '1', 10);
const row = db.prepare(`SELECT use_agent_sender FROM agent_settings WHERE agent_id = ?`).get(agentId);
const useAgentSender = row ? Boolean(row.use_agent_sender) : false;
let agentEmail = '';
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) {
agentEmail = prefRes.rows[0].preferences_value || '';
}
const envSender = process.env.AZURE_MAIL_SENDER || process.env.SMTP_FROM || process.env.SMTP_USER || '';
res.json({
agent_id: agentId,
use_agent_sender: useAgentSender,
agent_email: agentEmail,
env_sender: envSender
});
} catch (err) {
console.error('[Email] Error fetching settings:', err);
res.status(500).json({ error: 'Errore durante il recupero delle impostazioni email: ' + err.message });
}
});
// POST /api/email/settings — Update agent sender preference
router.post('/settings', async (req, res) => {
try {
const { agent_id, use_agent_sender } = req.body;
const agentId = parseInt(agent_id || '1', 10);
const val = use_agent_sender ? 1 : 0;
const existing = db.prepare(`SELECT agent_id FROM agent_settings WHERE agent_id = ?`).get(agentId);
if (existing) {
db.prepare(`UPDATE agent_settings SET use_agent_sender = ? WHERE agent_id = ?`).run(val, agentId);
} else {
db.prepare(`INSERT INTO agent_settings (agent_id, use_agent_sender) VALUES (?, ?)`).run(agentId, val);
}
res.json({ success: true, use_agent_sender: Boolean(val) });
} catch (err) {
console.error('[Email] Error updating settings:', err);
res.status(500).json({ error: 'Errore durante il salvataggio delle impostazioni email: ' + err.message });
}
});
// ─── SEND EMAIL ───────────────────────────────────────────────────────────────
// POST /api/email/send — send an email for a ticket
@@ -207,7 +260,6 @@ router.post('/send', async (req, res) => {
attachments = [],
inlineImages = [],
agentId,
agentName = 'Agente',
keepHelpdeskCopy = true,
inReplyTo,
references,
@@ -218,6 +270,39 @@ router.post('/send', async (req, res) => {
if (!bodyHtml) return res.status(400).json({ error: 'Il corpo della email è obbligatorio' });
try {
// Fetch agent email and login details
let agentEmail = '';
let agentName = '';
if (agentId) {
const userRes = await pool.query(
`SELECT login, first_name, last_name FROM users WHERE id = $1`,
[agentId]
);
if (userRes.rows.length > 0) {
const u = userRes.rows[0];
agentName = (u.first_name && u.last_name) ? `${u.first_name} ${u.last_name}` : (u.login || '');
}
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) {
agentEmail = prefRes.rows[0].preferences_value || '';
}
}
// Check if agent sender override is enabled in agent_settings SQLite
let customSender = null;
if (agentId) {
const settingRow = db.prepare(`SELECT use_agent_sender FROM agent_settings WHERE agent_id = ?`).get(parseInt(agentId, 10));
const useAgentSender = settingRow ? settingRow.use_agent_sender === 1 : false;
if (useAgentSender && agentEmail) {
customSender = agentName ? `"${agentName}" <${agentEmail}>` : agentEmail;
}
}
// 1. Fetch ticket number and title for subject
const ticketResult = await pool.query(
`SELECT tn, title FROM ticket WHERE id = $1`,
@@ -263,29 +348,25 @@ router.post('/send', async (req, res) => {
const messageId = `<${Date.now()}.${Math.random().toString(36).substring(2)}@pharmaidea.com>`;
// 3. Send via configured mailer (Graph API or SMTP)
const mailResult = await sendMail({ to, cc, bcc: bccList, subject, bodyHtml: processedBodyHtml, attachments, inlineImages: finalInlineImages, inReplyTo, references, messageId });
const mailResult = await sendMail({
from: customSender,
to,
cc,
bcc: bccList,
subject,
bodyHtml: processedBodyHtml,
attachments,
inlineImages: finalInlineImages,
inReplyTo,
references,
messageId
});
const finalMessageId = (mailResult && mailResult.internetMessageId) || messageId;
// 4. Log article in OTRS ticket via DB as a standard Email article
try {
const now = Math.floor(Date.now() / 1000);
const agentLoginResult = agentId
? await pool.query(`SELECT login FROM users WHERE id = $1`, [agentId])
: null;
const agentLogin = agentLoginResult?.rows[0]?.login || 'system';
// Query email from user_preferences for agent
let agentEmail = '';
if (agentId) {
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) {
agentEmail = prefRes.rows[0].preferences_value || '';
}
}
const aFrom = agentEmail ? `"${agentLogin}" <${agentEmail}>` : `"${agentLogin}" <${process.env.AZURE_MAIL_SENDER || process.env.SMTP_FROM || 'helpdesk@example.com'}>`;
const aFrom = customSender || (agentEmail ? `"${agentName}" <${agentEmail}>` : `"${agentName || 'system'}" <${process.env.AZURE_MAIL_SENDER || process.env.SMTP_FROM || 'helpdesk@example.com'}>`);
const toList = to.join(', ');
+10 -3
View File
@@ -3,6 +3,12 @@ const router = express.Router();
const pool = require('../db');
const { db, logAttivita } = require('../activityDb');
function getLocalTimestamp() {
const d = new Date();
const pad = (n) => String(n).padStart(2, '0');
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}
// Helper to fetch details of a list of tickets from OTRS DB
async function fetchTicketsDetails(ticketIds) {
if (!ticketIds || ticketIds.length === 0) return [];
@@ -295,16 +301,17 @@ router.post('/close-end-of-day', async (req, res) => {
stateId = fallbackRes.rows[0]?.id || 2;
}
const localNow = getLocalTimestamp();
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}
change_time = $${openTicketIds.length + 2},
change_by = $${openTicketIds.length + 3}
WHERE id IN (${updatePlaceholders})
`;
await pool.query(updateQuery, [...openTicketIds, stateId, operatorId]);
await pool.query(updateQuery, [...openTicketIds, stateId, localNow, operatorId]);
// Log activity
logAttivita({
+109 -48
View File
@@ -890,8 +890,9 @@ router.patch('/:id', async (req, res) => {
if (setClauses.length > 0) {
// Always update change_time and change_by
setClauses.push(`change_time = NOW()`);
setClauses.push(`change_time = $${pIdx++}`);
setClauses.push(`change_by = $${pIdx++}`);
setParams.push(localNow);
setParams.push(operatorId);
setParams.push(parseInt(id));
@@ -902,8 +903,8 @@ router.patch('/:id', async (req, res) => {
} else {
// If there are no fields modified but we have a note or time_unit, we still update change_time/change_by
await client.query(
`UPDATE ticket SET change_time = NOW(), change_by = $1 WHERE id = $2`,
[operatorId, id]
`UPDATE ticket SET change_time = $1, change_by = $2 WHERE id = $3`,
[localNow, operatorId, id]
);
}
@@ -950,13 +951,13 @@ router.patch('/:id', async (req, res) => {
) VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8,
NOW(), $9, NOW(), $9
$9, $10, $9, $10
)`,
[
historyName,
htResult.rows[0].id, parseInt(id), newTypeId, newQueueId,
newOwnerId, newPriorityId, newStateId,
operatorId
localNow, operatorId
]
);
}
@@ -983,9 +984,9 @@ router.patch('/:id', async (req, res) => {
is_visible_for_customer, search_index_needs_rebuild,
create_time, create_by, change_time, change_by
) VALUES (
$1, $2, $3, 0, 1, NOW(), $4, NOW(), $4
$1, $2, $3, 0, 1, $4, $5, $4, $5
) RETURNING id`,
[id, senderTypeId, channelId, operatorId]
[id, senderTypeId, channelId, localNow, operatorId]
);
const articleId = articleResult.rows[0].id;
@@ -1012,9 +1013,9 @@ router.patch('/:id', async (req, res) => {
$1, $2, '', '', '', '', $3, $4,
'', '', '',
'text/html; charset=utf-8', EXTRACT(EPOCH FROM NOW())::INTEGER, $5,
NOW(), $6, NOW(), $6
$6, $7, $6, $7
)`,
[articleId, fromHeader, noteSubject, htmlBody, contentPath, operatorId]
[articleId, fromHeader, noteSubject, htmlBody, contentPath, localNow, operatorId]
);
// Create article_data_mime_attachment for OTRS CE HTML rendering
@@ -1024,9 +1025,9 @@ router.patch('/:id', async (req, res) => {
create_time, create_by, change_time, change_by
) VALUES (
$1, 'file-1', $2, 'text/html; charset="utf-8"', '', $3,
NOW(), $4, NOW(), $4
$4, $5, $4, $5
)`,
[articleId, String(contentSize), base64Body, operatorId]
[articleId, String(contentSize), base64Body, localNow, operatorId]
);
// Log the time in time_accounting if provided
@@ -1035,8 +1036,8 @@ router.patch('/:id', async (req, res) => {
`INSERT INTO time_accounting (
ticket_id, article_id, time_unit,
create_time, create_by, change_time, change_by
) VALUES ($1, $2, $3, NOW(), $4, NOW(), $4)`,
[id, articleId, timeUnitVal, operatorId]
) VALUES ($1, $2, $3, $4, $5, $4, $5)`,
[id, articleId, timeUnitVal, localNow, operatorId]
);
}
}
@@ -1733,44 +1734,60 @@ router.post('/merge', async (req, res) => {
const client = await pool.connect();
try {
await client.query('BEGIN');
const localNow = getLocalTimestamp();
// Fetch target ticket details
const targetRes = await client.query('SELECT tn, title FROM ticket WHERE id = $1', [targetId]);
const targetRes = await client.query(
'SELECT tn, title, queue_id, user_id, ticket_priority_id, ticket_state_id, type_id FROM ticket WHERE id = $1',
[targetId]
);
if (targetRes.rows.length === 0) {
throw new Error(`Ticket di destinazione #${targetId} non trovato`);
}
const targetTn = targetRes.rows[0].tn;
const targetTitle = targetRes.rows[0].title;
const targetRow = targetRes.rows[0];
// Fetch history type IDs
const htStateRes = await client.query(`SELECT id FROM ticket_history_type WHERE name = 'StateUpdate'`);
const stateUpdateTypeId = htStateRes.rows.length > 0 ? htStateRes.rows[0].id : 1;
const htNoteRes = await client.query(`SELECT id FROM ticket_history_type WHERE name = 'AddNote'`);
const addNoteTypeId = htNoteRes.rows.length > 0 ? htNoteRes.rows[0].id : 1;
for (const sourceId of sourceIds) {
// Fetch source ticket details
const sourceRes = await client.query('SELECT tn, title FROM ticket WHERE id = $1', [sourceId]);
const sourceRes = await client.query(
'SELECT tn, title, queue_id, user_id, ticket_priority_id, type_id FROM ticket WHERE id = $1',
[sourceId]
);
if (sourceRes.rows.length === 0) continue;
const sourceTn = sourceRes.rows[0].tn;
const sourceTitle = sourceRes.rows[0].title;
const sourceRow = sourceRes.rows[0];
// 1. Move all articles of source to target
await client.query(
`UPDATE article
SET ticket_id = $1, change_time = NOW(), change_by = $2
WHERE ticket_id = $3`,
[targetId, operatorId, sourceId]
SET ticket_id = $1, change_time = $2, change_by = $3
WHERE ticket_id = $4`,
[targetId, localNow, operatorId, sourceId]
);
// 2. Move all time accounting entries
await client.query(
`UPDATE time_accounting
SET ticket_id = $1, change_time = NOW(), change_by = $2
WHERE ticket_id = $3`,
[targetId, operatorId, sourceId]
SET ticket_id = $1, change_time = $2, change_by = $3
WHERE ticket_id = $4`,
[targetId, localNow, operatorId, sourceId]
);
// 3. Mark source ticket as merged (state_id = 9)
await client.query(
`UPDATE ticket
SET ticket_state_id = 9, change_time = NOW(), change_by = $1
WHERE id = $2`,
[operatorId, sourceId]
SET ticket_state_id = 9, change_time = $1, change_by = $2
WHERE id = $3`,
[localNow, operatorId, sourceId]
);
// 4. Create ParentChild relation link (source B is child/merged of target A)
@@ -1786,8 +1803,8 @@ router.post('/merge', async (req, res) => {
`INSERT INTO link_relation (
source_object_id, source_key, target_object_id, target_key,
type_id, state_id, create_time, create_by
) VALUES (1, $1, 1, $2, 2, 1, NOW(), $3)`,
[String(sourceId), String(targetId), operatorId]
) VALUES (1, $1, 1, $2, 2, 1, $3, $4)`,
[String(sourceId), String(targetId), localNow, operatorId]
);
}
@@ -1801,8 +1818,8 @@ router.post('/merge', async (req, res) => {
ticket_id, article_sender_type_id, communication_channel_id,
is_visible_for_customer, search_index_needs_rebuild,
create_time, create_by, change_time, change_by
) VALUES ($1, 1, 1, 0, 1, NOW(), $2, NOW(), $2) RETURNING id`,
[targetId, operatorId]
) VALUES ($1, 1, 1, 0, 1, $2, $3, $2, $3) RETURNING id`,
[targetId, localNow, operatorId]
);
const articleId = artResult.rows[0].id;
@@ -1818,9 +1835,9 @@ router.post('/merge', async (req, res) => {
$1, 'Sistema OTRS Turbo', '', '', '', '', 'Ticket Unito', $2,
'', '', '',
'text/plain; charset=utf-8', EXTRACT(EPOCH FROM NOW())::INTEGER, $3,
NOW(), $4, NOW(), $4
$4, $5, $4, $5
)`,
[articleId, mergeNoteBody, contentPath, operatorId]
[articleId, mergeNoteBody, contentPath, localNow, operatorId]
);
// Create internal system note inside source ticket B (the merged ticket)
@@ -1829,8 +1846,8 @@ router.post('/merge', async (req, res) => {
ticket_id, article_sender_type_id, communication_channel_id,
is_visible_for_customer, search_index_needs_rebuild,
create_time, create_by, change_time, change_by
) VALUES ($1, 1, 1, 0, 1, NOW(), $2, NOW(), $2) RETURNING id`,
[sourceId, operatorId]
) VALUES ($1, 1, 1, 0, 1, $2, $3, $2, $3) RETURNING id`,
[sourceId, localNow, operatorId]
);
const sourceArticleId = sourceArtResult.rows[0].id;
const sourceMergeNoteText = `Merged Ticket ${sourceTn} to ${targetTn}`;
@@ -1845,12 +1862,54 @@ router.post('/merge', async (req, res) => {
$1, 'Sistema OTRS Turbo', '', '', '', '', $2, $2,
'', '', '',
'text/plain; charset=utf-8', EXTRACT(EPOCH FROM NOW())::INTEGER, $3,
NOW(), $4, NOW(), $4
$4, $5, $4, $5
)`,
[sourceArticleId, sourceMergeNoteText, contentPath, operatorId]
[sourceArticleId, sourceMergeNoteText, contentPath, localNow, operatorId]
);
// 6. Record ticket history for source ticket (state changed to merged = 9)
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 (
'%%merged%%', $1, $2, $3, $4,
$5, $6, 9,
$7, $8, $7, $8
)`,
[
stateUpdateTypeId, sourceId, sourceRow.type_id || 1, sourceRow.queue_id,
sourceRow.user_id || 1, sourceRow.ticket_priority_id || 1,
localNow, operatorId
]
);
// 7. Record ticket history for target ticket (note added for merge)
await client.query(
`INSERT INTO ticket_history (
name, history_type_id, ticket_id, article_id, type_id, queue_id,
owner_id, priority_id, state_id,
create_time, create_by, change_time, change_by
) VALUES (
'%%', $1, $2, $3, $4, $5,
$6, $7, $8,
$9, $10, $9, $10
)`,
[
addNoteTypeId, targetId, articleId, targetRow.type_id || 1, targetRow.queue_id,
targetRow.user_id || 1, targetRow.ticket_priority_id || 1, targetRow.ticket_state_id || 1,
localNow, operatorId
]
);
}
// Update target ticket change_time & change_by
await client.query(
`UPDATE ticket SET change_time = $1, change_by = $2 WHERE id = $3`,
[localNow, operatorId, targetId]
);
await client.query('COMMIT');
resolveAgentName(operatorId).then(agente_nome => {
logAttivita({
@@ -2185,6 +2244,8 @@ router.post('/auto-time', async (req, res) => {
const tn = `${datePrefix}${String(counter).padStart(counterPadding, '0')}`;
// 3. Create Ticket (lock = 1 [unlock], owner & responsible = operatorId)
const localNow = getLocalTimestamp();
const ticketResult = await client.query(
`INSERT INTO ticket (
tn, title, queue_id, ticket_lock_id, type_id,
@@ -2205,14 +2266,14 @@ router.post('/auto-time', async (req, res) => {
0, 0,
0, 0,
0,
NOW(), $10, NOW(), $10
$10, $11, $10, $11
) RETURNING id, tn`,
[
tn, title, queueId, typeId,
operatorId,
priorityId, stateId,
customerId, customerUserId,
operatorId
localNow, operatorId
]
);
const ticketId = ticketResult.rows[0].id;
@@ -2227,8 +2288,8 @@ router.post('/auto-time', async (req, res) => {
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, $3, $4, $5, $6, $7, $8, NOW(), $9, NOW(), $9)`,
['%%', historyTypeId, ticketId, typeId || 1, queueId, operatorId, priorityId, stateId, operatorId]
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $9, $10)`,
['%%', historyTypeId, ticketId, typeId || 1, queueId, operatorId, priorityId, stateId, localNow, operatorId]
);
// 4. Create Article (Internal note)
@@ -2247,8 +2308,8 @@ router.post('/auto-time', async (req, res) => {
ticket_id, article_sender_type_id, communication_channel_id,
is_visible_for_customer, search_index_needs_rebuild,
create_time, create_by, change_time, change_by
) VALUES ($1, $2, $3, 0, 1, NOW(), $4, NOW(), $4) RETURNING id`,
[ticketId, senderTypeId, channelId, operatorId]
) VALUES ($1, $2, $3, 0, 1, $4, $5, $4, $5) RETURNING id`,
[ticketId, senderTypeId, channelId, localNow, operatorId]
);
const articleId = articleResult.rows[0].id;
@@ -2269,9 +2330,9 @@ router.post('/auto-time', async (req, res) => {
) VALUES (
$1, $2, '', $3, $4,
'text/html; charset=utf-8', EXTRACT(EPOCH FROM NOW())::INTEGER, $5,
NOW(), $6, NOW(), $6
$6, $7, $6, $7
)`,
[articleId, fromHeader, subject, htmlBody, contentPath, operatorId]
[articleId, fromHeader, subject, htmlBody, contentPath, localNow, operatorId]
);
// Create article attachment (file-1) for OTRS CE HTML display
@@ -2279,8 +2340,8 @@ router.post('/auto-time', async (req, res) => {
`INSERT INTO article_data_mime_attachment (
article_id, filename, content_size, content_type, disposition, content,
create_time, create_by, change_time, change_by
) VALUES ($1, 'file-1', $2, 'text/html; charset="utf-8"', '', $3, NOW(), $4, NOW(), $4)`,
[articleId, String(contentSize), base64Body, operatorId]
) VALUES ($1, 'file-1', $2, 'text/html; charset="utf-8"', '', $3, $4, $5, $4, $5)`,
[articleId, String(contentSize), base64Body, localNow, operatorId]
);
// 5. Create Time Accounting Entry for remaining minutes
@@ -2288,8 +2349,8 @@ router.post('/auto-time', async (req, res) => {
`INSERT INTO time_accounting (
ticket_id, article_id, time_unit,
create_time, create_by, change_time, change_by
) VALUES ($1, $2, $3, NOW(), $4, NOW(), $4)`,
[ticketId, articleId, remaining, operatorId]
) VALUES ($1, $2, $3, $4, $5, $4, $5)`,
[ticketId, articleId, remaining, localNow, operatorId]
);
// 6. Close tickets in default group 'CHIUDI A FINE GIORNATA'
@@ -2303,8 +2364,8 @@ router.post('/auto-time', async (req, res) => {
// Update ticket state to closed in OTRS DB
await client.query(
`UPDATE ticket SET ticket_state_id = $${groupTicketIds.length + 1}, change_time = NOW(), change_by = $${groupTicketIds.length + 2} WHERE id IN (${idPlaceholders})`,
[...groupTicketIds, stateId, operatorId]
`UPDATE ticket SET ticket_state_id = $${groupTicketIds.length + 1}, change_time = $${groupTicketIds.length + 2}, change_by = $${groupTicketIds.length + 3} WHERE id IN (${idPlaceholders})`,
[...groupTicketIds, stateId, localNow, operatorId]
);
console.log(`[Auto-Time] Closed ${groupTicketIds.length} tickets from group 'CHIUDI A FINE GIORNATA'`);