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

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 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.categories import CategoryHandler
from db.handlers.stock import PlacementHandler, StockHandler from db.handlers.stock import PlacementHandler, StockHandler
from db.handlers.toolbox import ToolboxHandler from db.handlers.toolbox import ToolboxHandler
from db.handlers.toolkit import ToolkitHandler from db.handlers.toolkit import ToolkitHandler
from db.schemas.stock import Placement
from utils import requestDict, logger 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 'неуспешно'}" f"Управление инструментами ({action}) прошло {'успешно' if response.get('status') == 'ok' else 'неуспешно'}"
) )
return response 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; } .badge.bg-danger { background-color: #dc3545 !important; }
/* Стили для кнопок действий в строке */ /* Стили для кнопок действий в строке */
.quick-actions,
.action-buttons { .action-buttons {
opacity: 0; opacity: 0;
transition: opacity 0.2s; transition: opacity 0.2s;
} }
tr:hover .quick-actions,
tr:hover .action-buttons { tr:hover .action-buttons {
opacity: 1; opacity: 1;
} }
/* Уменьшаем отступы у кнопок в строках */ /* Уменьшаем отступы у кнопок в строках */
.quick-actions .btn,
.action-buttons .btn { .action-buttons .btn {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
font-size: 0.875rem; font-size: 0.875rem;
+164 -1
View File
@@ -2057,6 +2057,7 @@ async function selectToolbox(toolboxId) {
await loadToolboxContent(toolboxId); await loadToolboxContent(toolboxId);
} }
async function loadToolboxContent(toolboxId) { async function loadToolboxContent(toolboxId) {
const contentContainer = document.querySelector('.toolbox-content-container'); const contentContainer = document.querySelector('.toolbox-content-container');
@@ -2072,6 +2073,8 @@ async function loadToolboxContent(toolboxId) {
</div> </div>
`; `;
try { try {
const resp = await apiRequest(`/stocks/`, { toolboxId }); const resp = await apiRequest(`/stocks/`, { toolboxId });
if (resp.status === 'ok') { if (resp.status === 'ok') {
@@ -3353,6 +3356,159 @@ function formatPrice(price) {
return parseFloat(price).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ' '); 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) { async function initializeToolboxTable(data, toolboxOwn, quantityMonitoring) {
let currentPage = 1; let currentPage = 1;
@@ -3465,7 +3621,12 @@ async function initializeToolboxTable(data, toolboxOwn, quantityMonitoring) {
${quantityMonitoring ? ${quantityMonitoring ?
`<td><span class="badge bg-${item.indicator?.class || 'secondary'}">${item.indicator?.text || '-'}</span></td>` : ''} `<td><span class="badge bg-${item.indicator?.class || 'secondary'}">${item.indicator?.text || '-'}</span></td>` : ''}
<td>${formatPrice(item.totalCost)} </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> <td>${item.lastUpdated}</td>
`; `;
@@ -3648,6 +3809,7 @@ async function showToolkitDetailsModal(toolkitId) {
let modal = document.getElementById(modalId); let modal = document.getElementById(modalId);
if (modal) { if (modal) {
modal.hide();
modal.remove(); modal.remove();
} }
@@ -3970,6 +4132,7 @@ async function showToolkitDetailsModal(toolkitId) {
<i class="bi bi-box-seam-fill"></i> <i class="bi bi-box-seam-fill"></i>
</button> </button>
` : ''} ` : ''}
${value.id ? toolkitFillReplace(value.id, toolkitData.id, 'toolkits') : ''}
</td> </td>
</tr> </tr>
`).join('')} `).join('')}
Binary file not shown.
Binary file not shown.
+8 -4
View File
@@ -17,7 +17,7 @@ class StocksActions:
toolbox_id: int, toolbox_id: int,
user_id: int, user_id: int,
quantity: int, quantity: int,
price: int, price: float,
placement: str, placement: str,
reason: str, reason: str,
) -> bool: ) -> bool:
@@ -33,7 +33,7 @@ class StocksActions:
logger.error( logger.error(
f"Приход инструмента {toolkit_id} на складе {toolbox_id} не удалось" f"Приход инструмента {toolkit_id} на складе {toolbox_id} не удалось"
) )
return False return {"errorMessage": "Приход инструмента не удалось"}
recorded = await StocksRecordsHandler.add( recorded = await StocksRecordsHandler.add(
action="Приход", action="Приход",
@@ -64,8 +64,12 @@ class StocksActions:
logger.error( logger.error(
f"Обновление даты последнего заполнения инструмента {toolkit_id} не удалось" f"Обновление даты последнего заполнения инструмента {toolkit_id} не удалось"
) )
return accepted return (
return False {"errorMessage": "Приход инструмента не удалось"}
if not accepted
else {}
)
return {"errorMessage": "Приход инструмента не удалось"}
async def moving( async def moving(
action: str, action: str,
+2 -1
View File
@@ -103,7 +103,8 @@ class StockHandler:
async def add(**kwargs) -> dict: async def add(**kwargs) -> dict:
newStock = await Stock(**kwargs).save() newStock = await Stock(**kwargs).save()
if "placement" in kwargs and kwargs["placement"]: if "placement" in kwargs and kwargs["placement"]:
await PlacementHandler.add(**kwargs) if kwargs["placement"] != "":
await PlacementHandler.add(**kwargs)
logger.info( logger.info(
f"Новая запись об инструменте {newStock.toolkit_data.title} на складе {newStock.toolbox_data.title} успешно создана" f"Новая запись об инструменте {newStock.toolkit_data.title} на складе {newStock.toolbox_data.title} успешно создана"
) )