diff --git a/api/routers/__init__.py b/api/routers/__init__.py index 7885478..fcd30fe 100644 --- a/api/routers/__init__.py +++ b/api/routers/__init__.py @@ -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, diff --git a/api/routers/__pycache__/__init__.cpython-313.pyc b/api/routers/__pycache__/__init__.cpython-313.pyc index d0e02ed..d8cbcd3 100644 Binary files a/api/routers/__pycache__/__init__.cpython-313.pyc and b/api/routers/__pycache__/__init__.cpython-313.pyc differ diff --git a/api/routers/__pycache__/toolbox.cpython-313.pyc b/api/routers/__pycache__/toolbox.cpython-313.pyc index be0dbf3..ee76735 100644 Binary files a/api/routers/__pycache__/toolbox.cpython-313.pyc and b/api/routers/__pycache__/toolbox.cpython-313.pyc differ diff --git a/api/routers/__pycache__/toolkit.cpython-313.pyc b/api/routers/__pycache__/toolkit.cpython-313.pyc index 660a310..d8e472b 100644 Binary files a/api/routers/__pycache__/toolkit.cpython-313.pyc and b/api/routers/__pycache__/toolkit.cpython-313.pyc differ diff --git a/api/routers/toolbox.py b/api/routers/toolbox.py index 26dcb86..1f5ba6d 100644 --- a/api/routers/toolbox.py +++ b/api/routers/toolbox.py @@ -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 diff --git a/api/routers/toolkit.py b/api/routers/toolkit.py index a519130..88bfff4 100644 --- a/api/routers/toolkit.py +++ b/api/routers/toolkit.py @@ -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 diff --git a/api/static/js/index.js b/api/static/js/index.js index 4f61fea..7eeae57 100644 --- a/api/static/js/index.js +++ b/api/static/js/index.js @@ -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 = ` + + `; + + 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 ` + + + + + + + + + + + + + + + + + + + + + + + + `; + } + + // Инициализация первой строки + 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 = ''; + 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) { diff --git a/api/static/js/toast.js b/api/static/js/toast.js index a57791a..392bd24 100644 --- a/api/static/js/toast.js +++ b/api/static/js/toast.js @@ -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 = ` -
+
Уведомление Только что - +
${message} diff --git a/db/handlers/__pycache__/actions.cpython-313.pyc b/db/handlers/__pycache__/actions.cpython-313.pyc index 9321a6e..ba8a3c4 100644 Binary files a/db/handlers/__pycache__/actions.cpython-313.pyc and b/db/handlers/__pycache__/actions.cpython-313.pyc differ diff --git a/db/handlers/__pycache__/stock.cpython-313.pyc b/db/handlers/__pycache__/stock.cpython-313.pyc index 86d6949..b89ba55 100644 Binary files a/db/handlers/__pycache__/stock.cpython-313.pyc and b/db/handlers/__pycache__/stock.cpython-313.pyc differ diff --git a/db/handlers/actions.py b/db/handlers/actions.py index 68a5ea8..406c9fe 100644 --- a/db/handlers/actions.py +++ b/db/handlers/actions.py @@ -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, diff --git a/db/handlers/stock.py b/db/handlers/stock.py index 3463ccc..a0439e1 100644 --- a/db/handlers/stock.py +++ b/db/handlers/stock.py @@ -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 diff --git a/db/schemas/__pycache__/stock.cpython-313.pyc b/db/schemas/__pycache__/stock.cpython-313.pyc index 4eda2c8..f3e586a 100644 Binary files a/db/schemas/__pycache__/stock.cpython-313.pyc and b/db/schemas/__pycache__/stock.cpython-313.pyc differ diff --git a/db/schemas/stock.py b/db/schemas/stock.py index cc980b1..a2e67a1 100644 --- a/db/schemas/stock.py +++ b/db/schemas/stock.py @@ -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)