Техническая документация — Fleet GPS Platform

Версия: 1.8 (упрощённый тестовый MVP)
Дата: 03.11.2026
Назначение: SaaS для мониторинга автопарков независимых компаний и собственного автомобиля частного водителя; GPS с телефонов поступает из собственного мобильного приложения платформы.

Статус: спецификация собственного тестового MVP, не отчёт о реализации. Приоритет — простой мониторинг и управление текущими перевозками. Документы, дорожные слои, накопление GPS-точек без сети и удаление аккаунта через интерфейс в MVP не входят. История на сервере формируется только из полученных online-точек; внешний GPS-клиент не используется.

1. Обзор проекта

Система обслуживает несколько независимых компаний и частных перевозчиков. Компании отслеживают автомобили на карте в реальном времени, управляют рейсами и сотрудниками. Администраторы приглашают водителей и видят только данные своей организации. Часть административных функций доступна в мобильном приложении.

Предусмотрены два самостоятельных сценария регистрации:

Для частника основной интерфейс — «Мой автомобиль», состояние трекера и «Поделиться постоянной ссылкой». Рейсы и остальные функции MVP доступны по необходимости. Компания и частник могут передать логисту постоянную ссылку на карту автомобиля: логист видит, где находится машина, подбирает заказы с учётом её положения и может переслать эту ссылку заказчику. Получателям достаточно браузера, регистрация не требуется.

Владелец компании и администратор всегда имеют доступ ко всему своему автопарку: на общей live-карте видны все активные машины с последней позицией, временем точки и связью независимо от рейса. Активные машины без координат остаются в списке как «Нет данных». Архивные машины доступны отдельно с карточками и сохранённой историей, но не показываются активными маркерами.

Референсы: https://qsgroup.eu/monitoring-gps-pojazdow/ и https://www.kornilov.de/ . Референсы являются ориентирами по назначению продукта; функциональные требования ниже самостоятельные.

2. Стек

Компонент Технологии
Веб-дашборд Vue 3, TypeScript, Vite, Pinia, Vue Router
Мобильное приложение платформы Собственный клиент: Tauri 2 + Vue 3, Android/iOS с первого релиза; GPS, рейсы, быстрая связь с диспетчером, push, состояние подключения
Нативный GPS Собственный Tauri plugin: Kotlin / Android location API и foreground service; Swift / Core Location на iOS
Передача GPS Нативный отправитель независимо от Vue/WebView; только актуальная позиция, без накопления offline-истории
Backend ASP.NET Core (.NET), C#
Realtime ASP.NET Core SignalR
База данных PostgreSQL + PostGIS
Кэш / масштабирование Redis по необходимости
Frontend tooling Bun
Локализация Vue I18n (vue-i18n), Composition API; общие словари для веба, мобильного интерфейса и страниц мониторинга
Карта MapLibre GL JS + картографические данные OpenStreetMap; общий компонент для веба, мобильного WebView и страниц постоянных ссылок
Картографическая подложка Сменяемый провайдер совместимых векторных тайлов и стиля MapLibre либо собственный хостинг; адреса сервисов, атрибуция, ключи и квоты задаются конфигурацией
Push FCM / APNs, интеграция с мобильным приложением
Деплой Docker, reverse proxy, CI/CD
Логи Serilog, OpenTelemetry

Технический риск: собственный нативный GPS-модуль входит в MVP. На Android предусматривается foreground service с видимым уведомлением, на iOS — Core Location с поддержкой фоновой геолокации. Разрешения, энергосбережение и завершение приложения ОС могут прерывать сбор и передачу. Нужен ранний прототип на реальных Android/iOS, включая заблокированный экран, стоянки, перезапуск и восстановление связи. JavaScript-таймеры и открытый WebView не должны быть условием работы трекера. Источники: Android: фоновая геолокация, Apple: фоновые обновления координат.

Карта и сменяемый провайдер тайлов

3. Роли и права

Роль Права
PlatformOwner Управление организациями и системой, служебный доступ к данным клиентов для поддержки с аудитом
OrganizationOwner Все ресурсы своей организации, администраторы и водители
IndividualOwner Свой личный кабинет, одна машина, свой трекер, рейсы и выдача/отзыв доступа к отслеживанию
Admin Весь автопарк своей компании, постоянный мониторинг всех машин, создание водителей, автомобили, рейсы и выдача ссылок
Dispatcher Мониторинг, управление рейсами и назначениями в их рамках, отчёты, выдача/отзыв ссылок; чтение документов после появления модуля в будущем
Driver Управление рабочими статусами своих рейсов и точек; принятие/отклонение, выезд/отмена выезда, прибытие/завершение; меню связи, профиль, передача GPS
Viewer Только конкретно разрешённая машина, её маршрут, статус и данные; без доступа ко всему автопарку и операциям изменения
Получатель ссылки Постоянный просмотр разрешённых данных автомобиля до отзыва ссылки; для временной ссылки — также до окончания её срока/рейса

Все данные кабинета принадлежат tenant_id; глобальные учётные записи users и системный справочник разрешений могут не иметь tenant. Backend проверяет активное членство и разрешения на каждом запросе, включая связанные ресурсы; пользователь не может назначить себе чужой tenant_id. Администратор не может выдать роль выше собственной. Служебный доступ PlatformOwner к данным клиентов разрешён для поддержки: отдельный служебный контекст с выбором клиента, причиной обращения и аудитом просмотров/изменений. Tenant-проверки не отключаются глобально. Viewer получает явное разрешение на конкретную машину, проверяемое также для истории и SignalR; это авторизованная роль, отдельная от получателя публичной ссылки.

