398 lines
26 KiB
Markdown
398 lines
26 KiB
Markdown
# Premium Механик — PWA Design Doc
|
||
|
||
- **Дата:** 2026-05-16
|
||
- **Статус:** Draft, ожидает review владельца
|
||
- **Владелец:** vladtechno@gmail.com (PremiumPark)
|
||
- **Родительский проект:** PremiumDriverApp
|
||
- **Экосистема:** Premium CRM (TaxiDashboard) на crm.pptaxi.ru
|
||
- **Предшественник:** реверс-разведка приложения «Механик» NaughtySoft (см. `01-static-analysis.md` + перехват трафика)
|
||
|
||
---
|
||
|
||
## 1. Контекст и цель
|
||
|
||
Сотрудники-механики PremiumPark сейчас работают через приложение «Механик» вендора NaughtySoft (`com.naughtysoft.ttc`, Xamarin/.NET). Все данные осмотров уходят в 1С вендора, доступ к ним из PremiumCRM невозможен без прохода через вендорские API.
|
||
|
||
Через разведку (см. `02-network-capture.md`) подтверждён формат backend-API вендора (REST на `api.ttcontrol.naughtysoft.ru`). Эндпоинты осмотров (`/api/vehicletechinspection/byvehicle`, `/api/vehiclestate/history`, `/api/image/<id>`, `/api/inspectionvideo`) подтверждают, что **модель данных осмотров с координатами повреждений и фото уже существует и нам полностью видна**.
|
||
|
||
**Цель проекта:** построить **собственное** приложение «Premium Механик» (PWA), которое:
|
||
|
||
1. Полностью заменяет вендорское приложение для сотрудников-механиков.
|
||
2. Хранит данные осмотров **в нашей PostgreSQL** в PremiumCRM, не отправляя их к вендору.
|
||
3. Поддерживает основные кейсы: съёмка фото авто, обводка повреждений на фото, метки повреждений с координатами, сравнение с прошлым осмотром при приёмке.
|
||
4. Развёртывается без RuStore / App Store — деплой = `git push`.
|
||
|
||
**Долгосрочная цель:** отвязать парк от 1С вендора, начав с самой ценной модели данных (осмотры с фото).
|
||
|
||
## 2. Цели и не-цели
|
||
|
||
### Цели
|
||
|
||
1. Делать осмотр авто: 4-8 фото по сторонам, метки и обводки повреждений на каждой фото.
|
||
2. При приёмке машины обратно от водителя — показать прошлые повреждения для сравнения «было / стало».
|
||
3. Все данные осмотра хранятся в PostgreSQL PremiumCRM (новая БД-схема), без участия вендора.
|
||
4. Доступ для авторизованных сотрудников-механиков (через существующий auth PremiumCRM).
|
||
5. Работает на Android-планшетах механиков, на iPad/iPhone, на десктопе — без раздельных сборок.
|
||
|
||
### Не-цели
|
||
|
||
- Не делаем приложение для водителя в рамках этого подпроекта (отдельная фаза).
|
||
- Не интегрируем фактическую интеграцию обратно в 1С вендора (наоборот — уходим от неё).
|
||
- Не делаем нативные APK / IPA в первой версии. Только PWA. Возможен Capacitor-fallback в фазе 3 при необходимости.
|
||
- Не делаем offline-first в MVP (механик работает с WiFi в парке). Service Worker — задел на будущее.
|
||
- Не делаем real-time чат / уведомления через push на iOS (сложно, неприоритетно).
|
||
- Не импортируем существующие осмотры из вендора в первой версии (отдельная задача миграции данных).
|
||
|
||
## 3. Технологический стек
|
||
|
||
### Frontend
|
||
|
||
| Слой | Выбор | Обоснование |
|
||
|---|---|---|
|
||
| Build/Dev | **Vite + TypeScript** | Быстрая итерация, типизация. Стек, уже знакомый команде. |
|
||
| UI Framework | **React 18** | Команда уже работает с React (SuperLanding на Astro + React islands). |
|
||
| UI Library | **shadcn/ui + Tailwind CSS** | Совпадает с design preferences (cream palette, Inter only, без жёлтого, без emoji). |
|
||
| State Client | **TanStack Query (React Query)** | Кэширование fetches, optimistic updates, retries. |
|
||
| Local State | **Zustand** | Минималистичный store без boilerplate. |
|
||
| Routing | **React Router 7** | Standard в React экосистеме. |
|
||
| Forms | **React Hook Form + Zod** | Типизированная валидация, лёгкий контроль. |
|
||
| Canvas/Annotation | **Konva.js + react-konva** | Лучший выбор для photo annotations: layers, touch, gestures, undo/redo. |
|
||
| HTTP | **Native fetch + ky или axios** | TBD на старте. |
|
||
| PWA | **vite-plugin-pwa** | Service Worker, web manifest, install prompt — из коробки. |
|
||
| QR-сканер | **html5-qrcode** | Для сканирования госномера-стикера машины. |
|
||
|
||
### Backend
|
||
|
||
| Слой | Выбор |
|
||
|---|---|
|
||
| Расширение | Новый модуль `app/mechanic/` в FastAPI PremiumCRM |
|
||
| Базовый URL | `/api/v1/mechanic/*` |
|
||
| ORM | SQLAlchemy (используется в TaxiDashboard) |
|
||
| Миграции | Alembic |
|
||
| Auth | Существующий PremiumCRM (JWT cookie + bcrypt PIN) — переиспользуем |
|
||
| Permissions | Существующая dept-based система permissions (см. memory: `project_taxidashboard_permissions`) |
|
||
| Файловое хранилище | **MinIO** на 100.64.0.12 (контейнер `taxi-minio`). Bucket **`pp-inspections`** (уже создан). Endpoint `s3.pptaxi.ru` (HTTPS), admin UI `s3-admin.pptaxi.ru`. Креды в `/opt/sites/taxi-dashboard/minio.env`. |
|
||
| S3 client | **`aiobotocore`** — переиспользуем существующую интеграцию из TaxiDashboard (она же используется для фото осмотров уже). |
|
||
| Upload-схема | **Pre-signed POST** — фронтенд льёт фото напрямую в MinIO, минуя FastAPI. Backend выдаёт presigned URL + поля. |
|
||
| Read-схема | Существующий nginx-proxy `/media/<key>` на VDS (уже работает для других фото TaxiDashboard) ИЛИ pre-signed GET с TTL 1 час. Решим на этапе implementation. |
|
||
| Image-processing | Pillow для thumbnails и EXIF strip — запускается celery-task'ом после `confirm` upload'а. |
|
||
| Дополнительно | **Только MinIO**, без локального дубля на FS (решение владельца). Расхождение с существующим dual-write-паттерном в TaxiDashboard оправдано тяжестью медиа осмотров (10-20 фото на осмотр) — забивать диск VDS дублями нецелесообразно. |
|
||
|
||
### Развёртывание
|
||
|
||
| Хост | Назначение |
|
||
|---|---|
|
||
| 100.64.0.12 (PremiumCRM VDS) | Backend + DB + статика фото |
|
||
| Caddy | Reverse proxy + TLS + raw уролы для фото |
|
||
| Поддомен | `mechanic.pptaxi.ru` либо `crm.pptaxi.ru/mechanic/` (TBD, см. open question) |
|
||
|
||
## 4. Модель данных
|
||
|
||
Новые таблицы в существующей `taxi_dashboard` PostgreSQL:
|
||
|
||
```sql
|
||
-- Осмотр
|
||
CREATE TABLE inspections (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
vehicle_id BIGINT NOT NULL REFERENCES vehicles(id),
|
||
type VARCHAR(32) NOT NULL, -- 'handover'|'return'|'periodic'|'ad-hoc'
|
||
performed_by INTEGER NOT NULL REFERENCES users(id),
|
||
driver_id INTEGER REFERENCES users(id),
|
||
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
finished_at TIMESTAMPTZ,
|
||
status VARCHAR(16) NOT NULL DEFAULT 'in_progress', -- 'in_progress'|'completed'|'cancelled'
|
||
notes TEXT,
|
||
prev_inspection_id BIGINT REFERENCES inspections(id), -- ссылка на предыдущий осмотр того же авто
|
||
meta JSONB DEFAULT '{}'::jsonb
|
||
);
|
||
|
||
CREATE INDEX idx_inspections_vehicle_started ON inspections(vehicle_id, started_at DESC);
|
||
|
||
-- Фото осмотра
|
||
CREATE TABLE inspection_photos (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
inspection_id BIGINT NOT NULL REFERENCES inspections(id) ON DELETE CASCADE,
|
||
side VARCHAR(16) NOT NULL, -- 'front'|'rear'|'left'|'right'|'interior'|'vin'|'odometer'|'wheel-fl'|'wheel-fr'|'wheel-rl'|'wheel-rr'|'free'
|
||
slot_index INT DEFAULT 0,
|
||
storage_key TEXT NOT NULL, -- путь относительно /opt/premiumcrm/uploads/
|
||
annotated_key TEXT, -- путь к фото с canvas-обводкой (опц.)
|
||
thumb_key TEXT,
|
||
width INT,
|
||
height INT,
|
||
bytes BIGINT,
|
||
taken_at TIMESTAMPTZ DEFAULT NOW(),
|
||
display_order INT DEFAULT 0
|
||
);
|
||
|
||
-- Метка повреждения (на фото и/или на схеме авто)
|
||
CREATE TABLE damage_markers (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
inspection_id BIGINT NOT NULL REFERENCES inspections(id) ON DELETE CASCADE,
|
||
photo_id BIGINT REFERENCES inspection_photos(id), -- NULL если только на схеме
|
||
side VARCHAR(16), -- сторона авто
|
||
x DECIMAL(6,4), -- normalized 0..1 в координатах фото или схемы
|
||
y DECIMAL(6,4),
|
||
polygon JSONB, -- опционально: координаты обведённого контура [{x,y}, ...]
|
||
damage_type VARCHAR(32), -- 'scratch'|'dent'|'paint'|'crack'|'missing'|'rust'|'glass'
|
||
severity VARCHAR(16), -- 'cosmetic'|'minor'|'moderate'|'severe'
|
||
description TEXT,
|
||
carried_over_from_id BIGINT REFERENCES damage_markers(id), -- если унаследовано от прошлого осмотра
|
||
resolved BOOLEAN DEFAULT FALSE, -- было ли устранено
|
||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||
);
|
||
|
||
CREATE INDEX idx_damage_markers_inspection ON damage_markers(inspection_id);
|
||
```
|
||
|
||
**Ключевая идея для дифа «было/стало»:**
|
||
|
||
- При создании return-inspection backend копирует все `damage_markers` из `prev_inspection_id` со ссылкой `carried_over_from_id`. Механик может либо подтвердить «есть» (оставить), либо отметить «устранено» (`resolved=true`), либо добавить новые маркеры. На фронте отображается две группы: «было раньше» (полупрозрачные) + «новые» (яркие).
|
||
|
||
## 5. API контракт
|
||
|
||
Все endpoints под `/api/v1/mechanic/`. Auth — через существующий JWT cookie PremiumCRM. Permission gate: `mechanic:inspect` (новое право в матрице, добавляется в модуль permissions).
|
||
|
||
### Машины
|
||
|
||
```
|
||
GET /vehicles?q=<гос-номер|VIN>&limit=20
|
||
→ [{id, license_plate, vin, make, model, year, current_driver, last_inspection_at}]
|
||
|
||
GET /vehicles/{id}
|
||
→ детали авто + список последних осмотров
|
||
|
||
GET /vehicles/{id}/last-inspection
|
||
→ последний завершённый осмотр (для дифа при приёмке)
|
||
```
|
||
|
||
### Осмотры
|
||
|
||
```
|
||
POST /inspections
|
||
body: {vehicle_id, type, driver_id?}
|
||
→ {id, ...} создан в статусе in_progress; если type='return', копирует markers из prev
|
||
|
||
GET /inspections/{id}
|
||
→ полная карточка с photos + markers + meta
|
||
|
||
PATCH /inspections/{id}
|
||
body: {status?, notes?, finished_at?}
|
||
|
||
POST /inspections/{id}/photos/upload-url
|
||
body: {side, slot_index, content_type, size}
|
||
→ {photo_id, presigned_url, fields, storage_key, expires_at}
|
||
# фронт льёт сам файл напрямую в MinIO через presigned_url
|
||
|
||
POST /photos/{photo_id}/confirm
|
||
body: {actual_size?, width?, height?}
|
||
→ {id, storage_key, thumb_key, status: 'processing'|'ready'}
|
||
# после confirm backend ставит celery-task на thumbnail и EXIF strip
|
||
|
||
POST /inspections/{id}/markers
|
||
body: {photo_id?, side, x, y, polygon?, damage_type, severity, description}
|
||
→ {id, ...}
|
||
|
||
PATCH /markers/{id}
|
||
body: {damage_type?, severity?, description?, polygon?, resolved?}
|
||
|
||
DELETE /markers/{id}
|
||
|
||
POST /photos/{id}/annotated/upload-url
|
||
body: {content_type, size}
|
||
→ {presigned_url, fields, storage_key, expires_at}
|
||
# аналогично: фронт льёт annotated-версию напрямую в MinIO
|
||
|
||
POST /photos/{id}/annotated/confirm
|
||
→ {id, annotated_key, status: 'ready'}
|
||
```
|
||
|
||
### Чтение фото
|
||
|
||
```
|
||
GET /photos/{id} → 302 Redirect на pre-signed S3 URL (TTL 1 час)
|
||
GET /photos/{id}/thumb → 302 Redirect на pre-signed S3 URL thumbnail
|
||
GET /photos/{id}/annotated → 302 Redirect на pre-signed S3 URL annotated
|
||
```
|
||
|
||
### Меня (текущий механик)
|
||
|
||
```
|
||
GET /me
|
||
→ {id, name, dept, permissions: [...], avatar_url}
|
||
```
|
||
|
||
## 6. UI экраны и поток
|
||
|
||
### Экран 1. Login
|
||
PIN-вход в PremiumCRM (как уже работает в TaxiDashboard).
|
||
|
||
### Экран 2. Главная — выбор машины
|
||
- Поле поиска по госномеру/VIN
|
||
- Кнопка «Сканировать QR» (стикер на лобовом стекле машины с госномером)
|
||
- Список последних 10 машин, с которыми работал этот механик
|
||
- На каждой карточке: гос-номер, модель, водитель, дата последнего осмотра
|
||
|
||
### Экран 3. Карточка авто
|
||
- Резюме (гос-номер, VIN, модель, цвет, год)
|
||
- Текущий водитель (если в аренде)
|
||
- **Кнопки:** «Передача водителю» (handover) | «Приёмка от водителя» (return) | «Плановый осмотр» (periodic) | «Свободный осмотр» (ad-hoc)
|
||
- **Прошлые осмотры** — лента (превью фото + метки)
|
||
|
||
### Экран 4. Новый осмотр — список «слотов» фото
|
||
Карточка-grid: 4 обязательных + 4 опциональных:
|
||
- Перед, Зад, Левый, Правый (обязательные)
|
||
- VIN, Одометр, Салон, Свободное фото
|
||
- Каждый слот: пустой → камера UI; заполнен → миниатюра + индикатор количества меток
|
||
- В режиме `return` каждая карточка слота показывает «было N меток, добавь новые / подтверди / отметь устранённые»
|
||
|
||
### Экран 5. Камера
|
||
- Полноэкранный preview (через `getUserMedia()` если поддерживается, иначе `<input type="file" capture="environment">`)
|
||
- Overlay-направляющая под текущий слот (силуэт стороны авто для guidance)
|
||
- Снять → preview → принять/перенять
|
||
|
||
### Экран 6a. Vehicle Scheme (zone-навигация)
|
||
|
||
- Интерактивная SVG-схема авто с тремя проекциями: top-down (вид сверху), боковая (left/right), интерьер
|
||
- Каждая зона (бампер, капот, дверь, крыша, диск, лобовое и т.д.) — кликабельный hot-spot SVG `<path>`
|
||
- При тапе на зону → переход в Экран 5 (Камера) с предзаполненным `side`/`slot_index`
|
||
- Поверх схемы — полупрозрачные метки прошлых повреждений (для return-режима): красные точки на тех зонах, где были повреждения
|
||
- Прогресс осмотра: подсветка зон, у которых уже есть фото (зелёный outline)
|
||
|
||
### Экран 6b. Annotation Editor (главная фишка)
|
||
- Фото на весь экран, поверх — Konva canvas
|
||
- Tool panel:
|
||
- 🔘 **Маркер** — тап создаёт точку-метку
|
||
- ✏️ **Обводка** — рисуем замкнутый контур (полигон) вокруг повреждения
|
||
- ↩️ Undo / 🗑️ Erase
|
||
- 🎨 Тип/severity выбор (4 типа × 4 severity)
|
||
- При тапе на маркер — modal с типом, severity, описанием
|
||
- В `return`-режиме старые маркеры показываются полупрозрачными; тап → «есть/устранено»
|
||
|
||
### Экран 7. Обзор осмотра
|
||
- Карточка-grid всех фото + список всех маркеров + общие notes
|
||
- Кнопка «Завершить осмотр» → finished_at, status=completed
|
||
|
||
### Экран 8. Подтверждение (опц.)
|
||
- Подпись водителя (canvas) — опционально
|
||
- Сохранение, переход на главную
|
||
|
||
## 7. Архитектура frontend
|
||
|
||
```
|
||
src/
|
||
├── api/
|
||
│ ├── client.ts # настройка fetch/axios + auth interceptor
|
||
│ ├── inspections.ts # endpoints через TanStack Query
|
||
│ ├── vehicles.ts
|
||
│ └── photos.ts
|
||
├── components/
|
||
│ ├── ui/ # shadcn-сгенерированные
|
||
│ ├── camera/
|
||
│ │ ├── CameraCapture.tsx
|
||
│ │ └── PhotoSlot.tsx
|
||
│ ├── annotation/
|
||
│ │ ├── AnnotationCanvas.tsx # Konva-based
|
||
│ │ ├── ToolPalette.tsx
|
||
│ │ └── MarkerModal.tsx
|
||
│ ├── vehicle-scheme/
|
||
│ │ ├── VehicleScheme.tsx # SVG interactive scheme
|
||
│ │ ├── schemes/ # SVG-файлы по типу кузова (sedan, hatch, suv)
|
||
│ │ │ ├── sedan-top.svg
|
||
│ │ │ ├── sedan-side.svg
|
||
│ │ │ └── interior.svg
|
||
│ │ └── HotSpot.tsx # переиспользуемая зона-кнопка
|
||
│ └── shared/
|
||
├── pages/
|
||
│ ├── Login.tsx
|
||
│ ├── Home.tsx
|
||
│ ├── VehicleCard.tsx
|
||
│ ├── InspectionStart.tsx
|
||
│ ├── InspectionEditor.tsx
|
||
│ └── InspectionReview.tsx
|
||
├── store/ # Zustand store(s)
|
||
│ ├── inspectionDraft.ts
|
||
│ └── auth.ts
|
||
├── lib/
|
||
│ ├── pwa.ts # SW registration
|
||
│ └── coords.ts # norm/denorm coords
|
||
├── App.tsx
|
||
└── main.tsx
|
||
```
|
||
|
||
## 8. Архитектура backend (модуль в PremiumCRM)
|
||
|
||
```
|
||
app/mechanic/
|
||
├── __init__.py
|
||
├── router.py # /api/v1/mechanic/ роуты
|
||
├── models.py # SQLAlchemy: Inspection, InspectionPhoto, DamageMarker
|
||
├── schemas.py # Pydantic
|
||
├── service.py # бизнес-логика (start_inspection, finalize, copy_markers, ...)
|
||
├── storage.py # сохранение файлов на VDS + thumbnails
|
||
├── deps.py # auth + permission deps
|
||
└── migrations/
|
||
└── 20260516_mechanic_init.py # Alembic
|
||
```
|
||
|
||
## 9. Фазы реализации
|
||
|
||
### Фаза 1. MVP — Skeleton (3-4 нед)
|
||
Только основной flow без annotation, для первичного теста UX:
|
||
|
||
- Backend: модуль `mechanic`, миграции, endpoints вэхикулов и осмотров, фото upload (без annotation)
|
||
- Frontend: Login → Home → Vehicle → Inspection (создать, выбрать тип, добавить фото в 4 слота, добавить просто маркер-точку без сложной обводки) → Review → Complete
|
||
- Дифф с прошлым: список маркеров прошлого осмотра, чекбоксы «есть/устранено»
|
||
|
||
### Фаза 2. Annotation Editor (+2-3 нед)
|
||
- Konva-based canvas-overlay с инструментами обводки (polygon)
|
||
- Сохранение annotated-фото отдельным URL
|
||
- Старые маркеры/обводки полупрозрачно поверх новой фотографии для сравнения
|
||
|
||
### Фаза 3. Polish (+1-2 нед)
|
||
- Service Worker + offline queue (фото снимаются на устройство, загружаются по WiFi)
|
||
- QR-сканер стикеров
|
||
- Push-уведомления для админа (через web push с FCM-фолбэком или Telegram-бота)
|
||
- Дашборд статистики в админ-части PremiumCRM
|
||
|
||
### Фаза 4 (опционально). Capacitor wrapper
|
||
- Если в production столкнёмся с ограничениями iOS Safari camera или offline
|
||
- Тот же codebase → native APK + IPA
|
||
- ~1-2 недели
|
||
|
||
## 10. Связь с разведкой
|
||
|
||
- Endpoints, обнаруженные у вендора (`api.ttcontrol.naughtysoft.ru/api/vehicletechinspection/byvehicle` и пр.), могут быть использованы **отдельно** для **миграции данных** — одноразовый импорт прошлых осмотров вендора в нашу базу. Это отдельный sub-project (не входит в MVP).
|
||
- В первой версии работаем только с НОВЫМИ осмотрами, создаваемыми в PremiumMechanic — старые остаются в системе вендора и используются для справки до момента миграции.
|
||
|
||
## 11. Решения и открытые вопросы
|
||
|
||
### Решения (по итогам ревью владельца)
|
||
|
||
| # | Решение |
|
||
|---|---|
|
||
| Поддомен | **`mechanic.pptaxi.ru`** (отдельный поддомен). DNS уже настроен владельцем. На VDS 100.64.0.12 в Caddyfile добавляется блок с TLS через ACME. |
|
||
| Подпись водителя | **Не нужна** в MVP. Из спеки исключена. |
|
||
| 2D-схема авто | **Нужна обязательно с MVP.** Делаем SVG-схему сами: top-down + боковая проекция + интерьер. Не привязываемся к ассетам вендора (в APK schemes как раздельных файлов нет, у них всё через WebView + либо embedded в Xamarin DLL, либо отдельная фотография крупным планом). У нас SVG-первый подход с zone-based hot-spots. |
|
||
|
||
### Замечания по разведке вендорского редактора
|
||
|
||
- Layout `vehicleinspectiondetaildrawinglayout.xml` подтверждает: вендор использует `<android.webkit.WebView>` для редактора рисования повреждений. То есть **наша PWA-стратегия = технически тот же подход, что у вендора**, только без обёртки Xamarin.
|
||
- Заголовок WebView в layout = «Левая дверь» — у вендора **zone-based** детализация (кнопка → крупный план зоны → редактирование).
|
||
- ObjectID формата `660e7abfd13231f1dd6e8a4d` в `vehicleId` доказывает, что у вендора на бэке **MongoDB**, а не 1С. Имя `taksi.0nalog.com:1703` (виденное в Driver-приложении) — отдельный 1С-узел, не основной storage. Это означает: имитировать «1С-структуру» нам не надо — у вендора и так нет 1С на ключевом пути данных осмотров.
|
||
|
||
### Остающиеся open questions
|
||
|
||
1. **Permissions:** добавить новое право `mechanic:inspect` или ограничиться существующим `dept=Тех. служба`? — выбор на этапе implementation.
|
||
2. **Storage:** решено использовать **MinIO**, **только** MinIO без локального FS-дубля (разрешено отклониться от dual-write паттерна для тяжёлых медиа). Параметры: bucket `pp-inspections`, endpoint `s3.pptaxi.ru` HTTPS, креды в `/opt/sites/taxi-dashboard/minio.env`. Существующая `aiobotocore`-интеграция переиспользуется. Остаётся **операционно**: настроить CORS на bucket для allowed origins (`https://mechanic.pptaxi.ru`, `https://crm.pptaxi.ru`, dev `http://localhost:5173`) — задача в плане реализации.
|
||
3. **Связь с CheckAuto:** опционально показывать в карточке машины автоматические проверки водителя — после MVP.
|
||
4. **Импорт истории осмотров от вендора:** через VendorBridge sub-project — после MVP.
|
||
|
||
## 12. Следующий шаг
|
||
|
||
После утверждения этой спеки:
|
||
|
||
1. Создать подробный план реализации Фазы 1 через `writing-plans` skill.
|
||
2. Сгенерировать миграции БД + scaffold бэкенд-модуля.
|
||
3. Скаффолд React-проекта в `mechanic-pwa/frontend/` с базовой страницей логина.
|
||
4. На итерациях по 2-3 экрана.
|