From 1a677f9ef2f00d95e6ebf088bd073c3c4a360592 Mon Sep 17 00:00:00 2001 From: Macbook Date: Sun, 14 Dec 2025 23:09:43 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8?= =?UTF-8?q?=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2?= =?UTF-8?q?=20=D0=BA=D0=B0=D1=80=D1=82=D0=BE=D1=87=D0=BA=D0=B0=D1=85=20?= =?UTF-8?q?=D0=B8=20=D0=BD=D0=B0=20=D1=81=D0=BA=D0=BB=D0=B0=D0=B4=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__pycache__/toolkit.cpython-313.pyc | Bin 11464 -> 13792 bytes api/routers/toolkit.py | 42 +++++ api/static/css/index.css | 3 + api/static/js/index.js | 165 +++++++++++++++++- .../__pycache__/actions.cpython-313.pyc | Bin 26390 -> 26557 bytes db/handlers/__pycache__/stock.cpython-313.pyc | Bin 13119 -> 13154 bytes db/handlers/actions.py | 12 +- db/handlers/stock.py | 3 +- 8 files changed, 219 insertions(+), 6 deletions(-) diff --git a/api/routers/__pycache__/toolkit.cpython-313.pyc b/api/routers/__pycache__/toolkit.cpython-313.pyc index 67dc3d1cd344740d597ae89ba4faa735918680ae..324d490ff4855456199254c1381b9c197f776b75 100644 GIT binary patch delta 3581 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 D64fZJ delta 2032 zcmaJ>Pi)&{6t@%GNt!rq^S4QtF3A{)SG8KZZWL{FT3h}!6@%5K0})iYvDXItiNg>Fu6*xHXxvC8N}k^L{{P;e{C05s zQS@(F9^vSxQeQHEnR*hP;OC!|rnf2q2;@1HQ-$?lB?KYHi|ZqmFoYQ|tw$;{$dxEW z^IVlLayKyw{lKUisE(;Y`U_RZ79@2f%RLfcygIsZmE>m<^J+wuU-wmgMXoB}QiK|UOx_aK#S~>D-7kfwd?$m_ zI3=!V09fFPk6Q+`-H>Bh^#jvc(;Xk6T*9F`YKd;xp8&Yc=E0pjG$)4PcJ^6iMB}$_Me3AQVx65TJl0K#3cr^(?nH z&)^10R?ZfOut>IJ)edYZqZ=+L%#tm6XObZr?W5TSgEr50V2K(Ox{CwR;T6i*xGuCevr)hP31vMwui+DW>V|d0 zu7TNfOsk>z;Ye$yA!i{UsR>YO|O11X77{j0FPMFl;_0~I{;90ef0N$! z4}*#xY7K2~fJ(Hf-`Cx*${+gT_mxREu&ddQ)-=J7K?RbzacPll)40AfyRS9$x&ihq zJ6FKiy(5o%l~@CAagZF|$aL5+#Fk^$Z5TlZ1nf%C`!f}Ev*z@$G5A+eDKxGAm9t9# zTurtXfHMHB7*>#^k+3BS-Z`}NR^3>FBPzp_w~r`&>5uU|f6Bf3f}20(?!4e;>9>1# OLFY&Q@?GH*EW*E7V3bV& diff --git a/api/routers/toolkit.py b/api/routers/toolkit.py index 9ba3b3b..95fe0e3 100644 --- a/api/routers/toolkit.py +++ b/api/routers/toolkit.py @@ -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 diff --git a/api/static/css/index.css b/api/static/css/index.css index d314205..44c5d53 100644 --- a/api/static/css/index.css +++ b/api/static/css/index.css @@ -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; diff --git a/api/static/js/index.js b/api/static/js/index.js index 20c0d0a..3df0e8d 100644 --- a/api/static/js/index.js +++ b/api/static/js/index.js @@ -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) { `; + + 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 = ` +
+ + +
+ `; + + // Навешиваем обработчики один раз + 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 = ` + + `; + } + + if (action === 'move') { + modalHtml = ` + + `; + } + + 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 ? `${item.indicator?.text || '-'}` : ''} ${formatPrice(item.totalCost)} ₽ - ${toolboxOwn === 'Общий склад' ? `${item.placement}` : ''} + ${toolboxOwn === 'Общий склад' ? ` + + ${item.placement} + ${toolkitFillReplace(item.toolboxId, item.id, 'toolbox')} + + ` : ''} ${item.lastUpdated} `; @@ -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) { ` : ''} + ${value.id ? toolkitFillReplace(value.id, toolkitData.id, 'toolkits') : ''} `).join('')} diff --git a/db/handlers/__pycache__/actions.cpython-313.pyc b/db/handlers/__pycache__/actions.cpython-313.pyc index 862d90ffe193987456c26af18842a2b9306b9db0..497a2dc4c5d7dc42ef4d9b8ce63f5f062cb03bd7 100644 GIT binary patch delta 1880 zcmaJ>U2IcT96zV`qqn#1ZMU}j=(c{a4(JBkSc?M!EDH=3fdPk(FbcJ63lz3iPg@fC z$TS8YMRO8kBvFyX_+TRDiZM}wz|e#Ttoj0d!Wc<>kqX8aUOfNPZOoY1C@bn1xzG|x!>YUXp}1fErhHK`Jfn=NULyNT{c$7QM!J6~}mW#Z&! z;ie?zxK0eQYYU(NBJDy0dkZ?Q5uRlJRJpmh$7bS&GsiL2#VJ2&<~yM9B(21|HEEA) z#5XT-aJLN0MSmU*p{wE7V>NE>yrjh55+4s#8zVjTfHduSQQVfMD%&DsMF zR*;-JQHgaBN6hi-eFsdp%@4el)wp;uq4!kl#q!|?CfW0bik3&RyG7E zZQ&~CI3|XHV(P-V5L%*mi*1o##x8b6etoAqfeb{Hn6yM`Tkw^Yxv%+v22o^@Yv@_L zE3Bbix4E6=wrN;8f}>3=ubgq$963shbZ4$qN*~QthS{{Tg8ig;%m>Q*7W^war=7C< zx~q{xRnOt72)P)`M?&@mNs4;Ttvh%gt^)7M5>DMlpYx>%n8)^4#)a&Tj=_ zpdO^dT%<6d$cbXr0zQwqQWqiGO1$)!C9WQ71d|mb>dWKs* z#sln=mN4FK-fp2Pj+mRewqkz_+{FQ&0T>2o2k2rK0{cgLfNB8CIYjkF3nV0N4Q_ z(ts(V8vr0p+6^EQ+RRx^K3>chYSK*lrJ7VK)2N;91%XIbTnHiZVVWtP%omRC&1H%- zOP8^2ef=FYN3UZN7o*a;(=2S(I!X76at*{SoWK z>r8+Aek&ieCojB)Coi4dA*qeN^J8u7!uWc19Y0k$#C{y|v4`W=@B#MO?u+UHzDQ$i zV$X(%Nag+%34!Trl%{pQe$QsMK!l3S}_i=e};qGWNeU&}Q&G{iNDwZLx cnznJKn5|6bHq$HY-jROStcJah5TFqK0~)H5-v9sr delta 1699 zcmaKre`s4(6vyvPUS3{)z5M7)W78&0*B>@*OGzB}LJcfZ5xuESwP{P|^TD0gk)rg{NyNW`2l5rq#+LXNqJ)sL1WY9=+k6!G=p8Vg$6*(H z)3lDq^eOqe0e{J=%Bn$Y(94zpwpV4OHN7!;->E&CV_#bXQyRX7M{UyW5$~vmx7>JC zm9{)1Du1~f!lN$fZp#SfFWZOks7Kmv4RiaR3M=N|mx*xv{4qJVzD z5kLc=iS;$lkv1?DKs!g+TFXI&g%F?x;09;_7r+B(1$1*1;YNn`9~xmlHuvJsb$e(6 z;~sss9AOik=2OL$&MfANxk5f!E@o&lm!?6s)m7LK z*^0v=L&&>g&Spv{E>EOs2YBfLz-6F8z8u3p8#DqbN6Vx zI~_fT@s2*<^S5k^!FsJN>|C_a1JJFFo2o*L{s5%w( z5T7y$uc88?Jc}T*?6r`M$GS8~vuNQs*r}_rZXDBZ#vc0kReR_375w5y+6zWG#AQ5x z|Jf-#z?RQG!1HWu>O*-RmiyT6Q^(rG&HMjb(yE`Deiq{tTPMx9#6BRyu61yb0;~eo z*l*;E&KWS{fLV@JQz#ONgu>E)D$Kr}dD|=cB!k!l5Jfo7R^tAdbNszxqN#i~!>2+y zpRV<|7j&X9X0Wm#lP+W~7pW*2^jDOzB40`^movrMEvOYy{~_+fIKXyiiCk+lJ3ZIU z8~Msy7^n2jxotakvTXX;1{L9@g+gJOiiqNBi3m`03jGkV=?!jGyvby-lq%&Glc`dP z<`&AOOfi|Hn|yDV4@4sX(O7_kkK#K2OI}A+so&(iulcG*K0oBMiOL5mUP^ zrgce7>!O&+iZCE?UBKjmfXQT0=`;g=1`&ggYz%^?KZ})sW;DqZIS|CcjnF9$M<^BE?0 z>Dz2Bm0!Zhm^4{R(VndVDEv8K@Q)SGqPm?#WXo4`>QBV{-q?&mJQ?< zfmF**Hc?jMZU=GmKt%rLL}fWfn0%{>8)M(*Gb*#725Slg* zF(!x&Tp$J9A|R7QK}6-`08J^;N)Qw5Zf6h+YI(h8HshkruQhKlGe&PdqN~g(l@5|+ z0}+-WA_hdnf{3`ujQZM)DU)^e!x%SgZqdKWsF(v1F9Z=KAVMBQfWqSzYg$fzVo6c; sL@Y<5h;_EUtzV>>&A4zgpVkd# z#;DEDb(I;V(m=9oAi@$vM1zPJ5D`0BMPHjSd2)b$7~}fQ%k{4^DrSSk3qV9Mh>!;n zpn$l=nwFEFSW;9qd9I-`4?C;kjFK-5K diff --git a/db/handlers/actions.py b/db/handlers/actions.py index ebc128e..51c8349 100644 --- a/db/handlers/actions.py +++ b/db/handlers/actions.py @@ -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, diff --git a/db/handlers/stock.py b/db/handlers/stock.py index 2bf3cab..b3e6cb9 100644 --- a/db/handlers/stock.py +++ b/db/handlers/stock.py @@ -103,7 +103,8 @@ class StockHandler: async def add(**kwargs) -> dict: newStock = await Stock(**kwargs).save() if "placement" in kwargs and kwargs["placement"]: - await PlacementHandler.add(**kwargs) + if kwargs["placement"] != "": + await PlacementHandler.add(**kwargs) logger.info( f"Новая запись об инструменте {newStock.toolkit_data.title} на складе {newStock.toolbox_data.title} успешно создана" )