Личный кабинет частника технически тоже является tenant с типом individual; корпоративный tenant имеет тип organization. Это внутренняя изоляция данных, не требование оформлять юридическое лицо. IndividualOwner совмещает владельца и водителя и управляет трекингом своей машины. Корпоративная роль Driver не получает права управления политикой трекинга. Для личного кабинета допускается одна активная машина; её замена сохраняет историю привязок. Переход частника в организацию — будущее расширение, не сценарий MVP.

Регистрация

Субаккаунты

Жизненный цикл доступа и устройств

4. MVP: функциональность

4.1 Веб-панель

4.2 Приложение водителя

4.3 Мобильный администратор

4.4 Постоянная ссылка для мониторинга автомобиля

4.5 Рейсы, геозоны, уведомления и отчёты

Рейсы, точки доставки, круглые геозоны, push и отчёты входят в MVP. Документооборот целиком перенесён в будущие расширения: экранов, загрузки/заполнения документов и файлового хранилища в первой версии нет. Будущее право диспетчера читать документы не добавляет модуль в текущий объём.

5. Архитектура

[Diagram]

Начальная архитектура — модульный монолит: Identity, Tenants, Fleet, Tracking, Sharing, Trips, Geofences, Notifications, Alerts, Reports, Audit. Tracking принимает текущую позицию авторизованного устройства. Нативный модуль собирает/передаёт координату и применяет политику независимо от UI, без устойчивой offline-очереди. Документы и внешние автомобильные трекеры — будущие расширения. Для тестового проекта выделение микросервисов не требуется.

Карты получают первоначальный снимок разрешённых машин/позиций через HTTP и затем обновления SignalR. При переподключении повторно проверяются права и загружается согласованный свежий снимок с учётом обновлений, пришедших во время загрузки; доставка событий SignalR сама по себе не гарантирует восстановление пропущенных изменений. Обновления содержат идентификатор машины и порядок/версию, достаточные для исключения отката к старой позиции. Удалённые права и отозванные ссылки проверяются для уже открытых соединений.

Изменения справочников/рейсов сохраняются транзакционно. Повтор запроса не создаёт повторных действий и уведомлений. Push дополняет сохранённые события в приложении; конкретный механизм повторов выбирается при реализации без отдельной инфраструктуры для MVP.

Путь GPS-точки

  1. Нативный модуль получает координаты и диагностические поля согласно действующей политике и GPS-профилю.
  2. При доступной сети свежая точка получает UUID/последовательный номер и передаётся нативным отправителем в /api/v1/tracking/position с авторизацией устройства.
  3. При потере сети история на телефоне не копится. Допустим один текущий замер в памяти, заменяемый новым, без записи очереди на диск. После восстановления сети получается и отправляется свежая координата; если GPS ещё недоступен, отправляется только состояние связи. Повтор после потери ответа использует прежний UUID, пока точка остаётся текущей; новая точка заменяет старую.
  4. API проверяет устройство, привязку, разрешение на приём, формат, время и повторы.
  5. API сохраняет допустимую историю и обновляет последнюю позицию машины, только если точка новее и относится к её текущему основному источнику.
  6. SignalR публикует позицию авторизованным подписчикам tenant и отдельно — получателям действующих ссылок в пределах их разрешений.
  7. Сервер рассчитывает остановки, пробег, события геозон и оповещения из полученных данных. Промежуток без связи остаётся пробелом и не восстанавливается вымышленным маршрутом или остановкой.

6. GPS: требования

Трекинг непрерывный, включая стоянки, без автоматического отключения по окончании смены или рейса. Realtime означает отображение новой принятой точки без ручного обновления; это не гарантия ежесекундной передачи с телефона. На карте всегда видны возраст точки и состояние связи; отсутствие новых данных не обозначается как подтверждённая стоянка.

Частота и батарея

Основные настройки — интервал GPS в секундах и точность: наивысшая / высокая / средняя / низкая (highest/high/medium/low). Их меняет владелец/администратор, у частника — сам владелец; корпоративный водитель не получает права обходить политику. Целевой интервал задаёт частоту получения/записи свежего замера, а не гарантированный таймер ОС. Стартовый технический диапазон для испытаний — 1–300 секунд, значение по умолчанию — 15 секунд; границы корректируются по батарее/нагрузке. Дистанционный фильтр опционален и по умолчанию отключён, чтобы не подменять настройку времени. Один телефон применяет один текущий профиль; разные устройства могут использовать разные профили.

Предлагаемые стартовые профили собственного клиента для испытаний:

Профиль Обновления в движении Heartbeat на стоянке Назначение
Сбалансированный, по умолчанию 15 с, высокая точность Определяется порогом связи ниже Обычный рейс, приоритет умеренного расхода батареи
Подробный, пример пресета 5 с, наивысшая точность Определяется порогом связи ниже Детальный маршрут, желательно при зарядке
Экономичный, пример пресета 60 с, средняя точность Определяется порогом связи ниже Длительная автономная работа с менее точным маршрутом

Пресеты — начальные предложения для испытаний, не ограничение ручной настройки. Низкая точность доступна независимо от пресетов. Названия уровней отражают запрос к ОС, а не гарантированный радиус в метрах; accuracy_m показывает фактическую оценку замера. Нельзя выдавать среднюю/низкую точность за точную GPS-позицию. Адаптивное снижение частоты на стоянке допустимо только как явная настройка, не как скрытая замена заданного интервала. Точность и частота влияют на батарею: Apple: эффективное использование геолокации.

Связь и пороги 5 / 15 / 30 / 60 секунд

Порог задаёт время с последнего подтверждённого сервером контакта устройства, а не с GPS-замера. Свежая точка или heartbeat подтверждают связь; heartbeat без координаты не подтверждает положение/стоянку. По умолчанию для испытаний предлагается 60 секунд. Для выбранного порога T клиент планирует контакт не реже T/3: примерно 1,5 / 5 / 10 / 20 секунд соответственно; очередная отправка GPS может заменить heartbeat. Сервер использует своё время приёма, отдельно показывает возраст GPS и создаёт одно уведомление при превышении порога.

