feat(super-admin): перенести справочник Стран в системную консоль
Some checks failed
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
CI / Backend (.NET 8) (push) Successful in 49s
CI / Web (React + Vite) (push) Successful in 38s
Docker API / Build + push API (push) Has been cancelled

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 2b9623d5cc
commit 6395cf348d
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); 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) public async Task<ActionResult<CountryDto>> Create([FromBody] CountryInput input, CancellationToken ct)
{ {
var e = new Country 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)); 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) public async Task<IActionResult> Update(Guid id, [FromBody] CountryInput input, CancellationToken ct)
{ {
var e = await _db.Countries.FirstOrDefaultAsync(x => x.Id == id, 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" element={<SuperAdminOrganizationsPage />} />
<Route path="organizations/new" element={<SuperAdminOrgCreatePage />} /> <Route path="organizations/new" element={<SuperAdminOrgCreatePage />} />
<Route path="audit-log" element={<SuperAdminAuditLogPage />} /> <Route path="audit-log" element={<SuperAdminAuditLogPage />} />
<Route path="countries" element={<CountriesPage />} />
</Route> </Route>
{/* Tenant-роуты обычный AppLayout, но с TenantRouteGuard: {/* Tenant-роуты обычный AppLayout, но с TenantRouteGuard:
@ -73,7 +74,6 @@ export default function App() {
<Route path="/catalog/counterparties" element={<CounterpartiesPage />} /> <Route path="/catalog/counterparties" element={<CounterpartiesPage />} />
<Route path="/catalog/stores" element={<StoresPage />} /> <Route path="/catalog/stores" element={<StoresPage />} />
<Route path="/catalog/retail-points" element={<RetailPointsPage />} /> <Route path="/catalog/retail-points" element={<RetailPointsPage />} />
<Route path="/catalog/countries" element={<CountriesPage />} />
<Route path="/inventory/stock" element={<StockPage />} /> <Route path="/inventory/stock" element={<StockPage />} />
<Route path="/inventory/movements" element={<StockMovementsPage />} /> <Route path="/inventory/movements" element={<StockMovementsPage />} />
<Route path="/purchases/supplies" element={<SuppliesPage />} /> <Route path="/purchases/supplies" element={<SuppliesPage />} />

View file

@ -6,7 +6,7 @@ import { logout } from '@/lib/auth'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { import {
LayoutDashboard, Package, FolderTree, Ruler, Tag, 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, Boxes, History, TruckIcon, ShoppingCart, Settings, Menu, X,
} from 'lucide-react' } from 'lucide-react'
import { Logo } from './Logo' import { Logo } from './Logo'
@ -49,9 +49,8 @@ function buildNav(isSuperAdmin: boolean): NavSection[] {
{ group: 'Продажи', items: [ { group: 'Продажи', items: [
{ to: '/sales/retail', icon: ShoppingCart, label: 'Розничные чеки' }, { to: '/sales/retail', icon: ShoppingCart, label: 'Розничные чеки' },
]}, ]},
{ group: 'Справочники', items: [ // Справочники типа «Страны» — глобальные, управляются SuperAdmin'ом
{ to: '/catalog/countries', icon: Globe, label: 'Страны' }, // в системной консоли. В tenant-меню их больше нет.
]},
{ group: 'Импорт', items: [ { group: 'Импорт', items: [
{ to: '/admin/import/moysklad', icon: Download, label: 'МойСклад' }, { 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 { useQuery } from '@tanstack/react-query'
import { import {
ShieldCheck, Building, FileClock, HeartPulse, HardDriveDownload, ShieldCheck, Building, FileClock, HeartPulse, HardDriveDownload,
Settings, Users, LayoutDashboard, LogOut, Menu, X, ChevronDown, Settings, Users, LayoutDashboard, LogOut, Menu, X, ChevronDown, Globe,
} from 'lucide-react' } from 'lucide-react'
import { api, getOrgOverride, setOrgOverride } from '@/lib/api' import { api, getOrgOverride, setOrgOverride } from '@/lib/api'
import { logout } from '@/lib/auth' import { logout } from '@/lib/auth'
@ -20,6 +20,9 @@ const NAV: NavSection[] = [
{ to: '/super-admin/organizations', icon: Building, label: 'Организации' }, { to: '/super-admin/organizations', icon: Building, label: 'Организации' },
{ to: '/super-admin/users', icon: Users, label: 'Пользователи', soon: true }, { to: '/super-admin/users', icon: Users, label: 'Пользователи', soon: true },
]}, ]},
{ group: 'Справочники', items: [
{ to: '/super-admin/countries', icon: Globe, label: 'Страны' },
]},
{ group: 'Аудит', items: [ { group: 'Аудит', items: [
{ to: '/super-admin/audit-log', icon: FileClock, label: 'Журнал действий' }, { to: '/super-admin/audit-log', icon: FileClock, label: 'Журнал действий' },
]}, ]},

View file

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