// api-users.js - API для управления исполнителями в задачах const express = require('express'); const router = express.Router(); module.exports = function(app, db) { // Проверка прав доступа к задаче function checkTaskAccess(userId, taskId, callback) { db.get(` SELECT t.created_by, ta.user_id FROM tasks t LEFT JOIN task_assignments ta ON t.id = ta.task_id AND ta.user_id = ? WHERE t.id = ? AND t.status = 'active' `, [userId, taskId], (err, result) => { if (err || !result) { return callback(err || new Error('Задача не найдена'), false); } // Проверяем права: создатель или администратор if (parseInt(result.created_by) === parseInt(userId)) { return callback(null, true); } // Проверяем, является ли пользователь администратором db.get("SELECT role FROM users WHERE id = ?", [userId], (err, user) => { if (err || !user) { return callback(err || new Error('Пользователь не найден'), false); } const isAdmin = user.role === 'admin'; const isTasks = user.role === 'tasks'; // Пользователи с ролью tasks тоже имеют доступ callback(null, isAdmin || isTasks); }); }); } // API для получения доступных исполнителей для добавления router.get('/api/tasks/:taskId/available-assignees', (req, res) => { const { taskId } = req.params; const userId = req.session.user.id; checkTaskAccess(userId, taskId, (err, hasAccess) => { if (err || !hasAccess) { return res.status(403).json({ error: 'Нет доступа к задаче' }); } // Получаем текущих исполнителей db.all(` SELECT user_id FROM task_assignments WHERE task_id = ? AND status != 'deleted' `, [taskId], (err, currentAssignees) => { if (err) { return res.status(500).json({ error: err.message }); } const currentAssigneeIds = currentAssignees.map(a => a.user_id); // Получаем всех пользователей, кроме текущих исполнителей db.all(` SELECT id, login, name, email, role, auth_type FROM users WHERE id NOT IN (${currentAssigneeIds.map(() => '?').join(',')}) AND id != ? ORDER BY name `, [...currentAssigneeIds, userId], (err, users) => { if (err) { return res.status(500).json({ error: err.message }); } res.json(users); }); }); }); }); // API для добавления исполнителя к задаче router.post('/api/tasks/:taskId/assignees', (req, res) => { const { taskId } = req.params; const userId = req.session.user.id; const { assigneeIds } = req.body; if (!Array.isArray(assigneeIds) || assigneeIds.length === 0) { return res.status(400).json({ error: 'Не указаны исполнители' }); } checkTaskAccess(userId, taskId, (err, hasAccess) => { if (err || !hasAccess) { return res.status(403).json({ error: 'Нет доступа к задаче' }); } // Получаем информацию о задаче db.get("SELECT due_date FROM tasks WHERE id = ?", [taskId], (err, task) => { if (err || !task) { return res.status(404).json({ error: 'Задача не найдена' }); } // Начинаем транзакцию db.serialize(() => { db.run("BEGIN TRANSACTION"); const errors = []; const addedAssignees = []; // Добавляем каждого исполнителя assigneeIds.forEach((assigneeId, index) => { db.run(` INSERT OR IGNORE INTO task_assignments (task_id, user_id, status, created_at, due_date) VALUES (?, ?, 'assigned', datetime('now'), ?) `, [taskId, assigneeId, task.due_date], function(err) { if (err) { errors.push(`Ошибка добавления исполнителя ${assigneeId}: ${err.message}`); } else if (this.changes > 0) { addedAssignees.push(assigneeId); // Логируем действие const activityService = require('./database'); if (activityService.logActivity) { activityService.logActivity( taskId, userId, 'TASK_ASSIGNED', `Добавлен исполнитель: ${assigneeId}` ); } } // Если это последний запрос, завершаем транзакцию if (index === assigneeIds.length - 1) { if (errors.length > 0) { db.run("ROLLBACK"); return res.status(500).json({ error: 'Ошибка добавления исполнителей', details: errors }); } else { db.run("COMMIT"); // Отправляем уведомления const { sendTaskNotifications } = require('./notifications'); if (sendTaskNotifications) { sendTaskNotifications(taskId, 'assigned', userId, assigneeIds); } res.json({ success: true, added: addedAssignees.length, message: `Добавлено ${addedAssignees.length} исполнителей` }); } } }); }); }); }); }); }); // API для удаления исполнителя из задачи router.delete('/api/tasks/:taskId/assignees/:assigneeId', (req, res) => { const { taskId, assigneeId } = req.params; const userId = req.session.user.id; checkTaskAccess(userId, taskId, (err, hasAccess) => { if (err || !hasAccess) { return res.status(403).json({ error: 'Нет доступа к задаче' }); } // Проверяем, что исполнитель существует db.get(` SELECT id FROM task_assignments WHERE task_id = ? AND user_id = ? AND status != 'deleted' `, [taskId, assigneeId], (err, assignment) => { if (err || !assignment) { return res.status(404).json({ error: 'Исполнитель не найден в задаче' }); } // Удаляем исполнителя (помечаем как удаленного) db.run(` UPDATE task_assignments SET status = 'deleted', updated_at = datetime('now') WHERE task_id = ? AND user_id = ? `, [taskId, assigneeId], function(err) { if (err) { return res.status(500).json({ error: err.message }); } if (this.changes > 0) { // Логируем действие const activityService = require('./database'); if (activityService.logActivity) { activityService.logActivity( taskId, userId, 'TASK_ASSIGNMENTS_UPDATED', `Удален исполнитель: ${assigneeId}` ); } res.json({ success: true, message: 'Исполнитель удален из задачи' }); } else { res.status(404).json({ error: 'Исполнитель не найден' }); } }); }); }); }); // API для замены всех исполнителей router.put('/api/tasks/:taskId/replace-all-assignees', (req, res) => { const { taskId } = req.params; const userId = req.session.user.id; const { newAssigneeIds } = req.body; if (!Array.isArray(newAssigneeIds) || newAssigneeIds.length === 0) { return res.status(400).json({ error: 'Не указаны новые исполнители' }); } checkTaskAccess(userId, taskId, (err, hasAccess) => { if (err || !hasAccess) { return res.status(403).json({ error: 'Нет доступа к задаче' }); } // Получаем информацию о задаче db.get("SELECT due_date FROM tasks WHERE id = ?", [taskId], (err, task) => { if (err || !task) { return res.status(404).json({ error: 'Задача не найдена' }); } // Начинаем транзакцию db.serialize(() => { db.run("BEGIN TRANSACTION"); // Удаляем всех текущих исполнителей db.run(` UPDATE task_assignments SET status = 'deleted', updated_at = datetime('now') WHERE task_id = ? AND status != 'deleted' `, [taskId], function(err) { if (err) { db.run("ROLLBACK"); return res.status(500).json({ error: err.message }); } const removedCount = this.changes; const errors = []; let addedCount = 0; // Изменено с const на let // Если нет новых исполнителей для добавления, просто завершаем if (newAssigneeIds.length === 0) { db.run("COMMIT"); // Логируем действие const activityService = require('./database'); if (activityService.logActivity) { activityService.logActivity( taskId, userId, 'TASK_ASSIGNMENTS_UPDATED', `Удалены все исполнители. Удалено: ${removedCount}` ); } res.json({ success: true, removed: removedCount, added: 0, message: `Удалены все исполнители: ${removedCount}` }); return; } // Счетчик для отслеживания завершенных операций let completedCount = 0; // Добавляем новых исполнителей newAssigneeIds.forEach((assigneeId) => { db.run(` INSERT INTO task_assignments (task_id, user_id, status, created_at, due_date) VALUES (?, ?, 'assigned', datetime('now'), ?) `, [taskId, assigneeId, task.due_date], function(err) { if (err) { errors.push(`Ошибка добавления исполнителя ${assigneeId}: ${err.message}`); } else { addedCount++; // Логируем добавление const activityService = require('./database'); if (activityService.logActivity) { activityService.logActivity( taskId, userId, 'TASK_ASSIGNED', `Добавлен исполнитель: ${assigneeId}` ); } } completedCount++; // Если все запросы завершены, завершаем транзакцию if (completedCount === newAssigneeIds.length) { if (errors.length > 0) { db.run("ROLLBACK"); return res.status(500).json({ error: 'Ошибка замены исполнителей', details: errors }); } else { db.run("COMMIT"); // Логируем замену всех const activityService = require('./database'); if (activityService.logActivity) { activityService.logActivity( taskId, userId, 'TASK_ASSIGNMENTS_UPDATED', `Заменены все исполнители. Удалено: ${removedCount}, добавлено: ${addedCount}` ); } // Отправляем уведомления const { sendTaskNotifications } = require('./notifications'); if (sendTaskNotifications) { sendTaskNotifications(taskId, 'assigned', userId, newAssigneeIds); } res.json({ success: true, removed: removedCount, added: addedCount, message: `Заменены исполнители: удалено ${removedCount}, добавлено ${addedCount}` }); } } }); }); }); }); }); }); }); // API для замены конкретного исполнителя router.put('/api/tasks/:taskId/replace-assignee', (req, res) => { const { taskId } = req.params; const userId = req.session.user.id; const { oldAssigneeId, newAssigneeId } = req.body; if (!oldAssigneeId || !newAssigneeId) { return res.status(400).json({ error: 'Не указан старый или новый исполнитель' }); } if (oldAssigneeId === newAssigneeId) { return res.status(400).json({ error: 'Старый и новый исполнитель одинаковы' }); } checkTaskAccess(userId, taskId, (err, hasAccess) => { if (err || !hasAccess) { return res.status(403).json({ error: 'Нет доступа к задаче' }); } // Получаем информацию о задаче db.get("SELECT due_date FROM tasks WHERE id = ?", [taskId], (err, task) => { if (err || !task) { return res.status(404).json({ error: 'Задача не найдена' }); } // Начинаем транзакцию db.serialize(() => { db.run("BEGIN TRANSACTION"); // Удаляем старого исполнителя db.run(` UPDATE task_assignments SET status = 'deleted', updated_at = datetime('now') WHERE task_id = ? AND user_id = ? AND status != 'deleted' `, [taskId, oldAssigneeId], function(err) { if (err) { db.run("ROLLBACK"); return res.status(500).json({ error: err.message }); } if (this.changes === 0) { db.run("ROLLBACK"); return res.status(404).json({ error: 'Старый исполнитель не найден' }); } // Добавляем нового исполнителя db.run(` INSERT INTO task_assignments (task_id, user_id, status, created_at, due_date) VALUES (?, ?, 'assigned', datetime('now'), ?) `, [taskId, newAssigneeId, task.due_date], function(err) { if (err) { db.run("ROLLBACK"); return res.status(500).json({ error: err.message }); } db.run("COMMIT"); // Логируем действие const activityService = require('./database'); if (activityService.logActivity) { activityService.logActivity( taskId, userId, 'TASK_ASSIGNMENTS_UPDATED', `Заменен исполнитель: ${oldAssigneeId} -> ${newAssigneeId}` ); } // Отправляем уведомления const { sendTaskNotifications } = require('./notifications'); if (sendTaskNotifications) { sendTaskNotifications(taskId, 'assigned', userId, [newAssigneeId]); } res.json({ success: true, message: `Исполнитель заменен: ${oldAssigneeId} -> ${newAssigneeId}` }); }); }); }); }); }); }); // API для смены всех исполнителей на конкретного пользователя (например, kalugin.o) router.put('/api/tasks/:taskId/assign-all-to-user', (req, res) => { const { taskId } = req.params; const userId = req.session.user.id; const { targetUserId } = req.body; if (!targetUserId) { return res.status(400).json({ error: 'Не указан целевой пользователь' }); } checkTaskAccess(userId, taskId, (err, hasAccess) => { if (err || !hasAccess) { return res.status(403).json({ error: 'Нет доступа к задаче' }); } // Получаем информацию о задаче db.get("SELECT due_date FROM tasks WHERE id = ?", [taskId], (err, task) => { if (err || !task) { return res.status(404).json({ error: 'Задача не найдена' }); } // Получаем текущих исполнителей db.all(` SELECT user_id FROM task_assignments WHERE task_id = ? AND status != 'deleted' AND user_id != ? `, [taskId, targetUserId], (err, currentAssignees) => { if (err) { return res.status(500).json({ error: err.message }); } // Начинаем транзакцию db.serialize(() => { db.run("BEGIN TRANSACTION"); // Удаляем всех текущих исполнителей, кроме целевого if (currentAssignees.length > 0) { const currentAssigneeIds = currentAssignees.map(a => a.user_id); const placeholders = currentAssigneeIds.map(() => '?').join(','); db.run(` UPDATE task_assignments SET status = 'deleted', updated_at = datetime('now') WHERE task_id = ? AND user_id IN (${placeholders}) `, [taskId, ...currentAssigneeIds], function(err) { if (err) { db.run("ROLLBACK"); return res.status(500).json({ error: err.message }); } processNextStep(); }); } else { processNextStep(); } function processNextStep() { // Проверяем, есть ли уже целевой пользователь в исполнителях db.get(` SELECT id FROM task_assignments WHERE task_id = ? AND user_id = ? AND status != 'deleted' `, [taskId, targetUserId], (err, existing) => { if (err) { db.run("ROLLBACK"); return res.status(500).json({ error: err.message }); } if (!existing) { // Добавляем целевого пользователя db.run(` INSERT INTO task_assignments (task_id, user_id, status, created_at, due_date) VALUES (?, ?, 'assigned', datetime('now'), ?) `, [taskId, targetUserId, task.due_date], function(err) { if (err) { db.run("ROLLBACK"); return res.status(500).json({ error: err.message }); } finishTransaction(); }); } else { finishTransaction(); } }); } function finishTransaction() { db.run("COMMIT"); // Логируем действие const activityService = require('./database'); if (activityService.logActivity) { activityService.logActivity( taskId, userId, 'TASK_ASSIGNMENTS_UPDATED', `Все исполнители заменены на: ${targetUserId}` ); } // Отправляем уведомления const { sendTaskNotifications } = require('./notifications'); if (sendTaskNotifications) { sendTaskNotifications(taskId, 'assigned', userId, [targetUserId]); } res.json({ success: true, removed: currentAssignees.length, added: 1, message: `Все исполнители заменены на пользователя ${targetUserId}` }); } }); }); }); }); }); // Подключаем роутер к приложению app.use(router); console.log('✅ API для управления исполнителями подключено'); };