fix: tentativo di fix per il riconoscimento del ticket ma otrs è stronzo-merda. feat: nella pagina di anteprima del ticket, aggiunto pulsante per aprire in una scheda interna.

This commit is contained in:
2026-07-15 00:07:34 +02:00
parent 47d11c311e
commit 1bbe561673
6 changed files with 38 additions and 13 deletions
+1 -3
View File
@@ -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;
+3 -2
View File
@@ -161,6 +161,7 @@ const TicketDetailView = {
<h2 class="ticket-detail-title" style="display:inline-flex; align-items:center; gap:6px; flex-wrap:wrap;">
<span class="copy-ticket-btn" data-tn="${App.escapeHtml(ticket.title || '')}" style="cursor: pointer; font-size: 0.85rem;" title="Copia titolo ticket">📋</span>
${App.escapeHtml(ticket.title || '(senza titolo)')}
<button class="open-tab-btn" data-id="${ticket.id}" data-tn="${ticket.tn}" data-title="${App.escapeHtml(ticket.title || '(senza titolo)')}" onclick="App.openTab(${ticket.id}, '${ticket.tn}', this.dataset.title); event.stopPropagation();" title="Apri in scheda" style="display:inline-flex; align-items:center; justify-content:center; width:16px; height:16px; background:var(--accent-primary); color:#fff; border:none; border-radius:50%; font-size:10px; font-weight:800; font-family:sans-serif; margin-left:6px; cursor:pointer; line-height:16px;">+</button>
</h2>
<div class="ticket-meta-badges">
<span class="badge badge-state" data-state-type="${(ticket.state_type || '').toLowerCase()}">${ticket.state_name}</span>
@@ -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');
+4 -3
View File
@@ -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
+1 -1
View File
@@ -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');
+26 -3
View File
@@ -60,7 +60,7 @@ async function getAccessToken() {
* @param {Array} [options.inlineImages] - [{ cid, content (base64), contentType }]
* @returns {Promise<void>}
*/
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();
+3 -1
View File
@@ -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 };