ознакомление
This commit is contained in:
@@ -108,6 +108,7 @@
|
|||||||
<option value="">Все типы</option>
|
<option value="">Все типы</option>
|
||||||
<option value="regular">Обычная задача</option>
|
<option value="regular">Обычная задача</option>
|
||||||
<option value="document">Согласование документа</option>
|
<option value="document">Согласование документа</option>
|
||||||
|
<option value="acquaintance">Ознакомление</option>
|
||||||
<option value="it">ИТ отдел</option>
|
<option value="it">ИТ отдел</option>
|
||||||
<option value="ahch">АХЧ</option>
|
<option value="ahch">АХЧ</option>
|
||||||
<option value="psychologist">Психолог</option>
|
<option value="psychologist">Психолог</option>
|
||||||
@@ -162,6 +163,7 @@
|
|||||||
<div class="task-type-selector">
|
<div class="task-type-selector">
|
||||||
<div class="task-type-buttons">
|
<div class="task-type-buttons">
|
||||||
<button type="button" class="task-type-btn active" data-type="regular" onclick="selectTaskType('regular')"><i class="fas fa-tasks"></i> Обычная задача</button>
|
<button type="button" class="task-type-btn active" data-type="regular" onclick="selectTaskType('regular')"><i class="fas fa-tasks"></i> Обычная задача</button>
|
||||||
|
<button type="button" class="task-type-btn" data-type="acquaintance" onclick="selectTaskType('acquaintance')"><i class="fas fa-eye"></i> Ознакомление</button>
|
||||||
<button type="button" class="task-type-btn" data-type="document" onclick="selectTaskType('document')"><i class="fas fa-file-signature"></i> Согласование документа</button>
|
<button type="button" class="task-type-btn" data-type="document" onclick="selectTaskType('document')"><i class="fas fa-file-signature"></i> Согласование документа</button>
|
||||||
<button type="button" class="task-type-btn" data-type="it" onclick="selectTaskType('it')"><i class="fas fa-desktop"></i> Заявка в ИТ отдел</button>
|
<button type="button" class="task-type-btn" data-type="it" onclick="selectTaskType('it')"><i class="fas fa-desktop"></i> Заявка в ИТ отдел</button>
|
||||||
<button type="button" class="task-type-btn" data-type="ahch" onclick="selectTaskType('ahch')"><i class="fas fa-tools"></i> Заявка в АХЧ</button>
|
<button type="button" class="task-type-btn" data-type="ahch" onclick="selectTaskType('ahch')"><i class="fas fa-tools"></i> Заявка в АХЧ</button>
|
||||||
@@ -522,7 +524,42 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="acquaintance-task-modal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span class="close" onclick="closeAcquaintanceModal()">×</span>
|
||||||
|
<h3>Создать задачу для ознакомления</h3>
|
||||||
|
<form id="acquaintance-task-form" enctype="multipart/form-data">
|
||||||
|
<input type="hidden" id="acquaintance-original-task-id">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Исходная задача:</label>
|
||||||
|
<div id="acquaintance-original-title"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Автор задачи (выберите из списка):</label>
|
||||||
|
<div class="user-search">
|
||||||
|
<input type="text" id="acquaintance-author-search" placeholder="Поиск авторов..." oninput="filterAcquaintanceAuthors()">
|
||||||
|
</div>
|
||||||
|
<div id="acquaintance-authors-checklist" class="checkbox-group"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Исполнитель:</label>
|
||||||
|
<div id="acquaintance-executor-info" style="padding: 10px; background: #f0f0f0; border-radius: 5px;">
|
||||||
|
<!-- сюда будет подставлено имя текущего пользователя -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="acquaintance-due-date">Дата выполнения:</label>
|
||||||
|
<input type="date" id="acquaintance-due-date" name="dueDate" required>
|
||||||
|
<input type="hidden" id="acquaintance-due-time" name="dueTime" value="19:00">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="acquaintance-comment">Комментарий (необязательно):</label>
|
||||||
|
<textarea id="acquaintance-comment" rows="3" placeholder="Добавьте комментарий к задаче ознакомления"></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn-primary">Создать задачу ознакомления</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="kanban-section" class="section kanban-section">
|
<div id="kanban-section" class="section kanban-section">
|
||||||
<div id="kanban-board" class="kanban-board">
|
<div id="kanban-board" class="kanban-board">
|
||||||
<div class="loading">Загрузка Канбан-доски...</div>
|
<div class="loading">Загрузка Канбан-доски...</div>
|
||||||
|
|||||||
@@ -85,6 +85,12 @@ function setupEventListeners() {
|
|||||||
notificationForm._hasSubmitListener = true;
|
notificationForm._hasSubmitListener = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ознакомление
|
||||||
|
const acquaintanceForm = document.getElementById('acquaintance-task-form');
|
||||||
|
if (acquaintanceForm && !acquaintanceForm._hasSubmitListener) {
|
||||||
|
acquaintanceForm.addEventListener('submit', createAcquaintanceTask);
|
||||||
|
acquaintanceForm._hasSubmitListener = true;
|
||||||
|
}
|
||||||
// Инициализация загрузки файлов
|
// Инициализация загрузки файлов
|
||||||
initializeFileUploads();
|
initializeFileUploads();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,15 @@ if (currentUser && (currentUser.role === 'admin' || (currentUser.role === 'tasks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Кнопка "Создать ознакомление" для админов и tasks
|
||||||
|
if (currentUser && (currentUser.role === 'admin' || currentUser.role === 'tasks')) {
|
||||||
|
actions.push({
|
||||||
|
label: '📖 Создать ознакомление',
|
||||||
|
handler: () => openAcquaintanceModal(task.id),
|
||||||
|
primary: true // можно отнести к админским или отдельной категории
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Доработка и изменение срока для необычных задач (исполнитель)
|
// Доработка и изменение срока для необычных задач (исполнитель)
|
||||||
if (!isDeleted && !isClosed && task.task_type !== 'regular' && task.assignments && task.assignments.some(a => parseInt(a.user_id) === currentUser?.id)) {
|
if (!isDeleted && !isClosed && task.task_type !== 'regular' && task.assignments && task.assignments.some(a => parseInt(a.user_id) === currentUser?.id)) {
|
||||||
if (typeof openReworkModal === 'function') { actions.push({ label: '🔄 Доработка', handler: () => openReworkModal(taskId),primary_task: true});
|
if (typeof openReworkModal === 'function') { actions.push({ label: '🔄 Доработка', handler: () => openReworkModal(taskId),primary_task: true});
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ const TASK_TYPE_OPTIONS = [
|
|||||||
{ value: 'speech_therapist', label: 'Логопед' },
|
{ value: 'speech_therapist', label: 'Логопед' },
|
||||||
{ value: 'hr', label: 'Диспетчер расписания' },
|
{ value: 'hr', label: 'Диспетчер расписания' },
|
||||||
{ value: 'certificate', label: 'Справка' },
|
{ value: 'certificate', label: 'Справка' },
|
||||||
{ value: 'e_journal', label: 'Эл. журнал' }
|
{ value: 'e_journal', label: 'Эл. журнал' },
|
||||||
|
{ value: 'acquaintance', label: 'Ознакомление' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// Функция показа секции отчёта
|
// Функция показа секции отчёта
|
||||||
|
|||||||
@@ -5202,3 +5202,7 @@ button.btn-primary {
|
|||||||
.btn-reset:hover {
|
.btn-reset:hover {
|
||||||
background: #5a6268;
|
background: #5a6268;
|
||||||
}
|
}
|
||||||
|
.task-type-badge.acquaintance {
|
||||||
|
background: #3498db; /* или любой подходящий цвет */
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
103
public/tasks.js
103
public/tasks.js
@@ -628,6 +628,106 @@ function canUserAddFilesToTask(task) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== ФУНКЦИИ ДЛЯ ОЗНАКОМЛЕНИЯ ====================
|
||||||
|
|
||||||
|
async function openAcquaintanceModal(taskId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/tasks/${taskId}`);
|
||||||
|
if (!response.ok) throw new Error('Ошибка загрузки задачи');
|
||||||
|
const task = await response.json();
|
||||||
|
|
||||||
|
// Заполняем модальное окно
|
||||||
|
document.getElementById('acquaintance-original-task-id').value = task.id;
|
||||||
|
document.getElementById('acquaintance-original-title').innerHTML = `
|
||||||
|
<strong>№${task.id}</strong> ${task.title}<br>
|
||||||
|
<small>Автор: ${task.creator_name}</small>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Устанавливаем дату выполнения по умолчанию (завтра)
|
||||||
|
const tomorrow = new Date();
|
||||||
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||||
|
document.getElementById('acquaintance-due-date').value = tomorrow.toISOString().split('T')[0];
|
||||||
|
document.getElementById('acquaintance-due-time').value = '19:00';
|
||||||
|
|
||||||
|
// Загружаем пользователей для выбора автора
|
||||||
|
await loadUsers(); // гарантируем, что users загружены
|
||||||
|
renderAcquaintanceAuthorsChecklist(users);
|
||||||
|
|
||||||
|
// Информация об исполнителе (текущий пользователь)
|
||||||
|
const executorInfo = document.getElementById('acquaintance-executor-info');
|
||||||
|
if (executorInfo) {
|
||||||
|
executorInfo.innerHTML = `Исполнитель: ${currentUser.name} (${currentUser.login})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отображаем модальное окно
|
||||||
|
document.getElementById('acquaintance-task-modal').style.display = 'block';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка открытия модального окна ознакомления:', error);
|
||||||
|
alert('Не удалось загрузить задачу');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAcquaintanceModal() {
|
||||||
|
const modal = document.getElementById('acquaintance-task-modal');
|
||||||
|
if (modal) modal.style.display = 'none';
|
||||||
|
|
||||||
|
const authorSearch = document.getElementById('acquaintance-author-search');
|
||||||
|
if (authorSearch) authorSearch.value = '';
|
||||||
|
|
||||||
|
const userSearch = document.getElementById('acquaintance-user-search');
|
||||||
|
if (userSearch) userSearch.value = '';
|
||||||
|
|
||||||
|
acquaintanceSelectedUsers = [];
|
||||||
|
acquaintanceSelectedAuthor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createAcquaintanceTask(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const originalTaskId = document.getElementById('acquaintance-original-task-id').value;
|
||||||
|
const dueDate = document.getElementById('acquaintance-due-date').value;
|
||||||
|
const dueTime = document.getElementById('acquaintance-due-time').value;
|
||||||
|
const fullDueDateTime = `${dueDate}T${dueTime}:00`;
|
||||||
|
const comment = document.getElementById('acquaintance-comment').value.trim();
|
||||||
|
const assignedUserIds = [currentUser.id]; // исполнитель – текущий пользователь
|
||||||
|
const creatorId = document.querySelector('input[name="acquaintance-author"]:checked')?.value;
|
||||||
|
|
||||||
|
if (!creatorId) {
|
||||||
|
alert('Выберите автора задачи');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/tasks/acquaintance', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
originalTaskId,
|
||||||
|
dueDate: fullDueDateTime,
|
||||||
|
assignedUserIds,
|
||||||
|
creatorId,
|
||||||
|
comment
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
alert('Задача ознакомления успешно создана!');
|
||||||
|
closeAcquaintanceModal();
|
||||||
|
loadTasks();
|
||||||
|
loadActivityLogs();
|
||||||
|
} else {
|
||||||
|
alert(`Ошибка: ${result.error || 'Неизвестная ошибка'}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка создания задачи ознакомления:', error);
|
||||||
|
alert('Сетевая ошибка');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Добавляем отладочную функцию
|
// Добавляем отладочную функцию
|
||||||
function debugDocumentFields() {
|
function debugDocumentFields() {
|
||||||
console.log('=== ОТЛАДКА ПОЛЕЙ ДОКУМЕНТОВ ===');
|
console.log('=== ОТЛАДКА ПОЛЕЙ ДОКУМЕНТОВ ===');
|
||||||
@@ -648,3 +748,6 @@ window.debugDocumentFields = debugDocumentFields;
|
|||||||
window.loadTasks = loadTasks;
|
window.loadTasks = loadTasks;
|
||||||
window.updateAssignment = updateAssignment;
|
window.updateAssignment = updateAssignment;
|
||||||
window.renderTasksForActiveSection = renderTasksForActiveSection;
|
window.renderTasksForActiveSection = renderTasksForActiveSection;
|
||||||
|
window.openAcquaintanceModal = openAcquaintanceModal;
|
||||||
|
window.closeAcquaintanceModal = closeAcquaintanceModal;
|
||||||
|
window.createAcquaintanceTask = createAcquaintanceTask;
|
||||||
@@ -1406,7 +1406,8 @@ function getTaskTypeName(type) {
|
|||||||
'Social_educator': 'повод для обращения к cоциальному педагогу: ',
|
'Social_educator': 'повод для обращения к cоциальному педагогу: ',
|
||||||
'hr': 'вопрос к кадровой службе',
|
'hr': 'вопрос к кадровой службе',
|
||||||
'certificate': 'тип необходимой справки',
|
'certificate': 'тип необходимой справки',
|
||||||
'e_journal': 'информацию для доступа к журналу'
|
'e_journal': 'информацию для доступа к журналу',
|
||||||
|
'acquaintance': 'Ознакомление с документом'
|
||||||
};
|
};
|
||||||
return typeNames[type] || 'задачу';
|
return typeNames[type] || 'задачу';
|
||||||
}
|
}
|
||||||
@@ -1422,7 +1423,8 @@ function getTaskTypeDisplayName(type) {
|
|||||||
'Social_educator': 'Социальный педагог: ',
|
'Social_educator': 'Социальный педагог: ',
|
||||||
'hr': 'Кадры',
|
'hr': 'Кадры',
|
||||||
'certificate': 'Справка',
|
'certificate': 'Справка',
|
||||||
'e_journal': 'Эл. журнал'
|
'e_journal': 'Эл. журнал',
|
||||||
|
'acquaintance': 'Ознакомление'
|
||||||
};
|
};
|
||||||
return typeNames[type] || type;
|
return typeNames[type] || type;
|
||||||
}
|
}
|
||||||
@@ -1438,7 +1440,8 @@ function getTaskTypeIcon(type) {
|
|||||||
'Social_educator': 'fas fa-brain',
|
'Social_educator': 'fas fa-brain',
|
||||||
'hr': 'fas fa-users',
|
'hr': 'fas fa-users',
|
||||||
'certificate': 'fas fa-file-certificate',
|
'certificate': 'fas fa-file-certificate',
|
||||||
'e_journal': 'fas fa-book'
|
'e_journal': 'fas fa-book',
|
||||||
|
'acquaintance': 'fas fa-eye'
|
||||||
};
|
};
|
||||||
return icons[type] || 'fas fa-tasks';
|
return icons[type] || 'fas fa-tasks';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ let filteredUsers = [];
|
|||||||
let selectedUsers = [];
|
let selectedUsers = [];
|
||||||
let editSelectedUsers = [];
|
let editSelectedUsers = [];
|
||||||
let copySelectedUsers = [];
|
let copySelectedUsers = [];
|
||||||
|
let acquaintanceSelectedUsers = []; // исполнители для ознакомления
|
||||||
|
let acquaintanceSelectedAuthor = null; // выбранный автор для ознакомления
|
||||||
|
|
||||||
// Переменные для пользовательских списков
|
// Переменные для пользовательских списков
|
||||||
let userLists = [];
|
let userLists = [];
|
||||||
@@ -662,6 +664,76 @@ async function saveListFromModal() {
|
|||||||
closeListModal();
|
closeListModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== ФУНКЦИИ ДЛЯ ОЗНАКОМЛЕНИЯ (выбор исполнителей) ====================
|
||||||
|
|
||||||
|
function renderAcquaintanceUsersChecklist(filtered = users) {
|
||||||
|
const container = document.getElementById('acquaintance-users-checklist');
|
||||||
|
if (!container) return;
|
||||||
|
container.innerHTML = filtered
|
||||||
|
.filter(user => user.id !== currentUser?.id)
|
||||||
|
.map(user => `
|
||||||
|
<div class="checkbox-item">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="assignedUsers" value="${user.id}"
|
||||||
|
onchange="toggleAcquaintanceUserSelection(this, ${user.id})"
|
||||||
|
${acquaintanceSelectedUsers.includes(user.id) ? 'checked' : ''}>
|
||||||
|
${escapeHtml(user.name)} (${user.login})
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAcquaintanceUserSelection(checkbox, userId) {
|
||||||
|
if (checkbox.checked) {
|
||||||
|
if (!acquaintanceSelectedUsers.includes(userId)) acquaintanceSelectedUsers.push(userId);
|
||||||
|
} else {
|
||||||
|
acquaintanceSelectedUsers = acquaintanceSelectedUsers.filter(id => id !== userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function filterAcquaintanceUsers() {
|
||||||
|
const search = document.getElementById('acquaintance-user-search')?.value.toLowerCase() || '';
|
||||||
|
let filtered = users.filter(user =>
|
||||||
|
user.name.toLowerCase().includes(search) ||
|
||||||
|
user.login.toLowerCase().includes(search) ||
|
||||||
|
(user.email && user.email.toLowerCase().includes(search))
|
||||||
|
);
|
||||||
|
renderAcquaintanceUsersChecklist(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== ФУНКЦИИ ДЛЯ ВЫБОРА АВТОРА В ЗАДАЧЕ ОЗНАКОМЛЕНИЯ ====================
|
||||||
|
|
||||||
|
function renderAcquaintanceAuthorsChecklist(filtered = users) {
|
||||||
|
const container = document.getElementById('acquaintance-authors-checklist');
|
||||||
|
if (!container) return;
|
||||||
|
container.innerHTML = filtered
|
||||||
|
.filter(user => user.id !== currentUser?.id) // можно разрешить выбирать себя, если нужно
|
||||||
|
.map(user => `
|
||||||
|
<div class="checkbox-item">
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="acquaintance-author" value="${user.id}"
|
||||||
|
onchange="toggleAcquaintanceAuthorSelection(this, ${user.id})"
|
||||||
|
${acquaintanceSelectedAuthor === user.id ? 'checked' : ''}>
|
||||||
|
${escapeHtml(user.name)} (${user.login})
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAcquaintanceAuthorSelection(radio, userId) {
|
||||||
|
acquaintanceSelectedAuthor = radio.checked ? userId : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function filterAcquaintanceAuthors() {
|
||||||
|
const search = document.getElementById('acquaintance-author-search')?.value.toLowerCase() || '';
|
||||||
|
let filtered = users.filter(user =>
|
||||||
|
user.name.toLowerCase().includes(search) ||
|
||||||
|
user.login.toLowerCase().includes(search) ||
|
||||||
|
(user.email && user.email.toLowerCase().includes(search))
|
||||||
|
);
|
||||||
|
renderAcquaintanceAuthorsChecklist(filtered);
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ====================
|
// ==================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ====================
|
||||||
|
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
@@ -705,6 +777,18 @@ window.closeListModal = closeListModal;
|
|||||||
window.saveListFromModal = saveListFromModal;
|
window.saveListFromModal = saveListFromModal;
|
||||||
window.filterListUsers = filterListUsers;
|
window.filterListUsers = filterListUsers;
|
||||||
|
|
||||||
|
// Экспорт функций для ознакомления (исполнители)
|
||||||
|
window.renderAcquaintanceUsersChecklist = renderAcquaintanceUsersChecklist;
|
||||||
|
window.toggleAcquaintanceUserSelection = toggleAcquaintanceUserSelection;
|
||||||
|
window.filterAcquaintanceUsers = filterAcquaintanceUsers;
|
||||||
|
window.acquaintanceSelectedUsers = acquaintanceSelectedUsers;
|
||||||
|
|
||||||
|
// Экспорт функций для выбора автора
|
||||||
|
window.renderAcquaintanceAuthorsChecklist = renderAcquaintanceAuthorsChecklist;
|
||||||
|
window.toggleAcquaintanceAuthorSelection = toggleAcquaintanceAuthorSelection;
|
||||||
|
window.filterAcquaintanceAuthors = filterAcquaintanceAuthors;
|
||||||
|
window.acquaintanceSelectedAuthor = acquaintanceSelectedAuthor;
|
||||||
|
|
||||||
// Также экспортируем переменные, которые могут понадобиться в других скриптах
|
// Также экспортируем переменные, которые могут понадобиться в других скриптах
|
||||||
window.selectedUsers = selectedUsers;
|
window.selectedUsers = selectedUsers;
|
||||||
window.editSelectedUsers = editSelectedUsers;
|
window.editSelectedUsers = editSelectedUsers;
|
||||||
|
|||||||
@@ -2056,6 +2056,112 @@ app.post('/api/tasks/:taskId/copy', requireAuth, checkTaskCreationTimeout, (req,
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// API для создания задачи ознакомления с указанием автора
|
||||||
|
app.post('/api/tasks/acquaintance', requireAuth, checkTaskCreationTimeout, (req, res) => {
|
||||||
|
const { originalTaskId, dueDate, assignedUserIds, creatorId, comment } = req.body;
|
||||||
|
const currentUserId = req.session.user.id;
|
||||||
|
const currentUser = req.session.user;
|
||||||
|
|
||||||
|
// Валидация
|
||||||
|
if (!originalTaskId) return res.status(400).json({ error: 'Не указана исходная задача' });
|
||||||
|
if (!dueDate) return res.status(400).json({ error: 'Дата выполнения обязательна' });
|
||||||
|
if (!assignedUserIds || assignedUserIds.length === 0) return res.status(400).json({ error: 'Не указаны исполнители' });
|
||||||
|
if (!creatorId) return res.status(400).json({ error: 'Не указан автор задачи' });
|
||||||
|
|
||||||
|
// Проверка прав: только admin и tasks могут создавать задачу от имени другого пользователя
|
||||||
|
if (currentUser.role !== 'admin' && currentUser.role !== 'tasks') {
|
||||||
|
return res.status(403).json({ error: 'Недостаточно прав для создания задачи от имени другого пользователя' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Исполнителем должен быть только текущий пользователь
|
||||||
|
if (assignedUserIds.length !== 1 || parseInt(assignedUserIds[0]) !== currentUserId) {
|
||||||
|
return res.status(400).json({ error: 'Исполнителем может быть только текущий пользователь' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.taskCreationCheckPassed) {
|
||||||
|
return res.status(429).json({ error: 'Слишком частое создание задач' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем исходную задачу с именем автора
|
||||||
|
db.get(`
|
||||||
|
SELECT t.*, u.name as creator_name
|
||||||
|
FROM tasks t
|
||||||
|
LEFT JOIN users u ON t.created_by = u.id
|
||||||
|
WHERE t.id = ?
|
||||||
|
`, [originalTaskId], (err, originalTask) => {
|
||||||
|
if (err || !originalTask) {
|
||||||
|
return res.status(404).json({ error: 'Исходная задача не найдена' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const startDate = new Date().toISOString();
|
||||||
|
const taskType = 'acquaintance';
|
||||||
|
|
||||||
|
const newTitle = `Ознакомление: ${originalTask.title}`;
|
||||||
|
const newDescription = `Задача для ознакомления
|
||||||
|
|
||||||
|
Оригинал: задача №${originalTask.id} "${originalTask.title}"
|
||||||
|
Автор оригинала: ${originalTask.creator_name || 'Неизвестно'}
|
||||||
|
${comment ? `Комментарий: ${comment}\n` : ''}
|
||||||
|
---
|
||||||
|
${originalTask.description || ''}`;
|
||||||
|
|
||||||
|
// Создаём задачу с указанным автором (creatorId)
|
||||||
|
db.run(
|
||||||
|
`INSERT INTO tasks
|
||||||
|
(title, description, created_by, original_task_id, start_date, due_date, task_type)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
[newTitle, newDescription, creatorId, originalTaskId, startDate, dueDate, taskType],
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error('❌ Ошибка создания задачи ознакомления:', err);
|
||||||
|
return res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTaskId = this.lastID;
|
||||||
|
|
||||||
|
// Обновляем таймаут создания
|
||||||
|
if (typeof updateLastTaskCreationTime === 'function') {
|
||||||
|
updateLastTaskCreationTime(currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем метаданные
|
||||||
|
if (typeof saveTaskMetadata === 'function') {
|
||||||
|
saveTaskMetadata(newTaskId, newTitle, newDescription, creatorId, originalTaskId, startDate, dueDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Назначаем исполнителя (текущий пользователь)
|
||||||
|
db.run(
|
||||||
|
`INSERT INTO task_assignments (task_id, user_id, start_date, due_date, status)
|
||||||
|
VALUES (?, ?, ?, ?, 'assigned')`,
|
||||||
|
[newTaskId, currentUserId, startDate, dueDate],
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.error('❌ Ошибка назначения исполнителя:', err);
|
||||||
|
db.run("DELETE FROM tasks WHERE id = ?", [newTaskId]);
|
||||||
|
return res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof logActivity === 'function') {
|
||||||
|
logActivity(newTaskId, currentUserId, 'TASK_CREATED',
|
||||||
|
`Создана задача ознакомления: ${newTitle} (автор: ${creatorId}, исполнитель: ${currentUserId})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof sendTaskNotifications === 'function') {
|
||||||
|
sendTaskNotifications('created', newTaskId, newTitle, newDescription, currentUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
taskId: newTaskId,
|
||||||
|
message: 'Задача ознакомления успешно создана',
|
||||||
|
timeoutInfo: { nextAllowedIn: 15 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { setupTaskEndpoints, getApproverUsers };
|
module.exports = { setupTaskEndpoints, getApproverUsers };
|
||||||
Reference in New Issue
Block a user