fix: correzione doppia consuntivazione note. fix: utenti ldap. feat: ricerca per customer useers. feat: possiblità di rimuovere le note
This commit is contained in:
+168
-23
@@ -192,30 +192,89 @@ router.get('/lock-types', async (req, res) => {
|
||||
router.get('/customer-companies/search', async (req, res) => {
|
||||
try {
|
||||
const { q = '' } = req.query;
|
||||
let result;
|
||||
if (!q) {
|
||||
result = await pool.query(
|
||||
`SELECT customer_id, name
|
||||
FROM customer_company
|
||||
WHERE valid_id = 1
|
||||
ORDER BY name
|
||||
LIMIT 20`
|
||||
);
|
||||
} else {
|
||||
const searchTerm = `%${q}%`;
|
||||
result = await pool.query(
|
||||
`SELECT customer_id, name
|
||||
FROM customer_company
|
||||
WHERE valid_id = 1 AND (
|
||||
customer_id ILIKE $1 OR
|
||||
name ILIKE $1
|
||||
)
|
||||
ORDER BY name
|
||||
LIMIT 20`,
|
||||
[searchTerm]
|
||||
);
|
||||
|
||||
// 1. Fetch from local SQLite LDAP cache
|
||||
let localRows = [];
|
||||
try {
|
||||
if (!q) {
|
||||
localRows = db.prepare(`
|
||||
SELECT DISTINCT customer_id AS customer_id, customer_id AS name
|
||||
FROM customer_user_cache
|
||||
WHERE customer_id IS NOT NULL AND customer_id != ''
|
||||
ORDER BY customer_id
|
||||
LIMIT 500
|
||||
`).all();
|
||||
} else {
|
||||
const searchTerm = `%${q}%`;
|
||||
localRows = db.prepare(`
|
||||
SELECT DISTINCT customer_id AS customer_id, customer_id AS name
|
||||
FROM customer_user_cache
|
||||
WHERE customer_id IS NOT NULL AND customer_id != '' AND customer_id LIKE ?
|
||||
ORDER BY customer_id
|
||||
LIMIT 500
|
||||
`).all(searchTerm);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to query local customer cache:', e.message);
|
||||
}
|
||||
res.json(result.rows);
|
||||
|
||||
// 2. Fetch from OTRS Postgres DB
|
||||
let dbRows = [];
|
||||
try {
|
||||
if (!q) {
|
||||
const result = await pool.query(
|
||||
`SELECT customer_id, name
|
||||
FROM customer_company
|
||||
WHERE valid_id = 1
|
||||
ORDER BY name
|
||||
LIMIT 500`
|
||||
);
|
||||
dbRows = result.rows;
|
||||
} else {
|
||||
const searchTerm = `%${q}%`;
|
||||
const result = await pool.query(
|
||||
`SELECT customer_id, name
|
||||
FROM customer_company
|
||||
WHERE valid_id = 1 AND (
|
||||
customer_id ILIKE $1 OR
|
||||
name ILIKE $1
|
||||
)
|
||||
ORDER BY name
|
||||
LIMIT 500`,
|
||||
[searchTerm]
|
||||
);
|
||||
dbRows = result.rows;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to query OTRS customer_company table:', e.message);
|
||||
}
|
||||
|
||||
// 3. Merge results and remove duplicates by customer_id
|
||||
const seen = new Set();
|
||||
const merged = [];
|
||||
|
||||
// Prioritize OTRS database rows (which might have better names)
|
||||
for (const row of dbRows) {
|
||||
const cid = String(row.customer_id).trim();
|
||||
if (cid && !seen.has(cid.toLowerCase())) {
|
||||
seen.add(cid.toLowerCase());
|
||||
merged.push({ customer_id: cid, name: row.name || cid });
|
||||
}
|
||||
}
|
||||
|
||||
// Add local LDAP rows
|
||||
for (const row of localRows) {
|
||||
const cid = String(row.customer_id).trim();
|
||||
if (cid && !seen.has(cid.toLowerCase())) {
|
||||
seen.add(cid.toLowerCase());
|
||||
merged.push({ customer_id: cid, name: row.name || cid });
|
||||
}
|
||||
}
|
||||
|
||||
// Sort alphabetically by name
|
||||
merged.sort((a, b) => a.name.localeCompare(b.name, 'it', { sensitivity: 'base' }));
|
||||
|
||||
res.json(merged.slice(0, 500));
|
||||
} catch (err) {
|
||||
console.error('Error searching customer companies:', err);
|
||||
res.status(500).json({ error: err.message });
|
||||
@@ -226,6 +285,92 @@ router.get('/customer-companies/search', async (req, res) => {
|
||||
router.get('/customer-users/search', async (req, res) => {
|
||||
const { q = '', customer_company_id } = req.query;
|
||||
|
||||
// If q is empty, we return a merged list for populating filter dropdowns
|
||||
if (!q) {
|
||||
let localRows = [];
|
||||
try {
|
||||
if (customer_company_id) {
|
||||
localRows = db.prepare(`
|
||||
SELECT login, email, first_name, last_name, customer_id
|
||||
FROM customer_user_cache
|
||||
WHERE customer_id = ?
|
||||
ORDER BY last_name, first_name
|
||||
LIMIT 1000
|
||||
`).all(customer_company_id);
|
||||
} else {
|
||||
localRows = db.prepare(`
|
||||
SELECT login, email, first_name, last_name, customer_id
|
||||
FROM customer_user_cache
|
||||
ORDER BY last_name, first_name
|
||||
LIMIT 1000
|
||||
`).all();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to query local customer user cache:', e.message);
|
||||
}
|
||||
|
||||
let dbRows = [];
|
||||
try {
|
||||
let queryText = `
|
||||
SELECT login, email, first_name, last_name, customer_id
|
||||
FROM customer_user
|
||||
WHERE valid_id = 1
|
||||
`;
|
||||
let queryParams = [];
|
||||
if (customer_company_id) {
|
||||
queryText += ` AND customer_id = $1`;
|
||||
queryParams.push(customer_company_id);
|
||||
}
|
||||
queryText += ` ORDER BY last_name, first_name LIMIT 1000`;
|
||||
|
||||
const result = await pool.query(queryText, queryParams);
|
||||
dbRows = result.rows;
|
||||
} catch (e) {
|
||||
console.warn('Failed to query OTRS customer_user table:', e.message);
|
||||
}
|
||||
|
||||
// Merge and deduplicate by login
|
||||
const seen = new Set();
|
||||
const merged = [];
|
||||
|
||||
for (const row of dbRows) {
|
||||
const login = String(row.login).trim();
|
||||
if (login && !seen.has(login.toLowerCase())) {
|
||||
seen.add(login.toLowerCase());
|
||||
merged.push({
|
||||
login,
|
||||
email: row.email || '',
|
||||
first_name: row.first_name || '',
|
||||
last_name: row.last_name || '',
|
||||
customer_id: row.customer_id || ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const row of localRows) {
|
||||
const login = String(row.login).trim();
|
||||
if (login && !seen.has(login.toLowerCase())) {
|
||||
seen.add(login.toLowerCase());
|
||||
merged.push({
|
||||
login,
|
||||
email: row.email || '',
|
||||
first_name: row.first_name || '',
|
||||
last_name: row.last_name || '',
|
||||
customer_id: row.customer_id || ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort alphabetically by last name, first name
|
||||
merged.sort((a, b) => {
|
||||
const nameA = `${a.last_name} ${a.first_name}`.trim();
|
||||
const nameB = `${b.last_name} ${b.first_name}`.trim();
|
||||
return nameA.localeCompare(nameB, 'it', { sensitivity: 'base' });
|
||||
});
|
||||
|
||||
return res.json(merged.slice(0, 1000));
|
||||
}
|
||||
|
||||
// 1. Try to search via OTRS GenericInterface REST API if configured
|
||||
if (process.env.OTRS_API_URL && process.env.OTRS_API_USER) {
|
||||
try {
|
||||
|
||||
+210
-42
@@ -19,6 +19,24 @@ async function resolveAgentName(agentId) {
|
||||
}
|
||||
|
||||
|
||||
// Helper: resolve agent details for a_from header (best-effort, fallback to 'OTRS Turbo Agent')
|
||||
async function resolveAgentFromHeader(agentId, client = pool) {
|
||||
try {
|
||||
const r = await client.query(
|
||||
`SELECT first_name, last_name, email, login FROM users WHERE id = $1`,
|
||||
[agentId]
|
||||
);
|
||||
if (r.rows.length > 0) {
|
||||
const u = r.rows[0];
|
||||
const fullName = [u.first_name, u.last_name].filter(Boolean).join(' ') || u.login || 'Agent';
|
||||
const email = u.email || 'agent@localhost';
|
||||
return `"${fullName}" <${email}>`;
|
||||
}
|
||||
} catch (_) { /* ignore */ }
|
||||
return 'OTRS Turbo Agent';
|
||||
}
|
||||
|
||||
|
||||
// Helper for OTRS CE GenericInterface REST API calls
|
||||
async function otrsRequest(method, path, bodyData = {}) {
|
||||
const OTRS_API_USER = process.env.OTRS_API_USER;
|
||||
@@ -72,7 +90,7 @@ async function otrsRequest(method, path, bodyData = {}) {
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
queue_id, state_id, priority_id, user_id, type_id,
|
||||
queue_id, state_id, priority_id, user_id, type_id, customer_user_id,
|
||||
search, sort_by = 'create_time', sort_dir = 'DESC',
|
||||
page = 1, per_page = 50,
|
||||
date_from, date_to
|
||||
@@ -82,6 +100,23 @@ router.get('/', async (req, res) => {
|
||||
const params = [];
|
||||
let paramIdx = 1;
|
||||
|
||||
if (customer_user_id) {
|
||||
let logins = [];
|
||||
if (Array.isArray(customer_user_id)) {
|
||||
logins = customer_user_id.map(l => String(l).trim()).filter(Boolean);
|
||||
} else if (typeof customer_user_id === 'string') {
|
||||
logins = customer_user_id.split(',').map(l => l.trim()).filter(Boolean);
|
||||
} else {
|
||||
logins = [String(customer_user_id).trim()];
|
||||
}
|
||||
|
||||
if (logins.length > 0) {
|
||||
const placeholders = logins.map(() => `$${paramIdx++}`).join(', ');
|
||||
conditions.push(`t.customer_user_id IN (${placeholders})`);
|
||||
params.push(...logins);
|
||||
}
|
||||
}
|
||||
|
||||
if (queue_id) {
|
||||
conditions.push(`t.queue_id = $${paramIdx++}`);
|
||||
params.push(parseInt(queue_id));
|
||||
@@ -336,7 +371,11 @@ router.get('/:id', async (req, res) => {
|
||||
LEFT JOIN communication_channel cc ON a.communication_channel_id = cc.id
|
||||
LEFT JOIN article_data_mime adm ON a.id = adm.article_id
|
||||
LEFT JOIN users creator ON a.create_by = creator.id
|
||||
LEFT JOIN time_accounting ta ON a.id = ta.article_id
|
||||
LEFT JOIN (
|
||||
SELECT article_id, SUM(time_unit) AS time_unit
|
||||
FROM time_accounting
|
||||
GROUP BY article_id
|
||||
) ta ON a.id = ta.article_id
|
||||
WHERE a.ticket_id = $1
|
||||
ORDER BY a.create_time DESC, a.id DESC`,
|
||||
[id]
|
||||
@@ -725,22 +764,6 @@ router.patch('/:id', async (req, res) => {
|
||||
}
|
||||
const result = await otrsRequest('PATCH', `/Ticket/${id}`, reqBody);
|
||||
|
||||
// Option 1: Log the time directly to the DB if the API succeeded but OTRS didn't save it
|
||||
if (!isNaN(timeUnit) && timeUnit > 0 && result && result.ArticleID) {
|
||||
try {
|
||||
await pool.query(
|
||||
`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, result.ArticleID, timeUnit, operatorId]
|
||||
);
|
||||
console.log(`[Time Accounting] Successfully logged ${timeUnit} minutes for ticket ${id} via DB insert.`);
|
||||
} catch (timeDbErr) {
|
||||
console.error('[Time Accounting] Failed to log time unit in database:', timeDbErr.message);
|
||||
}
|
||||
}
|
||||
|
||||
const isClosing = updates.ticket_state_id && current.ticket_state_id !== updates.ticket_state_id;
|
||||
resolveAgentName(operatorId).then(agente_nome => {
|
||||
logAttivita({
|
||||
@@ -911,6 +934,8 @@ router.patch('/:id', async (req, res) => {
|
||||
const contentSize = Buffer.byteLength(htmlBody, 'utf-8');
|
||||
const base64Body = binaryBody.toString('base64');
|
||||
|
||||
const fromHeader = await resolveAgentFromHeader(operatorId, client);
|
||||
|
||||
// Create article_data_mime
|
||||
await client.query(
|
||||
`INSERT INTO article_data_mime (
|
||||
@@ -924,7 +949,7 @@ router.patch('/:id', async (req, res) => {
|
||||
'text/html; charset=utf-8', EXTRACT(EPOCH FROM NOW())::INTEGER, $5,
|
||||
NOW(), $6, NOW(), $6
|
||||
)`,
|
||||
[articleId, 'OTRS Turbo Agent', 'Consuntivazione', htmlBody, contentPath, operatorId]
|
||||
[articleId, fromHeader, 'Consuntivazione', htmlBody, contentPath, operatorId]
|
||||
);
|
||||
|
||||
// Create article_data_mime_attachment for OTRS CE HTML rendering
|
||||
@@ -995,7 +1020,11 @@ router.get('/:id/articles', async (req, res) => {
|
||||
LEFT JOIN communication_channel cc ON a.communication_channel_id = cc.id
|
||||
LEFT JOIN article_data_mime adm ON a.id = adm.article_id
|
||||
LEFT JOIN users creator ON a.create_by = creator.id
|
||||
LEFT JOIN time_accounting ta ON a.id = ta.article_id
|
||||
LEFT JOIN (
|
||||
SELECT article_id, SUM(time_unit) AS time_unit
|
||||
FROM time_accounting
|
||||
GROUP BY article_id
|
||||
) ta ON a.id = ta.article_id
|
||||
WHERE a.ticket_id = $1
|
||||
ORDER BY a.create_time DESC, a.id DESC`,
|
||||
[id]
|
||||
@@ -1047,21 +1076,7 @@ router.post('/:id/articles', async (req, res) => {
|
||||
|
||||
const result = await otrsRequest('PATCH', `/Ticket/${id}`, payload);
|
||||
|
||||
// Option 1: Log the time directly to the DB if the API succeeded but OTRS didn't save it
|
||||
if (time_unit && result && result.ArticleID) {
|
||||
try {
|
||||
await pool.query(
|
||||
`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, result.ArticleID, parseFloat(time_unit), operatorId]
|
||||
);
|
||||
console.log(`[Time Accounting] Successfully logged ${time_unit} minutes for ticket ${id} via DB insert.`);
|
||||
} catch (timeDbErr) {
|
||||
console.error('[Time Accounting] Failed to log time unit in database:', timeDbErr.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
resolveAgentName(operatorId).then(agente_nome => {
|
||||
logAttivita({
|
||||
@@ -1072,14 +1087,24 @@ router.post('/:id/articles', async (req, res) => {
|
||||
esito: 'successo',
|
||||
});
|
||||
});
|
||||
|
||||
return res.status(201).json({
|
||||
message: 'Nota aggiunta! (via API REST)',
|
||||
article_id: result.ArticleID,
|
||||
result
|
||||
});
|
||||
} catch (restErr) {
|
||||
console.warn('Failed to add article via REST API, falling back to database insert:', restErr.message);
|
||||
// Fall through to standard direct database update below
|
||||
console.error('Failed to add article via REST API:', restErr.message);
|
||||
resolveAgentName(operatorId).then(agente_nome => {
|
||||
logAttivita({
|
||||
agente_id: operatorId,
|
||||
agente_nome,
|
||||
titolo_azione: 'Aggiunta Nota',
|
||||
azione: { ticket_id: id, error: restErr.message },
|
||||
esito: 'errore'
|
||||
});
|
||||
});
|
||||
return res.status(500).json({ error: 'Errore durante l\'aggiunta della nota tramite API: ' + restErr.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1090,6 +1115,62 @@ router.post('/:id/articles', async (req, res) => {
|
||||
const { subject, body, is_visible_for_customer = 0, time_unit, attachments } = req.body;
|
||||
const operatorId = parseInt(req.headers['x-agent-id'], 10) || 1;
|
||||
|
||||
// Check if this article was already inserted (e.g., via REST API or concurrent request)
|
||||
const thirtySecondsAgo = new Date(Date.now() - 30000);
|
||||
const recentCheck = await client.query(
|
||||
`SELECT a.id
|
||||
FROM article a
|
||||
JOIN article_data_mime adm ON a.id = adm.article_id
|
||||
WHERE a.ticket_id = $1
|
||||
AND adm.a_subject = $2
|
||||
AND adm.a_body = $3
|
||||
AND a.create_time >= $4`,
|
||||
[id, subject || 'Nota interna', body, thirtySecondsAgo]
|
||||
);
|
||||
|
||||
if (recentCheck.rows.length > 0) {
|
||||
const existingArticleId = recentCheck.rows[0].id;
|
||||
console.log(`[Fallback Check] Found existing recent article (ID: ${existingArticleId}) in database. Skipping duplicate insert.`);
|
||||
|
||||
await client.query('BEGIN');
|
||||
// If time_unit is provided, insert into time_accounting if not already present
|
||||
if (time_unit !== undefined && time_unit !== null && time_unit !== '') {
|
||||
const parsedTime = parseFloat(time_unit);
|
||||
if (!isNaN(parsedTime) && parsedTime > 0) {
|
||||
const timeCheck = await client.query(
|
||||
`SELECT id FROM time_accounting WHERE ticket_id = $1 AND article_id = $2`,
|
||||
[id, existingArticleId]
|
||||
);
|
||||
if (timeCheck.rows.length === 0) {
|
||||
await client.query(
|
||||
`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, existingArticleId, parsedTime, operatorId]
|
||||
);
|
||||
console.log(`[Fallback Check] Logged missing time accounting (${parsedTime} min) for existing article.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
await client.query('COMMIT');
|
||||
|
||||
resolveAgentName(operatorId).then(agente_nome => {
|
||||
logAttivita({
|
||||
agente_id: operatorId,
|
||||
agente_nome,
|
||||
titolo_azione: 'Aggiunta Nota',
|
||||
azione: { ticket_id: id, subject, time_unit, has_attachments: !!(attachments && attachments.length) },
|
||||
esito: 'successo',
|
||||
});
|
||||
});
|
||||
|
||||
return res.status(201).json({
|
||||
article_id: existingArticleId,
|
||||
message: 'Nota aggiunta! (rilevata in DB)',
|
||||
});
|
||||
}
|
||||
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Verify ticket exists
|
||||
@@ -1129,6 +1210,8 @@ router.post('/:id/articles', async (req, res) => {
|
||||
const now = new Date();
|
||||
const contentPath = `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')}`;
|
||||
|
||||
const fromHeader = await resolveAgentFromHeader(operatorId, client);
|
||||
|
||||
// Create article_data_mime
|
||||
await client.query(
|
||||
`INSERT INTO article_data_mime (
|
||||
@@ -1142,7 +1225,7 @@ router.post('/:id/articles', async (req, res) => {
|
||||
$5, EXTRACT(EPOCH FROM NOW())::INTEGER, $6,
|
||||
NOW(), $7, NOW(), $7
|
||||
)`,
|
||||
[articleId, 'OTRS Turbo Agent', subject || 'Nota interna', body, contentType, contentPath, operatorId]
|
||||
[articleId, fromHeader, subject || 'Nota interna', body, contentType, contentPath, operatorId]
|
||||
);
|
||||
|
||||
// Create article_data_mime_attachment for OTRS CE HTML rendering (using file-1 and base64 encoded text)
|
||||
@@ -1433,6 +1516,90 @@ router.post('/articles/:articleId/retrodata-article', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/tickets/articles/:articleId — Delete an article/note
|
||||
router.delete('/articles/:articleId', async (req, res) => {
|
||||
const { articleId } = req.params;
|
||||
const operatorId = parseInt(req.headers['x-agent-id'], 10) || 1;
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// 1. Set article_id in ticket_history to NULL to avoid constraint violation
|
||||
await client.query(
|
||||
`UPDATE ticket_history SET article_id = NULL WHERE article_id = $1`,
|
||||
[articleId]
|
||||
);
|
||||
|
||||
// Delete flags from article_flag
|
||||
await client.query(
|
||||
`DELETE FROM article_flag WHERE article_id = $1`,
|
||||
[articleId]
|
||||
);
|
||||
|
||||
// Delete search index entries from article_search_index
|
||||
await client.query(
|
||||
`DELETE FROM article_search_index WHERE article_id = $1`,
|
||||
[articleId]
|
||||
);
|
||||
|
||||
// 2. Delete time accounting entries
|
||||
await client.query(
|
||||
`DELETE FROM time_accounting WHERE article_id = $1`,
|
||||
[articleId]
|
||||
);
|
||||
|
||||
// 3. Delete attachments
|
||||
await client.query(
|
||||
`DELETE FROM article_data_mime_attachment WHERE article_id = $1`,
|
||||
[articleId]
|
||||
);
|
||||
|
||||
// 4. Delete mime data
|
||||
await client.query(
|
||||
`DELETE FROM article_data_mime WHERE article_id = $1`,
|
||||
[articleId]
|
||||
);
|
||||
|
||||
// 5. Delete article itself
|
||||
const deleteRes = await client.query(
|
||||
`DELETE FROM article WHERE id = $1 RETURNING ticket_id`,
|
||||
[articleId]
|
||||
);
|
||||
|
||||
const ticketId = deleteRes.rows.length > 0 ? deleteRes.rows[0].ticket_id : null;
|
||||
|
||||
await client.query('COMMIT');
|
||||
|
||||
resolveAgentName(operatorId).then(agente_nome => {
|
||||
logAttivita({
|
||||
agente_id: operatorId,
|
||||
agente_nome,
|
||||
titolo_azione: 'Eliminazione Articolo',
|
||||
azione: { article_id: articleId, ticket_id: ticketId },
|
||||
esito: 'successo',
|
||||
});
|
||||
});
|
||||
|
||||
res.json({ message: 'Articolo eliminato con successo!' });
|
||||
} catch (err) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error('Error deleting article:', err);
|
||||
resolveAgentName(operatorId).then(agente_nome => {
|
||||
logAttivita({
|
||||
agente_id: operatorId,
|
||||
agente_nome,
|
||||
titolo_azione: 'Eliminazione Articolo',
|
||||
azione: { article_id: articleId, error: err.message },
|
||||
esito: 'errore',
|
||||
});
|
||||
});
|
||||
res.status(500).json({ error: err.message });
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/tickets/attachments/:id — Download/View attachment
|
||||
router.get('/attachments/:id', async (req, res) => {
|
||||
try {
|
||||
@@ -1902,6 +2069,7 @@ router.post('/auto-time', async (req, res) => {
|
||||
const binaryBody = Buffer.from(htmlBody, 'utf-8');
|
||||
const contentSize = Buffer.byteLength(htmlBody, 'utf-8');
|
||||
const base64Body = binaryBody.toString('base64');
|
||||
const fromHeader = await resolveAgentFromHeader(operatorId, client);
|
||||
|
||||
await client.query(
|
||||
`INSERT INTO article_data_mime (
|
||||
@@ -1909,11 +2077,11 @@ router.post('/auto-time', async (req, res) => {
|
||||
a_content_type, incoming_time, content_path,
|
||||
create_time, create_by, change_time, change_by
|
||||
) VALUES (
|
||||
$1, 'OTRS Turbo Agent', '', $2, $3,
|
||||
'text/html; charset=utf-8', EXTRACT(EPOCH FROM NOW())::INTEGER, $4,
|
||||
NOW(), $5, NOW(), $5
|
||||
$1, $2, '', $3, $4,
|
||||
'text/html; charset=utf-8', EXTRACT(EPOCH FROM NOW())::INTEGER, $5,
|
||||
NOW(), $6, NOW(), $6
|
||||
)`,
|
||||
[articleId, subject, htmlBody, contentPath, operatorId]
|
||||
[articleId, fromHeader, subject, htmlBody, contentPath, operatorId]
|
||||
);
|
||||
|
||||
// Create article attachment (file-1) for OTRS CE HTML display
|
||||
|
||||
Reference in New Issue
Block a user