< Summary

Information
Class: OzonOrdersWeb.WebServices.Orders.OrdersIndexQueryService
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/OrdersIndexQueryService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 102
Coverable lines: 102
Total lines: 242
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 24
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
LoadPageAsync()0%210140%
BuildFilteredQueryableAsync()0%620%
PrepareDistinctOrders(...)0%620%
SelectOrdersForPage(...)0%2040%
LoadByIdsPreservingOrderAsync()0%620%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/OrdersIndexQueryService.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using OServices.FiltersServices.FilterModels;
 3using OzonDomains.Models;
 4using OzonDomains.Models.AccessControl;
 5using Services.DataServices;
 6using Services.FiltersServices.DataFilterManagers;
 7using Services.FiltersServices.SortModels;
 8
 9namespace OzonOrdersWeb.WebServices.Orders;
 10
 11public sealed class OrdersIndexQueryRequest
 12{
 13    public OrderFilterModel Filter { get; init; } = new();
 14
 15    public int Page { get; init; } = 1;
 16
 17    public int PageSize { get; init; } = 15;
 18
 19    public OrderSortState SortOrder { get; init; } = OrderSortState.StandardState;
 20
 21    public AccessProfile? AccessProfile { get; init; }
 22
 23    public bool HasFilters { get; init; }
 24
 25    public bool HasUserAccessRestrictions { get; init; }
 26
 27    public string? UserId { get; init; }
 28
 29    /// <summary>Страница возвратов: быстрый путь по <c>IsReturned</c> без прочих фильтров.</summary>
 30    public bool IsReturnsListPage { get; init; }
 31
 32    public CancellationToken CancellationToken { get; init; }
 33
 34    /// <summary>
 35    /// Дополнительное сужение запроса после общих фильтров (например, множественный выбор справочных
 36    /// значений в гриде). Применяется только на SQL-пути; вызывающий обязан выставить <see cref="HasFilters"/>.
 37    /// </summary>
 38    public Func<IQueryable<Order>, IQueryable<Order>>? QueryPostProcessor { get; init; }
 39}
 40
 41public sealed class OrdersIndexQueryResult
 42{
 43    public List<Order> Orders { get; init; } = new();
 44
 45    public int TotalCount { get; init; }
 46
 47    /// <summary>Нужна in-memory сортировка (нет фильтров и нет RLS).</summary>
 48    public bool RequiresInMemorySort { get; init; }
 49}
 50
 51public interface IOrdersIndexQueryService
 52{
 53    Task<OrdersIndexQueryResult> LoadPageAsync(OrdersIndexQueryRequest request);
 54
 55    List<Order> PrepareDistinctOrders(IReadOnlyList<Order> orders, bool groupByShipment);
 56
 57    List<Order> SelectOrdersForPage(
 58        IReadOnlyList<Order> distinctOrders,
 59        int page,
 60        int pageSize,
 61        out List<Order> ordersForPage);
 62
 63    Task<List<Order>> LoadByIdsPreservingOrderAsync(
 64        IReadOnlyList<int> orderIds,
 65        bool includeArchived);
 66
 67    /// <summary>
 68    /// Отфильтрованный запрос списка (фильтры, ES-сужение, RLS-скоуп) без пагинации —
 69    /// для агрегатов поверх текущего фильтра (например, счётчики значений в справочных фильтрах).
 70    /// </summary>
 71    Task<IQueryable<Order>> BuildFilteredQueryableAsync(OrdersIndexQueryRequest request);
 72}
 73
 74/// <summary>
 75/// Загрузка страницы заказов для Index / IndexAjax: фильтрация, ES, RLS, пагинация на уровне БД.
 76/// </summary>
 77public sealed class OrdersIndexQueryService : IOrdersIndexQueryService
 78{
 79    private readonly OrdersDataServices _orderServices;
 80    private readonly OrderDataFilterManager _dataFilterManager;
 81    private readonly IOrderDataScopeQueryApplier _dataScopeApplier;
 82
 083    public OrdersIndexQueryService(
 084        OrdersDataServices orderServices,
 085        OrderDataFilterManager dataFilterManager,
 086        IOrderDataScopeQueryApplier dataScopeApplier)
 87    {
 088        _orderServices = orderServices;
 089        _dataFilterManager = dataFilterManager;
 090        _dataScopeApplier = dataScopeApplier;
 091    }
 92
 93    public async Task<OrdersIndexQueryResult> LoadPageAsync(OrdersIndexQueryRequest request)
 94    {
 095        var page = request.Page < 1 ? 1 : request.Page;
 096        var pageSize = request.PageSize < 1 ? 15 : request.PageSize;
 097        var ct = request.CancellationToken;
 98
 099        if (!request.HasFilters && !request.HasUserAccessRestrictions)
 100        {
 0101            if (request.IsReturnsListPage)
 102            {
 0103                var baseQuery = _orderServices.GetOrdersQueryable(request.Filter.IncludeArchived)
 0104                    .Where(o => o.IsReturned)
 0105                    .OrderByDescending(o => o.ProcessingDate);
 106
 0107                var total = await baseQuery.CountAsync(ct);
 0108                var skip = (page - 1) * pageSize;
 0109                var returnedOrderIds = await baseQuery
 0110                    .Skip(skip)
 0111                    .Take(pageSize)
 0112                    .Select(o => o.Id)
 0113                    .ToListAsync(ct);
 114
 0115                var orders = returnedOrderIds.Count == 0
 0116                    ? new List<Order>()
 0117                    : await LoadByIdsPreservingOrderAsync(returnedOrderIds, request.Filter.IncludeArchived);
 118
 0119                return new OrdersIndexQueryResult
 0120                {
 0121                    Orders = orders,
 0122                    TotalCount = total,
 0123                    RequiresInMemorySort = true
 0124                };
 125            }
 126
 0127            var indexSkip = (page - 1) * pageSize;
 0128            var indexOrders = await _orderServices.GetOrders(indexSkip, pageSize);
 0129            var indexTotal = await _orderServices.GetTotalCount();
 0130            return new OrdersIndexQueryResult
 0131            {
 0132                Orders = indexOrders,
 0133                TotalCount = indexTotal,
 0134                RequiresInMemorySort = true
 0135            };
 136        }
 137
 0138        var ordersQuery = await BuildFilteredQueryableAsync(request);
 139
 140        // ES-сужение и RLS-скоуп сверху дописывают Where к уже отсортированному запросу и
 141        // не должны ломать порядок, но правило "сортировка по дате принятия, не по релевантности
 142        // ES" обязано соблюдаться строго для всех поисков — переустанавливаем ORDER BY явно
 143        // как последний шаг перед пагинацией.
 0144        ordersQuery = _dataFilterManager.ApplyFinalSortQueryable(ordersQuery, request.SortOrder);
 145
 0146        var totalCount = await ordersQuery.CountAsync(ct);
 0147        var dbSkip = (page - 1) * pageSize;
 0148        var filteredOrderIds = await ordersQuery
 0149            .Skip(dbSkip)
 0150            .Take(pageSize)
 0151            .Select(o => o.Id)
 0152            .ToListAsync(ct);
 153
 0154        var loaded = filteredOrderIds.Count == 0
 0155            ? new List<Order>()
 0156            : await LoadByIdsPreservingOrderAsync(filteredOrderIds, request.Filter.IncludeArchived);
 157
 0158        return new OrdersIndexQueryResult
 0159        {
 0160            Orders = loaded,
 0161            TotalCount = totalCount,
 0162            RequiresInMemorySort = false
 0163        };
 0164    }
 165
 166    public async Task<IQueryable<Order>> BuildFilteredQueryableAsync(OrdersIndexQueryRequest request)
 167    {
 0168        var ordersQuery = _orderServices.GetOrdersQueryable(request.Filter.IncludeArchived);
 0169        ordersQuery = _dataFilterManager.FilterAndSortQueryable(
 0170            ordersQuery,
 0171            request.Filter,
 0172            request.AccessProfile,
 0173            request.SortOrder);
 0174        if (request.QueryPostProcessor != null)
 175        {
 0176            ordersQuery = request.QueryPostProcessor(ordersQuery);
 177        }
 0178        ordersQuery = await _dataFilterManager.NarrowByElasticsearchAsync(ordersQuery, request.Filter);
 0179        return await _dataScopeApplier.ApplyAsync(ordersQuery, request.UserId, request.CancellationToken);
 0180    }
 181
 182    public List<Order> PrepareDistinctOrders(IReadOnlyList<Order> orders, bool groupByShipment)
 183    {
 0184        var distinct = orders
 0185            .GroupBy(o => o.Id)
 0186            .Select(g => g.First())
 0187            .ToList();
 188
 0189        if (!groupByShipment)
 190        {
 0191            return distinct;
 192        }
 193
 0194        var withShipment = distinct
 0195            .Where(o => o.ShipmentId.HasValue)
 0196            .GroupBy(o => o.ShipmentId!.Value)
 0197            .SelectMany(g => g);
 0198        var withoutShipment = distinct.Where(o => !o.ShipmentId.HasValue);
 0199        return withShipment.Concat(withoutShipment).ToList();
 200    }
 201
 202    public List<Order> SelectOrdersForPage(
 203        IReadOnlyList<Order> distinctOrders,
 204        int page,
 205        int pageSize,
 206        out List<Order> ordersForPage)
 207    {
 0208        var viewModelPage = 1;
 0209        var skipForPage = (viewModelPage - 1) * pageSize;
 0210        ordersForPage = distinctOrders.Skip(skipForPage).Take(pageSize).ToList();
 211
 0212        var ordersForPageIds = new HashSet<int>(ordersForPage.Select(o => o.Id));
 0213        foreach (var order in distinctOrders)
 214        {
 0215            if (!ordersForPageIds.Contains(order.Id))
 216            {
 0217                order.OrderExtensions = new List<OrderExtension>();
 0218                order.CartItemStatus = null;
 0219                order.CartItemStatusColor = null;
 220            }
 221        }
 222
 0223        return distinctOrders.ToList();
 224    }
 225
 226    public async Task<List<Order>> LoadByIdsPreservingOrderAsync(
 227        IReadOnlyList<int> orderIds,
 228        bool includeArchived)
 229    {
 0230        if (orderIds.Count == 0)
 231        {
 0232            return new List<Order>();
 233        }
 234
 0235        var ordersFromCache = await _orderServices.GetOrdersByIds(orderIds.ToList(), includeArchived);
 0236        var orderDict = ordersFromCache.ToDictionary(o => o.Id);
 0237        return orderIds
 0238            .Select(id => orderDict.GetValueOrDefault(id))
 0239            .OfType<Order>()
 0240            .ToList();
 0241    }
 242}