Files
aura-crm/production/PROJECT.md
T

308 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Сервисный центр — платформа: обзор проекта и руководство пользователя
> Этот файл — человеко-читаемое описание всей платформы и её основного модуля
> (`production`), для владельца бизнеса и для себя на будущее. Техническая
> справка по API/деплою этого репозитория — в [README.md](README.md), она не
> дублируется здесь.
## 1. Что это
Модульная CRM для сервисного центра (Севастополь): приём техники в ремонт,
заправка картриджей, продажа/выкуп техники, склад и закупки, касса,
аналитика. Заменяет разрозненные таблицы и мессенджеры одним рабочим местом
для владельца, менеджеров и мастеров.
Разработка началась 2026-08-08. Основная дорожная карта (фазы 0–10: MVP,
документы, картриджи, склад, AI-приёмка, касса, уведомления, аналитика,
AI-диагностика) закрыта к 2026-08-10, после чего добавлен второй слой фич
(лояльность, запись на приём, поставщики и закупки, гарантия/возвраты,
трейд-ин, кастомные поля, шаблоны документов, PWA, управление модулями).
## 2. Архитектура платформы
Платформа состоит из независимых сервисов — каждый со своей БД, своим
рантаймом, своим docker-compose. Общее — только staff-идентичность (JWT) и
реестр модулей, оба живут в `core`.
| Сервис | Репозиторий | Роль | Порт (loopback) |
|---|---|---|---|
| **core** | `~/service-center` | Auth (JWT owner/manager/master), реестр модулей, heartbeat/restart/maintenance | backend `18090` |
| **production** | `~/production` (этот репозиторий) | Приём в ремонт, картриджи, склад, касса, аналитика — основной объём функциональности | backend `18091`, web `18092` |
| **site** | `~/site` | Публичный лендинг + форма лида | backend `18093`, web `18094` |
| **online-store** | отдельный прод-деплой (Next.js) | Продажа техники, уже в проде, синкается вебхуком в `production.sale` | — |
Модуль регистрируется в `core` (`POST /api/modules`), получает `MODULE_TOKEN`
и шлёт heartbeat каждые 30с. `core` может дистанционно перезапустить модуль
или перевести его в режим обслуживания (страница `/modules`, owner-only).
`production` не имеет собственного логина — JWT выпускает `core`, оба сервиса
проверяют его одним и тем же `JWT_SECRET` (HS256).
Все сервисы стоят за системным Caddy на хосте (домен ещё не выбран), сеть
между контейнерами — общий docker-сеть `platform_net`.
## 3. Технологический стек
**Backend:** Go + Fiber, PostgreSQL (`pgx/v5`, собственные SQL-миграции —
не Supabase/ORM), MinIO (файлы: фото заявок, документы). AI — Gemini
(бесплатный тариф): structured output для AI-приёмки заявок и AI-диагностики
по фото, свободный текст для аналитической сводки. Docker + Caddy для
деплоя.
**Frontend (`web/`):** React 19 + Vite (без TypeScript, `.jsx`), React
Router 7, `@dnd-kit` для drag-n-drop Kanban-досок, `lucide-react` для икон,
`vite-plugin-pwa` для PWA-манифеста/service worker. Без Tailwind и без
UI-фреймворка — своя лёгкая дизайн-система (раздел 9).
**Аутентификация:** JWT, роли `owner` / `manager` / `master`, токен хранится
в `localStorage`, состояние — React Context (`AuthContext`).
## 4. Роли и права доступа
| Роль | Доступ |
|---|---|
| **Владелец (owner)** | Всё. Плюс единственный, кто видит: Сотрудники, Поля приёмки, Справочники, Шаблоны документов, Модули, Настройки |
| **Менеджер (manager)** | Всё операционное: канбан, картриджи, склад, поставщики, закупки, возвраты, гарантия, трейд-ин, клиенты. Плюс Касса и Аналитика |
| **Мастер (master)** | Операционные разделы, но видит только заявки/партии, назначенные на него (или ещё неназначенные — чтобы взять в работу). Кассу и Аналитику как отдельные страницы не видит, но кассовая история конкретного своего заказа доступна внутри карточки заказа |
Разграничение — не только на фронтенде (скрытие пункта меню), а на
уровне API (`internal/authz`): чужой заказ/партия картриджей отдают 403 на
чтение и запись для мастера, включая фото/комментарии/списание запчастей.
## 5. Руководство пользователя
Ниже — по каждому пункту левого меню (`web/src/layout/Sidebar.jsx`), в том
порядке, в котором он идёт в интерфейсе.
### Канбан (`/kanban`)
Главный экран. Доска приёма в ремонт с колонками по статусу: **Новая →
Диагностика → В ремонте → Ждём запчасти → Готово → Выдано** (плюс
**Отменено**). Карточки заказов перетаскиваются между колонками
(`@dnd-kit`) — перетаскивание пишет новую запись в таймлайн заказа. Клик по
карточке открывает модалку заказа: данные клиента и устройства, назначенный
мастер, оценка/итоговая цена, гарантия (срок + связь с исходным заказом при
повторном ремонте), таймлайн (смена статусов — всегда публична;
комментарии и фото — публичность настраивается отдельно для каждой записи),
кнопки генерации Счёта и Акта (PDF), кнопка AI-диагностики по фото, форма
AI-приёмки нового заказа (Gemini разбирает свободный текст на структурированные
поля). Обновляется поллингом раз в 20 секунд — realtime-канал (`internal/realtime`)
уже реализован для уведомлений о новых заявках, полноценный live-канбан без
поллинга — не сделано.
### Заявки на запись (`/bookings`)
Заявки, оставленные клиентом с публичной страницы записи (`/book`, без
логина) — желаемая дата/время, что за проблема. Владелец/менеджер/мастер
переводит заявку в реальный заказ или отклоняет.
### Картриджи (`/cartridges`)
Отдельная Kanban-доска для заправки картриджей — та же механика статусов,
что и у заказов в ремонт, но свой домен (`internal/cartridge`): партия
картриджей, а не устройство. Тоже поддерживает документы (Счёт/Акт), фото,
комментарии, назначенного мастера и тот же ACL по назначению.
### Склад (`/parts`)
Учёт запчастей и расходников: остатки, себестоимость, списание при ремонте
(привязка расхода к конкретному заказу — для реальной маржинальности по
заявке, не только по кассе в целом).
### Поставщики (`/suppliers`)
Справочник поставщиков запчастей — контакты, кто у кого что заказывает.
### Заказы поставщикам (`/purchase-orders`)
Цикл закупки: заказ поставщику → поступление (GRN) → приход на склад.
### Возвраты поставщикам (`/rma`)
Брак/возврат запчастей поставщику — отдельный от клиентской гарантии поток.
### Гарантийные случаи (`/warranty-claims`)
Повторные обращения по гарантии — связаны с исходным заказом
(`original_order_id`), чтобы видеть историю ремонта одного устройства
целиком.
### Выкуп техники (`/trade-ins`)
Приём техники от клиента на выкуп (trade-in) — отдельный поток от ремонта:
оценка состояния, цена выкупа, статус сделки.
### Касса (`/cash`) — только owner/manager
Ручная кассовая книга (`cash_transactions`) — приход/расход, привязка
операции к заказу или партии картриджей. Отдельная страница — сводный
журнал по всей кассе; по конкретному заказу кассовая история видна и
мастеру внутри карточки этого заказа (см. раздел 4).
### Аналитика (`/analytics`) — только owner/manager
Агрегированная отчётность: выручка, операционные метрики (сколько заказов в
каком статусе, средний срок ремонта и т.п.), остатки склада, плюс
AI-сводка на свободном тексте (Gemini интерпретирует цифры человеческим
языком).
### Клиенты (`/clients`)
Физ- и юрлица, поиск, карточка клиента с полной историей его заказов.
Программа лояльности (`internal/loyalty`) считается здесь же.
### Сотрудники (`/staff`) — только owner
Единственный способ управлять персоналом (у `core`, который реально владеет
staff-аккаунтами, своего фронтенда нет — эта страница ходит напрямую в
`core` API). Создание сотрудника, смена роли, отключение/включение
аккаунта, сброс пароля (владелец задаёт новый пароль лично — self-service
восстановления по почте нет, инфраструктуры email в проекте нет). Нельзя
разжаловать или отключить последнего активного owner'а — защита от
случайной блокировки.
### Поля приёмки (`/order-fields`) — только owner
Кастомные поля для формы приёма заказа (текст/число/выбор/чекбокс,
обязательное или нет, порядок отображения). Позволяет владельцу подстроить
форму приёма под свою специфику без изменения кода. Архивация вместо
удаления — старые значения в уже созданных заказах не теряются.
### Справочники (`/catalogs`) — только owner
Каталог устройств (`devicecatalog`) — бренды/модели для автозаполнения
формы приёма (плюс кнопка автозаполнения марки/модели телефона по IMEI).
### Шаблоны документов (`/document-templates`) — только owner
Блочный редактор вёрстки Счёта и Акта: реордер блоков, скрытие
необязательных блоков, свои текстовые вставки. Не freeform HTML/CSS —
осознанное ограничение, чтобы нельзя было случайно сломать документ.
Структура обязательных блоков и шрифты не редактируются.
### Модули (`/modules`) — только owner
Панель управления всей платформой на уровне `core`: список зарегистрированных
модулей (`production`, `site`, `online-store`) со статусом (`healthy` /
`unhealthy` / `maintenance`), кнопки реального перезапуска контейнера и
перевода в режим обслуживания (503 на все роуты, кроме `/health`).
### Настройки (`/settings`) — только owner
Реквизиты бизнеса для PDF-документов, ключ/модель Gemini, токен и chat_id
Telegram-бота, ключ IMEI-сервиса — всё редактируется владельцем без
редеплоя (фоллбэк на `.env`, если поле в БД пустое). Инфраструктурные
секреты (`JWT_SECRET`, пароли БД/MinIO, токены модулей) сюда сознательно не
вынесены — только `.env`.
## 6. Публичные страницы (без логина)
- **`/track/:token`** — трекинг заказа для клиента: статус, устройство,
гарантия, только публичные события таймлайна (фото на этой странице пока
не рендерятся, только факт "добавлено фото").
- **`/book`** — форма записи на приём, попадает в раздел «Заявки на запись».
## 7. Backend: домены (`backend/internal/`)
Каждый пакет — одна зона ответственности:
| Пакет | Отвечает за |
|---|---|
| `auth` | Верификация staff JWT, выпущенного `core` |
| `authz` | Единое правило ACL: свой/чужой заказ или партия по `assigned_master_id` |
| `order` / `cartridge` | Заказы в ремонт / партии картриджей — ядро домена |
| `client` | Физ/юр клиенты |
| `booking` | Публичные заявки на запись |
| `cash` | Ручная кассовая книга |
| `inventory` | Склад запчастей, списание, себестоимость |
| `supplier` / `purchaseorder` / `rma` | Поставщики → закупки → возвраты браком |
| `tradein` | Выкуп техники у клиента |
| `loyalty` | Программа лояльности клиентов |
| `customfields` | Owner-настраиваемые поля формы приёма |
| `devicecatalog` | Каталог брендов/моделей устройств |
| `imei` | Автозаполнение марки/модели по IMEI (HiCellTek TAC lookup) |
| `aiintake` | AI-приёмка заявки: свободный текст → структурированные поля (Gemini) |
| `diagnosis` | AI-диагностика по фото (Gemini vision + Google Search grounding) |
| `analytics` | Агрегированная отчётность + AI-сводка |
| `document` / `doctemplates` / `pdfgen` / `ordernum` | Генерация Счёта/Акта, блочные шаблоны, нумерация документов |
| `file` | Хранение файлов в MinIO, реальный MIME-снифф по байтам (не доверяет заголовку клиента) |
| `settings` | Owner-редактируемая конфигурация без редеплоя |
| `notify` / `clientnotify` / `tgbot` / `smsgw` | Уведомления персоналу и клиентам (Telegram/SMS) |
| `coreclient` / `modulecontrol` | Heartbeat в `core`, приём команд restart/maintenance от `core` |
| `realtime` | Push-сигналы подключённым клиентам о изменениях |
| `sale` | Записи о продажах, синкаемые из `online-store` |
| `scheduler` | Фоновые задачи по расписанию |
| `db` / `dbutil` | Подключение к БД и общие SQL-хелперы |
## 8. Документы и уведомления
PDF (Счёт на оплату / Акт выполненных работ) генерируются на лету
(`internal/pdfgen`, кириллица через встроенные шрифты DejaVu Sans),
структура настраивается блоками в «Шаблонах документов», реквизиты — в
«Настройках». Уведомления клиенту о смене статуса — Telegram/SMS
(`clientnotify`), уведомления персоналу о новых заявках — Telegram
(`notify`/`tgbot`).
## 9. Дизайн-система
Своя лёгкая дизайн-система, без Tailwind и без готового UI-кита — базовые
токены в `web/src/index.css`, специфика конкретного компонента — инлайн
`style={{}}` в самом компоненте (осознанный выбор для проекта такого
размера, не технический долг).
**Шрифты:** заголовки — **Montserrat** (600800, `letter-spacing: -0.02em`
плотный, «инженерный» вид), текст — **Roboto** (300500). Оба через Google
Fonts.
**Палитра** (светлая тема, единственная — тёмная не реализована):
| Токен | Значение | Назначение |
|---|---|---|
| `--bg-light` | `#f8fafc` | Фон страницы |
| `--bg-lighter` | `#ffffff` | Фон карточек/панелей |
| `--accent-blue` | `#4361ee` | Основной акцент — активный пункт меню, кнопки, ссылки |
| `--accent-orange` | `#ea580c` | Вторичный акцент (предупреждения/ожидание) |
| `--accent-green` | `#22c55e` | Успех/готово |
| `--accent-red` | `#ef4444` | Ошибка/отмена |
| `--text-main` | `#1e1e2d` | Основной текст |
| `--text-muted` | `#64748b` | Второстепенный текст |
| `--border-light` | `#e9ecef` | Границы, разделители |
**Статусы заказа** (`orders/statuses.js`) имеют собственную, более широкую
палитру для наглядности канбан-доски: новая `#f59e0b` (янтарный),
диагностика `#0ea5e9` (голубой), в ремонте `#8b5cf6` (фиолетовый), ждём
запчасти `#f97316` (оранжевый), готово `#22c55e` (зелёный), выдано `#64748b`
(серый), отменено `#ef4444` (красный) — цвет статуса используется как
акцент на карточке и бейдже, не просто текстовая метка.
**Компоненты и приёмы:**
- `.glass-panel` — базовая карточка/панель: белый фон, тонкая граница
`border-light`, скругление 16px, мягкая тень (`0 8px 32px rgba(15,23,42,.08)`)
— лёгкий «стеклянный» эффект без размытия (blur не используется).
- `.btn-primary` (заливка `accent-blue`, белый текст, radius 8px) /
`.btn-outline` (белый фон, граница `border-light`, при hover обводка
синеет) — два уровня действий, primary/secondary.
- Sidebar фиксированный, 240px, белый, активный пункт — светло-синяя
подложка (`#eef2ff`) + синяя левая полоса 3px + синий текст/иконка. На
мобильном (`isMobile`) уезжает за экран и выезжает по `transform:
translateX`, с крестиком закрытия.
- Иконки — `lucide-react`, по одной на каждый пункт меню, семантически
подобранные (гаечный ключ для ремонта, капля для картриджей, коробки для
склада, кошелёк для кассы и т.д.).
- Формы/модалки переиспользуют общие стили (`orders/formStyles.js` и
аналоги) — не единый компонентный кит, а общие объекты стилей на домен.
**Характер дизайна:** утилитарный внутренний инструмент для персонала
(канбан-доска, таблицы, модалки), не публичный маркетинговый сайт —
приоритет на плотность информации и скорость сканирования глазами (статус
через цвет, а не только текст), а не на декоративность. Единственная
по-настоящему «клиентская» поверхность — `/track/:token` и `/book`.
## 10. Разработка и деплой
Кратко (полная версия — [README.md](README.md)):
```bash
docker network create platform_net # один раз, общая сеть с core/site
cp .env.example .env # JWT_SECRET должен совпадать с core/.env
docker compose up -d --build
curl http://127.0.0.1:18091/health
cd web && cp .env.example .env && npm install && npm run dev
```
## 11. Текущий статус и известные ограничения
- Полная дорожная карта (фазы 0–10) закрыта, плюс второй слой фич (см.
раздел 1) — реализован и живьём протестирован.
- Юрлицо бизнеса ещё не зарегистрировано → нет интеграции с Атол Онлайн
(фискализация, 54-ФЗ) и с Т-Банк эквайрингом для самого сервисного центра.
- Kanban обновляется поллингом (20с), не realtime (транспорт для realtime
уже есть в `internal/realtime`, но не подключён к канбан-доске).
- Публичный трекинг не показывает сами фото, только факт их добавления.
- Юнит-тестов почти нет (`core` — 0; `production` — часть пакетов без
тестов: `auth`, `client`, `coreclient`, `document`, `file`, `order`).
- Секреты в «Настройках» хранятся plaintext без истории изменений — нельзя
откатить или увидеть, кто менял банковские реквизиты.