Перейти к содержимому
Данные
Модель данных

Сущности, связи и инварианты

Логическая модель future-продукта. На портале она реализована типами общее состояние; рабочая схема должна быть подтверждена техническим проектированием и миграциями.
Сущностей
21
Деньги
целые минимальные единицы
Источник истины
PostgreSQL (предложение)

ER-диаграмма ключевых сущностей

Связи между пользователем, календарём, деньгами и зарядкой

Диаграмма упрощена: показаны основные связи, влияющие на целостность денег и броней.

На узком экране прокручивается по горизонтали
Отрисовываем схему…
Полный список полей, связей и инвариантов приведён в карточках сущностей ниже.

Инварианты, которые нельзя терять

Один интервал — одна бронь
Уникальность внешнего события и транзакционная проверка пересечений.
Доступный баланс ≥ 0
Резерв не может превысить доступные средства; двойное удержание запрещено.
Проводка неизменяема
Корректировка создаёт отдельную компенсирующую запись с причиной.
Тарифный снимок
Сессия и бронь сохраняют версию тарифа; история не пересчитывается.
Идемпотентность
Повтор webhook/команды не создаёт второй эффект.
Неопределённость
Тайм-аут и неизвестный статус не превращаются в FINISHED/ACTIVE.
Аудит
Ручное действие фиксирует актора, роль, старое и новое значение, причину.
Телеметрия
Возраст данных определяется по времени измерения/получения; старые данные блокируют операции.

Каталог сущностей

Пользователи: 3Каталог: 3Календарь: 3Деньги: 5Зарядка: 3Поддержка: 2Система: 2

User

ПользовательПользователи

Учётная запись водителя, состояние входа и базовые настройки. Персональные данные минимизируются.

Поля

ПолеТипЗаметка
id•UUIDВнутренний стабильный идентификатор
phoneHashstringНе хранить открытый номер без необходимости
statusenumactive / blocked / deleted
consentVersionstringПоследняя принятая версия согласия
pushEnabledbooleanРазрешение на push
createdAttimestamptzВремя создания в UTC

Связи

  • Vehicle · владеет (1 → N)
  • Consent · принимает (1 → N)
  • Reservation · создаёт (1 → N)

Инварианты

  • Нельзя показывать чужие операции
  • Удаление учитывает обязательное хранение финансовых документов

Vehicle

АвтомобильПользователи

Демонстрационный/пользовательский автомобиль с совместимым разъёмом и ограничением мощности.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
userId•UUIDВладелец
namestringОтображаемое название
connectorenumType 2 / Type 1
maxPowerKwnumericОграничение мощности
isPrimarybooleanОсновной автомобиль

Связи

  • ChargingSession · используется в (1 → N)

Инварианты

  • Неполные параметры не блокируют просмотр каталога
  • Разъём проверяется до Start

Site

ПлощадкаКаталог

Место установки станции с адресом, координатами, часами и правилами доступа.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
addressstringАдрес площадки
coordinatespointКоординаты
hoursjsonbЧасы работы
accessRulesstringПравила доступа

Связи

  • Station · содержит (1 → N)

Инварианты

  • Часы площадки учитываются при доступности

Station

СтанцияКаталог

Логическая/физическая зарядная станция и ссылка на внешнюю систему.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
siteId•UUIDПлощадка
namestringНазвание
csmsIdstringВнешний идентификатор
statusenumopen / closed / maintenance
lastSeenAttimestamptzПоследняя связь

Связи

  • Port · содержит (1 → N)
  • Site · находится на (N → 1)

Инварианты

  • Закрытая станция не предлагает запуск
  • Статус и возраст данных видны пользователю

Port

Порт / разъёмКаталог

Физическая точка подключения с разъёмом, мощностью, QR и текущим статусом.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
stationId•UUIDСтанция
labelstringНомер/буква порта
connectorenumType 2 / Type 1
powerKwnumericНоминальная мощность
qrstringУникальный QR-идентификатор
statusenumavailable / occupied / reserved / maintenance / stale / awaiting_release

Связи

  • PortAvailability · имеет (1 → N)
  • SlotHold · удерживается (1 → N)
  • ChargingSession · используется (1 → N)

