From 27fd3543ec395b9a147d546eb509985d58804805 Mon Sep 17 00:00:00 2001 From: kalugin66 Date: Wed, 25 Feb 2026 16:00:39 +0500 Subject: [PATCH] upravlenie-service --- upravlenie-service.js | 415 ++++++++++++++++++++++++++---------------- 1 file changed, 261 insertions(+), 154 deletions(-) diff --git a/upravlenie-service.js b/upravlenie-service.js index 22a0525..26c97f5 100644 --- a/upravlenie-service.js +++ b/upravlenie-service.js @@ -126,63 +126,66 @@ class UpravlenieService { }); } - /** - * Создание нового подключения - */ - async createConnection(data) { - const { - service_id, service_name, service_type, login, password, - api_url, local_user_id, local_user_login, sync_direction = 'outgoing', - sync_enabled = 1, sync_interval = 60 - } = data; +/** + * Создание нового подключения + */ +async createConnection(data) { + const { + service_id, service_name, service_type, login, password, + api_url, local_user_id, local_user_login, sync_direction = 'outgoing', + sync_enabled = 1, sync_interval = 60 + } = data; - // Валидация service_id - if (service_id < SERVICE_ID_RANGE.min || service_id > SERVICE_ID_RANGE.max) { - throw new Error(`service_id должен быть в диапазоне ${SERVICE_ID_RANGE.min}-${SERVICE_ID_RANGE.max}`); - } - - // Для организатора api_url может быть пустым - if (service_type === 'executor' && !api_url) { - throw new Error('Для исполнителя необходимо указать api_url организатора'); - } - - // Проверяем уникальность service_id для активных подключений - const existing = await this.getConnectionByServiceId(service_id); - if (existing && existing.is_active) { - throw new Error(`Подключение с service_id ${service_id} уже существует`); - } - - const sql = ` - INSERT INTO upravlenie ( - service_id, service_name, service_type, login, password, - api_url, local_user_id, local_user_login, sync_direction, - sync_enabled, sync_interval - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `; - - return new Promise((resolve, reject) => { - this.db.run(sql, [ - service_id, service_name, service_type, login, password, - api_url, local_user_id || null, local_user_login || null, - sync_direction, sync_enabled, sync_interval - ], function(err) { - if (err) { - reject(err); - } else { - const connectionId = this.lastID; - console.log(`✅ Создано подключение ${service_name} (ID: ${connectionId}, service_id: ${service_id})`); - - // Запускаем синхронизацию для нового подключения - if (sync_enabled) { - this.startSyncJob(connectionId, sync_interval); - } - - resolve({ id: connectionId, ...data }); - } - }); - }); + // Валидация service_id + if (service_id < SERVICE_ID_RANGE.min || service_id > SERVICE_ID_RANGE.max) { + throw new Error(`service_id должен быть в диапазоне ${SERVICE_ID_RANGE.min}-${SERVICE_ID_RANGE.max}`); } + // Для организатора api_url может быть пустым + if (service_type === 'executor' && !api_url) { + throw new Error('Для исполнителя необходимо указать api_url организатора'); + } + + // Проверяем существование локального пользователя если указан + if (local_user_id) { + const userExists = await this.checkLocalUserExists(local_user_id); + if (!userExists) { + throw new Error(`Пользователь с ID ${local_user_id} не существует`); + } + } + + // Проверяем уникальность service_id для активных подключений + const existing = await this.getConnectionByServiceId(service_id); + if (existing && existing.is_active) { + throw new Error(`Подключение с service_id ${service_id} уже существует`); + } + + const sql = ` + INSERT INTO upravlenie ( + service_id, service_name, service_type, login, password, + api_url, local_user_id, local_user_login, sync_direction, + sync_enabled, sync_interval + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `; + + return new Promise((resolve, reject) => { + this.db.run(sql, [ + service_id, service_name, service_type, login, password, + api_url, local_user_id || null, local_user_login || null, + sync_direction, sync_enabled, sync_interval + ], function(err) { + if (err) { + reject(err); + } else { + const connectionId = this.lastID; + console.log(`✅ Создано подключение ${service_name} (ID: ${connectionId}, service_id: ${service_id})`); + + resolve({ id: connectionId, ...data }); + } + }); + }); +} + /** * Получение подключения по ID */ @@ -279,12 +282,6 @@ class UpravlenieService { reject(err); } else { console.log(`✅ Подключение ${id} обновлено`); - - // Перезапускаем задачу синхронизации если изменились настройки - if (data.sync_enabled !== undefined || data.sync_interval !== undefined) { - this.restartSyncJob(id); - } - resolve({ id, changes: this.changes }); } }); @@ -307,9 +304,9 @@ class UpravlenieService { sync_enabled: true }); - connections.forEach(conn => { + for (const conn of connections) { this.startSyncJob(conn.id, conn.sync_interval); - }); + } console.log(`✅ Запущено ${connections.length} задач синхронизации`); } @@ -419,52 +416,86 @@ class UpravlenieService { }); } - /** - * Синхронизация от исполнителя к организатору - */ - async syncToOrganizer(connection) { - if (!connection.api_url) { - throw new Error('Для исполнителя не указан api_url организатора'); - } - - // Получаем задачи, назначенные локальному пользователю - const localTasks = await this.getTasksForLocalUser(connection.local_user_id); - - // Получаем задачи от организатора - const organizerTasks = await this.fetchTasksFromOrganizer(connection); - - // Обновляем локальные задачи - await this.syncTasksWithOrganizer(connection, organizerTasks, localTasks); - - // Отправляем обновленные статусы организатору - await this.sendTaskStatusesToOrganizer(connection, localTasks); +/** + * Синхронизация от исполнителя к организатору + */ +async syncToOrganizer(connection) { + if (!connection.api_url) { + throw new Error('Для исполнителя не указан api_url организатора'); } - /** - * Синхронизация от организатора к исполнителям - */ - async syncFromExecutors(connection) { - // Получаем все активные подключения исполнителей с таким же service_id - const executors = await this.getAllConnections({ - service_type: 'executor', - is_active: true, - sync_enabled: true - }); + // Проверяем наличие локального пользователя + if (!connection.local_user_id) { + console.warn(`⚠️ Для подключения ${connection.service_name} не указан локальный пользователь. Задачи не будут создаваться.`); + return; + } - for (const executor of executors) { - try { - // Получаем статусы задач от исполнителя - const taskStatuses = await this.fetchTaskStatusesFromExecutor(executor); - + // Получаем задачи, назначенные локальному пользователю + const localTasks = await this.getTasksForLocalUser(connection.local_user_id); + + // Получаем задачи от организатора + const organizerTasks = await this.fetchTasksFromOrganizer(connection); + + if (organizerTasks.length === 0) { + console.log(`📭 Нет новых задач от организатора для ${connection.service_name}`); + return; + } + + // Обновляем локальные задачи + await this.syncTasksWithOrganizer(connection, organizerTasks, localTasks); + + // Отправляем обновленные статусы организатору + await this.sendTaskStatusesToOrganizer(connection, localTasks); +} + +/** + * Синхронизация от организатора к исполнителям + */ +async syncFromExecutors(connection) { + // Получаем все активные подключения исполнителей с таким же service_id + const executors = await this.getAllConnections({ + service_type: 'executor', + is_active: true, + sync_enabled: true + }); + + if (executors.length === 0) { + console.log(`📭 Нет активных исполнителей для организатора ${connection.service_name}`); + return; + } + + for (const executor of executors) { + try { + // Проверяем наличие локального пользователя у исполнителя + if (!executor.local_user_id) { + console.warn(`⚠️ Для исполнителя ${executor.service_name} не указан локальный пользователь. Пропускаем...`); + continue; + } + + // Получаем статусы задач от исполнителя + const taskStatuses = await this.fetchTaskStatusesFromExecutor(executor); + + if (taskStatuses.length > 0) { // Обновляем статусы в локальной БД await this.updateTaskStatusesFromExecutor(executor, taskStatuses); - - } catch (error) { - console.error(`❌ Ошибка получения статусов от исполнителя ${executor.service_name}:`, error.message); + console.log(`✅ Получено ${taskStatuses.length} статусов от исполнителя ${executor.service_name}`); } + + } catch (error) { + console.error(`❌ Ошибка получения статусов от исполнителя ${executor.service_name}:`, error.message); } } - +} +/** + * Проверка существования локального пользователя + */ +async checkLocalUserExists(userId) { + return new Promise((resolve) => { + this.db.get('SELECT id FROM users WHERE id = ?', [userId], (err, row) => { + resolve(!!row); + }); + }); +} /** * Получение задач для локального пользователя */ @@ -582,62 +613,99 @@ class UpravlenieService { } } - /** - * Создание задачи из данных организатора - */ - async createTaskFromOrganizer(connection, taskData) { - return new Promise((resolve, reject) => { - this.db.serialize(() => { - // Создаем задачу +// upravlenie-service.js (исправленная версия метода createTaskFromOrganizer) + +/** + * Создание задачи из данных организатора + */ +async createTaskFromOrganizer(connection, taskData) { + return new Promise((resolve, reject) => { + // Проверяем, что local_user_id не NULL + if (!connection.local_user_id) { + reject(new Error('local_user_id не может быть пустым. Укажите локального пользователя для создания задач.')); + return; + } + + // Сначала проверяем структуру таблицы tasks + this.db.all("PRAGMA table_info(tasks)", (err, columns) => { + if (err) { + reject(err); + return; + } + + const columnNames = columns.map(c => c.name); + + // Формируем SQL запрос динамически + let fields = ['title', 'description', 'status', 'created_by', 'start_date', 'due_date', 'task_type']; + let placeholders = ['?', '?', '?', '?', '?', '?', '?']; + let values = [ + taskData.title, + taskData.description || '', + 'active', + connection.local_user_id, // теперь точно не NULL + taskData.start_date || new Date().toISOString(), + taskData.due_date || null, + 'external' + ]; + + // Добавляем external_task_id если колонка существует + if (columnNames.includes('external_task_id')) { + fields.push('external_task_id'); + placeholders.push('?'); + values.push(taskData.id); + } + + // Добавляем external_service_id если колонка существует + if (columnNames.includes('external_service_id')) { + fields.push('external_service_id'); + placeholders.push('?'); + values.push(connection.service_id); + } + + const sql = `INSERT INTO tasks (${fields.join(', ')}) VALUES (${placeholders.join(', ')})`; + + console.log(`📝 Создание задачи от организатора:`, { + sql, + values, + local_user_id: connection.local_user_id, + service_id: connection.service_id + }); + + this.db.run(sql, values, function(err) { + if (err) { + console.error('❌ Ошибка создания задачи:', err); + reject(err); + return; + } + + const newTaskId = this.lastID; + + // Назначаем задачу локальному пользователю this.db.run( - `INSERT INTO tasks ( - title, description, status, created_by, - external_task_id, external_service_id, start_date, due_date, task_type - ) VALUES (?, ?, 'active', ?, ?, ?, ?, ?, 'external')`, + `INSERT INTO task_assignments (task_id, user_id, status, start_date, due_date) + VALUES (?, ?, ?, ?, ?)`, [ - taskData.title, - taskData.description || '', + newTaskId, connection.local_user_id, - taskData.id, - connection.service_id, + taskData.status || 'assigned', taskData.start_date || new Date().toISOString(), taskData.due_date || null ], - function(err) { + (err) => { if (err) { + console.error('❌ Ошибка назначения задачи:', err); reject(err); return; } - const newTaskId = this.lastID; - - // Назначаем задачу локальному пользователю - this.db.run( - `INSERT INTO task_assignments (task_id, user_id, status, start_date, due_date) - VALUES (?, ?, ?, ?, ?)`, - [ - newTaskId, - connection.local_user_id, - taskData.status || 'assigned', - taskData.start_date || new Date().toISOString(), - taskData.due_date || null - ], - (err) => { - if (err) { - reject(err); - return; - } - - console.log(`✅ Создана задача ${newTaskId} от организатора ${connection.service_name}`); - resolve(newTaskId); - } - ); + console.log(`✅ Создана задача ${newTaskId} от организатора ${connection.service_name}`); + resolve(newTaskId); } ); }); }); - } - + }); +} /** * Обновление задачи из данных организатора */ @@ -668,30 +736,65 @@ class UpravlenieService { this.db.run( `UPDATE task_assignments SET status = ?, updated_at = CURRENT_TIMESTAMP - WHERE task_id = ( - SELECT id FROM tasks - WHERE external_task_id = ? AND external_service_id = ? - ) AND user_id = ?`, + WHERE task_id = ? AND user_id = ?`, [ status.status, - status.external_task_id, - executor.service_id, + status.task_id, executor.local_user_id ], function(err) { if (err) reject(err); else { if (this.changes > 0) { - console.log(`✅ Обновлен статус задачи ${status.external_task_id}: ${status.status}`); + console.log(`✅ Обновлен статус задачи ${status.task_id}: ${status.status}`); } resolve(); } } ); }); + + // Если статус "completed" - проверяем и закрываем задачу если все выполнили + if (status.status === 'completed') { + await this.checkAndCloseTaskIfAllCompleted(status.task_id); + } } } + /** + * Проверка и закрытие задачи если все исполнители выполнили + */ + async checkAndCloseTaskIfAllCompleted(taskId) { + return new Promise((resolve) => { + this.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) { + this.db.run( + 'UPDATE tasks SET closed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP WHERE id = ?', + [taskId], + (err) => { + if (!err) { + console.log(`✅ Задача ${taskId} автоматически закрыта`); + } + resolve(); + } + ); + } else { + resolve(); + } + } else { + resolve(); + } + } + ); + }); + } + /** * Загрузка файла в удаленный сервис */ @@ -767,7 +870,7 @@ class UpravlenieService { // Сохраняем файл локально const { createUserTaskFolder } = require('./database'); - const userFolder = createUserTaskFolder(localTaskId, connection.local_user_login); + const userFolder = createUserTaskFolder(localTaskId, connection.local_user_login || 'external'); const fileName = response.headers['x-file-name'] || `remote_${Date.now()}.bin`; const filePath = path.join(userFolder, fileName); @@ -904,7 +1007,7 @@ function setupUpravlenieEndpoints(app, db) { // Middleware для проверки аутентификации const requireAuth = (req, res, next) => { - if (!req.session.user) { + if (!req.session || !req.session.user) { return res.status(401).json({ error: 'Требуется аутентификация' }); } next(); @@ -912,7 +1015,10 @@ function setupUpravlenieEndpoints(app, db) { // Middleware для проверки прав администратора const requireAdmin = (req, res, next) => { - if (!req.session.user || req.session.user.role !== 'admin') { + if (!req.session || !req.session.user) { + return res.status(401).json({ error: 'Требуется аутентификация' }); + } + if (req.session.user.role !== 'admin') { return res.status(403).json({ error: 'Требуются права администратора' }); } next(); @@ -966,7 +1072,7 @@ function setupUpravlenieEndpoints(app, db) { res.json({ success: true, message: 'Подключение создано', - connection + connection: connection }); } catch (error) { console.error('❌ Ошибка создания подключения:', error); @@ -1066,6 +1172,7 @@ function setupUpravlenieEndpoints(app, db) { req.externalConnection = connection; next(); } catch (error) { + console.error('❌ Ошибка аутентификации:', error); res.status(500).json({ error: 'Ошибка аутентификации' }); } }; @@ -1178,7 +1285,7 @@ function setupUpravlenieEndpoints(app, db) { // Если статус "completed" - закрываем задачу если все исполнители выполнили if (status.status === 'completed') { - await checkAndCloseTaskIfAllCompleted(status.task_id); + await checkAndCloseTaskIfAllCompleted(db, status.task_id); } } @@ -1343,7 +1450,7 @@ function setupUpravlenieEndpoints(app, db) { }); // Вспомогательная функция для проверки и закрытия задачи - async function checkAndCloseTaskIfAllCompleted(taskId) { + async function checkAndCloseTaskIfAllCompleted(db, taskId) { return new Promise((resolve) => { db.all( 'SELECT status FROM task_assignments WHERE task_id = ?',