From 1bbe5616735febc39307bef46c53c66f5ff2c48b Mon Sep 17 00:00:00 2001 From: Gabriele Cimaschi Date: Wed, 15 Jul 2026 00:07:34 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20tentativo=20di=20fix=20per=20il=20ricono?= =?UTF-8?q?scimento=20del=20ticket=20ma=20otrs=20=C3=A8=20stronzo-merda.?= =?UTF-8?q?=20feat:=20nella=20pagina=20di=20anteprima=20del=20ticket,=20ag?= =?UTF-8?q?giunto=20pulsante=20per=20aprire=20in=20una=20scheda=20interna.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/js/views/emailCompose.js | 4 +--- public/js/views/ticketDetail.js | 5 +++-- routes/email.js | 7 ++++--- server.js | 2 +- utils/graphMailer.js | 29 ++++++++++++++++++++++++++--- utils/smtpMailer.js | 4 +++- 6 files changed, 38 insertions(+), 13 deletions(-) diff --git a/public/js/views/emailCompose.js b/public/js/views/emailCompose.js index 4e4c080..7f11fe9 100644 --- a/public/js/views/emailCompose.js +++ b/public/js/views/emailCompose.js @@ -433,10 +433,8 @@ const EmailCompose = (() => { } else { const tn = options.ticketTn || ''; const title = options.ticketTitle || ''; - subjectEl.value = tn ? `Re: [Ticket#${tn}] ${title}` : title; + subjectEl.value = tn ? `[Ticket#${tn}] Re: ${title}` : title; } - - // Signature and groups select const sigSelect = document.getElementById('ec-signature-select'); const groupsSelect = document.getElementById('ec-groups-select'); const agentId = App.currentAgentId || 0; diff --git a/public/js/views/ticketDetail.js b/public/js/views/ticketDetail.js index b5bdf50..837dbcb 100644 --- a/public/js/views/ticketDetail.js +++ b/public/js/views/ticketDetail.js @@ -161,6 +161,7 @@ const TicketDetailView = {

📋 ${App.escapeHtml(ticket.title || '(senza titolo)')} +

${ticket.state_name} @@ -566,7 +567,7 @@ const TicketDetailView = { this.updateNoteAttachmentList(); } - this.bindEvents(ticket, articles, container, groupsData); + this.bindEvents(ticket, articles, container, groupsData, attachments); } catch (err) { container.innerHTML = ` @@ -580,7 +581,7 @@ const TicketDetailView = { } }, - bindEvents(ticket, articles, container, groupsData) { + bindEvents(ticket, articles, container, groupsData, attachments) { // Quick-edit change detection const fields = document.querySelectorAll('.quick-edit-select:not(#qe-queue-search), #qe-queue, #qe-customer-user-id, #qe-customer-id'); const saveBtn = document.getElementById('qe-save'); diff --git a/routes/email.js b/routes/email.js index 986ce9f..373fa61 100644 --- a/routes/email.js +++ b/routes/email.js @@ -226,7 +226,7 @@ router.post('/send', async (req, res) => { if (!ticketResult.rows.length) return res.status(404).json({ error: 'Ticket non trovato' }); const { tn, title } = ticketResult.rows[0]; - const subject = customSubject || `Re: [Ticket#${tn}] ${title}`; + const subject = customSubject || `[Ticket#${tn}] Re: ${title}`; // 2. Build BCC list (include OTRS system mailbox if keepHelpdeskCopy is true) const bccList = [...bcc]; @@ -263,7 +263,8 @@ 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) - await sendMail({ to, cc, bcc: bccList, subject, bodyHtml: processedBodyHtml, attachments, inlineImages: finalInlineImages, inReplyTo, references, messageId }); + const mailResult = await sendMail({ 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 { @@ -308,7 +309,7 @@ router.post('/send', async (req, res) => { await pool.query(` INSERT INTO article_data_mime (article_id, a_from, a_to, a_cc, a_bcc, a_subject, a_body, a_content_type, a_message_id, incoming_time, create_time, create_by, change_time, change_by) VALUES ($1, $2, $3, $4, $5, $6, $7, 'text/html; charset=utf-8', $8, $9, $11, $10, $11, $10)`, - [articleId, aFrom, toList, cc.join(', '), bccList.join(', '), subject, processedBodyHtml, messageId, now, agentId || 1, localNow] + [articleId, aFrom, toList, cc.join(', '), bccList.join(', '), subject, processedBodyHtml, finalMessageId, now, agentId || 1, localNow] ); // Helper to strip HTML tags diff --git a/server.js b/server.js index 6ae2e15..14b95be 100644 --- a/server.js +++ b/server.js @@ -1,4 +1,4 @@ -require('dotenv').config(); +require('dotenv').config({ path: require('path').resolve(__dirname, '.env') }); const express = require('express'); const cors = require('cors'); const path = require('path'); diff --git a/utils/graphMailer.js b/utils/graphMailer.js index 81b1038..69cd4f3 100644 --- a/utils/graphMailer.js +++ b/utils/graphMailer.js @@ -60,7 +60,7 @@ async function getAccessToken() { * @param {Array} [options.inlineImages] - [{ cid, content (base64), contentType }] * @returns {Promise} */ -async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments = [], inlineImages = [], inReplyTo, references }) { +async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments = [], inlineImages = [], inReplyTo, references, messageId }) { const sender = process.env.AZURE_MAIL_SENDER; if (!sender) throw new Error('AZURE_MAIL_SENDER non configurato nel .env'); @@ -115,7 +115,7 @@ async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments attachments: allAttachments, internetMessageHeaders: headers.length ? headers : undefined, }, - saveToSentItems: false, + saveToSentItems: true, }; const url = `https://graph.microsoft.com/v1.0/users/${encodeURIComponent(sender)}/sendMail`; @@ -130,7 +130,30 @@ async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments }); if (res.status === 202) { - return; // Successo (Graph API risponde con 202 No Content) + let actualMessageId = messageId; + try { + // Wait 1.5 seconds for Exchange to process and place it in Sent Items + await new Promise(resolve => setTimeout(resolve, 1500)); + + const searchUrl = `https://graph.microsoft.com/v1.0/users/${encodeURIComponent(sender)}/mailFolders/sentItems/messages?$filter=subject eq '${subject.replace(/'/g, "''")}'&$top=1&$select=internetMessageId`; + const searchRes = await fetch(searchUrl, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }); + + if (searchRes.ok) { + const searchData = await searchRes.json(); + if (searchData.value && searchData.value.length > 0) { + actualMessageId = searchData.value[0].internetMessageId; + } + } + } catch (searchErr) { + console.warn('[Graph Mailer] Failed to retrieve actual InternetMessageId from Sent Items:', searchErr.message); + } + + return { internetMessageId: actualMessageId }; } const errText = await res.text(); diff --git a/utils/smtpMailer.js b/utils/smtpMailer.js index 6a78928..a33f148 100644 --- a/utils/smtpMailer.js +++ b/utils/smtpMailer.js @@ -29,7 +29,7 @@ function getTransporter() { * Invia una email tramite SMTP (nodemailer). * Stessa interfaccia di graphMailer.sendMail. */ -async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments = [], inlineImages = [], inReplyTo, references }) { +async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments = [], inlineImages = [], inReplyTo, references, messageId }) { const transporter = getTransporter(); const mailOptions = { @@ -41,6 +41,7 @@ async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments html: bodyHtml, inReplyTo, references, + messageId, attachments: [ ...attachments.map(a => ({ filename: a.filename, @@ -57,6 +58,7 @@ async function sendMail({ to, cc = [], bcc = [], subject, bodyHtml, attachments }; await transporter.sendMail(mailOptions); + return { internetMessageId: messageId }; } module.exports = { sendMail };