Инварианты

  • Один порт не имеет двух активных сессий
  • QR однозначно сопоставлен порту

TariffVersion

Версия тарифаДеньги

Версия тарифа с периодом действия и параметрами расчёта. Исторический снимок не переписывается.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор версии
versionstringЧеловекочитаемая версия
energyPriceintegerМинимальные единицы
shortBookingPriceintegerСтоимость короткой брони
plannedBookingPriceintegerСтоимость плановой брони
effectiveFromtimestamptzНачало действия
statusenumactive / future / archived

Связи

  • Reservation · применяется к (1 → N)
  • ChargingSession · фиксируется в (1 → N)

Инварианты

  • Изменение тарифа не меняет начатую сессию
  • Деньги хранятся целыми единицами

PortAvailability

Доступность портаКалендарь

Рассчитанная доступность порта/интервала и происхождение статуса.

Поля

ПолеТипЗаметка
portId•UUIDПорт
interval•tstzrangeИнтервал
statusenumavailable / occupied / unknown
sourcestringCSMS / датчик / оператор
measuredAttimestamptzКогда получено
confidenceenumconfirmed / estimated / unknown

Связи

  • Port · описывает (N → 1)
  • Incident · может создать (1 → N)

Инварианты

  • Неизвестный статус не отображается как свободный
  • Кэш не является источником истины

SlotHold

Удержание слотаКалендарь

Временная блокировка интервала до оплаты или истечения.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
portId•UUIDПорт
intervaltstzrangeИнтервал
statusenumactive / confirmed / cancelled / expired
expiresAttimestamptzСрок действия
priceintegerСтоимость брони

Связи

  • Reservation · превращается в (1 → 0..1)
  • Payment · оплачивается (1 → N)

Инварианты

  • Активный hold не блокирует слот бесконечно
  • Истечение освобождает интервал

Reservation

БроньКалендарь

Подтверждённое право на порт в интервале с применённым тарифом.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
holdIdUUIDИсходный hold
userId•UUIDПользователь
portId•UUIDПорт
intervaltstzrangeИнтервал
priceintegerСтоимость брони
statusenumconfirmed / completed / cancelled / expired
tariffVersionIdUUIDПрименённый тариф

Связи

  • SlotHold · происходит из (1 → 1)
  • ChargingSession · связана с (1 → 0..1)
  • Payment · оплачена (1 → N)

Инварианты

  • На один интервал порта — одна подтверждённая бронь
  • Отмена освобождает интервал

Payment

ПлатёжДеньги

Попытка/операция оплаты с провайдером, внешним ID, статусом и ключом идемпотентности.

Поля

ПолеТипЗаметка
id•UUIDВнутренний идентификатор
providerIdstringВнешний ID
amountintegerМинимальные единицы
statusenumpending / confirmed / declined / refunded / reconciliation
idempotencyKeystringЗащита от повтора
correlationIdstringСвязь событий
eventsjsonbИстория обработки

Связи

  • WalletEntry · создаёт (1 → 0..N)
  • Reservation · оплачивает (N → 1)

Инварианты

  • Повтор webhook не создаёт второй эффект
  • Сумма и заказ сверяются

WalletEntry

Проводка кошелькаДеньги

Неизменяемая запись движения EcoCoin с типом и происхождением.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
userId•UUIDПользователь
amountintegerПоложительная или отрицательная сумма
typeenumtopup / bonus / compensation / refund / booking / energy / admin_adjustment
relatedIdstringБизнес-операция
reasonstringОбязательна для ручной корректировки

Связи

  • WalletHold · резервируется (1 → N)
  • Payment · связана (N → 0..1)

Инварианты

  • Запись не редактируется задним числом
  • Корректировка — отдельная компенсирующая проводка

WalletHold

Резерв кошелькаДеньги

Зарезервированная часть доступного баланса под бронь или энергию.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
amountintegerСумма резерва
statusenumactive / captured / released
relatedIdstringСвязанная операция

Связи

  • WalletEntry · уменьшает доступный баланс (N → 1)
  • ChargingSession · обеспечивает (1 → 0..1)

Инварианты

  • Доступный баланс не становится отрицательным
  • Одна сумма не удерживается дважды

ChargingSession

Зарядная сессияЗарядка

