// notifications.js const postgresLogger = require('./postgres'); 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}`); // Получаем заказчика 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); }); }); // Получаем исполнителей 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 || []); }); }); // Получаем информацию об авторе действия 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'; // Логируем в PostgreSQL (если настроено) let postgresLogIds = []; if (postgresLogger.initialized) { postgresLogIds = await logNotificationToPostgres({ type, taskId, taskTitle, taskDescription, authorId, authorName, authorLogin, participants: [...(creator ? [{...creator, role: 'creator'}] : []), ...assignees.map(a => ({...a, role: 'assignee'}))], comment, status, userName }); } // Отправляем email уведомления const participants = [...(creator ? [creator] : []), ...assignees].filter(p => p.user_id !== authorId); for (const participant of participants) { const taskData = { taskId, title: taskTitle, description: taskDescription, due_date: null, // Можно добавить получение срока из БД author_name: authorName, comment: comment, status: status, user_name: userName || participant.user_name, hours_left: type === 'deadline' ? 24 : null // Для уведомлений о дедлайне }; await emailNotifications.sendTaskNotification( participant.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); } } }); } // Вспомогательные функции для работы с PostgreSQL async function logNotificationToPostgres(data) { try { const { type, taskId, taskTitle, taskDescription, authorId, authorName, authorLogin, participants = [], comment = '', status = '', userName = '', error = '' } = data; // Создаем сообщение let messageContent = ''; switch (type) { case 'created': messageContent = `Создана новая задача: ${taskTitle}`; break; case 'updated': messageContent = `Обновлена задача: ${taskTitle}`; break; case 'rework': messageContent = `Задача возвращена на доработку: ${taskTitle}. Комментарий: ${comment}`; break; case 'closed': messageContent = `Задача закрыта: ${taskTitle}`; break; case 'status_changed': messageContent = `Изменен статус задачи: ${taskTitle}. Новый статус: ${getStatusText(status)}`; break; } // Логируем для каждого получателя отдельно const recipientsToNotify = participants.filter(p => p.user_id !== authorId); const logIds = []; for (const recipient of recipientsToNotify) { const logId = await postgresLogger.logNotification({ taskId, taskTitle, taskDescription, notificationType: type, authorId, authorName, authorLogin, recipientId: recipient.user_id, recipientName: recipient.user_name, recipientLogin: recipient.user_login, messageContent: `${messageContent}\n\nЗадача: ${taskTitle}\nОписание: ${taskDescription || 'Без описания'}\nАвтор: ${authorName}`, messageSubject: getNotificationSubject(type, taskTitle), deliveryMethods: ['email', 'telegram', 'vk'], comments: error ? `Ошибка: ${error}` : comment }); if (logId) { logIds.push(logId); } } return logIds; } catch (error) { console.error('❌ Ошибка логирования в PostgreSQL:', error); return []; } } async function updatePostgresLogStatus(logIds, status, errorMessage = null, sentAt = null) { if (!logIds || logIds.length === 0) return; for (const logId of logIds) { await postgresLogger.updateNotificationStatus(logId, status, errorMessage, sentAt); } } 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 // Экспортируем для доступа к методам };