// notifications.js const { getDb } = require('./database'); const emailNotifications = require('./email-notifications'); /** * Отправляет уведомления о событиях в задаче согласно новой логике: * - Если инициатор = автор → уведомления всем исполнителям * - Если инициатор = исполнитель → уведомление только автору * - Иначе (например, администратор вне задачи) → уведомления всем, кроме инициатора (старое поведение) */ async function sendTaskNotifications(type, taskId, taskTitle, taskDescription, authorId, comment = '', status = '', userName = '') { try { const db = getDb(); if (!db) { console.log('❌ База данных не доступна для отправки уведомлений'); return; } console.log(`📢 Начинаем отправку уведомлений для задачи ${taskId}:`); console.log(` Тип: ${type}, Автор действия: ${authorId}, Название: ${taskTitle}`); // 1. Получаем автора задачи (создателя) const creator = await new Promise((resolve, reject) => { db.get(` SELECT t.created_by as user_id, u.name as user_name, u.login as user_login, u.email FROM tasks t LEFT JOIN users u ON t.created_by = u.id WHERE t.id = ? `, [taskId], (err, row) => { if (err) reject(err); else resolve(row); }); }); if (!creator) { console.error(`❌ Задача ${taskId} не найдена или у неё нет автора`); return; } // 2. Получаем всех исполнителей задачи const assignees = await new Promise((resolve, reject) => { db.all(` SELECT ta.user_id, u.name as user_name, u.login as user_login, u.email FROM task_assignments ta LEFT JOIN users u ON ta.user_id = u.id WHERE ta.task_id = ? `, [taskId], (err, rows) => { if (err) reject(err); else resolve(rows || []); }); }); // 3. Получаем информацию об авторе действия (инициаторе) const author = await new Promise((resolve, reject) => { db.get("SELECT name, login, email FROM users WHERE id = ?", [authorId], (err, row) => { if (err) reject(err); else resolve(row); }); }); const authorName = author ? author.name : 'Система'; const authorLogin = author ? author.login : 'system'; // 4. Определяем получателей уведомления согласно новой логике let recipients = []; // Проверяем, является ли инициатор автором задачи if (parseInt(authorId) === parseInt(creator.user_id)) { // Инициатор = автор → уведомления всем исполнителям recipients = assignees; console.log(` Инициатор является автором. Получатели: ${assignees.length} исполнителей`); } // Проверяем, является ли инициатор одним из исполнителей else { const isInitiatorAssignee = assignees.some(a => parseInt(a.user_id) === parseInt(authorId)); if (isInitiatorAssignee) { // Инициатор = исполнитель → уведомление только автору recipients = [creator]; console.log(` Инициатор является исполнителем. Получатель: автор`); } else { // Инициатор не является ни автором, ни исполнителем (например, администратор) // Отправляем уведомления всем участникам, кроме инициатора (старое поведение) recipients = [creator, ...assignees].filter(p => parseInt(p.user_id) !== parseInt(authorId)); console.log(` Инициатор вне задачи. Получатели: ${recipients.length} участников (старое поведение)`); } } // 5. Отправляем email уведомления выбранным получателям for (const recipient of recipients) { const taskData = { taskId, title: taskTitle, description: taskDescription, due_date: null, // при необходимости можно добавить получение срока из БД author_name: authorName, comment: comment, status: status, user_name: userName || recipient.user_name, hours_left: type === 'deadline' ? 24 : null }; await emailNotifications.sendTaskNotification( recipient.user_id, taskData, type ); } console.log(`✅ Уведомления отправлены для задачи ${taskId}`); } catch (error) { console.error('❌ Общая ошибка при обработке уведомлений:', error); } } /** * Отправляет уведомление о приближающемся дедлайне */ async function sendDeadlineNotification(assignment, hoursLeft) { try { const taskData = { taskId: assignment.task_id, title: assignment.title, description: assignment.description || '', due_date: assignment.due_date, author_name: assignment.creator_name, hours_left: hoursLeft }; // Отправляем уведомление исполнителю await emailNotifications.sendTaskNotification( assignment.user_id, taskData, 'deadline' ); // Отправляем уведомление заказчику (автору) await emailNotifications.sendTaskNotification( assignment.created_by, taskData, 'deadline' ); console.log(`✅ Email уведомление о сроке (${hoursLeft}ч) отправлено для задачи ${assignment.task_id}`); } catch (error) { console.error('❌ Ошибка отправки email уведомления о сроке:', error); } } /** * Проверяет приближающиеся дедлайны и отправляет уведомления */ async function checkUpcomingDeadlines() { const now = new Date(); const in48Hours = new Date(now.getTime() + 48 * 60 * 60 * 1000).toISOString(); const in24Hours = new Date(now.getTime() + 24 * 60 * 60 * 1000).toISOString(); const nowISO = now.toISOString(); const query = ` SELECT DISTINCT ta.*, t.title, t.description, t.created_by, u.name as user_name, u.email as user_email, creator.name as creator_name, creator.email as creator_email FROM task_assignments ta JOIN tasks t ON ta.task_id = t.id JOIN users u ON ta.user_id = u.id JOIN users creator ON t.created_by = creator.id WHERE ta.due_date IS NOT NULL AND ta.due_date > ? AND ta.due_date <= ? AND ta.status NOT IN ('completed', 'overdue') AND t.status = 'active' AND t.closed_at IS NULL `; const db = getDb(); if (!db) { console.error('❌ База данных не доступна для проверки сроков'); return; } db.all(query, [nowISO, in48Hours], async (err, assignments) => { if (err) { console.error('❌ Ошибка при проверке сроков задач:', err); return; } for (const assignment of assignments) { const dueDate = new Date(assignment.due_date); const timeLeft = dueDate.getTime() - now.getTime(); const hoursLeft = Math.floor(timeLeft / (60 * 60 * 1000)); if (hoursLeft <= 48 && hoursLeft > 24) { await sendDeadlineNotification(assignment, 48); } else if (hoursLeft <= 24) { await sendDeadlineNotification(assignment, 24); } } }); } /** * Возвращает тему уведомления в зависимости от типа */ function getNotificationSubject(type, taskTitle) { switch (type) { case 'created': return `Новая задача: ${taskTitle}`; case 'updated': return `Обновлена задача: ${taskTitle}`; case 'rework': return `Задача возвращена на доработку: ${taskTitle}`; case 'closed': return `Задача закрыта: ${taskTitle}`; case 'status_changed': return `Изменен статус задачи: ${taskTitle}`; default: return `Уведомление по задаче: ${taskTitle}`; } } /** * Преобразует внутренний статус в читаемый текст */ function getStatusText(status) { const statusMap = { 'assigned': 'Назначена', 'in_progress': 'В работе', 'completed': 'Выполнена', 'overdue': 'Просрочена', 'rework': 'На доработке' }; return statusMap[status] || status; } // Экспортируем функции module.exports = { sendTaskNotifications, checkUpcomingDeadlines, sendDeadlineNotification, getStatusText, emailNotifications // Экспортируем для доступа к методам };