Жизненный цикл зарядки с применённым тарифным снимком и итогом.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
reservationIdUUIDСвязанная бронь
portId•UUIDПорт
vehicleIdUUIDАвтомобиль
statusenumSTART_REQUESTED … RECONCILIATION_REQUIRED
requestedKwhnumericЗапрошенный объём
energyKwhnumericФактический объём
tariffVersionIdUUIDСнимок тарифа
finalAmountintegerИтоговая сумма

Связи

  • MeterSample · имеет (1 → N)
  • Receipt · формирует (1 → 0..1)
  • Incident · может создать (1 → 0..N)

Инварианты

  • ACTIVE только после подтверждения станции
  • Тарифный снимок не меняется
  • Повторная финализация не дублирует проводку

MeterSample

ПоказаниеЗарядка

Показание энергии/мощности с внешним временем, временем получения и источником.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
sessionId•UUIDСессия
energyKwhnumericЗначение
powerKwnumericМощность
measuredAttimestamptzВремя измерения
receivedAttimestamptzВремя получения
sourceenumcsms / demo

Связи

  • ChargingSession · относится к (N → 1)

Инварианты

  • Устаревание определяется по measuredAt/receivedAt
  • Отрицательное или уменьшающееся значение требует проверки

Command

Команда оборудованиюЗарядка

Start/Stop и другие команды с ID, статусом и результатом.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
sessionIdUUIDСессия
typeenumstart / stop / extend
statusenumpending / accepted / failed / unknown
idempotencyKeystringЗащита от повторной команды
correlationIdstringСвязь с CSMS

Связи

  • ChargingSession · влияет на (N → 1)

Инварианты

  • Повтор не создаёт вторую команду
  • Неизвестный результат не считается успехом

Receipt

ЧекДеньги

Кассовый документ/результат фискализации и история попыток.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
sessionIdUUIDСессия
statusenumforming / ready / error
amountintegerСумма
attemptsintegerЧисло попыток
lastErrorstringБезопасное описание ошибки

Связи

  • ChargingSession · относится к (N → 1)

Инварианты

  • Ошибка чека не удаляет сессию
  • Повтор не дублирует документ

Incident

ИнцидентПоддержка

Разбор неисправности, конфликта или несверенной операции.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
typestringКласс проблемы
severityenumlow / medium / high
statusenumopen / investigating / resolved
relatedIdstringОбъект
correlationIdstringТрассировка

Связи

  • ChargingSession · связан с (N → 1)
  • Port · связан с (N → 1)

Инварианты

  • Инцидент не подменяет финансовую операцию
  • Закрытие аудируется

SupportTicket

ОбращениеПоддержка

Обращение клиента с контекстными связями и историей статусов.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
userIdUUIDПользователь
categorystringКатегория
statusenumnew / assigned / in_progress / resolved
stationIdUUIDКонтекст станции
sessionIdUUIDКонтекст сессии
assigneestringОтветственный

Связи

  • User · создано (N → 1)

Инварианты

  • Поддержка видит только необходимый контекст
  • Назначение и статус аудируются

Notification

УведомлениеСистема

Событие уведомления, канал, статус и связанный объект.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
userIdUUIDПолучатель
channelenumin_app / push
statusenumcreated / sent / failed / read
entityIdstringСвязанный объект

Связи

  • User · адресовано (N → 1)

Инварианты

  • Отказ от push не скрывает in-app событие

AuditEvent

Событие аудитаСистема

Кто, что, когда и почему изменил; старое и новое значение.

Поля

ПолеТипЗаметка
id•UUIDИдентификатор
actorIdstringПользователь или система
rolestringРоль на момент действия
entityTypestringТип объекта
entityIdstringID объекта
oldValue/newValuejsonbБезопасные значения
reasonstringПричина ручного действия

Связи

  • Отдельная запись аудита

Инварианты

  • Аудит не редактируется
  • Секреты и OTP не попадают в аудит
Демо реализация ≠ рабочая схема
В портале сущности описаны TypeScript-типами и живут в клиентском общее состояние. Это позволяет показать корректные переходы и связанные ID, но не заменяет транзакции PostgreSQL, ограничения БД, миграции и проверку конкурентного доступа.