24 KiB
Сервисный центр — платформа: обзор проекта и руководство пользователя
Этот файл — человеко-читаемое описание всей платформы и её основного модуля (
production), для владельца бизнеса и для себя на будущее. Техническая справка по API/деплою этого репозитория — в 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 (600–800, letter-spacing: -0.02em —
плотный, «инженерный» вид), текст — Roboto (300–500). Оба через 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):
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 без истории изменений — нельзя откатить или увидеть, кто менял банковские реквизиты.