заказы
This commit is contained in:
+48
-26
@@ -1,15 +1,17 @@
|
|||||||
from datetime import date, datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse
|
||||||
|
|
||||||
from db.handlers.access import AccessLevelHandler
|
from db.handlers.access import AccessLevelHandler
|
||||||
from db.handlers.categories import CategoryHandler
|
from db.handlers.categories import CategoryHandler
|
||||||
|
from db.handlers.orders import OrdersHandler
|
||||||
from utils import render, requestDict, logger
|
from utils import render, requestDict, logger
|
||||||
from .user import router as user
|
from .user import router as user
|
||||||
from .stocks import router as stocks
|
from .stocks import router as stocks
|
||||||
from .toolbox import router as toolbox
|
from .toolbox import router as toolbox
|
||||||
from .toolkit import router as toolkit
|
from .toolkit import router as toolkit
|
||||||
from .records import router as records
|
from .records import router as records
|
||||||
|
from .orders import router as orders
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
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(toolbox, prefix="/toolbox", tags=["toolbox"])
|
||||||
router.include_router(toolkit, prefix="/toolkit", tags=["toolkit"])
|
router.include_router(toolkit, prefix="/toolkit", tags=["toolkit"])
|
||||||
router.include_router(records, prefix="/records", tags=["records"])
|
router.include_router(records, prefix="/records", tags=["records"])
|
||||||
|
router.include_router(orders, prefix="/orders", tags=["orders"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
@@ -52,6 +55,21 @@ async def post_requests(
|
|||||||
from db.handlers.toolkit import ToolkitHandler
|
from db.handlers.toolkit import ToolkitHandler
|
||||||
from db.handlers.user import UserHandler
|
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 = {
|
reqData = {
|
||||||
"tab": request_data.get("body").get("tabId"),
|
"tab": request_data.get("body").get("tabId"),
|
||||||
}
|
}
|
||||||
@@ -105,18 +123,7 @@ async def post_requests(
|
|||||||
"categories": categories,
|
"categories": categories,
|
||||||
}
|
}
|
||||||
case "jurnal_toolkits":
|
case "jurnal_toolkits":
|
||||||
startDate = request_data.get("body").get(
|
startDate, endDate = getDates(request_data.get("body"))
|
||||||
"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
|
|
||||||
|
|
||||||
jurnal_toolkits = await StocksRecordsHandler.getLogs(startDate, endDate)
|
jurnal_toolkits = await StocksRecordsHandler.getLogs(startDate, endDate)
|
||||||
if isinstance(jurnal_toolkits, list):
|
if isinstance(jurnal_toolkits, list):
|
||||||
@@ -147,18 +154,7 @@ async def post_requests(
|
|||||||
"endDate": endDate.strftime("%Y-%m-%d"),
|
"endDate": endDate.strftime("%Y-%m-%d"),
|
||||||
}
|
}
|
||||||
case "jurnal_service":
|
case "jurnal_service":
|
||||||
startDate = request_data.get("body").get(
|
startDate, endDate = getDates(request_data.get("body"))
|
||||||
"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
|
|
||||||
|
|
||||||
jurnal_service = await ServiceRecordsHandler.getLogs(startDate, endDate)
|
jurnal_service = await ServiceRecordsHandler.getLogs(startDate, endDate)
|
||||||
if isinstance(jurnal_service, list):
|
if isinstance(jurnal_service, list):
|
||||||
@@ -199,7 +195,33 @@ async def post_requests(
|
|||||||
"users": users,
|
"users": users,
|
||||||
"accessLevels": accessLevels,
|
"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 _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return resultData
|
return resultData
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||||
@@ -2,6 +2,7 @@ body {
|
|||||||
background-image: url("../images/background.svg");
|
background-image: url("../images/background.svg");
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
background-size: 512px auto;
|
background-size: 512px auto;
|
||||||
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader-bg {
|
.loader-bg {
|
||||||
|
|||||||
+621
-7
@@ -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 => {
|
document.querySelectorAll('.tab-nav-btn').forEach(btn => {
|
||||||
btn.classList.remove('active');
|
btn.classList.remove('active');
|
||||||
@@ -136,6 +140,12 @@ function prepareTabs() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tabsData['orders'] = {
|
||||||
|
title: 'Заказы',
|
||||||
|
icon: 'bi-basket',
|
||||||
|
description: 'Управление заказами'
|
||||||
|
};
|
||||||
|
|
||||||
if (accessData.view_requests) {
|
if (accessData.view_requests) {
|
||||||
tabsData['jurnal_toolkits'] = {
|
tabsData['jurnal_toolkits'] = {
|
||||||
title: 'Журнал перемещений',
|
title: 'Журнал перемещений',
|
||||||
@@ -170,11 +180,11 @@ function prepareTabs() {
|
|||||||
<div id="mainTabsNavWrapper">
|
<div id="mainTabsNavWrapper">
|
||||||
<nav class="nav nav-pills gap-2" id="mainTabsNav" role="tablist">
|
<nav class="nav nav-pills gap-2" id="mainTabsNav" role="tablist">
|
||||||
${Object.entries(tabsData).map(([tabId, tabData], index) => `
|
${Object.entries(tabsData).map(([tabId, tabData], index) => `
|
||||||
<button class="nav-link tab-nav-btn d-flex flex-column align-items-center justify-content-center py-3 px-2"
|
<button class="nav-link tab-nav-btn d-flex flex-column align-items-center justify-content-center py-3 px-2 position-relative"
|
||||||
id="${tabId}-tab"
|
id="${tabId}-tab"
|
||||||
role="tab"
|
role="tab"
|
||||||
onclick="openTab(event, '${tabId}')"
|
onclick="openTab(event, '${tabId}')"
|
||||||
style="min-width: 120px; transition: all 0.3s ease;">
|
style="min-width: 120px; transition: all 0.3s ease;">
|
||||||
<i class="${tabData.icon} nav-icon fs-3 mb-2 text-muted" style="transition: all 0.3s ease;"></i>
|
<i class="${tabData.icon} nav-icon fs-3 mb-2 text-muted" style="transition: all 0.3s ease;"></i>
|
||||||
<span class="nav-title fw-medium" style="font-size: 0.9rem;">${tabData.title}</span>
|
<span class="nav-title fw-medium" style="font-size: 0.9rem;">${tabData.title}</span>
|
||||||
<div class="nav-indicator"></div>
|
<div class="nav-indicator"></div>
|
||||||
@@ -240,7 +250,7 @@ function prepareTabs() {
|
|||||||
const activeTabId = activeTabData.tabId;
|
const activeTabId = activeTabData.tabId;
|
||||||
const tabBtn = document.getElementById(`${activeTabId}-tab`);
|
const tabBtn = document.getElementById(`${activeTabId}-tab`);
|
||||||
if (tabBtn) {
|
if (tabBtn) {
|
||||||
openTab({ currentTarget: tabBtn }, activeTabId);
|
openTab({ currentTarget: tabBtn }, activeTabId, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,6 +290,9 @@ function fillTab(tabId, tabData) {
|
|||||||
case 'toolkits':
|
case 'toolkits':
|
||||||
renderToolkitsTab(tabId, tabData.toolkits, tabData.categories);
|
renderToolkitsTab(tabId, tabData.toolkits, tabData.categories);
|
||||||
break;
|
break;
|
||||||
|
case 'orders':
|
||||||
|
renderOrdersTab(tabId, tabData);
|
||||||
|
break;
|
||||||
case 'jurnal_toolkits':
|
case 'jurnal_toolkits':
|
||||||
renderJurnalToolkitsTab(tabId, tabData);
|
renderJurnalToolkitsTab(tabId, tabData);
|
||||||
break;
|
break;
|
||||||
@@ -5986,6 +5999,578 @@ function renderRequestsTab(tabId, tabData) {
|
|||||||
renderRequestsTable();
|
renderRequestsTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderOrdersTab(tabId, tabData) {
|
||||||
|
|
||||||
|
const tabContent = document.getElementById(`${tabId}-tab-content`);
|
||||||
|
const tabOptionalContent = document.getElementById(`${tabId}-tab-optional-content`);
|
||||||
|
|
||||||
|
const { orders, users, startDate, endDate, fullAccess } = tabData;
|
||||||
|
|
||||||
|
// Создаем мапу пользователей
|
||||||
|
const userMap = {};
|
||||||
|
users.forEach(user => {
|
||||||
|
userMap[user.id] = user.username;
|
||||||
|
});
|
||||||
|
|
||||||
|
// const ordersStatuses = {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Сохраненные фильтры
|
||||||
|
const savedFilters = loadFromStorage(tabId);
|
||||||
|
let currentFilters = {
|
||||||
|
customer: savedFilters?.customer || 'all',
|
||||||
|
status: savedFilters?.status || 'all',
|
||||||
|
search: savedFilters?.search || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
// Хранилище измененных данных
|
||||||
|
const changedOrders = {};
|
||||||
|
const statusesList = []
|
||||||
|
const statusesMap = {
|
||||||
|
'new': 'Новый',
|
||||||
|
'working': 'В работе',
|
||||||
|
'complete': 'Выполнен',
|
||||||
|
'cancelled': 'Отменен',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Оригинальные данные заказов (для сравнения)
|
||||||
|
const originalOrders = {};
|
||||||
|
orders.forEach(o => {
|
||||||
|
originalOrders[o.id] = {
|
||||||
|
status: o.status,
|
||||||
|
executor_comment: o.executor_comment || ''
|
||||||
|
};
|
||||||
|
if (!statusesList.includes(o.status)) {
|
||||||
|
statusesList.push(o.status)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Рендерим дополнительный контейнер с фильтрами и кнопками
|
||||||
|
tabOptionalContent.innerHTML = `
|
||||||
|
<div class="row align-items-center mb-1">
|
||||||
|
<!-- Левая часть с фильтрами -->
|
||||||
|
<div class="col-12 col-md-6 mb-2 mb-md-0">
|
||||||
|
<div class="row align-items-center mb-2">
|
||||||
|
<!-- Поиск -->
|
||||||
|
<div class="col-12 col-md-9">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<i class="bi bi-search"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
id="${tabId}-search-filter"
|
||||||
|
placeholder="Поиск по таблице..."
|
||||||
|
value="${currentFilters.search}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Кнопка сброса -->
|
||||||
|
<div class="col-12 col-md-3">
|
||||||
|
<button class="btn btn-outline-secondary" id="${tabId}-filter-reset-btn">
|
||||||
|
<i class="bi bi-x-circle me-1"></i>Сброс
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Фильтр по статусу -->
|
||||||
|
<div class="row g-2">
|
||||||
|
<div class="col-12 col-md-5">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<i class="bi bi-list-check"></i>
|
||||||
|
</span>
|
||||||
|
<select class="form-select" id="${tabId}-status-filter">
|
||||||
|
<option value="all">Все статусы</option>
|
||||||
|
${statusesList.map(status => `<option value="${status}">${statusesMap[status] || status}</option>`).join('')}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${!fullAccess ? `
|
||||||
|
<div class="col-12 col-md-7">
|
||||||
|
<button class="btn btn-primary" id="${tabId}-new-order-btn">
|
||||||
|
<i class="bi bi-plus-circle me-1"></i>Оформить заказ
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
` : `
|
||||||
|
<div class="col-12 col-md-7">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<i class="bi bi-person"></i>
|
||||||
|
</span>
|
||||||
|
<select class="form-select" id="${tabId}-customer-filter">
|
||||||
|
<option value="all">Все заказчики</option>
|
||||||
|
${[...new Set(orders.map(o => o.customer_id))].map(customerId => `
|
||||||
|
<option value="${customerId}">${userMap[customerId] || `Пользователь ${customerId}`}</option>
|
||||||
|
`).join('')}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-2">
|
||||||
|
<button class="btn btn-outline-primary" id="${tabId}-date-update-btn">
|
||||||
|
Обновить список заказов
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Правая часть с датами и кнопками -->
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<div class="d-flex flex-wrap gap-2 justify-content-md-end">
|
||||||
|
<div class="input-group date">
|
||||||
|
<span class="input-group-text" style="width: 170px;">
|
||||||
|
<i class="bi bi-calendar me-1"></i>Дата начала:
|
||||||
|
</span>
|
||||||
|
<input type="date" class="form-control" id="${tabId}-date-from">
|
||||||
|
</div>
|
||||||
|
<div class="input-group date">
|
||||||
|
<span class="input-group-text" style="width: 170px;">
|
||||||
|
<i class="bi bi-calendar me-1"></i>Дата окончания:
|
||||||
|
</span>
|
||||||
|
<input type="date" class="form-control" id="${tabId}-date-to">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Модальное окно для нового заказа
|
||||||
|
if (!fullAccess) {
|
||||||
|
document.body.insertAdjacentHTML('beforeend', `
|
||||||
|
<div class="modal fade" id="${tabId}-new-order-modal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Оформление нового заказа</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="${tabId}-order-description" class="form-label">Описание заказа</label>
|
||||||
|
<textarea class="form-control" id="${tabId}-order-description"
|
||||||
|
rows="4" placeholder="Опишите, что вам нужно..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="${tabId}-order-submit-btn">Заказать</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Инициализация фильтров
|
||||||
|
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 = `
|
||||||
|
<div class="alert alert-info m-4" role="alert">
|
||||||
|
<i class="bi bi-info-circle me-2"></i>
|
||||||
|
Загрузка данных...
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="alert alert-info m-4" role="alert">
|
||||||
|
<i class="bi bi-info-circle me-2"></i>
|
||||||
|
Нет заказов за период ${startDate} - ${endDate}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Рендерим таблицу
|
||||||
|
tabContent.innerHTML = `
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0" id="${tabId}-orders-table">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th width="150">Заказчик</th>
|
||||||
|
<th>Описание заказа</th>
|
||||||
|
<th width="120">Статус</th>
|
||||||
|
<th width="150">Исполнитель</th>
|
||||||
|
<th>Комментарий исполнителя</th>
|
||||||
|
${fullAccess ? '<th width="30">⚙️</th>' : ''}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="${tabId}-orders-body">
|
||||||
|
<!-- Заказы будут вставлены здесь -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-3 border-top" id="${tabId}-no-orders" style="display: none;">
|
||||||
|
<i class="bi bi-inbox fs-1 text-muted"></i>
|
||||||
|
<p class="text-muted mt-2">Нет заказов по выбранным фильтрам</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Функция фильтрации
|
||||||
|
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 = `
|
||||||
|
<select class="form-select form-select-sm order-status"
|
||||||
|
data-order-id="${order.id}"
|
||||||
|
style="min-width: 120px;">
|
||||||
|
<option value="new" ${order.status === 'new' ? 'selected' : ''} disabled>Новый</option>
|
||||||
|
<option value="working" ${order.status === 'working' ? 'selected' : ''}>В работе</option>
|
||||||
|
<option value="complete" ${order.status === 'complete' ? 'selected' : ''}>Выполнен</option>
|
||||||
|
<option value="cancelled" ${order.status === 'cancelled' ? 'selected' : ''}>Отменен</option>
|
||||||
|
</select>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
statusCell = `<span class="badge bg-${statusClass}">${statusText}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Рендерим комментарий исполнителя
|
||||||
|
let commentCell;
|
||||||
|
if (fullAccess && (order.status === 'new' || order.status === 'working')) {
|
||||||
|
commentCell = `
|
||||||
|
<textarea class="form-control form-control-sm executor-comment"
|
||||||
|
data-order-id="${order.id}"
|
||||||
|
rows="2"
|
||||||
|
placeholder="Введите комментарий...">${order.executor_comment || ''}</textarea>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
commentCell = `<small class="text-muted">${order.executor_comment || 'Нет комментария'}</small>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<tr data-order-id="${order.id}">
|
||||||
|
<td class="align-middle">
|
||||||
|
${userMap[order.customer_id] || `Пользователь ${order.customer_id}`}<br>
|
||||||
|
<small class="text-muted">${order.created_at}</small>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle"><small>${order.customer_comment}</small></td>
|
||||||
|
<td class="align-middle">
|
||||||
|
${statusCell}<br>
|
||||||
|
<small class="text-muted">${order.updated_at}</small>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle">${order.executor_id ? (userMap[order.executor_id] || `Пользователь ${order.executor_id}`) : '-'}</td>
|
||||||
|
<td class="align-middle">${commentCell}</td>
|
||||||
|
${fullAccess ? `
|
||||||
|
<td class="align-middle text-center">
|
||||||
|
${fullAccess && (order.status === 'new' || order.status === 'working') ? `
|
||||||
|
<button class="btn btn-sm btn-success save-row-btn"
|
||||||
|
data-order-id="${order.id}"
|
||||||
|
disabled>
|
||||||
|
<i class="bi bi-save"></i>
|
||||||
|
</button>
|
||||||
|
` : '☑️'}
|
||||||
|
</td>
|
||||||
|
`: ''}
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}).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 = '<i class="bi bi-check-circle"></i>';
|
||||||
|
setTimeout(() => {
|
||||||
|
this.innerHTML = '<i class="bi bi-save"></i>';
|
||||||
|
}, 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) {
|
function renderJurnalToolkitsTab(tabId, tabData) {
|
||||||
const tabContent = document.getElementById(`${tabId}-tab-content`);
|
const tabContent = document.getElementById(`${tabId}-tab-content`);
|
||||||
const tabOptionalContent = document.getElementById(`${tabId}-tab-optional-content`);
|
const tabOptionalContent = document.getElementById(`${tabId}-tab-optional-content`);
|
||||||
@@ -7446,6 +8031,34 @@ function renderUsersTab(tabId, tabData) {
|
|||||||
renderUsersCards();
|
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 () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
await getCookieData();
|
await getCookieData();
|
||||||
|
|
||||||
@@ -7456,6 +8069,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prepareTabs();
|
prepareTabs();
|
||||||
|
await checkNewOrders();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.openTab = openTab;
|
window.openTab = openTab;
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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"}
|
||||||
@@ -25,6 +25,8 @@ def handleToolkitImage(imageData, title: str):
|
|||||||
|
|
||||||
|
|
||||||
class ToolkitHandler:
|
class ToolkitHandler:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def add(toolkitData: dict, user_id: int = None):
|
async def add(toolkitData: dict, user_id: int = None):
|
||||||
title = toolkitData.get("title", None)
|
title = toolkitData.get("title", None)
|
||||||
if not title:
|
if not title:
|
||||||
@@ -71,6 +73,7 @@ class ToolkitHandler:
|
|||||||
await ServiceRecordsHandler.add(user_id, {"Добавлен инструмент": toolkitData})
|
await ServiceRecordsHandler.add(user_id, {"Добавлен инструмент": toolkitData})
|
||||||
return newToolkit.toDict()
|
return newToolkit.toDict()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def updateMovindDate(toolkitId: int):
|
async def updateMovindDate(toolkitId: int):
|
||||||
toolkit = await CRUD.read(select(Toolkit).where(Toolkit.id == toolkitId))
|
toolkit = await CRUD.read(select(Toolkit).where(Toolkit.id == toolkitId))
|
||||||
if not toolkit:
|
if not toolkit:
|
||||||
@@ -82,6 +85,7 @@ class ToolkitHandler:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def updateRefillDate(toolkitId: int):
|
async def updateRefillDate(toolkitId: int):
|
||||||
logger.info(f"Обновление даты пополнения инструмента {toolkitId}...")
|
logger.info(f"Обновление даты пополнения инструмента {toolkitId}...")
|
||||||
toolkit = await CRUD.read(select(Toolkit).where(Toolkit.id == toolkitId))
|
toolkit = await CRUD.read(select(Toolkit).where(Toolkit.id == toolkitId))
|
||||||
@@ -94,12 +98,14 @@ class ToolkitHandler:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def hideToolkit(userId: int, toolkitId: int, hidden: bool = True):
|
async def hideToolkit(userId: int, toolkitId: int, hidden: bool = True):
|
||||||
logger.info(
|
logger.info(
|
||||||
f"{'Скрытие' if hidden else 'Отображение'} инструмента {toolkitId}..."
|
f"{'Скрытие' if hidden else 'Отображение'} инструмента {toolkitId}..."
|
||||||
)
|
)
|
||||||
return await ToolkitHandler.edit(userId, id=toolkitId, hidden=hidden)
|
return await ToolkitHandler.edit(userId, id=toolkitId, hidden=hidden)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def edit(user_id: int, **kwargs):
|
async def edit(user_id: int, **kwargs):
|
||||||
title = kwargs.get("title", None)
|
title = kwargs.get("title", None)
|
||||||
toolkitId = kwargs.pop("id")
|
toolkitId = kwargs.pop("id")
|
||||||
@@ -165,11 +171,13 @@ class ToolkitHandler:
|
|||||||
)
|
)
|
||||||
return editedToolkit.toDict()
|
return editedToolkit.toDict()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def getAll():
|
async def getAll():
|
||||||
query = select(Toolkit)
|
query = select(Toolkit)
|
||||||
toolkits = await CRUD.read(query, True)
|
toolkits = await CRUD.read(query, True)
|
||||||
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
|
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def get(toolkitId: int):
|
async def get(toolkitId: int):
|
||||||
query = select(Toolkit).where(Toolkit.id == toolkitId)
|
query = select(Toolkit).where(Toolkit.id == toolkitId)
|
||||||
toolkit = await CRUD.read(query)
|
toolkit = await CRUD.read(query)
|
||||||
@@ -183,16 +191,19 @@ class ToolkitHandler:
|
|||||||
logger.info(data)
|
logger.info(data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def getSeveral(toolkitIds: list[int]) -> list[dict]:
|
async def getSeveral(toolkitIds: list[int]) -> list[dict]:
|
||||||
query = select(Toolkit).where(Toolkit.id.in_(toolkitIds))
|
query = select(Toolkit).where(Toolkit.id.in_(toolkitIds))
|
||||||
toolkits = await CRUD.read(query, True)
|
toolkits = await CRUD.read(query, True)
|
||||||
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
|
return [toolkit.toDict() for toolkit in toolkits] if toolkits else []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def checkCatogoryUse(category_id: int):
|
async def checkCatogoryUse(category_id: int):
|
||||||
query = select(Toolkit).where(Toolkit.category_id == category_id)
|
query = select(Toolkit).where(Toolkit.category_id == category_id)
|
||||||
toolkit = await CRUD.read(query)
|
toolkit = await CRUD.read(query)
|
||||||
return True if toolkit else False
|
return True if toolkit else False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def delete(toolkitId: int, user_id: int = None):
|
async def delete(toolkitId: int, user_id: int = None):
|
||||||
movements = await StockHandler.checkToolkitExists(toolkitId)
|
movements = await StockHandler.checkToolkitExists(toolkitId)
|
||||||
if movements:
|
if movements:
|
||||||
@@ -217,6 +228,7 @@ class ToolkitHandler:
|
|||||||
)
|
)
|
||||||
return {"status": "ok"} if result else {"errorMessage": "Инструмент не удален"}
|
return {"status": "ok"} if result else {"errorMessage": "Инструмент не удален"}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def addComment(toolkitId: int, user_id: int, comment: str):
|
async def addComment(toolkitId: int, user_id: int, comment: str):
|
||||||
logger.info(f"Добавление комментария к инструменту {toolkitId}...")
|
logger.info(f"Добавление комментария к инструменту {toolkitId}...")
|
||||||
logger.info(f"Комментарий: {comment}")
|
logger.info(f"Комментарий: {comment}")
|
||||||
@@ -236,6 +248,7 @@ class ToolkitHandler:
|
|||||||
logger.info(f"Комментарий к инструменту {toolkit.title} успешно добавлен")
|
logger.info(f"Комментарий к инструменту {toolkit.title} успешно добавлен")
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
async def initialize():
|
async def initialize():
|
||||||
from .categories import CategoryHandler
|
from .categories import CategoryHandler
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,17 @@ from .toolkit import *
|
|||||||
from .categories import *
|
from .categories import *
|
||||||
from .toolbox import *
|
from .toolbox import *
|
||||||
from .stock import *
|
from .stock import *
|
||||||
|
from .records import *
|
||||||
|
from .orders import *
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"User",
|
"User",
|
||||||
"Access",
|
"AccessLevel",
|
||||||
"Toolbox",
|
|
||||||
"Category",
|
|
||||||
"Stock",
|
|
||||||
"Toolkit",
|
"Toolkit",
|
||||||
|
"Category",
|
||||||
|
"Toolbox",
|
||||||
|
"Stock",
|
||||||
|
"StocksRecords",
|
||||||
|
"ServicesRecords",
|
||||||
|
"Orders",
|
||||||
]
|
]
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||||
Reference in New Issue
Block a user