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"]
|
||||
else:
|
||||
response["status"] = "ok"
|
||||
if "data" in result.keys():
|
||||
response["data"] = result["data"]
|
||||
return response
|
||||
|
||||
|
||||
@@ -251,3 +253,40 @@ async def quick_action(reqData: dict = Depends(requestDict)):
|
||||
case _:
|
||||
pass
|
||||
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
|
||||
|
||||
@@ -459,4 +459,8 @@ tr:hover .action-buttons {
|
||||
.modal-footer {
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content-green {
|
||||
background-color: #f4fbf6; /* светло-зелёный */
|
||||
}
|
||||
+396
-16
@@ -1,6 +1,6 @@
|
||||
import { getCookie } from '/static/js/cookies.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 userData;
|
||||
@@ -3836,14 +3836,16 @@ async function getToolkitStocks(toolkitId) {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
// Функция показа модального окна с деталями инструмента
|
||||
// Функция показа модального окна с деталями инструмента
|
||||
async function showToolkitDetailsModal(toolkitId) {
|
||||
const modalId = 'toolkitDetailsModal';
|
||||
let modal = document.getElementById(modalId);
|
||||
|
||||
if (modal) {
|
||||
modal.hide();
|
||||
const modalInstance = bootstrap.Modal.getInstance(modal);
|
||||
if (modalInstance) {
|
||||
modalInstance.hide();
|
||||
}
|
||||
modal.remove();
|
||||
}
|
||||
|
||||
@@ -3930,9 +3932,12 @@ async function showToolkitDetailsModal(toolkitId) {
|
||||
` : '<div class="col-md-4"></div>';
|
||||
}
|
||||
|
||||
// Переменная для хранения данных об остатках (будет загружена при раскрытии аккордеона)
|
||||
// Переменные для хранения данных
|
||||
let toolkitStocksData = null;
|
||||
let isStocksLoading = false;
|
||||
let compatibilityData = null;
|
||||
let isCompatibilityLoading = false;
|
||||
let allToolkitsData = null;
|
||||
|
||||
// Форматирование даты комментария
|
||||
let commentDateInfo = '';
|
||||
@@ -4028,6 +4033,8 @@ async function showToolkitDetailsModal(toolkitId) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
${toolkitData.external_link ? `
|
||||
<div class="mt-3">
|
||||
<a href="${toolkitData.external_link}" target="_blank" class="btn btn-sm btn-outline-primary">
|
||||
@@ -4037,6 +4044,45 @@ async function showToolkitDetailsModal(toolkitId) {
|
||||
` : ''}
|
||||
</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">
|
||||
@@ -4140,8 +4186,6 @@ async function showToolkitDetailsModal(toolkitId) {
|
||||
|
||||
// Показываем результат
|
||||
if (response.status === 'ok') {
|
||||
// Успешное отображение
|
||||
|
||||
// Обновляем список инструментов
|
||||
if (typeof uploadTab === 'function') {
|
||||
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');
|
||||
stocksCollapse.addEventListener('show.bs.collapse', async () => {
|
||||
// Загружаем данные только если они еще не загружены
|
||||
if (!toolkitStocksData && !isStocksLoading) {
|
||||
await loadToolkitStocks();
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик для принудительной перезагрузки данных (например, при повторном открытии аккордеона)
|
||||
const stocksHeading = modal.querySelector('#stocksHeading');
|
||||
stocksHeading.addEventListener('click', async (e) => {
|
||||
// Если данные уже загружены, можно обновить их при повторном клике
|
||||
const isExpanded = stocksCollapse.classList.contains('show');
|
||||
if (isExpanded && toolkitStocksData) {
|
||||
// Можно добавить кнопку обновления или обновлять автоматически
|
||||
// Для простоты пока оставляем как есть
|
||||
// Обработчик события раскрытия аккордеона совместимости
|
||||
const compatibilityCollapse = modal.querySelector('#compatibilityCollapse');
|
||||
compatibilityCollapse.addEventListener('show.bs.collapse', async () => {
|
||||
if (!compatibilityData && !isCompatibilityLoading) {
|
||||
await loadCompatibilityData();
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик для кнопки добавления совместимости
|
||||
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.remove();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
from datetime import date, datetime, time
|
||||
from sqlalchemy import func, select
|
||||
from db import CRUD
|
||||
from db.handlers.records import ServiceRecordsHandler
|
||||
from db.schemas.orders import Orders
|
||||
from utils.loggers import logger
|
||||
|
||||
@@ -10,6 +11,10 @@ class OrdersHandler:
|
||||
async def new(user_id: int, order: str):
|
||||
try:
|
||||
await Orders(customer_id=user_id, customer_comment=order).save()
|
||||
await ServiceRecordsHandler.add(
|
||||
user_id,
|
||||
{f"Добавлен заказ": f"{order}"},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка создания заказа: {str(e)}")
|
||||
return {"errorMessage": f"Ошибка создания заказа: {str(e)}"}
|
||||
@@ -92,6 +97,10 @@ class OrdersHandler:
|
||||
if comment:
|
||||
changeData["executor_comment"] = comment
|
||||
await order.edit(**changeData)
|
||||
await ServiceRecordsHandler.add(
|
||||
user_id,
|
||||
{f"Обновлен заказ": f"{order.customer_comment}"},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка обновления заказа: {str(e)}")
|
||||
return {"errorMessage": f"Ошибка обновления заказа: {str(e)}"}
|
||||
|
||||
+44
-9
@@ -3,7 +3,7 @@ from db.handlers.stock import StockHandler
|
||||
from db.handlers.user import UserHandler
|
||||
from utils import logger, saveImage, safeFilename
|
||||
from db import CRUD
|
||||
from db.schemas.toolkit import Toolkit
|
||||
from db.schemas.toolkit import Toolkit, ToolkitCompatibility
|
||||
from sqlalchemy import select
|
||||
from db.handlers.records import ServiceRecordsHandler
|
||||
from utils.image import deleteImage
|
||||
@@ -173,7 +173,7 @@ class ToolkitHandler:
|
||||
|
||||
@staticmethod
|
||||
async def getAll():
|
||||
query = select(Toolkit)
|
||||
query = select(Toolkit).order_by(Toolkit.id)
|
||||
toolkits = await CRUD.read(query, True)
|
||||
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
|
||||
|
||||
@@ -184,16 +184,15 @@ class ToolkitHandler:
|
||||
if not toolkit:
|
||||
logger.error("Инструмент не найден")
|
||||
return {}
|
||||
data = toolkit.toDict()
|
||||
if toolkit.comment_user_id:
|
||||
user_data = await UserHandler.get(toolkit.comment_user_id)
|
||||
data = toolkit.toDict()
|
||||
data["comment_user_data"] = user_data
|
||||
logger.info(data)
|
||||
data["comment_user_data"] = user_data
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
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)
|
||||
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
|
||||
|
||||
@@ -230,9 +229,6 @@ class ToolkitHandler:
|
||||
|
||||
@staticmethod
|
||||
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)
|
||||
toolkit = await CRUD.read(query)
|
||||
if not toolkit:
|
||||
@@ -248,6 +244,45 @@ class ToolkitHandler:
|
||||
logger.info(f"Комментарий к инструменту {toolkit.title} успешно добавлен")
|
||||
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
|
||||
async def initialize():
|
||||
from .categories import CategoryHandler
|
||||
|
||||
Binary file not shown.
+147
-2
@@ -1,9 +1,154 @@
|
||||
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.orm import relationship
|
||||
from db import Base, CRUD
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user