---
title: Синхронизация врачей и отзывов (Backend)
---

# Сценарий 4.1: Синхронизация врачей и отзывов (Infoclinica + Bitrix)

## Бизнес-цель

Актуализировать справочники backend из внешних систем:

- **Список врачей из Инфоклиники** загружается и складывается в PostgreSQL (сущность **`Idoctor`** — staging/интеграционная модель).
- **Нормализация признаков `Specialist`** из Bitrix-related данных (команда **`bitrix-update-doctors`** — очистка `dcodes`).
- **Отзывы** подтягиваются **из MySQL Bitrix** через `BitrixService` и сохраняются как `Review`, связанные с `Specialist`.

## Точки входа

| Тип | Имя команды Symfony | Класс |
| --- | --- | --- |
| CLI | `upload:doctors` | `UploadDoctorsCommand` |
| CLI | `bitrix-update-doctors` | `BitrixUpdateDoctorsCommand` |
| CLI | `bitrix-update-reviews` | `BitrixUpdateReviewsCommand` |

Все три предназначены для **cron** или ручного запуска в контейнере `php84`.

## Сценарий A — `upload:doctors` (Инфоклиника → `Idoctor`)

### Алгоритм

1. Читает активные отделения из PostgreSQL (`Department`, опция `--department` для одного `did`).
2. Для каждого отделения в цикле вызывает HTTP **через клиент Инфоклиники**: `GET /specialists/doctors?departments={did}&onlineMode={0|1}&firstrow=&lastrow=` чанками (`CHUNK_SIZE` 300).
3. Для каждого врача определяется ключ `"{dcode}_{departmentId}_{onlineMode}"`, подгружаются существующие `Idoctor` тем же ключом.
4. `updateDoctorEntity` обновляет поля (`dcode`, `name`, `department`, `filial`, `nearestDate`, `onlineMode`), `persist`, пакетный `flush` каждые `BATCH_SIZE` (150).
5. Между отделениями — `sleep(1)`; между чанками — `usleep(200000)`.

### «Конфликты»

Явного SQL `ON CONFLICT` нет: используется **ORM upsert-паттерн** — найти сущность по составному ключу в PHP или создать `new Idoctor()`, затем `persist`.

## Сценарий B — `bitrix-update-doctors` (PostgreSQL `Specialist`)

### Алгоритм

1. Загружает **все** `Specialist` из БД.
2. Нормализует строку `dcodes`: для каждого врача фильтрует коды длиной ≥ 7, отбрасывает `'0'`, пустые наборы превращает в `null`.
3. `flush` один раз в конце.
4. Обращение к `BitrixService` для `kodoper` **закомментировано** в текущей версии файла.

**Это не загрузка врачей из Bitrix**, а офлайн-очистка данных в уже существующей таблице `specialist`.

## Сценарий C — `bitrix-update-reviews` (MySQL Bitrix → PostgreSQL `Review`)

### Алгоритм

1. Постранично обходит `Specialist` батчами по 5 записей.
2. Для каждого врача `BitrixService::getReviews($specialist->getId())`:
   - читает связанные элементы инфоблоков в **MySQL** (`doctrine.dbal.mysql_connection`);
   - для каждого отзыва известен `REVIEW_ID`.
3. В PostgreSQL ищется `Review` с тем же `externalId`; если нет — `new Review()` + `setExternalId`.
4. Поля текста, рейтинга, автора, даты, активности заполняются из структуры Bitrix (включая «распаковку» сериализованных полей в `getReviews`).
5. Неактивные или без текста — пропуск.
6. `$specialist->addReview($review)`, `flush`; при ошибке драйвера — логирование проблемных UTF-8 последовательностей.

### «Конфликты»

Снова **без `ON CONFLICT`**: идемпотентность за счёт поиска по `externalId` перед вставкой.

## Mermaid

```mermaid
flowchart LR
    subgraph MIS["Инфоклиника"]
        API["GET /specialists/doctors"]
    end
    subgraph PG["PostgreSQL"]
        ID["Idoctor"]
        SP["Specialist"]
        RV["Review"]
    end
    subgraph BX["Bitrix MySQL"]
        IB["Инфоблоки отзывов"]
    end

    UC["upload:doctors"] --> API
    API --> ID
    BD["bitrix-update-doctors"] --> SP
    BR["bitrix-update-reviews"] --> IB
    BR --> RV
    RV --> SP
```

Узлы `UC` / `BD` / `BR` — это команды `upload:doctors`, `bitrix-update-doctors`, `bitrix-update-reviews`.

## Внешние зависимости

| Система | Сценарий |
| --- | --- |
| Инфоклиника HTTP | `upload:doctors` |
| PostgreSQL | все три команды |
| Bitrix MySQL | `bitrix-update-reviews` (и потенциально расширения `BitrixService`) |

## Обработка ошибок и edge cases

- **Сетевые ошибки загрузки врачей** — warning в консоли, переход к следующему чанку/отделению.
- **Проблемные отзывы** — `DriverException` логируется с дампом полей.
- **`BitrixService` зависит от `regionId` в некоторых методах** — убедитесь, что выставление региона покрыто в вашем окружении при расширении команды.

## Ссылки на классы

- `apps/backend/src/Command/UploadDoctorsCommand.php`
- `apps/backend/src/Command/BitrixUpdateDoctorsCommand.php`
- `apps/backend/src/Command/BitrixUpdateReviewsCommand.php`
- `apps/backend/src/Service/Bitrix/BitrixService.php`
- `apps/backend/src/Entity/Idoctor.php`, `Specialist.php`, `Review.php`

См. [backend-ddd.md](../backend-ddd.md) и [data-model.md](../../data-model.md).
