# Модели данных

Эта страница описывает основные сущности, найденные в `apps/backend/src/Entity` и `apps/cabinet/src/Entity`. В проектах есть много справочных полей; ниже выделены доменные ядра и связи, которые важны для понимания системы.

## Backend ER-схема

```mermaid
erDiagram
  SPECIALIST ||--o{ LOCATION : "id -> specialist_id"
  SPECIALIST ||--o{ REVIEW : "id -> specialist_id"
  SPECIALIST ||--o{ SPECIALIST_DOCS : "id -> specialist_id"
  SPECIALIST ||--o{ SPECIALIST_DCODE_DESCRIPTION : "id -> specialist_id"
  SPECIALIST }o--o{ STOCK : "join table"
  RECORD ||--o| ALERT_SMS : "id -> record_id"
  WIDGET_FORM ||--o{ WIDGET_FORM_INPUT : "id -> widget_form_id"

  SPECIALIST {
    int id PK
    string name
    string alias
    int region_id
    string post
    string experience
    bool active
    bool display_schedule
    jsonb dcodes
  }

  LOCATION {
    bigint id PK
    int specialist_id FK
    bigint dcode
    bigint department
    int filial
    bool online_mode
    date nearest_date
  }

  REVIEW {
    int id PK
    int specialist_id FK
    bool active
    date date_create
    string author
    float rating
    string source
    int external_id
  }

  SPECIALIST_DOCS {
    int id PK
    int specialist_id FK
    string name
    string picture
    bool active
    string type
  }

  SPECIALIST_DCODE_DESCRIPTION {
    int id PK
    int specialist_id FK
    bigint dcode
    bigint department
    text content
  }

  STOCK {
    int id PK
    string name
    text content
    date start_date
    date end_date
  }

  RECORD {
    int id PK
    int specialist_id
    string phone
    datetime create_at
    string hash
    json reserve
  }

  ALERT_SMS {
    int id PK
    int record_id FK
    datetime date_create
    text response
  }

  WIDGET_FORM {
    int id PK
    string name
  }

  WIDGET_FORM_INPUT {
    int id PK
    int widget_form_id FK
    string text
    string type
    string bitrix24_id
    int sort
  }
```

### Backend: перевод сущностей

| Сущность на диаграмме | Русское название | Смысл |
| --- | --- | --- |
| `SPECIALIST` | Врач | Карточка врача в новом API |
| `LOCATION` | Локация приема | Привязка врача к отделению, филиалу и режиму приема |
| `REVIEW` | Отзыв | Отзыв о враче |
| `SPECIALIST_DOCS` | Документ врача | Сертификат/документ/изображение врача |
| `SPECIALIST_DCODE_DESCRIPTION` | Описание врача по `dcode` | Текстовое описание для конкретного кода врача/отделения |
| `STOCK` | Акция | Акция, связанная с врачами через `ManyToMany` |
| `RECORD` | Запись пациента | Локальная запись факта бронирования |
| `ALERT_SMS` | SMS-уведомление | Ответ SMS-провайдера по записи |
| `WIDGET_FORM` | Форма виджета | Конструктор формы |
| `WIDGET_FORM_INPUT` | Поле формы | Поле формы виджета |

### Backend: ключи связей

| Связь | Тип | Ключи |
| --- | --- | --- |
| Врач -> Локация приема | `OneToMany` / `ManyToOne` | `specialist.id = location.specialist_id` |
| Врач -> Отзыв | `OneToMany` / `ManyToOne` | `specialist.id = review.specialist_id` |
| Врач -> Документ врача | `OneToMany` / `ManyToOne` | Doctrine создает `specialist_docs.specialist_id -> specialist.id` |
| Врач -> Описание по dcode | `ManyToOne` на стороне описания | `specialist_dcode_description.specialist_id -> specialist.id` |
| Врач <-> Акция | `ManyToMany` | join-таблица генерируется Doctrine; владелец связи `Stock::$specialist` |
| Запись -> SMS-уведомление | `OneToOne` | Doctrine создает `alert_sms.record_id -> record.id` |
| Форма виджета -> Поле формы | `OneToMany` / `ManyToOne` | Doctrine создает `widget_form_input.widget_form_id -> widget_form.id` |

