feat(tyres): доступ к выдаче резины по праву склада, не только админам

Плитка «Выдача резины» и маршруты /wh/tyres* открываются по признаку
warehouse:tyres из /me (или is_admin) — новый гейт RequireTyres вместо
RequireAdmin. Приёмка/инвентаризация остаются админскими. Бэкенд: право
warehouse.tyres в crm2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 16:11:17 +10:00
co-authored by Claude Fable 5
parent 8deddf2630
commit b3e5d05849
2 changed files with 27 additions and 5 deletions
+20 -4
View File
@@ -44,6 +44,22 @@ function RequireAdmin({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}
/**
* Выдача резины — по складскому праву (warehouse:tyres в /me), не только
* админам. Приёмка и инвентаризация остаются под RequireAdmin.
*/
function RequireTyres({ children }: { children: React.ReactNode }) {
const token = useAuth((s) => s.token);
const meQuery = useQuery({ queryKey: ["me"], queryFn: getMe, staleTime: 60_000, enabled: !!token });
if (!token) return <Navigate to="/login" replace />;
if (meQuery.isLoading) return null;
const me = meQuery.data;
if (!me?.is_admin && !me?.permissions?.includes("warehouse:tyres")) {
return <Navigate to="/" replace />;
}
return <>{children}</>;
}
export default function App() {
// У35: фоновая сверка прав механика раз в 5 минут — выкидывает на /login
// с сообщением «Доступ отозван», если админ снял can_login_pwa или вывел
@@ -72,10 +88,10 @@ export default function App() {
<Route path="/wh/receipts/:id" element={<RequireAdmin><ReceiptDetailPage /></RequireAdmin>} />
<Route path="/wh/stocktake" element={<RequireAdmin><StocktakePage /></RequireAdmin>} />
<Route path="/wh/stocktake/:id" element={<RequireAdmin><StocktakeSessionPage /></RequireAdmin>} />
{/* Сезонная выдача резины — тот же круг, что приёмка и инвентаризация. */}
<Route path="/wh/tyres" element={<RequireAdmin><TyresPage /></RequireAdmin>} />
<Route path="/wh/tyres/sizes" element={<RequireAdmin><TyreSizesPage /></RequireAdmin>} />
<Route path="/wh/tyres/journal" element={<RequireAdmin><TyreJournalPage /></RequireAdmin>} />
{/* Сезонная выдача резины — по складскому праву (не только админам). */}
<Route path="/wh/tyres" element={<RequireTyres><TyresPage /></RequireTyres>} />
<Route path="/wh/tyres/sizes" element={<RequireTyres><TyreSizesPage /></RequireTyres>} />
<Route path="/wh/tyres/journal" element={<RequireTyres><TyreJournalPage /></RequireTyres>} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
);
@@ -34,7 +34,7 @@ export default function ActionMenu() {
emoji: "🔧",
onClick: () => navigate("/repairs"),
},
// Складские разделы правят остатки и деньги — пока только админам
// Приёмка и инвентаризация правят остатки и деньги — пока только админам
// (тот же гейт стоит на сервере, здесь просто не показываем лишнего).
...(meQuery.data?.is_admin
? [
@@ -52,6 +52,12 @@ export default function ActionMenu() {
emoji: "📋",
onClick: () => navigate("/wh/stocktake"),
},
]
: []),
// Выдача резины — по складскому праву (не только админам): признак
// warehouse:tyres приходит в /me, если у механика есть право склада.
...(meQuery.data?.is_admin || meQuery.data?.permissions?.includes("warehouse:tyres")
? [
{
key: "tyres",
title: "Выдача резины",