feat(super-admin): перенести справочник Стран в системную консоль

Country — глобальный справочник (Entity, не TenantEntity), магазины-
клиенты выбирают страны из готового списка но не управляют ими.
Управление переносится в SuperAdmin консоль.

Изменения:
- API: POST/PUT /api/catalog/countries теперь Authorize(Roles=SuperAdmin)
  (раньше был SuperAdmin,Admin). DELETE и так был SuperAdmin.
- GET остаётся [Authorize] без роли — нужен tenant'у для селектов в
  формах создания орги/контрагентов/товаров.
- Tenant AppLayout: убран блок «Справочники» с пунктом «Страны».
  Иконка Globe больше не импортируется в tenant-меню.
- Tenant роут /catalog/countries удалён из App.tsx.
- В OrganizationSettingsPage ссылка «откройте справочник Страны»
  заменена на текст «обратитесь к администратору платформы».
- SuperAdminLayout: новый блок «Справочники» с пунктом «Страны»
  (/super-admin/countries). Иконка Globe.
- Роут /super-admin/countries использует существующий CountriesPage —
  компонент unchanged, страница теперь рендерится в SuperAdminLayout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nns 2026-04-26 16:09:02 +05:00
parent 61cb97a1b7
commit 8d72e9da2d
5 changed files with 11 additions and 9 deletions

View file

@ -57,7 +57,7 @@ public async Task<ActionResult<CountryDto>> Get(Guid id, CancellationToken ct)
return c is null ? NotFound() : Project(c);
}
[HttpPost, Authorize(Roles = "SuperAdmin,Admin")]
[HttpPost, Authorize(Roles = "SuperAdmin")]
public async Task<ActionResult<CountryDto>> Create([FromBody] CountryInput input, CancellationToken ct)
{
var e = new Country
@ -73,7 +73,7 @@ public async Task<ActionResult<CountryDto>> Create([FromBody] CountryInput input
return CreatedAtAction(nameof(Get), new { id = e.Id }, Project(e));
}
[HttpPut("{id:guid}"), Authorize(Roles = "SuperAdmin,Admin")]
[HttpPut("{id:guid}"), Authorize(Roles = "SuperAdmin")]
public async Task<IActionResult> Update(Guid id, [FromBody] CountryInput input, CancellationToken ct)
{
var e = await _db.Countries.FirstOrDefaultAsync(x => x.Id == id, ct);

View file

@ -57,6 +57,7 @@ export default function App() {
<Route path="organizations" element={<SuperAdminOrganizationsPage />} />
<Route path="organizations/new" element={<SuperAdminOrgCreatePage />} />
<Route path="audit-log" element={<SuperAdminAuditLogPage />} />
<Route path="countries" element={<CountriesPage />} />
</Route>
{/* Tenant-роуты обычный AppLayout, но с TenantRouteGuard:
@ -73,7 +74,6 @@ export default function App() {
<Route path="/catalog/counterparties" element={<CounterpartiesPage />} />
<Route path="/catalog/stores" element={<StoresPage />} />
<Route path="/catalog/retail-points" element={<RetailPointsPage />} />
<Route path="/catalog/countries" element={<CountriesPage />} />
<Route path="/inventory/stock" element={<StockPage />} />
<Route path="/inventory/movements" element={<StockMovementsPage />} />
<Route path="/purchases/supplies" element={<SuppliesPage />} />

View file

@ -6,7 +6,7 @@ import { logout } from '@/lib/auth'
import { cn } from '@/lib/utils'
import {
LayoutDashboard, Package, FolderTree, Ruler, Tag,
Users, Warehouse, Store as StoreIcon, Globe, LogOut, Download, UserCog, Shield, ShieldCheck,
Users, Warehouse, Store as StoreIcon, LogOut, Download, UserCog, Shield, ShieldCheck,
Boxes, History, TruckIcon, ShoppingCart, Settings, Menu, X,
} from 'lucide-react'
import { Logo } from './Logo'
@ -49,9 +49,8 @@ function buildNav(isSuperAdmin: boolean): NavSection[] {
{ group: 'Продажи', items: [
{ to: '/sales/retail', icon: ShoppingCart, label: 'Розничные чеки' },
]},
{ group: 'Справочники', items: [
{ to: '/catalog/countries', icon: Globe, label: 'Страны' },
]},
// Справочники типа «Страны» — глобальные, управляются SuperAdmin'ом
// в системной консоли. В tenant-меню их больше нет.
{ group: 'Импорт', items: [
{ to: '/admin/import/moysklad', icon: Download, label: 'МойСклад' },
]},

View file

@ -3,7 +3,7 @@ import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom'
import { useQuery } from '@tanstack/react-query'
import {
ShieldCheck, Building, FileClock, HeartPulse, HardDriveDownload,
Settings, Users, LayoutDashboard, LogOut, Menu, X, ChevronDown,
Settings, Users, LayoutDashboard, LogOut, Menu, X, ChevronDown, Globe,
} from 'lucide-react'
import { api, getOrgOverride, setOrgOverride } from '@/lib/api'
import { logout } from '@/lib/auth'
@ -20,6 +20,9 @@ const NAV: NavSection[] = [
{ to: '/super-admin/organizations', icon: Building, label: 'Организации' },
{ to: '/super-admin/users', icon: Users, label: 'Пользователи', soon: true },
]},
{ group: 'Справочники', items: [
{ to: '/super-admin/countries', icon: Globe, label: 'Страны' },
]},
{ group: 'Аудит', items: [
{ to: '/super-admin/audit-log', icon: FileClock, label: 'Журнал действий' },
]},

View file

@ -100,7 +100,7 @@ export function OrganizationSettingsPage() {
</div>
<p className="text-xs text-slate-500 -mt-2">
Валюта и ставка НДС берутся из страны (<strong>{form.countryCode}</strong>)
чтобы изменить, откройте справочник <a className="underline" href="/catalog/countries">Страны</a>.
чтобы изменить обратитесь к администратору платформы (справочник стран управляется в системной консоли).
</p>
<Checkbox