@@ -1617,18 +1648,18 @@ function setupFilters(tabId, tools, categoriesMap, specData) {
render();
}
-function loadFromStorage(tabId) {
+function loadFromStorage(title) {
try {
- return JSON.parse(localStorage.getItem(`toolboxStotage:${tabId}`)) || {};
+ return JSON.parse(localStorage.getItem(`toolboxStotage:${title}`)) || {};
} catch {
return {};
}
}
-function saveToStorage(tabId, storageData) {
+function saveToStorage(title, data) {
localStorage.setItem(
- `toolboxStotage:${tabId}`,
- JSON.stringify(storageData)
+ `toolboxStotage:${title}`,
+ JSON.stringify(data)
);
}
@@ -1948,12 +1979,11 @@ function renderToolboxTab(tabData) {
// Сортируем список складов по названию
tabData.sort((a, b) => a.title.localeCompare(b.title, 'ru'));
- const toolboxNav = `
+ tabOptionalContent.innerHTML = `
`;
+ const toolboxNav = document.getElementById('toolboxNav');
+ toolboxNav.addEventListener('click', async (event) => {
+ const button = event.target.closest('.toolbox-nav-btn');
+ if (!button) return;
+ if (button.dataset.toolboxId) {
+ try {
+ await selectToolbox(button.dataset.toolboxId);
+ } catch (err) {
+ console.error('Ошибка выбора склада:', err);
+ }
+ }
+ });
+
// Создаем контейнер для содержимого склада
- const toolboxContent = `
+ tabContent.innerHTML = `
@@ -1979,12 +2022,10 @@ function renderToolboxTab(tabData) {
`;
- tabOptionalContent.innerHTML = toolboxNav;
-
if (accessData.manage_toolboxes) {
const addToolboxBtn = document.createElement('button');
- addToolboxBtn.className = 'btn btn-outline-success toolbox-nav-btn d-flex align-items-center mb-2';
+ addToolboxBtn.className = 'btn btn-outline-success toolbox-nav-btn';
addToolboxBtn.innerHTML = `
Добавить
@@ -1996,33 +2037,41 @@ function renderToolboxTab(tabData) {
document.getElementById('toolboxNav').appendChild(addToolboxBtn);
}
- tabContent.innerHTML = toolboxContent;
-
const choiceToolbox = loadFromStorage('toolbox');
if (choiceToolbox.toolboxId) {
- window.selectToolbox(choiceToolbox.toolboxId).then(() => { }).catch(() => { });
+ (async () => {
+ await selectToolbox(choiceToolbox.toolboxId);
+ })().catch(err => {
+ console.error('Ошибка выбора склада:', err);
+ });
}
}
-
// Функция для выбора склада
-window.selectToolbox = async function (toolboxId, index) {
- // Убираем активный класс со всех кнопок складов
- document.querySelectorAll('.toolbox-nav-btn').forEach(btn => {
- btn.classList.remove('active');
- });
-
- // Добавляем активный класс выбранной кнопке
- const selectedBtn = document.querySelector(`.toolbox-nav-btn[data-toolbox-id="${toolboxId}"]`);
- if (selectedBtn) {
- selectedBtn.classList.add('active');
+async function selectToolbox(toolboxId) {
+ if (typeof toolboxId === 'string') {
+ try {
+ toolboxId = parseInt(toolboxId);
+ } catch (err) {
+ console.error('Неверный идентификатор склада:', toolboxId);
+ return;
+ }
}
+ document.querySelectorAll('.toolbox-nav-btn')
+ .forEach(btn => btn.classList.remove('active'));
+
+ const selectedBtn = document.querySelector(
+ `.toolbox-nav-btn[data-toolbox-id="${toolboxId}"]`
+ );
+
+ selectedBtn?.classList.add('active');
saveToStorage('toolbox', { toolboxId });
- // Загружаем содержимое склада
+
await loadToolboxContent(toolboxId);
}
async function loadToolboxContent(toolboxId) {
+
const contentContainer = document.querySelector('.toolbox-content-container');
// Показываем индикатор загрузки
@@ -2214,12 +2263,15 @@ async function loadToolboxContent(toolboxId) {
Не удалось загрузить содержимое склада
${error.message}
-
`;
+
+ // Добавляем обработчик для кнопки "Попробовать снова"
+ contentContainer.querySelector('#tryAgainBtn').addEventListener('click', async () => await selectToolbox(toolboxId));
}
}
@@ -3326,7 +3378,6 @@ async function initializeToolboxTable(data, toolboxOwn, quantityMonitoring) {
async function initializePagination() {
const totalPages = Math.ceil(filteredData.length / itemsPerPage);
const paginationContainer = document.getElementById('toolboxPagination');
- const tbody = document.getElementById('toolboxItemsBody');
// Очищаем текущее содержимое
paginationContainer.innerHTML = '';
@@ -3443,6 +3494,7 @@ async function initializeToolboxTable(data, toolboxOwn, quantityMonitoring) {
const itemId = e.currentTarget.dataset.id;
const selectedItem = data.find(d => d.id == itemId);
if (selectedItem) {
+ selectedItem.skipRefresh = true;
await showOperationModal(action, selectedItem);
}
});
@@ -5107,26 +5159,736 @@ async function actionRequest(operation, quantity, comment, selectedItem) {
}
}
-function formatKey(key) {
- const keyMap = {
- 'id': 'ID',
- 'title': 'Название',
- 'description': 'Описание',
- 'owner_id': 'ID владельца',
- 'monitoring': 'Мониторинг',
- 'created_at': 'Дата создания',
- 'updated_at': 'Дата обновления'
+function renderRequestsTab(tabId, tabData) {
+ const tabContent = document.getElementById(`${tabId}-tab-content`);
+ const tabOptionalContent = document.getElementById(`${tabId}-tab-optional-content`);
+
+ const { requests, users, toolboxes, toolkits } = tabData;
+
+ // Собираем списки для фильтров
+ const initUsers = [...new Set(requests.map(r => r.init_user_id))];
+ const userMap = {};
+ users.forEach(user => {
+ userMap[user.id] = user.username;
+ });
+
+ const actionTypes = [...new Set(requests.map(r => r.action))];
+
+ const ownRequestsCount = requests.filter(r => r.init_user_id === userData.id).length;
+
+ // Создаем мапу для toolboxes
+ const toolboxMap = {};
+ toolboxes.forEach(box => {
+ toolboxMap[box.id] = box.title;
+ });
+
+ // Создаем мапу для toolkits
+ const toolkitMap = {};
+ toolkits.forEach(kit => {
+ toolkitMap[kit.id] = kit.title;
+ });
+
+ // Фильтры
+ let currentFilters = {
+ user: 'all',
+ action: 'all',
};
- return keyMap[key] || key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, ' ');
+
+ // Рендерим дополнительный контейнер с фильтрами
+ tabOptionalContent.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${(accessData.refund_request_confirm || accessData.debit_request_confirm) ? `
+
+
+ Все принять
+
+
+ Все отклонить
+
+
+ ` : ''}
+ ${ownRequestsCount > 0 ? `
+
+ Все отозвать
+
+ ` : ''}
+
+
+
+ `;
+
+ // Рендерим основной контейнер с таблицей запросов
+ tabContent.innerHTML = `
+
+
+
+
+
+
+ | Тип |
+ Оформил |
+ Со склада |
+ На склад |
+ Инструмент |
+ Кол-во |
+ Обоснование |
+ Действия |
+
+
+
+
+
+
+
+
+
+
Нет запросов по выбранным фильтрам
+
+
+
+ `;
+
+ // Функция для фильтрации запросов
+ function filterRequests() {
+ let filtered = requests;
+
+ // Фильтр по пользователю
+ if (currentFilters.user !== 'all') {
+ filtered = filtered.filter(r => r.init_user_id == currentFilters.user);
+ }
+
+ // Фильтр по типу действия
+ if (currentFilters.action !== 'all') {
+ filtered = filtered.filter(r => r.action === currentFilters.action);
+ }
+
+ return filtered;
+ }
+
+ // Функция для рендеринга строк таблицы
+ function renderRequestsTable() {
+ const tbody = document.getElementById(`${tabId}-requests-body`);
+ const noRequestsDiv = document.getElementById(`${tabId}-no-requests`);
+ const filteredRequests = filterRequests();
+
+ if (filteredRequests.length === 0) {
+ tbody.innerHTML = '';
+ noRequestsDiv.style.display = 'block';
+ return;
+ }
+
+ noRequestsDiv.style.display = 'none';
+
+ tbody.innerHTML = filteredRequests.map(request => {
+
+ // Определяем доступные действия
+ const actions = [];
+
+ // Кнопка отзыва (только для инициатора и неподтвержденных запросов)
+ if (request.init_user_id === userData.id && request.accepted === null) {
+ actions.push(`
+
+
+
+ `);
+ }
+
+ // Кнопки принятия/отклонения (в зависимости от прав)
+ let canDecide = false;
+
+ // Проверяем права в зависимости от типа запроса
+ if (request.action === 'Возврат' && accessData.refund_request_confirm) {
+ canDecide = true;
+ } else if (request.action === 'Списание' && accessData.debit_request_confirm) {
+ canDecide = true;
+ } else if (request.action !== 'Возврат' && request.action !== 'Списание' &&
+ (accessData.refund_request_confirm || accessData.debit_request_confirm)) {
+ // Для других типов запросов, если есть хотя бы одно из прав
+ console.warning('Unknown request action', request.action);
+ canDecide = true;
+ }
+
+ if (canDecide) {
+ actions.push(`
+
+
+
+
+
+
+
+
+ `);
+ }
+
+ // Если нет доступных действий
+ if (actions.length === 0) {
+ actions.push('
Нет действий');
+ }
+
+ return `
+
+ |
+ ${request.action}
+ ${request.created_at}
+ |
+ ${userMap[request.init_user_id] || `Пользователь ${request.init_user_id}`} |
+ ${request.source_toolbox_id ? (toolboxMap[request.source_toolbox_id] || `Склад ${request.source_toolbox_id}`) : '-'} |
+ ${request.target_toolbox_id ? (toolboxMap[request.target_toolbox_id] || `Склад ${request.target_toolbox_id}`) : '-'} |
+ ${request.toolkit_id ? (toolkitMap[request.toolkit_id] || `Инструмент ${request.toolkit_id}`) : '-'} |
+
+ ${request.quantity}
+ |
+
+ ${request.reason || 'Нет обоснования'}
+ |
+
+
+ ${actions.join('')}
+
+ |
+
+ `;
+ }).join('');
+ }
+
+ // Функция для показа модального окна подтверждения
+ function showConfirmationModal(title, message, onConfirm) {
+ // Проверяем, есть ли уже модальное окно
+ let modal = document.getElementById('confirmation-modal');
+
+ if (!modal) {
+ // Создаем модальное окно
+ modal = document.createElement('div');
+ modal.className = 'modal fade';
+ modal.id = 'confirmation-modal';
+ modal.tabIndex = -1;
+ modal.setAttribute('aria-hidden', 'true');
+ modal.innerHTML = `
+
+ `;
+ document.body.appendChild(modal);
+ }
+
+ // Устанавливаем содержимое
+ document.getElementById('confirmation-message').innerHTML = message;
+
+ // Очищаем предыдущие обработчики
+ const confirmBtn = document.getElementById('confirm-action-btn');
+ const oldConfirmBtn = confirmBtn.cloneNode(true);
+ confirmBtn.parentNode.replaceChild(oldConfirmBtn, confirmBtn);
+
+ const newConfirmBtn = document.getElementById('confirm-action-btn');
+
+ // Устанавливаем новый обработчик
+ newConfirmBtn.addEventListener('click', function () {
+ const modalInstance = bootstrap.Modal.getInstance(modal);
+ modalInstance.hide();
+ onConfirm();
+ });
+
+ // Показываем модальное окно
+ const modalInstance = new bootstrap.Modal(modal);
+ modalInstance.show();
+ }
+
+ async function sendRequestDecision(requestId, accepted, requestResult = null) {
+ const data = await apiRequest('/records/', { request_id: requestId, user_id: userData.id, accepted: accepted });
+ if (data.status == 'ok') {
+ const requestIndex = requests.findIndex(r => r.id == requestId);
+ if (requestIndex !== -1) {
+ requests.splice(requestIndex, 1);
+ }
+ // Перерисовываем таблицу
+ renderRequestsTable();
+
+ // Показываем уведомление об успехе
+ requestResult = requestResult === null ? (accepted ? 'Принят' : 'Отклонен') : requestResult;
+ showInfo(`Запрос успешно ${requestResult}`, 'success');
+ } else {
+ const errorMessage = data.message || 'Ошибка сервера';
+ showInfo(errorMessage, 'error');
+ throw new Error(errorMessage);
+ }
+ }
+
+ // Функция для обработки решения по запросу
+ function handleRequestDecision(requestId, accepted) {
+ const action = accepted ? 'принять' : 'отклонить';
+
+ showConfirmationModal(
+ `Подтверждение действия`,
+ `Вы уверены, что хотите ${action} этот запрос?`,
+ async () => {
+ await sendRequestDecision(requestId, accepted);
+ }
+ );
+ }
+
+ // Функция для отзыва запроса
+ function handleRequestWithdrawal(requestId) {
+ showConfirmationModal(
+ 'Отзыв запроса',
+ 'Вы уверены, что хотите отозвать этот запрос?',
+ async () => {
+ await sendRequestDecision(requestId, false, 'Отозван');
+ }
+ );
+ }
+
+ // Функция для массовых действий
+ function handleBulkAction(actionType) {
+ const filteredRequests = filterRequests();
+
+ // Фильтруем только те запросы, с которыми можно совершить действие
+ let applicableRequests = filteredRequests;
+
+ if (actionType === 'accept' || actionType === 'reject') {
+ // Для принятия/отклонения: только ожидающие решения
+ applicableRequests = filteredRequests.filter(r => r.accepted === null);
+
+ // Проверяем права для каждого запроса
+ applicableRequests = applicableRequests.filter(r => {
+ if (r.action === 'Возврат') {
+ return accessData.refund_request_confirm;
+ } else if (r.action === 'Списание') {
+ return accessData.debit_request_confirm;
+ } else {
+ return accessData.refund_request_confirm || accessData.debit_request_confirm;
+ }
+ });
+ } else if (actionType === 'withdraw') {
+ // Для отзыва: только мои и ожидающие решения
+ applicableRequests = filteredRequests.filter(r =>
+ r.init_user_id === userData.id && r.accepted === null
+ );
+ }
+
+ if (applicableRequests.length === 0) {
+ showInfo('Нет подходящих запросов для этого действия', 'warning');
+ return;
+ }
+
+ const actionName = actionType === 'accept' ? 'принять' :
+ actionType === 'reject' ? 'отклонить' : 'отозвать';
+
+ showConfirmationModal(
+ 'Массовое действие',
+ `Вы уверены, что хотите
${actionName} все отправленные запросы (${applicableRequests.length})?`,
+ async () => {
+ // Отправляем запросы на сервер
+ const promises = applicableRequests.map(request =>
+ sendRequestDecision(request.id, actionType === 'accept')
+ );
+ await Promise.all(promises);
+ }
+ );
+ }
+
+
+ // Назначаем обработчики событий для фильтров
+ document.getElementById(`${tabId}-user-filter`).addEventListener('change', function () {
+ currentFilters.user = this.value;
+ renderRequestsTable();
+ });
+
+ document.getElementById(`${tabId}-action-filter`).addEventListener('change', function () {
+ currentFilters.action = this.value;
+ renderRequestsTable();
+ });
+
+ // Назначаем обработчики для массовых действий
+ const acceptAllBtn = document.getElementById(`${tabId}-accept-all-btn`);
+ const rejectAllBtn = document.getElementById(`${tabId}-reject-all-btn`);
+ const withdrawAllBtn = document.getElementById(`${tabId}-withdraw-all-btn`);
+
+ if (acceptAllBtn) {
+ acceptAllBtn.addEventListener('click', () => handleBulkAction('accept'));
+ }
+
+ if (rejectAllBtn) {
+ rejectAllBtn.addEventListener('click', () => handleBulkAction('reject'));
+ }
+
+ if (withdrawAllBtn) {
+ withdrawAllBtn.addEventListener('click', () => handleBulkAction('withdraw'));
+ }
+
+ // Назначаем делегированные обработчики для действий в таблице
+ document.getElementById(`${tabId}-requests-body`).addEventListener('click', function (e) {
+ const target = e.target;
+
+ // Находим ближайшую кнопку или родительскую кнопку
+ const button = target.closest('.accept-btn, .reject-btn, .withdraw-btn');
+ if (!button) return;
+
+ const requestId = button.dataset.requestId;
+
+ if (button.classList.contains('accept-btn')) {
+ handleRequestDecision(requestId, true);
+ } else if (button.classList.contains('reject-btn')) {
+ handleRequestDecision(requestId, false);
+ } else if (button.classList.contains('withdraw-btn')) {
+ handleRequestWithdrawal(requestId);
+ }
+ });
+
+ // Первоначальный рендеринг таблицы
+ renderRequestsTable();
}
-function formatValue(value) {
- if (value === null || value === undefined) return '—';
- if (typeof value === 'boolean') return value ? 'Да' : 'Нет';
- if (typeof value === 'object') return JSON.stringify(value);
- return value.toString();
-}
+function renderJurnalToolkitsTab(tabId, tabData) {
+ const tabContent = document.getElementById(`${tabId}-tab-content`);
+ const tabOptionalContent = document.getElementById(`${tabId}-tab-optional-content`);
+
+ const { requests, users, toolboxes, toolkits, startDate, endDate } = tabData;
+
+ if (requests.length === 0) {
+ tabContent.innerHTML = `
+
+
+ Нет данных за период ${startDate} - ${endDate}
+
+ `;
+ return;
+ }
+
+ // Собираем списки для фильтров
+ const initUsers = [...new Set(requests.map(r => r.init_user_id))];
+ const userMap = {};
+ users.forEach(user => {
+ userMap[user.id] = user.username;
+ });
+
+ const actionTypes = [...new Set(requests.map(r => r.action))];
+
+ // Создаем мапу для toolboxes
+ const toolboxMap = {};
+ toolboxes.forEach(box => {
+ toolboxMap[box.id] = box.title;
+ });
+
+ // Создаем мапу для toolkits
+ const toolkitMap = {};
+ toolkits.forEach(kit => {
+ toolkitMap[kit.id] = kit.title;
+ });
+
+ const savedFilters = loadFromStorage(tabId);
+ // Фильтры
+ let currentFilters = {
+ user: savedFilters?.user || 'all',
+ action: savedFilters?.action || 'all',
+ status: savedFilters?.status || 'all'
+ };
+
+ // Рендерим дополнительный контейнер с фильтрами
+ tabOptionalContent.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Сброс
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Дата начала:
+
+
+
+
+
+ Дата окончания:
+
+
+
+
+
+ Обновить журнал
+
+
+
+
+
+ `;
+
+ const filterResetBtn = document.getElementById(`${tabId}-filter-reset-btn`);
+ filterResetBtn.addEventListener('click', () => {
+ currentFilters = {
+ user: 'all',
+ action: 'all',
+ status: 'all'
+ };
+ document.getElementById(`${tabId}-user-filter`).value = currentFilters.user;
+ document.getElementById(`${tabId}-action-filter`).value = currentFilters.action;
+ document.getElementById(`${tabId}-status-filter`).value = currentFilters.status;
+ saveToStorage(tabId, currentFilters);
+ renderRequestsTable();
+ });
+
+ const startDateInput = document.getElementById(`${tabId}-date-from`);
+ const endDateInput = document.getElementById(`${tabId}-date-to`);
+ startDateInput.value = startDate;
+ endDateInput.value = endDate;
+
+ const refreshDateBtn = document.getElementById(`${tabId}-date-update-btn`);
+ refreshDateBtn.addEventListener('click', async () => {
+ const newStartDate = startDateInput.value;
+ const newEndDate = endDateInput.value;
+ const newDateRequestData = {
+ tabId: tabId,
+ startDate: newStartDate,
+ endDate: newEndDate
+ };
+ if (newStartDate && newEndDate) {
+ tabContent.innerHTML = `
+
+
+ Загрузка данных...
+
+ `;
+ const newPeriodData = await apiRequest('/', newDateRequestData);
+ if (newPeriodData.status == 'ok') {
+ renderJurnalToolkitsTab(tabId, newPeriodData.data);
+ }
+ }
+ });
+
+ // Рендерим основной контейнер с таблицей запросов
+ tabContent.innerHTML = `
+
+
+
+
+
+
+ | Тип |
+ Оформил |
+ Решил |
+ Со склада |
+ На склад |
+ Инструмент |
+ Кол-во |
+ Обоснование |
+
+
+
+
+
+
+
+
+
+
Нет запросов по выбранным фильтрам
+
+
+
+ `;
+
+ // Функция для фильтрации запросов
+ function filterRequests() {
+ let filtered = requests;
+
+ // Фильтр по пользователю
+ if (currentFilters.user !== 'all') {
+ filtered = filtered.filter(r => r.init_user_id == currentFilters.user);
+ document.getElementById(`${tabId}-user-filter`).value = currentFilters.user;
+ }
+
+ // Фильтр по типу действия
+ if (currentFilters.action !== 'all') {
+ filtered = filtered.filter(r => r.action === currentFilters.action);
+ document.getElementById(`${tabId}-action-filter`).value = currentFilters.action;
+ }
+
+ // Фильтр по статусу
+ if (currentFilters.status !== 'all') {
+ document.getElementById(`${tabId}-status-filter`).value = currentFilters.status;
+ switch (currentFilters.status) {
+ case 'accepted':
+ filtered = filtered.filter(r => r.accepted === true);
+ break;
+ case 'rejected':
+ filtered = filtered.filter(r => r.accepted === false);
+ break;
+ }
+ }
+
+ return filtered;
+ }
+
+ // Функция для рендеринга строк таблицы
+ function renderRequestsTable() {
+ const tbody = document.getElementById(`${tabId}-requests-body`);
+ const noRequestsDiv = document.getElementById(`${tabId}-no-requests`);
+ const filteredRequests = filterRequests();
+
+ if (filteredRequests.length === 0) {
+ tbody.innerHTML = '';
+ noRequestsDiv.style.display = 'block';
+ return;
+ }
+
+ noRequestsDiv.style.display = 'none';
+
+ tbody.innerHTML = filteredRequests.map(request => {
+ // Определяем статус запроса
+ let statusBadge = '';
+ if (request.accepted === true) {
+ statusBadge = '
Принято';
+ } else {
+ statusBadge = '
Отклонено';
+ }
+
+ return `
+
+ |
+ ${request.action}
+ ${statusBadge}
+ |
+ ${userMap[request.init_user_id] || `Пользователь ${request.init_user_id}`} ${request.created_at} |
+ ${userMap[request.decision_user_id] || `Пользователь ${request.decision_user_id}`} ${request.decided_at} |
+ ${request.source_toolbox_id ? (toolboxMap[request.source_toolbox_id] || `Склад ${request.source_toolbox_id}`) : '-'} |
+ ${request.target_toolbox_id ? (toolboxMap[request.target_toolbox_id] || `Склад ${request.target_toolbox_id}`) : '-'} |
+ ${request.toolkit_id ? (toolkitMap[request.toolkit_id] || `Инструмент ${request.toolkit_id}`) : '-'} |
+
+ ${request.quantity}
+ |
+
+ ${request.reason || 'Нет обоснования'}
+ |
+
+ `;
+ }).join('');
+ }
+
+ // Назначаем обработчики событий для фильтров
+ document.getElementById(`${tabId}-user-filter`).addEventListener('change', function () {
+ currentFilters.user = this.value;
+ saveToStorage(tabId, currentFilters);
+ renderRequestsTable();
+ });
+
+ document.getElementById(`${tabId}-action-filter`).addEventListener('change', function () {
+ currentFilters.action = this.value;
+ saveToStorage(tabId, currentFilters);
+ renderRequestsTable();
+ });
+
+ document.getElementById(`${tabId}-status-filter`).addEventListener('change', function () {
+ currentFilters.status = this.value;
+ saveToStorage(tabId, currentFilters);
+ renderRequestsTable();
+ });
+
+ // Первоначальный рендеринг таблицы
+ renderRequestsTable();
+}
document.addEventListener('DOMContentLoaded', async () => {
await getCookieData();
diff --git a/api/static/js/user.js b/api/static/js/user.js
index ebb923a..44a5832 100644
--- a/api/static/js/user.js
+++ b/api/static/js/user.js
@@ -120,6 +120,9 @@ class ClientManager {
// Очищаем cookie пользователя
this.clearUserCookie();
+ // Очищаем локальное хранилище
+ localStorage.clear();
+
// Переход на страницу выхода
setTimeout(() => {
window.location.href = '/user/login';
diff --git a/db/handlers/__pycache__/access.cpython-313.pyc b/db/handlers/__pycache__/access.cpython-313.pyc
index b70ed85..96388f2 100644
Binary files a/db/handlers/__pycache__/access.cpython-313.pyc and b/db/handlers/__pycache__/access.cpython-313.pyc differ
diff --git a/db/handlers/__pycache__/actions.cpython-313.pyc b/db/handlers/__pycache__/actions.cpython-313.pyc
index bda3e2d..862d90f 100644
Binary files a/db/handlers/__pycache__/actions.cpython-313.pyc and b/db/handlers/__pycache__/actions.cpython-313.pyc differ
diff --git a/db/handlers/__pycache__/records.cpython-313.pyc b/db/handlers/__pycache__/records.cpython-313.pyc
index ded9ab5..dd10b1e 100644
Binary files a/db/handlers/__pycache__/records.cpython-313.pyc and b/db/handlers/__pycache__/records.cpython-313.pyc differ
diff --git a/db/handlers/access.py b/db/handlers/access.py
index 037354e..da16f38 100644
--- a/db/handlers/access.py
+++ b/db/handlers/access.py
@@ -90,21 +90,16 @@ class AccessLevelHandler:
"admin": {
"title": "Администратор",
"description": "Администратор. Полный доступ",
- "receiving_edit": True,
- "refund_request_edit": True,
"refund_request_confirm": True,
- "debit_request_edit": True,
"debit_request_confirm": True,
"tools_creation": True,
"tools_registration": True,
- "tools_registration_edit": True,
"tools_edit": True,
"tools_delete": True,
"users_creation": True,
"users_edit": True,
"users_disabling": True,
"users_view": True,
- "available_own_toolbox": False,
"view_all_toolboxes": True,
"view_requests": True,
"view_services": True,
diff --git a/db/handlers/actions.py b/db/handlers/actions.py
index cb62598..ebc128e 100644
--- a/db/handlers/actions.py
+++ b/db/handlers/actions.py
@@ -270,7 +270,7 @@ class StocksActions:
if not accept:
return accept
- totalRecordsIds = [record_id]
+ totalRecordsIds = [record_id] if not record else [record.id]
if len(stocksMovements) > 1:
for stock in stocksMovements[1:]:
@@ -298,7 +298,7 @@ class StocksActions:
)
if not accept:
return False
- totalRecordsIds.append(recorded)
+ totalRecordsIds.append(recorded.id)
logger.info(
f"Записи {', '.join(map(str, totalRecordsIds))} о {movingRecord.action} инструмента успешно приняты {user_id}"
diff --git a/db/handlers/records.py b/db/handlers/records.py
index 601b6d3..a4129e4 100644
--- a/db/handlers/records.py
+++ b/db/handlers/records.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timedelta
+from datetime import date, datetime, time, timedelta
from sqlalchemy import select
@@ -142,39 +142,69 @@ class StocksRecordsHandler:
logger.error(f"Ошибка обновления записи: {str(e)}")
return False
- async def get(user_id: int = None, days: int = 30):
+ async def getLogs(startDate: date, endDate: date):
from db import CRUD
try:
- if user_id:
- userInfo = f"пользователя {user_id} "
- decided = "не решенных "
- daysLimit = ""
- query = select(StocksRecords).where(
- StocksRecords.init_user_id == user_id,
- StocksRecords.decision_user_id == None,
+ start_dt = datetime.combine(startDate, time.min)
+ end_dt = datetime.combine(endDate, time.max)
+
+ query = (
+ select(StocksRecords)
+ .where(
+ StocksRecords.created_at.between(start_dt, end_dt),
+ StocksRecords.decision_user_id != None,
+ )
+ .order_by(StocksRecords.created_at.desc())
+ )
+
+ logger.debug("Получение записей за период %s - %s", startDate, endDate)
+
+ records = await CRUD.read(query, True)
+
+ logger.debug(
+ "%d записей за период %s - %s успешно получены",
+ len(records),
+ startDate,
+ endDate,
+ )
+
+ return [record.toDict() for record in records]
+
+ except Exception:
+ logger.exception("Ошибка получения записей")
+ return []
+
+ async def get(user_id: int, manager: bool):
+ from db import CRUD
+
+ try:
+ if manager:
+ query = (
+ select(StocksRecords)
+ .where(
+ StocksRecords.decision_user_id == None,
+ )
+ .order_by(StocksRecords.created_at.asc())
)
else:
- userInfo = "всех пользователей "
- decided = ""
- daysLimit = f"за последние {days} дн."
- query = select(StocksRecords).where(
- StocksRecords.created_at > datetime.now() - timedelta(days=days),
+ query = (
+ select(StocksRecords)
+ .where(
+ StocksRecords.init_user_id == user_id,
+ StocksRecords.decision_user_id == None,
+ )
+ .order_by(StocksRecords.created_at.asc())
)
- logger.debug(f"Получение всех {decided}записей {userInfo}{daysLimit}")
+ logger.debug(f"Получение всех записей без решения")
records = await CRUD.read(query, True)
- logger.debug(
- f"{len(records)} {decided}записей {userInfo}{daysLimit} успешно получены"
- )
- if len(records) == 0:
- return []
- records.sort(key=lambda x: x.created_at, reverse=True)
+ logger.debug(f"{len(records)} записей без решения успешно получены")
recordsData = [record.toDict() for record in records]
logger.debug(recordsData)
return recordsData
except Exception as e:
logger.error(f"Ошибка получения записей: {str(e)}")
- return False
+ return []
async def getById(record_id: int, record: bool = False):
from db import CRUD
diff --git a/db/schemas/__pycache__/access.cpython-313.pyc b/db/schemas/__pycache__/access.cpython-313.pyc
index 6b44e0c..9d5344d 100644
Binary files a/db/schemas/__pycache__/access.cpython-313.pyc and b/db/schemas/__pycache__/access.cpython-313.pyc differ
diff --git a/db/schemas/access.py b/db/schemas/access.py
index a281ef6..76fe808 100644
--- a/db/schemas/access.py
+++ b/db/schemas/access.py
@@ -14,14 +14,10 @@ class AccessLevel(Base):
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
# permissions
- receiving_edit = Column(Boolean, default=False)
- refund_request_edit = Column(Boolean, default=False)
refund_request_confirm = Column(Boolean, default=False)
- debit_request_edit = Column(Boolean, default=False)
debit_request_confirm = Column(Boolean, default=False)
tools_creation = Column(Boolean, default=False)
tools_registration = Column(Boolean, default=False)
- tools_registration_edit = Column(Boolean, default=False)
tools_edit = Column(Boolean, default=False)
tools_delete = Column(Boolean, default=False)
users_creation = Column(Boolean, default=False)