Завершена работа со складами

This commit is contained in:
2025-12-10 23:03:32 +03:00
parent 197a4d0b1e
commit fcbe25f7ec
14 changed files with 689 additions and 34 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ async def post_requests(
case "toolkits":
toolkits = await ToolkitHandler.getAll()
categories = await CategoryHandler.getAll()
if toolkits:
if toolkits and categories:
resultData["status"] = "ok"
resultData["data"] = {
"toolkits": toolkits,
Binary file not shown.
Binary file not shown.
Binary file not shown.
+33
View File
@@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends
from db.handlers.actions import StocksActions
from db.handlers.stock import StockHandler
from db.handlers.toolbox import ToolboxHandler
from utils import requestDict, logger
@@ -48,3 +49,35 @@ async def delete_toolbox(reqDict=Depends(requestDict)):
if result:
response["status"] = "ok"
return response
@router.post("/fill", summary="Заполнение ящика")
async def fill_toolbox(reqDict=Depends(requestDict)):
logger.info(f"Заполнение ящика")
logger.info(reqDict.get("body"))
response = {"status": "error"}
toolboxId = reqDict.get("body").get("toolboxId")
userId = reqDict.get("body").get("userId")
reason = reqDict.get("body").get("reason")
items = reqDict.get("body").get("items")
successCount = 0
for item in items:
success = await StocksActions.registration(
item.get("toolkit_id"),
toolboxId,
userId,
item.get("quantity"),
item.get("price"),
item.get("placement"),
reason,
)
if success:
successCount += 1
if successCount == len(items):
response["status"] = "ok"
else:
response["message"] = (
f"Оприходовано {successCount} записей из {len(items)}. Проверьте остатки и повторите попытку"
)
logger.info(response)
return response
+20 -1
View File
@@ -1,6 +1,8 @@
from fastapi import APIRouter, Depends
from db.handlers.stock import StockHandler
from db.handlers.categories import CategoryHandler
from db.handlers.stock import PlacementHandler, StockHandler
from db.handlers.toolbox import ToolboxHandler
from db.handlers.toolkit import ToolkitHandler
from utils import requestDict, logger
@@ -55,3 +57,20 @@ async def toolkit_request(
response["status"] = "ok"
response["data"] = stocksData
return response
@router.post("/fill_prepare", summary="Подготовка заполнения ящика")
async def fill_toolbox():
logger.info(f"Подготовка заполнения ящика")
response = {"status": "error"}
toolkits = await ToolkitHandler.getAll()
categories = await CategoryHandler.getAll()
placements = await PlacementHandler.getAll()
if toolkits and categories:
response["status"] = "ok"
response["data"] = {
"toolkits": sorted(toolkits, key=lambda toolkit: toolkit["title"]),
"categories": sorted(categories, key=lambda category: category["title"]),
"placements": [placement.toDict() for placement in placements],
}
return response
+500 -5
View File
@@ -879,8 +879,8 @@ async function loadToolboxContent(toolboxId) {
function handleFillBtn() {
if (accessData.tools_registration && !toolboxInfo.owner_id) {
contentContainer.querySelector('#fillToolbox').addEventListener('click', () => {
fillToolbox(toolboxInfo);
contentContainer.querySelector('#fillToolbox').addEventListener('click', async () => {
await fillToolbox(toolboxInfo);
});
} else {
contentContainer.querySelector('#fillToolbox').remove();
@@ -1047,9 +1047,504 @@ async function loadToolboxContent(toolboxId) {
}
}
function fillToolbox(toolboxInfo) {
console.log(toolboxInfo);
showInfo('Функционал еще в разработке', 'warning');
async function fillToolbox(toolboxInfo) {
const allToolkitsData = await apiRequest('/toolkit/fill_prepare');
if (allToolkitsData.status !== 'ok') {
showInfo('Ошибка загрузки данных инструментов', 'error');
return;
}
// Проверяем, существует ли уже модальное окно
let modal = document.getElementById('fillToolboxModal');
if (modal) {
modal.remove();
}
// Создаем модальное окно
modal = document.createElement('div');
modal.className = 'modal fade';
modal.id = 'fillToolboxModal';
modal.tabIndex = -1;
modal.setAttribute('aria-hidden', 'true');
modal.innerHTML = `
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="bi bi-cart-plus-fill me-2"></i>
Пополнение склада: ${toolboxInfo.title}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<form id="fillToolboxForm" novalidate>
<div class="modal-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0" id="fillToolboxTable">
<thead class="table-light sticky-top">
<tr>
<th style="width: 20%;">Категория</th>
<th style="width: 25%;">Инструмент</th>
<th style="width: 10%;">Количество</th>
<th style="width: 12%;">Цена, ₽</th>
<th style="width: 15%;">Расположение</th>
<th style="width: 12%;">Стоимость, ₽</th>
<th style="width: 6%;"></th>
</tr>
</thead>
<tbody id="fillToolboxRows">
<!-- Строки будут добавляться здесь -->
</tbody>
<tfoot class="table-light">
<tr>
<td colspan="2" class="text-end fw-bold">
<button type="button" class="btn btn-sm btn-outline-primary" id="addRowBtn">
<i class="bi bi-plus-circle me-1"></i>Добавить строку
</button>
</td>
<td class="fw-bold text-center" id="totalQuantity">0</td>
<td></td>
<td></td>
<td class="fw-bold text-center" id="totalCost">0.00</td>
<td></td>
</tr>
<tr>
<td colspan="2" class="text-end">Итого:</td>
<td class="text-center fw-bold" id="totalRowsCount">0 позиций</td>
<td colspan="4"></td>
</tr>
<tr>
<td colspan="7" class="p-3">
<div class="mb-3">
<label for="fillReason" class="form-label required">Обоснование пополнения</label>
<textarea class="form-control" id="fillReason" rows="1"
placeholder="Укажите основание пополнения склада (например, накладная, счёт-фактура и т.д.)"
required minlength="10" maxlength="500"></textarea>
<div class="invalid-feedback">
Пожалуйста, укажите обоснование (не менее 10 символов, не более 500)
</div>
</div>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal-footer">
<div id="fillToolboxError" class="alert alert-danger d-none w-100 mb-3" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>
<span id="fillToolboxErrorMessage"></span>
</div>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="submit" class="btn btn-success" id="submitFillBtn">
<span class="spinner-border spinner-border-sm me-1"
id="submitFillSpinner" style="display: none;"></span>
<span id="submitFillText">Подтвердить пополнение</span>
</button>
</div>
</form>
</div>
</div>
`;
document.body.appendChild(modal);
// Инициализация модального окна
const bsModal = new bootstrap.Modal(modal);
// Получаем данные
const { toolkits, categories, placements } = allToolkitsData.data;
const placementMap = {};
// Создаем карту расположений для текущего склада
placements
.filter(p => p.toolbox_id === toolboxInfo.id)
.forEach(p => {
placementMap[p.toolkit_id] = p.placement;
});
// Создаем карту инструментов по категориям
const toolkitsByCategory = {};
toolkits.forEach(tool => {
if (!toolkitsByCategory[tool.category_id]) {
toolkitsByCategory[tool.category_id] = [];
}
toolkitsByCategory[tool.category_id].push(tool);
});
// Функция для форматирования стоимости с разделителями
function formatCost(value) {
if (typeof value !== 'number') {
value = parseFloat(value) || 0;
}
return formatPrice(value);
}
// Функция для создания строки формы
function createRow(rowIndex = 0) {
const rowId = `row-${Date.now()}-${rowIndex}`;
return `
<tr id="${rowId}" data-row-id="${rowId}">
<td>
<select class="form-select form-select-sm category-select"
data-row="${rowId}" required>
<option value="">Выберите категорию</option>
${categories.map(cat =>
`<option value="${cat.id}">${cat.title}</option>`
).join('')}
</select>
</td>
<td>
<select class="form-select form-select-sm toolkit-select"
data-row="${rowId}" disabled required>
<option value="">Выберите инструмент</option>
</select>
</td>
<td>
<input type="number" class="form-control form-control-sm quantity"
data-row="${rowId}" min="1" step="1"
placeholder="0" disabled required>
</td>
<td>
<input type="number" class="form-control form-control-sm price"
data-row="${rowId}" min="0.01" step="0.01"
placeholder="0.00" disabled required>
</td>
<td>
<input type="text" class="form-control form-control-sm placement"
data-row="${rowId}" placeholder="Не указано" disabled>
</td>
<td>
<input type="text" class="form-control form-control-sm cost"
data-row="${rowId}" value="0.00 ₽" readonly>
</td>
<td class="text-center">
<button type="button" class="btn btn-sm btn-outline-danger remove-row"
data-row="${rowId}" ${rowIndex === 0 ? 'disabled' : ''}>
<i class="bi bi-x-lg"></i>
</button>
</td>
</tr>
`;
}
// Инициализация первой строки
const rowsContainer = document.getElementById('fillToolboxRows');
rowsContainer.innerHTML = createRow(0);
// Добавляем обработчики событий
setupEventHandlers();
// Функция для обновления итогов
function updateTotals() {
const rows = rowsContainer.querySelectorAll('tr');
let totalQuantity = 0;
let totalCost = 0;
let filledRows = 0;
rows.forEach(row => {
const quantityInput = row.querySelector('.quantity');
const costInput = row.querySelector('.cost');
if (quantityInput && costInput &&
quantityInput.value && costInput.value) {
const quantity = parseInt(quantityInput.value) || 0;
const cost = parseFloat(costInput.value.replace(/[^0-9.,]/g, '').replace(',', '.')) || 0;
totalQuantity += quantity;
totalCost += cost;
filledRows++;
}
});
document.getElementById('totalQuantity').textContent = totalQuantity;
document.getElementById('totalCost').textContent = formatCost(totalCost) + ' ₽';
document.getElementById('totalRowsCount').textContent =
`${filledRows} позиций`;
}
// Функция для расчета стоимости строки
function calculateRowCost(row) {
const quantityInput = row.querySelector('.quantity');
const priceInput = row.querySelector('.price');
const costInput = row.querySelector('.cost');
if (!quantityInput || !priceInput || !costInput) return;
const quantity = parseFloat(quantityInput.value) || 0;
const price = parseFloat(priceInput.value.replace(',', '.')) || 0;
const cost = quantity * price;
costInput.value = formatCost(cost) + ' ₽';
updateTotals();
}
// Функция для настройки обработчиков событий
function setupEventHandlers() {
// Обработчик добавления строки
document.getElementById('addRowBtn').addEventListener('click', function () {
const newRow = createRow(rowsContainer.children.length);
rowsContainer.insertAdjacentHTML('beforeend', newRow);
// Обновляем доступность кнопок удаления
const removeButtons = rowsContainer.querySelectorAll('.remove-row');
if (removeButtons.length > 1) {
removeButtons.forEach(btn => btn.disabled = false);
}
// Настраиваем обработчики для новой строки
const newRowElement = rowsContainer.lastElementChild;
setupRowHandlers(newRowElement);
});
// Настраиваем обработчики для всех строк
rowsContainer.querySelectorAll('tr').forEach(row => {
setupRowHandlers(row);
});
// Валидация поля обоснования
const reasonInput = document.getElementById('fillReason');
reasonInput.addEventListener('input', function () {
if (this.value.length >= 10) {
this.classList.remove('is-invalid');
this.classList.add('is-valid');
} else if (this.value.length > 0) {
this.classList.add('is-invalid');
this.classList.remove('is-valid');
} else {
this.classList.remove('is-invalid', 'is-valid');
}
});
// Обработчик отправки формы
document.getElementById('fillToolboxForm').addEventListener('submit', async function (e) {
e.preventDefault();
const submitBtn = document.getElementById('submitFillBtn');
const spinner = document.getElementById('submitFillSpinner');
const submitText = document.getElementById('submitFillText');
// Проверка заполнения всех обязательных полей
const rows = rowsContainer.querySelectorAll('tr');
const items = [];
let isValid = true;
let errorMessage = '';
// Проверка обоснования
const reason = reasonInput.value.trim();
if (reason.length < 10) {
isValid = false;
errorMessage = 'Введите обоснование (не менее 10 символов)';
reasonInput.classList.add('is-invalid');
reasonInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else {
reasonInput.classList.remove('is-invalid');
reasonInput.classList.add('is-valid');
}
// Проверка строк с инструментами
rows.forEach((row, index) => {
const categorySelect = row.querySelector('.category-select');
const toolkitSelect = row.querySelector('.toolkit-select');
const quantityInput = row.querySelector('.quantity');
const priceInput = row.querySelector('.price');
if (!categorySelect.value || !toolkitSelect.value ||
!quantityInput.value || !priceInput.value) {
isValid = false;
errorMessage = `Заполните все обязательные поля в строке ${index + 1}`;
// Подсвечиваем проблемные поля
[categorySelect, toolkitSelect, quantityInput, priceInput].forEach(field => {
if (!field.value) {
field.classList.add('is-invalid');
}
});
return;
}
items.push({
toolkit_id: parseInt(toolkitSelect.value),
quantity: parseInt(quantityInput.value),
price: parseFloat(priceInput.value.replace(',', '.')),
placement: row.querySelector('.placement').value || null
});
});
if (!isValid) {
document.getElementById('fillToolboxErrorMessage').textContent = errorMessage;
document.getElementById('fillToolboxError').classList.remove('d-none');
document.getElementById('fillToolboxError').scrollIntoView({ behavior: 'smooth', block: 'center' });
return;
}
if (items.length === 0) {
document.getElementById('fillToolboxErrorMessage').textContent = 'Добавьте хотя бы одну позицию';
document.getElementById('fillToolboxError').classList.remove('d-none');
return;
}
// Запрос подтверждения (двойное нажатие в течение 10 секунд)
const originalText = submitText.textContent;
submitText.textContent = 'Нажмите еще раз для подтверждения (10 сек)';
submitBtn.disabled = true;
let confirmed = false;
let timeout = setTimeout(() => {
if (!confirmed) {
submitText.textContent = originalText;
submitBtn.disabled = false;
document.getElementById('fillToolboxError').classList.add('d-none');
}
}, 10000);
submitBtn.addEventListener('click', async function confirmHandler(e) {
e.preventDefault();
confirmed = true;
clearTimeout(timeout);
// Показываем индикатор загрузки
submitBtn.disabled = true;
spinner.style.display = 'inline-block';
try {
const response = await apiRequest('/toolbox/fill', {
toolboxId: toolboxInfo.id,
items: items,
reason: reason,
userId: userData.id
}, 'POST');
if (response.status === 'ok') {
showInfo('Склад успешно пополнен', 'success');
bsModal.hide();
// Обновляем содержимое склада
await loadToolboxContent(toolboxInfo.id);
} else {
throw new Error(response.message || 'Ошибка при пополнении склада');
}
} catch (error) {
console.error('Ошибка при пополнении склада:', error);
document.getElementById('fillToolboxErrorMessage').textContent =
error.message || 'Произошла ошибка при пополнении склада';
document.getElementById('fillToolboxError').classList.remove('d-none');
// Возвращаем кнопку в исходное состояние
submitBtn.disabled = false;
spinner.style.display = 'none';
submitText.textContent = originalText;
}
// Удаляем обработчик подтверждения
submitBtn.removeEventListener('click', confirmHandler);
}, { once: true });
submitBtn.disabled = false;
});
}
// Функция для настройки обработчиков строки
function setupRowHandlers(row) {
const rowId = row.id;
// Обработчик выбора категории
const categorySelect = row.querySelector('.category-select');
categorySelect.addEventListener('change', function () {
this.classList.remove('is-invalid');
const toolkitSelect = row.querySelector('.toolkit-select');
const quantityInput = row.querySelector('.quantity');
const priceInput = row.querySelector('.price');
const placementInput = row.querySelector('.placement');
// Очищаем и деактивируем зависимые поля
toolkitSelect.innerHTML = '<option value="">Выберите инструмент</option>';
toolkitSelect.disabled = !this.value;
quantityInput.disabled = !this.value;
priceInput.disabled = !this.value;
placementInput.disabled = !this.value;
if (!this.value) {
quantityInput.value = '';
priceInput.value = '';
placementInput.value = '';
row.querySelector('.cost').value = '0.00 ₽';
updateTotals();
return;
}
// Заполняем список инструментов выбранной категории
const categoryId = parseInt(this.value);
const categoryTools = toolkitsByCategory[categoryId] || [];
categoryTools.forEach(tool => {
const option = document.createElement('option');
option.value = tool.id;
option.textContent = tool.title;
toolkitSelect.appendChild(option);
});
});
// Обработчик выбора инструмента
const toolkitSelect = row.querySelector('.toolkit-select');
toolkitSelect.addEventListener('change', function () {
this.classList.remove('is-invalid');
if (!this.value) return;
const toolId = parseInt(this.value);
// Автозаполнение расположения, если есть
if (placementMap[toolId]) {
row.querySelector('.placement').value = placementMap[toolId];
}
});
// Обработчики изменения количества и цены
const quantityInput = row.querySelector('.quantity');
const priceInput = row.querySelector('.price');
[quantityInput, priceInput].forEach(input => {
input.addEventListener('input', function () {
this.classList.remove('is-invalid');
calculateRowCost(row);
});
});
// Обработчик удаления строки
const removeBtn = row.querySelector('.remove-row');
removeBtn.addEventListener('click', function () {
if (rowsContainer.children.length <= 1) return;
row.remove();
updateTotals();
// Если осталась одна строка, делаем кнопку удаления недоступной
if (rowsContainer.children.length === 1) {
rowsContainer.querySelector('.remove-row').disabled = true;
}
});
}
// Очистка при закрытии модального окна
modal.addEventListener('hidden.bs.modal', () => {
setTimeout(() => {
if (modal.parentNode) {
modal.remove();
}
}, 300);
});
// Показываем модальное окно
bsModal.show();
// Устанавливаем фокус на первую категорию
setTimeout(() => {
const firstCategorySelect = rowsContainer.querySelector('.category-select');
if (firstCategorySelect) {
firstCategorySelect.focus();
}
}, 500);
}
async function deleteToolbox(toolboxId) {
+7 -7
View File
@@ -18,28 +18,28 @@ export function showInfo(message, type = 'info') {
// Определяем классы в зависимости от типа
const typeConfig = {
'success': { bgClass: 'bg-success', icon: 'bi-check-circle', delay: 5000 },
'error': { bgClass: 'bg-danger', icon: 'bi-exclamation-circle', delay: 10000 },
'info': { bgClass: 'bg-info', icon: 'bi-info-circle', delay: 3000 },
'warning': { bgClass: 'bg-warning', icon: 'bi-exclamation-triangle', delay: 8000 }
'success': { bgClass: 'bg-success', icon: 'bi-check-circle', delay: 5000, textColor: 'text-white' },
'error': { bgClass: 'bg-danger', icon: 'bi-exclamation-circle', delay: 10000, textColor: 'text-white' },
'info': { bgClass: 'bg-info', icon: 'bi-info-circle', delay: 3000, textColor: 'text-dark' },
'warning': { bgClass: 'bg-warning', icon: 'bi-exclamation-triangle', delay: 8000, textColor: 'text-dark' }
};
const config = typeConfig[type] || typeConfig.info;
// Создаем тост
const toast = document.createElement('div');
toast.className = `toast ${config.bgClass} text-white`;
toast.className = `toast ${config.bgClass} ${config.textColor} shadow-sm`;
toast.id = toastId;
toast.setAttribute('role', 'alert');
toast.setAttribute('aria-live', 'assertive');
toast.setAttribute('aria-atomic', 'true');
toast.innerHTML = `
<div class="toast-header ${config.bgClass} text-white">
<div class="toast-header ${config.bgClass} ${config.textColor}">
<i class="bi ${config.icon} me-2"></i>
<strong class="me-auto">Уведомление</strong>
<small>Только что</small>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
<button type="button" class="btn-close btn-close-dark" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">
${message}
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -20,7 +20,7 @@ class StocksActions:
price: int,
placement: str,
reason: str,
):
) -> bool:
logger.info(f"Приход инструмента {toolkit_id} на складе {toolbox_id} ...")
newStocks = await StockHandler.add(
toolkit_id=toolkit_id,
+92 -17
View File
@@ -1,34 +1,109 @@
from sqlalchemy import select
from db.schemas.stock import Stock
from db.schemas.stock import Placement, Stock
from utils import logger
def filterQuantity(stocksData, filtered):
async def filterQuantity(stocksData, filtered):
def filterStock(stock):
if stock.quantity > 0:
return stock
else:
return False
def convertPlacementData(placementList: list) -> dict:
placementDict = {}
for placementData in placementList:
toolboxId = placementData.toolbox_id
toolkitId = placementData.toolkit_id
placement = placementData.placement
if toolboxId not in placementDict:
placementDict[toolboxId] = {}
if toolkitId not in placementDict[toolboxId]:
placementDict[toolboxId][toolkitId] = placement
return placementDict
async def combinePlacementAndStock(stockData):
if stockData:
if isinstance(stockData, list):
placementList = await PlacementHandler.getAll()
placementDict = convertPlacementData(placementList)
for stock in stockData:
toolboxId = stock.get("toolbox_id")
toolkitId = stock.get("toolkit_id")
stock["placement"] = placementDict.get(toolboxId, {}).get(
toolkitId, None
)
else:
toolboxId = stockData.get("toolbox_id")
toolkitId = stockData.get("toolkit_id")
placement = await PlacementHandler.get(toolboxId, toolkitId)
stockData["placement"] = placement.placement
return stockData
exitData = []
if isinstance(stocksData, list):
if len(stocksData) == 0:
return []
stocksData.sort(key=lambda stock: stock.created_at)
filteredStocks = (
list(filter(filterStock, stocksData)) if filtered else stocksData
)
return [stock.toDict() for stock in filteredStocks] if filteredStocks else []
if len(stocksData) > 0:
stocksData.sort(key=lambda stock: stock.created_at)
filteredStocks = (
list(filter(filterStock, stocksData)) if filtered else stocksData
)
if filteredStocks:
exitData = [stock.toDict() for stock in filteredStocks]
else:
stock = filterStock(stocksData) if filtered else stocksData
if stock:
return stock.toDict()
exitData = stock.toDict()
return await combinePlacementAndStock(exitData)
class PlacementHandler:
async def add(**kwargs):
placement = kwargs.get("placement", None)
if not placement:
return
toolboxId = kwargs.get("toolbox_id", None)
toolkitId = kwargs.get("toolkit_id", None)
if not (toolkitId and toolboxId):
logger.error("toolkit_id or toolbox_id not found")
return
chechExists = await PlacementHandler.get(toolboxId, toolkitId)
if chechExists:
if placement != chechExists.placement:
logger.info(
f"Обновление места хранения инструмента {toolkitId} в ящике {toolboxId} с {chechExists.placement} на {placement}"
)
return await PlacementHandler.edit(chechExists, placement)
else:
return {}
logger.info(
f"Новое место хранения инструмента {toolkitId} в ящике {toolboxId} успешно создано {placement}"
)
return await Placement(**kwargs).save()
async def get(toolboxId: int, toolkitId: int) -> Placement:
from db import CRUD
return await CRUD.read(
select(Placement)
.where(Placement.toolbox_id == toolboxId)
.where(Placement.toolkit_id == toolkitId)
)
async def getAll() -> list[Placement]:
from db import CRUD
return await CRUD.read(select(Placement), all=True)
async def edit(toolboxId: int, toolkitId: int, placement: str):
placementDB = await PlacementHandler.get(toolboxId, toolkitId)
if placementDB and placementDB.placement != placement:
return await placementDB.edit(placement)
class StockHandler:
async def add(**kwargs) -> dict:
newStock = await Stock(**kwargs).save()
if "placement" in kwargs and kwargs["placement"]:
await PlacementHandler.add(**kwargs)
logger.info(
f"Новая запись об инструменте {newStock.toolkit_data.title} на складе {newStock.toolbox_data.title} успешно создана"
)
@@ -38,7 +113,7 @@ class StockHandler:
from db import CRUD
stocks = await CRUD.read(select(Stock), all=True)
return filterQuantity(stocks, filtered)
return await filterQuantity(stocks, filtered)
async def get(stockId: int, filtered: bool = True, record: bool = False):
from db import CRUD
@@ -47,21 +122,21 @@ class StockHandler:
if not stock:
logger.error(f"Запись {stockId} об остатках не найдена")
return {}
return filterQuantity(stock, filtered) if not record else stock
return await filterQuantity(stock, filtered) if not record else stock
async def getByToolboxId(toolboxId: int, filtered: bool = True):
from db import CRUD
query = select(Stock).where(Stock.toolbox_id == toolboxId)
stocks = await CRUD.read(query, True)
return filterQuantity(stocks, filtered)
return await filterQuantity(stocks, filtered)
async def getByToolkitId(toolkitId: int, filtered: bool = True):
from db import CRUD
query = select(Stock).where(Stock.toolkit_id == toolkitId)
stocks = await CRUD.read(query, True)
return filterQuantity(stocks, filtered)
return await filterQuantity(stocks, filtered)
async def getByToolboxIdAndToolkitId(
toolboxId: int, toolkitId: int, filtered: bool = True
@@ -74,7 +149,7 @@ class StockHandler:
.where(Stock.toolkit_id == toolkitId)
)
stocks = await CRUD.read(query, True)
return filterQuantity(stocks, filtered)
return await filterQuantity(stocks, filtered)
async def getByToolboxIdAndToolkitIdAndQPrice(
toolboxId: int, toolkitId: int, price: float, filtered: bool = True
@@ -88,7 +163,7 @@ class StockHandler:
.where(Stock.price == price)
)
stocks = await CRUD.read(query, True)
return filterQuantity(stocks, filtered)
return await filterQuantity(stocks, filtered)
async def edit(stockId: int, **kwargs) -> dict:
from db import CRUD
Binary file not shown.
+35 -2
View File
@@ -32,13 +32,15 @@ class Stock(Base):
)
quantity = Column(Integer, nullable=False)
price = Column(Float, nullable=False)
placement = Column(String, nullable=True)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
try:
setattr(self, key, value)
except Exception:
pass
def toDict(self):
return utils.toDict(self)
@@ -48,3 +50,34 @@ class Stock(Base):
async def edit(self, **kwargs):
return await CRUD.update(Stock, self.id, **kwargs)
class Placement(Base):
__tablename__ = "placements"
id = Column(Integer, primary_key=True, index=True)
toolbox_id = Column(
Integer, ForeignKey("toolboxes.id", ondelete="CASCADE"), nullable=False
)
toolkit_id = Column(
Integer, ForeignKey("toolkits.id", ondelete="CASCADE"), nullable=False
)
placement = Column(String, nullable=False)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
def __init__(self, **kwargs):
for key, value in kwargs.items():
try:
setattr(self, key, value)
except Exception:
pass
def toDict(self):
return utils.toDict(self)
async def save(self) -> "Placement":
return await CRUD.create(self, refresh=True)
async def edit(self, placement: str):
return await CRUD.update(Placement, self.id, placement=placement)