`Record.specialistId` в backend не является Doctrine-связью с `Specialist`: это логический внешний идентификатор врача, сохраненный как число.

## Backend справочники и контент

```mermaid
classDiagram
  class Department {
    id
    did
    name
    groupName
    onlineMode
    alias
    active
  }

  class Filial {
    id
    fid
    name
    address
    regionId
    siteId
    company
    phone
    email
  }

  class PriceDepartment {
    id
    name
    groupId
    doctCount
    viewInWeb
  }

  class PriceList {
    id
    kodoper
    schname
    specname
    speccode
    priceInfo
    discprice
    filial
    groupId
  }

  class SiteService {
    id
    name
    alias
    regionId
    anons
    content
    faq
    tags
    clinics
  }

  class Disease {
    id
    name
    alias
    regionId
    symptom
    content
    tags
    staffList
  }

  class MedicalCenter {
    id
    name
    alias
    regionId
    doctors
    services
    content
  }

  class Article {
    id
    name
    alias
    doctors
    services
    content
  }

  class News {
    id
    name
    alias
    regionId
    content
    photos
  }

  class Promo {
    id
    name
    alias
    regionId
    clinics
    period
  }
```

## Cabinet ER-схема

```mermaid
erDiagram
  CITY ||--o{ FILIAL : "id -> city_id"
  CITY ||--o{ REVIEW_SOURCE : "id -> city_id"
  CITY ||--o| BANNER : "id -> city_id"
  FILIAL ||--o{ REVIEW_SOURCE : "id -> filial_id"
  CATEGORY_PAGE ||--o{ PAGE : "id -> category_id"
  RECORD ||--o| ALERT_SMS : "id -> record_id"
  WIDGET_FORM ||--o{ WIDGET_FORM_INPUT : "id -> widget_form_id"

  CITY {
    int id PK
    string name
    int region_id
    int time_zone
  }

  FILIAL {
    int id PK
    int city_id FK
    int fid
    string name
    string address
    int site_id
    bool active
    string company
  }

  REVIEW_SOURCE {
    int id PK
    int city_id FK
    int filial_id FK
    string name
    int count_row
    bool active
    float rating
    date date_create
  }

  BANNER {
    int id PK
    int city_id FK
    string href
    string src
    bool active
  }

  CATEGORY_PAGE {
    int id PK
    string name
    bool active
  }

  PAGE {
    int id PK
    int category_id FK
    string name
    string alias
    text description
    bool active
  }

  RECORD {
    int id PK
    int specialist_id
    string phone
    datetime create_at
    string hash
    json reserve
  }

  ALERT_SMS {
    int id PK
    int record_id FK
    datetime date_create
    text response
  }

  WIDGET_FORM {
    int id PK
    string name
  }

  WIDGET_FORM_INPUT {
    int id PK
    int widget_form_id FK
    string text
    string type
    string bitrix24_id
    int sort
  }
```

### Cabinet: перевод сущностей

| Сущность на диаграмме | Русское название | Смысл |
| --- | --- | --- |
| `CITY` | Город | Региональная привязка кабинета |
| `FILIAL` | Филиал | Клиника/филиал, привязанный к городу |
| `REVIEW_SOURCE` | Источник отзывов | Внешний источник рейтинга/отзывов по городу или филиалу |
| `BANNER` | Баннер | Региональный баннер |
| `CATEGORY_PAGE` | Категория страниц | Группа CMS-страниц |
| `PAGE` | Страница | CMS-страница |
| `RECORD` | Запись пациента | Локальная запись факта бронирования |
| `ALERT_SMS` | SMS-уведомление | Ответ SMS-провайдера по записи |
| `WIDGET_FORM` | Форма виджета | Конструктор формы |
| `WIDGET_FORM_INPUT` | Поле формы | Поле формы виджета |

