Files
minicrm/api-users.js
2026-02-13 14:02:21 +05:00

556 lines
28 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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';
const isTasks = user.role === 'tasks'; // Пользователи с ролью tasks тоже имеют доступ
callback(null, isAdmin || isTasks);
});
});
}
// 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 для управления исполнителями подключено');
};