diff --git a/api/routers/__init__.py b/api/routers/__init__.py index deaeac6..039138e 100644 --- a/api/routers/__init__.py +++ b/api/routers/__init__.py @@ -1,15 +1,17 @@ -from datetime import date, datetime, timedelta +from datetime import datetime, timedelta from fastapi import APIRouter, Depends, Request from fastapi.responses import RedirectResponse from db.handlers.access import AccessLevelHandler from db.handlers.categories import CategoryHandler +from db.handlers.orders import OrdersHandler from utils import render, requestDict, logger from .user import router as user from .stocks import router as stocks from .toolbox import router as toolbox from .toolkit import router as toolkit from .records import router as records +from .orders import router as orders router = APIRouter() @@ -19,6 +21,7 @@ router.include_router(stocks, prefix="/stocks", tags=["stocks"]) router.include_router(toolbox, prefix="/toolbox", tags=["toolbox"]) router.include_router(toolkit, prefix="/toolkit", tags=["toolkit"]) router.include_router(records, prefix="/records", tags=["records"]) +router.include_router(orders, prefix="/orders", tags=["orders"]) @router.get("/") @@ -52,6 +55,21 @@ async def post_requests( from db.handlers.toolkit import ToolkitHandler from db.handlers.user import UserHandler + def getDates(data: dict): + startDate = data.get("startDate") + if isinstance(startDate, str): + startDate = datetime.strptime(startDate, "%Y-%m-%d").date() + if startDate is None: + startDate = datetime.now().date() - timedelta(days=30) + endDate = data.get("endDate") + if isinstance(endDate, str): + endDate = datetime.strptime(endDate, "%Y-%m-%d").date() + if endDate is None: + endDate = datetime.now().date() + if startDate > endDate: + startDate, endDate = endDate, startDate + return startDate, endDate + reqData = { "tab": request_data.get("body").get("tabId"), } @@ -105,18 +123,7 @@ async def post_requests( "categories": categories, } case "jurnal_toolkits": - startDate = request_data.get("body").get( - "startDate", date.today() - timedelta(days=7) - ) - if isinstance(startDate, str): - startDate = datetime.strptime(startDate, "%Y-%m-%d").date() - - endDate = request_data.get("body").get("endDate", date.today()) - if isinstance(endDate, str): - endDate = datetime.strptime(endDate, "%Y-%m-%d").date() - - if startDate > endDate: - startDate, endDate = endDate, startDate + startDate, endDate = getDates(request_data.get("body")) jurnal_toolkits = await StocksRecordsHandler.getLogs(startDate, endDate) if isinstance(jurnal_toolkits, list): @@ -147,18 +154,7 @@ async def post_requests( "endDate": endDate.strftime("%Y-%m-%d"), } case "jurnal_service": - startDate = request_data.get("body").get( - "startDate", date.today() - timedelta(days=7) - ) - if isinstance(startDate, str): - startDate = datetime.strptime(startDate, "%Y-%m-%d").date() - - endDate = request_data.get("body").get("endDate", date.today()) - if isinstance(endDate, str): - endDate = datetime.strptime(endDate, "%Y-%m-%d").date() - - if startDate > endDate: - startDate, endDate = endDate, startDate + startDate, endDate = getDates(request_data.get("body")) jurnal_service = await ServiceRecordsHandler.getLogs(startDate, endDate) if isinstance(jurnal_service, list): @@ -199,7 +195,33 @@ async def post_requests( "users": users, "accessLevels": accessLevels, } - # logger.info(resultData) + case "orders": + fullAccess = reqData["accessData"].get("view_all_toolboxes", False) + startDate, endDate = getDates(request_data.get("body")) + if fullAccess: + result = await OrdersHandler.get_all(startDate, endDate) + if "errorMessage" in result.keys(): + resultData["message"] = result["errorMessage"] + else: + resultData["status"] = "ok" + if "orders" in result.keys(): + resultData["data"]["orders"] = result["orders"] + else: + userId = reqData.get("userData").get("id") + result = await OrdersHandler.get_all_by_consumer( + userId, startDate, endDate + ) + if "errorMessage" in result.keys(): + resultData["message"] = result["errorMessage"] + else: + resultData["status"] = "ok" + if "orders" in result.keys(): + resultData["data"]["orders"] = result["orders"] + if resultData["status"] == "ok": + resultData["data"]["startDate"] = startDate.strftime("%Y-%m-%d") + resultData["data"]["endDate"] = endDate.strftime("%Y-%m-%d") + resultData["data"]["users"] = await UserHandler.getAll() + resultData["data"]["fullAccess"] = fullAccess case _: pass return resultData diff --git a/api/routers/__pycache__/__init__.cpython-313.pyc b/api/routers/__pycache__/__init__.cpython-313.pyc index 3eb2c86..2f92d94 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__/orders.cpython-313.pyc b/api/routers/__pycache__/orders.cpython-313.pyc new file mode 100644 index 0000000..64492da Binary files /dev/null and b/api/routers/__pycache__/orders.cpython-313.pyc differ diff --git a/api/routers/orders.py b/api/routers/orders.py new file mode 100644 index 0000000..d8ebb46 --- /dev/null +++ b/api/routers/orders.py @@ -0,0 +1,42 @@ +from fastapi import APIRouter, Depends +from db.handlers.orders import OrdersHandler +from utils import requestDict, logger + + +router = APIRouter() + + +@router.get("/") +async def get_requests(): + return await OrdersHandler.countNew() + + +@router.post("/") +async def post_requests( + reqData: dict = Depends(requestDict), +): + logger.info("Обновление заказа") + response = {"status": "error"} + orderId = reqData.get("body").get("orderId") + userId = reqData.get("body").get("userId") + status = reqData.get("body").get("status") + comment = reqData.get("body").get("comment") + if (orderId is not None and userId is not None) and ( + status is not None or comment is not None + ): + result = await OrdersHandler.update(int(orderId), userId, status, comment) + if "errorMessage" in result.keys(): + response["message"] = result["errorMessage"] + else: + response["status"] = "ok" + if "orders" in result.keys(): + response["data"] = result["orders"] + return response + + +@router.post("/new", summary="Добавление нового заказа") +async def add_order(reqData: dict = Depends(requestDict)): + logger.info(f"Добавление нового заказа") + userId = reqData.get("body").get("userId") + customer_comment = reqData.get("body").get("customer_comment") + return await OrdersHandler.new(userId, customer_comment) diff --git a/api/static/css/layout.css b/api/static/css/layout.css index f090e7c..5f86c77 100644 --- a/api/static/css/layout.css +++ b/api/static/css/layout.css @@ -2,6 +2,7 @@ body { background-image: url("../images/background.svg"); background-repeat: repeat; background-size: 512px auto; + background-attachment: fixed; } .loader-bg { diff --git a/api/static/js/index.js b/api/static/js/index.js index 803ce47..10ea46a 100644 --- a/api/static/js/index.js +++ b/api/static/js/index.js @@ -89,7 +89,11 @@ async function checkActiveUser() { } } -async function openTab(event, tabId) { +async function openTab(event, tabId, autoLoad = false) { + const activeTab = loadFromStorage('tab'); + if (activeTab && activeTab.tabId === tabId && !autoLoad) { + return; + } // Убираем активный класс со всех вкладок и кнопок document.querySelectorAll('.tab-nav-btn').forEach(btn => { btn.classList.remove('active'); @@ -136,6 +140,12 @@ function prepareTabs() { }; } + tabsData['orders'] = { + title: 'Заказы', + icon: 'bi-basket', + description: 'Управление заказами' + }; + if (accessData.view_requests) { tabsData['jurnal_toolkits'] = { title: 'Журнал перемещений', @@ -170,11 +180,11 @@ function prepareTabs() {
+ + + +
+
+
+ + + + +
+
+ ${!fullAccess ? ` +
+ +
+ ` : ` +
+
+ + + + +
+
+ ` } +
+ + + + +
+ +
+ + +
+
+
+ + Дата начала: + + +
+
+ + Дата окончания: + + +
+
+
+ + + + `; + + // Модальное окно для нового заказа + if (!fullAccess) { + document.body.insertAdjacentHTML('beforeend', ` + + `); + } + + + + // Инициализация фильтров + if (fullAccess) { + document.getElementById(`${tabId}-customer-filter`).value = currentFilters.customer; + } + document.getElementById(`${tabId}-status-filter`).value = currentFilters.status; + document.getElementById(`${tabId}-search-filter`).value = currentFilters.search; + + // Обработчики событий + const filterResetBtn = document.getElementById(`${tabId}-filter-reset-btn`); + filterResetBtn.addEventListener('click', () => { + currentFilters = { + customer: 'all', + status: 'all', + search: '' + }; + if (fullAccess) { + document.getElementById(`${tabId}-customer-filter`).value = currentFilters.customer; + } + document.getElementById(`${tabId}-status-filter`).value = currentFilters.status; + document.getElementById(`${tabId}-search-filter`).value = currentFilters.search; + saveToStorage(tabId, currentFilters); + renderOrdersTable(); + }); + + // Дата фильтры + const startDateInput = document.getElementById(`${tabId}-date-from`); + const endDateInput = document.getElementById(`${tabId}-date-to`); + startDateInput.value = startDate; + endDateInput.value = endDate; + + const refreshDateBtn = document.getElementById(`${tabId}-date-update-btn`); + refreshDateBtn.addEventListener('click', async () => { + const newStartDate = startDateInput.value; + const newEndDate = endDateInput.value; + if (newStartDate && newEndDate) { + tabContent.innerHTML = ` + + `; + + const cookiesData = { userData, accessData }; + const newPeriodData = await apiRequest('/', { + tabId: tabId, + startDate: newStartDate, + endDate: newEndDate, + cookiesData + }); + if (newPeriodData.status === 'ok') { + renderOrdersTab(tabId, { + ...tabData, + ...newPeriodData.data, + startDate: newStartDate, + endDate: newEndDate, + fullAccess + }); + await checkNewOrders(); + } + } + }); + + // Кнопка нового заказа + if (!fullAccess) { + const newOrderBtn = document.getElementById(`${tabId}-new-order-btn`); + newOrderBtn.addEventListener('click', () => { + const modal = new bootstrap.Modal(document.getElementById(`${tabId}-new-order-modal`)); + modal.show(); + }); + + // Отправка нового заказа + const orderSubmitBtn = document.getElementById(`${tabId}-order-submit-btn`); + orderSubmitBtn.onclick = async () => { + if (orderSubmitBtn.disabled) return; + + orderSubmitBtn.disabled = true; + + const description = document.getElementById(`${tabId}-order-description`).value; + if (!description.trim()) { + showInfo('Пожалуйста, введите описание заказа', 'warning'); + orderSubmitBtn.disabled = false; + return; + } + + try { + const result = await apiRequest('/orders/new', { + userId: userData.id, + customer_comment: description + }); + + if (result.status === 'ok') { + const modal = bootstrap.Modal.getInstance( + document.getElementById(`${tabId}-new-order-modal`) + ); + modal.hide(); + document.getElementById(`${tabId}-order-description`).value = ''; + refreshDateBtn.click(); + } else { + showInfo(result.message || 'Ошибка при создании заказа', 'danger'); + } + } finally { + orderSubmitBtn.disabled = false; + } + }; + } + + + if (orders.length === 0) { + tabContent.innerHTML = ` + + `; + return; + } + + // Рендерим таблицу + tabContent.innerHTML = ` +
+
+
+ + + + + + + + + ${fullAccess ? '' : ''} + + + + + +
ЗаказчикОписание заказаСтатусИсполнительКомментарий исполнителя⚙️
+
+ +
+
+ `; + + // Функция фильтрации + function filterOrders() { + let filtered = orders; + + // Фильтр по заказчику + if (fullAccess && currentFilters.customer !== 'all') { + filtered = filtered.filter(o => o.customer_id == currentFilters.customer); + } + + // Фильтр по статусу + if (currentFilters.status !== 'all') { + filtered = filtered.filter(o => o.status === currentFilters.status); + } + + // Поиск + if (currentFilters.search.trim()) { + const searchTerm = currentFilters.search.toLowerCase(); + filtered = filtered.filter(o => { + return ( + (userMap[o.customer_id] || '').toLowerCase().includes(searchTerm) || + o.customer_comment.toLowerCase().includes(searchTerm) || + (userMap[o.executor_id] || '').toLowerCase().includes(searchTerm) || + (o.executor_comment || '').toLowerCase().includes(searchTerm) || + o.status.toLowerCase().includes(searchTerm) + ); + }); + } + + return filtered; + } + + // Функция рендеринга таблицы + function renderOrdersTable() { + const tbody = document.getElementById(`${tabId}-orders-body`); + const noOrdersDiv = document.getElementById(`${tabId}-no-orders`); + const filteredOrders = filterOrders(); + + if (filteredOrders.length === 0) { + tbody.innerHTML = ''; + noOrdersDiv.style.display = 'block'; + return; + } + + noOrdersDiv.style.display = 'none'; + + tbody.innerHTML = filteredOrders.map(order => { + // Определяем класс для статуса + const statusClass = { + new: 'warning', + working: 'primary', + complete: 'success', + cancelled: 'danger' + }[order.status] || 'secondary'; + + // Определяем русское название статуса + const statusText = { + new: 'Новый', + working: 'В работе', + complete: 'Выполнен', + cancelled: 'Отменен' + }[order.status] || order.status; + + // Рендерим статус + let statusCell; + if (fullAccess && (order.status === 'new' || order.status === 'working')) { + statusCell = ` + + `; + } else { + statusCell = `${statusText}`; + } + + // Рендерим комментарий исполнителя + let commentCell; + if (fullAccess && (order.status === 'new' || order.status === 'working')) { + commentCell = ` + + `; + } else { + commentCell = `${order.executor_comment || 'Нет комментария'}`; + } + + return ` + + + ${userMap[order.customer_id] || `Пользователь ${order.customer_id}`}
+ ${order.created_at} + + ${order.customer_comment} + + ${statusCell}
+ ${order.updated_at} + + ${order.executor_id ? (userMap[order.executor_id] || `Пользователь ${order.executor_id}`) : '-'} + ${commentCell} + ${fullAccess ? ` + + ${fullAccess && (order.status === 'new' || order.status === 'working') ? ` + + ` : '☑️'} + + `: ''} + + `; + }).join(''); + + function updateRowState(orderId) { + const original = originalOrders[orderId]; + const current = changedOrders[orderId]; + + const hasChanges = + current && + ( + (current.status !== undefined && current.status !== original.status) || + (current.executor_comment !== undefined && current.executor_comment !== original.executor_comment) + ); + + const saveBtn = document.querySelector( + `.save-row-btn[data-order-id="${orderId}"]` + ); + + if (saveBtn) { + saveBtn.disabled = !hasChanges; + } + } + + // Добавляем обработчики изменений + if (fullAccess) { + document.querySelectorAll(`.order-status`).forEach(select => { + select.addEventListener('change', function () { + const orderId = this.dataset.orderId; + + changedOrders[orderId] = { + ...changedOrders[orderId], + status: this.value + }; + + updateRowState(orderId); + }); + }); + + document.querySelectorAll(`.executor-comment`).forEach(textarea => { + textarea.addEventListener('input', function () { + const orderId = this.dataset.orderId; + + changedOrders[orderId] = { + ...changedOrders[orderId], + executor_comment: this.value + }; + + updateRowState(orderId); + }); + }); + document.querySelectorAll('.save-row-btn').forEach(btn => { + btn.addEventListener('click', async function () { + const orderId = this.dataset.orderId; + const original = originalOrders[orderId]; + const current = changedOrders[orderId]; + + if (!current) return; + + // Формируем diff + const payload = { orderId, userId: userData.id }; + + if (current.status !== undefined && current.status !== original.status) { + payload.status = current.status; + } + + if ( + current.executor_comment !== undefined && + current.executor_comment !== original.executor_comment + ) { + payload.comment = current.executor_comment; + } + + if (Object.keys(payload).length === 2) return; + + this.disabled = true; + + const result = await apiRequest('/orders/', payload); + + if (result.status === 'ok') { + // Обновляем оригинал + originalOrders[orderId] = { + status: payload.status ?? original.status, + executor_comment: payload.comment ?? original.executor_comment + }; + + delete changedOrders[orderId]; + + if (payload.status && original.status === 'new') { + await checkNewOrders(); + } + + this.innerHTML = ''; + setTimeout(() => { + this.innerHTML = ''; + }, 1500); + + if (originalOrders[orderId].status === 'complete' || originalOrders[orderId].status === 'cancelled') { + const foundOrder = orders.find(order => order.id === Number(orderId)); + if (foundOrder) { + foundOrder.executor_id = userData.id; + foundOrder.executor_comment = originalOrders[orderId].executor_comment; + foundOrder.status = current.status; + } + renderOrdersTable(); + } + } else { + showInfo('Ошибка сохранения', 'danger'); + this.disabled = false; + } + }); + }); + } + } + + // Обработчики фильтров + if (fullAccess) { + document.getElementById(`${tabId}-customer-filter`).addEventListener('change', function () { + currentFilters.customer = this.value; + saveToStorage(tabId, currentFilters); + renderOrdersTable(); + }); + } + + document.getElementById(`${tabId}-status-filter`).addEventListener('change', function () { + currentFilters.status = this.value; + saveToStorage(tabId, currentFilters); + renderOrdersTable(); + }); + + document.getElementById(`${tabId}-search-filter`).addEventListener('input', function () { + currentFilters.search = this.value; + saveToStorage(tabId, currentFilters); + renderOrdersTable(); + }); + + // Первоначальный рендеринг + renderOrdersTable(); +} + function renderJurnalToolkitsTab(tabId, tabData) { const tabContent = document.getElementById(`${tabId}-tab-content`); const tabOptionalContent = document.getElementById(`${tabId}-tab-optional-content`); @@ -7446,6 +8031,34 @@ function renderUsersTab(tabId, tabData) { renderUsersCards(); } +async function checkNewOrders() { + const result = await apiRequest('/orders/', {}, 'GET'); + + if (result && result.orders > 0) { + const ordersTabBtn = document.getElementById('orders-tab'); + + // Удаляем старый бейдж, если есть + const oldBadge = ordersTabBtn.querySelector('.orders-badge'); + if (oldBadge) oldBadge.remove(); + + const newOrdersBadge = document.createElement('span'); + newOrdersBadge.className = + 'badge rounded-pill bg-danger position-absolute orders-badge'; + newOrdersBadge.textContent = result.orders; + + // Позиция: правый верх + newOrdersBadge.style.top = '6px'; + newOrdersBadge.style.right = '8px'; + + ordersTabBtn.appendChild(newOrdersBadge); + } + if (result && result.orders == 0) { + const ordersTabBtn = document.getElementById('orders-tab'); + const oldBadge = ordersTabBtn.querySelector('.orders-badge'); + if (oldBadge) oldBadge.remove(); + } +} + document.addEventListener('DOMContentLoaded', async () => { await getCookieData(); @@ -7456,6 +8069,7 @@ document.addEventListener('DOMContentLoaded', async () => { } prepareTabs(); + await checkNewOrders(); }); window.openTab = openTab; \ No newline at end of file diff --git a/db/__pycache__/initialize.cpython-313.pyc b/db/__pycache__/initialize.cpython-313.pyc index e3e3afd..4b288ab 100644 Binary files a/db/__pycache__/initialize.cpython-313.pyc and b/db/__pycache__/initialize.cpython-313.pyc differ diff --git a/db/handlers/__pycache__/orders.cpython-313.pyc b/db/handlers/__pycache__/orders.cpython-313.pyc new file mode 100644 index 0000000..08700df Binary files /dev/null and b/db/handlers/__pycache__/orders.cpython-313.pyc differ diff --git a/db/handlers/__pycache__/toolkit.cpython-313.pyc b/db/handlers/__pycache__/toolkit.cpython-313.pyc index 2a75d44..e54d0d7 100644 Binary files a/db/handlers/__pycache__/toolkit.cpython-313.pyc and b/db/handlers/__pycache__/toolkit.cpython-313.pyc differ diff --git a/db/handlers/orders.py b/db/handlers/orders.py new file mode 100644 index 0000000..d0e9e1b --- /dev/null +++ b/db/handlers/orders.py @@ -0,0 +1,98 @@ +from datetime import date, datetime, time +from sqlalchemy import func, select +from db import CRUD +from db.schemas.orders import Orders +from utils.loggers import logger + + +class OrdersHandler: + @staticmethod + async def new(user_id: int, order: str): + try: + await Orders(customer_id=user_id, customer_comment=order).save() + except Exception as e: + logger.error(f"Ошибка создания заказа: {str(e)}") + return {"errorMessage": f"Ошибка создания заказа: {str(e)}"} + return {"status": "ok"} + + @staticmethod + async def get_all_by_consumer( + user_id: int, startDate: date, endDate: date, toDict: bool = True + ): + try: + startDate = datetime.combine(startDate, time.min) + endDate = datetime.combine(endDate, time.max) + query = ( + select(Orders) + .where( + Orders.customer_id == user_id, + Orders.created_at.between(startDate, endDate), + ) + .order_by(Orders.created_at.asc()) + ) + orders = await CRUD.read(query, True) + ordresList = [order.toDict() for order in orders] if toDict else orders + return {"orders": ordresList} + except Exception as e: + logger.error(f"Ошибка получения заказов: {str(e)}") + return {"errorMessage": f"Ошибка получения заказов: {str(e)}"} + + @staticmethod + async def get_all(startDate: date, endDate: date, toDict: bool = True): + try: + startDate = datetime.combine(startDate, time.min) + endDate = datetime.combine(endDate, time.max) + query = ( + select(Orders) + .where( + Orders.created_at.between(startDate, endDate), + ) + .order_by(Orders.created_at.asc()) + ) + orders = await CRUD.read(query, True) + ordresList = [order.toDict() for order in orders] if toDict else orders + return {"orders": ordresList} + except Exception as e: + logger.error(f"Ошибка получения заказов: {str(e)}") + return {"errorMessage": f"Ошибка получения заказов: {str(e)}"} + + @staticmethod + async def countNew(): + try: + query = select(func.count(Orders.id)).where(Orders.status == "new") + return {"orders": await CRUD.read(query)} + except Exception as e: + logger.error(f"Ошибка получения количества заказов: {str(e)}") + return {"errorMessage": f"Ошибка получения количества заказов: {str(e)}"} + + @staticmethod + async def get(order_id: int, toDict: bool = False): + try: + order = await CRUD.read(select(Orders).where(Orders.id == order_id)) + return order.toDict() if toDict else order + except Exception as e: + logger.error(f"Ошибка получения заказа: {str(e)}") + return {"errorMessage": f"Ошибка получения заказа: {str(e)}"} + + @staticmethod + async def update( + order_id: int, user_id: int, status: str = None, comment: str = None + ): + try: + if status is None and comment is None: + logger.error("Не указан статус и комментарий") + return {"errorMessage": "Не указан статус и комментарий"} + order = await OrdersHandler.get(order_id) + if not order or isinstance(order, dict): + logger.error("Заказ не найден") + return {"errorMessage": "Заказ не найден"} + changeData = {"executor_id": user_id} + if status: + changeData["status"] = status + if comment: + changeData["executor_comment"] = comment + await order.edit(**changeData) + except Exception as e: + logger.error(f"Ошибка обновления заказа: {str(e)}") + return {"errorMessage": f"Ошибка обновления заказа: {str(e)}"} + return {"status": "ok"} diff --git a/db/handlers/toolkit.py b/db/handlers/toolkit.py index a3e6d72..7d395c9 100644 --- a/db/handlers/toolkit.py +++ b/db/handlers/toolkit.py @@ -25,6 +25,8 @@ def handleToolkitImage(imageData, title: str): class ToolkitHandler: + + @staticmethod async def add(toolkitData: dict, user_id: int = None): title = toolkitData.get("title", None) if not title: @@ -71,6 +73,7 @@ class ToolkitHandler: await ServiceRecordsHandler.add(user_id, {"Добавлен инструмент": toolkitData}) return newToolkit.toDict() + @staticmethod async def updateMovindDate(toolkitId: int): toolkit = await CRUD.read(select(Toolkit).where(Toolkit.id == toolkitId)) if not toolkit: @@ -82,6 +85,7 @@ class ToolkitHandler: return False return True + @staticmethod async def updateRefillDate(toolkitId: int): logger.info(f"Обновление даты пополнения инструмента {toolkitId}...") toolkit = await CRUD.read(select(Toolkit).where(Toolkit.id == toolkitId)) @@ -94,12 +98,14 @@ class ToolkitHandler: return False return True + @staticmethod async def hideToolkit(userId: int, toolkitId: int, hidden: bool = True): logger.info( f"{'Скрытие' if hidden else 'Отображение'} инструмента {toolkitId}..." ) return await ToolkitHandler.edit(userId, id=toolkitId, hidden=hidden) + @staticmethod async def edit(user_id: int, **kwargs): title = kwargs.get("title", None) toolkitId = kwargs.pop("id") @@ -165,11 +171,13 @@ class ToolkitHandler: ) return editedToolkit.toDict() + @staticmethod async def getAll(): query = select(Toolkit) toolkits = await CRUD.read(query, True) return [toolkit.toDict() for toolkit in toolkits] if toolkits else [] + @staticmethod async def get(toolkitId: int): query = select(Toolkit).where(Toolkit.id == toolkitId) toolkit = await CRUD.read(query) @@ -183,16 +191,19 @@ class ToolkitHandler: logger.info(data) return data + @staticmethod async def getSeveral(toolkitIds: list[int]) -> list[dict]: query = select(Toolkit).where(Toolkit.id.in_(toolkitIds)) toolkits = await CRUD.read(query, True) return [toolkit.toDict() for toolkit in toolkits] if toolkits else [] + @staticmethod async def checkCatogoryUse(category_id: int): query = select(Toolkit).where(Toolkit.category_id == category_id) toolkit = await CRUD.read(query) return True if toolkit else False + @staticmethod async def delete(toolkitId: int, user_id: int = None): movements = await StockHandler.checkToolkitExists(toolkitId) if movements: @@ -217,6 +228,7 @@ class ToolkitHandler: ) return {"status": "ok"} if result else {"errorMessage": "Инструмент не удален"} + @staticmethod async def addComment(toolkitId: int, user_id: int, comment: str): logger.info(f"Добавление комментария к инструменту {toolkitId}...") logger.info(f"Комментарий: {comment}") @@ -236,6 +248,7 @@ class ToolkitHandler: logger.info(f"Комментарий к инструменту {toolkit.title} успешно добавлен") return {"status": "ok"} + @staticmethod async def initialize(): from .categories import CategoryHandler diff --git a/db/schemas/__init__.py b/db/schemas/__init__.py index adefaaf..bcfcb81 100644 --- a/db/schemas/__init__.py +++ b/db/schemas/__init__.py @@ -4,13 +4,17 @@ from .toolkit import * from .categories import * from .toolbox import * from .stock import * - +from .records import * +from .orders import * __all__ = [ "User", - "Access", - "Toolbox", - "Category", - "Stock", + "AccessLevel", "Toolkit", + "Category", + "Toolbox", + "Stock", + "StocksRecords", + "ServicesRecords", + "Orders", ] diff --git a/db/schemas/__pycache__/__init__.cpython-313.pyc b/db/schemas/__pycache__/__init__.cpython-313.pyc index 651e7d6..364a38c 100644 Binary files a/db/schemas/__pycache__/__init__.cpython-313.pyc and b/db/schemas/__pycache__/__init__.cpython-313.pyc differ diff --git a/db/schemas/__pycache__/orders.cpython-313.pyc b/db/schemas/__pycache__/orders.cpython-313.pyc new file mode 100644 index 0000000..e322b0f Binary files /dev/null and b/db/schemas/__pycache__/orders.cpython-313.pyc differ diff --git a/db/schemas/orders.py b/db/schemas/orders.py new file mode 100644 index 0000000..d32d87e --- /dev/null +++ b/db/schemas/orders.py @@ -0,0 +1,30 @@ +from datetime import datetime +from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text +from db import CRUD, Base +import utils + + +class Orders(Base): + __tablename__ = "orders" + + id = Column(Integer, primary_key=True, autoincrement=True) + customer_id = Column(Integer, ForeignKey("users.id")) + executor_id = Column(Integer, ForeignKey("users.id"), nullable=True) + customer_comment = Column(Text, nullable=False) + executor_comment = Column(String, nullable=True) + status = Column(String, default="new") + 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) + + def toDict(self): + return utils.toDict(self) + + async def save(self): + return await CRUD.create(self, refresh=True) + + async def edit(self, **kwargs): + return await CRUD.update(Orders, self.id, **kwargs)