< Summary

Information
Class: OzonOrdersWeb.Areas.Admin.Models.DataScopeLookupItem
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Admin/Models/DataScopeTemplates.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 1
Coverable lines: 1
Total lines: 250
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Id()100%210%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Admin/Models/DataScopeTemplates.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using OzonRepositories.Context;
 3
 4namespace OzonOrdersWeb.Areas.Admin.Models;
 5
 6/// <summary>Тип шаблона: список ID из справочника или числовой диапазон по полю заказа.</summary>
 7public enum DataScopeTemplateKind
 8{
 9    /// <summary>Множество значений (FK) из БД.</summary>
 10    Lookup,
 11    /// <summary>Минимум/максимум по числовому полю <see cref="DataScopeTemplate.FkField"/>.</summary>
 12    Range
 13}
 14
 15/// <summary>
 16/// Шаблон «источник данных» — готовая связь заказа (или другой сущности) с справочником.
 17/// Позволяет администратору собрать DataScope без написания Dynamic LINQ: выбрать поле и
 18/// отметить значения из списка, либо задать числовой диапазон.
 19/// </summary>
 20public sealed class DataScopeTemplate
 21{
 22    /// <summary>Уникальный ключ шаблона (используется в URL).</summary>
 23    public required string Key { get; init; }
 24
 25    /// <summary>Имя сущности-владельца фильтра (например, «Order»).</summary>
 26    public required string EntityName { get; init; }
 27
 28    /// <summary>Человекочитаемое название шаблона («По поставщику», «По статусу клиента»).</summary>
 29    public required string DisplayName { get; init; }
 30
 31    /// <summary>Описание/подсказка на форме.</summary>
 32    public string? Description { get; init; }
 33
 34    /// <summary>Иконка Bootstrap Icons.</summary>
 35    public string Icon { get; init; } = "bi-funnel";
 36
 37    /// <summary>Тип шаблона: список из справочника или диапазон.</summary>
 38    public DataScopeTemplateKind Kind { get; init; } = DataScopeTemplateKind.Lookup;
 39
 40    /// <summary>
 41    /// Для <see cref="DataScopeTemplateKind.Lookup"/>: имя FK-поля. Для <see cref="DataScopeTemplateKind.Range"/>: имя
 42    /// числового поля (например, <c>Price</c>, <c>Quantity</c>).
 43    /// </summary>
 44    public required string FkField { get; init; }
 45
 46    /// <summary>Для диапазона: <see langword="true"/> — <see cref="decimal?"/>, иначе <see cref="int?"/>.</summary>
 47    public bool RangeIsDecimal { get; init; } = true;
 48
 49    /// <summary>Делегат, возвращающий пары (id, name) справочника. Для Range не используется (может вернуть пустой спис
 50    public required Func<ApplicationDbContext, CancellationToken, Task<List<DataScopeLookupItem>>> LoadItems { get; init
 51}
 52
 053public sealed record DataScopeLookupItem(string Id, string Name);
 54
 55/// <summary>
 56/// Реестр доступных шаблонов DataScope. Добавление нового шаблона — просто добавить запись.
 57/// </summary>
 58public static class DataScopeTemplateRegistry
 59{
 60    public static readonly IReadOnlyList<DataScopeTemplate> All = new List<DataScopeTemplate>
 61    {
 62        new()
 63        {
 64            Key = "order-supplier", EntityName = "Order", FkField = "SupplierId",
 65            DisplayName = "По поставщику", Icon = "bi-truck",
 66            Description = "Доступ к заказам только выбранных поставщиков.",
 67            LoadItems = async (db, ct) => await db.Suppliers.AsNoTracking()
 68                .OrderBy(x => x.Name)
 69                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToListAsync(ct)
 70        },
 71        new()
 72        {
 73            Key = "order-client-status", EntityName = "Order", FkField = "ClientStatusId",
 74            DisplayName = "По статусу клиента", Icon = "bi-person-badge",
 75            Description = "Доступ к заказам с выбранными статусами клиента.",
 76            LoadItems = async (db, ct) => await db.ClientStatuses.AsNoTracking()
 77                .OrderBy(x => x.Name)
 78                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name)).ToListAsync(ct)
 79        },
 80        new()
 81        {
 82            Key = "order-app-status", EntityName = "Order", FkField = "AppStatusId",
 83            DisplayName = "По статусу в отчёте", Icon = "bi-clipboard-check",
 84            Description = "Доступ к заказам с выбранными внутренними статусами.",
 85            LoadItems = async (db, ct) => await db.AppStatuses.AsNoTracking()
 86                .OrderBy(x => x.Name)
 87                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToListAsync(ct)
 88        },
 89        new()
 90        {
 91            Key = "order-ozon-client", EntityName = "Order", FkField = "OzonClientId",
 92            DisplayName = "По клиенту Ozon", Icon = "bi-building",
 93            Description = "Доступ к заказам выбранных клиентов (организаций) Ozon.",
 94            LoadItems = async (db, ct) => await db.OzonClients.AsNoTracking()
 95                .OrderBy(x => x.Name)
 96                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToListAsync(ct)
 97        },
 98        new()
 99        {
 100            Key = "order-manufacturer", EntityName = "Order", FkField = "ManufacturerId",
 101            DisplayName = "По производителю", Icon = "bi-tools",
 102            Description = "Доступ к заказам выбранных производителей товаров.",
 103            LoadItems = async (db, ct) => await db.Manufacturers.AsNoTracking()
 104                .OrderBy(x => x.Name)
 105                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToListAsync(ct)
 106        },
 107        new()
 108        {
 109            Key = "order-warehouse", EntityName = "Order", FkField = "ShipmentWarehouseId",
 110            DisplayName = "По складу отгрузки", Icon = "bi-box-seam",
 111            Description = "Доступ к заказам, отгружаемым с выбранных складов.",
 112            LoadItems = async (db, ct) => await db.Warehouses.AsNoTracking()
 113                .OrderBy(x => x.Name)
 114                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToListAsync(ct)
 115        },
 116        new()
 117        {
 118            Key = "bitrix-warehouse", EntityName = "BitrixWarehouse", FkField = "Name",
 119            DisplayName = "По складу Bitrix", Icon = "bi-building-gear",
 120            Description = "Ограничивает выбор складов Bitrix/1С в транзакциях и формах маппинга складов.",
 121            LoadItems = async (db, ct) => await db.WarehouseMappings.AsNoTracking()
 122                .Where(x => !string.IsNullOrWhiteSpace(x.BitrixWarehouseName))
 123                .Select(x => x.BitrixWarehouseName!)
 124                .Distinct()
 125                .OrderBy(x => x)
 126                .Select(x => new DataScopeLookupItem(x, x))
 127                .ToListAsync(ct)
 128        },
 129        new()
 130        {
 131            Key = "order-delivery", EntityName = "Order", FkField = "DeliveryId",
 132            DisplayName = "По типу доставки", Icon = "bi-geo-alt",
 133            Description = "Доступ к заказам с выбранными типами доставки.",
 134            LoadItems = async (db, ct) => await db.Deliveries.AsNoTracking()
 135                .OrderBy(x => x.Name)
 136                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name)).ToListAsync(ct)
 137        },
 138        new()
 139        {
 140            Kind = DataScopeTemplateKind.Range,
 141            Key = "order-price", EntityName = "Order", FkField = "Price", RangeIsDecimal = true,
 142            DisplayName = "По цене (мин–макс)", Icon = "bi-currency-exchange",
 143            Description = "Заказы, у которых цена единицы (Price) в заданном диапазоне. Пустая граница — без ограничения
 144            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 145        },
 146        new()
 147        {
 148            Kind = DataScopeTemplateKind.Range,
 149            Key = "order-quantity", EntityName = "Order", FkField = "Quantity", RangeIsDecimal = false,
 150            DisplayName = "По количеству (мин–макс)", Icon = "bi-boxes",
 151            Description = "Заказы с количеством единиц (Quantity) в заданных пределах.",
 152            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 153        },
 154        new()
 155        {
 156            Kind = DataScopeTemplateKind.Range,
 157            Key = "order-shipment-amount", EntityName = "Order", FkField = "ShipmentAmount", RangeIsDecimal = true,
 158            DisplayName = "По сумме отправления (мин–макс)", Icon = "bi-receipt",
 159            Description = "Заказы по полю ShipmentAmount (общая сумма отправления).",
 160            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 161        },
 162        new()
 163        {
 164            Kind = DataScopeTemplateKind.Range,
 165            Key = "order-purchase-price", EntityName = "Order", FkField = "PurchasePrice", RangeIsDecimal = true,
 166            DisplayName = "По закупочной цене (мин–макс)", Icon = "bi-bag",
 167            Description = "Заказы, у которых закупочная цена (PurchasePrice) в заданном диапазоне.",
 168            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 169        },
 170        new()
 171        {
 172            Kind = DataScopeTemplateKind.Range,
 173            Key = "order-min-profit", EntityName = "Order", FkField = "MinProfit", RangeIsDecimal = true,
 174            DisplayName = "По MinProfit (мин–макс)", Icon = "bi-graph-down",
 175            Description = "Диапазон по полю MinProfit (нижняя оценка прибыли).",
 176            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 177        },
 178        new()
 179        {
 180            Kind = DataScopeTemplateKind.Range,
 181            Key = "order-max-profit", EntityName = "Order", FkField = "MaxProfit", RangeIsDecimal = true,
 182            DisplayName = "По MaxProfit (мин–макс)", Icon = "bi-graph-up",
 183            Description = "Диапазон по полю MaxProfit (верхняя оценка прибыли).",
 184            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 185        },
 186        // === Склады для транзакции "Принят на склад" ===
 187        new()
 188        {
 189            Key = "accepted-to-warehouse-sender", EntityName = "AcceptedToWarehouseSender", FkField = "Name",
 190            DisplayName = "Склад отправителя (Принят на склад)", Icon = "bi-arrow-up-circle",
 191            Description = "Ограничивает выбор склада отправителя на странице «Принят на склад».",
 192            LoadItems = async (db, ct) => await db.WarehouseMappings.AsNoTracking()
 193                .Where(x => !string.IsNullOrWhiteSpace(x.BitrixWarehouseName))
 194                .Select(x => x.BitrixWarehouseName!)
 195                .Distinct()
 196                .OrderBy(x => x)
 197                .Select(x => new DataScopeLookupItem(x, x))
 198                .ToListAsync(ct)
 199        },
 200        new()
 201        {
 202            Key = "accepted-to-warehouse-recipient", EntityName = "AcceptedToWarehouseRecipient", FkField = "Name",
 203            DisplayName = "Склад получателя (Принят на склад)", Icon = "bi-arrow-down-circle",
 204            Description = "Ограничивает выбор склада получателя на странице «Принят на склад».",
 205            LoadItems = async (db, ct) => await db.WarehouseMappings.AsNoTracking()
 206                .Where(x => !string.IsNullOrWhiteSpace(x.BitrixWarehouseName))
 207                .Select(x => x.BitrixWarehouseName!)
 208                .Distinct()
 209                .OrderBy(x => x)
 210                .Select(x => new DataScopeLookupItem(x, x))
 211                .ToListAsync(ct)
 212        },
 213    };
 214
 215    public static DataScopeTemplate? GetByKey(string? key)
 216        => All.FirstOrDefault(t => string.Equals(t.Key, key, StringComparison.OrdinalIgnoreCase));
 217}
 218
 219/// <summary>
 220/// ViewModel формы создания DataScope из шаблона.
 221/// </summary>
 222public sealed class DataScopeFromTemplateVm
 223{
 224    public string TemplateKey { get; set; } = string.Empty;
 225    public string EntityName { get; set; } = string.Empty;
 226    public string FkField { get; set; } = string.Empty;
 227    public string TemplateDisplayName { get; set; } = string.Empty;
 228    public string? TemplateIcon { get; set; }
 229    public string? TemplateDescription { get; set; }
 230
 231    public string Code { get; set; } = string.Empty;
 232    public string DisplayName { get; set; } = string.Empty;
 233    public string? Description { get; set; }
 234    public bool IsActive { get; set; } = true;
 235
 236    public string[] SelectedIds { get; set; } = Array.Empty<string>();
 237    public List<DataScopeLookupItem> Items { get; set; } = new();
 238
 239    /// <summary>Заполняется с сервера: шаблон с диапазоном (форма без списка).</summary>
 240    public bool IsRangeTemplate { get; set; }
 241
 242    /// <summary>Для диапазона: числовой тип поля (десятичный / целый) — для генерации выражения.</summary>
 243    public bool RangeIsDecimal { get; set; } = true;
 244
 245    /// <summary>Нижняя граница (включительно), опционально.</summary>
 246    public decimal? MinValue { get; set; }
 247
 248    /// <summary>Верхняя граница (включительно), опционально.</summary>
 249    public decimal? MaxValue { get; set; }
 250}

Methods/Properties

get_Id()