Это целевой режим, а не обещание точного фонового таймера телефона. ОС, сеть и энергосбережение могут задерживать heartbeat, особенно на iOS; 5 секунд повышают расход сети/батареи и число ложных тревог. При ограничениях ОС UI показывает, что выбранный режим не подтверждён испытаниями/недостижим в текущих условиях; порог самовольно не увеличивается. Постоянное HTTP-подключение/частые heartbeat не превращают стороннюю ОС в гарантированный канал реального времени. Серверная проверка порога и задержка доставки push измеряются отдельно. При административном выключении трекера/архивировании вместо тревоги «потеря связи» показывается соответствующее управляемое состояние.

Низкий расход батареи — приоритет, числовой бюджет пока не задан. В испытаниях фиксируются расход %/час, модель телефона, версия ОС/клиента, профиль, движение/стоянка, зарядка, сеть и пропуски точек за полный рабочий день. Профиль принимается по этим результатам. Платформа хранит назначенный и подтверждённый устройством профиль; собственный клиент получает настройки с сервера и применяет их без переустановки.

Потеря и восстановление связи

При отсутствии сети водитель продолжает поездку, приложение не сохраняет неотправленные GPS-точки для последующей загрузки. Диспетчер/администратор получает уведомление по заданному порогу, карта сохраняет последнюю принятую точку с её временем и состоянием «Нет связи». После восстановления передаётся актуальная позиция, состояние обновляется, событие потери связи закрывается. Серверная история содержит только фактически полученные координаты.

Пробег, движение и остановки за пропуск GPS неизвестны; линия между последней и новой точкой не считается фактическим маршрутом и не включается автоматически в пробег. Геозона не создаёт задним числом события о пути, которого сервер не видел. Это осознанное упрощение тестового проекта. Кэш списка рейсов допустим и не является GPS-очередью; изменения статусов пока подтверждаются при доступной сети.

Управление трекингом

В компании включение/выключение сбора, профиль и разрешение приёма данных меняет владелец/уполномоченный администратор; корпоративный водитель таких прав в UI/API не имеет. Частник делает это сам как IndividualOwner. Изменения аудируются. Политика раздельно задаёт collection_enabled (сбор на телефоне) и ingest_enabled (приём на сервере). Нативный модуль получает политику через авторизованный API, применяет её и подтверждает результат по версии. Получение команды не считается успешным запуском GPS: отдельно сообщаются разрешения, фактическое состояние сбора и ошибка/ожидание ОС. Старое подтверждение не перезаписывает состояние более новой команды. SignalR/push могут ускорять синхронизацию, но после запуска и восстановления сети клиент обязательно запрашивает актуальную политику.

Без связи изменение политики ожидает применения, клиент использует последнюю успешно применённую политику. Серверный запрет приёма действует сразу; после разрешения принимается свежая позиция, досылки накопленных точек нет. Heartbeat/политика доступны при выключенном GPS, если устройство не отозвано; их выполнение ограничено возможностями ОС.

Собственный клиент не отменяет ограничений ОС: человек может отозвать разрешения, выключить телефон или остановить приложение системными средствами. Удалённый запуск фонового сбора не гарантируется, если ОС не разрешает его; администратор видит последнее сообщённое состояние, время сообщения и известную причину, а при необходимости пользователь получает запрос открыть приложение/выдать разрешение. При отсутствии heartbeat точная причина недоступности неизвестна: нельзя утверждать, что телефон выключен или разрешение отозвано. Поведение проверяется на целевых версиях Android/iOS. Источники: Android: ограничения запуска foreground service, Apple: фоновые обновления координат.

Формат и обработка

Поля точки: id, device_id, recorded_at, received_at, latitude, longitude, accuracy_m, speed_mps?, heading_deg?, altitude_m?, battery_pct?, sequence_no, trip_id?. received_at устанавливает сервер; recorded_at — время GPS-замера, а не отправки. Во внешнем JSON используются имена camelCase, в схеме БД — snake_case.

7. Схема данных

