---
title: Расписание врача и кеш слотов (Backend)
---

# Сценарий 2.2: Расписание и «кеш» слотов

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

Пациенту показывают **свободные интервалы** записи к врачу в конкретном филиале и режиме (очный / онлайн). Чтобы не дёргать MIS на каждый запрос, backend хранит **недавно полученное расписание** и отдаёт его при повторных обращениях с теми же параметрами.

**Важно для архитектуры:** несмотря на название сервиса, **данные кеша живут в PostgreSQL** (таблица сущности `Schedule`), а не в Redis. Redis в этом сценарии **не используется** (см. `ScheduleCacheService`).

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

| Тип | Метод + URL | Класс |
| --- | --- | --- |
| HTTP | `GET /specialist/schedule?...` | `SpecialistController::specialistSchedule` |
| CLI | `app:schedule:clear-cache` | `ClearScheduleCacheCommand` (очистка старых строк) |

Фактический вызов MIS выполняется внутри `GetScheduleMessageHandler` (см. [schedule-messenger.md](./schedule-messenger.md)).

## Параметры запроса расписания

Используется `App\Dto\ScheduleDto`:

- `st`, `en` — границы времени (целые);
- `dcode` — код врача;
- `filial` — филиал (в query строка строится как `filialId`);
- `onlineMode` — признак онлайн-расписания.

`ScheduleDto::toQueryString()` формирует строку для поиска в кеше и запроса к MIS (HTTP query).

## Пошаговый алгоритм HTTP

1. Контроллер наполняет DTO из query, валидирует.
2. `SpecialistService::getSchedule($dto)` диспатчит `GetScheduleMessage` (см. отдельную статью).
3. Хендлер сначала зовёт `ScheduleCacheService::getCachedSchedule($queryString, $isOnlineMode)`.

## Логика `ScheduleCacheService`

### TTL («как долго живёт кеш»)

Константа **`CACHE_TTL_MINUTES = 5`**.

`getCachedSchedule`:

- вычисляет порог `createdAt >= now - 5 minutes`;
- `ScheduleRepository::findByQueryModeAndTime($queryString, $isOnlineMode, $createdAfter)`;
- если строк нет — `null` (промах кеша).

То есть **инвалидация по времени**: записи старше 5 минут не считаются валидными для ответа.

### Запись и перезапись

`saveSchedule` перед вставкой новых слотов вызывает **`removeByQueryStringAndMode`**: удаляет все записи кеша с тем же `queryString` и `onlineMode`. Это **жёсткое обновление** среза расписания под ключ запроса.

Каждый слот — строка `Schedule` с полями врача, отделения, даты, интервала, `queryString`, `onlineMode`, `createdAt` и др.

### Ошибки чтения из БД

В `getCachedSchedule` перехват `Exception` → лог `Error reading from cache` → возврат `null` (как промах кеша, дальше пойдут в MIS).

## Инвалидация / уборка

| Механизм | Описание |
| --- | --- |
| По TTL при чтении | старше 5 минут не отдаются |
| `saveSchedule` | удаление предыдущих строк с тем же ключом перед insert |
| `app:schedule:clear-cache --hours=N` | массовое удаление по `createdAt < now - N hours` через `clearOldCache` |
| Опция `--stats` | статистика по таблице без удаления |

## `ScheduleErrorHandlerService`

Не часть кеша; вызывается из хендлера при ошибках HTTP-клиента или непойманных исключений: логирование и возврат массива с `status_code`, телом ответа MIS, длительностью и т.д. (см. [schedule-messenger.md](./schedule-messenger.md)).

## Mermaid

```mermaid
flowchart TD
    A[GET /specialist/schedule] --> B[ScheduleDto + validate]
    B --> C[SpecialistService.getSchedule]
    C --> H[GetScheduleMessageHandler]
    H --> D{Есть свежие Schedule строки?}
    D -->|да ≤5 мин| R[reconstructFromDatabase → ответ cached]
    D -->|нет| E[HTTP MIS intervals]
    E --> S[saveSchedule: delete old + insert Schedule rows]
    S --> R2[ответ api + _meta]
```

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

| Система | Роль |
| --- | --- |
| PostgreSQL | хранение «кеша» `Schedule` |
| Инфоклиника (MIS) | источник расписания при промахе |
| Redis | **не используется** в этом сценарии по текущему коду |

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

- **БД недоступна при чтении кеша** — лог, ответ пойдёт в MIS; если и MIS недоступна — ошибка из хендлера.
- **Пустой ответ MIS** — `normalize` / пустые массивы зависят от клиента (см. `InfoclinicaClientService`).
- **Несогласованность `onlineMode` в DTO**: в контроллере в поле может попадать `0|1` из query — при строгой типизации возможны проблемы валидации (наблюдение для джуна при отладке).

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

- `apps/backend/src/Controller/SpecialistController.php` (`specialistSchedule`)
- `apps/backend/src/Dto/ScheduleDto.php`
- `apps/backend/src/Service/ScheduleCache/ScheduleCacheService.php`
- `apps/backend/src/Repository/ScheduleRepository.php`
- `apps/backend/src/Command/ClearScheduleCacheCommand.php`
- `apps/backend/src/Service/ErrorHandler/ScheduleErrorHandlerService.php`

Связанный сценарий Messenger: [schedule-messenger.md](./schedule-messenger.md).
