< Summary

Information
Class: OzonOrdersWeb.WebServices.Orders.Grid.AgGridRequestMapper
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/Grid/AgGridRequestMapper.cs
Line coverage
87%
Covered lines: 173
Uncovered lines: 25
Coverable lines: 198
Total lines: 313
Line coverage: 87.3%
Branch coverage
60%
Covered branches: 180
Total branches: 296
Branch coverage: 60.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Filter()100%11100%
get_Sort()100%210%
get_Page()100%11100%
get_MultiValueFilters()100%11100%
get_ExtraFilters()100%11100%
get_Sorts()100%11100%
get_ComputedFilters()100%11100%
IsComputedColumn(...)100%11100%
.cctor()100%11100%
get_SortableColumns()100%11100%
Map(...)76.66%313090.47%
MapSort(...)100%1010100%
IsNativelySupported(...)91.17%3434100%
ShouldUseDbPath(...)100%22100%
ApplyFilter(...)41.66%143915662.5%
ApplyRange(...)100%1010100%
SetValues(...)100%44100%
TextValue(...)88.88%1818100%
NumberValue(...)100%1010100%
DateValue(...)50%66100%
ParseBool(...)28.57%211466.66%
NullIfEmpty(...)100%22100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/Grid/AgGridRequestMapper.cs