Таблица Ключевые поля
tenants id, type (organization/individual), owner_user_id, name, status, settings_json
users id, email, email_verified_at?, phone?, password_hash, status, disabled_at?, preferred_locale
memberships tenant_id, user_id, role_id, status
vehicle_access_grants tenant_id, user_id, vehicle_id, permissions, revoked_at?
roles, permissions роли и разрешения
devices id, tenant_id, driver_id, source_type, external_id?, credential_hash, platform, app_version, tracking_profile_id, last_seen_at, revoked_at
tracking_profiles id, tenant_id, version, name, interval_s, min_interval_s, distance_m?, stationary_interval_s?, accuracy (highest/high/medium/low), connection_timeout_s (5/15/30/60)
tracking_policies id, tenant_id, device_id, version, profile_id, profile_version, collection_enabled, ingest_enabled, effective_at, actor_id
device_tracking_states tenant_id, device_id, applied_policy_version, applied_at, collection_state, permission_state, last_fix_at, last_heartbeat_at, error_code?
vehicles id, tenant_id, plate_number, vin?, status, archived_at?
vehicle_assignments tenant_id, driver_id, vehicle_id, starts_at, ends_at
device_vehicle_assignments id, tenant_id, device_id, driver_id, vehicle_id, starts_at, ends_at, is_primary
trips id, tenant_id, driver_id, dispatcher_user_id?, vehicle_id, status, dates, version
trip_events id, tenant_id, trip_id, trip_stop_id?, actor_id, action, previous_status, new_status, created_at
trip_stops id, tenant_id, trip_id, type (pickup/delivery), ordinal, address, location, status
trip_cargo_items id, tenant_id, trip_id, name, description?, pickup_stop_id, delivery_stop_id
geofences id, tenant_id, name, center, radius_m, rules_json
geofence_assignments tenant_id, geofence_id, vehicle_id, trip_stop_id?, starts_at, ends_at?
geofence_events id, tenant_id, geofence_id, vehicle_id, type, recorded_at
tracking_shares id, tenant_id, created_by, token_hash, token_ciphertext, recipient_label?, vehicle_id, mode (permanent/temporary), trip_id?, permissions_json, history_from?, history_to?, expires_at?, revoked_at
push_subscriptions id, tenant_id, user_id, platform, token, revoked_at
gps_points id, tenant_id, device_id, driver_id, vehicle_id, assignment_id, trip_id?, sequence_no, recorded_at, received_at, location, accuracy_m, speed_mps?, heading_deg?, altitude_m?, battery_pct?
last_positions tenant_id, vehicle_id, device_id, point_id, recorded_at, location, position_version
alerts tenant_id, type, entity_id, created_at, resolved_at
notifications id, tenant_id, recipient_user_id, event_id, type, payload, created_at, read_at?, push_state
trip_summaries tenant_id, trip_id, driver_id, vehicle_id, distance_m, moving_s, stopped_s, unknown_s, calculated_at
activity_summaries tenant_id, vehicle_id, driver_id, period_start, period_end, distance_m, moving_s, stopped_s, unknown_s
audit_logs tenant_id, actor_id, action, target_id, created_at

Индексы: (tenant_id, recorded_at DESC), (device_id, recorded_at DESC), GiST для координат, уникальность (device_id, id). При росте объёма — партиционирование GPS по времени.

Схема концептуальная: технические таблицы сессий/refresh-токенов, приглашений, восстановления доступа и outbox проектируются при детализации Identity/Notifications. Email обязателен, нормализуется и подтверждается; телефон необязателен для связи. Проверяются уникальность email, одно активное членство пользователя в обычном кабинете, принадлежность связанных сущностей одному tenant. Служебный контекст PlatformOwner не создаёт ему членство во всех клиентах. Ограничения одной активной машины individual, одного действующего телефона машины и одной машины на устройство обеспечиваются транзакционно. История привязок сохраняется.

driver_id обозначает пользователя в контексте проверенного членства в данном tenant, а не самостоятельный глобальный доступ к машине. Политики ссылаются на неизменяемую версию GPS-профиля; редактирование профиля не меняет исторически подтверждённые настройки. История статусов рейсов и состояние обработчиков круглых геозон проектируются в своих модулях; таблицы документов не входят в текущую схему MVP.

last_positions содержит одну позицию на машину, а не маркер на каждый телефон. Архивные машины сохраняют снимок внутри кабинета без live-маркера/внешнего доступа. Сводки сохраняют данные рейсов и активность машины/водителя, включая работу без рейса, после очистки точных координат. Они рассчитываются только по полученным данным; пробел связи сохраняется как неизвестный интервал, без дорисовки маршрута/пробега. Это не бессрочная история точного GPS-трека.

Секреты устройств и ссылок не хранятся открытым текстом в БД/логах. Для постоянной ссылки сохраняются хеш проверки и защищённо зашифрованный токен с ключом вне БД: уполномоченный владелец/администратор/диспетчер может повторно скопировать тот же URL после входа. Ротация ключей шифрования не меняет URL; восстановление ключей учитывается в резервном копировании. Только явный сброс генерирует новый токен и прекращает доступ по старому URL; удаление/отзыв закрывает существующий. Получатель может переслать уже имеющийся URL без права сброса.

Для постоянной ссылки expires_at = null, привязки к ограничивающему доступ рейсу нет. Для временной ссылки задаётся срок и/или конкретный рейс. Проверка доступа различает эти режимы: завершение рейса не отзывает постоянную ссылку на автомобиль.

8. API v1

Базовый URL: /api/v1. HTTPS, JSON, UTC ISO-8601, пагинация, стандартные ответы об ошибках.

Ниже — каталог операций, а не окончательный OpenAPI-контракт. Для каждого ресурса уточняются схемы, доступные методы, фильтры, ограничения и коды ошибок. Пользовательская авторизация и авторизация GPS-устройства имеют разные области доступа.

