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;