#LineLine coverage
 1using System.Globalization;
 2using System.Text.Json;
 3using OServices.FiltersServices.FilterModels;
 4using Services.FiltersServices.SortModels;
 5
 6namespace OzonOrdersWeb.WebServices.Orders.Grid;
 7
 8/// <summary>
 9/// Чистое преобразование запроса AG Grid в <see cref="OrderFilterModel"/> + <see cref="OrderSortState"/> + номер страни
 10/// Ключи colId совпадают с camelCase-именами полей <see cref="OrderGridRowDto"/>.
 11/// Ограничения унаследованы от общего конвейера фильтрации: справочные фильтры односложные,
 12/// текстовые — подстрока (кроме явно указанных exact), диапазоны только у прибыли/наценки.
 13/// </summary>
 14public static class AgGridRequestMapper
 15{
 16    /// <param name="MultiValueFilters">Set-фильтры с 2+ значениями (colId → значения) — общий конвейер умеет
 17    /// только одно значение, поэтому они применяются отдельно через <see cref="OrdersGridMultiValueFilters"/>.</param>
 18    /// <param name="ExtraFilters">Операторы, которых нет в OrderFilterModel (начало строки, &gt;/&lt;, диапазоны дат) —
 19    /// применяются <see cref="OrdersGridExtraFilters"/>.</param>
 3020    public sealed record MappedRequest(
 6121        OrderFilterModel Filter,
 022        OrderSortState Sort,
 323        int Page,
 324        IReadOnlyDictionary<string, IReadOnlyList<string>> MultiValueFilters,
 725        IReadOnlyList<OrdersGridExtraFilters.ExtraFilter> ExtraFilters,
 526        IReadOnlyList<OrdersGridSorting.SortItem> Sorts,
 3027        IReadOnlyList<OrdersGridComputedFilters.ComputedFilter> ComputedFilters);
 28
 29    /// <summary>colId пользовательского вычисляемого столбца (см. orders-grid-computed.js).</summary>
 3930    public static bool IsComputedColumn(string colId) => colId.StartsWith("c_", StringComparison.OrdinalIgnoreCase);
 31
 132    private static readonly Dictionary<string, (OrderSortState Asc, OrderSortState Desc)> SortMap =
 133        new(StringComparer.OrdinalIgnoreCase)
 134        {
 135            ["shipmentNumber"] = (OrderSortState.ShipmentNumberAsc, OrderSortState.ShipmentNumberDesc),
 136            ["scanit"] = (OrderSortState.ScanitAsc, OrderSortState.ScanitDesc),
 137            ["deliveryProvider"] = (OrderSortState.DeliveryAsc, OrderSortState.DeliveryDesc),
 138            ["ozonClient"] = (OrderSortState.OzonClientAsc, OrderSortState.OzonClientDesc),
 139            ["deliveryCity"] = (OrderSortState.DeliveryCityAsc, OrderSortState.DeliveryCityDesc),
 140            ["processingDate"] = (OrderSortState.ProcessingDateAsc, OrderSortState.ProcessingDateDesc),
 141            ["shippingDate"] = (OrderSortState.ShippingDateAsc, OrderSortState.ShippingDateDesc),
 142            ["deliveryPeriod"] = (OrderSortState.DeliveryPeriodAsc, OrderSortState.DeliveryPeriodDesc),
 143            ["timeLeftDays"] = (OrderSortState.TimeLeftAsc, OrderSortState.TimeLeftDesc),
 144            ["article"] = (OrderSortState.ArticleAsc, OrderSortState.ArticleDesc),
 145            ["manufacturer"] = (OrderSortState.ManufacturerAsc, OrderSortState.ManufacturerDesc),
 146            ["productName"] = (OrderSortState.ProductNameAsc, OrderSortState.ProductNameDesc),
 147            ["clientStatus"] = (OrderSortState.StatusAsc, OrderSortState.StatusDesc),
 148            ["appStatus"] = (OrderSortState.AppStatusIdAsc, OrderSortState.AppStatusIdDesc),
 149            ["shipmentWarehouse"] = (OrderSortState.ShipmentWarehouseIdAsc, OrderSortState.ShipmentWarehouseIdDesc),
 150            ["lastStatusChangeDate"] = (OrderSortState.LastStatusChangeDateAsc, OrderSortState.LastStatusChangeDateDesc)
 151            ["supplier"] = (OrderSortState.SupplierAsc, OrderSortState.SupplierDesc),
 152            ["orderNumberToSupplier"] = (OrderSortState.OrderNumberToSupplierAsc, OrderSortState.OrderNumberToSupplierDe
 153            ["category"] = (OrderSortState.CategoryAsc, OrderSortState.CategoryDesc),
 154            ["volumetricWeight"] = (OrderSortState.VolumeAsc, OrderSortState.VolumeDesc),
 155            ["sitePrice"] = (OrderSortState.CurrentPriceAsc, OrderSortState.CurrentPriceDesc),
 156            ["quantity"] = (OrderSortState.QuantityAsc, OrderSortState.QuantityDesc),
 157            ["price"] = (OrderSortState.PriceAsc, OrderSortState.PriceDesc),
 158            ["shipmentAmount"] = (OrderSortState.ShipmentAmountAsc, OrderSortState.ShipmentAmountDesc),
 159            ["purchasePrice"] = (OrderSortState.PurchasePriceAsc, OrderSortState.PurchasePriceDesc),
 160            ["costPrice"] = (OrderSortState.CostPriceAsc, OrderSortState.CostPriceDesc),
 161            ["ozonCommission"] = (OrderSortState.MinOzonCommissionAsc, OrderSortState.MinOzonCommissionDesc),
 162            ["ozonCommissionMax"] = (OrderSortState.MaxOzonCommissionAsc, OrderSortState.MaxOzonCommissionDesc),
 163            ["profit"] = (OrderSortState.ProfitAsc, OrderSortState.ProfitDesc),
 164            ["discount"] = (OrderSortState.DiscountAsc, OrderSortState.DiscountDesc),
 165        };
 66
 67    /// <summary>colId, по которым грид может сортировать (для colDef.sortable).</summary>
 168    public static IReadOnlyCollection<string> SortableColumns => SortMap.Keys;
 69
 70    public static MappedRequest Map(AgGridDataRequest request, int blockSize)
 71    {
 3072        ArgumentNullException.ThrowIfNull(request);
 3073        if (blockSize < 1) throw new ArgumentOutOfRangeException(nameof(blockSize));
 74
 3075        var page = Math.Max(0, request.StartRow) / blockSize + 1;
 3076        var sort = MapSort(request.SortModel);
 3077        var sorts = (request.SortModel ?? [])
 378            .Where(s => !string.IsNullOrWhiteSpace(s.ColId) && (OrdersGridSorting.Supports(s.ColId) || IsComputedColumn(
 279            .Select(s => new OrdersGridSorting.SortItem(s.ColId, string.Equals(s.Sort, "desc", StringComparison.OrdinalI
 3080            .ToList();
 81
 3082        var flags = request.Extra ?? new AgGridExtraFlags();
 3083        var filter = new OrderFilterModel
 3084        {
 3085            Search = NullIfEmpty(request.Search),
 3086            IncludeArchived = request.IncludeArchived,
 3087            GroupByShipment = request.GroupByShipment || OrdersGridGrouping.IsShipment(request.GroupBy),
 3088            SyncClientStatusesOnLoad = flags.SyncClientStatuses,
 3089            OnlyWithZasyl = flags.OnlyWithZasyl,
 3090            OnlyWithReturns = flags.OnlyWithReturns,
 3091            OnlyWithTrackingNumber = flags.OnlyWithTrackingNumber,
 3092            ReturnLogisticBarcode = NullIfEmpty(flags.ReturnLogisticBarcode)
 3093        };
 94
 3095        var multi = new Dictionary<string, IReadOnlyList<string>>(StringComparer.OrdinalIgnoreCase);
 3096        var extra = new List<OrdersGridExtraFilters.ExtraFilter>();
 3097        var computed = new List<OrdersGridComputedFilters.ComputedFilter>();
 12498        foreach (var (colId, item) in request.FilterModel)
 99        {
 32100            if (item is null) continue;
 101
 32102            if (IsComputedColumn(colId))
 103            {
 0104                computed.Add(new OrdersGridComputedFilters.ComputedFilter(
 0105                    colId, (item.Type ?? "equals").ToLowerInvariant(), TextValue(item), NumberValue(item.Filter), Number
 0106                    DateValue(item.DateFrom), DateValue(item.DateTo), SetValues(item)));
 0107                continue;
 108            }
 109
 32110            var values = SetValues(item);
 32111            if (values.Count > 1 && OrdersGridMultiValueFilters.Supports(colId))
 112            {
 2113                multi[colId] = values;
 2114                continue;
 115            }
 116
 30117            var type = (item.Type ?? "equals").ToLowerInvariant();
 30118            if (!IsNativelySupported(colId, type) && OrdersGridExtraFilters.Supports(colId, type))
 119            {
 5120                extra.Add(new OrdersGridExtraFilters.ExtraFilter(
 5121                    colId, type, TextValue(item), NumberValue(item.Filter), NumberValue(item.FilterTo),
 5122                    DateValue(item.DateFrom), DateValue(item.DateTo)));
 5123                continue;
 124            }
 125
 25126            ApplyFilter(filter, colId, item);
 127        }
 128
 30129        return new MappedRequest(filter, sort, page, multi, extra, sorts, computed);
 130    }
 131
 132    public static OrderSortState MapSort(IReadOnlyList<AgGridSortModelItem>? sortModel)
 133    {
 76134        var first = sortModel?.FirstOrDefault();
 107135        if (first is null || string.IsNullOrWhiteSpace(first.ColId)) return OrderSortState.StandardState;
 46136        if (!SortMap.TryGetValue(first.ColId, out var pair)) return OrderSortState.StandardState;
 44137        return string.Equals(first.Sort, "desc", StringComparison.OrdinalIgnoreCase) ? pair.Desc : pair.Asc;
 138    }
 139
 1140    private static readonly HashSet<string> NativeTextColumns = new(StringComparer.OrdinalIgnoreCase)
 1141    {
 1142        "shipmentNumber", "scanit", "trackingNumber", "deliveryCity", "productKey", "productName", "orderNumberToSupplie
 1143    };
 144
 1145    private static readonly HashSet<string> NativeNumberEqualsColumns = new(StringComparer.OrdinalIgnoreCase)
 1146    {
 1147        "sitePrice", "quantity", "price", "shipmentAmount", "purchasePrice", "costPrice", "ozonCommission",
 1148        "profit", "discount", "volumetricWeight", "timeLeftDays"
 1149    };
 150
 151    /// <summary>Пара колонка/оператор, которую умеет OrderFilterModel сама (без пост-фильтра).</summary>
 152    public static bool IsNativelySupported(string colId, string type)
 153    {
 30154        type = type.ToLowerInvariant();
 34155        if (NativeTextColumns.Contains(colId)) return type == "contains";
 29156        if (string.Equals(colId, "article", StringComparison.OrdinalIgnoreCase)) return type is "contains" or "equals";
 23157        if (colId.Equals("manufacturer", StringComparison.OrdinalIgnoreCase) || colId.Equals("category", StringCompariso
 23158        if (colId.Equals("profit", StringComparison.OrdinalIgnoreCase) || colId.Equals("discount", StringComparison.Ordi
 159        {
 5160            return type is "equals" or "greaterthan" or "lessthan" or "inrange";
 161        }
 22162        if (NativeNumberEqualsColumns.Contains(colId)) return type == "equals";
 16163        if (colId is "processingDate" or "shippingDate" or "lastStatusChangeDate") return type == "equals";
 12164        return true; // справочники, deliveryPeriod, hasOzonPromotion — как раньше
 165    }
 166
 167    /// <summary>Быстрый путь запроса игнорирует сортировку — при явной сортировке надо идти через SQL.</summary>
 168    public static bool ShouldUseDbPath(bool hasFilters, OrderSortState sort)
 3169        => hasFilters || sort != OrderSortState.StandardState;
 170
 171    private static void ApplyFilter(OrderFilterModel f, string colId, AgGridFilterModelItem item)
 172    {
 25173        var type = (item.Type ?? "equals").ToLowerInvariant();
 25174        var text = TextValue(item);
 25175        var num = NumberValue(item.Filter);
 25176        var numTo = NumberValue(item.FilterTo);
 25177        var date = DateValue(item.DateFrom);
 178
 25179        switch (colId.ToLowerInvariant())
 180        {
 2181            case "shipmentnumber": f.ShipmentNumber = text; break;
 0182            case "trackingnumber": f.TrackingNumber = text; break;
 2183            case "scanit": f.Scanit = text; break;
 2184            case "ozonclient": f.OzonClient = text; break;
 0185            case "deliverycity": f.DeliveryCity = text; break;
 0186            case "deliveryprovider": f.Provider = text; break;
 0187            case "deliveryname": f.DeliveryName = text; break;
 2188            case "processingdate": f.ProcessingDate = date; break;
 0189            case "shippingdate": f.ShippingDate = date; break;
 0190            case "laststatuschangedate": f.LastStatusChangeDate = date; break;
 2191            case "deliveryperiod": f.DeliveryPeriod = text; break;
 0192            case "timeleftdays": f.TimeLeftDay = (int?)num; break;
 0193            case "productkey": f.Key = text; break;
 0194            case "manufacturer": f.Manufacturer = text; break;
 195            case "article":
 2196                f.Article = text;
 2197                f.ArticleExactMatch = type == "equals";
 2198                break;
 0199            case "productname": f.ProductName = text; break;
 2200            case "clientstatus": f.Status = text; break;
 4201            case "appstatus": f.AppStatus = text; break;
 202            case "supplierstatus":
 2203                f.SupplierStatusId = item.Id ?? (int.TryParse(text, out var sid) ? sid : (int?)num);
 2204                break;
 4205            case "shipmentwarehouse": f.ShipmentWarehouse = text; break;
 2206            case "supplier": f.Supplier = text; break;
 0207            case "ordernumbertosupplier": f.OrderNumberToSupplier = text; break;
 0208            case "category": f.CommercialCategory = text; break;
 0209            case "volumetricweight": f.VolumetricWeight = (double?)num; break;
 0210            case "siteprice": f.CurrentPriceWithDiscount = num; break;
 2211            case "quantity": f.Quantity = (int?)num; break;
 4212            case "price": f.Price = num; break;
 0213            case "shipmentamount": f.ShipmentAmount = num; break;
 0214            case "purchaseprice": f.PurchasePrice = num; break;
 0215            case "costprice": f.CostPrice = num; break;
 0216            case "ozoncommission": f.OzonCommission = num; break;
 217            case "profit":
 4218                ApplyRange(type, num, numTo,
 9219                    v => f.Profit = v, v => f.ProfitMin = v, v => f.ProfitMax = v);
 4220                break;
 221            case "discount":
 1222                ApplyRange(type, num, numTo,
 3223                    v => f.Discount = v, v => f.DiscountMin = v, v => f.DiscountMax = v);
 1224                break;
 225            case "hasozonpromotion":
 1226                f.HasOzonPromotion = ParseBool(text);
 227                break;
 228            default:
 229                // Неизвестный colId — молча пропускаем: клиент мог прислать колонку без серверного фильтра.
 230                break;
 231        }
 1232    }
 233
 234    private static void ApplyRange(
 235        string type, decimal? from, decimal? to,
 236        Action<decimal?> setEquals, Action<decimal?> setMin, Action<decimal?> setMax)
 237    {
 238        switch (type)
 239        {
 240            case "greaterthan":
 241            case "greaterthanorequal":
 1242                setMin(from);
 1243                break;
 244            case "lessthan":
 245            case "lessthanorequal":
 1246                setMax(from);
 1247                break;
 248            case "inrange":
 2249                setMin(from);
 2250                setMax(to);
 2251                break;
 252            default:
 1253                setEquals(from);
 254                break;
 255        }
 1256    }
 257
 258    /// <summary>Значения set-фильтра без пустых и дублей; «Все» означает отсутствие фильтра.</summary>
 259    private static List<string> SetValues(AgGridFilterModelItem item)
 260    {
 32261        var list = new List<string>();
 32262        if (item.Values != null)
 263        {
 18264            list.AddRange(item.Values.Where(v => !string.IsNullOrWhiteSpace(v)).Select(v => v.Trim()));
 265        }
 28266        else if (!string.IsNullOrWhiteSpace(item.Value))
 267        {
 8268            list.Add(item.Value.Trim());
 269        }
 270
 32271        return list.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
 272    }
 273
 274    private static string? TextValue(AgGridFilterModelItem item)
 275    {
 32276        if (item.Values is { Count: 1 }) return NullIfEmpty(item.Values[0]);
 28277        if (item.Values is { Count: > 1 }) return null;
 36278        if (!string.IsNullOrWhiteSpace(item.Value)) return item.Value.Trim();
 29279        if (item.Filter is { ValueKind: JsonValueKind.String } s) return NullIfEmpty(s.GetString());
 20280        if (item.Filter is { ValueKind: JsonValueKind.Number } n) return n.GetRawText();
 2281        return null;
 282    }
 283
 284    private static decimal? NumberValue(JsonElement? element)
 285    {
 100286        if (element is null) return null;
 20287        var e = element.Value;
 20288        return e.ValueKind switch
 20289        {
 22290            JsonValueKind.Number when e.TryGetDecimal(out var d) => d,
 10291            JsonValueKind.String when decimal.TryParse(e.GetString(), NumberStyles.Any, CultureInfo.InvariantCulture, ou
 8292            _ => null
 20293        };
 294    }
 295
 296    private static DateTime? DateValue(string? raw)
 297    {
 67298        if (string.IsNullOrWhiteSpace(raw)) return null;
 3299        string[] formats = ["yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd", "yyyy-MM-ddTHH:mm:ss"];
 3300        return DateTime.TryParseExact(raw.Trim(), formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out var d)
 3301            ? d
 3302            : DateTime.TryParse(raw, CultureInfo.InvariantCulture, DateTimeStyles.None, out var d2) ? d2 : null;
 303    }
 304
 1305    private static bool? ParseBool(string? text) => text?.ToLowerInvariant() switch
 1306    {
 1307        "true" or "1" or "да" => true,
 0308        "false" or "0" or "нет" => false,
 0309        _ => null
 1310    };
 311
 71312    private static string? NullIfEmpty(string? s) => string.IsNullOrWhiteSpace(s) ? null : s.Trim();
 313}

Methods/Properties

.ctor(OServices.FiltersServices.FilterModels.OrderFilterModel,Services.FiltersServices.SortModels.OrderSortState,System.Int32,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Collections.Generic.IReadOnlyList`1<System.String>>,System.Collections.Generic.IReadOnlyList`1<OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridExtraFilters/ExtraFilter>,System.Collections.Generic.IReadOnlyList`1<OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridSorting/SortItem>,System.Collections.Generic.IReadOnlyList`1<OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridComputedFilters/ComputedFilter>)
get_Filter()
get_Sort()
get_Page()
get_MultiValueFilters()
get_ExtraFilters()
get_Sorts()
get_ComputedFilters()
IsComputedColumn(System.String)
.cctor()
get_SortableColumns()
Map(OzonOrdersWeb.WebServices.Orders.Grid.AgGridDataRequest,System.Int32)
MapSort(System.Collections.Generic.IReadOnlyList`1<OzonOrdersWeb.WebServices.Orders.Grid.AgGridSortModelItem>)
IsNativelySupported(System.String,System.String)
ShouldUseDbPath(System.Boolean,Services.FiltersServices.SortModels.OrderSortState)
ApplyFilter(OServices.FiltersServices.FilterModels.OrderFilterModel,System.String,OzonOrdersWeb.WebServices.Orders.Grid.AgGridFilterModelItem)
ApplyRange(System.String,System.Nullable`1<System.Decimal>,System.Nullable`1<System.Decimal>,System.Action`1<System.Nullable`1<System.Decimal>>,System.Action`1<System.Nullable`1<System.Decimal>>,System.Action`1<System.Nullable`1<System.Decimal>>)
SetValues(OzonOrdersWeb.WebServices.Orders.Grid.AgGridFilterModelItem)
TextValue(OzonOrdersWeb.WebServices.Orders.Grid.AgGridFilterModelItem)
NumberValue(System.Nullable`1<System.Text.Json.JsonElement>)
DateValue(System.String)
ParseBool(System.String)
NullIfEmpty(System.String)