This commit is contained in:
2026-04-06 23:39:27 +05:00
parent 76ee4b7ac3
commit 73ace950fa
4 changed files with 235 additions and 9 deletions

View File

@@ -232,11 +232,171 @@ function getStatusText(status) {
return statusMap[status] || status;
}
/**
* Отправляет сводку о непрочитанных сообщениях в чатах всем пользователям,
* у которых есть такие сообщения (раз в час).
*/
async function sendChatSummaryNotifications() {
const db = getDb();
if (!db) {
console.error('❌ База данных не доступна для отправки сводки чата');
return;
}
// Получаем всех пользователей, у которых включены email уведомления
const users = await new Promise((resolve, reject) => {
db.all(`
SELECT u.id, u.name, u.email, u.login,
COALESCE(us.email_notifications, 1) as email_notifications,
us.notification_email,
us.last_chat_notification_sent_at
FROM users u
LEFT JOIN user_settings us ON u.id = us.user_id
WHERE u.email IS NOT NULL AND u.email != ''
`, (err, rows) => {
if (err) reject(err);
else resolve(rows || []);
});
});
for (const user of users) {
// Проверяем настройки уведомлений
if (!user.email_notifications) continue;
const emailTo = user.notification_email || user.email;
if (!emailTo) continue;
// Время последней отправки уведомления (или NULL тогда берём текущее, но при логине уже должно быть установлено)
let lastSent = user.last_chat_notification_sent_at;
if (!lastSent) {
// Если нет метки устанавливаем сейчас и пропускаем (при первом входе всё равно сбросится)
await new Promise((resolve) => {
db.run(
`UPDATE user_settings SET last_chat_notification_sent_at = CURRENT_TIMESTAMP WHERE user_id = ?`,
[user.id],
resolve
);
});
continue;
}
// Получаем задачи с количеством новых сообщений после lastSent
const tasks = await new Promise((resolve, reject) => {
db.all(`
SELECT
t.id,
t.title,
COUNT(m.id) as new_messages_count
FROM tasks t
LEFT JOIN task_chat_messages m ON t.id = m.task_id
AND m.created_at > ?
AND m.is_deleted = 0
WHERE (t.created_by = ? OR EXISTS (
SELECT 1 FROM task_assignments ta
WHERE ta.task_id = t.id AND ta.user_id = ?
))
GROUP BY t.id
HAVING new_messages_count > 0
`, [lastSent, user.id, user.id], (err, rows) => {
if (err) reject(err);
else resolve(rows || []);
});
});
if (tasks.length === 0) continue;
// Формируем HTMLписьмо со списком задач
const appUrl = process.env.APP_URL || 'http://localhost:3000';
let tasksHtml = '';
for (const task of tasks) {
const taskUrl = `${appUrl}/task?id=${task.id}`;
tasksHtml += `
<li style="margin-bottom: 15px;">
<strong><a href="${taskUrl}" style="color: #3498db;">${escapeHtml(task.title)}</a></strong><br>
Новых сообщений: ${task.new_messages_count}
</li>
`;
}
const subject = `📬 Новые сообщения в чатах задач (${tasks.length} задач)`;
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #3498db; color: white; padding: 15px; border-radius: 10px 10px 0 0; }
.content { padding: 20px; border: 1px solid #ddd; border-top: none; }
.footer { margin-top: 20px; font-size: 12px; color: #666; text-align: center; }
ul { padding-left: 20px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>💬 Новые сообщения в чатах</h2>
</div>
<div class="content">
<p>Здравствуйте, ${escapeHtml(user.name)}!</p>
<p>У вас есть непрочитанные сообщения в следующих задачах:</p>
<ul>${tasksHtml}</ul>
<p>Вы получили это письмо, потому что подписаны на уведомления о чатах.<br>
Уведомления приходят раз в час. Чтобы отключить их, измените настройки в личном кабинете.</p>
</div>
<div class="footer">
<p>© School CRM, ${new Date().getFullYear()}</p>
</div>
</div>
</body>
</html>
`;
// Отправляем email
const result = await emailNotifications.sendEmailNotification(
emailTo,
subject,
htmlContent,
user.id,
null,
'chat_summary'
);
if (result.sent) {
// Обновляем время последней отправки
await new Promise((resolve) => {
db.run(
`UPDATE user_settings SET last_chat_notification_sent_at = CURRENT_TIMESTAMP WHERE user_id = ?`,
[user.id],
resolve
);
});
console.log(`✅ Отправлена сводка чата для ${user.login} (${tasks.length} задач)`);
} else {
console.error(`Не удалось отправить сводку чата для ${user.login}: ${result.error}`);
// Не обновляем last_sent в следующий раз попробуем снова
}
}
}
/**
* Вспомогательная функция для экранирования HTML
*/
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
return m;
});
}
// Экспортируем функции
module.exports = {
sendTaskNotifications,
checkUpcomingDeadlines,
sendDeadlineNotification,
getStatusText,
emailNotifications // Экспортируем для доступа к методам
emailNotifications,
sendChatSummaryNotifications
};