Метод Endpoint Назначение
POST /auth/register/company Регистрация владельца и организации
POST /auth/register/individual Регистрация частного водителя и личного кабинета
POST /auth/login Вход
POST /auth/refresh Обновление токенов
POST /auth/logout Отзыв только UI-сессии, без остановки и отзыва трекера
POST /auth/contacts:verify, /auth/invitations:accept, /auth/password:reset-request, /auth/password:reset Проверка контакта, активация приглашения и восстановление доступа; отдельные операции
GET /me Профиль, единственный активный кабинет, тип и разрешения
PATCH /me Профиль и предпочитаемая локаль
GET/PATCH /tenants/{tenantId} Кабинет и его разрешённые настройки
GET/POST/PATCH /tenants/{tenantId}/memberships[/{id}] Приглашения/членство, роли, блокировка с серверной проверкой полномочий
GET/POST/DELETE /tenants/{tenantId}/vehicle-access-grants[/{id}] Выдача/отзыв доступа Viewer к конкретной машине
GET/POST /tenants/{tenantId}/drivers Водители
PATCH /tenants/{tenantId}/drivers/{id} Изменение водителя
GET/POST /tenants/{tenantId}/vehicles Автомобили
PATCH /tenants/{tenantId}/vehicles/{id} Изменение/архивирование машины с сохранением истории
GET/POST/PATCH /tenants/{tenantId}/vehicle-assignments[/{id}] Назначения водителей и периоды действия
GET/POST/PATCH /tenants/{tenantId}/device-vehicle-assignments[/{id}] Основной GPS-источник машины и история переключений
GET/POST /tenants/{tenantId}/trips Рейсы
PATCH /tenants/{tenantId}/trips/{id} Данные/назначение рейса для администратора/диспетчера; не ручное изменение рабочего статуса
PATCH /tenants/{tenantId}/trips/{id}/status Рабочее действие назначенного водителя; completed вычисляет сервер
GET/POST/PATCH /tenants/{tenantId}/trips/{tripId}/stops[/{id}] Погрузки/разгрузки; состав редактирует диспетчер, рабочий статус — водитель
GET/POST/PATCH /tenants/{tenantId}/trips/{tripId}/cargo-items[/{id}] Список грузов и их точки погрузки/разгрузки
GET/POST/PATCH/DELETE /tenants/{tenantId}/geofences[/{id}] Геозоны и правила событий
GET/POST/PATCH /tenants/{tenantId}/geofence-assignments[/{id}] Привязки геозон к машинам/точкам доставки
POST /tenants/{tenantId}/devices Регистрация телефона/трекера с проверкой прав и выдача учётных данных устройства
GET /tenants/{tenantId}/devices[/{id}] Устройства, фактическое состояние и применённая политика
POST /tenants/{tenantId}/devices/{id}:revoke Отзыв GPS-устройства
POST /tracking/auth:refresh Безопасное обновление учётных данных активного устройства
GET/POST/PATCH /tenants/{tenantId}/tracking-profiles[/{id}] GPS-профили с версионированием настроек
PATCH /tenants/{tenantId}/devices/{id}/tracking-policy Управление сбором, приёмом GPS и назначенным профилем
GET /tracking/config Актуальная политика и GPS-профиль авторизованного устройства
POST /tracking/config:ack Подтверждение применения версии политики или сообщение об ошибке
POST /tracking/heartbeat Текущее состояние устройства, разрешений и время GPS-замера
POST /tracking/position Актуальная позиция собственного клиента, один замер без offline-пакетов
GET/POST /tenants/{tenantId}/tracking-shares Список и создание постоянных (по умолчанию) или временных ссылок
PATCH/DELETE /tenants/{tenantId}/tracking-shares/{id} Изменение или отзыв ссылки
POST /tenants/{tenantId}/tracking-shares/{id}:copy Получить тот же URL для повторного копирования с проверкой прав
POST /tenants/{tenantId}/tracking-shares/{id}:reset Явно сменить токен, прекратить HTTP/realtime-доступ по старому URL
GET /shared/tracking/{token} Только разрешённые данные по действующей ссылке
POST/DELETE /me/push-subscriptions[/{id}] Регистрация/отзыв push-устройства
GET /tenants/{tenantId}/tracking/last Последние позиции
GET /tenants/{tenantId}/tracking/history История
GET /tenants/{tenantId}/reports/activity Пробег, движение, остановки и время; driver/vehicle/trip, период, timezone и units
GET /tenants/{tenantId}/alerts События
GET /me/notifications Центр уведомлений пользователя
PATCH /me/notifications/{id} Отметка прочтения собственного уведомления
GET /tenants/{tenantId}/audit-logs Журнал действий в пределах прав
GET /hubs/tracking SignalR

