diff --git a/database.js b/database.js
index 36dfae6..82bb00d 100644
--- a/database.js
+++ b/database.js
@@ -93,6 +93,18 @@ function initializeSQLite() {
}
function createSQLiteTables() {
+ // notification_history
+ db.run(`CREATE TABLE IF NOT EXISTS notification_history (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER NOT NULL,
+ task_id INTEGER NOT NULL,
+ notification_type TEXT NOT NULL,
+ last_sent_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users (id),
+ FOREIGN KEY (task_id) REFERENCES tasks (id),
+ UNIQUE(user_id, task_id, notification_type)
+ )`);
// SQLite таблицы
db.run(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
diff --git a/email-notifications.js b/email-notifications.js
index 374f8d6..2bb9260 100644
--- a/email-notifications.js
+++ b/email-notifications.js
@@ -6,6 +6,7 @@ class EmailNotifications {
constructor() {
this.transporter = null;
this.initialized = false;
+ this.notificationCooldown = 12 * 60 * 60 * 1000; // 12 часов в миллисекундах
this.init();
}
@@ -45,17 +46,104 @@ class EmailNotifications {
}
}
+ async canSendNotification(userId, taskId, notificationType) {
+ if (!getDb) return true; // Если БД не готова, разрешаем отправку
+
+ return new Promise((resolve, reject) => {
+ const db = getDb();
+ db.get(
+ `SELECT last_sent_at FROM notification_history
+ WHERE user_id = ? AND task_id = ? AND notification_type = ?`,
+ [userId, taskId, notificationType],
+ (err, record) => {
+ if (err) {
+ console.error('❌ Ошибка проверки истории уведомлений:', err);
+ resolve(true); // В случае ошибки разрешаем отправку
+ return;
+ }
+
+ if (!record) {
+ // Записи нет, можно отправлять
+ resolve(true);
+ return;
+ }
+
+ // Проверяем прошло ли 12 часов
+ const lastSent = new Date(record.last_sent_at);
+ const now = new Date();
+ const timeDiff = now.getTime() - lastSent.getTime();
+
+ if (timeDiff >= this.notificationCooldown) {
+ resolve(true);
+ } else {
+ const hoursLeft = Math.ceil((this.notificationCooldown - timeDiff) / (60 * 60 * 1000));
+ console.log(`⏰ Уведомление пропущено: пользователь ${userId}, задача ${taskId}, тип ${notificationType}`);
+ console.log(` Последнее уведомление было отправлено: ${lastSent.toLocaleString('ru-RU')}`);
+ console.log(` Следующее уведомление можно отправить через: ${hoursLeft} часов`);
+ console.log(` Время следующей отправки: ${new Date(lastSent.getTime() + this.notificationCooldown).toLocaleString('ru-RU')}`);
+ resolve(false);
+ }
+ }
+ );
+ });
+ }
+
+ async recordNotificationSent(userId, taskId, notificationType) {
+ if (!getDb) return;
+
+ return new Promise((resolve, reject) => {
+ const db = getDb();
+ db.run(
+ `INSERT OR REPLACE INTO notification_history
+ (user_id, task_id, notification_type, last_sent_at)
+ VALUES (?, ?, ?, CURRENT_TIMESTAMP)`,
+ [userId, taskId, notificationType],
+ (err) => {
+ if (err) {
+ console.error('❌ Ошибка записи истории уведомлений:', err);
+ reject(err);
+ } else {
+ console.log(`📝 Записана история уведомления: пользователь ${userId}, задача ${taskId}, тип ${notificationType}`);
+ resolve();
+ }
+ }
+ );
+ });
+ }
+
+ async forceSendNotification(userId, taskId, notificationType) {
+ // Принудительно удаляем запись из истории, чтобы можно было отправить уведомление сразу
+ if (!getDb) return;
+
+ return new Promise((resolve, reject) => {
+ const db = getDb();
+ db.run(
+ `DELETE FROM notification_history
+ WHERE user_id = ? AND task_id = ? AND notification_type = ?`,
+ [userId, taskId, notificationType],
+ (err) => {
+ if (err) {
+ console.error('❌ Ошибка принудительного удаления истории:', err);
+ reject(err);
+ } else {
+ console.log(`♻️ История уведомления принудительно очищена: пользователь ${userId}, задача ${taskId}, тип ${notificationType}`);
+ resolve();
+ }
+ }
+ );
+ });
+ }
+
async getUserNotificationSettings(userId) {
if (!getDb) return null;
return new Promise((resolve, reject) => {
const db = getDb();
- db.get(`
- SELECT us.*, u.email as user_email, u.name as user_name
- FROM user_settings us
- LEFT JOIN users u ON us.user_id = u.id
- WHERE us.user_id = ?
- `, [userId], (err, settings) => {
+ db.get(`SELECT us.*, u.email as user_email, u.name as user_name
+ FROM user_settings us
+ LEFT JOIN users u ON us.user_id = u.id
+ WHERE us.user_id = ?`,
+ [userId], (err, settings) => {
if (err) {
reject(err);
} else {
@@ -88,45 +176,43 @@ class EmailNotifications {
if (existing) {
// Обновляем существующую запись
- db.run(`
- UPDATE user_settings
- SET email_notifications = ?,
- notification_email = ?,
- telegram_notifications = ?,
- telegram_chat_id = ?,
- vk_notifications = ?,
- vk_user_id = ?,
- updated_at = CURRENT_TIMESTAMP
- WHERE user_id = ?
- `, [
- email_notifications ? 1 : 0,
- notification_email,
- telegram_notifications ? 1 : 0,
- telegram_chat_id,
- vk_notifications ? 1 : 0,
- vk_user_id,
- userId
- ], function(err) {
+ db.run(`UPDATE user_settings
+ SET email_notifications = ?,
+ notification_email = ?,
+ telegram_notifications = ?,
+ telegram_chat_id = ?,
+ vk_notifications = ?,
+ vk_user_id = ?,
+ updated_at = CURRENT_TIMESTAMP
+ WHERE user_id = ?`,
+ [
+ email_notifications ? 1 : 0,
+ notification_email,
+ telegram_notifications ? 1 : 0,
+ telegram_chat_id,
+ vk_notifications ? 1 : 0,
+ vk_user_id,
+ userId
+ ], function(err) {
if (err) reject(err);
else resolve(true);
});
} else {
// Создаем новую запись
- db.run(`
- INSERT INTO user_settings (
+ db.run(`INSERT INTO user_settings (
user_id, email_notifications, notification_email,
telegram_notifications, telegram_chat_id,
vk_notifications, vk_user_id
- ) VALUES (?, ?, ?, ?, ?, ?, ?)
- `, [
- userId,
- email_notifications ? 1 : 0,
- notification_email,
- telegram_notifications ? 1 : 0,
- telegram_chat_id,
- vk_notifications ? 1 : 0,
- vk_user_id
- ], function(err) {
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)`,
+ [
+ userId,
+ email_notifications ? 1 : 0,
+ notification_email,
+ telegram_notifications ? 1 : 0,
+ telegram_chat_id,
+ vk_notifications ? 1 : 0,
+ vk_user_id
+ ], function(err) {
if (err) reject(err);
else resolve(true);
});
@@ -159,10 +245,100 @@ class EmailNotifications {
}
}
+ getTaskIdFromData(taskData) {
+ // Ищем ID задачи в различных возможных полях объекта
+ if (!taskData) {
+ console.error('❌ taskData is null or undefined');
+ return null;
+ }
+
+ // Проверяем различные возможные поля
+ if (taskData.id) {
+ return taskData.id;
+ }
+
+ if (taskData.task_id) {
+ return taskData.task_id;
+ }
+
+ if (taskData.taskId) {
+ return taskData.taskId;
+ }
+
+ // Если есть assignment_id, пытаемся найти задачу через БД
+ if (taskData.assignment_id && getDb) {
+ console.log(`🔍 Ищу ID задачи через assignment_id: ${taskData.assignment_id}`);
+ try {
+ const db = getDb();
+ // Используем синхронный запрос через промис
+ return new Promise((resolve) => {
+ db.get("SELECT task_id FROM task_assignments WHERE id = ?",
+ [taskData.assignment_id],
+ (err, row) => {
+ if (err || !row) {
+ console.error(`❌ Не удалось найти задачу по assignment_id ${taskData.assignment_id}:`, err?.message);
+ resolve(null);
+ } else {
+ console.log(`✅ Найдена задача: ${row.task_id} для assignment_id ${taskData.assignment_id}`);
+ resolve(row.task_id);
+ }
+ }
+ );
+ });
+ } catch (error) {
+ console.error('❌ Ошибка поиска задачи по assignment_id:', error);
+ return null;
+ }
+ }
+
+ console.error('❌ Не удалось определить ID задачи из данных:',
+ Object.keys(taskData).length > 0 ?
+ JSON.stringify(taskData, null, 2).substring(0, 500) : 'empty object');
+
+ return null;
+ }
+
async sendTaskNotification(userId, taskData, notificationType) {
try {
+ // Получаем ID задачи (обрабатываем и синхронные и асинхронные случаи)
+ let taskId;
+
+ if (taskData.assignment_id && getDb) {
+ // Асинхронный поиск через assignment_id
+ taskId = await this.getTaskIdFromData(taskData);
+ } else {
+ // Синхронный поиск в полях объекта
+ taskId = this.getTaskIdFromData(taskData);
+ }
+
+ if (!taskId) {
+ console.error('❌ Не удалось определить ID задачи для уведомления. Данные:', {
+ userId,
+ notificationType,
+ taskDataKeys: Object.keys(taskData || {}),
+ taskDataSample: taskData ? {
+ id: taskData.id,
+ task_id: taskData.task_id,
+ taskId: taskData.taskId,
+ assignment_id: taskData.assignment_id,
+ title: taskData.title
+ } : 'null'
+ });
+ return false;
+ }
+
+ console.log(`🔍 Отправка уведомления: пользователь ${userId}, задача ${taskId}, тип ${notificationType}`);
+
+ // Проверяем, можно ли отправлять уведомление
+ const canSend = await this.canSendNotification(userId, taskId, notificationType);
+ if (!canSend) {
+ console.log(`⏰ Пропущено уведомление для пользователя ${userId}, задача ${taskId}, тип ${notificationType} (12-часовой кд)`);
+ return false;
+ }
+
const settings = await this.getUserNotificationSettings(userId);
if (!settings || !settings.email_notifications) {
+ console.log(`⚠️ Пользователь ${userId} отключил email уведомления`);
return false;
}
@@ -201,15 +377,30 @@ class EmailNotifications {
subject = `Скоро срок выполнения: ${taskData.title}`;
htmlContent = this.getDeadlineHtml(taskData);
break;
+ case 'overdue':
+ subject = `Задача просрочена: ${taskData.title}`;
+ htmlContent = this.getOverdueHtml(taskData);
+ break;
default:
subject = `Уведомление по задаче: ${taskData.title}`;
htmlContent = this.getDefaultHtml(taskData);
}
- return await this.sendEmailNotification(emailTo, subject, htmlContent);
+ const result = await this.sendEmailNotification(emailTo, subject, htmlContent);
+
+ // Если уведомление успешно отправлено, записываем в историю
+ if (result) {
+ await this.recordNotificationSent(userId, taskId, notificationType);
+ console.log(`✅ Уведомление отправлено: пользователь ${userId}, задача ${taskId}, тип ${notificationType}`);
+ } else {
+ console.log(`❌ Не удалось отправить уведомление: пользователь ${userId}, задача ${taskId}, тип ${notificationType}`);
+ }
+
+ return result;
} catch (error) {
console.error('❌ Ошибка отправки уведомления о задаче:', error);
+ console.error('Stack trace:', error.stack);
return false;
}
}
@@ -229,6 +420,7 @@ class EmailNotifications {
.task-info { margin-bottom: 15px; }
.button { display: inline-block; background: #667eea; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; }
.footer { margin-top: 20px; font-size: 12px; color: #666; }
+ .cooldown-notice { background: #f0f0f0; padding: 10px; border-radius: 5px; margin-top: 15px; font-size: 12px; }
@@ -237,18 +429,22 @@ class EmailNotifications {
📋 Новая задача
-
${taskData.title}
+
${taskData.title || 'Без названия'}
Описание: ${taskData.description || 'Без описания'}
-
Срок выполнения: ${new Date(taskData.due_date).toLocaleString('ru-RU')}
-
Создал: ${taskData.author_name || 'Неизвестно'}
+ ${taskData.due_date ? `
Срок выполнения: ${new Date(taskData.due_date).toLocaleString('ru-RU')}
` : ''}
+ ${taskData.author_name ? `
Создал: ${taskData.author_name}
` : ''}
Для просмотра подробной информации перейдите в систему управления задачами.
Перейти в CRM
+
+
⚠️ Следующее уведомление по этой задаче будет отправлено не ранее чем через 12 часов.
+
Вы можете изменить настройки уведомлений в личном кабинете.
+
@@ -270,6 +466,7 @@ class EmailNotifications {
.task-info { margin-bottom: 15px; }
.button { display: inline-block; background: #4CAF50; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; }
.footer { margin-top: 20px; font-size: 12px; color: #666; }
+ .cooldown-notice { background: #f0f0f0; padding: 10px; border-radius: 5px; margin-top: 15px; font-size: 12px; }
@@ -278,17 +475,20 @@ class EmailNotifications {
🔄 Обновлена задача
-
${taskData.title}
+
${taskData.title || 'Без названия'}
-
Изменения внес: ${taskData.author_name || 'Неизвестно'}
+ ${taskData.author_name ? `
Изменения внес: ${taskData.author_name}
` : ''}
Время: ${new Date().toLocaleString('ru-RU')}
Для просмотра изменений перейдите в систему управления задачами.
Перейти в CRM
+
+
⚠️ Следующее уведомление по этой задаче будет отправлено не ранее чем через 12 часов.
+
@@ -311,6 +511,7 @@ class EmailNotifications {
.comment-box { background: #FFF3E0; padding: 15px; border-left: 4px solid #FF9800; margin: 15px 0; }
.button { display: inline-block; background: #FF9800; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; }
.footer { margin-top: 20px; font-size: 12px; color: #666; }
+ .cooldown-notice { background: #f0f0f0; padding: 10px; border-radius: 5px; margin-top: 15px; font-size: 12px; }
@@ -319,9 +520,9 @@ class EmailNotifications {
🔄 Задача возвращена на доработку
-
${taskData.title}
+
${taskData.title || 'Без названия'}
-
Автор замечания: ${taskData.author_name || 'Неизвестно'}
+ ${taskData.author_name ? `
Автор замечания: ${taskData.author_name}
` : ''}
Пожалуйста, исправьте замечания и обновите статус задачи.
Перейти к задаче
+
+
⚠️ Следующее уведомление по этой задаче будет отправлено не ранее чем через 12 часов.
+
@@ -362,9 +566,9 @@ class EmailNotifications {
✅ Задача закрыта
-
${taskData.title}
+
${taskData.title || 'Без названия'}
-
Закрыта: ${taskData.author_name || 'Неизвестно'}
+ ${taskData.author_name ? `
Закрыта: ${taskData.author_name}
` : ''}
Время закрытия: ${new Date().toLocaleString('ru-RU')}
Задача завершена и перемещена в архив.
@@ -372,7 +576,7 @@ class EmailNotifications {
@@ -406,18 +614,21 @@ class EmailNotifications {
🔄 Изменен статус задачи
-
${taskData.title}
+
${taskData.title || 'Без названия'}
-
Новый статус: ${this.getStatusText(taskData.status)}
+
Новый статус: ${statusText}
Изменил: ${taskData.user_name || taskData.author_name || 'Неизвестно'}
Время: ${new Date().toLocaleString('ru-RU')}
Для просмотра деталей перейдите в систему управления задачами.
Перейти в CRM
+
+
⚠️ Следующее уведомление по этой задаче будет отправлено не ранее чем через 12 часов.
+
@@ -448,17 +661,63 @@ class EmailNotifications {
⚠️ Скоро срок выполнения
-
${taskData.title}
+
${taskData.title || 'Без названия'}
-
ВНИМАНИЕ! До окончания срока задачи осталось менее ${taskData.hours_left} часов!
-
Срок выполнения: ${new Date(taskData.due_date).toLocaleString('ru-RU')}
+
ВНИМАНИЕ! До окончания срока задачи осталось менее ${hoursLeft} часов!
+ ${taskData.due_date ? `
Срок выполнения: ${new Date(taskData.due_date).toLocaleString('ru-RU')}
` : ''}
Пожалуйста, завершите задачу в указанный срок.
Перейти к задаче
+
+
⚠️ Следующее уведомление о дедлайне будет отправлено не ранее чем через 12 часов.
+
Если дедлайн изменится, вы получите новое уведомление.
+
+
+
+
+
Комментарий:
@@ -329,10 +530,13 @@ class EmailNotifications {