using Rugrid.Sdaid.Notation.Documents;
namespace Rugrid.Sdaid.Epas.Nts.Ui
{
/// <summary>
/// Окно подбора модификаций ТС — модальное окно над карточкой ТС: отбор модификаций
/// справочника «Марка, модель и модификация ТС» под конкретное ТС и расчёт базовой нормы.
/// </summary>
public class DOC_ModificationSelectionModal : IPageDocument, IHasStructuralLinks, IFolder<NtsUiFolder>
{
public SdaidLink[] Links => new[]
{
Rel.Uses<Rugrid.Sdaid.Epas.Nts.Domain.DOC_Vehicle>(),
Rel.Uses<Rugrid.Sdaid.Epas.Nts.Domain.DOC_VehicleCalc>(),
Rel.Uses<Rugrid.Sdaid.Epas.Nts.Domain.DOC_VehicleModification>(),
Rel.Uses<Rugrid.Sdaid.Epas.Nts.Domain.DOC_SelectedModifications>(),
Rel.Uses<Rugrid.Sdaid.Epas.Nts.Backend.Calc.DOC_ProcessingStatusPolicy>(),
Rel.Uses<Rugrid.Sdaid.Epas.Nts.Backend.Calc.DOC_AisRefreshPolicy>(),
Rel.Uses<Rugrid.Sdaid.Epas.Nts.Backend.ModificationSelection.DOC_ModificationGroupSelection>(),
Rel.Uses<Rugrid.Sdaid.Epas.Nts.Backend.ModificationSelection.DOC_ModificationNameRecognition>(),
Rel.Uses<DOC_VehicleCard>(),
Rel.Uses<DOC_VehicleModificationCard>(),
};
public static string _refName = "Окно подбора модификаций";
public string Name => "Окно подбора модификаций";
public string Description =>
@"Отбор модификаций ТС под конкретное ТС и расчёт базовой нормы расхода топлива.";
public string Version => "0.1";
public string Status => "draft";
public string[] Comments => new string[0];
/// <summary>Макет экрана: живой прототип окна подбора модификаций.</summary>
public static string Wireframe = $""""
## Макет
{SdaidHtml.IframeOf<DOC_ModificationSelectionModal>("modification-selection.html", 620)}
{SdaidHtml.LinkTo<DOC_ModificationSelectionModal>("modification-selection.html", "Открыть макет в отдельной вкладке")}
Окно подбора открывается иконкой «Подобрать модификацию» у поля «Базовая норма расхода, л/100 км».
"""";
public static string Structure = $$""""
## Структура экрана
Модальное окно над карточкой ТС; открывается иконкой «Подобрать модификацию» у поля
«Базовая норма расхода, л/100 км». Отбирает модификации справочника
{{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Domain.DOC_VehicleModification>()}} и
определяет базовую норму расхода топлива Hs.
### Основные интерфейсные элементы
| Элемент | Роль |
|---|---|
| Итоговая строка | Выводится в верхней части окна, над всеми блоками; выделена как результат работы окна. Результат секций `fuel.base_norm` и `fuel.reference_norm`: `H_min: X · H_max: Y · Hs: Z · H_эт: W л/100 км` |
| Наименование ТС | Контекстная информация окна, только чтение |
| Вид ТС | Контекстная информация окна, только чтение; задаётся ТС |
| Вид топлива | Контекстная информация окна, только чтение; задаётся ТС |
| Блок «Условия подбора модификаций» (маска) | Задаёт условия отбора в «Отфильтрованные модификации». Маска — значения трёх полей: Марка, Модель, Модификация |
| — Марка | Выбор из марок справочника по виду ТС; «Все марки» — условие не задано |
| — Модель | Выбор из моделей, доступных в группе после фильтра по марке; при смене марки список перестраивается; «Все модели» — условие не задано |
| — Модификация | Подстрока по полному наименованию |
| Блок «Отфильтрованные модификации» | Модификации, прошедшие условия; каждая строка — чекбокс «добавить в Отобранные» и «Выбрать все» |
| — Сообщение при пустом отборе | «нет модификаций для Вид ТС «…» и Вид топлива «…»» — если для условий нет модификаций с установленной нормой |
| Блок «Отобранные модификации» | Самостоятельный список, не зависит от текущей маски; «×» на строку — удалить, «Очистить» — удалить все |
| Баннер «Модификация определена» | Зелёный, при одной отобранной |
| Баннер «Группа модификаций определена» | Жёлтый, при нескольких отобранных |
| Баннер «Модификация не определена» | Серый, при отсутствии отобранных |
| Кнопка «Создать модификацию» | В подвале окна, всегда доступна (независимо от результата отбора — нужной модификации может не быть и при непустом списке); по правам пользователя (администратор модуля) |
| Кнопки «Отменить», «Применить» | Закрытие без изменений / фиксация отобранного |
### Правила подбора
1. **Текущая группа.** При открытии окна «Отобранные» заполняются один раз группой,
уже подобранной для ТС — это разовое копирование, не постоянная синхронизация с
«Отфильтрованными» (см. правило 3а). Если группу сохранил пользователь по
«Применить», способ подбора — «эксперт»; если группа получена автоподбором при приёме
данных ТС из АИС (первом или повторном) — «авто». Когда именно сервис выполняет этот
автоподбор — см. {{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Backend.Calc.DOC_AisRefreshPolicy>()}}.
2. **Базовая группа.** Базовая группа строится при открытии всегда — по виду ТС и виду
топлива. В неё входят все модификации вида ТС с установленной нормой; если по
основному виду топлива такие модификации есть, группа сужается по топливу (модификации
с пустым видом топлива остаются). Если таких модификаций нет, группа остаётся по виду
ТС. Какое поле справочника считается установленной нормой — открытый вопрос
{{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Epics.NtsFuelCalc.OQ_BaseNorm>()}}; сам
отбор — {{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Backend.ModificationSelection.DOC_ModificationGroupSelection>()}},
раздел «Базовая группа».
3. **Маска.** Маска — значения полей Марка, Модель и Модификация; Вид ТС и Вид топлива —
контекст ТС, только чтение, в маску не входят. Маска формируется заново **при каждом
открытии окна**, только из текущих данных АИС («Марка и модель базы ТС») — не зависит
ни от способа подбора, ни от того, что сохранено в «Отобранных». Если базовая группа
пуста — маска пустая, автоуточнение не выполняется. Иначе — автоуточнение: марка —
распознанная в «Марка и модель базы ТС» и присутствующая в базовой группе; модель —
самая длинная модель группы после фильтра по марке, входящая в наименование ТС; если
модель распознана, модификация не заполняется, иначе — остаток наименования без марки.
Распознавание — {{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Backend.ModificationSelection.DOC_ModificationNameRecognition>()}},
композиция уточнения —
{{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Backend.ModificationSelection.DOC_ModificationGroupSelection>()}},
раздел «Композиция уточнения». Маску нигде не нужно хранить — она не переживает
закрытие окна, поэтому хранилища для неё в домене не требуется.
3а. **Независимость «Отфильтрованных» и «Отобранных».** Это два самостоятельных списка.
«Отфильтрованные» — результат применения текущей маски к базовой группе, пересчитывается
при любом изменении маски. «Отобранные» — набор модификаций, собранный правилом 1 при
открытии и далее меняющийся только явными действиями пользователя (правило 3б); смена
маски состав «Отобранных» не меняет.
3б. **Действия с отбором.** Чекбокс строки в «Отфильтрованных» только добавляет её в
«Отобранные»; после добавления строка в «Отфильтрованных» блокируется (серая,
недоступна для повторного действия) — повторно её можно отобрать снова только после
удаления из «Отобранных». «Выбрать все» в «Отфильтрованных» — массовое добавление всех
текущих строк, с той же блокировкой; отдельной кнопки «Снять» в «Отфильтрованных» нет —
убрать уже отобранную запись можно только в блоке «Отобранные». «×» на строку в
«Отобранных» удаляет её оттуда и разблокирует соответствующую строку в
«Отфильтрованных», если она там всё ещё есть (под текущей маской); «Очистить» —
то же самое для всех отобранных разом. Счётчик «Отфильтровано: N» включает
заблокированные строки — N всегда равен числу строк, подошедших под текущую маску,
независимо от того, отобраны они или нет.
4. **Ручные условия.** Марка и Модель — точное совпадение; Модификация — подстрока
полного наименования. Условие, заданное вручную, строгое: если оно даёт пустой
результат, выборка пуста и показывается сообщение об отсутствии модификаций. «Все
марки» / «Все модели» и очистка поля означают, что условие не задано. При смене марки
список моделей перестраивается, а модель, которой нет в новом списке, сбрасывается.
Правки маски, внесённые вручную в открытом окне, не сохраняются между открытиями —
при следующем открытии маска снова формируется из АИС (правило 3). Изменение условий
(ручное или через переоткрытие) не сбрасывает уже отобранные модификации (правило 3а).
5. **Нормы.** По текущей группе сразу считаются минимум $H_{min}$, максимум $H_{max}$ и
базовая норма $H_s$ (секция методики `fuel.base_norm`): $H_s = (H_{max} + H_{min}) / 2$.
$H_s$ показывается в поле «Базовая норма расхода», $H_{min}$ и $H_{max}$ используются
расчётом. Отдельно, независимо от «Отобранных», считается эталонная норма $H_{эт}$ —
определение и формула — секция `fuel.reference_norm`
{{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Backend.Calc.DOC_NtsCalc>()}}. В отличие от
$H_{min}$/$H_{max}$/$H_s$, $H_{эт}$ не пересчитывается при изменении отбора — только
при пересборке самой базовой группы (правило 2); меняется на переоткрытии окна, если
изменились вид ТС или вид топлива ТС.
6. **Статус подбора** — свойство расчёта ТС
({{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Domain.DOC_VehicleCalc>()}}): «Не определена» —
ни одной отобранной модификации; «Определена» — одна; «Определена группа» — несколько.
Бейдж «Не определена» в карточке ТС — красный (не нейтральный): подбор уже проводился
и дал пустой результат, это не то же самое, что «подбор ещё не выполнялся».
7. **Способ подбора** — свойство расчёта ТС: «не определён» — подбор ещё не выполнялся;
«авто» — группа получена сервисом при загрузке ТС и пользователь не нажимал
«Применить»; «эксперт» — пользователь нажал «Применить».
8. **«Применить»** сохраняет группу (содержимое «Отобранных») в
{{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Domain.DOC_SelectedModifications>()}}; маску
не сохраняет — она не хранится нигде (правило 3). Ставит
способ «эксперт», пересчитывает статус и $H_{min}$, $H_{max}$, $H_s$. Марка, модель и
модификация записываются в
{{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Domain.DOC_Vehicle>()}} только если они
однозначны: при одной отобранной — все три; при нескольких модификациях модификация не
записывается, при нескольких моделях — и модель, при нескольких марках — и марка.
«Применить» — самостоятельный триггер расчёта, независимый от приёма данных из АИС
({{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Backend.Calc.DOC_AisRefreshPolicy>()}}
описывает только триггеры, связанные с АИС): расчёт запускается безусловно тем же
механизмом, что и всегда — валидаторы секций
({{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Backend.Calc.DOC_CalcValidators>()}}) либо
дают результат, либо классифицированную ошибку; само применение модификации отдельных
условий на запуск расчёта не накладывает.
9. **Пустая группа.** Если в отобранных нет ни одной модификации с установленной нормой,
статус — «Не определена», $H_s$ не определена, «Применить» недоступна. Расчёт всё равно
запускается безусловно (правило 8) — валидатор секции `fuel.base_norm` возвращает
«Базовая норма не определена», статус расчёта «Выполнен с ошибкой», статус обработки
«Нет модификаций» (см. {{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Ui.DOC_VehicleCard>()}},
{{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Backend.Calc.DOC_ProcessingStatusPolicy>()}},
правило 2).
10. **Создание недостающей модификации.** Кнопка «Создать модификацию» в подвале окна
доступна всегда, не только при пустом отборе: нужной записи может не быть в справочнике
и тогда, когда отбор по виду ТС и топливу непуст (например, нет нужного сочетания
марки/модели/модификации) — по правам пользователя (роль «Администратор модуля»,
{{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Vision.DOC_NtsVision>()}}, «Роли
пользователей»). Кнопка открывает
{{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Ui.DOC_VehicleModificationCard>()}} с видом ТС
и видом топлива, перенесёнными точно, и маркой/моделью — предложением, разобранным из
«Марка и модель базы ТС» отдельной композицией распознавания
({{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Backend.ModificationSelection.DOC_ModificationNameRecognition>()}}):
марка — распознанная марка, модель — остаток наименования без марки. Сохранение не
закрывает окно подбора и не применяет группу автоматически: новая запись становится
доступна для отбора в этом же окне, а условия подбора пересчитываются
({{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Epics.NtsFuelCalc.UseCases.UC_CreateVehicleModification>()}}).
### Поведение
- При открытии выводятся контекст ТС (наименование, вид ТС, вид топлива); маска
формируется из АИС (правило 3), «Отфильтрованные» строятся по ней, «Отобранные» разово
заполняются текущей группой (правило 1). Состав «Отфильтрованных» строится в порядке:
Вид ТС → Вид топлива → Марка → Модель → Модификация.
- Если для вида ТС и вида топлива модификаций с установленной нормой нет, блок
«Отфильтрованные модификации» показывает «нет модификаций для Вид ТС «…» и Вид топлива
«…»»; итоговая строка — «—», «Применить» недоступна (если и «Отобранные» при этом пусты).
Кнопка «Создать модификацию» в подвале окна при этом доступна, как и всегда
(администратору модуля). Тот же текст показывается, когда модификации есть, но их
отсекли ручные условия маски.
- Изменение Марки, Модели или Модификации (как при переоткрытии, так и вручную) пересчитывает
«Отфильтрованные»; «Отобранные» не меняются (правило 3а).
- Список марок — все марки строк справочника по виду ТС. Список моделей ограничен
выбранной маркой и перестраивается при её смене; модель, которой нет в новом списке,
сбрасывается.
- Действия с чекбоксами и блокировкой строк — правило 3б.
- При изменении отобранных пересчитываются $H_{min}$, $H_{max}$ и $H_s$ и обновляется
баннер: зелёный — в отобранных одна модификация, жёлтый — несколько, серый — ни одной.
- Итоговая строка выводится в верхней части окна, над всеми блоками, и выделена как
результат работы окна: `H_min: X · H_max: Y · Hs: Z л/100 км`; при отсутствии норм — `—`.
- «Применить» недоступна, если в отобранных нет модификаций с установленной нормой.
- «Отменить» — окно закрывается без изменений.
- «Обновить из АИС»: маска и «Отфильтрованные» в любом случае пересобираются заново из
текущих данных АИС при следующем открытии окна (правило 3) — отдельного поведения для
«Обновить из АИС» по маске не требуется. Влияние смены вида ТС или вида топлива на уже
сохранённые «Отобранные» описано в {{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Backend.Calc.DOC_AisRefreshPolicy>()}}
(раздел «Подбор модификации», строка «Изменились»): прежняя группа отбрасывается
целиком. Сами триггеры обновления —
{{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Backend.Calc.DOC_AisRefreshPolicy>()}}.
### Открытые вопросы
Открытые вопросы окна подбора модификаций — мастер-список марок и алиасы, источник для
спецтехники и различение пустых состояний — собраны в эпике:
{{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Epics.NtsFuelCalc.OQ_ModificationSelection>()}}.
Статусная модель там же.
"""";
}
}