fix: ora le ricerche asincrone non si sovrappongono più alle attività. feat: aggiunta funzione Storico Consuntivazione
This commit is contained in:
1 parent
ba23635c7d
commit
3650bf5790
9 files changed
+737
-33
No files matched your search
+96
-4
@@ -754,7 +754,7 @@ router.patch('/:id', async (req, res) => {
|
||||
let current;
|
||||
try {
|
||||
const currentResult = await pool.query(
|
||||
`SELECT ticket_state_id, ticket_priority_id, queue_id, user_id, responsible_user_id, type_id, title, ticket_lock_id, customer_id, customer_user_id
|
||||
`SELECT tn, ticket_state_id, ticket_priority_id, queue_id, user_id, responsible_user_id, type_id, title, ticket_lock_id, customer_id, customer_user_id
|
||||
FROM ticket WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
@@ -832,7 +832,7 @@ router.patch('/:id', async (req, res) => {
|
||||
agente_id: operatorId,
|
||||
agente_nome,
|
||||
titolo_azione: isClosing ? 'Chiusura Ticket' : 'Modifica Rapida Ticket',
|
||||
azione: { ticket_id: id, ...updates },
|
||||
azione: { ticket_id: id, tn: current.tn, ...updates },
|
||||
esito: 'successo',
|
||||
});
|
||||
});
|
||||
@@ -1048,7 +1048,7 @@ router.patch('/:id', async (req, res) => {
|
||||
agente_id: operatorId,
|
||||
agente_nome,
|
||||
titolo_azione: isClosingDB ? 'Chiusura Ticket' : 'Modifica Rapida Ticket',
|
||||
azione: { ticket_id: id, ...updates },
|
||||
azione: { ticket_id: id, tn: current.tn, ...updates },
|
||||
esito: 'successo',
|
||||
});
|
||||
});
|
||||
@@ -1392,7 +1392,7 @@ router.post('/:id/articles', async (req, res) => {
|
||||
agente_id: operatorId,
|
||||
agente_nome,
|
||||
titolo_azione: 'Aggiunta Nota',
|
||||
azione: { ticket_id: id, subject, time_unit, has_attachments: !!(attachments && attachments.length) },
|
||||
azione: { ticket_id: id, tn: t.tn, subject, time_unit, has_attachments: !!(attachments && attachments.length) },
|
||||
esito: 'successo',
|
||||
});
|
||||
});
|
||||
@@ -1874,6 +1874,98 @@ router.post('/merge', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// GET /api/tickets/time-accounting/summary — Summary of notes/emails and time per agent per day
|
||||
// ============================================================
|
||||
router.get('/time-accounting/summary', async (req, res) => {
|
||||
try {
|
||||
const operatorId = parseInt(req.headers['x-agent-id'], 10) || 1;
|
||||
const agentId = parseInt(req.query.agent_id, 10) || operatorId;
|
||||
const dateStr = req.query.date || new Date().toISOString().slice(0, 10);
|
||||
|
||||
const targetDate = new Date(`${dateStr}T00:00:00Z`);
|
||||
const startDateObj = new Date(targetDate);
|
||||
startDateObj.setDate(startDateObj.getDate() - 7);
|
||||
const endDateObj = new Date(targetDate);
|
||||
endDateObj.setDate(endDateObj.getDate() + 7);
|
||||
|
||||
const startTime = `${startDateObj.toISOString().slice(0, 10)} 00:00:00`;
|
||||
const endTime = `${endDateObj.toISOString().slice(0, 10)} 23:59:59`;
|
||||
|
||||
const result = await pool.query(
|
||||
`SELECT
|
||||
a.id AS article_id,
|
||||
a.ticket_id,
|
||||
t.tn AS ticket_tn,
|
||||
t.title AS ticket_title,
|
||||
ast.name AS sender_type,
|
||||
cc.name AS channel,
|
||||
adm.a_subject,
|
||||
adm.a_body,
|
||||
adm.a_from,
|
||||
adm.a_to,
|
||||
a.create_time,
|
||||
COALESCE(ta.time_unit, 0) AS time_unit
|
||||
FROM article a
|
||||
JOIN ticket t ON a.ticket_id = t.id
|
||||
JOIN article_sender_type ast ON a.article_sender_type_id = ast.id
|
||||
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 (
|
||||
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.create_by = $1
|
||||
AND a.create_time >= $2
|
||||
AND a.create_time <= $3
|
||||
ORDER BY a.create_time DESC, a.id DESC`,
|
||||
[agentId, startTime, endTime]
|
||||
);
|
||||
|
||||
const dailyTotals = {};
|
||||
const dayArticles = [];
|
||||
let totalMinutes = 0;
|
||||
|
||||
result.rows.forEach(r => {
|
||||
let isoDate = '';
|
||||
if (r.create_time instanceof Date) {
|
||||
const y = r.create_time.getUTCFullYear();
|
||||
const m = String(r.create_time.getUTCMonth() + 1).padStart(2, '0');
|
||||
const d = String(r.create_time.getUTCDate()).padStart(2, '0');
|
||||
isoDate = `${y}-${m}-${d}`;
|
||||
} else {
|
||||
isoDate = String(r.create_time || '').slice(0, 10);
|
||||
}
|
||||
const timeVal = parseFloat(r.time_unit || 0);
|
||||
|
||||
dailyTotals[isoDate] = (dailyTotals[isoDate] || 0) + timeVal;
|
||||
|
||||
if (isoDate === dateStr) {
|
||||
dayArticles.push(r);
|
||||
totalMinutes += timeVal;
|
||||
}
|
||||
});
|
||||
|
||||
const targetMinutes = parseInt(process.env.DAILY_TARGET_TIME, 10) || 480;
|
||||
const workDaysRaw = process.env.WORK_DAYS || '1,2,3,4,5';
|
||||
const workDays = workDaysRaw.split(',').map(x => parseInt(x.trim(), 10)).filter(x => !isNaN(x));
|
||||
|
||||
res.json({
|
||||
agent_id: agentId,
|
||||
date: dateStr,
|
||||
total_minutes: totalMinutes,
|
||||
target_minutes: targetMinutes,
|
||||
work_days: workDays,
|
||||
articles: dayArticles,
|
||||
daily_totals: dailyTotals,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error fetching time accounting summary:', err);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// PUT /api/articles/:articleId/time — Update time accounted for an article
|
||||
// ============================================================
|
||||
|
||||
Reference in new issue
Block a user