кнопки пополнения и изменения положения в карточках и на складе

This commit is contained in:
2025-12-14 23:09:43 +03:00
parent a99d82c8e3
commit 1a677f9ef2
8 changed files with 219 additions and 6 deletions
Binary file not shown.
+42
View File
@@ -1,8 +1,12 @@
from fastapi import APIRouter, Depends
from sqlalchemy import select
from db import CRUD
from db.handlers.actions import StocksActions
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 db.schemas.stock import Placement
from utils import requestDict, logger
@@ -191,3 +195,41 @@ async def manage_toolkit(reqData: dict = Depends(requestDict)):
f"Управление инструментами ({action}) прошло {'успешно' if response.get('status') == 'ok' else 'неуспешно'}"
)
return response
@router.post("/quick_action", summary="Быстрое действие")
async def quick_action(reqData: dict = Depends(requestDict)):
logger.info(f"Быстрое действие")
response = {"status": "error"}
action = reqData.get("body").get("action")
toolboxId = int(reqData.get("body").get("toolboxId"))
toolkitId = int(reqData.get("body").get("toolkitId"))
userId = reqData.get("body").get("userId")
data = reqData.get("body").get("data")
match action:
case "fill":
toolkit = await StocksActions.registration(
toolkitId,
toolboxId,
userId,
int(data.get("amount")),
float(data.get("price")),
"",
data.get("reason"),
)
response = handleResult(toolkit, response)
case "move":
try:
placement = await CRUD.read(
select(Placement).where(
Placement.toolkit_id == toolkitId,
Placement.toolbox_id == toolboxId,
)
)
await placement.edit(data.get("location"))
response["status"] = "ok"
except Exception as e:
logger.error(e)
case _:
pass
return response
+3
View File
@@ -230,16 +230,19 @@ th[data-sort]:hover {
.badge.bg-danger { background-color: #dc3545 !important; }
/* Стили для кнопок действий в строке */
.quick-actions,
.action-buttons {
opacity: 0;
transition: opacity 0.2s;
}
tr:hover .quick-actions,
tr:hover .action-buttons {
opacity: 1;
}
/* Уменьшаем отступы у кнопок в строках */
.quick-actions .btn,
.action-buttons .btn {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
+164 -1
View File
@@ -2057,6 +2057,7 @@ async function selectToolbox(toolboxId) {
await loadToolboxContent(toolboxId);
}
async function loadToolboxContent(toolboxId) {
const contentContainer = document.querySelector('.toolbox-content-container');
@@ -2072,6 +2073,8 @@ async function loadToolboxContent(toolboxId) {
</div>
`;
try {
const resp = await apiRequest(`/stocks/`, { toolboxId });
if (resp.status === 'ok') {
@@ -3353,6 +3356,159 @@ function formatPrice(price) {
return parseFloat(price).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
}
function toolkitFillReplace(toolboxId, toolkitId, tabId) {
if (!accessData.tools_registration) return '';
const manageBtns = `
<div class="btn-group btn-group-sm quick-actions ms-2" role="group">
<button type="button"
class="btn btn-outline-success toolkit-action-btn"
title="Пополнить"
data-action="fill"
data-toolbox="${toolboxId}"
data-toolkit="${toolkitId}"
data-tab="${tabId}">
<i class="bi bi-plus-circle"></i>
</button>
<button type="button"
class="btn btn-outline-primary toolkit-action-btn"
title="Изменить расположение"
data-action="move"
data-toolbox="${toolboxId}"
data-toolkit="${toolkitId}"
data-tab="${tabId}">
<i class="bi bi-geo-alt"></i>
</button>
</div>
`;
// Навешиваем обработчики один раз
if (!document.body.dataset.toolkitHandlers) {
document.body.dataset.toolkitHandlers = 'true';
document.addEventListener('click', async (e) => {
const actionBtn = e.target.closest('.toolkit-action-btn');
if (!actionBtn) return;
const { action, toolbox, toolkit, tab } = actionBtn.dataset;
// Удаляем старую модалку если была
document.getElementById('toolkitDynamicModal')?.remove();
let modalHtml = '';
if (action === 'fill') {
modalHtml = `
<div class="modal fade" id="toolkitDynamicModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Пополнение инструмента</h5>
<button class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="toolkitModalForm">
<div class="mb-3">
<label class="form-label required">Количество</label>
<input type="number" class="form-control" name="amount" min="1" required>
</div>
<div class="mb-3">
<label class="form-label required">Цена</label>
<input type="number" class="form-control" name="price" min="1" required>
</div>
<div class="mb-3">
<label class="form-label required">Обоснование</label>
<textarea class="form-control" name="reason" rows="3" required></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button class="btn btn-success" id="toolkitModalSubmit">
Пополнить
</button>
</div>
</div>
</div>
</div>
`;
}
if (action === 'move') {
modalHtml = `
<div class="modal fade" id="toolkitDynamicModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Новое расположение</h5>
<button class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="toolkitModalForm">
<div class="mb-3">
<label class="form-label required">Расположение</label>
<input type="text" class="form-control" name="location" required>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button class="btn btn-primary" id="toolkitModalSubmit">
Сохранить
</button>
</div>
</div>
</div>
</div>
`;
}
document.body.insertAdjacentHTML('beforeend', modalHtml);
const modalEl = document.getElementById('toolkitDynamicModal');
const modal = new bootstrap.Modal(modalEl);
modal.show();
modalEl.querySelector('#toolkitModalSubmit').addEventListener('click', async () => {
const form = modalEl.querySelector('#toolkitModalForm');
if (!form.checkValidity()) {
form.reportValidity();
return;
}
const data = Object.fromEntries(new FormData(form).entries());
const userId = userData.id;
const response = await apiRequest('/toolkit/quick_action', {
action,
toolboxId: toolbox,
toolkitId: toolkit,
data,
userId
});
if (response.status === 'ok') {
showInfo('Действие успешно выполнено', 'success');
if (tab === 'toolbox') { await selectToolbox(toolbox); }
if (tab === 'toolkits') {
await uploadTab(tab);
showInfo('В открытом окне карточки данные не обновятся автоматически', 'info');
}
} else {
showInfo('Произошла ошибка при выполнении действия', 'danger');
throw new Error(response.message || 'Произошла ошибка при выполнении действия');
}
modal.hide();
modalEl.remove();
}, { once: true });
});
}
return manageBtns;
}
// Функция инициализации таблицы
async function initializeToolboxTable(data, toolboxOwn, quantityMonitoring) {
let currentPage = 1;
@@ -3465,7 +3621,12 @@ async function initializeToolboxTable(data, toolboxOwn, quantityMonitoring) {
${quantityMonitoring ?
`<td><span class="badge bg-${item.indicator?.class || 'secondary'}">${item.indicator?.text || '-'}</span></td>` : ''}
<td>${formatPrice(item.totalCost)} </td>
${toolboxOwn === 'Общий склад' ? `<td>${item.placement}</td>` : ''}
${toolboxOwn === 'Общий склад' ? `
<td>
<span class="me-2">${item.placement}</span>
${toolkitFillReplace(item.toolboxId, item.id, 'toolbox')}
</td>
` : ''}
<td>${item.lastUpdated}</td>
`;
@@ -3648,6 +3809,7 @@ async function showToolkitDetailsModal(toolkitId) {
let modal = document.getElementById(modalId);
if (modal) {
modal.hide();
modal.remove();
}
@@ -3970,6 +4132,7 @@ async function showToolkitDetailsModal(toolkitId) {
<i class="bi bi-box-seam-fill"></i>
</button>
` : ''}
${value.id ? toolkitFillReplace(value.id, toolkitData.id, 'toolkits') : ''}
</td>
</tr>
`).join('')}
Binary file not shown.
Binary file not shown.
+8 -4
View File
@@ -17,7 +17,7 @@ class StocksActions:
toolbox_id: int,
user_id: int,
quantity: int,
price: int,
price: float,
placement: str,
reason: str,
) -> bool:
@@ -33,7 +33,7 @@ class StocksActions:
logger.error(
f"Приход инструмента {toolkit_id} на складе {toolbox_id} не удалось"
)
return False
return {"errorMessage": "Приход инструмента не удалось"}
recorded = await StocksRecordsHandler.add(
action="Приход",
@@ -64,8 +64,12 @@ class StocksActions:
logger.error(
f"Обновление даты последнего заполнения инструмента {toolkit_id} не удалось"
)
return accepted
return False
return (
{"errorMessage": "Приход инструмента не удалось"}
if not accepted
else {}
)
return {"errorMessage": "Приход инструмента не удалось"}
async def moving(
action: str,
+1
View File
@@ -103,6 +103,7 @@ class StockHandler:
async def add(**kwargs) -> dict:
newStock = await Stock(**kwargs).save()
if "placement" in kwargs and kwargs["placement"]:
if kwargs["placement"] != "":
await PlacementHandler.add(**kwargs)
logger.info(
f"Новая запись об инструменте {newStock.toolkit_data.title} на складе {newStock.toolbox_data.title} успешно создана"