< Summary

Information
Class: OzonOrdersWeb.Areas.Admin.Models.DataScopeTemplateRegistry
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Admin/Models/DataScopeTemplates.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 155
Coverable lines: 155
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
.cctor()100%210%
GetByKey(...)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
 53public sealed record DataScopeLookupItem(string Id, string Name);
 54
 55/// <summary>
 56/// Реестр доступных шаблонов DataScope. Добавление нового шаблона — просто добавить запись.
 57/// </summary>
 58public static class DataScopeTemplateRegistry
 59{
 060    public static readonly IReadOnlyList<DataScopeTemplate> All = new List<DataScopeTemplate>
 061    {
 062        new()
 063        {
 064            Key = "order-supplier", EntityName = "Order", FkField = "SupplierId",
 065            DisplayName = "По поставщику", Icon = "bi-truck",
 066            Description = "Доступ к заказам только выбранных поставщиков.",
 067            LoadItems = async (db, ct) => await db.Suppliers.AsNoTracking()
 068                .OrderBy(x => x.Name)
 069                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToListAsync(ct)
 070        },
 071        new()
 072        {
 073            Key = "order-client-status", EntityName = "Order", FkField = "ClientStatusId",
 074            DisplayName = "По статусу клиента", Icon = "bi-person-badge",
 075            Description = "Доступ к заказам с выбранными статусами клиента.",
 076            LoadItems = async (db, ct) => await db.ClientStatuses.AsNoTracking()
 077                .OrderBy(x => x.Name)
 078                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name)).ToListAsync(ct)
 079        },
 080        new()
 081        {
 082            Key = "order-app-status", EntityName = "Order", FkField = "AppStatusId",
 083            DisplayName = "По статусу в отчёте", Icon = "bi-clipboard-check",
 084            Description = "Доступ к заказам с выбранными внутренними статусами.",
 085            LoadItems = async (db, ct) => await db.AppStatuses.AsNoTracking()
 086                .OrderBy(x => x.Name)
 087                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToListAsync(ct)
 088        },
 089        new()
 090        {
 091            Key = "order-ozon-client", EntityName = "Order", FkField = "OzonClientId",
 092            DisplayName = "По клиенту Ozon", Icon = "bi-building",
 093            Description = "Доступ к заказам выбранных клиентов (организаций) Ozon.",
 094            LoadItems = async (db, ct) => await db.OzonClients.AsNoTracking()
 095                .OrderBy(x => x.Name)
 096                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToListAsync(ct)
 097        },
 098        new()
 099        {
 0100            Key = "order-manufacturer", EntityName = "Order", FkField = "ManufacturerId",
 0101            DisplayName = "По производителю", Icon = "bi-tools",
 0102            Description = "Доступ к заказам выбранных производителей товаров.",
 0103            LoadItems = async (db, ct) => await db.Manufacturers.AsNoTracking()
 0104                .OrderBy(x => x.Name)
 0105                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToListAsync(ct)
 0106        },
 0107        new()
 0108        {
 0109            Key = "order-warehouse", EntityName = "Order", FkField = "ShipmentWarehouseId",
 0110            DisplayName = "По складу отгрузки", Icon = "bi-box-seam",
 0111            Description = "Доступ к заказам, отгружаемым с выбранных складов.",
 0112            LoadItems = async (db, ct) => await db.Warehouses.AsNoTracking()
 0113                .OrderBy(x => x.Name)
 0114                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name ?? $"#{x.Id}")).ToListAsync(ct)
 0115        },
 0116        new()
 0117        {
 0118            Key = "bitrix-warehouse", EntityName = "BitrixWarehouse", FkField = "Name",
 0119            DisplayName = "По складу Bitrix", Icon = "bi-building-gear",
 0120            Description = "Ограничивает выбор складов Bitrix/1С в транзакциях и формах маппинга складов.",
 0121            LoadItems = async (db, ct) => await db.WarehouseMappings.AsNoTracking()
 0122                .Where(x => !string.IsNullOrWhiteSpace(x.BitrixWarehouseName))
 0123                .Select(x => x.BitrixWarehouseName!)
 0124                .Distinct()
 0125                .OrderBy(x => x)
 0126                .Select(x => new DataScopeLookupItem(x, x))
 0127                .ToListAsync(ct)
 0128        },
 0129        new()
 0130        {
 0131            Key = "order-delivery", EntityName = "Order", FkField = "DeliveryId",
 0132            DisplayName = "По типу доставки", Icon = "bi-geo-alt",
 0133            Description = "Доступ к заказам с выбранными типами доставки.",
 0134            LoadItems = async (db, ct) => await db.Deliveries.AsNoTracking()
 0135                .OrderBy(x => x.Name)
 0136                .Select(x => new DataScopeLookupItem(x.Id.ToString(), x.Name)).ToListAsync(ct)
 0137        },
 0138        new()
 0139        {
 0140            Kind = DataScopeTemplateKind.Range,
 0141            Key = "order-price", EntityName = "Order", FkField = "Price", RangeIsDecimal = true,
 0142            DisplayName = "По цене (мин–макс)", Icon = "bi-currency-exchange",
 0143            Description = "Заказы, у которых цена единицы (Price) в заданном диапазоне. Пустая граница — без ограничения
 0144            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 0145        },
 0146        new()
 0147        {
 0148            Kind = DataScopeTemplateKind.Range,
 0149            Key = "order-quantity", EntityName = "Order", FkField = "Quantity", RangeIsDecimal = false,
 0150            DisplayName = "По количеству (мин–макс)", Icon = "bi-boxes",
 0151            Description = "Заказы с количеством единиц (Quantity) в заданных пределах.",
 0152            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 0153        },
 0154        new()
 0155        {
 0156            Kind = DataScopeTemplateKind.Range,
 0157            Key = "order-shipment-amount", EntityName = "Order", FkField = "ShipmentAmount", RangeIsDecimal = true,
 0158            DisplayName = "По сумме отправления (мин–макс)", Icon = "bi-receipt",
 0159            Description = "Заказы по полю ShipmentAmount (общая сумма отправления).",
 0160            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 0161        },
 0162        new()
 0163        {
 0164            Kind = DataScopeTemplateKind.Range,
 0165            Key = "order-purchase-price", EntityName = "Order", FkField = "PurchasePrice", RangeIsDecimal = true,
 0166            DisplayName = "По закупочной цене (мин–макс)", Icon = "bi-bag",
 0167            Description = "Заказы, у которых закупочная цена (PurchasePrice) в заданном диапазоне.",
 0168            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 0169        },
 0170        new()
 0171        {
 0172            Kind = DataScopeTemplateKind.Range,
 0173            Key = "order-min-profit", EntityName = "Order", FkField = "MinProfit", RangeIsDecimal = true,
 0174            DisplayName = "По MinProfit (мин–макс)", Icon = "bi-graph-down",
 0175            Description = "Диапазон по полю MinProfit (нижняя оценка прибыли).",
 0176            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 0177        },
 0178        new()
 0179        {
 0180            Kind = DataScopeTemplateKind.Range,
 0181            Key = "order-max-profit", EntityName = "Order", FkField = "MaxProfit", RangeIsDecimal = true,
 0182            DisplayName = "По MaxProfit (мин–макс)", Icon = "bi-graph-up",
 0183            Description = "Диапазон по полю MaxProfit (верхняя оценка прибыли).",
 0184            LoadItems = static async (_, _) => await Task.FromResult(new List<DataScopeLookupItem>())
 0185        },
 0186        // === Склады для транзакции "Принят на склад" ===
 0187        new()
 0188        {
 0189            Key = "accepted-to-warehouse-sender", EntityName = "AcceptedToWarehouseSender", FkField = "Name",
 0190            DisplayName = "Склад отправителя (Принят на склад)", Icon = "bi-arrow-up-circle",
 0191            Description = "Ограничивает выбор склада отправителя на странице «Принят на склад».",
 0192            LoadItems = async (db, ct) => await db.WarehouseMappings.AsNoTracking()
 0193                .Where(x => !string.IsNullOrWhiteSpace(x.BitrixWarehouseName))
 0194                .Select(x => x.BitrixWarehouseName!)
 0195                .Distinct()
 0196                .OrderBy(x => x)
 0197                .Select(x => new DataScopeLookupItem(x, x))
 0198                .ToListAsync(ct)
 0199        },
 0200        new()
 0201        {
 0202            Key = "accepted-to-warehouse-recipient", EntityName = "AcceptedToWarehouseRecipient", FkField = "Name",
 0203            DisplayName = "Склад получателя (Принят на склад)", Icon = "bi-arrow-down-circle",
 0204            Description = "Ограничивает выбор склада получателя на странице «Принят на склад».",
 0205            LoadItems = async (db, ct) => await db.WarehouseMappings.AsNoTracking()
 0206                .Where(x => !string.IsNullOrWhiteSpace(x.BitrixWarehouseName))
 0207                .Select(x => x.BitrixWarehouseName!)
 0208                .Distinct()
 0209                .OrderBy(x => x)
 0210                .Select(x => new DataScopeLookupItem(x, x))
 0211                .ToListAsync(ct)
 0212        },
 0213    };
 214
 215    public static DataScopeTemplate? GetByKey(string? key)
 0216        => 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

.cctor()
GetByKey(System.String)