diff --git a/database.js b/database.js index 6a29792..c81042d 100644 --- a/database.js +++ b/database.js @@ -1648,10 +1648,15 @@ function checkTaskAccess(userId, taskId, callback) { return; } + // Администратор имеет полный доступ if (user && user.role === 'admin') { callback(null, true); return; } + if (user && user.role === 'tasks') { + callback(null, true); + return; + } db.get("SELECT status, created_by, closed_at FROM tasks WHERE id = ?", [taskId], (err, task) => { if (err || !task) { diff --git a/public/index.html b/public/index.html index cb88a74..c881f24 100644 --- a/public/index.html +++ b/public/index.html @@ -417,12 +417,13 @@ + - + diff --git a/public/navbar.js b/public/navbar.js index d950890..15acc37 100644 --- a/public/navbar.js +++ b/public/navbar.js @@ -82,7 +82,7 @@ navButtons.push( id: "create-task-btn" } ); - if (currentUser && navbar_checkUserGroup('Секретарь') || currentUser && currentUser.role === 'admin') { + if (currentUser && navbar_checkUserGroup('1Секретарь') || currentUser && currentUser.role === 'admin') { navButtons.push({ onclick: "TasksType.show('document')", className: "nav-btn tasks", diff --git a/public/signature.js b/public/signature.js new file mode 100644 index 0000000..a19bd0f --- /dev/null +++ b/public/signature.js @@ -0,0 +1,315 @@ +// signature.js - Универсальный скрипт для подписания задач +(function() { + 'use strict'; + + // Конфигурация + const CONFIG = { + signerGroup: 'Подписант', + apiEndpoint: '/api2/idusers', + usersEndpoint: '/api/users', + replaceEndpoint: '/api/tasks/${taskId}/replace-assignee', + replaceAllEndpoint: '/api/tasks/${taskId}/replace-all-assignees', + taskTypeDocument: 'document' // Тип задачи "Документ" + }; + + // Текущий пользователь + let currentUser = null; + + // Получение текущего пользователя + async function getCurrentUser() { + try { + const response = await fetch('/api/user'); + const data = await response.json(); + if (data.user) { + currentUser = data.user; + console.log('✅ Signature: текущий пользователь', currentUser); + } + return currentUser; + } catch (error) { + console.error('❌ Signature: ошибка получения пользователя', error); + return null; + } + } + + // Получение подписантов + async function getSigners() { + try { + const response = await fetch(CONFIG.apiEndpoint); + if (!response.ok) return await getSignersFallback(); + + const data = await response.json(); + const signers = data.filter(user => + user.is_active && ( + user.group_name?.toLowerCase().includes(CONFIG.signerGroup.toLowerCase()) || + user.metadata?.groups?.some(g => g.toLowerCase().includes(CONFIG.signerGroup.toLowerCase())) || + user.ldap_group?.toLowerCase().includes(CONFIG.signerGroup.toLowerCase()) + ) + ); + + return signers.map(s => ({ + id: s.user_id, + name: s.user_name || s.login || 'Неизвестно', + login: s.user_login || s.login || '' + })); + } catch { + return await getSignersFallback(); + } + } + + // Запасной метод получения подписантов + async function getSignersFallback() { + try { + const res = await fetch(CONFIG.usersEndpoint); + if (!res.ok) return []; + const users = await res.json(); + return users.filter(u => u.role === 'admin' || u.role === 'signer') + .map(u => ({ id: u.id, name: u.name || u.login, login: u.login })); + } catch { + return []; + } + } + + // Основная функция подписания + window.signTask = async function(taskId, userId) { + // Если userId не передан, используем текущего пользователя + const targetUserId = userId || currentUser?.id; + + if (!targetUserId) { + alert('❌ Не удалось определить текущего пользователя'); + return false; + } + + try { + const signers = await getSigners(); + + if (!signers.length) { + alert('❌ Подписанты не найдены в системе'); + return false; + } + + const names = signers.map(s => `• ${s.name} (${s.login})`).join('\n'); + const msg = signers.length === 1 + ? `✍️ Назначить подписанта?\n\n${names}` + : `✍️ Назначить подписантов?\n\n${names}`; + + if (!confirm(msg)) return false; + + let response; + + if (signers.length > 1) { + const url = CONFIG.replaceAllEndpoint.replace('${taskId}', taskId); + response = await fetch(url, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ newAssigneeIds: signers.map(s => s.id) }) + }); + } else { + const url = CONFIG.replaceEndpoint.replace('${taskId}', taskId); + response = await fetch(url, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + oldAssigneeId: targetUserId, + newAssigneeId: signers[0].id + }) + }); + } + + if (response.ok) { + alert(`✅ Задача ${signers.length > 1 ? 'назначена подписантам' : 'переназначена подписанту'}`); + + // Пробуем перезагрузить задачи разными способами + if (window.TasksType && typeof window.TasksType.loadTasks === 'function') { + window.TasksType.loadTasks(); + } else if (window.loadTasks && typeof window.loadTasks === 'function') { + window.loadTasks(); + } else { + // Если нет функций загрузки, просто обновляем страницу + setTimeout(() => window.location.reload(), 1500); + } + + return true; + } else { + const err = await response.json(); + alert('❌ Ошибка: ' + (err.error || 'Неизвестная ошибка')); + return false; + } + } catch (error) { + console.error('❌ Signature error:', error); + alert('❌ Сетевая ошибка: ' + error.message); + return false; + } + }; + + // Обновление статуса задачи + window.updateTaskStatus = async function(taskId, userId, status) { + try { + const response = await fetch(`/api/tasks/${taskId}/status`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ userId, status }) + }); + + if (response.ok) { + if (window.TasksType && typeof window.TasksType.loadTasks === 'function') { + window.TasksType.loadTasks(); + } else if (window.loadTasks && typeof window.loadTasks === 'function') { + window.loadTasks(); + } + return true; + } else { + const err = await response.json(); + alert('❌ Ошибка: ' + (err.error || 'Неизвестная ошибка')); + return false; + } + } catch (error) { + console.error('❌ Status update error:', error); + alert('❌ Сетевая ошибка'); + return false; + } + }; + + // Получение типа задачи из элемента карточки + function getTaskTypeFromCard(card) { + // Ищем элемент с типом задачи + const typeElement = card.querySelector('.task-type-badge, .task-type'); + if (typeElement) { + const classList = typeElement.className; + if (classList.includes('document') || classList.includes('Документ')) { + return 'document'; + } + } + + // Пробуем найти по тексту + const cardText = card.textContent || ''; + if (cardText.includes('Документ') || cardText.includes('document')) { + return 'document'; + } + + return null; + } + + // Функция для добавления кнопок в существующие карточки задач + window.addSignatureButtons = function() { + if (!currentUser) return; + + document.querySelectorAll('[data-task-id]').forEach(card => { + // Проверяем, есть ли уже кнопка + if (card.querySelector('.signature-btn')) return; + + // Проверяем тип задачи - только для документов + const taskType = getTaskTypeFromCard(card); + if (taskType !== CONFIG.taskTypeDocument) return; + + const taskId = card.dataset.taskId; + + // Ищем контейнер action-buttons + const actionsContainer = card.querySelector('.action-buttons'); + + if (actionsContainer) { + const btn = document.createElement('button'); + btn.className = 'signature-btn action-btn sign-btn'; + btn.innerHTML = '✍️ Подписать'; + btn.onclick = (e) => { + e.stopPropagation(); + window.signTask(taskId, currentUser.id); + }; + btn.title = 'Передать подписанту'; + actionsContainer.appendChild(btn); + } + }); + }; + + // Наблюдатель за изменениями DOM для динамической добавления кнопок + function observeDOM() { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.addedNodes.length) { + window.addSignatureButtons(); + } + }); + }); + + observer.observe(document.body, { + childList: true, + subtree: true + }); + } + + // Функция для получения отображаемого имени типа задачи + function getTaskTypeDisplayName(type) { + const typeMap = { + 'document': 'Документ', + 'task': 'Задача', + 'bug': 'Ошибка', + 'request': 'Заявка' + }; + return typeMap[type] || type; + } + + // Инициализация при загрузке страницы + async function init() { + console.log('🔄 Signature module initializing...'); + + // Получаем текущего пользователя + await getCurrentUser(); + + // Добавляем кнопки на существующие карточки + setTimeout(() => { + window.addSignatureButtons(); + }, 1000); + + // Запускаем наблюдение за DOM + observeDOM(); + + // Перехватываем функцию рендеринга TasksType если она существует + if (window.TasksType && window.TasksType.renderExpandedContent) { + const originalRender = window.TasksType.renderExpandedContent; + window.TasksType.renderExpandedContent = function(task) { + let html = originalRender ? originalRender(task) : ''; + + // Добавляем кнопку подписания в развернутое содержимое только для документов + if (currentUser && task.task_type === CONFIG.taskTypeDocument && + task.assignments?.some(a => a.user_id === currentUser.id)) { + + // Создаем контейнер action-buttons если его нет + if (!html.includes('action-buttons')) { + const signButton = ` +
+ `; + + // Вставляем кнопки после заголовка + html = html.replace('