| | | 1 | | namespace OzonOrdersWeb.WebServices.Sidebar; |
| | | 2 | | |
| | | 3 | | /// <summary>Узел бокового меню: группа (уровень 0), подгруппа (уровень 1) или ссылка.</summary> |
| | 103 | 4 | | public sealed record SidebarNode( |
| | 293 | 5 | | string Id, |
| | 119 | 6 | | string Title, |
| | 88 | 7 | | string Icon, |
| | 492 | 8 | | string? Href = null, |
| | 44 | 9 | | string? Keywords = null, |
| | 0 | 10 | | string? BadgeId = null, |
| | 109 | 11 | | bool External = false, |
| | 428 | 12 | | IReadOnlyList<SidebarNode>? Children = null) |
| | | 13 | | { |
| | 223 | 14 | | public bool IsLeaf => Href is not null; |
| | | 15 | | } |
| | | 16 | | |
| | | 17 | | /// <summary> |
| | | 18 | | /// Дерево бокового меню. Идентификаторы узлов стабильны — по ним пользовательские настройки |
| | | 19 | | /// (избранное, порядок, свёрнутые группы) хранятся в <c>UserSidebarSettings</c>; переименование |
| | | 20 | | /// или перенос пункта не теряет их, а удаление узла просто делает его настройки невидимыми. |
| | | 21 | | /// </summary> |
| | | 22 | | public static class SidebarNavigationCatalog |
| | | 23 | | { |
| | | 24 | | private static readonly IReadOnlyList<SidebarNode> Common = new[] |
| | | 25 | | { |
| | | 26 | | new SidebarNode("orders", "Заказы", "fa-table", Children: new[] |
| | | 27 | | { |
| | | 28 | | new SidebarNode("orders.grid", "Таблица заказов", "fa-table-cells", "/Studio2/OrdersGrid", "grid ag-grid таб |
| | | 29 | | new SidebarNode("orders.list", "Список заказов", "fa-list", "/Studio2/Orders", BadgeId: "sidebar-order-contr |
| | | 30 | | new SidebarNode("orders.upload", "Загрузка заказов", "fa-cloud-upload-alt", Children: new[] |
| | | 31 | | { |
| | | 32 | | new SidebarNode("orders.upload.api", "Из API / файла", "fa-cloud-arrow-down", "/Studio2/Orders/Upload"), |
| | | 33 | | new SidebarNode("orders.upload.file", "Только файл", "fa-file-arrow-up", "/Studio2/Orders/UploadFile"), |
| | | 34 | | new SidebarNode("orders.upload.percentage", "Проценка из отчёта Ozon", "fa-percent", "/Studio2/Percentag |
| | | 35 | | }), |
| | | 36 | | new SidebarNode("orders.work", "Работа с заказом", "fa-pen-to-square", Children: new[] |
| | | 37 | | { |
| | | 38 | | new SidebarNode("orders.history", "История заказов", "fa-clock-rotate-left", "/Studio2/OrderHistory"), |
| | | 39 | | new SidebarNode("orders.control", "Контроль заказов", "fa-list-check", "/Studio2/OrderControl"), |
| | | 40 | | new SidebarNode("orders.labels", "Этикетки", "fa-tags", "/Studio2/Labels"), |
| | | 41 | | new SidebarNode("orders.assembly", "Лист подбора", "fa-clipboard-list", "/Studio2/AssemblyList"), |
| | | 42 | | new SidebarNode("orders.assembly.keys", "Ключи доступа к листам", "fa-key", "/Studio2/AssemblyListAccess |
| | | 43 | | }), |
| | | 44 | | new SidebarNode("orders.docs", "Документы и операции", "fa-file-invoice", Children: new[] |
| | | 45 | | { |
| | | 46 | | new SidebarNode("orders.transactions", "Журнал документов", "fa-book", "/Studio2/Transaction"), |
| | | 47 | | new SidebarNode("orders.transaction.jobs", "Задания транзакций", "fa-diagram-project", "/Studio2/Transac |
| | | 48 | | new SidebarNode("orders.time.tracking", "Трекинг времени", "fa-stopwatch", "/Studio2/TransactionTimeTrac |
| | | 49 | | }), |
| | | 50 | | new SidebarNode("orders.returns", "Возвраты и архив", "fa-box-archive", Children: new[] |
| | | 51 | | { |
| | | 52 | | new SidebarNode("orders.archive", "Архив заказов", "fa-box-archive", "/Studio2/OrderArchive"), |
| | | 53 | | new SidebarNode("orders.returns.ozon", "Возвраты Ozon", "fa-rotate-left", "/Studio2/OrderReturns"), |
| | | 54 | | new SidebarNode("orders.returns.journal", "Журнал возвратов", "fa-receipt", "/Studio2/Orders/Returns"), |
| | | 55 | | new SidebarNode("orders.returns.ozon.external", "Возвраты озон", "fa-arrow-up-right-from-square", "https |
| | | 56 | | }), |
| | | 57 | | }), |
| | | 58 | | new SidebarNode("purchases", "Закупки и склады", "fa-cart-shopping", Children: new[] |
| | | 59 | | { |
| | | 60 | | new SidebarNode("purchases.cart", "Заказы и корзины поставщиков", "fa-cart-shopping", "/Studio2/SupplierCart |
| | | 61 | | new SidebarNode("purchases.bitrix.stock", "Остатки 1С → Ozon", "fa-boxes-stacked", "/Studio2/BitrixStock?tab |
| | | 62 | | new SidebarNode("purchases.warehouse.stock", "Остатки склада", "fa-warehouse", "/Studio2/WarehouseStock"), |
| | | 63 | | new SidebarNode("purchases.onec.stock", "Остатки 1С по складам", "fa-pallet", "/Studio2/OneCStock", "1с odat |
| | | 64 | | new SidebarNode("purchases.match.files", "Сравнение файлов", "fa-code-compare", "/Studio2/MatchFileData"), |
| | | 65 | | new SidebarNode("purchases.interparts.currency", "Курсы Interparts", "fa-coins", "/Studio2/InterpartsSutCurr |
| | | 66 | | new SidebarNode("purchases.interparts.access", "Доступ к складам Interparts", "fa-warehouse", "/Studio2/Inte |
| | | 67 | | }), |
| | | 68 | | new SidebarNode("settlements", "Взаиморасчёты", "fa-scale-balanced", "/Settlements", "взаиморасчеты поставщики с |
| | | 69 | | new SidebarNode("analytics", "Аналитика и коммуникации", "fa-chart-line", Children: new[] |
| | | 70 | | { |
| | | 71 | | new SidebarNode("analytics.products", "Анализ продуктов", "fa-chart-pie", "/Studio2/ProductAnalysis"), |
| | | 72 | | new SidebarNode("analytics.prices", "История цен", "fa-chart-line", "/Studio2/PriceHistory"), |
| | | 73 | | new SidebarNode("analytics.ozon.chat", "Чаты Ozon", "fa-comments", "/Studio2/OzonChat", BadgeId: "sidebar-oz |
| | | 74 | | new SidebarNode("analytics.ai", "AI Ассистент", "fa-robot", "/Studio2/AiAssistant"), |
| | | 75 | | }), |
| | | 76 | | new SidebarNode("integrations", "Интеграции", "fa-plug", Children: new[] |
| | | 77 | | { |
| | | 78 | | new SidebarNode("integrations.onec", "1С", "fa-database", Children: new[] |
| | | 79 | | { |
| | | 80 | | new SidebarNode("integrations.onec.fresh", "1С Fresh", "fa-database", "/OneCFresh/Home"), |
| | | 81 | | new SidebarNode("integrations.onec.m2", "М2 Проект", "fa-arrow-up-right-from-square", "https://1c.mpsupe |
| | | 82 | | }), |
| | | 83 | | new SidebarNode("integrations.mail", "Почта и файлы", "fa-envelope-open-text", Children: new[] |
| | | 84 | | { |
| | | 85 | | new SidebarNode("integrations.mail.results", "Импорт заказов из почты", "fa-inbox", "/Studio2/EmailRouti |
| | | 86 | | new SidebarNode("integrations.mail.rules", "Правила импорта", "fa-filter", "/Studio2/EmailRoutingRules") |
| | | 87 | | new SidebarNode("integrations.mail.scheduler", "Планировщик импорта", "fa-calendar-days", "/Studio2/Emai |
| | | 88 | | new SidebarNode("integrations.files.records", "Записи загрузки файлов", "fa-file-lines", "/Studio2/FileU |
| | | 89 | | new SidebarNode("integrations.files.mappings", "Сопоставления Excel / CSV", "fa-table-columns", "/Studio |
| | | 90 | | }), |
| | | 91 | | new SidebarNode("integrations.feeds", "Фиды", "fa-chart-line", Children: new[] |
| | | 92 | | { |
| | | 93 | | new SidebarNode("integrations.feeds.stat", "Фиды", "fa-chart-line", "/Studio2/FidStat"), |
| | | 94 | | new SidebarNode("integrations.feeds.links", "Связи с фидом", "fa-link", "/Studio2/FidSupplierLink"), |
| | | 95 | | }), |
| | | 96 | | new SidebarNode("integrations.ozon", "Ozon", "fa-store", Children: new[] |
| | | 97 | | { |
| | | 98 | | new SidebarNode("integrations.ozon.webhooks", "Push-уведомления Ozon", "fa-bell", "/Studio2/OzonWebhookL |
| | | 99 | | new SidebarNode("integrations.ozon.clients", "Клиенты Ozon", "fa-users", "/Studio2/OzonClients"), |
| | | 100 | | }), |
| | | 101 | | new SidebarNode("integrations.suppliers", "Поставщики API", "fa-truck-fast", Children: new[] |
| | | 102 | | { |
| | | 103 | | new SidebarNode("integrations.suppliers.settings", "Настройки API-поставщиков", "fa-sliders", "/Studio2/ |
| | | 104 | | new SidebarNode("integrations.suppliers.audit", "Логи запросов API", "fa-file-waveform", "/Studio2/ApiSu |
| | | 105 | | new SidebarNode("integrations.suppliers.consoles", "Консоли поставщиков", "fa-terminal", "/Armtek", "кон |
| | | 106 | | }), |
| | | 107 | | }), |
| | | 108 | | }; |
| | | 109 | | |
| | | 110 | | private static readonly IReadOnlyList<SidebarNode> AdminOnly = new[] |
| | | 111 | | { |
| | | 112 | | new SidebarNode("catalogs", "Справочники", "fa-book-open", Children: new[] |
| | | 113 | | { |
| | | 114 | | new SidebarNode("catalogs.goods", "Товары и контрагенты", "fa-box", Children: new[] |
| | | 115 | | { |
| | | 116 | | new SidebarNode("catalogs.products", "Товары", "fa-box", "/Studio2/Products"), |
| | | 117 | | new SidebarNode("catalogs.suppliers", "Поставщики", "fa-truck", "/Studio2/Supplier"), |
| | | 118 | | new SidebarNode("catalogs.manufacturers", "Производители Ozon", "fa-industry", "/Studio2/Manufacturers") |
| | | 119 | | new SidebarNode("catalogs.producers", "Производители ET", "fa-industry", "/Studio2/Producers"), |
| | | 120 | | new SidebarNode("catalogs.excluded.articles", "Исключённые артикулы", "fa-ban", "/Studio2/ExcludedArticl |
| | | 121 | | }), |
| | | 122 | | new SidebarNode("catalogs.warehouses", "Склады", "fa-warehouse", Children: new[] |
| | | 123 | | { |
| | | 124 | | new SidebarNode("catalogs.warehouse", "Склады", "fa-warehouse", "/Studio2/Warehouse"), |
| | | 125 | | new SidebarNode("catalogs.warehouse.mapping", "Связи складов", "fa-shuffle", "/Studio2/WarehouseMapping" |
| | | 126 | | new SidebarNode("catalogs.warehouse.excluded", "Исключённые склады Bitrix", "fa-circle-minus", "/Studio2 |
| | | 127 | | }), |
| | | 128 | | new SidebarNode("catalogs.statuses", "Статусы", "fa-tag", Children: new[] |
| | | 129 | | { |
| | | 130 | | new SidebarNode("catalogs.status.app", "Статусы заказов", "fa-tag", "/Studio2/AppStatus"), |
| | | 131 | | new SidebarNode("catalogs.status.transitions", "Переходы статусов", "fa-arrow-right-arrow-left", "/Studi |
| | | 132 | | new SidebarNode("catalogs.status.supplier", "Статусы поставщиков", "fa-tags", "/Studio2/SupplierStatus") |
| | | 133 | | new SidebarNode("catalogs.status.client", "Статусы клиентов", "fa-user-tag", "/Studio2/ClientStatus"), |
| | | 134 | | }), |
| | | 135 | | new SidebarNode("catalogs.expense.types", "Типы расходов", "fa-wallet", "/Studio2/ExpenseType"), |
| | | 136 | | }), |
| | | 137 | | new SidebarNode("admin", "Администрирование", "fa-user-shield", Children: new[] |
| | | 138 | | { |
| | | 139 | | new SidebarNode("admin.center", "Центр администрирования", "fa-user-shield", "/Admin/Home/Index"), |
| | | 140 | | new SidebarNode("admin.access.profiles", "Профили доступа", "fa-user-lock", "/Studio2/AccessProfiles"), |
| | | 141 | | new SidebarNode("admin.data.scopes", "Наборы областей данных", "fa-filter", "/Admin/DataScopes/Index"), |
| | | 142 | | new SidebarNode("admin.partners", "Кабинет партнёров", "fa-handshake", "/PartnerOffice/Partners", "партнёры |
| | | 143 | | new SidebarNode("admin.job.logs", "Журнал задач", "fa-clipboard-list", "/Studio2/JobLogs", BadgeId: "sidebar |
| | | 144 | | new SidebarNode("admin.config", "Конфигурация и база данных", "fa-screwdriver-wrench", "/Studio2/Config/Logi |
| | | 145 | | }), |
| | | 146 | | new SidebarNode("system", "Система", "fa-server", Children: new[] |
| | | 147 | | { |
| | | 148 | | new SidebarNode("system.hangfire.stats", "Статистика Hangfire", "fa-chart-bar", "/Studio2/HangfireQueue"), |
| | | 149 | | new SidebarNode("system.hangfire", "Задачи Hangfire", "fa-tasks", "/Hangfire", External: true), |
| | | 150 | | new SidebarNode("system.autoload", "Автозагрузка", "fa-robot", "/Studio2/Job"), |
| | | 151 | | }), |
| | | 152 | | }; |
| | | 153 | | |
| | | 154 | | /// <summary> |
| | | 155 | | /// Меню раздела /Admin (_AdminSidebar): своё дерево, настройки пользователя — под префиксом |
| | | 156 | | /// <see cref="AdminKeyPrefix"/>, чтобы избранное и порядок не смешивались с меню Studio2. |
| | | 157 | | /// </summary> |
| | | 158 | | public static readonly IReadOnlyList<SidebarNode> Admin = new[] |
| | | 159 | | { |
| | | 160 | | new SidebarNode("adm.main", "Основное", "fa-gauge", Children: new[] |
| | | 161 | | { |
| | | 162 | | new SidebarNode("adm.home", "Обзор", "fa-gauge", "/Admin/Home/Index", "админка панель обзор kpi"), |
| | | 163 | | new SidebarNode("adm.studio2", "В основное приложение", "fa-arrow-up-right-from-square", "/Studio2/OrdersGri |
| | | 164 | | }), |
| | | 165 | | new SidebarNode("adm.access", "Права доступа", "fa-user-shield", Children: new[] |
| | | 166 | | { |
| | | 167 | | new SidebarNode("adm.roles", "Роли", "fa-id-badge", "/Admin/Roles/Index", "роли матрица права страницы поля" |
| | | 168 | | new SidebarNode("adm.users", "Пользователи", "fa-users", "/Admin/Users/Index", "пользователи вход одобрение |
| | | 169 | | }), |
| | | 170 | | new SidebarNode("adm.registry", "Реестры", "fa-layer-group", Children: new[] |
| | | 171 | | { |
| | | 172 | | new SidebarNode("adm.pages", "Страницы", "fa-window-restore", "/Admin/Pages/Index", "страницы реестр синхрон |
| | | 173 | | new SidebarNode("adm.fields", "Колонки заказов", "fa-table-columns", "/Admin/Fields/Index", "поля колонки or |
| | | 174 | | new SidebarNode("adm.scopes", "Наборы данных", "fa-filter", "/Admin/DataScopes/Index", "наборы данных data s |
| | | 175 | | }), |
| | | 176 | | new SidebarNode("adm.control", "Контроль", "fa-clipboard-check", Children: new[] |
| | | 177 | | { |
| | | 178 | | new SidebarNode("adm.audit", "Журнал изменений", "fa-book", "/Admin/AuditLog/Index", "аудит журнал изменений |
| | | 179 | | new SidebarNode("adm.order.control", "Требует действий", "fa-bell", "/Studio2/OrderControl"), |
| | | 180 | | new SidebarNode("adm.order.rules", "Правила SLA", "fa-sliders", "/Studio2/OrderControl/Rules"), |
| | | 181 | | }), |
| | | 182 | | }; |
| | | 183 | | |
| | | 184 | | /// <summary>Префикс ключей настроек меню /Admin в <c>UserSidebarSettings</c>.</summary> |
| | | 185 | | public const string AdminKeyPrefix = "admin."; |
| | | 186 | | |
| | | 187 | | /// <summary>Дерево для пользователя: администраторы видят дополнительные группы.</summary> |
| | | 188 | | public static IReadOnlyList<SidebarNode> For(bool isAdmin) => |
| | | 189 | | isAdmin ? Common.Concat(AdminOnly).ToArray() : Common; |
| | | 190 | | |
| | | 191 | | /// <summary>Все идентификаторы узлов (для валидации сохраняемых настроек).</summary> |
| | | 192 | | public static IReadOnlySet<string> AllIds { get; } = Flatten(Common.Concat(AdminOnly)).Select(n => n.Id).ToHashSet(S |
| | | 193 | | |
| | | 194 | | public static IEnumerable<SidebarNode> Flatten(IEnumerable<SidebarNode> nodes) |
| | | 195 | | { |
| | | 196 | | foreach (var node in nodes) |
| | | 197 | | { |
| | | 198 | | yield return node; |
| | | 199 | | if (node.Children is null) continue; |
| | | 200 | | foreach (var child in Flatten(node.Children)) yield return child; |
| | | 201 | | } |
| | | 202 | | } |
| | | 203 | | } |