release
This commit is contained in:
Binary file not shown.
@@ -18,6 +18,8 @@ def handleResult(result: dict, response: dict) -> dict:
|
|||||||
response["message"] = result["errorMessage"]
|
response["message"] = result["errorMessage"]
|
||||||
else:
|
else:
|
||||||
response["status"] = "ok"
|
response["status"] = "ok"
|
||||||
|
if "data" in result.keys():
|
||||||
|
response["data"] = result["data"]
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@@ -251,3 +253,40 @@ async def quick_action(reqData: dict = Depends(requestDict)):
|
|||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/all", summary="Получение инструментов")
|
||||||
|
async def get_toolkits(reqData: dict = Depends(requestDict)):
|
||||||
|
logger.info(f"Получение инструментов")
|
||||||
|
return {"status": "ok", "data": await ToolkitHandler.getAll()}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/compatibility", summary="Получение совместимости инструментов")
|
||||||
|
async def get_compatibility(reqData: dict = Depends(requestDict)):
|
||||||
|
response = {"status": "error"}
|
||||||
|
toolkitId = reqData.get("query").get("toolkitId")
|
||||||
|
toolkitId = int("".join(filter(str.isdigit, toolkitId)))
|
||||||
|
logger.info(f"Получение совместимости инструмента {toolkitId}")
|
||||||
|
toolkit = await ToolkitHandler.getCompatibility(toolkitId)
|
||||||
|
response = handleResult(toolkit, response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/compatibility", summary="Управление совместимостью инструментов")
|
||||||
|
async def compatibility(reqData: dict = Depends(requestDict)):
|
||||||
|
logger.info(f"Управление совместимостью инструментов")
|
||||||
|
response = {"status": "error"}
|
||||||
|
action = reqData.get("body").get("action")
|
||||||
|
userId = reqData.get("body").get("userId")
|
||||||
|
data = reqData.get("body").get("data")
|
||||||
|
match action:
|
||||||
|
case "add":
|
||||||
|
toolkit = await ToolkitHandler.addCompatibility(userId, data)
|
||||||
|
response = handleResult(toolkit, response)
|
||||||
|
case "delete":
|
||||||
|
toolkit = await ToolkitHandler.deleteCompatibility(userId, data)
|
||||||
|
response = handleResult(toolkit, response)
|
||||||
|
case _:
|
||||||
|
logger.error(f"Unknown action: {action}")
|
||||||
|
pass
|
||||||
|
return response
|
||||||
|
|||||||
@@ -460,3 +460,7 @@ tr:hover .action-buttons {
|
|||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-content-green {
|
||||||
|
background-color: #f4fbf6; /* светло-зелёный */
|
||||||
|
}
|
||||||
+396
-16
@@ -1,6 +1,6 @@
|
|||||||
import { getCookie } from '/static/js/cookies.js';
|
import { getCookie } from '/static/js/cookies.js';
|
||||||
import { apiRequest } from '/static/js/api.js';
|
import { apiRequest } from '/static/js/api.js';
|
||||||
import { showInfo } from '/static/js//toast.js';
|
import { showInfo } from '/static/js/toast.js';
|
||||||
|
|
||||||
let accessData;
|
let accessData;
|
||||||
let userData;
|
let userData;
|
||||||
@@ -3836,14 +3836,16 @@ async function getToolkitStocks(toolkitId) {
|
|||||||
return resp.data;
|
return resp.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Функция показа модального окна с деталями инструмента
|
|
||||||
// Функция показа модального окна с деталями инструмента
|
// Функция показа модального окна с деталями инструмента
|
||||||
async function showToolkitDetailsModal(toolkitId) {
|
async function showToolkitDetailsModal(toolkitId) {
|
||||||
const modalId = 'toolkitDetailsModal';
|
const modalId = 'toolkitDetailsModal';
|
||||||
let modal = document.getElementById(modalId);
|
let modal = document.getElementById(modalId);
|
||||||
|
|
||||||
if (modal) {
|
if (modal) {
|
||||||
modal.hide();
|
const modalInstance = bootstrap.Modal.getInstance(modal);
|
||||||
|
if (modalInstance) {
|
||||||
|
modalInstance.hide();
|
||||||
|
}
|
||||||
modal.remove();
|
modal.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3930,9 +3932,12 @@ async function showToolkitDetailsModal(toolkitId) {
|
|||||||
` : '<div class="col-md-4"></div>';
|
` : '<div class="col-md-4"></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Переменная для хранения данных об остатках (будет загружена при раскрытии аккордеона)
|
// Переменные для хранения данных
|
||||||
let toolkitStocksData = null;
|
let toolkitStocksData = null;
|
||||||
let isStocksLoading = false;
|
let isStocksLoading = false;
|
||||||
|
let compatibilityData = null;
|
||||||
|
let isCompatibilityLoading = false;
|
||||||
|
let allToolkitsData = null;
|
||||||
|
|
||||||
// Форматирование даты комментария
|
// Форматирование даты комментария
|
||||||
let commentDateInfo = '';
|
let commentDateInfo = '';
|
||||||
@@ -4028,6 +4033,8 @@ async function showToolkitDetailsModal(toolkitId) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
${toolkitData.external_link ? `
|
${toolkitData.external_link ? `
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<a href="${toolkitData.external_link}" target="_blank" class="btn btn-sm btn-outline-primary">
|
<a href="${toolkitData.external_link}" target="_blank" class="btn btn-sm btn-outline-primary">
|
||||||
@@ -4038,6 +4045,45 @@ async function showToolkitDetailsModal(toolkitId) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Аккордеон для совместимости инструментов -->
|
||||||
|
<div class="accordion mt-3" id="compatibilityAccordion">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="compatibilityHeading">
|
||||||
|
<button class="accordion-button collapsed" type="button"
|
||||||
|
data-bs-toggle="collapse" data-bs-target="#compatibilityCollapse"
|
||||||
|
aria-expanded="false" aria-controls="compatibilityCollapse">
|
||||||
|
<i class="bi bi-link-45deg me-2"></i>Совместимые инструменты
|
||||||
|
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="compatibilityCollapse" class="accordion-collapse collapse"
|
||||||
|
aria-labelledby="compatibilityHeading" data-bs-parent="#compatibilityAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div id="compatibilityLoading" class="text-center py-3">
|
||||||
|
<div class="spinner-border spinner-border-sm text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Загрузка...</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-muted">Загрузка данных о совместимости...</p>
|
||||||
|
</div>
|
||||||
|
<div id="compatibilityContent" class="d-none">
|
||||||
|
<!-- Содержимое будет загружено динамически -->
|
||||||
|
</div>
|
||||||
|
<div id="compatibilityError" class="d-none text-center text-danger py-3 mb-3">
|
||||||
|
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||||
|
<span>Не удалось загрузить данные о совместимости</span>
|
||||||
|
</div>
|
||||||
|
${accessData.tools_edit ? `
|
||||||
|
<button class="btn btn-sm btn-outline-success ms-auto me-2"
|
||||||
|
id="addCompatibilityBtn"
|
||||||
|
style="display: none;">
|
||||||
|
<i class="bi bi-plus-circle me-1"></i>Добавить совместимый инструмент
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Секция комментариев -->
|
<!-- Секция комментариев -->
|
||||||
<div class="border-top pt-3 mt-3">
|
<div class="border-top pt-3 mt-3">
|
||||||
<h6><i class="bi bi-chat-left-text me-1"></i>Комментарий</h6>
|
<h6><i class="bi bi-chat-left-text me-1"></i>Комментарий</h6>
|
||||||
@@ -4140,8 +4186,6 @@ async function showToolkitDetailsModal(toolkitId) {
|
|||||||
|
|
||||||
// Показываем результат
|
// Показываем результат
|
||||||
if (response.status === 'ok') {
|
if (response.status === 'ok') {
|
||||||
// Успешное отображение
|
|
||||||
|
|
||||||
// Обновляем список инструментов
|
// Обновляем список инструментов
|
||||||
if (typeof uploadTab === 'function') {
|
if (typeof uploadTab === 'function') {
|
||||||
await uploadTab('toolkits');
|
await uploadTab('toolkits');
|
||||||
@@ -4370,26 +4414,362 @@ async function showToolkitDetailsModal(toolkitId) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Обработчик события раскрытия аккордеона
|
// Функция для загрузки данных о совместимости
|
||||||
|
const loadCompatibilityData = async () => {
|
||||||
|
if (isCompatibilityLoading) return;
|
||||||
|
|
||||||
|
const compatibilityLoading = modal.querySelector('#compatibilityLoading');
|
||||||
|
const compatibilityContent = modal.querySelector('#compatibilityContent');
|
||||||
|
const compatibilityError = modal.querySelector('#compatibilityError');
|
||||||
|
const addCompatibilityBtn = modal.querySelector('#addCompatibilityBtn');
|
||||||
|
|
||||||
|
try {
|
||||||
|
isCompatibilityLoading = true;
|
||||||
|
|
||||||
|
// Показываем спиннер, скрываем контент и ошибку
|
||||||
|
compatibilityLoading.classList.remove('d-none');
|
||||||
|
compatibilityContent.classList.add('d-none');
|
||||||
|
compatibilityError.classList.add('d-none');
|
||||||
|
|
||||||
|
// Загружаем данные о совместимости
|
||||||
|
const response = await apiRequest(`/toolkit/compatibility?toolkitId=${toolkitData.id}`, {}, 'GET');
|
||||||
|
|
||||||
|
if (response.status === 'ok') {
|
||||||
|
compatibilityData = response.data;
|
||||||
|
|
||||||
|
// Формируем HTML для совместимости
|
||||||
|
let compatibilityHtml = '';
|
||||||
|
|
||||||
|
if (Object.keys(compatibilityData.records || {}).length > 0) {
|
||||||
|
compatibilityHtml = `
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Инструмент</th>
|
||||||
|
<th>Категория</th>
|
||||||
|
<th>Комментарий</th>
|
||||||
|
${accessData.tools_edit ? '<th width="50">Действия</th>' : ''}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${Object.entries(compatibilityData.records || {}).map(([recordId, compatibleToolkitId]) => {
|
||||||
|
const compatibleToolkit = compatibilityData.toolkits[compatibleToolkitId];
|
||||||
|
const compatibleCategory = categories[compatibleToolkit?.category_id];
|
||||||
|
|
||||||
|
return `
|
||||||
|
<tr data-record-id="${recordId}" data-toolkit-id="${compatibleToolkitId}">
|
||||||
|
<td>
|
||||||
|
<strong>${compatibleToolkit?.title || 'Неизвестный инструмент'}</strong>
|
||||||
|
${compatibleToolkit ? `
|
||||||
|
<button class="btn btn-sm btn-link p-0 ms-1 view-toolkit-btn"
|
||||||
|
data-toolkit-id="${compatibleToolkitId}"
|
||||||
|
title="Просмотреть детали">
|
||||||
|
<i class="bi bi-eye"></i>
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
</td>
|
||||||
|
<td>${compatibleCategory?.title || 'Неизвестно'}</td>
|
||||||
|
<td><small class="text-muted">${compatibleToolkit?.comment_text || 'Нет комментария'}</small></td>
|
||||||
|
${accessData.tools_edit ? `
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-danger delete-compatibility-btn"
|
||||||
|
data-record-id="${recordId}"
|
||||||
|
data-toolkit-id="${compatibleToolkitId}"
|
||||||
|
title="Удалить совместимость">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
` : ''}
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
compatibilityHtml = `
|
||||||
|
<div class="alert alert-info mb-3">
|
||||||
|
<i class="bi bi-info-circle me-2"></i>
|
||||||
|
Нет данных о совместимых инструментах
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вставляем HTML и показываем контент
|
||||||
|
compatibilityContent.innerHTML = compatibilityHtml;
|
||||||
|
compatibilityContent.classList.remove('d-none');
|
||||||
|
|
||||||
|
// Показываем кнопку добавления, если есть права
|
||||||
|
if (accessData.tools_edit) {
|
||||||
|
addCompatibilityBtn.style.display = 'inline-block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем обработчики для кнопок просмотра инструмента
|
||||||
|
compatibilityContent.querySelectorAll('.view-toolkit-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const compatibleToolkitId = e.currentTarget.dataset.toolkitId;
|
||||||
|
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance(modal);
|
||||||
|
if (modalInstance) {
|
||||||
|
modalInstance.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.addEventListener('hidden.bs.modal', async () => {
|
||||||
|
await showToolkitDetailsModal(compatibleToolkitId);
|
||||||
|
}, { once: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Добавляем обработчики для кнопок удаления
|
||||||
|
compatibilityContent.querySelectorAll('.delete-compatibility-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const recordId = e.currentTarget.dataset.recordId;
|
||||||
|
const compatibleToolkitId = e.currentTarget.dataset.toolkitId;
|
||||||
|
|
||||||
|
if (confirm('Вы уверены, что хотите удалить эту связь совместимости?')) {
|
||||||
|
await deleteCompatibility(recordId, compatibleToolkitId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || 'Ошибка загрузки данных о совместимости');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при загрузке данных о совместимости:', error);
|
||||||
|
compatibilityError.classList.remove('d-none');
|
||||||
|
} finally {
|
||||||
|
compatibilityLoading.classList.add('d-none');
|
||||||
|
isCompatibilityLoading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для удаления совместимости
|
||||||
|
const deleteCompatibility = async (recordId, compatibleToolkitId) => {
|
||||||
|
try {
|
||||||
|
const response = await apiRequest('/toolkit/compatibility', {
|
||||||
|
action: 'delete',
|
||||||
|
userId: userData.id,
|
||||||
|
data: {
|
||||||
|
toolkitId: toolkitData.id,
|
||||||
|
compatibleToolkitId: compatibleToolkitId
|
||||||
|
}
|
||||||
|
}, 'POST');
|
||||||
|
|
||||||
|
if (response.status === 'ok') {
|
||||||
|
showInfo('Связь совместимости успешно удалена', 'success');
|
||||||
|
// Обновляем данные
|
||||||
|
compatibilityData = null;
|
||||||
|
await loadCompatibilityData();
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || 'Ошибка удаления связи');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при удалении совместимости:', error);
|
||||||
|
showInfo(error.message || 'Произошла ошибка при удалении связи', 'danger');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для добавления совместимости
|
||||||
|
const addCompatibility = async (compatibleToolkitId) => {
|
||||||
|
try {
|
||||||
|
const response = await apiRequest('/toolkit/compatibility', {
|
||||||
|
action: 'add',
|
||||||
|
userId: userData.id,
|
||||||
|
data: {
|
||||||
|
toolkitId: toolkitData.id,
|
||||||
|
compatibleToolkitId: compatibleToolkitId
|
||||||
|
}
|
||||||
|
}, 'POST');
|
||||||
|
|
||||||
|
if (response.status === 'ok') {
|
||||||
|
showInfo('Связь совместимости успешно добавлена', 'success');
|
||||||
|
// Обновляем данные
|
||||||
|
compatibilityData = null;
|
||||||
|
await loadCompatibilityData();
|
||||||
|
// Закрываем модальное окно добавления
|
||||||
|
const addModal = bootstrap.Modal.getInstance(document.getElementById(`${toolkitData.id}-add-compatibility-modal`));
|
||||||
|
if (addModal) addModal.hide();
|
||||||
|
} else {
|
||||||
|
throw new Error(response.message || 'Ошибка добавления связи');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при добавлении совместимости:', error);
|
||||||
|
showInfo(error.message || 'Произошла ошибка при добавлении связи', 'danger');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для показа модального окна добавления совместимости
|
||||||
|
const showAddCompatibilityModal = async () => {
|
||||||
|
// Загружаем все инструменты, если еще не загружены
|
||||||
|
if (!allToolkitsData) {
|
||||||
|
try {
|
||||||
|
const response = await apiRequest('/toolkit/all', {}, 'GET');
|
||||||
|
if (response.status === 'ok') {
|
||||||
|
allToolkitsData = response.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка загрузки списка инструментов:', error);
|
||||||
|
showInfo('Не удалось загрузить список инструментов', 'danger');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем модальное окно
|
||||||
|
const addModalId = `${toolkitData.id}-add-compatibility-modal`;
|
||||||
|
let addModal = document.getElementById(addModalId);
|
||||||
|
|
||||||
|
if (addModal) {
|
||||||
|
addModal.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
addModal = document.createElement('div');
|
||||||
|
addModal.className = 'modal fade';
|
||||||
|
addModal.id = addModalId;
|
||||||
|
addModal.tabIndex = -1;
|
||||||
|
|
||||||
|
// Фильтруем инструменты: исключаем текущий и уже совместимые
|
||||||
|
const compatibleIds = Object.values(compatibilityData?.records || {});
|
||||||
|
const filteredToolkits = Object.values(allToolkitsData || {}).filter(toolkit =>
|
||||||
|
toolkit.id !== toolkitData.id && !compatibleIds.includes(toolkit.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
addModal.innerHTML = `
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content modal-content-green">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Добавить совместимый инструмент</h5>
|
||||||
|
<button type="button"
|
||||||
|
class="btn-close btn-close"
|
||||||
|
data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<input type="text" class="form-control" id="compatibilitySearch"
|
||||||
|
placeholder="Поиск инструмента по названию...">
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
|
||||||
|
<table class="table table-sm table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="40"></th>
|
||||||
|
<th>Название</th>
|
||||||
|
<th>Категория</th>
|
||||||
|
<th>Описание</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="compatibilityToolkitsList">
|
||||||
|
${filteredToolkits.map(toolkit => {
|
||||||
|
const category = categories[toolkit.category_id];
|
||||||
|
return `
|
||||||
|
<tr data-toolkit-id="${toolkit.id}"
|
||||||
|
data-toolkit-title="${toolkit.title.toLowerCase()}">
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-primary select-compatibility-btn"
|
||||||
|
data-toolkit-id="${toolkit.id}">
|
||||||
|
<i class="bi bi-plus"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
<td><strong>${toolkit.title}</strong></td>
|
||||||
|
<td>${category?.title || 'Неизвестно'}</td>
|
||||||
|
<td><small class="text-muted">${toolkit.description || 'Нет описания'}</small></td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
${filteredToolkits.length === 0 ? `
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="bi bi-info-circle me-2"></i>
|
||||||
|
Нет доступных инструментов для добавления
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer bg-success text-white">
|
||||||
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal">
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(addModal);
|
||||||
|
const bsAddModal = new bootstrap.Modal(addModal);
|
||||||
|
bsAddModal.show();
|
||||||
|
|
||||||
|
// Функция поиска инструментов
|
||||||
|
const searchInput = addModal.querySelector('#compatibilitySearch');
|
||||||
|
const toolkitsList = addModal.querySelector('#compatibilityToolkitsList');
|
||||||
|
const rows = toolkitsList.querySelectorAll('tr');
|
||||||
|
|
||||||
|
searchInput.addEventListener('input', function () {
|
||||||
|
const searchTerm = this.value.toLowerCase();
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
const title = row.dataset.toolkitTitle;
|
||||||
|
const isVisible = title.includes(searchTerm);
|
||||||
|
row.style.display = isVisible ? '' : 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчики для кнопок выбора
|
||||||
|
addModal.querySelectorAll('.select-compatibility-btn').forEach(button => {
|
||||||
|
button.addEventListener('click', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const compatibleToolkitId = e.currentTarget.dataset.toolkitId;
|
||||||
|
await addCompatibility(compatibleToolkitId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Очистка при закрытии
|
||||||
|
addModal.addEventListener('hidden.bs.modal', () => {
|
||||||
|
addModal.remove();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обработчик события раскрытия аккордеона остатков
|
||||||
const stocksCollapse = modal.querySelector('#stocksCollapse');
|
const stocksCollapse = modal.querySelector('#stocksCollapse');
|
||||||
stocksCollapse.addEventListener('show.bs.collapse', async () => {
|
stocksCollapse.addEventListener('show.bs.collapse', async () => {
|
||||||
// Загружаем данные только если они еще не загружены
|
|
||||||
if (!toolkitStocksData && !isStocksLoading) {
|
if (!toolkitStocksData && !isStocksLoading) {
|
||||||
await loadToolkitStocks();
|
await loadToolkitStocks();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Обработчик для принудительной перезагрузки данных (например, при повторном открытии аккордеона)
|
// Обработчик события раскрытия аккордеона совместимости
|
||||||
const stocksHeading = modal.querySelector('#stocksHeading');
|
const compatibilityCollapse = modal.querySelector('#compatibilityCollapse');
|
||||||
stocksHeading.addEventListener('click', async (e) => {
|
compatibilityCollapse.addEventListener('show.bs.collapse', async () => {
|
||||||
// Если данные уже загружены, можно обновить их при повторном клике
|
if (!compatibilityData && !isCompatibilityLoading) {
|
||||||
const isExpanded = stocksCollapse.classList.contains('show');
|
await loadCompatibilityData();
|
||||||
if (isExpanded && toolkitStocksData) {
|
|
||||||
// Можно добавить кнопку обновления или обновлять автоматически
|
|
||||||
// Для простоты пока оставляем как есть
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Обработчик для кнопки добавления совместимости
|
||||||
|
const addCompatibilityBtn = modal.querySelector('#addCompatibilityBtn');
|
||||||
|
if (addCompatibilityBtn) {
|
||||||
|
addCompatibilityBtn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
showAddCompatibilityModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Остановка всплытия события при клике на кнопку внутри аккордеона
|
||||||
|
const compatibilityHeading = modal.querySelector('#compatibilityHeading');
|
||||||
|
compatibilityHeading.addEventListener('click', (e) => {
|
||||||
|
if (addCompatibilityBtn.contains(e.target)) {
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Очистка при закрытии модалки
|
// Очистка при закрытии модалки
|
||||||
modal.addEventListener('hidden.bs.modal', () => {
|
modal.addEventListener('hidden.bs.modal', () => {
|
||||||
modal.remove();
|
modal.remove();
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,7 @@
|
|||||||
from datetime import date, datetime, time
|
from datetime import date, datetime, time
|
||||||
from sqlalchemy import func, select
|
from sqlalchemy import func, select
|
||||||
from db import CRUD
|
from db import CRUD
|
||||||
|
from db.handlers.records import ServiceRecordsHandler
|
||||||
from db.schemas.orders import Orders
|
from db.schemas.orders import Orders
|
||||||
from utils.loggers import logger
|
from utils.loggers import logger
|
||||||
|
|
||||||
@@ -10,6 +11,10 @@ class OrdersHandler:
|
|||||||
async def new(user_id: int, order: str):
|
async def new(user_id: int, order: str):
|
||||||
try:
|
try:
|
||||||
await Orders(customer_id=user_id, customer_comment=order).save()
|
await Orders(customer_id=user_id, customer_comment=order).save()
|
||||||
|
await ServiceRecordsHandler.add(
|
||||||
|
user_id,
|
||||||
|
{f"Добавлен заказ": f"{order}"},
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка создания заказа: {str(e)}")
|
logger.error(f"Ошибка создания заказа: {str(e)}")
|
||||||
return {"errorMessage": f"Ошибка создания заказа: {str(e)}"}
|
return {"errorMessage": f"Ошибка создания заказа: {str(e)}"}
|
||||||
@@ -92,6 +97,10 @@ class OrdersHandler:
|
|||||||
if comment:
|
if comment:
|
||||||
changeData["executor_comment"] = comment
|
changeData["executor_comment"] = comment
|
||||||
await order.edit(**changeData)
|
await order.edit(**changeData)
|
||||||
|
await ServiceRecordsHandler.add(
|
||||||
|
user_id,
|
||||||
|
{f"Обновлен заказ": f"{order.customer_comment}"},
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка обновления заказа: {str(e)}")
|
logger.error(f"Ошибка обновления заказа: {str(e)}")
|
||||||
return {"errorMessage": f"Ошибка обновления заказа: {str(e)}"}
|
return {"errorMessage": f"Ошибка обновления заказа: {str(e)}"}
|
||||||
|
|||||||
+43
-8
@@ -3,7 +3,7 @@ from db.handlers.stock import StockHandler
|
|||||||
from db.handlers.user import UserHandler
|
from db.handlers.user import UserHandler
|
||||||
from utils import logger, saveImage, safeFilename
|
from utils import logger, saveImage, safeFilename
|
||||||
from db import CRUD
|
from db import CRUD
|
||||||
from db.schemas.toolkit import Toolkit
|
from db.schemas.toolkit import Toolkit, ToolkitCompatibility
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from db.handlers.records import ServiceRecordsHandler
|
from db.handlers.records import ServiceRecordsHandler
|
||||||
from utils.image import deleteImage
|
from utils.image import deleteImage
|
||||||
@@ -173,7 +173,7 @@ class ToolkitHandler:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def getAll():
|
async def getAll():
|
||||||
query = select(Toolkit)
|
query = select(Toolkit).order_by(Toolkit.id)
|
||||||
toolkits = await CRUD.read(query, True)
|
toolkits = await CRUD.read(query, True)
|
||||||
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
|
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
|
||||||
|
|
||||||
@@ -184,16 +184,15 @@ class ToolkitHandler:
|
|||||||
if not toolkit:
|
if not toolkit:
|
||||||
logger.error("Инструмент не найден")
|
logger.error("Инструмент не найден")
|
||||||
return {}
|
return {}
|
||||||
|
data = toolkit.toDict()
|
||||||
if toolkit.comment_user_id:
|
if toolkit.comment_user_id:
|
||||||
user_data = await UserHandler.get(toolkit.comment_user_id)
|
user_data = await UserHandler.get(toolkit.comment_user_id)
|
||||||
data = toolkit.toDict()
|
|
||||||
data["comment_user_data"] = user_data
|
data["comment_user_data"] = user_data
|
||||||
logger.info(data)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def getSeveral(toolkitIds: list[int]) -> list[dict]:
|
async def getSeveral(toolkitIds: list[int]) -> list[dict]:
|
||||||
query = select(Toolkit).where(Toolkit.id.in_(toolkitIds))
|
query = select(Toolkit).where(Toolkit.id.in_(toolkitIds)).order_by(Toolkit.id)
|
||||||
toolkits = await CRUD.read(query, True)
|
toolkits = await CRUD.read(query, True)
|
||||||
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
|
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
|
||||||
|
|
||||||
@@ -230,9 +229,6 @@ class ToolkitHandler:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def addComment(toolkitId: int, user_id: int, comment: str):
|
async def addComment(toolkitId: int, user_id: int, comment: str):
|
||||||
logger.info(f"Добавление комментария к инструменту {toolkitId}...")
|
|
||||||
logger.info(f"Комментарий: {comment}")
|
|
||||||
logger.info(f"Пользователь: {user_id}")
|
|
||||||
query = select(Toolkit).where(Toolkit.id == toolkitId)
|
query = select(Toolkit).where(Toolkit.id == toolkitId)
|
||||||
toolkit = await CRUD.read(query)
|
toolkit = await CRUD.read(query)
|
||||||
if not toolkit:
|
if not toolkit:
|
||||||
@@ -248,6 +244,45 @@ class ToolkitHandler:
|
|||||||
logger.info(f"Комментарий к инструменту {toolkit.title} успешно добавлен")
|
logger.info(f"Комментарий к инструменту {toolkit.title} успешно добавлен")
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def addCompatibility(userId, data):
|
||||||
|
newCompatibility = await ToolkitCompatibility.add_compatibility(
|
||||||
|
int(data.get("toolkitId")), int(data.get("compatibleToolkitId"))
|
||||||
|
)
|
||||||
|
if "errorMessage" not in newCompatibility:
|
||||||
|
await ServiceRecordsHandler.add(
|
||||||
|
userId,
|
||||||
|
{
|
||||||
|
f"Добавлена совместимость": f"{data.get('toolkitId')} - {data.get('compatibleToolkitId')}"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return newCompatibility
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def deleteCompatibility(userId, data):
|
||||||
|
deleteCompatibility = await ToolkitCompatibility.remove_compatibility(
|
||||||
|
int(data.get("toolkitId")), int(data.get("compatibleToolkitId"))
|
||||||
|
)
|
||||||
|
if "errorMessage" not in deleteCompatibility:
|
||||||
|
await ServiceRecordsHandler.add(
|
||||||
|
userId,
|
||||||
|
{
|
||||||
|
f"Удалена совместимость": f"{data.get('toolkitId')} - {data.get('compatibleToolkitId')}"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return deleteCompatibility
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def getCompatibility(toolkitId: int):
|
||||||
|
result = await ToolkitCompatibility.get_compatibility(toolkitId)
|
||||||
|
if "errorMessage" in result:
|
||||||
|
return result
|
||||||
|
toolkitsIds = list(result.get("data").values())
|
||||||
|
toolkitsList = await ToolkitHandler.getSeveral(toolkitsIds)
|
||||||
|
toolkitsData = {toolkit["id"]: toolkit for toolkit in toolkitsList}
|
||||||
|
data = {"records": result.get("data"), "toolkits": toolkitsData}
|
||||||
|
return {"status": "ok", "data": data}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def initialize():
|
async def initialize():
|
||||||
from .categories import CategoryHandler
|
from .categories import CategoryHandler
|
||||||
|
|||||||
Binary file not shown.
+147
-2
@@ -1,9 +1,154 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
|
from sqlalchemy import (
|
||||||
|
Boolean,
|
||||||
|
Column,
|
||||||
|
DateTime,
|
||||||
|
ForeignKey,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
select,
|
||||||
|
)
|
||||||
from sqlalchemy.dialects.postgresql import JSONB
|
from sqlalchemy.dialects.postgresql import JSONB
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
from db import Base, CRUD
|
from db import Base, CRUD
|
||||||
import utils
|
import utils
|
||||||
|
from sqlalchemy import Column, Integer, ForeignKey, UniqueConstraint, CheckConstraint
|
||||||
|
|
||||||
|
|
||||||
|
class ToolkitCompatibility(Base):
|
||||||
|
__tablename__ = "toolkit_compatibility"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
toolkit_id = Column(
|
||||||
|
Integer, ForeignKey("toolkits.id", ondelete="CASCADE"), nullable=False
|
||||||
|
)
|
||||||
|
compatible_toolkit_id = Column(
|
||||||
|
Integer, ForeignKey("toolkits.id", ondelete="CASCADE"), nullable=False
|
||||||
|
)
|
||||||
|
__table_args__ = (
|
||||||
|
# Запрещаем дубли (A-B и A-B)
|
||||||
|
UniqueConstraint(
|
||||||
|
"toolkit_id", "compatible_toolkit_id", name="uq_toolkit_compatibility"
|
||||||
|
),
|
||||||
|
# Запрещаем связь с самим собой
|
||||||
|
CheckConstraint(
|
||||||
|
"toolkit_id <> compatible_toolkit_id", name="ck_toolkit_not_self"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
def toDict(self):
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"toolkit_id": self.toolkit_id,
|
||||||
|
"compatible_toolkit_id": self.compatible_toolkit_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getUnique(
|
||||||
|
compatibles: list["ToolkitCompatibility"], originalToolkitId: int
|
||||||
|
) -> dict:
|
||||||
|
unique = []
|
||||||
|
uniqueData = {}
|
||||||
|
for c in compatibles:
|
||||||
|
if c.toolkit_id not in unique and c.toolkit_id != originalToolkitId:
|
||||||
|
unique.append(c.toolkit_id)
|
||||||
|
uniqueData[c.id] = c.toolkit_id
|
||||||
|
if (
|
||||||
|
c.compatible_toolkit_id not in unique
|
||||||
|
and c.compatible_toolkit_id != originalToolkitId
|
||||||
|
):
|
||||||
|
unique.append(c.compatible_toolkit_id)
|
||||||
|
uniqueData[c.id] = c.compatible_toolkit_id
|
||||||
|
return uniqueData
|
||||||
|
|
||||||
|
async def save(self):
|
||||||
|
await CRUD.create(self)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def add_compatibility(a_id: int, b_id: int):
|
||||||
|
errorMsg = {
|
||||||
|
"status": "error",
|
||||||
|
}
|
||||||
|
|
||||||
|
if a_id == b_id:
|
||||||
|
utils.logger.error("Невозможно добавить совместимость с самим собой")
|
||||||
|
errorMsg["errorMessage"] = "Невозможно добавить совместимость с самим собой"
|
||||||
|
return errorMsg
|
||||||
|
|
||||||
|
toolkit_id, compatible_id = sorted([a_id, b_id])
|
||||||
|
try:
|
||||||
|
|
||||||
|
await ToolkitCompatibility(
|
||||||
|
toolkit_id=toolkit_id, compatible_toolkit_id=compatible_id
|
||||||
|
).save()
|
||||||
|
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
utils.logger.error(f"Ошибка добавления совместимости: {str(e)}")
|
||||||
|
errorMsg["errorMessage"] = f"Ошибка добавления совместимости: {str(e)}"
|
||||||
|
return errorMsg
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def remove_compatibility(a_id: int, b_id: int):
|
||||||
|
errorMsg = {
|
||||||
|
"status": "error",
|
||||||
|
}
|
||||||
|
if a_id == b_id:
|
||||||
|
utils.logger.error("Невозможно удалить совместимость с самим собой")
|
||||||
|
errorMsg["errorMessage"] = "Невозможно удалить совместимость с самим собой"
|
||||||
|
return errorMsg
|
||||||
|
|
||||||
|
toolkit_id, compatible_id = sorted([a_id, b_id])
|
||||||
|
|
||||||
|
try:
|
||||||
|
compatibility = await CRUD.read(
|
||||||
|
select(ToolkitCompatibility).where(
|
||||||
|
ToolkitCompatibility.toolkit_id == toolkit_id,
|
||||||
|
ToolkitCompatibility.compatible_toolkit_id == compatible_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not compatibility:
|
||||||
|
utils.logger.error("Совместимость не найдена")
|
||||||
|
errorMsg["errorMessage"] = "Совместимость не найдена"
|
||||||
|
return errorMsg
|
||||||
|
|
||||||
|
await CRUD.delete(compatibility)
|
||||||
|
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
utils.logger.error(f"Ошибка удаления совместимости: {str(e)}")
|
||||||
|
errorMsg["errorMessage"] = f"Ошибка удаления совместимости: {str(e)}"
|
||||||
|
return errorMsg
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_compatibility(toolkit_id: int):
|
||||||
|
try:
|
||||||
|
|
||||||
|
result = await CRUD.read(
|
||||||
|
select(ToolkitCompatibility).where(
|
||||||
|
(ToolkitCompatibility.toolkit_id == toolkit_id)
|
||||||
|
| (ToolkitCompatibility.compatible_toolkit_id == toolkit_id)
|
||||||
|
),
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
if not result:
|
||||||
|
return {"status": "ok", "data": {}}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "ok",
|
||||||
|
"data": ToolkitCompatibility.getUnique(result, toolkit_id),
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
utils.logger.error(f"Ошибка получения совместимости: {str(e)}")
|
||||||
|
return {"errorMessage": f"Ошибка получения совместимости: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
class Toolkit(Base):
|
class Toolkit(Base):
|
||||||
|
|||||||
Reference in New Issue
Block a user