using System;
using Rugrid.Sdaid.Notation.Documents;
namespace Rugrid.Sdaid.Epas.Nts.Domain
{
/// <summary>
/// Сводная статистика ТС: агрегирующий read-model по реестру ТС, не хранимая сущность —
/// вычисляется по запросу из уже сохранённых «Транспортное средство» и «Расчёт ТС».
/// Два разреза: состояние обработки (количество ТС по каждому значению справочника «Статус
/// обработки ТС») и потребление топлива (по видам топлива, по нормативному расходу из
/// успешных расчётов). Опциональный фильтр по организационному объекту — тот же принцип
/// «объект и, при включённом «С потомками», подчинённые объекты», что и в реестре ТС.
/// </summary>
public class DOC_VehicleSummaryStatistics : IDomainEntityDocument, IHasStructuralLinks, IFolder<NtsDomainFolder>
{
public SdaidLink[] Links => new[]
{
Rel.Uses<DOC_Vehicle>(),
Rel.Uses<DOC_VehicleCalc>(),
Rel.Uses<DOC_ProcessingStatus>(),
Rel.Uses<DOC_CalculationStatus>(),
Rel.Uses<DOC_FuelKind>(),
Rel.Uses<DOC_NavEntity>(),
};
public static string _refName = "Сводная статистика ТС";
public string Name => "Сводная статистика ТС";
public string Description =>
@"Агрегирующий read-model по реестру ТС: состояние обработки (количество ТС по каждому статусу обработки) и потребление топлива в разрезе вида топлива (нормативный расход — сумма, максимум, минимум, среднее). Не хранимая сущность, вычисляется по запросу из уже сохранённых данных.";
public string Version => "0.1";
public string Status => "draft";
public string[] Comments => new string[0];
public Type Entity => typeof(VehicleSummaryStatistics);
public static string Purpose = $""""
## Назначение
Сводная статистика — не хранимая сущность, а форма результата агрегирующего запроса
по реестру ТС. Вычисляется по запросу (не отчётный движок, не фоновая задача), читает
уже сохранённые {SdaidAnchors.RefTo<DOC_Vehicle>()} и {SdaidAnchors.RefTo<DOC_VehicleCalc>()},
никаких собственных данных не хранит и ничего не пересчитывает.
Используется в двух местах: на главной странице модуля (без фильтра, по всему реестру
ТС) и на вкладке «Сведения» организационного объекта (с фильтром по этому объекту).
Оба места — один и тот же read-model с разным значением входного фильтра, см.
{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Ui.DOC_VehicleSummaryWidget>()}.
## Фильтр по организационному объекту
Поле `OrgObject` — тот же фильтр, что и в {SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Nts.Ui.DOC_VehicleRegistry>()}:
не задан — статистика по всему реестру ТС; задан — по ТС этого объекта и, при включённом
«С потомками», подчинённых объектов (ТС числятся за МЭС и ПМЭС). На главной странице
модуля фильтр не задаётся; на вкладке «Сведения» — всегда задаётся объектом вкладки,
переключателя «С потомками» на самой вкладке нет, значение фиксировано как «с потомками»
(сводка по объекту в целом).
## Группа 1 — состояние обработки
`TotalCount` — общее количество ТС, отобранных фильтром. `ByProcessingStatus` — одна
строка {SdaidAnchors.RefTo<DOC_VehicleProcessingStatusCount>()} на каждое значение
справочника {SdaidAnchors.RefTo<DOC_ProcessingStatus>()} (состав — там, не повторяется
здесь) — строка выводится всегда, даже если по этому статусу нет ни одного ТС
(`Count = 0`), чтобы состав плашек не менялся от выборки к выборке.
{SdaidTable.OfClass<VehicleProcessingStatusCount>()}
## Группа 2 — потребление топлива, в разрезе вида топлива
`ByFuelKind` — одна строка {SdaidAnchors.RefTo<DOC_VehicleFuelConsumptionSummary>()} на
каждое значение {SdaidAnchors.RefTo<DOC_FuelKind>()}, встретившееся среди ТС, отобранных
фильтром (бензин/дизельное топливо; литры разных видов топлива не складываются — два
самостоятельных разреза). `VehicleCount` — количество ТС этого вида топлива с успешным
расчётом (`CalculationStatus` = «Успешно выполнен»), участвующих в агрегации. Нормативный
расход (`NormConsumptionSum/Max/Min/Avg`) — сумма, максимум, минимум и среднее по полю
`VehicleCalc.NormConsumption` (итоговый результат расчёта $Q_н$,
{SdaidAnchors.RefTo<DOC_VehicleCalc>()}) этих ТС; источник — уже сохранённый результат
расчёта, агрегация не обращается к `InputModel` (контракту входа расчёта, существующему
только во время его выполнения) и не запускает расчёт повторно. `ActualConsumption` —
зарезервировано на будущее: в домене ЭНТС сейчас нет источника фактических данных по
топливу ТС; поле не вычисляется и не выводится, пока источник не появится. Не путать с
«Фактическое потребление» модуля ЭНЗС (Ntr) — там про тепло/электричество зданий, другая
модель данных, другой модуль.
{SdaidTable.OfClass<VehicleFuelConsumptionSummary>()}
"""";
}
/// <summary>
/// Доменная сущность «Сводная статистика ТС» — верхнеуровневая форма ответа: фильтр и общее
/// количество ТС. Разрезы — отдельные построчные формы: <see cref="VehicleProcessingStatusCount"/>
/// и <see cref="VehicleFuelConsumptionSummary"/>.
/// </summary>
[DomainEntity]
public class VehicleSummaryStatistics
{
/// <summary>Организационный объект — фильтр</summary>
[Relation]
[DomainField(Alias = "org_object_id",
Description = "Не задан — статистика по всему реестру ТС; задан — по объекту и, при «С потомками», подчинённым объектам")]
public NavEntity? OrgObject { get; set; }
/// <summary>Всего ТС</summary>
[DomainField(Alias = "total_count", Range = "целое, ≥ 0",
Calculated = "Да, количество ТС, отобранных фильтром `OrgObject`")]
public int TotalCount { get; set; }
}
/// <summary>Доменная сущность «Количество ТС по статусу обработки» — строка группы 1.</summary>
public class DOC_VehicleProcessingStatusCount : IDomainEntityDocument, IFolder<NtsDomainFolder>
{
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];
public Type Entity => typeof(VehicleProcessingStatusCount);
}
[DomainEntity]
public class VehicleProcessingStatusCount
{
/// <summary>Статус обработки</summary>
[Relation]
[DomainField(Alias = "processing_status_id", RequiredOnCreate = true)]
public ProcessingStatus? ProcessingStatus { get; set; }
/// <summary>Количество ТС</summary>
[DomainField(Alias = "count", Range = "целое, ≥ 0",
Calculated = "Да, количество ТС выборки с этим статусом обработки")]
public int Count { get; set; }
}
/// <summary>Доменная сущность «Потребление топлива по виду топлива» — строка группы 2.</summary>
public class DOC_VehicleFuelConsumptionSummary : IDomainEntityDocument, IFolder<NtsDomainFolder>
{
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];
public Type Entity => typeof(VehicleFuelConsumptionSummary);
}
[DomainEntity]
public class VehicleFuelConsumptionSummary
{
/// <summary>Вид топлива</summary>
[Relation]
[DomainField(Alias = "fuel_kind_id", RequiredOnCreate = true,
Description = "Vehicle.FuelKindPrimary — основной вид топлива ТС")]
public FuelKind? FuelKind { get; set; }
/// <summary>Количество ТС с успешным расчётом</summary>
[DomainField(Alias = "vehicle_count", Range = "целое, ≥ 0",
Calculated = "Да, количество ТС этого вида топлива с CalculationStatus = «Успешно выполнен»")]
public int VehicleCount { get; set; }
/// <summary>Нормативный расход — сумма, л</summary>
[DomainField(Alias = "norm_consumption_sum", Unit = "л", Range = "вещественное, ≥ 0",
Calculated = "Да, сумма VehicleCalc.NormConsumption по ТС этого вида топлива с успешным расчётом")]
public decimal NormConsumptionSum { get; set; }
/// <summary>Нормативный расход — максимум, л</summary>
[DomainField(Alias = "norm_consumption_max", Unit = "л", Range = "вещественное, ≥ 0",
Calculated = "Да, максимум VehicleCalc.NormConsumption по той же выборке")]
public decimal NormConsumptionMax { get; set; }
/// <summary>Нормативный расход — минимум, л</summary>
[DomainField(Alias = "norm_consumption_min", Unit = "л", Range = "вещественное, ≥ 0",
Calculated = "Да, минимум VehicleCalc.NormConsumption по той же выборке")]
public decimal NormConsumptionMin { get; set; }
/// <summary>Нормативный расход — среднее, л</summary>
[DomainField(Alias = "norm_consumption_avg", Unit = "л", Range = "вещественное, ≥ 0",
Calculated = "Да, среднее VehicleCalc.NormConsumption по той же выборке")]
public decimal NormConsumptionAvg { get; set; }
/// <summary>Фактический расход, л — зарезервировано</summary>
[DomainField(Alias = "actual_consumption", Unit = "л",
Description = "Зарезервировано на будущее: источник фактических данных по топливу ТС в домене ЭНТС не определён; поле не вычисляется")]
public decimal? ActualConsumption { get; set; }
}
}