using System;
using Ban.Sdaid.Notation.Documents;
using Ban.Sdaid.Icd;
namespace Ban.Sdaid.Icd.Arch.Tech.Angular
{
/// <summary>
/// ADR-UI-012: API-клиент и модели. Клиент генерируется из OpenAPI бэка и не пишется руками;
/// UI не зависит от API-моделей напрямую — свои UI-модели + маппинг статической функцией в
/// per-page service. Весь трафик к API — через сгенерированный клиент (включая multipart-загрузки).
/// </summary>
public class ADR_UI_012_ApiAndModels : IAdrDocument, IFolder<ArchAngularFolder>
{
public string Name => "ADR-UI-012. API-клиент и модели";
public string Description =>
@"UI ходит к бэку по REST. Контракт описан OpenAPI; клиент к нему **генерируется**, а не пишется
руками. Но UI не должен зависеть от API-моделей напрямую — иначе любая правка контракта расходится по
компонентам и шаблонам. Фиксируем: где живёт и как регистрируется сгенерированный клиент, как устроены
UI-модели, где и как выполняется маппинг API↔UI, и что весь трафик идёт через клиент.";
public string Version => "1.0";
public string Status => "accepted";
public string[] Comments => new[]
{
"2026-08-13. Принято при переводе потребления API на изоляцию через генерируемый клиент; после того как бэк научили отдавать корректный multipart для загрузки файлов.",
};
public Type? Supersedes => null;
public static string S1_Context = """
## Контекст и постановка задачи
Фронт обращается к бэку по REST. Контракт описан OpenAPI-спекой, и клиент к нему **генерируется**
(`swagger-codegen`, `src-ui/swagger-codegen/generate.py`), а не пишется вручную. Прямая зависимость
UI от сгенерированных API-моделей опасна: переименование поля, смена формата даты, вложенные DTO —
и правка расползается по компонентам и шаблонам.
Нужно зафиксировать:
- как генерируется, где лежит и как регистрируется клиент;
- как организованы UI-модели и чем они отличаются от API-моделей;
- где и как выполняется маппинг API ↔ UI;
- что **всё** сетевое взаимодействие идёт через клиент (в т. ч. загрузка файлов).
Контракт — **наш** (бэк в этом же репозитории), поэтому эндпоинты описываются так, чтобы генератор
выдавал рабочие типизированные методы, а не подгоняются под ограничения инструмента задним числом.
""";
public static string S2_DecisionDrivers = """
## Драйверы решения
1. **Изоляция UI от мутаций контракта.** Правка API — точечное изменение маппинга, не сквозная
замена по шаблонам.
2. **Минимум boilerplate.** Слой изоляции не должен плодить файлы ради самого себя — маппинг
простой и локальный.
3. **Прозрачность.** Понятно, где тип, где маппинг, где запрос.
4. **Единый контракт ответа.** Все ручки возвращают одну обёртку (ADR-SRV-001) — фронт разворачивает
её единообразно.
5. **Совместимость с per-page паттерном** (ADR-UI-013): запрос+маппинг+эффекты — в сервисе страницы.
""";
public static string S3_ConsideredOptions = """
## Рассмотренные варианты
- **A. UI работает прямо на API-моделях.** Минимум кода, максимум связности с контрактом — любая
его правка бьёт по компонентам.
- **B. Свои UI-модели; маппинг — всегда в отдельных mapper-файлах.** Чисто, но заводит слой-файл
на каждый DTO даже там, где он не нужен.
- **C. Свои UI-модели; маппинг — статической функцией в per-page service, отдельный
`<feature>.mapper.ts` вводится только при росте/переиспользовании.** Локально, без лишних файлов.
""";
public static string S4_DecisionOutcome = """
## Решение
Выбран **Вариант C**.
### Сгенерированный клиент
- **Генерируется** из OpenAPI бэка (`src-ui/swagger-codegen/generate.py`), один контекст — `icd`.
- **Место**: `src/app/services/api-clients/icd/`; импорт по алиасу `@api/icd` (ADR-UI-003).
- **Не редактируется руками.** Любые правки — в бэке/контракте и перегенерации.
- **Регистрация в DI — явная.** Сгенерированный `ApiModule` не используем (приложение
standalone) — нужные api-сервисы перечисляются в `providers` `app.config.ts`. Базовый URL —
токен `BASE_PATH` из `RuntimeConfigService` (ADR-UI-014).
**Чеклист после перегенерации:** просмотреть `api-clients/icd/api/api.ts` (список сервисов) и
добавить в `providers` `app.config.ts` каждый api-сервис, используемый в per-page сервисах.
Пропуск даёт `NG0201: No provider for _XxxService` при первом обращении.
### UI-модели
- **Общие** (не привязанные к фиче) — в `src/app/models/`: `Pagination`, `Sorting`, `Option<T>` и т. п.
- **Доменные** — в `modules/<feature>/models/<entity>.ts` (имя типа — PascalCase сущности, без
инфраструктурного префикса; имена файлов — по ADR-UI-002).
UI-модель — это **то, что удобно показывать и редактировать**. Типичные отличия от API-модели:
денормализация (плоские lookup-карты вместо вложенных ссылок), удобные форматы (enum-код → подпись,
ISO-строка → отображаемая дата), поля «только для UI». Покрывать **все** поля API-модели она не обязана —
только используемые страницей. Пример — справочники ввода (`modules/input/models/input-reference.ts`),
наполняемые из `@api/icd` маппером.
### Маппинг API ↔ UI
- **База** — статическая функция `toUi` в per-page service: `api.method(...).pipe(map(res => toUi(res.payload)))`.
- **Вынос в `modules/<feature>/<feature>.mapper.ts`** — когда функция разрослась (≈>30–40 строк)
или одна сущность маппится в нескольких сервисах фичи (переиспользование). Пример уже вынесенного —
`modules/input/models/input-reference.mapper.ts`.
- **Направления**: `toUi` (`ApiX → X`, чтение) и `toApi` (`X → команда`, запись; часто отдельные
функции под create/update — состав полей команд различается).
- **Шаблоны видят только UI-модели.** Импорт `@api/*` в шаблонах/компонентах-представлениях запрещён.
### Обёртка ответа
Все ручки возвращают единый контракт `IcdBaseRequestResult` (ADR-SRV-001): `payload` / `isSuccess`
/ `errors[]`. Per-page service разворачивает `payload`, проверяет `isSuccess`, а при ошибке
показывает `errors[].errorMessage` — тексты формирует бэк по-русски, UI их **не переводит**
(ADR-UI-015).
### Весь трафик — через слой `api-clients`
Per-page service **инжектирует сгенерированный api-сервис и зовёт его методы**; он не строит
URL/`HttpClient`/`FormData` сам. Это касается и **загрузки файлов**: так как контракт наш,
эндпоинт описывается корректным `multipart/form-data` (файлы — бинарные поля), и генератор выдаёт
типизированный метод (напр. `apiInputPacketPostForm(payload, files)`), внутри которого сам собирает
`FormData`. Корректность multipart-описания на стороне бэка обеспечивается отдельно (см. связанный
backend-артефакт).
### Загрузка данных
- Методы per-page service возвращают `Observable<UI-модель>` / `Observable<UI-модель[]>`; API-модели
за пределы сервиса не выходят.
- Компонент подписывается через `takeUntilDestroyed()` и кладёт результат в сигнал (ADR-UI-008)
либо использует `toSignal()`.
### Чего не делаем
- **Не используем API-модели в шаблонах.**
- **Не правим сгенерированный клиент руками** — только перегенерация.
- **Не тащим прямой `HttpClient`/`FormData`/`apiBaseUrl` в per-page service** (текущий обход на
загрузке пакета — миграционный долг, S8).
- **Не делаем универсальный авто-маппер** — явные функции дешевле в поддержке.
- **Не кэшируем** данные между страницами (при появлении проблемы — отдельный ADR).
""";
public static string S5_PositiveConsequences = """
## Положительные следствия
- Правка API-контракта — точечное изменение функции маппинга, не сквозная по компонентам.
- UI-модели читаются как «то, что на экране», без шума серверных DTO.
- Per-page service — единая точка запроса, маппинга и эффектов одной страницы.
- Один контракт ответа и один способ разворачивать `payload`/`errors` на всех страницах.
""";
public static string S6_NegativeConsequences = """
## Отрицательные следствия и компромиссы
- Дублирование структуры: API-модель + UI-модель + функция маппинга. Для тривиальных сущностей
избыточно — принимаем как цену изоляции.
- При множестве маппингов в одной фиче сервис разрастается — мониторим и выносим в `<feature>.mapper.ts`.
- Требование «контракт под генератор» иногда обязывает поправить описание эндпоинта на бэке (напр.
корректный multipart), а не обходить его на фронте.
""";
public static string S7_Validation = """
## Проверка
- В шаблонах нет импортов из `@api/*`; шаблоны ссылаются только на UI-модели.
- Per-page services возвращают типы из `modules/<feature>/models/` или `app/models/`, не из `@api/*`.
- Каждый инжектируемый api-сервис перечислен в `providers` `app.config.ts` — открытие страницы не даёт `NG0201`.
- Переименование поля в контракте → перегенерация клиента → правка одной функции маппинга → код собирается.
- Загрузка файлов идёт через сгенерированный метод клиента, а не через прямой `HttpClient`+`FormData`.
""";
public static string S8_OpenQuestions = """
## Открытые вопросы / отложено
- **Миграционный долг — сдача пакета.** Сейчас `modules/input/services/data-input-page.service.ts`
шлёт `POST /api/InputPacket` напрямую `HttpClient`+`FormData` (историческое следствие некорректного
multipart-описания на бэке). Бэк исправлен — перегенерировать клиент, зарегистрировать
`InputPacketService` в `app.config.ts` и перевести сервис на `apiInputPacketPostForm(payload, files)`.
- **Auth-ошибки (401/403).** Глобальная обработка interceptor-ом появится с auth-ADR; до тех пор
ошибки ловит сам per-page service.
- **Помощник `serverErrors → поля формы`** (ADR-UI-009) — введём при первой форме с серверной валидацией.
- **Кэширование частых справочников** — отложено (пока каждая страница грузит заново).
""";
public static string S99_Related = $"""
## Связанные артефакты
- {nameof(Ban.Sdaid.Icd.Arch.Tech.Angular.ADR_UI_003_ProjectStructure)} — место `api-clients/`, алиас `@api`, папки моделей.
- {nameof(Ban.Sdaid.Icd.Arch.Tech.Angular.ADR_UI_013_FeaturePagePattern)} — per-page service как точка запроса и маппинга.
- {nameof(Ban.Sdaid.Icd.Arch.Tech.Angular.ADR_UI_014_RuntimeConfig)} — базовый URL клиента через токен `BASE_PATH` из `RuntimeConfigService`.
- {nameof(Ban.Sdaid.Icd.Arch.Tech.Angular.ADR_UI_008_State)} — данные из API кладутся в сигналы.
- {nameof(Ban.Sdaid.Icd.Arch.Tech.Angular.ADR_UI_015_I18n)} — тексты ошибок формирует бэк, UI не переводит.
- {nameof(Ban.Sdaid.Icd.Arch.Tech.CSharp.ADR_SRV_001_Cqrs)} — единая обёртка ответа `IcdBaseRequestResult`.
""";
}
}