From 155687f3bb8772441e22fcf64136d8b330a1e6ba Mon Sep 17 00:00:00 2001 From: kalugin66 Date: Thu, 12 Feb 2026 19:52:41 +0500 Subject: [PATCH] =?UTF-8?q?=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8?= =?UTF-8?q?=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/ui.js | 29 +++++- task-endpoints.js | 245 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 217 insertions(+), 57 deletions(-) diff --git a/public/ui.js b/public/ui.js index ab96bd0..9b95303 100644 --- a/public/ui.js +++ b/public/ui.js @@ -227,7 +227,32 @@ function openReworkAssignmentModal(taskId, userId, userName) { setTimeout(() => textarea.focus(), 100); } } +// Функция для принудительного завершения задачи исполнителя +async function forceCompleteAssignment(taskId, userId, userName) { + if (!confirm(`Вы уверены, что хотите отметить задачу как выполненную для сотрудника ${userName}?\nЭто действие нельзя отменить.`)) { + return; + } + try { + const response = await fetch(`/api/tasks/${taskId}/force-complete/${userId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + } + }); + + if (response.ok) { + alert(`✅ Задача отмечена как выполненная для сотрудника ${userName}`); + loadTasks(); // Перезагружаем задачи + } else { + const error = await response.json(); + alert(`❌ Ошибка: ${error.error || 'Неизвестная ошибка'}`); + } + } catch (error) { + console.error('❌ Ошибка:', error); + alert('Сетевая ошибка при выполнении операции'); + } +} // Вспомогательная функция для экранирования HTML function escapeHtml(text) { if (!text) return ''; @@ -426,7 +451,9 @@ ${isCurrentUser && assignment.status === 'assigned' ? ${isCurrentUser && (assignment.status === 'in_progress' || assignment.status === 'overdue' || assignment.status === 'rework') ? `` : ''} ${isTaskCreator && assignment.status !== 'assigned' ? -`` : ''} +`` : ''} +${isTaskCreator && assignment.status !== 'completed' ? +`` : ''} ${canEdit ? `` : ''} diff --git a/task-endpoints.js b/task-endpoints.js index b86daed..b577d99 100644 --- a/task-endpoints.js +++ b/task-endpoints.js @@ -1269,12 +1269,18 @@ app.get('/api/document-approval-tasks', requireAuth, (req, res) => { }); }); }); - // API для отправки конкретного исполнителя на доработку +// API для отправки конкретного исполнителя на доработку app.put('/api/tasks/:taskId/rework-assignment/:userId', requireAuth, (req, res) => { const { taskId, userId } = req.params; const { comment } = req.body; const currentUserId = req.session.user.id; + console.log('=== ОТПРАВКА НА ДОРАБОТКУ ==='); + console.log('taskId:', taskId); + console.log('userId:', userId); + console.log('comment:', comment); + console.log('=============================='); + if (!comment) { return res.status(400).json({ error: 'Комментарий к доработке обязателен' }); } @@ -1285,68 +1291,195 @@ app.put('/api/tasks/:taskId/rework-assignment/:userId', requireAuth, (req, res) } // Проверяем права: автор задачи или администратор - if (req.session.user.role !== 'admin' && parseInt(task.created_by) !== currentUserId) { + if (req.session.user.role !== 'admin' && parseInt(task.created_by) !== parseInt(currentUserId)) { return res.status(403).json({ error: 'Только автор задачи может отправлять на доработку' }); } - db.serialize(() => { - // Обновляем статус конкретного исполнителя - db.run( - `UPDATE task_assignments - SET status = 'rework', - rework_comment = ?, - updated_at = CURRENT_TIMESTAMP - WHERE task_id = ? AND user_id = ?`, - [comment, taskId, userId], - function(err) { - if (err) { - res.status(500).json({ error: err.message }); - return; - } - - if (this.changes === 0) { - return res.status(404).json({ error: 'Исполнитель не найден в задаче' }); - } - - // Логируем действие - const { logActivity } = require('./database'); - if (logActivity) { - logActivity(taskId, currentUserId, 'ASSIGNMENT_REWORK', - `Исполнитель ${userId} отправлен на доработку: ${comment}`); - } - - // Получаем данные для уведомления - db.get(`SELECT t.title, t.description, u.name as executor_name - FROM tasks t - LEFT JOIN users u ON u.id = ? - WHERE t.id = ?`, - [userId, taskId], (err, taskData) => { - if (!err && taskData) { - const { sendTaskNotifications } = require('./notifications'); - sendTaskNotifications( - 'rework_assignment', - taskId, - taskData.title, - taskData.description, - currentUserId, - comment, - 'rework', - req.session.user.name, - userId // ID исполнителя для персонального уведомления - ); - } - }); - - res.json({ - success: true, - message: 'Исполнитель отправлен на доработку' - }); + // Проверяем существование назначения + db.get("SELECT id, status FROM task_assignments WHERE task_id = ? AND user_id = ?", + [taskId, userId], + (err, assignment) => { + if (err) { + return res.status(500).json({ error: err.message }); } - ); - }); + + if (!assignment) { + return res.status(404).json({ error: 'Исполнитель не назначен на эту задачу' }); + } + + // Обновляем статус конкретного исполнителя + db.run( + `UPDATE task_assignments + SET status = 'rework', + rework_comment = ?, + updated_at = CURRENT_TIMESTAMP + WHERE task_id = ? AND user_id = ?`, + [comment, taskId, userId], + function(err) { + if (err) { + res.status(500).json({ error: err.message }); + return; + } + + // Логируем действие + try { + logActivity(parseInt(taskId), currentUserId, 'ASSIGNMENT_REWORK', + `Исполнитель ${userId} отправлен на доработку: ${comment.substring(0, 50)}`); + } catch (e) { + console.error('Ошибка логирования:', e); + } + + // Получаем данные для уведомления + db.get(`SELECT t.title, t.description, u.name as executor_name + FROM tasks t + LEFT JOIN users u ON u.id = ? + WHERE t.id = ?`, + [userId, taskId], (err, taskData) => { + if (!err && taskData) { + try { + sendTaskNotifications( + 'rework_assignment', + taskId, + taskData.title, + taskData.description, + currentUserId, + comment, + 'rework', + req.session.user.name, + userId + ); + } catch (e) { + console.error('Ошибка отправки уведомления:', e); + } + } + }); + + res.json({ + success: true, + message: 'Исполнитель отправлен на доработку' + }); + } + ); + } + ); }); }); +// API для принудительного завершения задачи исполнителем (для автора/админа) +app.put('/api/tasks/:taskId/force-complete/:userId', requireAuth, (req, res) => { + const { taskId, userId } = req.params; + const currentUserId = req.session.user.id; + + console.log('=== ПРИНУДИТЕЛЬНОЕ ЗАВЕРШЕНИЕ ==='); + console.log('taskId:', taskId); + console.log('userId:', userId); + console.log('currentUserId:', currentUserId); + console.log('=================================='); + + db.get("SELECT created_by, title, description, closed_at FROM tasks WHERE id = ?", [taskId], (err, task) => { + if (err) { + console.error('❌ Ошибка при поиске задачи:', err); + return res.status(500).json({ error: 'Ошибка базы данных' }); + } + + if (!task) { + return res.status(404).json({ error: 'Задача не найдена' }); + } + + // Проверяем права: автор задачи или администратор + if (req.session.user.role !== 'admin' && parseInt(task.created_by) !== parseInt(currentUserId)) { + return res.status(403).json({ error: 'Только автор задачи может принудительно завершать выполнение' }); + } + + // Проверяем, что задача не закрыта + if (task.closed_at) { + return res.status(400).json({ error: 'Задача уже закрыта' }); + } + + // Проверяем существование назначения + db.get("SELECT id, status FROM task_assignments WHERE task_id = ? AND user_id = ?", + [taskId, userId], + (err, assignment) => { + if (err) { + console.error('❌ Ошибка при поиске назначения:', err); + return res.status(500).json({ error: 'Ошибка базы данных' }); + } + + if (!assignment) { + return res.status(404).json({ error: 'Исполнитель не назначен на эту задачу' }); + } + + // Если уже выполнено, просто возвращаем успех + if (assignment.status === 'completed') { + return res.json({ + success: true, + message: 'Задача уже была отмечена как выполненная' + }); + } + + // Обновляем статус конкретного исполнителя на "completed" + db.run( + `UPDATE task_assignments + SET status = 'completed', + updated_at = CURRENT_TIMESTAMP + WHERE task_id = ? AND user_id = ?`, + [taskId, userId], + function(err) { + if (err) { + console.error('❌ Ошибка при обновлении статуса:', err); + return res.status(500).json({ error: err.message }); + } + + console.log(`✅ Статус исполнителя ${userId} обновлен на completed`); + + // Логируем действие + try { + logActivity(parseInt(taskId), currentUserId, 'ASSIGNMENT_FORCE_COMPLETED', + `Исполнитель ${userId} принудительно отмечен как выполнивший задачу`); + } catch (e) { + console.error('Ошибка логирования:', e); + } + + // Проверяем, все ли исполнители выполнили задачу + db.all( + "SELECT status FROM task_assignments WHERE task_id = ?", + [taskId], + (err, assignments) => { + if (!err && assignments && assignments.length > 0) { + const allCompleted = assignments.every(a => a.status === 'completed'); + + if (allCompleted) { + // Если все исполнители выполнили, автоматически закрываем задачу + db.run( + "UPDATE tasks SET closed_at = CURRENT_TIMESTAMP, closed_by = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", + [currentUserId, taskId], + function(err) { + if (!err) { + console.log(`✅ Задача ${taskId} автоматически закрыта`); + try { + logActivity(parseInt(taskId), currentUserId, 'TASK_CLOSED', + 'Задача автоматически закрыта после выполнения всеми исполнителями'); + } catch (e) { + console.error('Ошибка логирования:', e); + } + } + } + ); + } + } + } + ); + + res.json({ + success: true, + message: 'Исполнитель отмечен как выполнивший задачу' + }); + } + ); + } + ); + }); +}); app.post('/api/tasks/:taskId/files', requireAuth, upload.array('files', 15), (req, res) => { const { taskId } = req.params; const userId = req.session.user.id;