| | | 1 | | using System.Globalization; |
| | | 2 | | using System.Text.Json; |
| | | 3 | | using OzonOrdersWeb.WebServices.Orders.Grid; |
| | | 4 | | using Services.BitrixStockServices; |
| | | 5 | | |
| | | 6 | | namespace OzonOrdersWeb.WebServices.BitrixStock; |
| | | 7 | | |
| | | 8 | | /// <summary>Запрос блока строк отчёта синхронизации от AG Grid (Infinite Row Model).</summary> |
| | | 9 | | public sealed class BitrixSyncGridRequest |
| | | 10 | | { |
| | 13 | 11 | | public int StartRow { get; set; } |
| | | 12 | | |
| | 14 | 13 | | public int EndRow { get; set; } = 100; |
| | | 14 | | |
| | 8 | 15 | | public List<AgGridSortModelItem> SortModel { get; set; } = []; |
| | | 16 | | |
| | 10 | 17 | | public Dictionary<string, AgGridFilterModelItem> FilterModel { get; set; } = new(StringComparer.Ordinal); |
| | | 18 | | |
| | 3 | 19 | | public string? Search { get; set; } |
| | | 20 | | |
| | 2 | 21 | | public bool ShowZero { get; set; } |
| | | 22 | | |
| | 1 | 23 | | public bool OnlyPriceChanged { get; set; } |
| | | 24 | | |
| | | 25 | | /// <summary>Отключённые связи складов «clientId|склад 1С|склад Ozon» (галки в «Управление связями»).</summary> |
| | 2 | 26 | | public List<string>? ExcludedMappings { get; set; } |
| | | 27 | | |
| | | 28 | | /// <summary>Для счётчиков значений: колонка, по которой считать.</summary> |
| | 0 | 29 | | public string? ColId { get; set; } |
| | | 30 | | } |
| | | 31 | | |
| | | 32 | | /// <summary> |
| | | 33 | | /// AG Grid → <see cref="BitrixSyncGridLinesQuery"/>: колонки/операторы вне whitelist |
| | | 34 | | /// <see cref="BitrixStockSyncReportGridSql"/> отбрасываются, лимиты проверяются до обращения к БД. |
| | | 35 | | /// </summary> |
| | | 36 | | public static class BitrixSyncGridRequestMapper |
| | | 37 | | { |
| | | 38 | | public static string? Validate(BitrixSyncGridRequest? request) |
| | | 39 | | { |
| | | 40 | | if (request is null) return "Пустой запрос."; |
| | | 41 | | if (request.StartRow < 0 || request.EndRow <= request.StartRow) return "Некорректный диапазон строк."; |
| | | 42 | | if (request.EndRow - request.StartRow > BitrixStockSyncReportGridSql.MaxBlockSize) |
| | | 43 | | return $"Слишком большой блок (максимум {BitrixStockSyncReportGridSql.MaxBlockSize} строк)."; |
| | | 44 | | if (request.ExcludedMappings is { Count: > BitrixStockSyncReportGridSql.MaxExcludedMappings }) |
| | | 45 | | return "Слишком много отключённых связей складов."; |
| | | 46 | | if (request.Search is { Length: > BitrixStockSyncReportGridSql.MaxTextLength }) return "Слишком длинная строка п |
| | | 47 | | if (request.FilterModel is { Count: > BitrixStockSyncReportGridSql.MaxConditions }) return "Слишком много фильтр |
| | | 48 | | if (request.SortModel is { Count: > BitrixStockSyncReportGridSql.MaxSorts }) return "Слишком много столбцов сорт |
| | | 49 | | foreach (var (colId, item) in request.FilterModel ?? new Dictionary<string, AgGridFilterModelItem>()) |
| | | 50 | | { |
| | | 51 | | if (colId.Length > 64) return "Некорректный идентификатор колонки фильтра."; |
| | | 52 | | if (item?.Values is { Count: > BitrixStockSyncReportGridSql.MaxSetValues }) return $"Слишком много значений |
| | | 53 | | if (item?.Filter is { ValueKind: JsonValueKind.String } f && f.GetString()!.Length > BitrixStockSyncReportGr |
| | | 54 | | return $"Слишком длинное значение фильтра «{colId}»."; |
| | | 55 | | } |
| | | 56 | | |
| | | 57 | | return null; |
| | | 58 | | } |
| | | 59 | | |
| | | 60 | | public static BitrixSyncGridLinesQuery Map(BitrixSyncGridRequest request) |
| | | 61 | | { |
| | | 62 | | var query = new BitrixSyncGridLinesQuery |
| | | 63 | | { |
| | | 64 | | StartRow = request.StartRow, |
| | | 65 | | EndRow = request.EndRow, |
| | | 66 | | Search = request.Search, |
| | | 67 | | ShowZero = request.ShowZero, |
| | | 68 | | OnlyPriceChanged = request.OnlyPriceChanged, |
| | | 69 | | ExcludedMappings = request.ExcludedMappings ?? new List<string>() |
| | | 70 | | }; |
| | | 71 | | |
| | | 72 | | foreach (var sort in request.SortModel ?? new List<AgGridSortModelItem>()) |
| | | 73 | | { |
| | | 74 | | if (!BitrixStockSyncReportGridSql.IsSortable(sort.ColId)) continue; |
| | | 75 | | query.Sorts.Add(new BitrixSyncGridSort |
| | | 76 | | { |
| | | 77 | | ColId = sort.ColId, |
| | | 78 | | Descending = string.Equals(sort.Sort, "desc", StringComparison.OrdinalIgnoreCase) |
| | | 79 | | }); |
| | | 80 | | } |
| | | 81 | | |
| | | 82 | | foreach (var (colId, item) in request.FilterModel ?? new Dictionary<string, AgGridFilterModelItem>()) |
| | | 83 | | { |
| | | 84 | | var condition = MapCondition(colId, item); |
| | | 85 | | if (condition != null) query.Conditions.Add(condition); |
| | | 86 | | } |
| | | 87 | | |
| | | 88 | | return query; |
| | | 89 | | } |
| | | 90 | | |
| | | 91 | | private static BitrixSyncGridCondition? MapCondition(string colId, AgGridFilterModelItem? item) |
| | | 92 | | { |
| | | 93 | | if (item is null) return null; |
| | | 94 | | var kind = item.FilterType switch |
| | | 95 | | { |
| | | 96 | | "number" => BitrixSyncGridConditionKind.Number, |
| | | 97 | | "set" => BitrixSyncGridConditionKind.Set, |
| | | 98 | | _ => BitrixSyncGridConditionKind.Text |
| | | 99 | | }; |
| | | 100 | | if (!BitrixStockSyncReportGridSql.IsFilterable(colId, kind)) return null; |
| | | 101 | | |
| | | 102 | | return new BitrixSyncGridCondition |
| | | 103 | | { |
| | | 104 | | ColId = colId, |
| | | 105 | | Kind = kind, |
| | | 106 | | Operator = string.IsNullOrWhiteSpace(item.Type) ? (kind == BitrixSyncGridConditionKind.Number ? "equals" : " |
| | | 107 | | Text = ReadString(item.Filter), |
| | | 108 | | Number = ReadDecimal(item.Filter), |
| | | 109 | | NumberTo = ReadDecimal(item.FilterTo), |
| | | 110 | | Values = item.Values ?? new List<string>() |
| | | 111 | | }; |
| | | 112 | | } |
| | | 113 | | |
| | | 114 | | private static string? ReadString(JsonElement? element) |
| | | 115 | | { |
| | | 116 | | if (element is not { } e) return null; |
| | | 117 | | return e.ValueKind switch |
| | | 118 | | { |
| | | 119 | | JsonValueKind.String => e.GetString(), |
| | | 120 | | JsonValueKind.Number => e.GetRawText(), |
| | | 121 | | _ => null |
| | | 122 | | }; |
| | | 123 | | } |
| | | 124 | | |
| | | 125 | | private static decimal? ReadDecimal(JsonElement? element) |
| | | 126 | | { |
| | | 127 | | if (element is not { } e) return null; |
| | | 128 | | if (e.ValueKind == JsonValueKind.Number && e.TryGetDecimal(out var number)) return number; |
| | | 129 | | if (e.ValueKind == JsonValueKind.String |
| | | 130 | | && decimal.TryParse(e.GetString()?.Replace(',', '.'), NumberStyles.Number, CultureInfo.InvariantCulture, out |
| | | 131 | | return parsed; |
| | | 132 | | return null; |
| | | 133 | | } |
| | | 134 | | } |