feat: potenziata visualizzazione storico consuntivazione. style: disabilitata apertura massiva. feat: migliorata modifica pdf e scaricamento di tutti gli allegati tramite shortcut.
This commit is contained in:
1 parent
b1754c3a67
commit
c7b18ac959
10 files changed
+1545
-191
No files matched your search
+36
-7
@@ -192,7 +192,7 @@ router.get('/user-signatures', (req, res) => {
|
||||
try {
|
||||
const operatorId = parseInt(req.headers['x-agent-id'] || '1', 10);
|
||||
const signatures = db.prepare(`
|
||||
SELECT * FROM user_signatures WHERE agent_id = ? ORDER BY id DESC
|
||||
SELECT * FROM user_signatures WHERE agent_id = ? ORDER BY is_default DESC, id DESC
|
||||
`).all(operatorId);
|
||||
res.json(signatures);
|
||||
} catch (err) {
|
||||
@@ -205,19 +205,24 @@ router.get('/user-signatures', (req, res) => {
|
||||
router.post('/user-signatures', (req, res) => {
|
||||
try {
|
||||
const operatorId = parseInt(req.headers['x-agent-id'] || '1', 10);
|
||||
const { name, image_data } = req.body;
|
||||
const { name, image_data, is_default } = req.body;
|
||||
|
||||
if (!name || !image_data) {
|
||||
return res.status(400).json({ error: 'Nome e dati immagine sono obbligatori' });
|
||||
}
|
||||
|
||||
const defaultVal = is_default ? 1 : 0;
|
||||
if (defaultVal === 1) {
|
||||
db.prepare(`UPDATE user_signatures SET is_default = 0 WHERE agent_id = ?`).run(operatorId);
|
||||
}
|
||||
|
||||
const info = db.prepare(`
|
||||
INSERT INTO user_signatures (agent_id, name, image_data)
|
||||
VALUES (?, ?, ?)
|
||||
`).run(operatorId, name.trim(), image_data);
|
||||
INSERT INTO user_signatures (agent_id, name, image_data, is_default)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`).run(operatorId, name.trim(), image_data, defaultVal);
|
||||
|
||||
const signatures = db.prepare(`
|
||||
SELECT * FROM user_signatures WHERE agent_id = ? ORDER BY id DESC
|
||||
SELECT * FROM user_signatures WHERE agent_id = ? ORDER BY is_default DESC, id DESC
|
||||
`).all(operatorId);
|
||||
|
||||
res.json({
|
||||
@@ -232,6 +237,30 @@ router.post('/user-signatures', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/config/user-signatures/:id/set-default — Set signature as default
|
||||
router.put('/user-signatures/:id/set-default', (req, res) => {
|
||||
try {
|
||||
const operatorId = parseInt(req.headers['x-agent-id'] || '1', 10);
|
||||
const { id } = req.params;
|
||||
|
||||
db.prepare(`UPDATE user_signatures SET is_default = 0 WHERE agent_id = ?`).run(operatorId);
|
||||
db.prepare(`UPDATE user_signatures SET is_default = 1 WHERE id = ? AND agent_id = ?`).run(id, operatorId);
|
||||
|
||||
const signatures = db.prepare(`
|
||||
SELECT * FROM user_signatures WHERE agent_id = ? ORDER BY is_default DESC, id DESC
|
||||
`).all(operatorId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Firma predefinita aggiornata con successo!',
|
||||
signatures
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error setting default signature:', err);
|
||||
res.status(500).json({ error: 'Errore aggiornamento firma predefinita', message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/config/user-signatures/:id — Delete user signature image
|
||||
router.delete('/user-signatures/:id', (req, res) => {
|
||||
try {
|
||||
@@ -241,7 +270,7 @@ router.delete('/user-signatures/:id', (req, res) => {
|
||||
db.prepare(`DELETE FROM user_signatures WHERE id = ? AND agent_id = ?`).run(id, operatorId);
|
||||
|
||||
const signatures = db.prepare(`
|
||||
SELECT * FROM user_signatures WHERE agent_id = ? ORDER BY id DESC
|
||||
SELECT * FROM user_signatures WHERE agent_id = ? ORDER BY is_default DESC, id DESC
|
||||
`).all(operatorId);
|
||||
|
||||
res.json({
|
||||
|
||||
+95
-39
@@ -2151,50 +2151,98 @@ router.post('/merge', async (req, res) => {
|
||||
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 agentIdsRaw = req.query.agent_ids || req.query.agent_id || String(operatorId);
|
||||
const agentIds = agentIdsRaw.split(',').map(x => parseInt(x.trim(), 10)).filter(x => !isNaN(x));
|
||||
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() - 14);
|
||||
const endDateObj = new Date(targetDate);
|
||||
endDateObj.setDate(endDateObj.getDate() + 14);
|
||||
const rangeParam = req.query.range || '35';
|
||||
const isAll = rangeParam === 'all';
|
||||
let result;
|
||||
if (isAll) {
|
||||
result = await pool.query(
|
||||
`SELECT
|
||||
a.id AS article_id,
|
||||
a.ticket_id,
|
||||
a.create_by AS agent_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 = ANY($1::int[])
|
||||
ORDER BY a.create_time DESC, a.id DESC`,
|
||||
[agentIds]
|
||||
);
|
||||
} else {
|
||||
const targetDate = new Date(`${dateStr}T00:00:00Z`);
|
||||
let startDateObj, endDateObj;
|
||||
|
||||
const startTime = `${startDateObj.toISOString().slice(0, 10)} 00:00:00`;
|
||||
const endTime = `${endDateObj.toISOString().slice(0, 10)} 23:59:59`;
|
||||
if (rangeParam === 'month') {
|
||||
const y = targetDate.getUTCFullYear();
|
||||
const m = targetDate.getUTCMonth();
|
||||
startDateObj = new Date(Date.UTC(y, m, 1));
|
||||
endDateObj = new Date(Date.UTC(y, m + 1, 0));
|
||||
} else {
|
||||
const daysBack = parseInt(rangeParam, 10) || 35;
|
||||
startDateObj = new Date(targetDate);
|
||||
startDateObj.setDate(startDateObj.getDate() - daysBack);
|
||||
endDateObj = new Date(targetDate);
|
||||
endDateObj.setDate(endDateObj.getDate() + 5);
|
||||
}
|
||||
|
||||
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 startTime = `${startDateObj.toISOString().slice(0, 10)} 00:00:00`;
|
||||
const endTime = `${endDateObj.toISOString().slice(0, 10)} 23:59:59`;
|
||||
|
||||
result = await pool.query(
|
||||
`SELECT
|
||||
a.id AS article_id,
|
||||
a.ticket_id,
|
||||
a.create_by AS agent_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 = ANY($1::int[])
|
||||
AND a.create_time >= $2
|
||||
AND a.create_time <= $3
|
||||
ORDER BY a.create_time DESC, a.id DESC`,
|
||||
[agentIds, startTime, endTime]
|
||||
);
|
||||
}
|
||||
|
||||
const dailyTotals = {};
|
||||
const dailyTotalsByAgent = {};
|
||||
const dayArticles = [];
|
||||
let totalMinutes = 0;
|
||||
|
||||
@@ -2209,9 +2257,15 @@ router.get('/time-accounting/summary', async (req, res) => {
|
||||
isoDate = String(r.create_time || '').slice(0, 10);
|
||||
}
|
||||
const timeVal = parseFloat(r.time_unit || 0);
|
||||
const agId = r.agent_id;
|
||||
|
||||
dailyTotals[isoDate] = (dailyTotals[isoDate] || 0) + timeVal;
|
||||
|
||||
if (!dailyTotalsByAgent[agId]) {
|
||||
dailyTotalsByAgent[agId] = {};
|
||||
}
|
||||
dailyTotalsByAgent[agId][isoDate] = (dailyTotalsByAgent[agId][isoDate] || 0) + timeVal;
|
||||
|
||||
if (isoDate === dateStr) {
|
||||
dayArticles.push(r);
|
||||
totalMinutes += timeVal;
|
||||
@@ -2223,13 +2277,15 @@ router.get('/time-accounting/summary', async (req, res) => {
|
||||
const workDays = workDaysRaw.split(',').map(x => parseInt(x.trim(), 10)).filter(x => !isNaN(x));
|
||||
|
||||
res.json({
|
||||
agent_id: agentId,
|
||||
agent_id: agentIds[0],
|
||||
agent_ids: agentIds,
|
||||
date: dateStr,
|
||||
total_minutes: totalMinutes,
|
||||
target_minutes: targetMinutes,
|
||||
work_days: workDays,
|
||||
articles: dayArticles,
|
||||
daily_totals: dailyTotals,
|
||||
daily_totals_by_agent: dailyTotalsByAgent,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error fetching time accounting summary:', err);
|
||||
|
||||
Reference in new issue
Block a user