Обозначение [/{id}] в таблице сокращённо объединяет коллекцию и отдельный ресурс, но не разрешает все перечисленные методы на обоих URL автоматически. Для каждой операции проверяются разрешения. Только /tracking/* использует область авторизации устройства; остальные закрытые операции требуют пользовательского контекста с проверкой роли, а /shared/* — действующей ссылки. deviceId сверяется с авторизованным устройством и не является секретом. Учётные данные хранятся в защищённом хранилище ОС и могут быть отозваны. Корпоративный водитель может зарегистрировать только свой разрешённый телефон и не меняет политику трекинга. Частник выполняет разрешённые операции со своей машиной как владелец.

GPS-профиль ссылается на версию настроек. /tracking/position принимает один актуальный замер; новая позиция заменяет неотправленную старую. Offline-batch API не требуется. Повтор UUID не создаёт вторую запись, включая потерю ответа сервера. Для рейсов отдельно проверяются права редактировать данные/назначение и права водителя менять рабочий статус; закрытие последней обязательной точки и завершение рейса сохраняются одной операцией.

tracking/last возвращает все разрешённые активные машины, включая записи без GPS; архив запрашивается отдельно с явным фильтром. tracking/history требует машину и ограниченный период. История фильтруется по сохранённой машине/привязке, а не по текущему владельцу телефона. Retention и пагинация применяются также к публичной истории; отсутствие старых сырых точек показывается как недоступная детализация, а не нулевой пробег.

Пример запроса собственного клиента к /tracking/position:

{
  "deviceId": "device-uuid",
  "position": {
    "id": "point-uuid",
    "sequenceNo": 1842,
    "recordedAt": "2026-10-26T10:00:00Z",
    "latitude": 52.2297,
    "longitude": 21.0122,
    "accuracyM": 8.5,
    "speedMps": 14.2,
    "headingDeg": 92
  }
}

Ответ: pointId, status (accepted / duplicate); отказ содержит понятный код причины. SignalR-события: tracking.position.updated, trip.status.changed, alert.created; доступ проверяется на сервере. Отозванная при архивировании ссылка не получает ни архивной карточки, ни событий машины.

Для ссылок используются отдельные подписки только на разрешённый объект; токен не даёт членства в tenant-группе. Отзыв/сброс закрывает текущую подписку; истечение срока/окончание рейса применяется только к временной ссылке. Постоянная ссылка доступна между рейсами; её разрешения проверяются для открытых HTTP/realtime-контекстов. Дополнительные события: geofence.entered, geofence.exited, vehicle.archived, notification.created; доставка push повторяется с ключами идемпотентности, но получение ровно один раз на телефоне не гарантируется.

9. Экраны

Web: Login → Overview → Live Map → History → Drivers → Vehicles → Trips → Alerts → Reports → Users & Roles → Settings → Audit.

Mobile Driver: Login → GPS Status → My Trips → Trip Details → Profile.

Mobile Admin: Overview → Map → Drivers → Trips → Alerts → Settings.

Регистрация: Выбор «Компания» / «Частный водитель» → Контакты и подтверждение → Организация и приглашения / Моя машина → Подключение телефона в собственном приложении → Разрешения геолокации и диагностика.

Частник (Web/Mobile): Мой автомобиль → Состояние трекера → История → Поделиться постоянной ссылкой → Активные ссылки; Рейсы по необходимости → Архив машин.

Компания: Карта активного автопарка → Автомобиль → Поделиться постоянной ссылкой → Копировать/отправить ссылку → Управление ссылками (в том числе явный сброс) → Архив машин.

Получатель ссылки: Карта автомобиля → Последнее обновление и связь → Копировать/переслать ссылку → Разрешённые сведения о рейсе; отдельные состояния «Доступ отозван» и, только для временных ссылок, «Ссылка истекла».

Дополнительные экраны MVP: Грузы и погрузки/разгрузки рейса → Меню связи → Круглые геозоны → Центр уведомлений и настройки push → Внутренний архив машин → Настройки GPS/связи → Часовой пояс и единицы. Удаления аккаунта в интерфейсе нет.

Общие требования: адаптивность, локализация на десять языков с первого релиза, включая страницы ссылок и уведомления; обработка ошибок, фильтры, пустые состояния.

Локализация и переводы (i18n)

Язык Код локали
Русский ru
Английский en
Польский pl
Немецкий de
Французский fr
Румынский ro
Испанский es
Итальянский it
Нидерландский nl
Чешский cs

10. Безопасность и соответствие

Размещение, инфраструктуру и организационно-правовые вопросы ведёт администратор заказчика. Команда разработки предоставляет продукт, инструкции развёртывания и настройки хранения/доступа; выбор страны размещения не входит в текущую задачу. Технические механизмы защиты и информирования остаются требованиями продукта.

11. Нефункциональные требования

Показатель Предварительная цель
Задержка realtime p95 ≤ 5 секунд от получения точки сервером до UI при активном соединении
API последних позиций p95 ≤ 500 мс при согласованной тестовой нагрузке
Доступность Цель 99,5%/месяц для MVP, уточнить SLA
GPS без сети Не накапливать точки; уведомить о потере связи, после восстановления передавать актуальную позицию
Масштаб Начало с 2 водителей; без фиксированного продуктового потолка для компаний, рост подтверждается нагрузочными тестами
Хранение GPS Тяжёлая сырая история — ориентир один месяц; техническое стартовое значение 30 суток, настраиваемое
Лёгкие данные Карточки, рейсы, последние позиции, сводки и аудит — без заданного продуктового срока/тарифного лимита в MVP; фактический размер контролируется
Энергопотребление Умеренный расход; бюджет в %/час определяется по испытаниям собственного клиента
Восстановление Целевые RPO/RTO определить перед production

Realtime измеряется от приёма сервером, а не от движения. Проверяются заданный GPS-интервал, связь, доставка на карту и push; для первого теста достаточно своих двух водителей. Большую нагрузку и эксплуатационные цели уточняют по результатам, не усложняя первую реализацию. Постоянные ссылки сохраняют ограничения частоты/объёма запросов.

Последняя позиция хранится отдельным снимком и не удаляется каскадно с gps_points. Принято сохранять карточки авто, рейсы, статусы и сводки без заданного срока; точный GPS-маршрут бессрочно не нужен. Сырые полученные точки хранятся 30 суток, затем остаются сводные показатели. Архивирование не стирает данные рейсов/авто; точный старый трек не восстанавливается. Периоды без связи не имеют точных координат и полного GPS-пробега даже в пределах этих 30 суток.

Оценка непрерывной записи с фиксированным интервалом: 500 устройств × 1 точка/15 секунд × 24 часа ≈ 2 880 000 точек/день. Это ориентир при 15 секундах, а не верхняя граница для настройки 1 секунда и не обещание производительности.

При 15 с за 30 суток одна машина даёт 172 800 точек: при расчётных 512–1024 байт/точку это примерно 84–169 МиБ, а 500 машин — примерно 41–82 ГиБ до резервных копий, репликации и временного запаса. При 1 с объём в 15 раз больше. Это основания считать сырые GPS-точки тяжёлыми; размеры БД/индексов измеряются в тестах. При фиксированном интервале на стоянке число точек не уменьшается: экономия возможна только при явно включённом адаптивном режиме. Heartbeat не сохраняется как отдельная GPS-история; хранится последнее состояние и события потери/восстановления связи.

В MVP тарифные ограничения для компаний не включаются. Будущие варианты подписок: обычный — 3 водителя, повышенный — 7, премиум — 10, безлимитный — без тарифного лимита. Это примеры будущих тарифов, а не текущие ограничения. Безлимитный тариф не означает бесконечную техническую ёмкость. Отдельный сценарий частника рассчитан на одну активную машину.

12. Тестирование и приёмка

Проверки выполняются по мере реализации; этот список не означает, что тесты уже пройдены. Первая практическая проверка — своя компания с двумя водителями, веб-карта и реальные Android/iOS.

  1. Email-регистрация, приглашение водителя и восстановление доступа работают; аккаунт имеет один кабинет. Пользователи разных компаний и Viewer одной машины не получают чужие данные.
  2. Администратор видит все активные машины, включая стоящие, потерявшие связь и не имеющие первой координаты. Собственный GPS работает в фоне в пределах проверенных возможностей ОС.
  3. Logout закрывает UI-сессию, сохраняя нативный трекер. Вход другого пользователя не перепривязывает телефон автоматически.
  4. При потере сети приходит уведомление по выбранному порогу 5/15/30/60 секунд. Точки на телефоне не накапливаются; реконнект отправляет свежую позицию, а пропуск остаётся пробелом в истории/отчёте. Нет ложной стоянки, вымышленного маршрута и досылки старых точек.
  5. Начало замены останавливает серверный приём старого устройства; до активации нового у машины нет активного трекера. Новое устройство начинает со свежей координаты, принятая ранее история сохраняется.
  6. Интервал в секундах и четыре уровня точности применяются устройством. Проверены движение, стоянка, заблокированный экран, перезапуск, батарея и реальные ограничения фоновой работы; неподтверждённые режимы не выдаются за проверенные.
  7. Повтор текущего UUID не дублирует точку, старый запрос не откатывает маркер. После разрыва SignalR карта восстанавливает актуальный снимок с проверкой прав.
  8. Частник регистрируется, добавляет одну активную машину, подключает телефон и делится постоянной ссылкой без обязательного рейса. Вторая активная машина/телефон не появляются из-за параллельных запросов.
  9. Ссылка сохраняется между рейсами и при замене телефона, повторно копируется после входа; явный сброс/отзыв закрывает прежний URL и открытые подписки. Архивирование отзывает внешний доступ и не раскрывает архив машины публично.
  10. Архив доступен внутри кабинета: машина исчезает с live-карты, но сохраняются данные авто, последняя позиция, рейсы и сводки. При возврате из архива отозванные ссылки/трекеры не восстанавливаются автоматически.
  11. Диспетчер/администратор создаёт и назначает рейс с грузами и их погрузками/разгрузками. Рабочие статусы отмечает водитель; закрытие последней обязательной точки автоматически завершает рейс. Администратору/диспетчеру не доступен обходной ручной переключатель статусов.
  12. Меню связи предлагает звонок диспетчеру/администратору и переход в настроенный внешний мессенджер; встроенный чат не требуется.
  13. Круглые геозоны создают события/уведомления без изменения статуса точки. Показания у границы и пропуск GPS не создают ложную историю пересечений. Центр уведомлений работает независимо от успеха push.
  14. Отчёты показывают пробег, движение, остановки и время по машине/водителю/рейсу с настройкой часового пояса/единиц. После очистки 30-дневных координат остаются карточки, данные рейсов и сводки, включая активность без рейса.
  15. Проверены десять локализаций, переключение языка, мобильные нативные сообщения и push. MapLibre работает в вебе, Android/iOS WebView и по внешним ссылкам; провайдер подложки заменяется конфигурацией без изменения мониторинга. Секреты ссылок не уходят картографическому провайдеру.
  16. Документы, удаление аккаунта через UI/API, публичный архив, накопление offline-точек и дорожные слои отсутствуют в MVP. Служебный доступ PlatformOwner аудируется; блокировка и деактивация сохраняют историю.

13. Этапы реализации

Этап Результат
0. Discovery и GPS spike Два сценария регистрации, UX, прототип собственного нативного GPS и фоновой отправки, батарея и ограничения управления на Android/iOS
1. Foundation Репозиторий, CI/CD, БД, регистрация компаний и частников, multi-tenant, RBAC, Vue I18n и общие словари десяти языков
2. Tracking & Sharing Собственный Kotlin/Swift GPS-модуль, передача актуальной позиции без offline-очереди, политика/профиль, SignalR, постоянные ссылки и отзыв доступа
3. Dashboard OpenStreetMap + MapLibre, конфигурируемый провайдер тайлов, кабинет одной машины, карта автопарка, история и статусы
4. Fleet & Trips Водители, автомобили, замена телефона, рейсы/точки в произвольном порядке, быстрая связь с диспетчером, субаккаунты, архив
5. Reports, Geofences & Alerts Отчёты с timezone/единицами, круглые геозоны, push Android/iOS и центр уведомлений, пороги связи, аудит
6. Hardening & Release Безопасность, нагрузка, батарея, публикация приложений

Прежняя оценка 12–20 недель остаётся предварительной: собственный GPS-клиент на двух ОС, геозоны, короткие пороги связи, push, десять языков и сценарий частника требуют уточнения после этапа 0. Документы исключены из текущей оценки. Разработка и испытания нативного фонового трекинга входят в MVP; срок не считается обязательством до оценки подтверждённых сценариев.

14. Структура репозитория

fleet-platform/
  apps/
    web/                 # Vue dashboard
    mobile/              # Tauri + Vue Android/iOS
      src-tauri/         # оболочка и интеграция собственного GPS-плагина
  plugins/
    tracking/            # Tauri plugin: native GPS, отправитель текущей позиции
      android/           # Kotlin: location service и фоновая работа
      ios/               # Swift: Core Location и фоновая работа
  backend/
    Fleet.Api/
    Fleet.Application/
    Fleet.Domain/
    Fleet.Infrastructure/
    Fleet.Tests/
  packages/
    ui/
    map/                 # MapLibre, конфигурация подложки, слои мониторинга
    api-client/
    shared-types/
    i18n/                # общие ключи и словари ru/en/pl/de/fr/ro/es/it/nl/cs
  infra/
    docker/
    deploy/
  docs/
    api/
    architecture/
    decisions/

Bun управляет frontend-зависимостями и сборкой; .NET SDK — backend. OpenAPI используется для генерации типизированного API-клиента.

15. Принятые решения и рабочие уточнения

Зафиксировано для тестового MVP

  1. SaaS для компаний и частника с одной активной машиной. В первой компании два водителя. Один аккаунт имеет один кабинет; многокомпанейность пользователя не нужна. Для компаний тарифных лимитов пока нет.
  2. Регистрация, приглашения, подтверждение и восстановление — по email. Водитель компании активирует приглашение, частник регистрируется самостоятельно.
  3. Собственное приложение Android/iOS с нативным GPS; logout закрывает только UI и не останавливает трекер. Корпоративный водитель не управляет GPS-политикой, частник управляет своей машиной как владелец.
  4. Один телефон обслуживает одну машину. При начале замены старый отключается, до подключения нового машина остаётся без активного трекера. Offline-досылки старого устройства нет.
  5. GPS-интервал задаётся в секундах, точность — наивысшая/высокая/средняя/низкая. Непрерывный режим включает стоянки; фоновые ограничения проверяются на устройствах без дополнительных требований к поведению водителя.
  6. История неотправленных GPS-точек не хранится. При потере связи диспетчер/администратор получает уведомление; после восстановления передаётся актуальная позиция. Пробел в маршруте не восстанавливается искусственно. Ранее предложенные 7 суток/100 000 точек/128 МиБ и batch-загрузка отменены.
  7. Диспетчер мониторит, создаёт/назначает рейсы и выдаёт ссылки. Рабочими статусами управляет водитель, диспетчер/администратор их отслеживает. Viewer видит конкретную разрешённую машину, маршрут, статус и данные. PlatformOwner имеет служебный доступ для поддержки с аудитом.
  8. Постоянные ссылки активных машин сохраняются до сброса/отзыва и передаются логистом клиентам без регистрации. Архивные машины не имеют публичного доступа: архивирование отзывает внешние ссылки, архивная карточка/история доступны только внутри кабинета.
  9. Архив предназначен для проданных/списанных машин. Он убирает активный маркер и трекер, но сохраняет сведения об авто, последнюю позицию, рейсы и сводки. Возврат допустим как простое действие администратора, не основной сценарий; отозванный доступ не восстанавливается автоматически.
  10. Достаточно долгосрочных данных о рейсе и машине; бессрочный полный маршрут не нужен. Сырые полученные GPS-точки — 30 суток, лёгкие карточки, рейсы, статусы и сводки — без установленного срока/тарифного лимита. Будущие пакеты смогут задавать лимиты.
  11. Удаление аккаунта не показывается в интерфейсе/управлении и не доступно пользовательским API первой версии. Можно предусмотреть будущую возможность в модели; достаточно текущей блокировки/деактивации. Будущая повторная регистрация того же email создаёт новый аккаунт, не восстанавливая старый; сейчас этот сценарий не разрабатывается.
  12. Рейс завершается автоматически после закрытия последней обязательной точки. Для первого UX используется рабочая модель: список грузов в одном рейсе, у каждого погрузка и разгрузка; подробности ниже. Отдельное ручное подтверждение завершения рейса администратором не нужно.
  13. Меню связи содержит звонок на рабочие номера диспетчера/администратора и переход в настроенные внешние мессенджеры. Встроенный чат не входит в MVP.
  14. Геозоны пока только круглые. Вход/выход создаёт событие и уведомление; статусы точек водитель меняет вручную. Уведомления — push и внутри приложения; варианты порога потери связи 5/15/30/60 секунд уже согласованы.
  15. Отчёты: пробег, движение, остановки и время по водителю/машине/рейсу. Часовой пояс и единицы настраиваются. Данные за период отсутствия GPS обозначаются как неизвестные, не выдаются за полный пробег/стоянку.
  16. OpenStreetMap + MapLibre со сменяемым провайдером тайлов; приоритет ЕС, провайдер/бюджет — после первых тестов. Языки: ru/en/pl/de/fr/ro/es/it/nl/cs. Для Vue-интерфейса — Vue I18n.
  17. Документы, переход частника в компанию, внешние автомобильные GPS/OBD и биллинг — будущие расширения. Дорожные слои отменены, desktop-сборка не нужна.
  18. Инфраструктура, размещение, подпись/публикация, поставщик email и эксплуатационные параметры определяются в процессе разработки. Тестовый проект не усложняется отдельными согласованиями обычных технических деталей.

Рабочий вариант рейса для первого прототипа

Один рейс объединяет грузы. Для каждого задаются описание и две точки: погрузка/разгрузка. Водитель видит, какой груз забрать/сдать в выбранной точке, и отмечает прибытие/завершение; после закрытия всех обязательных точек рейс завершается автоматически. Жёсткой очередности между разными грузами нет, но разгрузка конкретного груза следует после его погрузки. Совпадающие места отображаются одной остановкой. Проверяем удобство на своих перевозках и корректируем экран, без отдельной сложной модели заказов/склада.

Это рабочее предложение по неокончательно выбранному представлению грузов, а не дополнительное обязательство заказчика. Других вопросов, требующих ответа до начала тестовой реализации, сейчас нет. Частоты GPS/связи, пороги расчётов, батарея и поставщики уточняются обычными испытаниями и разработкой.

16. Будущие расширения

Документы (загрузка, заполнение, статусы и чтение диспетчером), переход частника в организацию, ETA, оптимизация маршрутов, экспорт PDF/XLSX, интеграции ERP/TMS, внешние GPS/OBD-устройства, фото-подтверждение доставки, техобслуживание, расширенная аналитика, подписки и биллинг SaaS. Собственный GPS-клиент Android/iOS, круглые геозоны, push и центр уведомлений входят в MVP. Дорожные слои отменены и не входят в план расширений автоматически.