выполнить

This commit is contained in:
2026-02-12 19:52:41 +05:00
parent 139b53ffbd
commit 155687f3bb
2 changed files with 217 additions and 57 deletions

View File

@@ -227,7 +227,32 @@ function openReworkAssignmentModal(taskId, userId, userName) {
setTimeout(() => textarea.focus(), 100); 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 // Вспомогательная функция для экранирования HTML
function escapeHtml(text) { function escapeHtml(text) {
if (!text) return ''; if (!text) return '';
@@ -426,7 +451,9 @@ ${isCurrentUser && assignment.status === 'assigned' ?
${isCurrentUser && (assignment.status === 'in_progress' || assignment.status === 'overdue' || assignment.status === 'rework') ? ${isCurrentUser && (assignment.status === 'in_progress' || assignment.status === 'overdue' || assignment.status === 'rework') ?
`<button onclick="updateStatus(${taskId}, ${assignment.user_id}, 'completed')">Выполнено</button>` : ''} `<button onclick="updateStatus(${taskId}, ${assignment.user_id}, 'completed')">Выполнено</button>` : ''}
${isTaskCreator && assignment.status !== 'assigned' ? ${isTaskCreator && assignment.status !== 'assigned' ?
`<button class="rework-btn" onclick="openReworkAssignmentModal(${taskId}, ${assignment.user_id}, '${assignment.user_name}')" title="Отправить на доработку">🔄</button>` : ''} `<button class="rework-btn" onclick="openReworkAssignmentModal(${taskId}, ${assignment.user_id}, '${assignment.user_name}')" title="Отправить на доработку">🔄Переделать</button>` : ''}
${isTaskCreator && assignment.status !== 'completed' ?
`<button class="force-complete-btn" onclick="forceCompleteAssignment(${taskId}, ${assignment.user_id}, '${assignment.user_name}')" title="Принудительно отметить как выполненное">✅ Завершить</button>` : ''}
${canEdit ? ${canEdit ?
`<button class="edit-date-btn" onclick="openEditAssignmentModal(${taskId}, ${assignment.user_id})" title="Редактировать сроки">📅</button>` : ''} `<button class="edit-date-btn" onclick="openEditAssignmentModal(${taskId}, ${assignment.user_id})" title="Редактировать сроки">📅</button>` : ''}
</div> </div>

View File

@@ -1275,6 +1275,12 @@ app.put('/api/tasks/:taskId/rework-assignment/:userId', requireAuth, (req, res)
const { comment } = req.body; const { comment } = req.body;
const currentUserId = req.session.user.id; const currentUserId = req.session.user.id;
console.log('=== ОТПРАВКА НА ДОРАБОТКУ ===');
console.log('taskId:', taskId);
console.log('userId:', userId);
console.log('comment:', comment);
console.log('==============================');
if (!comment) { if (!comment) {
return res.status(400).json({ error: 'Комментарий к доработке обязателен' }); return res.status(400).json({ error: 'Комментарий к доработке обязателен' });
} }
@@ -1285,11 +1291,22 @@ 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: 'Только автор задачи может отправлять на доработку' }); return res.status(403).json({ error: 'Только автор задачи может отправлять на доработку' });
} }
db.serialize(() => { // Проверяем существование назначения
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( db.run(
`UPDATE task_assignments `UPDATE task_assignments
@@ -1304,15 +1321,12 @@ app.put('/api/tasks/:taskId/rework-assignment/:userId', requireAuth, (req, res)
return; return;
} }
if (this.changes === 0) {
return res.status(404).json({ error: 'Исполнитель не найден в задаче' });
}
// Логируем действие // Логируем действие
const { logActivity } = require('./database'); try {
if (logActivity) { logActivity(parseInt(taskId), currentUserId, 'ASSIGNMENT_REWORK',
logActivity(taskId, currentUserId, 'ASSIGNMENT_REWORK', `Исполнитель ${userId} отправлен на доработку: ${comment.substring(0, 50)}`);
`Исполнитель ${userId} отправлен на доработку: ${comment}`); } catch (e) {
console.error('Ошибка логирования:', e);
} }
// Получаем данные для уведомления // Получаем данные для уведомления
@@ -1322,7 +1336,7 @@ app.put('/api/tasks/:taskId/rework-assignment/:userId', requireAuth, (req, res)
WHERE t.id = ?`, WHERE t.id = ?`,
[userId, taskId], (err, taskData) => { [userId, taskId], (err, taskData) => {
if (!err && taskData) { if (!err && taskData) {
const { sendTaskNotifications } = require('./notifications'); try {
sendTaskNotifications( sendTaskNotifications(
'rework_assignment', 'rework_assignment',
taskId, taskId,
@@ -1332,8 +1346,11 @@ app.put('/api/tasks/:taskId/rework-assignment/:userId', requireAuth, (req, res)
comment, comment,
'rework', 'rework',
req.session.user.name, req.session.user.name,
userId // ID исполнителя для персонального уведомления userId
); );
} catch (e) {
console.error('Ошибка отправки уведомления:', e);
}
} }
}); });
@@ -1343,10 +1360,126 @@ app.put('/api/tasks/:taskId/rework-assignment/:userId', requireAuth, (req, res)
}); });
} }
); );
}); }
);
}); });
}); });
// 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) => { app.post('/api/tasks/:taskId/files', requireAuth, upload.array('files', 15), (req, res) => {
const { taskId } = req.params; const { taskId } = req.params;
const userId = req.session.user.id; const userId = req.session.user.id;