555 lines
27 KiB
JavaScript
555 lines
27 KiB
JavaScript
// 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';
|
||
callback(null, isAdmin);
|
||
});
|
||
});
|
||
}
|
||
|
||
// 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 для управления исполнителями подключено');
|
||
}; |