### Cabinet: ключи связей

| Связь | Тип | Ключи |
| --- | --- | --- |
| Город -> Филиал | `OneToMany` / `ManyToOne` | Doctrine создает `filial.city_id -> city.id` |
| Город -> Источник отзывов | `OneToMany` / `ManyToOne` | Doctrine создает `review_source.city_id -> city.id`, `nullable=false` |
| Город -> Баннер | `OneToOne` | Doctrine создает `banner.city_id -> city.id` |
| Филиал -> Источник отзывов | `OneToMany` / `ManyToOne` | Doctrine создает `review_source.filial_id -> filial.id` |
| Категория страниц -> Страница | `OneToMany` / `ManyToOne` | Doctrine создает `page.category_id -> category_page.id`, `nullable=false` |
| Запись -> SMS-уведомление | `OneToOne` | Doctrine создает `alert_sms.record_id -> record.id` |
| Форма виджета -> Поле формы | `OneToMany` / `ManyToOne` | Doctrine создает `widget_form_input.widget_form_id -> widget_form.id` |

`Record.specialistId`, `SpecialistView.dcode`, `LocationView.specialistId`, `PriceList.filial`, `PriceList.groupId`, `PriceList.kodoper` в cabinet используются как логические внешние ключи и фильтры, но не оформлены как Doctrine relations.

## Cabinet представления и справочники

```mermaid
classDiagram
  class SpecialistView {
    id
    name
    speciality
    category
    experience
    description
    alias
    dcode
    regionId
    kodoper
    acceptsDms
  }

  class LocationView {
    id
    dcode
    department
    filial
    specialistId
    onlineMode
    active
    nearestDate
  }

  class PriceDepartment {
    id
    name
    groupId
    groupName
    doctCount
    viewInWeb
  }

  class PriceList {
    id
    kodoper
    schname
    specname
    speccode
    priceInfo
    discprice
    filial
    groupId
  }

  class User {
    id
    email
    roles
    uid
    token
    fullName
    phone
    confirm
    createdAt
    lastActivityAt
  }

  class Usrlog {
    id
    pcode
    agent
    clientIp
    method
    createdAt
  }

  class DirectCompany {
    id
    name
    companyId
    city
  }

  class DirectReport {
    id
    date
    adGroupId
    campaignId
    adId
    impressions
    clicks
    cost
    conversions
  }
```

## Общие доменные понятия

- `Specialist` / `SpecialistView` - врач. В `backend` это полноценная сущность с явными связями; в `cabinet` это view-модель для чтения.
- `Location` / `LocationView` - где и как врач принимает.
- `PriceList` и `PriceDepartment` - прайс и группы услуг.
- `Record` - запись пациента, хранит payload бронирования в `reserve`.
- `AlertSms` - SMS-уведомление по записи.
- `Filial` и `Department` - филиалы и отделения.
- `Review` и `ReviewSource` - отзывы и источники отзывов.
- `WidgetForm` и `WidgetFormInput` - динамические формы для виджетов.

## Особенности модели

- В `backend` используются PHP attributes Doctrine, в `cabinet` - annotations.
- Часть связей хранится не как Doctrine relation, а как внешние идентификаторы (`dcode`, `fid`, `did`, `regionId`, `groupId`, `kodoper`). Это важно: не все связи можно увидеть по `ManyToOne`.
- `backend` подключается к нескольким источникам данных: основной PostgreSQL, Bitrix MySQL и базе cabinet.
- `cabinet` содержит view-сущности (`SpecialistView`, `LocationView`), поэтому часть данных, вероятно, приходит из SQL views или синхронизированных таблиц.
