From 3885782afdc490d1b729225717ca05b7e3b8f37a Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Mon, 4 May 2026 11:01:31 +0700 Subject: [PATCH] Remove obsolete markdown documentation artifacts --- docs/.vitepress/config.ts | 30 - .../theme/components/MermaidDiagram.vue | 99 -- .../theme/components/NamedMermaidDiagram.vue | 12 - .../theme/components/diagramSources.ts | 238 ----- docs/.vitepress/theme/index.ts | 11 - docs/index.md | 29 - docs/public/diagrams/database-model.svg | 1 - .../scripts/generate-wireframe-prototypes.mjs | 906 ------------------ docs/scripts/mermaid-config.json | 9 - docs/scripts/puppeteer-config.json | 9 - docs/scripts/render-mermaid-assets.mjs | 80 -- docs/tz/acceptance.md | 109 --- docs/tz/data-entities.md | 484 ---------- docs/tz/development-stages.md | 99 -- docs/tz/documentation-requirements.md | 89 -- docs/tz/economic-indicators.md | 25 - docs/tz/functional-requirements.md | 183 ---- docs/tz/index.md | 26 - docs/tz/integrations.md | 137 --- docs/tz/non-functional-requirements.md | 69 -- docs/tz/normative-base.md | 37 - docs/tz/product-scope.md | 97 -- docs/tz/project-overview.md | 79 -- docs/tz/roles-access.md | 90 -- docs/tz/stage-1/index.md | 423 -------- docs/tz/technical-architecture.md | 286 ------ 26 files changed, 3657 deletions(-) delete mode 100644 docs/.vitepress/config.ts delete mode 100644 docs/.vitepress/theme/components/MermaidDiagram.vue delete mode 100644 docs/.vitepress/theme/components/NamedMermaidDiagram.vue delete mode 100644 docs/.vitepress/theme/components/diagramSources.ts delete mode 100644 docs/.vitepress/theme/index.ts delete mode 100644 docs/index.md delete mode 100644 docs/public/diagrams/database-model.svg delete mode 100644 docs/scripts/generate-wireframe-prototypes.mjs delete mode 100644 docs/scripts/mermaid-config.json delete mode 100644 docs/scripts/puppeteer-config.json delete mode 100644 docs/scripts/render-mermaid-assets.mjs delete mode 100644 docs/tz/acceptance.md delete mode 100644 docs/tz/data-entities.md delete mode 100644 docs/tz/development-stages.md delete mode 100644 docs/tz/documentation-requirements.md delete mode 100644 docs/tz/economic-indicators.md delete mode 100644 docs/tz/functional-requirements.md delete mode 100644 docs/tz/index.md delete mode 100644 docs/tz/integrations.md delete mode 100644 docs/tz/non-functional-requirements.md delete mode 100644 docs/tz/normative-base.md delete mode 100644 docs/tz/product-scope.md delete mode 100644 docs/tz/project-overview.md delete mode 100644 docs/tz/roles-access.md delete mode 100644 docs/tz/stage-1/index.md delete mode 100644 docs/tz/technical-architecture.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts deleted file mode 100644 index 25c8187..0000000 --- a/docs/.vitepress/config.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { defineConfig } from 'vitepress'; - -export default defineConfig({ - title: 'Техническое задание', - description: 'Техническое задание на разработку личного кабинета Фрегат', - lang: 'ru-RU', - cleanUrls: true, - themeConfig: { - nav: [ - { text: 'Техническое задание', link: '/' }, - ], - sidebar: [ - { - text: 'Техническое задание', - items: [ - { text: 'Единый документ ТЗ', link: '/' }, - { text: 'Содержание разделов', link: '/tz/' }, - ], - }, - ], - outline: [2, 3], - search: { - provider: 'local', - }, - footer: { - message: 'Техническое задание на разработку программного продукта', - copyright: 'Фрегат Групп / ИП Бакиев', - }, - }, -}); diff --git a/docs/.vitepress/theme/components/MermaidDiagram.vue b/docs/.vitepress/theme/components/MermaidDiagram.vue deleted file mode 100644 index b8eb7f0..0000000 --- a/docs/.vitepress/theme/components/MermaidDiagram.vue +++ /dev/null @@ -1,99 +0,0 @@ - - - - - {{ error }} - - - - - diff --git a/docs/.vitepress/theme/components/NamedMermaidDiagram.vue b/docs/.vitepress/theme/components/NamedMermaidDiagram.vue deleted file mode 100644 index e798fc7..0000000 --- a/docs/.vitepress/theme/components/NamedMermaidDiagram.vue +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/docs/.vitepress/theme/components/diagramSources.ts b/docs/.vitepress/theme/components/diagramSources.ts deleted file mode 100644 index cc3b8f7..0000000 --- a/docs/.vitepress/theme/components/diagramSources.ts +++ /dev/null @@ -1,238 +0,0 @@ -export const diagramSources: Record = { - 'architecture-overview': `flowchart LR - Browser["Браузер клиента"] - Frontend["web-frontendNuxt 4 + Vue 3"] - Proxy["/api/graphql proxy"] - Backend["apollo-backendApollo Server + Express"] - Prisma["Prisma ORM"] - Db[("PostgreSQL")] - Vault["Vault"] - OneC["1С"] - Bots["Telegram / MAX / email"] - Dokploy["Dokploy"] - - Browser --> Frontend - Frontend --> Proxy - Proxy --> Backend - Backend --> Prisma - Prisma --> Db - Backend -.bootstrap env.-> Vault - Backend -.sync / events.-> OneC - Backend -.notifications.-> Bots - Frontend -.deploy.-> Dokploy - Backend -.deploy.-> Dokploy`, - 'component-map': `flowchart LR - subgraph UI["UI и маршруты"] - Pages["app/pages"] - Components["app/components"] - Composables["app/composables"] - Middleware["middleware / plugins"] - end - - subgraph Transport["Контракт и transport"] - Ops["graphql/operations"] - Generated["generated.ts"] - Apollo["Apollo client"] - Api["server/api/graphql"] - end - - subgraph Server["Backend logic"] - Schema["schema.graphql"] - Resolvers["resolvers.js"] - Auth["auth.js / access.js"] - Context["context.js"] - Messenger["messenger / telegram / max"] - end - - subgraph Data["Data layer"] - Prisma["prisma-client.js"] - PrismaSchema["schema.prisma"] - Db[("PostgreSQL")] - end - - Pages --> Components - Pages --> Composables - Components --> Composables - Composables --> Apollo - Middleware --> Pages - Ops --> Generated - Generated --> Apollo - Apollo --> Api - Api --> Schema - Schema --> Resolvers - Resolvers --> Auth - Resolvers --> Context - Resolvers --> Messenger - Resolvers --> Prisma - Prisma --> PrismaSchema - Prisma --> Db`, - 'infrastructure-topology': `flowchart TB - subgraph Repo["Репозиторий fregat"] - FrontendRepo["web-frontend"] - BackendRepo["apollo-backend"] - TgRepo["tg-bot"] - MaxRepo["max-bot"] - BonusRepo["bonus-bot"] - WorkerRepo["hatchet-worker"] - VaultRepo["vault"] - end - - subgraph Dokploy["Dokploy"] - FrontendSvc["web-frontend service"] - BackendSvc["apollo-backend service"] - TgSvc["tg-bot service"] - MaxSvc["max-bot service"] - BonusSvc["bonus-bot service"] - WorkerSvc["hatchet-worker service"] - VaultSvc["vault service"] - end - - subgraph Infra["Инфраструктурный контур"] - VaultData["Vault raft storage /vault/data"] - BackendDb[("PostgreSQL for app")] - HatchetEngine["Hatchet engine"] - HatchetPg[("PostgreSQL for Hatchet")] - OneC["1С"] - Tailscale["Tailscale SSH / diagnostics"] - end - - FrontendRepo --> FrontendSvc - BackendRepo --> BackendSvc - TgRepo --> TgSvc - MaxRepo --> MaxSvc - BonusRepo --> BonusSvc - WorkerRepo --> WorkerSvc - VaultRepo --> VaultSvc - - FrontendSvc --> BackendSvc - BackendSvc --> BackendDb - BackendSvc --> VaultSvc - TgSvc --> VaultSvc - MaxSvc --> VaultSvc - BonusSvc --> VaultSvc - WorkerSvc --> VaultSvc - VaultSvc --> VaultData - WorkerSvc --> HatchetEngine - HatchetEngine --> HatchetPg - BackendSvc -.exchange.-> OneC - FrontendSvc -.ops / debug.-> Tailscale - BackendSvc -.ops / debug.-> Tailscale`, - 'database-model': `classDiagram - direction LR - - class Company - class User - class CounterpartyProfile - class DeliveryAddress - class RegistrationRequest - class Invitation - class MessengerConnection - class Product - class Warehouse - class ProductStock - class CatalogProductTypeSetting - class Cart - class CartItem - class Order - class OrderItem - class OrderStatusEvent - class ReferralLink - class BonusTransaction - class RewardWithdrawalRequest - - Company "1" --> "*" User : users - User "1" --> "0..1" CounterpartyProfile : profile - User "1" --> "*" DeliveryAddress : addresses - User "1" --> "*" MessengerConnection : channels - User "1" --> "0..1" Cart : cart - User "1" --> "*" Order : orders - User "1" --> "*" RegistrationRequest : requests - User "1" --> "*" Invitation : invitations - Product "1" --> "*" ProductStock : balances - Warehouse "1" --> "*" ProductStock : inventory - Product "1" --> "*" CartItem : cartItems - Product "1" --> "*" OrderItem : orderItems - Cart "1" --> "*" CartItem : items - Order "1" --> "*" OrderItem : items - Order "1" --> "*" OrderStatusEvent : history - User "1" --> "*" BonusTransaction : bonus - User "1" --> "*" RewardWithdrawalRequest : withdrawals - User "1" --> "*" ReferralLink : referrals - CatalogProductTypeSetting --> Product : productType`, - dashboard: `flowchart TB - subgraph Page["Главная страница клиента"] - Header["Header / навигация"] - Quick["Быстрые действия"] - Active["Актуальные заказы и заявки"] - Notes["Последние уведомления"] - Bonus["Бонусный блок"] - end - - Header --> Quick --> Active --> Notes --> Bonus`, - 'catalog-grid': `flowchart TB - subgraph Catalog["Каталог продукции"] - Title["Заголовок раздела"] - Search["Поиск"] - Grid["Сетка карточек товарных направлений"] - Cards["Упаковочный скотч | Алюминиевый скотч | Крепп | Вспененный | Двусторонний ПП | Двусторонний PVC"] - end - - Title --> Search --> Grid --> Cards`, - 'product-card': `flowchart TB - subgraph ProductPage["Карточка товара"] - Title["Заголовок товара и навигация"] - subgraph TopRow["Верхний блок"] - Image["Изображение товара"] - Params["Параметры выбора"] - Action["SKU / действие В корзину"] - end - Help["Пояснения по параметрам"] - Table["Таблица доступных вариантов"] - end - - Title --> TopRow --> Help --> Table`, - cart: `flowchart TB - subgraph CartPage["Корзина"] - Items["Список выбранных позиций"] - Delivery["Адрес доставки"] - Comment["Комментарий клиента"] - Submit["Отправить заявку"] - end - - Items --> Delivery --> Comment --> Submit`, - 'client-order': `flowchart TB - subgraph ClientOrder["Карточка заявки / заказа"] - Summary["Номер документа и статус"] - Composition["Состав позиций"] - Terms["Стоимость и условия поставки"] - History["История изменений"] - end - - Summary --> Composition --> Terms --> History`, - 'bonus-cabinet': `flowchart TB - subgraph BonusPage["Бонусный кабинет"] - Balance["Текущий бонусный баланс"] - History["История операций"] - Referrals["Реферальные связи"] - Action["Подача заявки на использование или вывод"] - end - - Balance --> History --> Referrals --> Action`, - 'manager-order': `flowchart TB - subgraph ManagerOrder["Карточка обработки заявки"] - Summary["Клиент / контрагент / менеджер"] - Payload["Состав заявки или расчетный payload"] - Terms["Стоимость и условия поставки"] - Actions["Опубликовать условия / перевести в работу / отменить"] - end - - Summary --> Payload --> Terms --> Actions`, - 'manager-orders': `flowchart TB - subgraph ManagerOrders["Список заказов менеджера"] - Header["Заголовок раздела"] - Filters["Фильтры: статус / клиент / период"] - Table["Таблица заказов"] - end - - Header --> Filters --> Table`, -}; diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts deleted file mode 100644 index 85165d5..0000000 --- a/docs/.vitepress/theme/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import DefaultTheme from 'vitepress/theme'; -import type { Theme } from 'vitepress'; - -const theme: Theme = { - ...DefaultTheme, - enhanceApp(ctx) { - DefaultTheme.enhanceApp?.(ctx); - }, -}; - -export default theme; diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 5337977..0000000 --- a/docs/index.md +++ /dev/null @@ -1,29 +0,0 @@ -# Техническое задание на разработку программного продукта - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/public/diagrams/database-model.svg b/docs/public/diagrams/database-model.svg deleted file mode 100644 index 89611c5..0000000 --- a/docs/public/diagrams/database-model.svg +++ /dev/null @@ -1 +0,0 @@ -usersprofileaddresseschannelscartordersrequestsinvitationsbalancesinventorycartItemsorderItemsitemsitemshistorybonuswithdrawalsreferralsproductType111111111111111111*0..1**0..1*************CompanyUserCounterpartyProfileDeliveryAddressRegistrationRequestInvitationMessengerConnectionProductWarehouseProductStockCatalogProductTypeSettingCartCartItemOrderOrderItemOrderStatusEventReferralLinkBonusTransactionRewardWithdrawalRequest \ No newline at end of file diff --git a/docs/scripts/generate-wireframe-prototypes.mjs b/docs/scripts/generate-wireframe-prototypes.mjs deleted file mode 100644 index 6a4db40..0000000 --- a/docs/scripts/generate-wireframe-prototypes.mjs +++ /dev/null @@ -1,906 +0,0 @@ -import { mkdirSync, writeFileSync } from 'node:fs'; -import { join } from 'node:path'; - -const OUTPUT_DIR = '/Users/ruslanbakiev/workspace/fregat/web-frontend/docs/public/prototypes'; -const WIDTH = 1440; - -const palette = { - bg: '#f3f4f6', - frame: '#ffffff', - border: '#d1d5db', - muted: '#e5e7eb', - line: '#9ca3af', - text: '#111827', - subtext: '#4b5563', - accent: '#dbeafe', - accentStroke: '#60a5fa', - success: '#dcfce7', - warning: '#fef3c7', - danger: '#fee2e2', - dark: '#101418', - darkCard: '#1b2228', - darkMuted: '#2d3741', - darkText: '#f9fafb', - darkSubtext: '#cbd5e1', -}; - -function esc(value) { - return String(value) - .replaceAll('&', '&') - .replaceAll('<', '<') - .replaceAll('>', '>') - .replaceAll('"', '"'); -} - -function svgText({ x, y, text, size = 16, weight = 500, fill = palette.text, anchor = 'start' }) { - return `${esc(text)}`; -} - -function svgRect({ x, y, width, height, fill = palette.frame, stroke = palette.border, rx = 18, dashed = false, strokeWidth = 1.5 }) { - return ``; -} - -function svgLine({ x1, y1, x2, y2, stroke = palette.border, strokeWidth = 1.5, dashed = false }) { - return ``; -} - -function pill({ x, y, width, label, fill = '#f9fafb', stroke = palette.border, textFill = palette.subtext }) { - return [ - svgRect({ x, y, width, height: 34, rx: 17, fill, stroke }), - svgText({ x: x + width / 2, y: y + 22, text: label, size: 13, weight: 600, fill: textFill, anchor: 'middle' }), - ].join(''); -} - -function cardTitle({ x, y, text, subtitle }) { - return [ - svgText({ x, y, text, size: 18, weight: 700 }), - subtitle ? svgText({ x, y: y + 24, text: subtitle, size: 12, weight: 500, fill: palette.subtext }) : '', - ].join(''); -} - -function windowFrame({ title, height, dark = false }) { - const bg = dark ? palette.dark : palette.bg; - const frame = dark ? palette.darkCard : palette.frame; - const stroke = dark ? palette.darkMuted : palette.border; - const titleFill = dark ? palette.darkText : palette.text; - const barFill = dark ? '#0b0f13' : '#f9fafb'; - - return [ - ``, - ``, - svgRect({ x: 24, y: 24, width: WIDTH - 48, height: height - 48, fill: frame, stroke, rx: 28 }), - svgRect({ x: 24, y: 24, width: WIDTH - 48, height: 56, fill: barFill, stroke, rx: 28 }), - ``, - ``, - ``, - ``, - svgText({ x: 136, y: 58, text: title, size: 17, weight: 700, fill: titleFill }), - ].join(''); -} - -function footer() { - return ''; -} - -function makeDashboard() { - const height = 1040; - const parts = [windowFrame({ title: 'Главная страница клиента', height })]; - - parts.push(svgText({ x: 72, y: 130, text: 'Главная', size: 30, weight: 800 })); - parts.push(svgText({ x: 72, y: 160, text: 'Быстрые действия, заказы, уведомления и бонусный контур на одном экране', size: 14, fill: palette.subtext })); - - const quickActions = { x: 72, y: 196, w: 430, h: 220 }; - parts.push(svgRect({ x: quickActions.x, y: quickActions.y, width: quickActions.w, height: quickActions.h })); - parts.push(cardTitle({ x: 96, y: 228, text: 'Быстрые действия', subtitle: 'Переход в каталог, заказы, профиль и бонусную программу' })); - const actionLabels = ['Каталог', 'Корзина', 'Мои заказы', 'Профиль', 'Уведомления', 'Бонусы']; - actionLabels.forEach((label, index) => { - const col = index % 3; - const row = Math.floor(index / 3); - const x = 96 + col * 108; - const y = 274 + row * 72; - parts.push(svgRect({ x, y, width: 92, height: 56, rx: 16, fill: palette.muted })); - parts.push(svgText({ x: x + 46, y: y + 33, text: label, size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - }); - - const profile = { x: 526, y: 196, w: 842, h: 220 }; - parts.push(svgRect({ x: profile.x, y: profile.y, width: profile.w, height: profile.h })); - parts.push(cardTitle({ x: 550, y: 228, text: 'Сводка клиента', subtitle: 'Статус профиля, активные заявки и бонусный баланс' })); - parts.push(svgRect({ x: 550, y: 266, width: 290, height: 118, fill: palette.accent, stroke: palette.accentStroke })); - parts.push(svgText({ x: 574, y: 302, text: 'Профиль заполнен на 82%', size: 16, weight: 700 })); - parts.push(svgText({ x: 574, y: 326, text: 'Реквизиты, адреса доставки, уведомления', size: 13, fill: palette.subtext })); - parts.push(svgRect({ x: 866, y: 266, width: 222, height: 118, fill: palette.success, stroke: '#86efac' })); - parts.push(svgText({ x: 890, y: 302, text: '2 активных заказа', size: 16, weight: 700 })); - parts.push(svgText({ x: 890, y: 326, text: 'Ожидают расчет и публикацию условий', size: 13, fill: palette.subtext })); - parts.push(svgRect({ x: 1114, y: 266, width: 230, height: 118, fill: palette.warning, stroke: '#facc15' })); - parts.push(svgText({ x: 1138, y: 302, text: '12 400 бонусов', size: 16, weight: 700 })); - parts.push(svgText({ x: 1138, y: 326, text: 'Доступно для подарочных карт и вывода', size: 13, fill: palette.subtext })); - - const orders = { x: 72, y: 446, w: 830, h: 500 }; - parts.push(svgRect({ x: orders.x, y: orders.y, width: orders.w, height: orders.h })); - parts.push(cardTitle({ x: 96, y: 478, text: 'Последние заявки и заказы', subtitle: 'Очередь клиента с текущими статусами и действиями' })); - parts.push(svgRect({ x: 96, y: 512, width: 782, height: 52, fill: '#f9fafb', stroke: palette.border, rx: 14 })); - ['Номер', 'Тип', 'Статус', 'Доставка', 'Действие'].forEach((label, index) => { - parts.push(svgText({ x: 118 + [0, 144, 338, 510, 678][index], y: 544, text: label, size: 12, weight: 700, fill: palette.subtext })); - }); - for (let i = 0; i < 5; i += 1) { - const y = 576 + i * 70; - parts.push(svgRect({ x: 96, y, width: 782, height: 58, rx: 14, fill: i % 2 === 0 ? '#ffffff' : '#fbfbfb' })); - parts.push(svgText({ x: 118, y: y + 33, text: `FRG-10${i + 1}`, size: 14, weight: 700 })); - parts.push(svgText({ x: 262, y: y + 33, text: i % 2 === 0 ? 'Заказ' : 'Расчет', size: 14 })); - parts.push(pill({ x: 420, y: y + 12, width: 120, label: i % 2 === 0 ? 'В работе' : 'Нужен расчет', fill: i % 2 === 0 ? palette.success : palette.warning })); - parts.push(svgText({ x: 628, y: y + 33, text: i % 2 === 0 ? 'Москва' : 'Санкт-Петербург', size: 14 })); - parts.push(svgRect({ x: 746, y: y + 12, width: 104, height: 34, rx: 17, fill: palette.muted })); - parts.push(svgText({ x: 798, y: y + 34, text: 'Открыть', size: 13, weight: 700, fill: palette.subtext, anchor: 'middle' })); - } - - const side = { x: 926, y: 446, w: 442, h: 500 }; - parts.push(svgRect({ x: side.x, y: side.y, width: side.w, height: side.h })); - parts.push(cardTitle({ x: 950, y: 478, text: 'Уведомления и бонусы', subtitle: 'Информационные блоки и отдельные CTA' })); - parts.push(svgRect({ x: 950, y: 518, width: 394, height: 110, fill: palette.muted })); - parts.push(svgText({ x: 974, y: 550, text: 'Последние уведомления', size: 16, weight: 700 })); - parts.push(svgLine({ x1: 974, y1: 566, x2: 1320, y2: 566 })); - ['Менеджер обновил стоимость заказа', 'Подтвержден адрес доставки', 'Создана заявка на вывод бонусов'].forEach((text, index) => { - parts.push(svgText({ x: 974, y: 590 + index * 20, text, size: 13, fill: palette.subtext })); - }); - parts.push(svgRect({ x: 950, y: 652, width: 394, height: 118, fill: palette.accent, stroke: palette.accentStroke })); - parts.push(svgText({ x: 974, y: 686, text: 'Бонусный кабинет', size: 18, weight: 800 })); - parts.push(svgText({ x: 974, y: 710, text: 'Переход в отдельный интерфейс истории бонусов, карт и выводов', size: 13, fill: palette.subtext })); - parts.push(svgRect({ x: 974, y: 726, width: 156, height: 34, rx: 17, fill: palette.frame })); - parts.push(svgText({ x: 1052, y: 748, text: 'Открыть кабинет', size: 13, weight: 700, anchor: 'middle' })); - parts.push(svgRect({ x: 950, y: 794, width: 394, height: 128, fill: '#f9fafb' })); - parts.push(svgText({ x: 974, y: 826, text: 'Заполненность профиля', size: 16, weight: 700 })); - parts.push(svgRect({ x: 974, y: 846, width: 330, height: 14, rx: 7, fill: palette.muted, stroke: palette.muted })); - parts.push(svgRect({ x: 974, y: 846, width: 272, height: 14, rx: 7, fill: '#86efac', stroke: '#86efac' })); - ['Реквизиты контрагента', 'Адреса доставки', 'Уведомления Telegram / Max'].forEach((text, index) => { - parts.push(svgText({ x: 974, y: 890 + index * 18, text: `• ${text}`, size: 13, fill: palette.subtext })); - }); - - parts.push(footer()); - return parts.join(''); -} - -function makeCatalogGrid() { - const height = 900; - const parts = [windowFrame({ title: 'Каталог продукции', height })]; - parts.push(svgText({ x: 72, y: 130, text: 'Каталог', size: 30, weight: 800 })); - parts.push(svgText({ x: 72, y: 160, text: 'Выбор товарного направления до перехода в детальную карточку', size: 14, fill: palette.subtext })); - parts.push(svgRect({ x: 72, y: 192, width: 820, height: 54, rx: 20, fill: '#f9fafb' })); - parts.push(svgText({ x: 104, y: 226, text: 'Поиск по типу товара', size: 14, fill: '#6b7280' })); - - const labels = [ - 'Алюминиевый скотч', - 'Армированный скотч', - 'Вспененный скотч', - 'Двусторонний ПП', - 'Двусторонний PVC', - 'Крепп', - 'Металлизированный', - 'Сигнальная лента', - 'Упаковочный скотч', - 'Цветной скотч', - ]; - - labels.forEach((label, index) => { - const col = index % 5; - const row = Math.floor(index / 5); - const x = 72 + col * 266; - const y = 278 + row * 272; - parts.push(svgRect({ x, y, width: 226, height: 236, rx: 28 })); - parts.push(svgRect({ x: x + 16, y: y + 16, width: 194, height: 154, rx: 22, fill: palette.muted })); - parts.push(svgLine({ x1: x + 36, y1: y + 48, x2: x + 190, y2: y + 48, stroke: '#cbd5e1', strokeWidth: 10 })); - parts.push(svgLine({ x1: x + 36, y1: y + 74, x2: x + 170, y2: y + 74, stroke: '#d1d5db', strokeWidth: 10 })); - parts.push(``); - const titleY = y + 196; - parts.push(svgText({ x: x + 16, y: titleY, text: label, size: 15, weight: 700 })); - parts.push(svgText({ x: x + 16, y: titleY + 24, text: 'Карточка товарного направления', size: 12, fill: palette.subtext })); - }); - - parts.push(footer()); - return parts.join(''); -} - -function makeProductCard() { - const height = 1180; - const parts = [windowFrame({ title: 'Карточка товара', height })]; - parts.push(svgText({ x: 72, y: 124, text: '← Назад', size: 14, weight: 700, fill: palette.subtext })); - parts.push(svgText({ x: 72, y: 170, text: 'Алюминиевый скотч', size: 32, weight: 800 })); - parts.push(svgText({ x: 72, y: 200, text: 'Выбор параметров, индивидуальные опции, остатки и добавление в корзину', size: 14, fill: palette.subtext })); - - parts.push(svgRect({ x: 20, y: 250, width: 132, height: 280, rx: 24, fill: '#eef2f7', stroke: '#cbd5e1' })); - parts.push(svgRect({ x: 1288, y: 250, width: 132, height: 280, rx: 24, fill: '#eef2f7', stroke: '#cbd5e1' })); - parts.push(svgText({ x: 86, y: 284, text: 'Соседний', size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - parts.push(svgText({ x: 86, y: 304, text: 'товар', size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - parts.push(svgRect({ x: 38, y: 324, width: 96, height: 118, rx: 20, fill: palette.muted })); - parts.push(svgText({ x: 86, y: 468, text: 'Крепп', size: 13, weight: 700, anchor: 'middle' })); - parts.push(svgText({ x: 1354, y: 284, text: 'Следующий', size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - parts.push(svgText({ x: 1354, y: 304, text: 'товар', size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - parts.push(svgRect({ x: 1306, y: 324, width: 96, height: 118, rx: 20, fill: palette.muted })); - parts.push(svgText({ x: 1354, y: 468, text: 'PVC', size: 13, weight: 700, anchor: 'middle' })); - - parts.push(svgRect({ x: 176, y: 244, width: 1088, height: 360, rx: 28 })); - parts.push(svgRect({ x: 208, y: 280, width: 300, height: 284, rx: 28, fill: palette.muted })); - parts.push(svgLine({ x1: 242, y1: 338, x2: 472, y2: 338, stroke: '#cbd5e1', strokeWidth: 14 })); - parts.push(svgLine({ x1: 242, y1: 374, x2: 438, y2: 374, stroke: '#d1d5db', strokeWidth: 14 })); - parts.push(``); - parts.push(svgText({ x: 542, y: 310, text: 'Параметры выбора', size: 22, weight: 800 })); - parts.push(svgText({ x: 542, y: 334, text: 'Ширина, длина, толщина, втулка, цвет и надпись', size: 13, fill: palette.subtext })); - - const groups = [ - { title: 'Ширина', options: ['48 мм', '75 мм'] }, - { title: 'Длина', options: ['25 м', '50 м', '100 м'] }, - { title: 'Толщина', options: ['43 мкм', '45 мкм'] }, - { title: 'Втулка', options: ['Стандарт', 'Логотип'] }, - { title: 'Цвет', options: ['Серебристый'] }, - { title: 'Надпись', options: ['Без надписи', 'Под заказ'] }, - ]; - groups.forEach((group, index) => { - const col = index % 2; - const row = Math.floor(index / 2); - const x = 542 + col * 250; - const y = 372 + row * 64; - parts.push(svgText({ x, y, text: group.title, size: 13, weight: 700, fill: palette.subtext })); - group.options.forEach((option, optionIndex) => { - parts.push(pill({ - x: x + optionIndex * 96, - y: y + 14, - width: Math.max(84, option.length * 7.2), - label: option, - fill: optionIndex === 0 ? palette.accent : '#f9fafb', - stroke: optionIndex === 0 ? palette.accentStroke : palette.border, - textFill: optionIndex === 0 ? '#1d4ed8' : palette.subtext, - })); - }); - }); - - parts.push(svgRect({ x: 940, y: 280, width: 292, height: 284, rx: 24, fill: '#fbfbfb' })); - parts.push(svgText({ x: 968, y: 314, text: 'SKU и действие', size: 18, weight: 800 })); - parts.push(svgText({ x: 968, y: 344, text: 'FRG-ALU-48-50', size: 16, weight: 700, fill: palette.subtext })); - parts.push(svgRect({ x: 968, y: 372, width: 236, height: 62, rx: 20, fill: palette.success, stroke: '#86efac' })); - parts.push(svgText({ x: 992, y: 408, text: 'В наличии: 2 140', size: 18, weight: 800 })); - parts.push(svgRect({ x: 968, y: 454, width: 236, height: 46, rx: 23, fill: palette.text, stroke: palette.text })); - parts.push(svgText({ x: 1086, y: 484, text: 'Добавить в корзину', size: 14, weight: 700, fill: '#ffffff', anchor: 'middle' })); - parts.push(svgText({ x: 968, y: 528, text: 'Если включены кастомные опции, под кнопкой появляется дополнительное поле заявки.', size: 12, fill: palette.subtext })); - - parts.push(svgRect({ x: 176, y: 636, width: 1088, height: 194, rx: 28 })); - parts.push(cardTitle({ x: 208, y: 670, text: 'Под заказ и ограничения', subtitle: 'Пояснения по любой длине, логотипу на втулке и нанесению надписи' })); - const infoBlocks = [ - ['Любая длина', 'Допустимый диапазон 25–150 м с шагом 5 м.'], - ['Логотип на втулке', 'Доступно после согласования макета и минимального тиража.'], - ['Нанесение надписи', 'Маркировка согласуется менеджером и попадает в расчет.'], - ]; - infoBlocks.forEach(([title, copy], index) => { - const x = 208 + index * 284; - parts.push(svgRect({ x, y: 706, width: 252, height: 92, rx: 18, fill: '#f9fafb' })); - parts.push(svgText({ x: x + 18, y: 734, text: title, size: 15, weight: 700 })); - parts.push(svgText({ x: x + 18, y: 758, text: copy, size: 12, fill: palette.subtext })); - }); - - parts.push(svgText({ x: 176, y: 874, text: 'Доступные варианты', size: 26, weight: 800 })); - parts.push(svgText({ x: 176, y: 902, text: 'Таблица складских вариантов с параметрами и остатками по складам', size: 13, fill: palette.subtext })); - parts.push(svgRect({ x: 176, y: 926, width: 1088, height: 196, rx: 24 })); - parts.push(svgRect({ x: 200, y: 950, width: 1040, height: 42, rx: 12, fill: '#f9fafb' })); - ['SKU', 'Параметры', 'Остаток', 'Склады', 'Действие'].forEach((label, index) => { - parts.push(svgText({ x: 220 + [0, 190, 640, 790, 946][index], y: 976, text: label, size: 12, weight: 700, fill: palette.subtext })); - }); - for (let i = 0; i < 3; i += 1) { - const y = 1006 + i * 38; - parts.push(svgLine({ x1: 200, y1: y, x2: 1240, y2: y })); - parts.push(svgText({ x: 220, y: y + 24, text: `FRG-ALU-${48 + i}-${50 + i * 10}`, size: 13, weight: 700 })); - parts.push(svgText({ x: 410, y: y + 24, text: `${48 + i} мм · ${50 + i * 10} м · ${43 + i} мкм`, size: 13, fill: palette.subtext })); - parts.push(pill({ x: 840, y: y + 6, width: 86, label: `${2100 - i * 300}`, fill: i === 2 ? palette.warning : palette.success })); - parts.push(svgText({ x: 1010, y: y + 24, text: i === 0 ? 'СПб / Москва' : 'СПб', size: 13, fill: palette.subtext })); - parts.push(svgRect({ x: 1122, y: y + 4, width: 98, height: 28, rx: 14, fill: '#f3f4f6' })); - parts.push(svgText({ x: 1171, y: y + 22, text: 'Выбрать', size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - } - - parts.push(footer()); - return parts.join(''); -} - -function makeCart() { - const height = 980; - const parts = [windowFrame({ title: 'Корзина', height })]; - parts.push(svgText({ x: 72, y: 130, text: 'Корзина', size: 30, weight: 800 })); - parts.push(svgText({ x: 72, y: 160, text: 'Состав заказа, адрес доставки и отправка заявки на расчет', size: 14, fill: palette.subtext })); - - parts.push(svgRect({ x: 72, y: 204, width: 916, height: 518, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 238, text: 'Состав заказа', subtitle: 'Текущие позиции из каталога с параметрами и количеством' })); - parts.push(svgRect({ x: 96, y: 276, width: 868, height: 44, rx: 12, fill: '#f9fafb' })); - ['Товар', 'Параметры', 'Кол-во', 'Управление'].forEach((label, index) => { - parts.push(svgText({ x: 118 + [0, 320, 640, 768][index], y: 304, text: label, size: 12, weight: 700, fill: palette.subtext })); - }); - for (let i = 0; i < 4; i += 1) { - const y = 334 + i * 84; - parts.push(svgRect({ x: 96, y, width: 868, height: 68, rx: 16, fill: i % 2 === 0 ? '#ffffff' : '#fbfbfb' })); - parts.push(svgText({ x: 118, y: y + 28, text: i % 2 === 0 ? 'Упаковочный скотч' : 'Алюминиевый скотч', size: 15, weight: 700 })); - parts.push(svgText({ x: 118, y: y + 48, text: `FRG-10${i + 1}`, size: 12, fill: palette.subtext })); - parts.push(svgText({ x: 438, y: y + 34, text: '48 мм · 50 м · 43 мкм · прозрачный', size: 13, fill: palette.subtext })); - parts.push(svgRect({ x: 736, y: y + 18, width: 70, height: 30, rx: 15, fill: '#f3f4f6' })); - parts.push(svgText({ x: 771, y: y + 38, text: String(i + 1), size: 13, weight: 700, anchor: 'middle' })); - parts.push(svgRect({ x: 830, y: y + 18, width: 108, height: 30, rx: 15, fill: palette.muted })); - parts.push(svgText({ x: 884, y: y + 38, text: 'Изменить', size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - } - - parts.push(svgRect({ x: 1012, y: 204, width: 356, height: 518, rx: 28, fill: '#fbfbfb' })); - parts.push(cardTitle({ x: 1038, y: 238, text: 'Оформление', subtitle: 'Проверка профиля клиента и отправка заявки' })); - parts.push(svgRect({ x: 1038, y: 278, width: 304, height: 84, rx: 18, fill: palette.warning, stroke: '#facc15' })); - parts.push(svgText({ x: 1060, y: 310, text: 'Профиль контрагента заполнен', size: 16, weight: 700 })); - parts.push(svgText({ x: 1060, y: 334, text: 'Если профиль неполный, здесь показывается предупреждение.', size: 12, fill: palette.subtext })); - parts.push(svgText({ x: 1038, y: 398, text: 'Адрес доставки', size: 15, weight: 700 })); - for (let i = 0; i < 3; i += 1) { - const y = 420 + i * 78; - parts.push(svgRect({ x: 1038, y, width: 304, height: 62, rx: 18, fill: i === 0 ? palette.accent : '#ffffff', stroke: i === 0 ? palette.accentStroke : palette.border })); - parts.push(``); - if (i === 0) { - parts.push(``); - } - parts.push(svgText({ x: 1084, y: y + 26, text: i === 0 ? 'Основной склад клиента' : `Адрес доставки ${i + 1}`, size: 13, weight: 700 })); - parts.push(svgText({ x: 1084, y: y + 46, text: 'Москва, улица и зона разгрузки', size: 12, fill: palette.subtext })); - } - parts.push(svgRect({ x: 1038, y: 676, width: 304, height: 44, rx: 22, fill: palette.text, stroke: palette.text })); - parts.push(svgText({ x: 1190, y: 704, text: 'Оформить заявку', size: 14, weight: 700, fill: '#ffffff', anchor: 'middle' })); - - parts.push(svgRect({ x: 72, y: 752, width: 1296, height: 152, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 786, text: 'Комментарий и итоговая сводка', subtitle: 'Дополнительные инструкции клиента и итог по количеству позиций' })); - parts.push(svgRect({ x: 96, y: 820, width: 900, height: 52, rx: 18, fill: '#f9fafb', dashed: true })); - parts.push(svgText({ x: 120, y: 852, text: 'Комментарий клиента к заказу / пожелания по доставке', size: 13, fill: palette.subtext })); - parts.push(svgRect({ x: 1026, y: 820, width: 318, height: 52, rx: 18, fill: palette.success, stroke: '#86efac' })); - parts.push(svgText({ x: 1050, y: 852, text: '4 позиции в корзине, 2 уникальных SKU, адрес выбран', size: 13, weight: 700 })); - - parts.push(footer()); - return parts.join(''); -} - -function makeClientOrder() { - const height = 1040; - const parts = [windowFrame({ title: 'Карточка заказа клиента', height })]; - parts.push(svgText({ x: 72, y: 124, text: '← Назад к заказам', size: 14, weight: 700, fill: palette.subtext })); - parts.push(svgText({ x: 72, y: 168, text: 'Заказ FRG-1042', size: 32, weight: 800 })); - parts.push(svgText({ x: 72, y: 196, text: 'Клиент видит только статус, состав, условия поставки и историю изменений', size: 14, fill: palette.subtext })); - - parts.push(svgRect({ x: 72, y: 234, width: 1296, height: 132, rx: 28, fill: palette.accent, stroke: palette.accentStroke })); - parts.push(cardTitle({ x: 98, y: 270, text: 'Статусная линия заказа', subtitle: 'Создан → Нужен расчет → Условия опубликованы → Подтвержден → Отгружен' })); - const steps = ['Создан', 'Расчет', 'Условия', 'Подтвержден', 'Отгрузка']; - steps.forEach((step, index) => { - const x = 132 + index * 240; - parts.push(``); - parts.push(svgText({ x, y: 353, text: step, size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - if (index < steps.length - 1) { - parts.push(svgLine({ x1: x + 18, y1: 320, x2: x + 222, y2: 320, stroke: index < 2 ? '#60a5fa' : '#d1d5db', strokeWidth: 6 })); - } - }); - - parts.push(svgRect({ x: 72, y: 396, width: 846, height: 420, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 430, text: 'Состав заказа', subtitle: 'Позиции, параметры и количество без менеджерских внутренних полей' })); - parts.push(svgRect({ x: 96, y: 468, width: 798, height: 42, rx: 12, fill: '#f9fafb' })); - ['Товар', 'SKU', 'Параметры', 'Кол-во'].forEach((label, index) => { - parts.push(svgText({ x: 118 + [0, 240, 420, 720][index], y: 494, text: label, size: 12, weight: 700, fill: palette.subtext })); - }); - for (let i = 0; i < 4; i += 1) { - const y = 522 + i * 64; - parts.push(svgLine({ x1: 96, y1: y, x2: 894, y2: y })); - parts.push(svgText({ x: 118, y: y + 28, text: i % 2 === 0 ? 'Упаковочный скотч' : 'Алюминиевый скотч', size: 14, weight: 700 })); - parts.push(svgText({ x: 358, y: y + 28, text: `FRG-20${i + 1}`, size: 13, fill: palette.subtext })); - parts.push(svgText({ x: 538, y: y + 28, text: '48 мм · 50 м · 43 мкм', size: 13, fill: palette.subtext })); - parts.push(svgText({ x: 794, y: y + 28, text: String(i + 1), size: 13, weight: 700 })); - } - - parts.push(svgRect({ x: 948, y: 396, width: 420, height: 420, rx: 28, fill: '#fbfbfb' })); - parts.push(cardTitle({ x: 972, y: 430, text: 'Условия и доставка', subtitle: 'Появляются после публикации менеджером' })); - parts.push(svgRect({ x: 972, y: 468, width: 372, height: 92, rx: 18, fill: palette.success, stroke: '#86efac' })); - parts.push(svgText({ x: 996, y: 500, text: 'Стоимость опубликована', size: 17, weight: 800 })); - parts.push(svgText({ x: 996, y: 524, text: 'Цена зафиксирована и доступна клиенту в карточке заказа.', size: 12, fill: palette.subtext })); - ['Доставка: Санкт-Петербург → Москва', 'Адрес: Основной склад клиента', 'Комментарий менеджера: подтверждены сроки 3–5 дней'].forEach((text, index) => { - parts.push(svgText({ x: 972, y: 604 + index * 28, text: `• ${text}`, size: 13, fill: palette.subtext })); - }); - parts.push(svgRect({ x: 972, y: 704, width: 372, height: 88, rx: 18, fill: '#f9fafb' })); - parts.push(svgText({ x: 996, y: 736, text: 'История статусов', size: 16, weight: 700 })); - ['Создан клиентом', 'Передан менеджеру', 'Условия опубликованы'].forEach((text, index) => { - parts.push(svgText({ x: 996, y: 760 + index * 18, text, size: 12, fill: palette.subtext })); - }); - - parts.push(svgRect({ x: 72, y: 846, width: 1296, height: 126, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 880, text: 'Системные комментарии и события', subtitle: 'Журнал изменений доступный клиенту' })); - ['Менеджер обновил условия поставки', 'Клиент подтвердил получение условий', 'Система отправила уведомление в Telegram'].forEach((text, index) => { - parts.push(svgText({ x: 96, y: 918 + index * 18, text: `• ${text}`, size: 13, fill: palette.subtext })); - }); - - parts.push(footer()); - return parts.join(''); -} - -function makeManagerOrders() { - const height = 980; - const parts = [windowFrame({ title: 'Список заказов менеджера', height })]; - parts.push(svgText({ x: 72, y: 130, text: 'Заказы', size: 30, weight: 800 })); - parts.push(svgText({ x: 72, y: 160, text: 'Фильтрация очереди, приоритеты и переход в карточку обработки', size: 14, fill: palette.subtext })); - - parts.push(svgRect({ x: 72, y: 198, width: 1296, height: 108, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 234, text: 'Фильтры и быстрые статусы', subtitle: 'Статус, клиент, период, город доставки и приоритет' })); - const filterLabels = ['Статус', 'Клиент', 'Период', 'Город', 'Приоритет']; - filterLabels.forEach((label, index) => { - const x = 96 + index * 246; - parts.push(svgText({ x, y: 270, text: label, size: 12, weight: 700, fill: palette.subtext })); - parts.push(svgRect({ x, y: 278, width: 210, height: 32, rx: 16, fill: '#f9fafb' })); - }); - - const stats = [ - ['Новые', '14', palette.warning, '#facc15'], - ['Нужен расчет', '9', palette.danger, '#fca5a5'], - ['Условия опубликованы', '18', palette.success, '#86efac'], - ['Ожидают отгрузку', '7', palette.accent, palette.accentStroke], - ]; - stats.forEach(([title, value, fill, stroke], index) => { - const x = 72 + index * 324; - parts.push(svgRect({ x, y: 338, width: 300, height: 90, rx: 24, fill, stroke })); - parts.push(svgText({ x: x + 24, y: 372, text: title, size: 15, weight: 700 })); - parts.push(svgText({ x: x + 24, y: 404, text: value, size: 28, weight: 800 })); - }); - - parts.push(svgRect({ x: 72, y: 458, width: 1296, height: 440, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 492, text: 'Таблица заказов', subtitle: 'Основная рабочая очередь менеджера' })); - parts.push(svgRect({ x: 96, y: 530, width: 1248, height: 44, rx: 12, fill: '#f9fafb' })); - ['Номер', 'Клиент', 'Статус', 'Доставка', 'Сумма', 'Действие'].forEach((label, index) => { - parts.push(svgText({ x: 118 + [0, 184, 470, 706, 946, 1098][index], y: 558, text: label, size: 12, weight: 700, fill: palette.subtext })); - }); - for (let i = 0; i < 6; i += 1) { - const y = 590 + i * 50; - parts.push(svgLine({ x1: 96, y1: y, x2: 1344, y2: y })); - parts.push(svgText({ x: 118, y: y + 30, text: `FRG-20${30 + i}`, size: 14, weight: 700 })); - parts.push(svgText({ x: 302, y: y + 30, text: `ООО Клиент ${i + 1}`, size: 13, fill: palette.subtext })); - parts.push(pill({ - x: 556, - y: y + 10, - width: 132, - label: i % 2 === 0 ? 'Нужен расчет' : 'Условия готовы', - fill: i % 2 === 0 ? palette.warning : palette.success, - })); - parts.push(svgText({ x: 824, y: y + 30, text: i % 2 === 0 ? 'Москва' : 'СПб → Москва', size: 13, fill: palette.subtext })); - parts.push(svgText({ x: 1064, y: y + 30, text: i % 2 === 0 ? '—' : '145 000 ₽', size: 13, weight: 700 })); - parts.push(svgRect({ x: 1160, y: y + 10, width: 136, height: 30, rx: 15, fill: palette.muted })); - parts.push(svgText({ x: 1228, y: y + 30, text: 'Открыть карточку', size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - } - - parts.push(footer()); - return parts.join(''); -} - -function makeManagerOrder() { - const height = 1100; - const parts = [windowFrame({ title: 'Карточка заказа менеджера', height })]; - parts.push(svgText({ x: 72, y: 124, text: '← Назад к заказам', size: 14, weight: 700, fill: palette.subtext })); - parts.push(svgText({ x: 72, y: 168, text: 'Заказ FRG-2034', size: 32, weight: 800 })); - parts.push(svgText({ x: 72, y: 196, text: 'Обработка условий, доставки, бонусных эффектов и журнал событий', size: 14, fill: palette.subtext })); - - parts.push(svgRect({ x: 72, y: 232, width: 1296, height: 126, rx: 28, fill: palette.accent, stroke: palette.accentStroke })); - parts.push(cardTitle({ x: 96, y: 266, text: 'Панель статуса и действий', subtitle: 'Переключение статуса, публикация цены, фиксация доставки' })); - const actionX = [842, 1010, 1178]; - ['Опубликовать условия', 'Запросить уточнение', 'Подтвердить отгрузку'].forEach((label, index) => { - parts.push(svgRect({ x: actionX[index], y: 260, width: 150, height: 42, rx: 21, fill: index === 0 ? palette.text : palette.frame, stroke: index === 0 ? palette.text : palette.border })); - parts.push(svgText({ x: actionX[index] + 75, y: 287, text: label, size: 12, weight: 700, fill: index === 0 ? '#ffffff' : palette.subtext, anchor: 'middle' })); - }); - parts.push(svgText({ x: 96, y: 320, text: 'Текущий статус: нужен расчет → следующий шаг: публикация условий клиенту', size: 13, weight: 700 })); - - parts.push(svgRect({ x: 72, y: 388, width: 780, height: 470, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 422, text: 'Состав заказа и расчет', subtitle: 'Позиции клиента, параметры, ручной расчет и итоговая публикация' })); - parts.push(svgRect({ x: 96, y: 460, width: 732, height: 42, rx: 12, fill: '#f9fafb' })); - ['Товар', 'Параметры', 'Кол-во', 'Цена', 'Итог'].forEach((label, index) => { - parts.push(svgText({ x: 118 + [0, 240, 492, 586, 678][index], y: 488, text: label, size: 12, weight: 700, fill: palette.subtext })); - }); - for (let i = 0; i < 4; i += 1) { - const y = 514 + i * 60; - parts.push(svgLine({ x1: 96, y1: y, x2: 828, y2: y })); - parts.push(svgText({ x: 118, y: y + 28, text: i % 2 === 0 ? 'Упаковочный скотч' : 'Вспененный скотч', size: 14, weight: 700 })); - parts.push(svgText({ x: 358, y: y + 28, text: '48 мм · 50 м · стандарт', size: 13, fill: palette.subtext })); - parts.push(svgText({ x: 612, y: y + 28, text: String(i + 1), size: 13, weight: 700 })); - parts.push(svgRect({ x: 662, y: y + 10, width: 68, height: 26, rx: 13, fill: '#f9fafb' })); - parts.push(svgText({ x: 696, y: y + 28, text: 'цена', size: 11, fill: palette.subtext, anchor: 'middle' })); - parts.push(svgText({ x: 776, y: y + 28, text: i % 2 === 0 ? '24 000' : '18 500', size: 12, weight: 700 })); - } - parts.push(svgRect({ x: 96, y: 772, width: 732, height: 62, rx: 18, fill: palette.success, stroke: '#86efac' })); - parts.push(svgText({ x: 120, y: 806, text: 'Блок публикации итоговых условий: сумма, комментарий, сроки, вид доставки', size: 14, weight: 700 })); - - parts.push(svgRect({ x: 882, y: 388, width: 486, height: 470, rx: 28, fill: '#fbfbfb' })); - parts.push(cardTitle({ x: 906, y: 422, text: 'Доставка и коммуникации', subtitle: 'Адрес, стоимость логистики, комментарии и история сообщений' })); - parts.push(svgRect({ x: 906, y: 460, width: 438, height: 88, rx: 18, fill: '#f9fafb' })); - ['Адрес доставки клиента', 'Стоимость доставки / самовывоз', 'Окно разгрузки и ограничения'].forEach((text, index) => { - parts.push(svgText({ x: 930, y: 492 + index * 20, text: `• ${text}`, size: 13, fill: palette.subtext })); - }); - parts.push(svgRect({ x: 906, y: 576, width: 438, height: 112, rx: 18, fill: palette.warning, stroke: '#facc15' })); - parts.push(svgText({ x: 930, y: 610, text: 'Влияние на бонусный контур', size: 16, weight: 700 })); - ['Начислить бонусы после подтверждения', 'Проверить реферальную привязку', 'Показать менеджеру связанный бонусный счет'].forEach((text, index) => { - parts.push(svgText({ x: 930, y: 636 + index * 18, text: `• ${text}`, size: 12, fill: palette.subtext })); - }); - parts.push(svgRect({ x: 906, y: 716, width: 438, height: 118, rx: 18, fill: '#ffffff' })); - parts.push(svgText({ x: 930, y: 748, text: 'Журнал событий', size: 16, weight: 700 })); - ['Менеджер открыл заказ', 'Клиент уточнил параметры товара', 'Система создала уведомление о расчете'].forEach((text, index) => { - parts.push(svgText({ x: 930, y: 774 + index * 18, text: text, size: 12, fill: palette.subtext })); - }); - - parts.push(svgRect({ x: 72, y: 888, width: 1296, height: 144, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 922, text: 'Внутренние комментарии и системные интеграции', subtitle: 'Заметки менеджера, данные для 1С и служебные идентификаторы' })); - parts.push(svgRect({ x: 96, y: 956, width: 864, height: 48, rx: 16, fill: '#f9fafb', dashed: true })); - parts.push(svgText({ x: 120, y: 986, text: 'Поле комментария менеджера к заказу / служебные заметки', size: 13, fill: palette.subtext })); - parts.push(svgRect({ x: 990, y: 956, width: 354, height: 48, rx: 16, fill: palette.muted })); - parts.push(svgText({ x: 1014, y: 986, text: 'Статус синхронизации с 1С / внешний идентификатор', size: 13, fill: palette.subtext })); - - parts.push(footer()); - return parts.join(''); -} - -function makeBonusCabinet() { - const height = 980; - const parts = [windowFrame({ title: 'Бонусный кабинет клиента', height, dark: true })]; - parts.push(svgText({ x: 72, y: 132, text: 'Чёрный кабинет бонусной программы', size: 30, weight: 800, fill: palette.darkText })); - parts.push(svgText({ x: 72, y: 162, text: 'Отдельный контур для бонусного баланса, истории, карт и заявок на вывод', size: 14, fill: palette.darkSubtext })); - - parts.push(svgRect({ x: 72, y: 198, width: 820, height: 220, rx: 28, fill: palette.darkCard, stroke: palette.darkMuted })); - parts.push(svgText({ x: 96, y: 232, text: 'Аккаунт клиента', size: 18, weight: 700, fill: palette.darkText })); - parts.push(svgText({ x: 96, y: 258, text: 'Имя пользователя, пояснение по программе и статус подключения', size: 13, fill: palette.darkSubtext })); - parts.push(svgText({ x: 96, y: 332, text: '12 400', size: 58, weight: 900, fill: palette.darkText })); - parts.push(svgText({ x: 96, y: 364, text: 'доступный баланс', size: 15, weight: 700, fill: palette.darkSubtext })); - const darkStats = [ - ['Рефералы', '8'], - ['Начисления', '42'], - ['Выводы', '3'], - ]; - darkStats.forEach(([title, value], index) => { - const x = 454 + index * 128; - parts.push(svgRect({ x, y: 294, width: 110, height: 88, rx: 18, fill: '#0f1419', stroke: palette.darkMuted })); - parts.push(svgText({ x: x + 18, y: 326, text: title, size: 12, weight: 700, fill: palette.darkSubtext })); - parts.push(svgText({ x: x + 18, y: 356, text: value, size: 26, weight: 800, fill: palette.darkText })); - }); - - parts.push(svgRect({ x: 918, y: 198, width: 450, height: 220, rx: 28, fill: palette.darkCard, stroke: palette.darkMuted })); - parts.push(svgText({ x: 944, y: 232, text: 'Вывод бонусов', size: 18, weight: 700, fill: palette.darkText })); - parts.push(svgText({ x: 944, y: 258, text: 'Форма подачи заявки с проверкой минимального порога и доступного баланса', size: 13, fill: palette.darkSubtext })); - parts.push(svgRect({ x: 944, y: 292, width: 398, height: 44, rx: 14, fill: '#0f1419', stroke: palette.darkMuted })); - parts.push(svgText({ x: 968, y: 320, text: 'Сумма заявки на вывод', size: 13, fill: '#93a2b5' })); - parts.push(svgRect({ x: 944, y: 354, width: 398, height: 48, rx: 24, fill: '#e5f7ea', stroke: '#86efac' })); - parts.push(svgText({ x: 1143, y: 384, text: 'Подать заявку на вывод', size: 14, weight: 700, fill: '#0f172a', anchor: 'middle' })); - - parts.push(svgRect({ x: 72, y: 448, width: 666, height: 470, rx: 28, fill: palette.darkCard, stroke: palette.darkMuted })); - parts.push(svgText({ x: 96, y: 482, text: 'История бонусных операций', size: 18, weight: 700, fill: palette.darkText })); - parts.push(svgText({ x: 96, y: 508, text: 'Начисления, списания и ссылки на связанные заказы', size: 13, fill: palette.darkSubtext })); - for (let i = 0; i < 5; i += 1) { - const y = 536 + i * 74; - parts.push(svgRect({ x: 96, y, width: 618, height: 58, rx: 18, fill: i % 2 === 0 ? '#11181f' : '#0f1419', stroke: palette.darkMuted, strokeWidth: 1 })); - parts.push(svgText({ x: 120, y: y + 28, text: `+${(i + 2) * 500} бонусов`, size: 15, weight: 700, fill: palette.darkText })); - parts.push(svgText({ x: 120, y: y + 46, text: 'Начисление за подтвержденный заказ / ручная транзакция', size: 12, fill: palette.darkSubtext })); - parts.push(svgText({ x: 580, y: y + 34, text: 'Открыть заказ', size: 12, weight: 700, fill: '#b8d4ff' })); - } - - parts.push(svgRect({ x: 764, y: 448, width: 604, height: 470, rx: 28, fill: palette.darkCard, stroke: palette.darkMuted })); - parts.push(svgText({ x: 788, y: 482, text: 'Магазин наград и активные выводы', size: 18, weight: 700, fill: palette.darkText })); - parts.push(svgText({ x: 788, y: 508, text: 'Подарочные карты и блок очереди заявок на вывод', size: 13, fill: palette.darkSubtext })); - for (let i = 0; i < 3; i += 1) { - const x = 788 + i * 184; - parts.push(svgRect({ x, y: 536, width: 160, height: 160, rx: 22, fill: '#0f1419', stroke: palette.darkMuted })); - parts.push(svgRect({ x: x + 18, y: 556, width: 124, height: 72, rx: 16, fill: '#1f2937', stroke: palette.darkMuted })); - parts.push(svgText({ x: x + 18, y: 650, text: i === 0 ? 'Ozon' : i === 1 ? 'Wildberries' : 'М.Видео', size: 14, weight: 700, fill: palette.darkText })); - parts.push(svgText({ x: x + 18, y: 672, text: `${(i + 3) * 1000} бонусов`, size: 12, fill: palette.darkSubtext })); - } - parts.push(svgRect({ x: 788, y: 724, width: 556, height: 164, rx: 22, fill: '#0f1419', stroke: palette.darkMuted })); - parts.push(svgText({ x: 812, y: 756, text: 'Активные заявки на вывод', size: 16, weight: 700, fill: palette.darkText })); - ['Заявка #1 · 1 500 бонусов · на проверке', 'Заявка #2 · 3 000 бонусов · подтверждена', 'Заявка #3 · 500 бонусов · отклонена'].forEach((text, index) => { - parts.push(svgText({ x: 812, y: 788 + index * 24, text, size: 12, fill: palette.darkSubtext })); - }); - - parts.push(footer()); - return parts.join(''); -} - -function makeLogin() { - const height = 860; - const parts = [windowFrame({ title: 'Логин и подключение', height })]; - parts.push(svgRect({ x: 110, y: 126, width: 1220, height: 658, rx: 36, fill: '#ffffff', stroke: palette.border })); - parts.push(svgRect({ x: 110, y: 126, width: 486, height: 658, rx: 36, fill: palette.accent, stroke: palette.accentStroke })); - parts.push(svgText({ x: 156, y: 210, text: 'Личный кабинет Фрегат', size: 34, weight: 800 })); - parts.push(svgText({ x: 156, y: 246, text: 'Вход по коду, заявка на подключение и быстрый выбор канала авторизации.', size: 15, fill: palette.subtext })); - ['Каталог готовой продукции', 'Индивидуальный расчет', 'Заказы, бонусы, уведомления'].forEach((text, index) => { - parts.push(svgText({ x: 156, y: 330 + index * 34, text: `• ${text}`, size: 15, fill: palette.subtext })); - }); - parts.push(svgRect({ x: 156, y: 480, width: 360, height: 220, rx: 28, fill: '#ffffff', stroke: palette.accentStroke })); - parts.push(svgText({ x: 184, y: 522, text: 'Поясняющий блок', size: 18, weight: 700 })); - parts.push(svgText({ x: 184, y: 552, text: 'Описание сценария для нового клиента:', size: 13, fill: palette.subtext })); - ['Оставить заявку на подключение', 'Дождаться проверки менеджером', 'Получить код и войти в кабинет'].forEach((text, index) => { - parts.push(svgText({ x: 184, y: 588 + index * 24, text: text, size: 13, fill: palette.subtext })); - }); - - parts.push(svgRect({ x: 650, y: 172, width: 580, height: 566, rx: 30, fill: '#fbfbfb' })); - parts.push(svgText({ x: 694, y: 228, text: 'Вход', size: 32, weight: 800 })); - parts.push(svgText({ x: 694, y: 258, text: 'Введите номер телефона или email для получения кода доступа.', size: 14, fill: palette.subtext })); - ['Телефон / Email', 'Код подтверждения'].forEach((label, index) => { - const y = 318 + index * 96; - parts.push(svgText({ x: 694, y, text: label, size: 13, weight: 700, fill: palette.subtext })); - parts.push(svgRect({ x: 694, y: y + 14, width: 492, height: 48, rx: 16, fill: '#ffffff' })); - }); - parts.push(svgRect({ x: 694, y: 520, width: 492, height: 48, rx: 24, fill: palette.text, stroke: palette.text })); - parts.push(svgText({ x: 940, y: 550, text: 'Получить код / Войти', size: 15, weight: 700, fill: '#ffffff', anchor: 'middle' })); - parts.push(svgText({ x: 694, y: 606, text: 'Альтернативные каналы входа', size: 13, weight: 700, fill: palette.subtext })); - ['Telegram', 'Max', 'Приглашение от менеджера'].forEach((label, index) => { - parts.push(svgRect({ x: 694 + index * 164, y: 626, width: 148, height: 42, rx: 21, fill: palette.muted })); - parts.push(svgText({ x: 768 + index * 164, y: 653, text: label, size: 13, weight: 700, fill: palette.subtext, anchor: 'middle' })); - }); - parts.push(svgText({ x: 694, y: 708, text: 'Ссылка: оставить самостоятельную заявку на подключение', size: 13, weight: 700, fill: '#2563eb' })); - - parts.push(footer()); - return parts.join(''); -} - -function makeProfile() { - const height = 980; - const parts = [windowFrame({ title: 'Профиль клиента', height })]; - parts.push(svgText({ x: 72, y: 130, text: 'Профиль', size: 30, weight: 800 })); - parts.push(svgText({ x: 72, y: 160, text: 'Базовые данные, контрагент, адреса доставки и уведомления', size: 14, fill: palette.subtext })); - parts.push(svgRect({ x: 72, y: 198, width: 282, height: 700, rx: 28, fill: '#fbfbfb' })); - parts.push(cardTitle({ x: 98, y: 234, text: 'Навигация профиля', subtitle: 'Внутренние подразделы клиента' })); - ['Основные данные', 'Контрагент', 'Адреса доставки', 'Уведомления'].forEach((item, index) => { - parts.push(svgRect({ x: 98, y: 278 + index * 66, width: 230, height: 46, rx: 16, fill: index === 0 ? palette.accent : '#ffffff', stroke: index === 0 ? palette.accentStroke : palette.border })); - parts.push(svgText({ x: 122, y: 307 + index * 66, text: item, size: 14, weight: 700, fill: index === 0 ? '#1d4ed8' : palette.subtext })); - }); - parts.push(svgRect({ x: 384, y: 198, width: 984, height: 700, rx: 28 })); - parts.push(cardTitle({ x: 412, y: 234, text: 'Карточка пользователя', subtitle: 'Редактирование данных и контроль заполненности профиля' })); - const fields = [ - ['ФИО', 'Руслан Бакиев'], - ['Телефон', '+7 9xx xxx-xx-xx'], - ['Email', 'client@fregat.ru'], - ['Должность', 'Руководитель закупок'], - ]; - fields.forEach(([label, value], index) => { - const col = index % 2; - const row = Math.floor(index / 2); - const x = 412 + col * 470; - const y = 286 + row * 112; - parts.push(svgText({ x, y, text: label, size: 13, weight: 700, fill: palette.subtext })); - parts.push(svgRect({ x, y: y + 14, width: 430, height: 52, rx: 16, fill: '#f9fafb' })); - parts.push(svgText({ x: x + 20, y: y + 46, text: value, size: 14 })); - }); - parts.push(svgRect({ x: 412, y: 534, width: 430, height: 122, rx: 22, fill: palette.success, stroke: '#86efac' })); - parts.push(svgText({ x: 438, y: 570, text: 'Статус заполненности', size: 18, weight: 700 })); - parts.push(svgRect({ x: 438, y: 592, width: 320, height: 14, rx: 7, fill: palette.muted, stroke: palette.muted })); - parts.push(svgRect({ x: 438, y: 592, width: 272, height: 14, rx: 7, fill: '#86efac', stroke: '#86efac' })); - parts.push(svgText({ x: 438, y: 628, text: '82% заполнено. Не хватает банковских реквизитов и второго адреса.', size: 12, fill: palette.subtext })); - parts.push(svgRect({ x: 882, y: 534, width: 430, height: 122, rx: 22, fill: palette.warning, stroke: '#facc15' })); - parts.push(svgText({ x: 908, y: 570, text: 'Быстрые переходы', size: 18, weight: 700 })); - ['Редактировать контрагента', 'Открыть адреса доставки', 'Настроить уведомления'].forEach((text, index) => { - parts.push(svgText({ x: 908, y: 602 + index * 18, text: `• ${text}`, size: 12, fill: palette.subtext })); - }); - parts.push(svgRect({ x: 412, y: 712, width: 900, height: 62, rx: 20, fill: palette.text, stroke: palette.text })); - parts.push(svgText({ x: 862, y: 751, text: 'Сохранить изменения', size: 15, weight: 700, fill: '#ffffff', anchor: 'middle' })); - parts.push(footer()); - return parts.join(''); -} - -function makeClientList() { - const height = 920; - const parts = [windowFrame({ title: 'Список клиентов', height })]; - parts.push(svgText({ x: 72, y: 130, text: 'Клиенты', size: 30, weight: 800 })); - parts.push(svgText({ x: 72, y: 160, text: 'Поиск компаний, переход в карточку клиента и приглашение нового пользователя', size: 14, fill: palette.subtext })); - parts.push(svgRect({ x: 72, y: 198, width: 1296, height: 92, rx: 28 })); - ['Поиск по компании', 'Менеджер', 'Статус', 'Город'].forEach((label, index) => { - const x = 96 + index * 270; - parts.push(svgText({ x, y: 232, text: label, size: 12, weight: 700, fill: palette.subtext })); - parts.push(svgRect({ x, y: 242, width: 230, height: 32, rx: 16, fill: '#f9fafb' })); - }); - parts.push(svgRect({ x: 1160, y: 232, width: 184, height: 42, rx: 21, fill: palette.text, stroke: palette.text })); - parts.push(svgText({ x: 1252, y: 258, text: 'Пригласить клиента', size: 13, weight: 700, fill: '#ffffff', anchor: 'middle' })); - parts.push(svgRect({ x: 72, y: 320, width: 1296, height: 520, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 354, text: 'Клиентская база', subtitle: 'Карточка компании, активность, заказы и бонусный статус' })); - parts.push(svgRect({ x: 96, y: 390, width: 1248, height: 42, rx: 12, fill: '#f9fafb' })); - ['Компания', 'Контакт', 'Заказы', 'Бонусы', 'Статус', 'Действие'].forEach((label, index) => { - parts.push(svgText({ x: 118 + [0, 282, 576, 738, 908, 1088][index], y: 416, text: label, size: 12, weight: 700, fill: palette.subtext })); - }); - for (let i = 0; i < 6; i += 1) { - const y = 446 + i * 58; - parts.push(svgLine({ x1: 96, y1: y, x2: 1344, y2: y })); - parts.push(svgText({ x: 118, y: y + 30, text: `ООО Клиент ${i + 1}`, size: 14, weight: 700 })); - parts.push(svgText({ x: 400, y: y + 30, text: 'Имя пользователя / телефон', size: 13, fill: palette.subtext })); - parts.push(svgText({ x: 694, y: y + 30, text: String(4 + i), size: 13, weight: 700 })); - parts.push(svgText({ x: 856, y: y + 30, text: `${(i + 1) * 1200}`, size: 13, weight: 700 })); - parts.push(pill({ x: 958, y: y + 10, width: 104, label: i % 2 === 0 ? 'Активен' : 'На проверке', fill: i % 2 === 0 ? palette.success : palette.warning })); - parts.push(svgRect({ x: 1104, y: y + 8, width: 132, height: 30, rx: 15, fill: palette.muted })); - parts.push(svgText({ x: 1170, y: y + 28, text: 'Открыть карточку', size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - } - parts.push(footer()); - return parts.join(''); -} - -function makeClientCard() { - const height = 1040; - const parts = [windowFrame({ title: 'Карточка клиента', height })]; - parts.push(svgText({ x: 72, y: 124, text: '← Назад к клиентам', size: 14, weight: 700, fill: palette.subtext })); - parts.push(svgText({ x: 72, y: 168, text: 'ООО Клиент 1', size: 32, weight: 800 })); - parts.push(svgText({ x: 72, y: 196, text: 'Компания, реквизиты, заказы, бонусы и реферальные связи клиента', size: 14, fill: palette.subtext })); - parts.push(svgRect({ x: 72, y: 234, width: 412, height: 320, rx: 28, fill: '#fbfbfb' })); - parts.push(cardTitle({ x: 98, y: 268, text: 'Карточка компании', subtitle: 'Юридические и контактные данные' })); - ['ИНН / КПП', 'Менеджер', 'Телефон', 'Email', 'Город', 'Дата регистрации'].forEach((label, index) => { - parts.push(svgText({ x: 98, y: 308 + index * 34, text: `${label}: значение`, size: 13, fill: palette.subtext })); - }); - parts.push(svgRect({ x: 514, y: 234, width: 412, height: 320, rx: 28 })); - parts.push(cardTitle({ x: 540, y: 268, text: 'Контрагент и доставка', subtitle: 'Реквизиты и адреса клиента' })); - ['Банковские реквизиты', 'Юридический адрес', 'Адрес доставки #1', 'Адрес доставки #2'].forEach((label, index) => { - parts.push(svgRect({ x: 540, y: 298 + index * 58, width: 360, height: 40, rx: 14, fill: '#f9fafb' })); - parts.push(svgText({ x: 560, y: 324 + index * 58, text: label, size: 13, fill: palette.subtext })); - }); - parts.push(svgRect({ x: 956, y: 234, width: 412, height: 320, rx: 28, fill: palette.warning, stroke: '#facc15' })); - parts.push(cardTitle({ x: 982, y: 268, text: 'Бонусный контур', subtitle: 'Связанные бонусные сущности клиента' })); - ['Баланс: 12 400', 'Рефералы: 8', 'Активные выводы: 2', 'Переход в бонусный кабинет менеджера'].forEach((label, index) => { - parts.push(svgText({ x: 982, y: 308 + index * 34, text: label, size: 13, fill: palette.subtext })); - }); - - parts.push(svgRect({ x: 72, y: 584, width: 1296, height: 384, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 618, text: 'История заказов клиента', subtitle: 'Заказы, расчеты и состояние отношений с клиентом' })); - parts.push(svgRect({ x: 96, y: 654, width: 1248, height: 42, rx: 12, fill: '#f9fafb' })); - ['Номер', 'Тип', 'Статус', 'Сумма', 'Дата', 'Открыть'].forEach((label, index) => { - parts.push(svgText({ x: 118 + [0, 240, 402, 642, 836, 1086][index], y: 680, text: label, size: 12, weight: 700, fill: palette.subtext })); - }); - for (let i = 0; i < 5; i += 1) { - const y = 710 + i * 50; - parts.push(svgLine({ x1: 96, y1: y, x2: 1344, y2: y })); - parts.push(svgText({ x: 118, y: y + 30, text: `FRG-30${i + 1}`, size: 14, weight: 700 })); - parts.push(svgText({ x: 358, y: y + 30, text: i % 2 === 0 ? 'Заказ' : 'Расчет', size: 13, fill: palette.subtext })); - parts.push(pill({ x: 474, y: y + 10, width: 128, label: i % 2 === 0 ? 'В работе' : 'Нужен расчет', fill: i % 2 === 0 ? palette.success : palette.warning })); - parts.push(svgText({ x: 760, y: y + 30, text: i % 2 === 0 ? '145 000 ₽' : '—', size: 13, weight: 700 })); - parts.push(svgText({ x: 954, y: y + 30, text: '01.05.2026', size: 13, fill: palette.subtext })); - parts.push(svgRect({ x: 1110, y: y + 8, width: 120, height: 30, rx: 15, fill: palette.muted })); - parts.push(svgText({ x: 1170, y: y + 28, text: 'Открыть', size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - } - parts.push(footer()); - return parts.join(''); -} - -function makeCatalogSettings() { - const height = 980; - const parts = [windowFrame({ title: 'Настройки каталога', height })]; - parts.push(svgText({ x: 72, y: 130, text: 'Настройки каталога', size: 30, weight: 800 })); - parts.push(svgText({ x: 72, y: 160, text: 'Управление типами товаров, параметрами и возможностями кастомизации', size: 14, fill: palette.subtext })); - parts.push(svgRect({ x: 72, y: 198, width: 302, height: 704, rx: 28, fill: '#fbfbfb' })); - parts.push(cardTitle({ x: 98, y: 234, text: 'Типы товаров', subtitle: 'Переход между настройками направлений' })); - ['Упаковочный скотч', 'Алюминиевый скотч', 'Крепп', 'Вспененный скотч', 'PVC', 'ПП'].forEach((item, index) => { - parts.push(svgRect({ x: 98, y: 278 + index * 62, width: 250, height: 42, rx: 16, fill: index === 0 ? palette.accent : '#ffffff', stroke: index === 0 ? palette.accentStroke : palette.border })); - parts.push(svgText({ x: 118, y: 305 + index * 62, text: item, size: 13, weight: 700, fill: index === 0 ? '#1d4ed8' : palette.subtext })); - }); - parts.push(svgRect({ x: 404, y: 198, width: 964, height: 704, rx: 28 })); - parts.push(cardTitle({ x: 432, y: 234, text: 'Параметры товарного направления', subtitle: 'Стандартные значения, кастомизация и пояснения по параметрам' })); - const checkboxes = ['Любая длина', 'Логотип на втулке', 'Нанесение надписи']; - checkboxes.forEach((label, index) => { - const y = 286 + index * 46; - parts.push(svgRect({ x: 432, y, width: 24, height: 24, rx: 6, fill: index < 2 ? palette.accent : '#ffffff', stroke: index < 2 ? palette.accentStroke : palette.border })); - if (index < 2) { - parts.push(svgText({ x: 444, y: y + 17, text: '✓', size: 14, weight: 800, fill: '#1d4ed8', anchor: 'middle' })); - } - parts.push(svgText({ x: 470, y: y + 17, text: label, size: 14, weight: 700 })); - }); - const paramGroups = [ - ['Ширина', ['38', '48', '75']], - ['Длина', ['40', '50', '66', '100']], - ['Толщина', ['38', '43', '45']], - ['Втулка', ['стандарт', 'логотип']], - ['Цвет', ['прозрачный', 'коричневый']], - ['Надпись', ['без надписи', 'хрупкое']], - ]; - paramGroups.forEach(([title, values], index) => { - const col = index % 2; - const row = Math.floor(index / 2); - const x = 432 + col * 430; - const y = 446 + row * 116; - parts.push(svgText({ x, y, text: title, size: 14, weight: 700 })); - parts.push(svgRect({ x, y: y + 14, width: 390, height: 74, rx: 18, fill: '#f9fafb' })); - values.forEach((value, tagIndex) => { - const width = Math.max(72, String(value).length * 9 + 28); - parts.push(pill({ x: x + 18 + tagIndex * 92, y: y + 34, width, label: String(value) })); - }); - parts.push(svgText({ x: x + 330, y: y + 64, text: '+', size: 20, weight: 800, fill: '#2563eb' })); - }); - parts.push(svgRect({ x: 432, y: 814, width: 220, height: 48, rx: 24, fill: palette.text, stroke: palette.text })); - parts.push(svgText({ x: 542, y: 844, text: 'Сохранить настройки', size: 14, weight: 700, fill: '#ffffff', anchor: 'middle' })); - parts.push(footer()); - return parts.join(''); -} - -function makeSyncSettings() { - const height = 920; - const parts = [windowFrame({ title: 'Настройки синхронизации', height })]; - parts.push(svgText({ x: 72, y: 130, text: 'Синхронизация и уведомления', size: 30, weight: 800 })); - parts.push(svgText({ x: 72, y: 160, text: 'Статусы обмена, шаблоны сообщений, диагностические ошибки и ручные действия', size: 14, fill: palette.subtext })); - const blocks = [ - { x: 72, y: 198, w: 410, h: 220, title: 'Контур обмена', subtitle: '1С, каталог, остатки, заявки', fill: palette.accent, stroke: palette.accentStroke }, - { x: 512, y: 198, w: 410, h: 220, title: 'Шаблоны сообщений', subtitle: 'Telegram, email, Max', fill: '#ffffff', stroke: palette.border }, - { x: 952, y: 198, w: 416, h: 220, title: 'Последние ошибки', subtitle: 'Журнал нештатных событий', fill: palette.warning, stroke: '#facc15' }, - ]; - blocks.forEach((block) => { - parts.push(svgRect({ x: block.x, y: block.y, width: block.w, height: block.h, rx: 28, fill: block.fill, stroke: block.stroke })); - parts.push(cardTitle({ x: block.x + 24, y: block.y + 36, text: block.title, subtitle: block.subtitle })); - }); - ['Остатки: успешно', 'Каталог: 136 позиций', 'Заказы: webhook включен'].forEach((text, index) => { - parts.push(svgText({ x: 96, y: 288 + index * 24, text, size: 13, fill: palette.subtext })); - }); - ['Шаблон заказа', 'Шаблон расчета', 'Шаблон бонусного уведомления'].forEach((text, index) => { - parts.push(svgText({ x: 536, y: 288 + index * 24, text, size: 13, fill: palette.subtext })); - }); - ['Ошибка 1С: таймаут', 'Ошибка webhook: 500', 'Переотправка из очереди'].forEach((text, index) => { - parts.push(svgText({ x: 976, y: 288 + index * 24, text, size: 13, fill: palette.subtext })); - }); - parts.push(svgRect({ x: 72, y: 448, width: 1296, height: 392, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 482, text: 'Журнал синхронизаций и ручные операции', subtitle: 'История запусков, статусы и диагностические поля' })); - parts.push(svgRect({ x: 96, y: 518, width: 1248, height: 42, rx: 12, fill: '#f9fafb' })); - ['Время', 'Сервис', 'Сценарий', 'Статус', 'Комментарий', 'Действие'].forEach((label, index) => { - parts.push(svgText({ x: 118 + [0, 180, 390, 650, 840, 1090][index], y: 544, text: label, size: 12, weight: 700, fill: palette.subtext })); - }); - for (let i = 0; i < 5; i += 1) { - const y = 574 + i * 52; - parts.push(svgLine({ x1: 96, y1: y, x2: 1344, y2: y })); - parts.push(svgText({ x: 118, y: y + 30, text: `01.05 1${i}:20`, size: 13, fill: palette.subtext })); - parts.push(svgText({ x: 298, y: y + 30, text: i % 2 === 0 ? 'apollo-backend' : 'web-frontend', size: 13, weight: 700 })); - parts.push(svgText({ x: 508, y: y + 30, text: i % 2 === 0 ? 'import catalog' : 'send notifications', size: 13, fill: palette.subtext })); - parts.push(pill({ x: 700, y: y + 10, width: 108, label: i === 2 ? 'Ошибка' : 'Успешно', fill: i === 2 ? palette.danger : palette.success })); - parts.push(svgText({ x: 858, y: y + 30, text: i === 2 ? 'Таймаут 1С' : 'Без замечаний', size: 13, fill: palette.subtext })); - parts.push(svgRect({ x: 1110, y: y + 8, width: 122, height: 30, rx: 15, fill: palette.muted })); - parts.push(svgText({ x: 1171, y: y + 28, text: 'Повторить', size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - } - parts.push(footer()); - return parts.join(''); -} - -function makeBonusManager() { - const height = 980; - const parts = [windowFrame({ title: 'Бонусная система менеджера', height })]; - parts.push(svgText({ x: 72, y: 130, text: 'Бонусная система', size: 30, weight: 800 })); - parts.push(svgText({ x: 72, y: 160, text: 'Рефералы, ручные транзакции, заявки на вывод и переход в карточку бонусного счета', size: 14, fill: palette.subtext })); - parts.push(svgRect({ x: 72, y: 198, width: 1296, height: 104, rx: 28 })); - ['Клиент', 'Тип операции', 'Статус', 'Период'].forEach((label, index) => { - const x = 96 + index * 270; - parts.push(svgText({ x, y: 234, text: label, size: 12, weight: 700, fill: palette.subtext })); - parts.push(svgRect({ x, y: 244, width: 230, height: 32, rx: 16, fill: '#f9fafb' })); - }); - ['Новая реферальная связь', 'Новая транзакция'].forEach((label, index) => { - parts.push(svgRect({ x: 1012 + index * 164, y: 234, width: 148, height: 42, rx: 21, fill: index === 0 ? palette.frame : palette.text, stroke: index === 0 ? palette.border : palette.text })); - parts.push(svgText({ x: 1086 + index * 164, y: 260, text: label, size: 12, weight: 700, fill: index === 0 ? palette.subtext : '#ffffff', anchor: 'middle' })); - }); - parts.push(svgRect({ x: 72, y: 332, width: 1296, height: 548, rx: 28 })); - parts.push(cardTitle({ x: 96, y: 366, text: 'Очередь бонусных сущностей', subtitle: 'Клиенты, начисления, выводы и связанные действия' })); - parts.push(svgRect({ x: 96, y: 402, width: 1248, height: 42, rx: 12, fill: '#f9fafb' })); - ['Клиент', 'Баланс', 'Рефералы', 'Выводы', 'Последнее действие', 'Открыть'].forEach((label, index) => { - parts.push(svgText({ x: 118 + [0, 262, 454, 636, 834, 1098][index], y: 428, text: label, size: 12, weight: 700, fill: palette.subtext })); - }); - for (let i = 0; i < 6; i += 1) { - const y = 458 + i * 58; - parts.push(svgLine({ x1: 96, y1: y, x2: 1344, y2: y })); - parts.push(svgText({ x: 118, y: y + 30, text: `ООО Клиент ${i + 1}`, size: 14, weight: 700 })); - parts.push(svgText({ x: 380, y: y + 30, text: `${(i + 1) * 1500}`, size: 13, weight: 700 })); - parts.push(svgText({ x: 572, y: y + 30, text: String(i + 2), size: 13, weight: 700 })); - parts.push(pill({ x: 688, y: y + 10, width: 118, label: i % 2 === 0 ? 'На проверке' : 'Нет', fill: i % 2 === 0 ? palette.warning : '#f3f4f6' })); - parts.push(svgText({ x: 952, y: y + 30, text: i % 2 === 0 ? 'Заявка на вывод' : 'Начисление за заказ', size: 13, fill: palette.subtext })); - parts.push(svgRect({ x: 1110, y: y + 8, width: 122, height: 30, rx: 15, fill: palette.muted })); - parts.push(svgText({ x: 1171, y: y + 28, text: 'Открыть счет', size: 12, weight: 700, fill: palette.subtext, anchor: 'middle' })); - } - parts.push(footer()); - return parts.join(''); -} - -const prototypes = { - 'login.svg': makeLogin(), - 'profile.svg': makeProfile(), - 'dashboard.svg': makeDashboard(), - 'catalog-grid.svg': makeCatalogGrid(), - 'product-card.svg': makeProductCard(), - 'cart.svg': makeCart(), - 'client-order.svg': makeClientOrder(), - 'client-list.svg': makeClientList(), - 'client-card.svg': makeClientCard(), - 'manager-orders.svg': makeManagerOrders(), - 'manager-order.svg': makeManagerOrder(), - 'catalog-settings.svg': makeCatalogSettings(), - 'sync-settings.svg': makeSyncSettings(), - 'bonus-cabinet.svg': makeBonusCabinet(), - 'bonus-manager.svg': makeBonusManager(), -}; - -mkdirSync(OUTPUT_DIR, { recursive: true }); - -for (const [fileName, contents] of Object.entries(prototypes)) { - writeFileSync(join(OUTPUT_DIR, fileName), contents, 'utf8'); -} - -console.log(`Generated ${Object.keys(prototypes).length} wireframe prototypes.`); diff --git a/docs/scripts/mermaid-config.json b/docs/scripts/mermaid-config.json deleted file mode 100644 index a36c86e..0000000 --- a/docs/scripts/mermaid-config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "theme": "neutral", - "securityLevel": "loose", - "flowchart": { - "useMaxWidth": true, - "htmlLabels": true - }, - "fontFamily": "Inter, Arial, sans-serif" -} diff --git a/docs/scripts/puppeteer-config.json b/docs/scripts/puppeteer-config.json deleted file mode 100644 index 50b1b64..0000000 --- a/docs/scripts/puppeteer-config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "headless": true, - "executablePath": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", - "args": [ - "--no-sandbox", - "--disable-setuid-sandbox", - "--disable-dev-shm-usage" - ] -} diff --git a/docs/scripts/render-mermaid-assets.mjs b/docs/scripts/render-mermaid-assets.mjs deleted file mode 100644 index 0ec52d0..0000000 --- a/docs/scripts/render-mermaid-assets.mjs +++ /dev/null @@ -1,80 +0,0 @@ -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; -import os from 'node:os'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { spawn } from 'node:child_process'; -import { diagramSources } from '../.vitepress/theme/components/diagramSources.ts'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const docsRoot = path.resolve(__dirname, '..'); -const publicRoot = path.join(docsRoot, 'public'); -const diagramsDir = path.join(publicRoot, 'diagrams'); -const prototypesDir = path.join(publicRoot, 'prototypes'); -const puppeteerConfig = path.join(__dirname, 'puppeteer-config.json'); -const mermaidConfig = path.join(__dirname, 'mermaid-config.json'); - -const prototypeNames = new Set([ - 'dashboard', - 'catalog-grid', - 'product-card', - 'cart', - 'client-order', - 'bonus-cabinet', - 'manager-order', - 'manager-orders', -]); - -function runCommand(command, args) { - return new Promise((resolve, reject) => { - const child = spawn(command, args, { - cwd: docsRoot, - stdio: 'inherit', - }); - - child.on('exit', (code) => { - if (code === 0) { - resolve(); - return; - } - - reject(new Error(`${command} ${args.join(' ')} exited with code ${code ?? 'unknown'}`)); - }); - - child.on('error', reject); - }); -} - -async function renderDiagram(name, source) { - const targetDir = prototypeNames.has(name) ? prototypesDir : diagramsDir; - const tmpDir = await mkdtemp(path.join(os.tmpdir(), `mermaid-${name}-`)); - const inputFile = path.join(tmpDir, `${name}.mmd`); - const outputFile = path.join(targetDir, `${name}.svg`); - - try { - await writeFile(inputFile, source, 'utf8'); - await runCommand('pnpm', [ - 'exec', - 'mmdc', - '-i', - inputFile, - '-o', - outputFile, - '-b', - 'transparent', - '-c', - mermaidConfig, - '-p', - puppeteerConfig, - ]); - } finally { - await rm(tmpDir, { recursive: true, force: true }); - } -} - -await mkdir(diagramsDir, { recursive: true }); -await mkdir(prototypesDir, { recursive: true }); - -for (const [name, source] of Object.entries(diagramSources)) { - await renderDiagram(name, source); -} diff --git a/docs/tz/acceptance.md b/docs/tz/acceptance.md deleted file mode 100644 index 10dd98b..0000000 --- a/docs/tz/acceptance.md +++ /dev/null @@ -1,109 +0,0 @@ -# 14. Порядок контроля, приемки и гарантийного сопровождения - -## 14.1 Общие положения приемки - -Приемка результата работ должна подтверждать соответствие программного продукта требованиям настоящего технического задания, условиям договора и согласованным требованиям заказчика. - -При приемке подлежат проверке: - -- клиентский контур -- менеджерский контур -- каталог готовой продукции -- сценарии заявок на заказ -- сценарии заявок на расчет -- сопровождение заказов -- уведомления -- бонусный и реферальный контур -- интеграционный обмен с 1С в согласованном объеме -- пользовательская и эксплуатационная документация в согласованном объеме - -## 14.2 Виды проверок - -Для контроля результата работ используются следующие виды проверок: - -- функциональная проверка основных пользовательских сценариев -- проверка разграничения ролей и прав доступа -- проверка корректности данных, статусов и истории изменений -- проверка интерфейсов на desktop и mobile -- проверка уведомлений по согласованным каналам -- проверка интеграционного обмена с 1С -- проверка запуска и работы сервисов в согласованном эксплуатационном контуре - -## 14.3 Критерии приемки - -Программный продукт считается соответствующим требованиям, если: - -- обязательные пользовательские сценарии выполняются корректно -- разграничение ролей и прав доступа реализовано корректно -- заявкам, заказам и бонусным операциям присваиваются и отображаются корректные статусы -- каталог и остатки отображаются корректно -- цена не отображается клиенту до публикации условий менеджером -- менеджер имеет возможность обработать заявку и опубликовать условия -- история изменений сохраняется и доступна в предусмотренных сценариях -- сведения из 1С отображаются в согласованном объеме -- текущая задолженность клиента и дата актуальности данных отображаются при наличии этих сведений из 1С -- критичные дефекты, препятствующие выполнению основных сценариев, устранены до передачи результата - -## 14.4 Передаваемые материалы - -В состав передаваемых заказчику материалов входят: - -- программный продукт, размещенный в согласованном эксплуатационном контуре -- исходный код разработанных компонентов в репозитории проекта -- согласованная редакция настоящего технического задания -- пользовательская документация в согласованном объеме -- эксплуатационная документация в согласованном объеме -- интеграционная спецификация 1С, если точные форматы обмена фиксируются отдельно от настоящего технического задания -- перечень ключевых сторонних компонентов, сформированный на основании фактических файлов проекта - -Технические схемы, модель данных, роли, архитектура, стек, состав сервисов и требования к интеграциям являются частью настоящего технического задания и не дублируются в отдельных документах без отдельного согласования сторон. - -## 14.5 Порядок фиксации замечаний - -Каждое замечание, выявленное при приемке, должно содержать: - -- описание проблемы -- сценарий воспроизведения -- ожидаемый результат -- фактический результат -- уровень критичности -- статус устранения - -Замечания, не препятствующие выполнению основных пользовательских и интеграционных сценариев, могут быть зафиксированы сторонами для последующего устранения в согласованном порядке. - -## 14.6 Гарантийный срок - -Гарантийный срок на разработанные модули, сервисы и дополнительный функционал составляет 6 месяцев с даты подписания акта приемки выполненных работ, если иной порядок не согласован сторонами. - -Гарантия распространяется на дефекты разработанного программного продукта, проявившиеся при штатной эксплуатации и относящиеся к функционалу, реализованному исполнителем. - -## 14.7 Порядок гарантийного обращения - -Гарантийное обращение должно быть передано исполнителю в письменной форме или иным согласованным сторонами способом. - -В обращении должны быть указаны: - -- описание дефекта -- пользовательская роль или контур, в котором проявляется дефект -- шаги воспроизведения -- ожидаемый результат -- фактический результат -- дата и время обнаружения -- дополнительные материалы, если они нужны для диагностики - -Исполнитель выполняет диагностику дефекта и, если дефект относится к гарантийной зоне ответственности, устраняет его без дополнительной оплаты. - -Срок устранения гарантийного дефекта составляет не более 3 дней с даты получения обращения либо иной срок, согласованный сторонами с учетом критичности и характера дефекта. - -## 14.8 Ограничения гарантийного сопровождения - -Гарантийное сопровождение не распространяется на случаи, когда некорректная работа вызвана: - -- самостоятельным изменением программного продукта заказчиком или третьими лицами без согласования с исполнителем -- ошибками сервера, хостинга, инфраструктуры или базы данных, не связанными с разработанным функционалом -- атакой, компрометацией доступа или нарушением требований информационной безопасности со стороны заказчика -- некорректной работой стороннего программного обеспечения -- недоступностью или некорректной работой внешних систем, включая 1С, Telegram, Max, почтовые сервисы и иные внешние API -- изменением форматов или правил работы внешних систем без предварительного согласования и обновления интеграционной спецификации - -Если дефект связан с внешней системой или инфраструктурой, исполнитель фиксирует результат диагностики и передает заказчику сведения, достаточные для дальнейшего устранения причины на стороне соответствующей системы или поставщика. diff --git a/docs/tz/data-entities.md b/docs/tz/data-entities.md deleted file mode 100644 index bae6825..0000000 --- a/docs/tz/data-entities.md +++ /dev/null @@ -1,484 +0,0 @@ -# 6. Требования к данным и сущностям - -## 6.1 Общие требования к данным - -Основное хранилище данных программного продукта реализуется на `PostgreSQL`. Прикладной доступ к данным осуществляется через `Prisma ORM`. - -Система должна обеспечивать хранение: - -- пользователей и ролей -- компаний и профилей контрагентов -- адресов доставки -- каталога и складских остатков -- корзины и ее позиций -- заказов и расчетных заявок -- событий изменения статусов -- подключений мессенджеров -- бонусных и реферальных сущностей - -В прикладной реализации должны использоваться фактические сущности базы данных, определенные в `schema.prisma`. Наименование сущностей в документации и в базе данных должно сопоставляться однозначно. - -## 6.2 Справочник сущностей базы данных - -| Модель в базе данных | Русское наименование | Назначение | -| --- | --- | --- | -| `Company` | Компания | Клиентская организация | -| `User` | Пользователь | Учетная запись клиента, менеджера или суперменеджера | -| `DeliveryAddress` | Адрес доставки | Справочник адресов доставки клиента | -| `CounterpartyProfile` | Профиль контрагента | Юридические и банковские реквизиты клиента | -| `RegistrationRequest` | Заявка на подключение | Самостоятельная заявка клиента на подключение | -| `Invitation` | Приглашение | Менеджерское приглашение на регистрацию | -| `MessengerConnection` | Подключение мессенджера | Связка пользователя с Telegram или MAX | -| `Product` | Товар | Карточка товарной позиции каталога | -| `CatalogProductTypeSetting` | Настройки типа товара | Правила параметров и кастомизации по товарному направлению | -| `Cart` | Корзина | Корзина клиента | -| `CartItem` | Позиция корзины | Конкретный выбранный товар в корзине | -| `Warehouse` | Склад | Справочник складов | -| `ProductStock` | Складской остаток | Остаток товара на складе | -| `Order` | Заказ / заявка | Единая заказная сущность для готовой продукции и расчета | -| `OrderItem` | Позиция заказа | Состав заказа | -| `OrderStatusEvent` | Событие статуса заказа | История изменения статусов | -| `ReferralLink` | Реферальная связь | Связь между рекомендателем и приглашенным клиентом | -| `BonusTransaction` | Бонусная транзакция | Начисление или списание бонусов | -| `RewardWithdrawalRequest` | Заявка на вывод бонусов | Заявка клиента на использование или вывод бонусов | - -## 6.3 Служебные перечисления и статусы - -В модели данных используются следующие перечисления: - -- `UserRole`: `CLIENT`, `MANAGER`, `SUPER_MANAGER` -- `RegistrationStatus`: `PENDING`, `APPROVED`, `REJECTED` -- `MessengerType`: `TELEGRAM`, `MAX` -- `OrderKind`: `READY`, `CALCULATION` -- `OrderStatus`: `NEW`, `MANAGER_PROCESSING`, `WAITING_DOUBLE_CONFIRM`, `CLIENT_REJECTED`, `MANAGER_REJECTED`, `MANAGER_BLOCKED`, `CONFIRMED`, `IN_PROGRESS`, `COMPLETED` -- `WithdrawalStatus`: `PENDING`, `APPROVED`, `REJECTED` - -## 6.4 Пользователи и компании - -### 6.4.1 Company - -Русское наименование: `Компания` - -Назначение: - -- хранение клиентской организации -- объединение пользователей одной компании - -Основные поля: - -- `id` -- `name` -- `inn` -- `createdAt` -- `updatedAt` - -Связи: - -- одна компания связана со многими пользователями - -### 6.4.2 User - -Русское наименование: `Пользователь` - -Назначение: - -- хранение клиентской, менеджерской или административной учетной записи -- связывание пользователя с заказами, корзиной, бонусами и адресами - -Основные поля: - -- `id` -- `email` -- `fullName` -- `role` -- `companyId` -- `defaultDeliveryAddressId` -- `createdAt` -- `updatedAt` - -Связи: - -- пользователь может быть связан с компанией -- пользователь может иметь профиль контрагента -- пользователь может иметь адреса доставки -- пользователь может иметь корзину -- пользователь может выступать клиентом или менеджером в заказах -- пользователь может иметь бонусные операции и заявки на вывод - -### 6.4.3 DeliveryAddress - -Русское наименование: `Адрес доставки` - -Назначение: - -- хранение адресов доставки клиента -- выбор адреса по умолчанию для корзины и заказов - -Основные поля: - -- `id` -- `userId` -- `label` -- `address` -- `unrestrictedValue` -- `fiasId` -- `createdAt` -- `updatedAt` - -### 6.4.4 CounterpartyProfile - -Русское наименование: `Профиль контрагента` - -Назначение: - -- хранение полных реквизитов клиента для договоров, счетов и поставки - -Основные поля: - -- `id` -- `userId` -- `companyName` -- `companyFullName` -- `inn` -- `kpp` -- `ogrn` -- `legalAddress` -- `bankName` -- `bik` -- `correspondentAccount` -- `checkingAccount` -- `signerFullName` -- `signerPosition` -- `signerBasis` -- `createdAt` -- `updatedAt` - -### 6.4.5 RegistrationRequest - -Русское наименование: `Заявка на подключение` - -Назначение: - -- хранение самостоятельной заявки клиента на подключение - -Основные поля: - -- `id` -- `companyName` -- `inn` -- `contactName` -- `email` -- `status` -- `rejectionReason` -- `requesterId` -- `reviewedById` -- `createdAt` -- `updatedAt` - -### 6.4.6 Invitation - -Русское наименование: `Приглашение` - -Назначение: - -- хранение менеджерского приглашения клиента на регистрацию - -Основные поля: - -- `id` -- `token` -- `email` -- `companyName` -- `managerId` -- `acceptedById` -- `expiresAt` -- `acceptedAt` -- `createdAt` - -### 6.4.7 MessengerConnection - -Русское наименование: `Подключение мессенджера` - -Назначение: - -- хранение подключенного Telegram или MAX-канала пользователя - -Основные поля: - -- `id` -- `userId` -- `type` -- `channelId` -- `displayName` -- `username` -- `avatarFileId` -- `avatarFileUniqueId` -- `isActive` -- `createdAt` - -## 6.5 Каталог и складской контур - -### 6.5.1 Product - -Русское наименование: `Товар` - -Назначение: - -- хранение карточки товарной позиции каталога -- хранение параметров товара и признаков кастомизации - -Основные поля: - -- `id` -- `sku` -- `name` -- `productType` -- `widthMm` -- `lengthM` -- `thicknessMicron` -- `sleeveBrand` -- `quantityPerBox` -- `tags` -- `description` -- `isCustomizable` -- `isActive` -- `createdAt` -- `updatedAt` - -### 6.5.2 CatalogProductTypeSetting - -Русское наименование: `Настройки типа товара` - -Назначение: - -- хранение правил параметров по товарному направлению -- хранение разрешений на кастомизацию - -Основные поля: - -- `id` -- `productType` -- `showQuantityPerBox` -- `allowCustomLength` -- `customLengthMinM` -- `customLengthMaxM` -- `customLengthStepM` -- `allowCustomSleeveBrand` -- `allowCustomLabel` -- `widthOptionsMm` -- `lengthOptionsM` -- `thicknessOptionsMicron` -- `sleeveOptions` -- `colorOptions` -- `labelOptions` -- `createdAt` -- `updatedAt` - -### 6.5.3 Warehouse - -Русское наименование: `Склад` - -Назначение: - -- хранение справочника складов - -Основные поля: - -- `id` -- `code` -- `name` -- `createdAt` -- `updatedAt` - -### 6.5.4 ProductStock - -Русское наименование: `Складской остаток` - -Назначение: - -- хранение остатка товара на конкретном складе - -Основные поля: - -- `id` -- `productId` -- `warehouseId` -- `availableQty` -- `updatedAt` - -## 6.6 Корзина, заявки и заказы - -### 6.6.1 Cart - -Русское наименование: `Корзина` - -Назначение: - -- хранение текущего набора выбранных клиентом позиций - -Основные поля: - -- `id` -- `userId` -- `deliveryAddressId` -- `createdAt` -- `updatedAt` - -### 6.6.2 CartItem - -Русское наименование: `Позиция корзины` - -Назначение: - -- хранение одной выбранной клиентом позиции с параметрами и количеством - -Основные поля: - -- `id` -- `cartId` -- `productId` -- `productName` -- `sku` -- `isCustomizable` -- `quantity` -- `parameters` -- `createdAt` -- `updatedAt` - -### 6.6.3 Order - -Русское наименование: `Заказ / заявка` - -Назначение: - -- хранение готовой заказной заявки и расчетной заявки в единой сущности -- хранение согласованных менеджером условий и статуса работы - -Основные поля: - -- `id` -- `code` -- `kind` -- `customerId` -- `deliveryAddressId` -- `deliveryAddress` -- `managerId` -- `status` -- `clientApproved` -- `managerApproved` -- `blockReason` -- `deliveryTerms` -- `deliveryFee` -- `totalPrice` -- `calculationPayload` -- `createdAt` -- `updatedAt` - -Комментарий к модели: - -- `kind = READY` означает сценарий заказа готовой продукции -- `kind = CALCULATION` означает сценарий расчета индивидуальной продукции -- поле `calculationPayload` хранит параметры расчетной заявки - -### 6.6.4 OrderItem - -Русское наименование: `Позиция заказа` - -Назначение: - -- хранение состава заказа - -Основные поля: - -- `id` -- `orderId` -- `productId` -- `productName` -- `quantity` -- `unitPrice` -- `createdAt` - -### 6.6.5 OrderStatusEvent - -Русское наименование: `Событие статуса заказа` - -Назначение: - -- хранение истории изменения статусов заказа или заявки - -Основные поля: - -- `id` -- `orderId` -- `status` -- `actorUserId` -- `note` -- `createdAt` - -## 6.7 Бонусный и реферальный контур - -### 6.7.1 ReferralLink - -Русское наименование: `Реферальная связь` - -Назначение: - -- фиксация связи между рекомендателем и приглашенным клиентом - -Основные поля: - -- `id` -- `referrerId` -- `refereeId` -- `createdById` -- `bonusPercent` -- `createdAt` - -### 6.7.2 BonusTransaction - -Русское наименование: `Бонусная транзакция` - -Назначение: - -- хранение начисления или списания бонусов - -Основные поля: - -- `id` -- `userId` -- `amount` -- `reason` -- `orderId` -- `referralLinkId` -- `createdAt` - -### 6.7.3 RewardWithdrawalRequest - -Русское наименование: `Заявка на вывод бонусов` - -Назначение: - -- хранение заявки клиента на использование или вывод бонусов - -Основные поля: - -- `id` -- `requesterId` -- `amount` -- `status` -- `reviewedById` -- `reviewComment` -- `createdAt` -- `updatedAt` - -## 6.8 Основные связи между сущностями - -Укрупненная структура связей определяется следующими правилами: - -- `Company` объединяет пользователей одной клиентской организации -- `User` связан с `CounterpartyProfile`, `DeliveryAddress`, `MessengerConnection`, `Cart`, `Order`, `BonusTransaction` и `RewardWithdrawalRequest` -- `Cart` содержит набор `CartItem`, привязанных к конкретным `Product` -- `Order` содержит набор `OrderItem` и историю `OrderStatusEvent` -- `Product` связан с остатками `ProductStock`, распределенными по сущностям `Warehouse` -- настройки параметров по товарному направлению хранятся в `CatalogProductTypeSetting` -- реферальные связи реализуются через `ReferralLink`, связывающий одного пользователя с другим пользователем diff --git a/docs/tz/development-stages.md b/docs/tz/development-stages.md deleted file mode 100644 index ac4042d..0000000 --- a/docs/tz/development-stages.md +++ /dev/null @@ -1,99 +0,0 @@ -# 13. Стадии и этапы разработки - -## 13.1 Общий порядок выполнения работ - -Работы выполняются поэтапно, чтобы согласовывать ключевые решения до перехода к следующей части реализации. - -Переход к следующему этапу выполняется после согласования сторонами результата предыдущего этапа либо после фиксации замечаний, не препятствующих продолжению работ. - -## 13.2 Этап 1. Разработка и согласование технического задания - -На этапе разрабатывается и согласуется настоящее техническое задание. - -Результат этапа: - -- согласованная редакция технического задания -- зафиксированные границы продукта -- зафиксированный состав пользовательских ролей -- зафиксированные функциональные, интеграционные, технические и эксплуатационные требования - -Критерий завершения этапа: утверждение технического задания сторонами. - -## 13.3 Этап 2. UX/UI и согласование визуального подхода - -На этапе подготавливаются 2-3 сверстанные страницы личного кабинета с основными элементами интерфейса. - -В состав страниц для согласования могут входить: - -- страница входа или регистрации -- каталог либо карточка товара -- корзина либо карточка заявки -- менеджерская карточка клиента или заказа - -Результат этапа: - -- согласованный визуальный подход -- согласованные базовые интерфейсные элементы -- подтверждение применимости выбранного подхода для клиентского и менеджерского контуров - -Критерий завершения этапа: согласование визуального подхода сторонами. - -## 13.4 Этап 3. Функциональная реализация без интеграции с 1С - -На этапе реализуются основные пользовательские и менеджерские сценарии без подключения обмена с 1С. - -В состав этапа входят: - -- регистрация и подключение клиентов -- роли и разграничение доступа -- каталог готовой продукции -- корзина и заявки на заказ -- заявки на расчет индивидуальной продукции -- обработка заявок менеджером -- статусы и история изменений -- уведомления в согласованном объеме -- бонусный и реферальный контур -- административные настройки, необходимые для работы продукта - -Результат этапа: - -- работоспособный программный продукт с основным функционалом -- возможность проверки клиентских, менеджерских и бонусных сценариев без обмена с 1С - -Критерий завершения этапа: готовность и приемка основного функционала без интеграции с 1С. - -## 13.5 Этап 4. Интеграция с 1С и отладка обмена - -На этапе выполняются подключение, настройка и отладка интеграции с 1С. - -В состав этапа входят: - -- согласование или уточнение интеграционной спецификации -- настройка приема webhook-событий от 1С -- настройка получения данных из 1С через согласованные методы -- сопоставление внутренних идентификаторов и идентификаторов 1С -- проверка получения каталога, остатков, заказов, статусов и задолженности -- проверка обработки дублей и ошибок обмена -- проверка отображения даты актуальности данных - -Результат этапа: - -- работоспособный интеграционный обмен с 1С в согласованном объеме -- журналирование ключевых интеграционных событий -- подтвержденная работоспособность сценариев, зависящих от данных 1С - -Критерий завершения этапа: подтвержденная сторонами работоспособность сценариев с 1С в согласованном объеме. - -## 13.6 Этап 5. Передача результата и приемка - -На этапе выполняются итоговая проверка, устранение критичных замечаний и передача результата работ. - -Результат этапа: - -- размещенный программный продукт в согласованном эксплуатационном контуре -- согласованная редакция технического задания -- пользовательская и эксплуатационная документация в согласованном объеме -- перечень ключевых сторонних компонентов -- акт приемки выполненных работ - -Критерий завершения этапа: подписание акта приемки либо наступление условий приемки, предусмотренных договором. diff --git a/docs/tz/documentation-requirements.md b/docs/tz/documentation-requirements.md deleted file mode 100644 index 371a51a..0000000 --- a/docs/tz/documentation-requirements.md +++ /dev/null @@ -1,89 +0,0 @@ -# 11. Требования к программной документации - -## 11.1 Общий подход - -Настоящее техническое задание является основным техническим документом программного продукта. - -В составе настоящего технического задания фиксируются: - -- назначение и границы продукта -- функциональные требования -- роли пользователей и права доступа -- требования к данным, сущностям и модели базы данных -- требования к интерфейсам -- требования к интеграциям -- архитектура, стек, компоненты и эксплуатационный контур -- нефункциональные требования -- порядок контроля, приемки и гарантийного сопровождения - -Отдельные документы не должны дублировать техническое задание. Дополнительная документация должна описывать только то, что необходимо для использования, эксплуатации или интеграции программного продукта и не раскрыто в настоящем документе в достаточном объеме. - -## 11.2 Пользовательская документация - -Пользовательская документация должна быть подготовлена в объеме, достаточном для работы пользователей в предусмотренных ролях: - -- клиент -- менеджер -- суперменеджер - -Пользовательская документация должна описывать: - -- вход в личный кабинет и завершение регистрации -- работу с профилем и каналами уведомлений -- просмотр каталога готовой продукции -- добавление товаров в корзину и отправку заявки -- создание заявки на расчет индивидуальной продукции -- просмотр заказов, статусов, условий и истории изменений -- работу с бонусным кабинетом, бонусным балансом и заявками на вывод -- действия менеджера по обработке клиентов, заявок, заказов и бонусных операций -- действия суперменеджера в административных разделах, если они отличаются от действий менеджера - -Документация должна быть написана прикладным языком и ориентирована на выполнение пользовательских сценариев, а не на описание внутренней реализации. - -## 11.3 Эксплуатационная документация - -Эксплуатационная документация должна быть подготовлена в объеме, достаточном для сопровождения программного продукта после передачи результата работ. - -Эксплуатационная документация должна описывать: - -- состав сервисов и их назначение -- порядок запуска и перезапуска сервисов через согласованный контур деплоя -- используемые окружения и общие принципы конфигурации -- порядок загрузки секретов из Vault -- порядок просмотра логов и диагностики типовых сбоев -- порядок проверки работоспособности клиентского, менеджерского и интеграционного контуров -- порядок обновления приложения через Git и Dokploy -- перечень технических контактов или зон ответственности, если они согласованы сторонами - -Эксплуатационная документация не должна содержать бизнес-секреты, токены, пароли и иные чувствительные значения. Для секретов указываются только имена переменных, назначение и источник получения. - -## 11.4 Интеграционная документация - -Для интеграции с 1С должна быть подготовлена интеграционная спецификация либо отдельный раздел настоящего технического задания, если к моменту согласования ТЗ формат обмена уже определен. - -Интеграционная документация должна описывать: - -- состав событий, передаваемых из 1С -- состав методов получения данных из 1С -- структуру payload для каждого события и метода -- обязательные и необязательные поля -- правила сопоставления идентификаторов -- требования к авторизации, подписи или иному механизму защиты запросов -- порядок обработки дублей -- порядок фиксации ошибок и повторной обработки сообщений -- критерии приемки интеграционного обмена - -Если точный формат обмена с 1С не определен на момент утверждения ТЗ, он фиксируется отдельной согласованной интеграционной спецификацией до начала завершающего этапа интеграции. - -## 11.5 Перечень сторонних компонентов - -Перечень сторонних компонентов формируется на основании фактических файлов проекта, включая `package.json`, lock-файлы, Dockerfile и конфигурационные файлы сервисов. - -Перечень должен содержать: - -- наименование компонента -- версию или диапазон версий -- назначение компонента в продукте -- источник установки или репозиторий, если он отличается от стандартного пакетного менеджера - -Ключевые сторонние компоненты, используемые в текущей реализации, перечислены в разделе технической архитектуры настоящего технического задания. diff --git a/docs/tz/economic-indicators.md b/docs/tz/economic-indicators.md deleted file mode 100644 index 6045bd7..0000000 --- a/docs/tz/economic-indicators.md +++ /dev/null @@ -1,25 +0,0 @@ -# 12. Технико-экономические показатели - -## 12.1 Назначение показателей - -Технико-экономические показатели используются для фиксации ожидаемого прикладного эффекта от разработки программного продукта. - -Расчет финансовой эффективности, окупаемости или экономического эффекта в денежном выражении не входит в состав настоящего технического задания, если стороны не согласуют такой расчет отдельно. - -## 12.2 Ожидаемый прикладной эффект - -Разработка программного продукта должна обеспечить: - -- снижение объема ручной коммуникации при приеме и сопровождении заказов -- единый интерфейс для клиента, менеджера и суперменеджера -- ускорение обработки заявок за счет фиксации состава, параметров и статусов в системе -- снижение риска потери информации по заказам, заявкам и бонусным операциям -- повышение прозрачности статусов заказов и актуальности данных для клиента -- централизованное хранение истории изменений -- возможность дальнейшего развития клиентского, менеджерского, бонусного и интеграционного контуров - -## 12.3 Ограничения - -Программный продукт не заменяет учетную систему 1С и не является первичным источником бухгалтерских, складских или финансовых данных. - -Экономический эффект зависит от полноты внедрения продукта в рабочие процессы заказчика, качества данных 1С, доступности внешних каналов уведомлений и соблюдения эксплуатационных требований. diff --git a/docs/tz/functional-requirements.md b/docs/tz/functional-requirements.md deleted file mode 100644 index 7d57c13..0000000 --- a/docs/tz/functional-requirements.md +++ /dev/null @@ -1,183 +0,0 @@ -# 4. Функциональные требования - -## 4.1 Требования к регистрации и подключению клиентов - -Система должна поддерживать два базовых сценария подключения клиента: - -- регистрация по персональному приглашению -- самостоятельная заявка на подключение - -Функциональные требования: - -1. Менеджер должен иметь возможность направить клиенту приглашение на регистрацию по электронной почте. -2. Клиент должен иметь возможность завершить регистрацию по персональной ссылке. -3. Клиент должен иметь возможность подать заявку на подключение через публичную форму. -4. Самостоятельная заявка должна поступать в менеджерский контур на рассмотрение. -5. Менеджер должен иметь возможность подтвердить либо отклонить заявку на подключение. -6. При подтверждении заявки система должна предоставить клиенту возможность завершить регистрацию. -7. После завершения регистрации клиент должен получить доступ к личному кабинету. -8. Система должна поддерживать подключение доступных каналов уведомлений для клиентской учетной записи. - -## 4.2 Требования к каталогу готовой продукции - -Система должна предоставлять клиенту каталог готовой продукции без отображения цены до обработки менеджером. - -Функциональные требования: - -1. Система должна отображать список товарных направлений. -2. Для каждого товарного направления система должна предоставлять отдельную карточку товара. -3. В карточке товара система должна отображать параметры выбора, применимые к данному типу продукции. -4. В карточке товара система должна отображать доступные стандартные варианты. -5. Для каждой доступной позиции система должна отображать складские остатки. -6. Система должна позволять клиенту выбрать параметры и добавить позицию в корзину. -7. Система должна исключать отображение стоимости до момента публикации условий менеджером. -8. Для параметров товара система должна отображать пояснения, помогающие клиенту понять назначение параметра и ограничения выбора. - -## 4.3 Требования к параметрам каталога и кастомизации - -Система должна поддерживать настройку параметров по каждому товарному направлению. - -Функциональные требования: - -1. Для каждого типа продукции должен задаваться перечень стандартных параметров выбора. -2. Для параметров длины должна поддерживаться настройка доступных стандартных значений. -3. Для параметров длины должна поддерживаться возможность индивидуального значения при наличии соответствующего разрешения. -4. Для параметров втулки должна поддерживаться возможность заказа втулки с логотипом при наличии соответствующего разрешения. -5. Для параметров надписи должна поддерживаться возможность заказа индивидуального нанесения при наличии соответствующего разрешения. -6. Наборы стандартных параметров должны редактироваться в административном контуре. -7. Изменение набора стандартных параметров не должно приводить к потере уже сохраненных заказных данных. - -## 4.4 Требования к корзине и заявке на заказ - -Система должна позволять клиенту собрать корзину и направить заявку на заказ. - -Функциональные требования: - -1. Клиент должен видеть перечень выбранных позиций. -2. Для каждой позиции клиент должен иметь возможность изменить количество. -3. Клиент должен иметь возможность удалить позицию из корзины. -4. Клиент должен иметь возможность направить заявку менеджеру. -5. После отправки заявки система должна зафиксировать состав, параметры и количество позиций. -6. Для заявки должны сохраняться дата создания, инициатор и закрепленный менеджер. -7. До обработки менеджером стоимость в заявке не должна отображаться клиенту. - -## 4.5 Требования к обработке заявки менеджером - -Менеджер должен иметь возможность обработать клиентскую заявку вручную. - -Функциональные требования: - -1. Менеджер должен видеть состав заявки и параметры заказанных позиций. -2. Менеджер должен видеть карточку клиента и сведения о контрагенте. -3. Менеджер должен иметь возможность указать стоимость. -4. Менеджер должен иметь возможность указать условия поставки и доставки. -5. Менеджер должен иметь возможность оставить комментарий к заявке. -6. Менеджер должен иметь возможность опубликовать согласованные условия клиенту. -7. До перевода заявки в работу менеджер должен иметь возможность скорректировать опубликованные условия. -8. Менеджер должен иметь возможность перевести заявку в работу. -9. Менеджер должен иметь возможность отменить заявку с фиксацией основания отмены. - -## 4.6 Требования к заявке на расчет индивидуальной продукции - -Система должна поддерживать отдельный сценарий расчета продукции с индивидуальными параметрами. - -Функциональные требования: - -1. Клиент должен иметь возможность перейти из каталога в сценарий расчета индивидуальной продукции. -2. Клиент должен иметь возможность указать параметры изделия. -3. Клиент должен иметь возможность приложить комментарий к заявке. -4. Клиент должен иметь возможность направить заявку менеджеру. -5. Менеджер должен иметь возможность обработать такую заявку по правилам, аналогичным заявке на заказ. -6. Стоимость и условия поставки должны публиковаться клиенту только после ручной обработки менеджером. - -Минимальный состав параметров расчетной заявки должен поддерживать: - -- тип продукции -- ширину -- длину -- толщину -- цвет -- надпись или маркировку -- иные параметры в зависимости от вида продукции -- текстовый комментарий клиента - -## 4.7 Требования к статусам заявок - -Система должна обеспечивать сквозное сопровождение заявок по статусам. - -Для заявок на заказ и заявок на расчет должны поддерживаться следующие базовые статусы: - -- создана -- направлена менеджеру -- обработана менеджером -- условия опубликованы -- в работе -- отменена - -Для каждого изменения статуса система должна сохранять: - -- предыдущее состояние -- новое состояние -- дату и время изменения -- пользователя или источник, выполнивший изменение -- комментарий, если он предусмотрен сценарием - -## 4.8 Требования к заказам и их сопровождению - -Система должна предоставлять клиенту и менеджеру доступ к списку заказов, карточке каждого заказа и актуальным учетным сведениям, полученным из 1С. - -Функциональные требования: - -1. Система должна отображать перечень заказов клиента. -2. Система должна поддерживать фильтрацию заказов по периоду и статусу. -3. Для каждого заказа система должна предоставлять отдельную карточку. -4. В карточке заказа должны отображаться состав, статус, стоимость, условия поставки и история изменений. -5. В карточке заказа должна отображаться дата актуальности данных. -6. При наличии обновлений из внешней системы сведения по заказу должны синхронизироваться и отображаться пользователю. -7. Система должна отображать текущую задолженность клиента, если такие сведения получены из 1С. -8. Для задолженности должна отображаться дата актуальности данных. - -## 4.9 Требования к уведомлениям - -Система должна поддерживать уведомления по нескольким каналам связи. - -Поддерживаемые каналы: - -- электронная почта -- Telegram -- Max - -Система должна поддерживать уведомления по следующим событиям: - -- приглашение к регистрации -- подтверждение либо отклонение заявки на подключение -- публикация условий по заявке -- изменение статуса заказа -- изменение бонусного баланса -- обработка заявки на использование либо вывод бонусов - -## 4.10 Требования к бонусной и реферальной программе - -Система должна включать бонусный контур как самостоятельную функциональную область с отдельным пользовательским интерфейсом. - -Функциональные требования: - -1. Система должна хранить правила участия клиента в бонусной программе. -2. Система должна поддерживать фиксацию реферальных связей. -3. Система должна хранить начисления, списания и текущий остаток бонусов. -4. Клиент должен видеть текущий бонусный баланс. -5. Клиент должен видеть историю бонусных операций. -6. Клиент должен иметь возможность использовать бонусы в пределах установленных правил. -7. Клиент должен иметь возможность подать заявку на вывод либо иную операцию, если это предусмотрено правилами программы. -8. Менеджер должен иметь возможность обрабатывать операции бонусного контура. -9. Система должна уведомлять клиента об изменениях бонусного состояния. - -## 4.11 Требования к административным настройкам - -Система должна содержать административные разделы для управления следующими объектами: - -- параметрами каталога -- пользовательскими описаниями параметров -- шаблонами уведомлений -- параметрами синхронизации -- отдельными настройками бонусного контура diff --git a/docs/tz/index.md b/docs/tz/index.md deleted file mode 100644 index 915b492..0000000 --- a/docs/tz/index.md +++ /dev/null @@ -1,26 +0,0 @@ -# Разделы технического задания - -Настоящий раздел содержит полный состав технического задания на разработку программного продукта `Личный кабинет Фрегат`. - -## Содержание - -1. [Введение, основание, цель и состав проекта](/tz/project-overview) -2. [Основания для разработки и нормативные материалы](/tz/normative-base) -3. [Назначение и границы программного продукта](/tz/product-scope) -4. [Функциональные требования](/tz/functional-requirements) -5. [Роли пользователей и права доступа](/tz/roles-access) -6. [Требования к данным, сущностям и модели базы данных](/tz/data-entities) -7. [Требования к интерфейсу и прототипам](/tz/stage-1/) -8. [Требования к интеграции с 1С и внешним интерфейсам](/tz/integrations) -9. [Техническая архитектура, стек и эксплуатационный контур](/tz/technical-architecture) -10. [Нефункциональные требования](/tz/non-functional-requirements) -11. [Требования к программной документации](/tz/documentation-requirements) -12. [Технико-экономические показатели](/tz/economic-indicators) -13. [Стадии и этапы разработки](/tz/development-stages) -14. [Порядок контроля, приемки и гарантийного сопровождения](/tz/acceptance) - -## Назначение раздела - -Материалы раздела используются для согласования требований к программному продукту, его функциям, данным, интерфейсам, интеграциям и условиям приемки. - -Технические схемы, описание данных, архитектура, роли и интеграции являются частью настоящего технического задания и не требуют дублирования в отдельных документах, если иное не согласовано сторонами. diff --git a/docs/tz/integrations.md b/docs/tz/integrations.md deleted file mode 100644 index dc2745e..0000000 --- a/docs/tz/integrations.md +++ /dev/null @@ -1,137 +0,0 @@ -# 8. Требования к интеграции с 1С и внешним интерфейсам - -## 8.1 Общие требования к интеграционному контуру - -Интеграционный слой должен обеспечивать обмен данными между личным кабинетом и внешними системами без потери целостности внутренних сущностей и статусов. - -Интеграционный контур должен обеспечивать: - -- получение данных из 1С -- прием событий от 1С -- передачу во внешние системы данных, необходимых для сопровождения заказов и клиентов, если такой обмен согласован сторонами -- сопоставление внутренних идентификаторов и идентификаторов внешних систем -- регистрацию входящих и исходящих операций обмена -- повторную обработку неуспешных сообщений -- хранение истории обновлений по интеграционным операциям - -## 8.2 Интеграция с 1С - -Интеграция с 1С должна обеспечивать обмен данными, необходимыми для сопровождения каталога, заказов, статусов, остатков и сведений о задолженности клиента. - -Система должна обеспечивать получение из 1С следующих данных: - -- каталог товаров -- характеристики товаров -- складские остатки -- сведения о заказах -- статусы заказов -- изменения состава, стоимости, доставки и иных существенных параметров заказа -- текущая задолженность клиента -- дата актуальности сведений, полученных из 1С - -1С рассматривается как первичный источник учетных данных по заказам, складам, статусам, стоимости, доставке и задолженности. Личный кабинет отображает эти сведения и фиксирует дату их актуальности. - -## 8.3 События от 1С - -Система должна поддерживать прием webhook-событий от 1С в согласованном формате. - -Минимальный состав событий: - -- создание нового заказа -- изменение информации по заказу -- изменение статуса заказа -- изменение сроков, условий или параметров доставки -- изменение состава заказа -- изменение сведений о задолженности клиента, если такие данные передаются событийно - -Для каждого события должны фиксироваться: - -- тип события -- внешний идентификатор объекта 1С -- внутренний идентификатор объекта, если сопоставление выполнено -- дата и время события -- источник события -- статус обработки -- текст ошибки при неуспешной обработке - -Система должна защищаться от повторной обработки дублей webhook-событий. - -## 8.4 Методы получения данных из 1С - -Система должна поддерживать получение данных из 1С через согласованные методы. - -Минимальный состав методов: - -- получение заказов клиента -- получение товарного каталога -- получение характеристик товаров -- получение доступных остатков по складам -- получение статусов и изменений по заказам -- получение текущей задолженности клиента и даты актуальности этих сведений - -Точный набор методов, параметры запросов, формат ответов и ограничения по частоте вызовов фиксируются в интеграционной спецификации. - -## 8.5 Требования к структурам обмена - -Входные и выходные данные интеграционного слоя должны описываться в структурированном виде и позволять однозначно восстанавливать: - -- тип объекта обмена -- идентификатор объекта -- дату и время события -- полезную нагрузку объекта -- статус обработки -- источник изменения - -Для обмена должны использоваться структурированные payload-форматы, пригодные для сериализации в `JSON`. - -Точная структура payload, схема подписи запросов и набор обязательных полей согласуются сторонами до начала этапа интеграции с 1С. - -## 8.6 Интеграционные поля и служебные атрибуты - -Для сущностей, участвующих в обмене, должны поддерживаться: - -- внешний идентификатор учетной системы -- дата последней синхронизации -- источник последнего обновления -- признак успешной или неуспешной обработки -- журнал интеграционных ошибок при наличии -- технический идентификатор последнего обработанного события, если он передается 1С - -## 8.7 Журналирование интеграционных операций - -Для ключевых операций обмена система должна сохранять: - -- тип объекта -- идентификатор объекта -- предыдущее состояние -- новое состояние -- дату и время изменения -- пользователя либо внешний источник, выполнивший изменение -- статус обработки сообщения -- текст ошибки при наличии - -## 8.8 Требования к защите интеграционного обмена - -Интеграционные запросы должны выполняться с использованием согласованного механизма авторизации или подписи. - -Система должна отклонять интеграционные запросы, если: - -- отсутствуют обязательные параметры авторизации -- подпись или токен не прошли проверку -- payload не соответствует согласованной структуре -- невозможно определить тип события или объект обработки - -Секреты, используемые для интеграции с 1С, должны храниться только в Vault и передаваться сервисам через runtime-конфигурацию. - -## 8.9 Критерии приемки интеграции с 1С - -Интеграция с 1С считается готовой в согласованном объеме, если: - -- каталог и характеристики товаров получаются и отображаются в личном кабинете -- остатки по складам отображаются в карточках товаров -- заказы клиента получаются и отображаются с актуальными статусами -- изменения заказа из 1С отображаются в карточке заказа -- текущая задолженность клиента и дата актуальности данных отображаются в предусмотренных интерфейсах -- webhook-события не обрабатываются повторно при дублях -- ошибки интеграционного обмена фиксируются в журнале -- неуспешные сообщения могут быть проанализированы и повторно обработаны в согласованном порядке diff --git a/docs/tz/non-functional-requirements.md b/docs/tz/non-functional-requirements.md deleted file mode 100644 index cdcdf75..0000000 --- a/docs/tz/non-functional-requirements.md +++ /dev/null @@ -1,69 +0,0 @@ -# 10. Нефункциональные требования - -## 10.1 Общие требования к архитектуре - -Программный продукт должен быть реализован как веб-система с разделением клиентского и менеджерского контуров, серверной бизнес-логикой, постоянным хранением данных и возможностью интеграционного обмена с внешними системами. - -## 10.2 Требования к доступности интерфейсов - -Система должна обеспечивать корректную работу: - -- в десктопных браузерах -- в мобильных браузерах -- на основных пользовательских разрешениях экрана - -Интерфейсы должны сохранять работоспособность и читаемость при адаптивном отображении. - -## 10.3 Требования к производительности - -При штатной эксплуатации система должна обеспечивать: - -- приемлемое время открытия основных экранов -- приемлемое время отправки заявок и выполнения пользовательских действий -- отображение каталогов, карточек и заказов без заметных задержек при типовом объеме данных - -Точные количественные показатели производительности подлежат фиксации в рабочей документации по инфраструктуре и тестированию. - -## 10.4 Требования к безопасности - -Система должна обеспечивать: - -- аутентификацию пользователей -- авторизацию по ролям -- ограничение доступа клиента только к данным своего контрагента -- хранение и передачу чувствительных данных с использованием защищенных механизмов -- исключение несанкционированного доступа к административным функциям - -## 10.5 Требования к надежности и журналированию - -Система должна обеспечивать: - -- сохранность пользовательских данных -- сохранность истории изменений по заявкам, заказам и бонусным операциям -- фиксацию ошибок интеграционного обмена -- фиксацию значимых системных и пользовательских событий - -## 10.6 Требования к сопровождаемости - -Реализация должна обеспечивать возможность: - -- сопровождения и развития клиентского контура -- сопровождения и развития менеджерского контура -- изменения параметров каталога и уведомлений без переработки базовой структуры системы -- расширения интеграционного обмена с 1С и иными внешними системами - -## 10.7 Требования к данным и актуальности сведений - -Система должна обеспечивать: - -- хранение актуального состояния пользовательских данных -- отображение даты актуальности сведений, полученных из внешних систем, когда это применимо -- защиту от потери данных при обновлении параметров каталога и заказных сущностей - -## 10.8 Требования к документации - -По результатам выполнения работ должна быть сформирована документация, достаточная для: - -- приемки результата работ -- дальнейшего сопровождения программного продукта -- понимания состава функций, данных, ролей и интеграций diff --git a/docs/tz/normative-base.md b/docs/tz/normative-base.md deleted file mode 100644 index a2ae22e..0000000 --- a/docs/tz/normative-base.md +++ /dev/null @@ -1,37 +0,0 @@ -# 2. Основания для разработки и нормативные материалы - -## 2.1 Основания для разработки - -Разработка программного продукта выполняется на основании следующих документов: - -- договор на разработку программного продукта №28/04-26ПО от 28 апреля 2026 года -- приложение №1 к договору: спецификация основных требований к программному обеспечению `Личный кабинет Фрегат` -- согласованные требования заказчика к клиентскому, менеджерскому, бонусному и интеграционному контурам - -## 2.2 Нормативные и методические материалы - -Настоящее техническое задание подготовлено как документ для согласования требований к разработке веб-программного продукта. - -Настоящий документ устанавливает требования к программному продукту применительно к современному веб-решению с разграничением ролей, интеграцией с учетной системой, журналированием событий и поддержкой клиентских и менеджерских сценариев. - -## 2.3 Исходные материалы для детализации требований - -При разработке технического задания использованы следующие исходные материалы: - -- договорная документация заказчика -- согласованные бизнес-сценарии работы клиента и менеджера -- перечень разделов личного кабинета -- перечень основных товарных направлений и параметров каталога -- требования к обмену данными с учетной системой 1С -- требования к уведомлениям, бонусной программе и административным настройкам - -## 2.4 Назначение настоящего документа - -Настоящий документ предназначен для: - -- фиксации полного объема требований к программному продукту -- определения состава реализуемых функций -- определения состава данных и интеграционных точек -- определения требований к пользовательским и административным интерфейсам -- определения критериев приемки результата работ -- определения состава артефактов, передаваемых заказчику diff --git a/docs/tz/product-scope.md b/docs/tz/product-scope.md deleted file mode 100644 index 73b40b4..0000000 --- a/docs/tz/product-scope.md +++ /dev/null @@ -1,97 +0,0 @@ -# 3. Назначение и границы программного продукта - -## 3.1 Назначение системы - -Программный продукт `Личный кабинет Фрегат` предназначен для организации единого цифрового канала взаимодействия между ООО `Фрегат Групп` и клиентами компании. - -Система должна обеспечивать: - -- подключение новых клиентов и ведение клиентских учетных записей -- предоставление клиенту каталога готовой продукции без публичного отображения цены -- прием заявок на заказ готовой продукции -- прием заявок на расчет продукции с индивидуальными параметрами -- обработку заявок менеджером с публикацией стоимости и условий поставки -- сопровождение заказов по статусам -- информирование клиентов о значимых изменениях -- ведение бонусной и реферальной программы - -## 3.2 Границы программного продукта - -В состав программного продукта входят следующие функциональные области: - -- регистрация и подключение клиента -- профиль клиента и данные компании -- каталог готовой продукции -- карточка товара и выбор параметров -- корзина и заявка на заказ -- заявка на расчет индивидуальной продукции -- обработка заявок менеджером -- список заказов и карточка заказа -- уведомления -- бонусный кабинет -- административные настройки - -## 3.3 Функции, не входящие в состав программного продукта - -Программный продукт не предназначен для выполнения следующих функций: - -- самостоятельного ценообразования клиентом -- ведения бухгалтерского учета -- выполнения функций публичного B2C-магазина -- прямого редактирования клиентом внутренних бизнес-правил компании -- замены учетной системы 1С как первичного источника учетных данных - -## 3.4 Пользовательские контуры - -В системе должны быть предусмотрены следующие пользовательские контуры: - -- клиентский контур -- менеджерский контур -- административный контур суперменеджера - -Клиентский контур предназначен для работы клиента с каталогом, заявками, заказами, уведомлениями и бонусным кабинетом. - -Менеджерский контур предназначен для обработки клиентских заявок, публикации коммерческих условий, сопровождения заказов, работы с клиентскими карточками и бонусными операциями. - -Административный контур предназначен для управления настройками каталога, уведомлений, интеграционных параметров и отдельных сервисных настроек системы. - -## 3.5 Основные бизнес-сценарии - -### 3.5.1 Подключение клиента - -1. Потенциальный клиент получает приглашение на регистрацию либо подает заявку на подключение самостоятельно. -2. Менеджер проверяет сведения о клиенте и принимает решение о подтверждении либо отклонении заявки. -3. При положительном решении клиенту предоставляется доступ к завершению регистрации. -4. После завершения регистрации клиент получает доступ к личному кабинету. - -### 3.5.2 Заказ готовой продукции - -1. Клиент открывает каталог готовой продукции. -2. Клиент выбирает товарное направление. -3. Клиент выбирает параметры товара. -4. Клиент просматривает доступные варианты и остатки. -5. Клиент добавляет выбранные позиции в корзину. -6. Клиент отправляет заявку на заказ. -7. Менеджер указывает стоимость, условия поставки и комментарий. -8. Система публикует обновленные условия клиенту. - -### 3.5.3 Заявка на расчет индивидуальной продукции - -1. Клиент переходит в режим расчета индивидуальной продукции. -2. Клиент указывает параметры требуемого изделия. -3. Клиент направляет заявку менеджеру. -4. Менеджер подготавливает коммерческие условия и публикует их клиенту. - -### 3.5.4 Сопровождение заказа - -1. Заказ получает уникальный идентификатор и статус. -2. Данные по заказу обновляются в системе по мере обработки. -3. Клиент отслеживает состав, статус, сроки и иные существенные сведения. -4. При изменении статуса либо условий система направляет уведомления. - -### 3.5.5 Бонусная и реферальная программа - -1. Для клиента фиксируются правила участия в бонусной программе и, при наличии, реферальные связи. -2. Система ведет учет начислений, списаний и остатка бонусов. -3. Клиент получает доступ к истории бонусных операций. -4. Менеджер обрабатывает заявки, связанные с использованием либо выводом бонусов, в пределах установленных правил. diff --git a/docs/tz/project-overview.md b/docs/tz/project-overview.md deleted file mode 100644 index d7659c9..0000000 --- a/docs/tz/project-overview.md +++ /dev/null @@ -1,79 +0,0 @@ -# 1. Введение, основание, цель и состав проекта - -Настоящее техническое задание описывает разработку программного продукта `Личный кабинет Фрегат` для поддержки клиентских, менеджерских, заказных, бонусных и интеграционных сценариев в едином веб-интерфейсе. - -Документ используется для согласования состава работ, функциональных и технических требований, этапов разработки, требований к программной документации, порядка приемки и гарантийного сопровождения. - -## 1.1 Основание для разработки - -Разработка программного продукта выполняется на основании следующих документов и материалов: - -- договор на разработку программного продукта №28/04-26ПО от 28 апреля 2026 года -- приложение №1 к договору: спецификация основных требований к программному обеспечению `Личный кабинет Фрегат` -- согласованные требования заказчика к клиентскому, менеджерскому, бонусному и интеграционному контурам - -## 1.2 Цель разработки - -Программный продукт `Личный кабинет Фрегат` предназначен для организации единого цифрового канала взаимодействия между ООО `Фрегат Групп` и B2B-клиентами компании. - -Система должна обеспечивать: - -- подключение и регистрацию клиентов -- выбор готовой продукции из каталога -- подачу заявок на заказ готовой продукции -- подачу заявок на расчет продукции с индивидуальными параметрами -- согласование стоимости и условий поставки -- сопровождение заказов по статусам -- отправку клиентских уведомлений -- ведение бонусной и реферальной программы - -## 1.3 Объект автоматизации - -Объектом автоматизации являются процессы клиентского обслуживания и внутренней обработки заявок, выполняемые менеджерами ООО `Фрегат Групп` при работе с готовой и индивидуальной продукцией. - -## 1.4 Состав системы - -В состав программного продукта входят: - -- клиентский веб-интерфейс -- менеджерский веб-интерфейс -- серверная бизнес-логика -- база данных -- модуль синхронизации с внешними системами -- модуль уведомлений -- модуль бонусной программы -- модуль административных настроек - -## 1.5 Границы реализации - -В состав программного продукта входят следующие функциональные области: - -- регистрация и подключение клиента -- профиль клиента и данные компании -- каталог готовой продукции -- карточка товара и выбор параметров -- корзина и заявка на заказ -- заявка на расчет индивидуальной продукции -- обработка заявок менеджером -- список заказов и карточка заказа -- уведомления -- бонусный кабинет -- административные настройки - -Программный продукт не предназначен для выполнения следующих функций: - -- самостоятельного ценообразования клиентом -- ведения бухгалтерского учета -- выполнения функций публичного B2C-магазина -- прямого редактирования клиентом внутренних бизнес-правил компании -- замены учетной системы 1С как первичного источника учетных данных - -## 1.6 Основные принципы работы - -Система должна обеспечивать следующие базовые принципы: - -- доступ к функциям и данным определяется ролью пользователя -- клиент работает только в пределах собственных данных и данных своего контрагента -- стоимость товара и условия поставки публикуются только после обработки менеджером -- история изменений по заявкам, заказам и бонусным операциям фиксируется в системе -- сведения о товарах, остатках, заказах и статусах могут обновляться из внешней учетной системы diff --git a/docs/tz/roles-access.md b/docs/tz/roles-access.md deleted file mode 100644 index f063701..0000000 --- a/docs/tz/roles-access.md +++ /dev/null @@ -1,90 +0,0 @@ -# 5. Требования к ролям и правам доступа - -## 5.1 Состав ролей - -В системе должны быть предусмотрены следующие роли пользователей: - -- клиент -- менеджер -- суперменеджер - -## 5.2 Роль клиента - -Пользователь с ролью `Клиент` представляет организацию-контрагента и работает только с данными своей компании. - -Клиенту должны быть доступны следующие действия: - -- завершение регистрации по персональному приглашению -- подача заявки на подключение -- просмотр и изменение разрешенных профильных данных -- подключение доступных каналов уведомлений -- просмотр каталога готовой продукции -- выбор параметров товара -- добавление позиций в корзину -- отправка заявки на заказ -- отправка заявки на расчет -- просмотр списка заявок и заказов -- просмотр карточки заявки и карточки заказа -- просмотр истории уведомлений -- просмотр бонусного баланса и истории бонусных операций -- подача заявки на использование либо вывод бонусов при наличии соответствующих правил - -## 5.3 Роль менеджера - -Пользователь с ролью `Менеджер` представляет сотрудника компании, закрепленного за клиентами. - -Менеджеру должны быть доступны следующие действия: - -- рассмотрение заявок на подключение клиентов -- подтверждение либо отклонение заявок на подключение -- привязка клиента к контрагенту и назначение ответственного сопровождения -- просмотр и обработка заявок на заказ -- просмотр и обработка заявок на расчет -- указание стоимости, условий поставки и сопроводительного комментария -- публикация условий клиенту -- перевод заявки в работу -- отмена заявки при наличии оснований -- просмотр карточек клиентов, заявок и заказов -- выполнение операций в бонусном контуре в пределах полномочий - -## 5.4 Роль суперменеджера - -Пользователь с ролью `Суперменеджер` обладает всеми правами менеджера и дополнительными административными полномочиями. - -Суперменеджеру должны быть доступны следующие действия: - -- доступ ко всем клиентам, заявкам и заказам -- управление параметрами каталога -- управление описаниями и наборами параметров товаров -- управление настройками уведомлений -- управление параметрами интеграции и синхронизации -- расширенное управление бонусным и реферальным контуром - -## 5.5 Матрица доступа - -| Действие | Клиент | Менеджер | Суперменеджер | -| --- | --- | --- | --- | -| Завершение регистрации | Да | Нет | Нет | -| Подача заявки на подключение | Да | Нет | Нет | -| Просмотр каталога | Да | Да | Да | -| Выбор параметров и добавление в корзину | Да | Нет | Нет | -| Отправка заявки на заказ | Да | Нет | Нет | -| Отправка заявки на расчет | Да | Нет | Нет | -| Назначение стоимости и условий поставки | Нет | Да | Да | -| Публикация условий клиенту | Нет | Да | Да | -| Перевод заявки в работу | Нет | Да | Да | -| Отмена заявки | Нет | Да | Да | -| Управление параметрами каталога | Нет | Нет | Да | -| Управление уведомлениями | Нет | Нет | Да | -| Управление параметрами синхронизации | Нет | Нет | Да | -| Управление бонусными правилами | Нет | Да | Да | - -## 5.6 Ограничения доступа и требования к безопасности - -Система должна обеспечивать: - -- раздельные интерфейсы для клиентского и менеджерского контуров -- доступ клиента только к данным собственного контрагента -- ограничение административных функций в соответствии с ролью -- журналирование значимых пользовательских действий -- хранение истории изменения статусов, условий заявок и бонусных операций diff --git a/docs/tz/stage-1/index.md b/docs/tz/stage-1/index.md deleted file mode 100644 index 657d197..0000000 --- a/docs/tz/stage-1/index.md +++ /dev/null @@ -1,423 +0,0 @@ -# 7. Требования к интерфейсу и прототипам - -## 7.1 Карта экранов - -Ниже приведен базовый состав экранов, подлежащих реализации и сопровождению в рамках программного продукта. - -| Раздел | Маршрут | Роль | Назначение | -| --- | --- | --- | --- | -| Главная страница | `/` | клиент | стартовая точка, быстрые действия, актуальные события | -| Каталог | `/products` | клиент | выбор товарного направления | -| Карточка товара | `/products/[slug]` | клиент | выбор параметров, просмотр вариантов, добавление в корзину | -| Корзина | `/cart` | клиент | формирование и отправка заявки | -| Список заказов клиента | `/client-orders` | клиент | просмотр истории заявок и заказов | -| Карточка заказа клиента | `/client-orders/[id]` | клиент | просмотр статуса, состава, условий и истории | -| Профиль | `/profile` | клиент | базовые данные учетной записи | -| Контрагент | `/profile/counterparty` | клиент | реквизиты и юридические данные | -| Адреса доставки | `/profile/addresses` | клиент | адресный справочник | -| Уведомления | `/notifications` | клиент | история уведомлений | -| Бонусный кабинет | `/bonus-program` | клиент | баланс, история и бонусные действия | -| Список клиентов | `/clients` | менеджер | клиентская база | -| Карточка клиента | `/clients/[id]` | менеджер | данные компании, заказы, бонусы | -| Приглашение клиента | `/clients/invite` | менеджер | выдача приглашения на регистрацию | -| Список заказов | `/orders` | менеджер | обработка заказного контура | -| Карточка заказа | `/orders/[id]` | менеджер | обработка условий, статуса и доставки | -| Настройки каталога | `/catalog-settings` | суперменеджер | параметры товарных направлений | -| Настройки синхронизации | `/settings-sync` | суперменеджер | мониторинг и управление обменом | -| Бонусная система | `/bonus-system/*` | менеджер/суперменеджер | рефералы, транзакции, выводы | - -## 7.2 Маршруты и экранные формы - -Ниже приведен перечень экранных форм, предусмотренных в составе frontend-контура программного продукта. - -### 7.2.1 Публичные и клиентские страницы - -| Маршрут | Экран | Назначение | -| --- | --- | --- | -| `/` | Главная страница | Стартовая страница личного кабинета | -| `/login` | Вход | Вход и первичный сценарий доступа | -| `/products` | Каталог продукции | Список товарных направлений | -| `/products/[slug]` | Карточка товара | Выбор параметров, просмотр вариантов, добавление в корзину | -| `/cart` | Корзина | Состав выбранных позиций и отправка заявки | -| `/client-orders` | Список заказов клиента | История заявок и заказов клиента | -| `/client-orders/[id]` | Карточка заказа клиента | Детали конкретного заказа клиента | -| `/notifications` | Уведомления | Список системных уведомлений | -| `/bonus-program` | Бонусный кабинет | Бонусный баланс, подарочные карты и бонусные действия | - -### 7.2.2 Профиль клиента - -| Маршрут | Экран | Назначение | -| --- | --- | --- | -| `/profile` | Профиль | Основные данные пользователя | -| `/profile/counterparty` | Реквизиты контрагента | Юридические и банковские данные | -| `/profile/addresses` | Адреса доставки | Список адресов доставки | -| `/profile/addresses/new` | Новый адрес доставки | Создание адреса доставки | -| `/profile/notifications` | Настройки уведомлений | Подключение и настройка каналов уведомлений | -| `/profile/notifications/success` | Успешное подключение уведомлений | Финальный экран сценария подключения канала | - -### 7.2.3 Менеджерские и административные страницы - -| Маршрут | Экран | Назначение | -| --- | --- | --- | -| `/clients` | Клиенты | Список клиентских компаний и пользователей | -| `/clients/[id]` | Карточка клиента | Детали клиента, его заказы и бонусные данные | -| `/clients/invite` | Пригласить клиента | Создание приглашения на регистрацию | -| `/orders` | Список заказов | Очередь заказов и заявок для менеджера | -| `/orders/[id]` | Карточка заказа | Обработка стоимости, условий поставки и статуса | -| `/catalog-settings` | Настройки каталога | Параметры товарных направлений и кастомизации | -| `/settings-sync` | 1С / синхронизация | Управление и мониторинг синхронизации | -| `/messages` | Сообщения | Шаблоны и тексты менеджерских сообщений | - -### 7.2.4 Бонусный менеджерский контур - -| Маршрут | Экран | Назначение | -| --- | --- | --- | -| `/bonus-system` | Бонусная система | Список клиентов и бонусных сущностей | -| `/bonus-system/[userId]` | Карточка бонусного счета клиента | История и состояние бонусного счета | -| `/bonus-system/referrals/new` | Создать бонусный счет | Создание реферальной связи | -| `/bonus-system/transactions/new` | Добавить бонусную транзакцию | Ручное начисление или списание | -| `/bonus-system/withdrawals/[id]` | Проверка заявки на вывод | Рассмотрение заявки клиента на вывод бонусов | - -## 7.3 Общие требования к экранным формам - -Экранные формы должны обеспечивать: - -- однозначное понимание текущего объекта работы -- явное отображение статуса объекта -- соответствие доступных действий роли пользователя -- единый визуальный подход для клиентского и менеджерского контуров -- понятное отображение параметров товара, условий заказа и бонусных операций - -Для экранов, связанных с товарами, заявками и заказами, должны выполняться дополнительные требования: - -- цена не отображается клиенту до публикации условий менеджером -- остатки и доступные варианты отображаются в наглядном виде -- пользователь понимает ограничения выбора и возможность кастомизации - -Ниже приведены низкодетализированные wireframe-прототипы. Они используются как визуальная фиксация состава страниц, ключевых блоков и пользовательских действий. - -## 7.4 Клиентские экранные формы - -### 7.4.1 Главная страница клиента - -Назначение страницы: - -- вход в основные разделы личного кабинета -- отображение актуальных действий и событий - -Состав страницы: - -- верхняя навигация -- быстрые переходы по основным разделам -- блок актуальных заказов и заявок -- блок последних уведомлений -- блок бонусной информации при наличии подключенного бонусного контура -- индикатор статуса заполненности профиля клиента - -Wireframe-прототип: - - - -### 7.4.2 Каталог продукции - -Назначение страницы: - -- отображение товарных направлений -- переход к карточке выбранного типа товара - -Состав страницы: - -- заголовок раздела -- поиск при необходимости -- сетка карточек товарных направлений -- карточка каждого товарного направления с изображением и наименованием -- переход в карточку выбранного товарного направления - -Wireframe-прототип: - - - -### 7.4.3 Карточка товара - -Назначение страницы: - -- выбор параметров товара -- просмотр стандартных вариантов -- просмотр складских остатков -- добавление выбранной позиции в корзину - -Состав страницы: - -- заголовок товара -- изображение товара -- блок выбора параметров -- блок пояснений по параметрам -- блок индивидуальных возможностей, если они разрешены -- блок добавления в корзину -- таблица доступных вариантов -- блок навигации к соседним товарным направлениям - -Маршрут страницы: - -- `/products/[slug]` - -Wireframe-прототип: - - - -Состав блока выбора параметров: - -- ширина -- длина -- толщина -- тип втулки -- цвет -- надпись -- индивидуальные опции при наличии разрешения - -Состав блока пояснений: - -- описание каждого параметра простым деловым языком -- ограничения по индивидуальной длине -- правила по втулке с логотипом -- правила по нанесению индивидуальной надписи - -### 7.4.4 Корзина - -Назначение страницы: - -- просмотр выбранных позиций -- изменение количества -- удаление позиции -- отправка заявки на заказ - -Состав страницы: - -- список позиций -- параметры и количество -- комментарий клиента -- действие отправки заявки -- выбранный адрес доставки -- итоговая сводка по количеству позиций - -Wireframe-прототип: - - - -### 7.4.5 Карточка заявки или заказа - -Назначение страницы: - -- просмотр состава -- просмотр статуса -- просмотр стоимости и условий поставки после публикации -- просмотр истории изменений - -Wireframe-прототип: - - - -Состав страницы: - -- номер документа -- статус -- состав позиций -- стоимость после публикации менеджером -- условия поставки и доставки -- история статусов -- системные комментарии - -### 7.4.6 Страница логина и регистрации - -Назначение страницы: - -- запуск входа в систему -- запуск самостоятельной заявки на подключение -- запуск сценариев входа через мессенджеры - -Состав страницы: - -- форма запроса кода входа -- выбор канала входа -- ссылка на самостоятельную заявку на подключение -- блок пояснения по дальнейшему сценарию доступа - -Wireframe-прототип: - - - -### 7.4.7 Список заказов - -Назначение страницы: - -- просмотр текущих и архивных заказов -- фильтрация по периоду и статусу - -Состав страницы: - -- фильтры -- таблица заказов -- переход в карточку конкретного заказа - -### 7.4.8 Уведомления - -Назначение страницы: - -- просмотр истории уведомлений по заказам, заявкам и бонусным операциям - -### 7.4.9 Бонусный кабинет - -Назначение страницы: - -- просмотр текущего бонусного баланса -- просмотр истории операций -- инициирование действий, допускаемых правилами бонусной программы - -Состав страницы: - -- текущий баланс -- история начислений и списаний -- связанные реферальные сведения -- форма подачи заявки на использование либо вывод бонусов -- правила бонусной программы - -Wireframe-прототип: - - - -## 7.5 Менеджерские экранные формы - -### 7.5.1 Список клиентов - -Назначение страницы: - -- просмотр клиентской базы -- переход в карточку конкретного клиента - -Состав страницы: - -- поиск и фильтры -- таблица клиентов -- индикаторы активности и количества заказов -- действие приглашения нового клиента - -Wireframe-прототип: - - - -### 7.5.2 Карточка клиента - -Назначение страницы: - -- просмотр сведений о компании -- просмотр истории заявок и заказов -- просмотр бонусных и реферальных данных - -Состав страницы: - -- карточка компании и контактных данных -- реквизиты контрагента -- список заказов клиента -- список бонусных операций -- связанные рефералы - -Wireframe-прототип: - - - -### 7.5.3 Карточка обработки заявки - -Назначение страницы: - -- просмотр состава заявки -- ввод коммерческих условий -- публикация условий клиенту -- перевод заявки в работу либо отмена - -Wireframe-прототип: - - - -Состав страницы: - -- клиент и контрагент -- состав позиции или расчетный payload -- стоимость -- доставка -- комментарий менеджера -- история изменений -- блок действий со статусом - -### 7.5.4 Список заказов менеджера - -Назначение страницы: - -- просмотр заказов по клиентам -- фильтрация по статусам -- переход к обработке конкретного заказа - -Wireframe-прототип: - - - -### 7.5.5 Настройки каталога - -Назначение страницы: - -- управление параметрами товарных направлений -- управление стандартными значениями параметров -- управление возможностями кастомизации -- управление описаниями параметров - -Состав страницы: - -- список товарных направлений -- карточка настроек конкретного направления -- чекбоксы разрешений кастомизации -- списки стандартных параметров -- единое действие сохранения настроек - -Wireframe-прототип: - - - -### 7.5.6 Настройки синхронизации и уведомлений - -Назначение страницы: - -- управление шаблонами уведомлений -- управление параметрами интеграционного обмена - -Состав страницы: - -- список шаблонов уведомлений -- каналы отправки -- статусы последних синхронизаций -- диагностическая информация по обмену - -Wireframe-прототип: - - - -## 7.6 Дополнительные профильные и сервисные страницы - -Помимо основных клиентских и менеджерских экранов, программный продукт включает дополнительные экранные формы: - -- профиль пользователя -- реквизиты контрагента -- список адресов доставки -- создание нового адреса доставки -- настройки уведомлений пользователя -- экран успешного подключения канала уведомлений -- менеджерский экран сообщений -- бонусная система для менеджера -- карточка бонусного счета клиента -- создание реферальной связи -- создание бонусной транзакции -- проверка заявки на вывод бонусов - -Прототипы служебных и дополнительных экранов: - - - - diff --git a/docs/tz/technical-architecture.md b/docs/tz/technical-architecture.md deleted file mode 100644 index 2134051..0000000 --- a/docs/tz/technical-architecture.md +++ /dev/null @@ -1,286 +0,0 @@ -# 9. Техническая архитектура, стек, компоненты и эксплуатационный контур - -## 9.1 Общая архитектурная схема - -Программный продукт реализуется по клиент-серверной модели и включает веб-клиент, сервер бизнес-логики, базу данных, модуль интеграции и вспомогательные сервисы уведомлений. - - - -## 9.2 Состав прикладных сервисов - -Согласно действующей карте деплоя в составе проекта используются следующие сервисы: - -- `web-frontend` — клиентский и менеджерский веб-интерфейс -- `apollo-backend` — сервер GraphQL и бизнес-логика -- `vault` — централизованное хранилище секретов -- `tg-bot` — Telegram-контур -- `max-bot` — MAX-контур -- `bonus-bot` — бонусный мессенджерный контур - -Основные прикладные сервисы `web-frontend` и `apollo-backend` разворачиваются через `dokploy_webhook` на сервере `main`. - -## 9.3 Технологический стек фронтенда - -Клиентская часть программного продукта реализуется на следующих технологиях: - -- `Nuxt 4` — прикладной веб-фреймворк -- `Vue 3` — библиотека пользовательского интерфейса -- `@nuxtjs/apollo` и `@vue/apollo-composable` — работа с GraphQL -- `GraphQL Code Generator` — генерация типизированных GraphQL-документов -- `Tailwind CSS` и `daisyUI` — базовая стилизация интерфейсов -- `Storybook` — контур изоляционного просмотра компонентов -- `VitePress` — подготовка проектной документации - -## 9.4 Технологический стек серверной части - -Серверная часть программного продукта реализуется на следующих технологиях: - -- `Node.js` — среда выполнения -- `Express 5` — HTTP-сервер -- `Apollo Server 5` — GraphQL-сервер -- `Prisma 7` — доступ к данным и ORM-слой -- `PostgreSQL` — основное хранилище данных -- `Zod` — валидация структур данных в прикладных сценариях -- `Nodemailer` — отправка уведомлений по электронной почте - -## 9.5 Архитектура клиентской части - -Клиентская часть организована по следующим слоям: - -- страницы `app/pages` — входные экранные формы -- компоненты `app/components` — переиспользуемые элементы интерфейса -- composables `app/composables` — клиентская логика и представление данных -- GraphQL-документы `graphql/operations` — отдельные запросы и мутации -- сгенерированная типизированная схема `app/composables/graphql/generated.ts` -- серверные proxy и интеграционные обработчики `server/api` - -Ключевые экранные маршруты текущей реализации: - -- `/products` и `/products/[slug]` — каталог и карточка товара -- `/cart` — корзина -- `/client-orders` и `/client-orders/[id]` — клиентские заявки и заказы -- `/clients` и `/clients/[id]` — менеджерский контур клиентов -- `/orders` и `/orders/[id]` — менеджерский контур заказов -- `/catalog-settings` — административные настройки каталога -- `/bonus-program`, `/bonus-system/*` — бонусный контур - -## 9.6 Карта слоев и компонентов - - - -## 9.7 Архитектура серверной части - -Серверная часть организована по следующим основным модулям: - -- `src/server.js` — инициализация HTTP-сервера и GraphQL-сервера -- `src/schema.graphql` — контракт GraphQL API -- `src/resolvers.js` — реализация GraphQL-операций -- `src/context.js` — построение контекста запроса -- `src/auth.js` — аутентификация и токены доступа -- `src/access.js` — правила авторизации и проверки ролей -- `src/prisma-client.js` — точка подключения Prisma -- `src/messenger*.js`, `src/telegram*.js`, `src/max-mini-app.js` — мессенджерный контур и мини-приложения - -## 9.8 Взаимодействие фронтенда и backend - -Взаимодействие клиентской и серверной части строится через GraphQL API. - -На стороне Nuxt используется серверный proxy-маршрут `/api/graphql`, который: - -- принимает запросы браузера -- прокидывает cookie и авторизационные заголовки -- перенаправляет запрос в `apollo-backend` -- возвращает результат в клиентское приложение - -Такой подход позволяет: - -- изолировать прямой backend endpoint от браузерного клиента -- централизованно передавать авторизационные данные -- поддерживать единый клиентский GraphQL endpoint - -## 9.9 Интеграционные и вспомогательные HTTP-точки - -Помимо GraphQL API, во фронтенд-контуре реализованы вспомогательные серверные маршруты: - -- `/api/graphql` — proxy в backend GraphQL API -- `/api/auth/messenger-start` — запуск сценариев messenger login / connect -- `/api/dadata/address` — подсказки адресов -- `/api/dadata/bank` — подсказки банковских реквизитов -- `/api/dadata/party` — подсказки контрагентов -- `/api/messenger-avatar/[connectionId]` — выдача изображений, связанных с мессенджерными подключениями - -## 9.10 Компонентные требования к реализации - -Архитектура программного продукта должна сохранять следующие правила: - -- экранная логика должна находиться на уровне страниц и composables -- переиспользуемые элементы интерфейса должны быть вынесены в компоненты -- каждый GraphQL-документ должен храниться в отдельном `.graphql` файле -- клиентский код должен использовать сгенерированные типизированные документы -- серверная логика доступа к данным должна проходить через Prisma -- бизнес-правила доступа должны контролироваться серверной частью, а не только интерфейсом - -## 9.11 Требования к конфигурации и секретам - -Сервисы программного продукта должны получать прикладные секреты из `Vault`. - -В конфигурации сервисов допускается хранение только bootstrap-параметров для подключения к Vault. Бизнес-секреты, ключи интеграций и иные чувствительные данные должны загружаться из Vault при старте приложения. - -## 9.12 Инфраструктура, деплой и эксплуатационный контур - -Текущая инфраструктурная схема проекта включает прикладные сервисы, сервис секретов, мессенджерные сервисы и вспомогательный worker-контур. - - - -Сервисы проекта и способ их развёртывания: - -| Сервис | Путь в репозитории | Роль | Deploy mode | Сервер | -| --- | --- | --- | --- | --- | -| web-frontend | `web-frontend` | клиентский и менеджерский веб-интерфейс | `dokploy_webhook` | `main` | -| apollo-backend | `apollo-backend` | GraphQL API и бизнес-логика | `dokploy_webhook` | `main` | -| hatchet-worker | `hatchet-worker` | фоновый worker-контур | `dokploy_webhook` | `main` | -| tg-bot | `tg-bot` | Telegram-контур | `dokploy_webhook` | `main` | -| max-bot | `max-bot` | MAX-контур | `dokploy_webhook` | `main` | -| bonus-bot | `bonus-bot` | бонусный сервис | `dokploy_webhook` | `main` | -| vault | `vault` | сервис секретов | `dokploy_webhook` | `main` | - -Серверная карта текущего проекта: - -- сервер `main` -- Tailscale user: `root` -- Tailscale host: `dsrptlab` - -Эксплуатационные операции доступа и диагностики выполняются через Tailscale SSH. - -## 9.13 Dokploy и цепочка развёртывания - -Для основных сервисов проекта используется режим `dokploy_webhook`. - -Это означает следующую последовательность: - -1. изменения фиксируются в Git-репозитории; -2. изменения публикуются в `main`; -3. Dokploy получает webhook-событие; -4. Dokploy выполняет сборку соответствующего сервиса; -5. сервис перезапускается с загрузкой секретов из Vault; -6. после старта выполняются bootstrap-действия, предусмотренные образом и entrypoint-командой. - -Текущие особенности прикладных сервисов: - -- `web-frontend` стартует через загрузку Vault env и запуск `.output/server/index.mjs` -- `apollo-backend` стартует через загрузку Vault env, `prisma migrate deploy` и запуск `src/server.js` -- bot-сервисы стартуют через общий паттерн загрузки Vault env и запуск `node src/server.js` - -## 9.14 Vault и хранение секретов - -Сервис `vault` развернут как отдельное приложение на базе образа `hashicorp/vault:1.21.3`. - -Текущие инфраструктурные правила работы Vault: - -- используется `raft` storage -- данные Vault сохраняются в `/vault/data` -- конфигурация сервиса хранится в `vault/config/vault.hcl` -- при старте выполняется проверка и, при необходимости, автоматический unseal из переменных окружения -- прикладные сервисы подключаются к Vault через bootstrap-переменные `VAULT_ADDR`, `VAULT_TOKEN`, `VAULT_KV_MOUNT`, `VAULT_SHARED_PATH`, `VAULT_PROJECT_PATH`, `VAULT_ENABLED` - -Прикладные сервисы используют общий shell-bootstrap `load-vault-env.sh`, который: - -- ожидает доступность Vault по health endpoint -- загружает shared secrets -- загружает project secrets -- экспортирует секреты в runtime environment процесса - -## 9.15 Структура сервисных каталогов - -Текущая сервисная структура репозитория: - -- `web-frontend` — Nuxt-приложение, GraphQL operations, VitePress-документация -- `apollo-backend` — Apollo Server, Prisma schema, миграции, импорт каталога -- `tg-bot` — Telegram-сервис -- `max-bot` — MAX-сервис -- `bonus-bot` — бонусный сервис -- `hatchet-worker` — worker-контур -- `vault` — Dockerfile, конфигурация и entrypoint Vault -- `hatchet` — docker-compose инфраструктурного контура Hatchet - -## 9.16 Контур фоновых процессов - -В проекте присутствует отдельный контур фонового исполнения задач: - -- `hatchet-worker` как прикладной worker -- `hatchet-engine` как движок исполнения -- `hatchet-dashboard` как интерфейс мониторинга -- отдельный `postgres` для Hatchet-контура - -Конфигурация этого контура находится в `hatchet/docker-compose.yml`. - -## 9.17 Зафиксированные runtime-версии и технологические зависимости - -### Базовые runtime и контейнерные образы - -| Компонент | Текущая версия | -| --- | --- | -| Node base image для web/backend/bots | `node:22-bookworm-slim` | -| Vault image | `hashicorp/vault:1.21.3` | -| Hatchet Postgres | `postgres:15.6` | -| Nuxt | `4.4.2` | -| Vue | `3.5.30` | -| Apollo Server | `5.5.0` | -| Prisma / Prisma Client | `7.6.0` | -| Mermaid | `11.14.0` | -| VitePress | `1.6.4` | - -### Основные зависимости фронтенда - -| Библиотека | Версия | Назначение | -| --- | --- | --- | -| `@nuxtjs/apollo` | `5.0.0-alpha.16` | GraphQL-клиентский слой | -| `@vue/apollo-composable` | `4.2.2` | composable API для GraphQL | -| `@apollo/client` | `3.14.1` | Apollo client runtime | -| `@fullcalendar/core` | `6.1.20` | calendar runtime | -| `@fullcalendar/daygrid` | `6.1.20` | calendar day grid | -| `@fullcalendar/vue3` | `6.1.20` | Vue integration for calendar | -| `@nuxt/eslint` | `1.15.2` | linting Nuxt-проекта | -| `@nuxtjs/tailwindcss` | `6.14.0` | Tailwind integration | -| `daisyui` | `5.5.19` | UI primitives | -| `graphql` | `16.13.2` | GraphQL runtime | -| `mermaid` | `11.14.0` | diagrams in documentation | -| `@sentry/vue` | `10.46.0` | error tracking | -| `storybook` | `8.6.14` | UI component review | -| `vue-router` | `5.0.4` | Vue routing runtime | - -### Основные зависимости backend - -| Библиотека | Версия | Назначение | -| --- | --- | --- | -| `@apollo/server` | `5.5.0` | GraphQL server | -| `@as-integrations/express5` | `1.1.2` | Apollo + Express integration | -| `express` | `5.2.1` | HTTP server | -| `@prisma/client` | `7.6.0` | data access | -| `@prisma/adapter-pg` | `7.6.0` | Prisma adapter | -| `pg` | `8.20.0` | PostgreSQL driver | -| `graphql` | `16.13.2` | GraphQL runtime | -| `zod` | `4.3.6` | validation | -| `nodemailer` | `8.0.4` | email notifications | -| `dotenv` | `17.3.1` | env bootstrap in local/runtime layers | - -### Основные зависимости worker-контура - -| Библиотека | Версия | Назначение | -| --- | --- | --- | -| `@hatchet-dev/typescript-sdk` | `1.19.0` | worker runtime | -| `dotenv` | `17.3.1` | env bootstrap in local/runtime layers | - -## 9.18 Технические конфигурационные файлы - -Ключевые технические файлы текущей реализации: - -- `deploy-map.toml` — карта сервисов, deploy mode и серверов -- `web-frontend/nuxt.config.ts` — конфигурация Nuxt и Apollo -- `web-frontend/codegen.ts` — конфигурация GraphQL codegen -- `apollo-backend/prisma.config.ts` — конфигурация Prisma -- `apollo-backend/prisma/schema.prisma` — модель данных -- `web-frontend/scripts/load-vault-env.sh` — Vault bootstrap frontend -- `apollo-backend/scripts/load-vault-env.sh` — Vault bootstrap backend -- `vault/config/vault.hcl` — конфигурация Vault -- `vault/entrypoint.sh` — startup/unseal логика Vault
users
profile
addresses
channels
cart
orders
requests
invitations
balances
inventory
cartItems
orderItems
items
history
bonus
withdrawals
referrals
productType
1
*
0..1
Company
User
CounterpartyProfile
DeliveryAddress
RegistrationRequest
Invitation
MessengerConnection
Product
Warehouse
ProductStock
CatalogProductTypeSetting
Cart
CartItem
Order
OrderItem
OrderStatusEvent
ReferralLink
BonusTransaction
RewardWithdrawalRequest