Files
minicrm/public/documents.js
2026-01-27 15:12:27 +05:00

495 lines
20 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.
// documents.js - Работа с документами для согласования
let documentTypes = [];
async function loadDocumentTypes() {
try {
const response = await fetch('/api/document-types');
documentTypes = await response.json();
populateDocumentTypeSelect();
} catch (error) {
console.error('Ошибка загрузки типов документов:', error);
}
}
function populateDocumentTypeSelect() {
const select = document.getElementById('document-type');
if (!select) return;
select.innerHTML = '<option value="">Выберите тип документа...</option>';
documentTypes.forEach(type => {
const option = document.createElement('option');
option.value = type.id;
option.textContent = type.name;
select.appendChild(option);
});
}
function initializeDocumentForm() {
const form = document.getElementById('create-document-form');
if (form) {
form.addEventListener('submit', createDocumentTask);
}
// Инициализация даты по умолчанию
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
const dateInput = document.getElementById('document-date');
if (dateInput) {
dateInput.value = todayStr;
}
loadDocumentTypes();
}
async function createDocumentTask(event) {
event.preventDefault();
if (!currentUser) {
alert('Требуется аутентификация');
return;
}
const formData = new FormData();
// Основные данные задачи
formData.append('title', document.getElementById('document-title').value);
formData.append('description', document.getElementById('document-description').value);
// Даты
const dueDateInput = document.getElementById('due-date');
if (dueDateInput.value) {
formData.append('dueDate', dueDateInput.value);
}
// Данные документа
formData.append('documentTypeId', document.getElementById('document-type').value);
formData.append('documentNumber', document.getElementById('document-number').value);
formData.append('documentDate', document.getElementById('document-date').value);
formData.append('pagesCount', document.getElementById('pages-count').value);
formData.append('urgencyLevel', document.getElementById('urgency-level').value);
formData.append('comment', document.getElementById('document-comment').value);
// Загружаем файлы
const filesInput = document.getElementById('document-files');
if (filesInput.files) {
for (let i = 0; i < filesInput.files.length; i++) {
formData.append('files', filesInput.files[i]);
}
}
try {
const response = await fetch('/api/documents', {
method: 'POST',
body: formData
});
if (response.ok) {
alert('Задача на согласование документа создана!');
document.getElementById('create-document-form').reset();
document.getElementById('document-file-list').innerHTML = '';
// Сброс даты
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
const dateInput = document.getElementById('document-date');
if (dateInput) {
dateInput.value = todayStr;
}
// Перенаправление на список документов
showDocumentSection('my-documents');
} else {
const error = await response.json();
alert(error.error || 'Ошибка создания задачи');
}
} catch (error) {
console.error('Ошибка:', error);
alert('Ошибка создания задачи');
}
}
function updateDocumentFileList() {
const fileInput = document.getElementById('document-files');
const fileList = document.getElementById('document-file-list');
const files = fileInput.files;
if (files.length === 0) {
fileList.innerHTML = '';
return;
}
let html = '<ul>';
let totalSize = 0;
for (let i = 0; i < files.length; i++) {
const file = files[i];
totalSize += file.size;
html += `<li>${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)</li>`;
}
html += '</ul>';
html += `<p><strong>Общий размер: ${(totalSize / 1024 / 1024).toFixed(2)} MB / 300 MB</strong></p>`;
fileList.innerHTML = html;
}
// Функции для работы с документами
async function loadMyDocuments() {
try {
const response = await fetch('/api/documents/my');
const documents = await response.json();
renderMyDocuments(documents);
} catch (error) {
console.error('Ошибка загрузки документов:', error);
}
}
async function loadSecretaryDocuments() {
try {
const response = await fetch('/api/documents/secretary');
const documents = await response.json();
renderSecretaryDocuments(documents);
} catch (error) {
console.error('Ошибка загрузки документов секретаря:', error);
}
}
function renderMyDocuments(documents) {
const container = document.getElementById('my-documents-list');
if (!container) return;
if (documents.length === 0) {
container.innerHTML = '<div class="empty-state">У вас нет документов на согласование</div>';
return;
}
container.innerHTML = documents.map(doc => `
<div class="document-card" data-document-id="${doc.id}">
<div class="document-header">
<div class="document-title">
<span class="document-number">Документ №${doc.document_number || doc.id}</span>
<strong>${doc.title}</strong>
<span class="document-status ${getDocumentStatusClass(doc.status)}">${getDocumentStatusText(doc.status)}</span>
${doc.urgency_level === 'urgent' ? '<span class="urgency-badge urgent">Срочно</span>' : ''}
${doc.urgency_level === 'very_urgent' ? '<span class="urgency-badge very-urgent">Очень срочно</span>' : ''}
</div>
<div class="document-meta">
<small>Создан: ${formatDateTime(doc.created_at)}</small>
${doc.due_date ? `<small>Срок: ${formatDateTime(doc.due_date)}</small>` : ''}
</div>
</div>
<div class="document-details">
<div class="document-info">
<p><strong>Тип:</strong> ${doc.document_type_name || 'Не указан'}</p>
<p><strong>Дата документа:</strong> ${doc.document_date ? formatDate(doc.document_date) : 'Не указана'}</p>
<p><strong>Количество страниц:</strong> ${doc.pages_count || 'Не указано'}</p>
${doc.comment ? `<p><strong>Комментарий:</strong> ${doc.comment}</p>` : ''}
</div>
<div class="document-files">
${doc.files && doc.files.length > 0 ? `
<strong>Файлы:</strong>
<div class="file-icons-container">
${doc.files.map(file => renderFileIcon(file)).join('')}
</div>
` : '<strong>Файлы:</strong> <span class="no-files">нет файлов</span>'}
</div>
${doc.refusal_reason ? `
<div class="refusal-reason">
<strong>Причина отказа:</strong> ${doc.refusal_reason}
</div>
` : ''}
<div class="document-actions">
${doc.status === 'assigned' || doc.status === 'in_progress' ? `
<button onclick="cancelDocument(${doc.id})" class="btn-warning">Отозвать</button>
` : ''}
${doc.status === 'refused' ? `
<button onclick="reworkDocument(${doc.id})" class="btn-primary">Исправить и отправить повторно</button>
` : ''}
${doc.status === 'approved' || doc.status === 'received' || doc.status === 'signed' ? `
<button onclick="downloadDocumentPackage(${doc.id})" class="btn-primary">Скачать пакет документов</button>
` : ''}
</div>
</div>
</div>
`).join('');
}
function renderSecretaryDocuments(documents) {
const container = document.getElementById('secretary-documents-list');
if (!container) return;
if (documents.length === 0) {
container.innerHTML = '<div class="empty-state">Нет документов для согласования</div>';
return;
}
container.innerHTML = documents.map(doc => `
<div class="document-card" data-document-id="${doc.id}">
<div class="document-header">
<div class="document-title">
<span class="document-number">Документ №${doc.document_number || doc.id}</span>
<strong>${doc.title}</strong>
<span class="document-status ${getDocumentStatusClass(doc.status)}">${getDocumentStatusText(doc.status)}</span>
${doc.urgency_level === 'urgent' ? '<span class="urgency-badge urgent">Срочно</span>' : ''}
${doc.urgency_level === 'very_urgent' ? '<span class="urgency-badge very-urgent">Очень срочно</span>' : ''}
</div>
<div class="document-meta">
<small>От: ${doc.creator_name}</small>
<small>Создан: ${formatDateTime(doc.created_at)}</small>
${doc.due_date ? `<small>Срок: ${formatDateTime(doc.due_date)}</small>` : ''}
</div>
</div>
<div class="document-details">
<div class="document-info">
<p><strong>Тип:</strong> ${doc.document_type_name || 'Не указан'}</p>
<p><strong>Номер:</strong> ${doc.document_number || 'Не указан'}</p>
<p><strong>Дата документа:</strong> ${doc.document_date ? formatDate(doc.document_date) : 'Не указана'}</p>
<p><strong>Количество страниц:</strong> ${doc.pages_count || 'Не указано'}</p>
${doc.comment ? `<p><strong>Комментарий автора:</strong> ${doc.comment}</p>` : ''}
</div>
<div class="document-files">
${doc.files && doc.files.length > 0 ? `
<strong>Файлы:</strong>
<div class="file-icons-container">
${doc.files.map(file => renderFileIcon(file)).join('')}
</div>
` : '<strong>Файлы:</strong> <span class="no-files">нет файлов</span>'}
</div>
<div class="secretary-actions" id="secretary-actions-${doc.id}">
${doc.status === 'assigned' ? `
<button onclick="updateDocumentStatus(${doc.id}, 'in_progress')" class="btn-primary">Взять в работу</button>
` : ''}
${doc.status === 'in_progress' ? `
<div class="status-buttons">
<button onclick="showApproveModal(${doc.id})" class="btn-success">Согласовать</button>
<button onclick="showReceiveModal(${doc.id})" class="btn-primary">Получен (оригинал)</button>
<button onclick="showRefuseModal(${doc.id})" class="btn-warning">Отказать</button>
</div>
` : ''}
${doc.status === 'approved' ? `
<div class="status-buttons">
<button onclick="showReceiveModal(${doc.id})" class="btn-primary">Получен (оригинал)</button>
<button onclick="updateDocumentStatus(${doc.id}, 'refused')" class="btn-warning">Отказать</button>
</div>
` : ''}
${doc.status === 'received' ? `
<div class="status-buttons">
<button onclick="updateDocumentStatus(${doc.id}, 'signed')" class="btn-success">Подписан</button>
<button onclick="updateDocumentStatus(${doc.id}, 'refused')" class="btn-warning">Отказать</button>
</div>
` : ''}
${doc.status === 'refused' ? `
<p class="refusal-info"><strong>Причина отказа:</strong> ${doc.refusal_reason}</p>
` : ''}
</div>
</div>
</div>
`).join('');
}
function getDocumentStatusClass(status) {
switch(status) {
case 'assigned': return 'status-assigned';
case 'in_progress': return 'status-in-progress';
case 'approved': return 'status-approved';
case 'received': return 'status-received';
case 'signed': return 'status-signed';
case 'refused': return 'status-refused';
case 'cancelled': return 'status-cancelled';
default: return 'status-assigned';
}
}
function getDocumentStatusText(status) {
switch(status) {
case 'assigned': return 'Назначена';
case 'in_progress': return 'В работе';
case 'approved': return 'Согласован';
case 'received': return 'Получен';
case 'signed': return 'Подписан';
case 'refused': return 'Отказано';
case 'cancelled': return 'Отозвано';
default: return status;
}
}
function formatDate(dateString) {
if (!dateString) return '';
return new Date(dateString).toLocaleDateString('ru-RU');
}
// Модальные окна для секретаря
function showApproveModal(documentId) {
currentDocumentId = documentId;
document.getElementById('approve-document-modal').style.display = 'block';
}
function closeApproveModal() {
document.getElementById('approve-document-modal').style.display = 'none';
document.getElementById('approve-comment').value = '';
}
function showReceiveModal(documentId) {
currentDocumentId = documentId;
document.getElementById('receive-document-modal').style.display = 'block';
}
function closeReceiveModal() {
document.getElementById('receive-document-modal').style.display = 'none';
document.getElementById('receive-comment').value = '';
}
function showRefuseModal(documentId) {
currentDocumentId = documentId;
document.getElementById('refuse-document-modal').style.display = 'block';
}
function closeRefuseModal() {
document.getElementById('refuse-document-modal').style.display = 'none';
document.getElementById('refuse-reason').value = '';
}
let currentDocumentId = null;
// Функции для работы с API
async function updateDocumentStatus(documentId, status, comment = '', refusalReason = '') {
try {
const response = await fetch(`/api/documents/${documentId}/status`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
status: status,
comment: comment,
refusalReason: refusalReason
})
});
if (response.ok) {
alert('Статус документа обновлен!');
// Закрываем модальные окна
closeApproveModal();
closeReceiveModal();
closeRefuseModal();
// Обновляем список документов
if (isSecretary()) {
loadSecretaryDocuments();
}
loadMyDocuments();
} else {
const error = await response.json();
alert(error.error || 'Ошибка обновления статуса');
}
} catch (error) {
console.error('Ошибка:', error);
alert('Ошибка обновления статуса');
}
}
async function cancelDocument(documentId) {
if (!confirm('Вы уверены, что хотите отозвать документ?')) {
return;
}
try {
const response = await fetch(`/api/documents/${documentId}/cancel`, {
method: 'POST'
});
if (response.ok) {
alert('Документ отозван!');
loadMyDocuments();
} else {
const error = await response.json();
alert(error.error || 'Ошибка отзыва документа');
}
} catch (error) {
console.error('Ошибка:', error);
alert('Ошибка отзыва документа');
}
}
async function reworkDocument(documentId) {
// Здесь можно открыть форму для повторной отправки
alert('Функция исправления и повторной отправки будет реализована в следующей версии');
}
async function downloadDocumentPackage(documentId) {
try {
const response = await fetch(`/api/documents/${documentId}/package`);
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `document_${documentId}_package.zip`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} else {
const error = await response.json();
alert(error.error || 'Ошибка скачивания пакета документов');
}
} catch (error) {
console.error('Ошибка:', error);
alert('Ошибка скачивания пакета документов');
}
}
function isSecretary() {
return currentUser && currentUser.groups && currentUser.groups.includes('Секретарь');
}
function showDocumentSection(sectionName) {
// Скрываем все секции
document.querySelectorAll('.document-section').forEach(section => {
section.style.display = 'none';
});
// Показываем выбранную секцию
const targetSection = document.getElementById(`${sectionName}-section`);
if (targetSection) {
targetSection.style.display = 'block';
}
// Загружаем данные для секции
if (sectionName === 'my-documents') {
loadMyDocuments();
} else if (sectionName === 'secretary-documents' && isSecretary()) {
loadSecretaryDocuments();
}
}
// Инициализация при загрузке страницы
document.addEventListener('DOMContentLoaded', function() {
if (window.location.pathname === '/doc') {
initializeDocumentForm();
// Показываем соответствующие секции
if (isSecretary()) {
document.getElementById('secretary-tab').style.display = 'block';
}
// По умолчанию показываем создание документа
showDocumentSection('create-document');
}
});