выполнить

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);
}
}
// Функция для принудительного завершения задачи исполнителя
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') ?
`<button onclick="updateStatus(${taskId}, ${assignment.user_id}, 'completed')">Выполнено</button>` : ''}
${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 ?
`<button class="edit-date-btn" onclick="openEditAssignmentModal(${taskId}, ${assignment.user_id})" title="Редактировать сроки">📅</button>` : ''}
</div>

View File

@@ -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;