| | | 1 | | using System.Net; |
| | | 2 | | using System.Text.Json; |
| | | 3 | | using OzonOrdersWeb.WebServices.OrderHistory; |
| | | 4 | | using Services.SupplierStatusServices; |
| | | 5 | | |
| | | 6 | | namespace OzonOrdersWeb.WebServices.Transactions; |
| | | 7 | | |
| | | 8 | | /// <summary>Строка сводки проверки заказа у поставщика для клиента (диалог на /Studio2/OrdersGrid).</summary> |
| | | 9 | | public sealed record SupplierVerificationRowDto( |
| | | 10 | | int OrderId, |
| | | 11 | | string? ShipmentNumber, |
| | | 12 | | string? Article, |
| | | 13 | | string? ProductName, |
| | | 14 | | string ProviderKey, |
| | | 15 | | string ProviderName, |
| | | 16 | | string? LogoUrl, |
| | | 17 | | string StageName, |
| | | 18 | | string Status, |
| | | 19 | | string StatusTitle, |
| | | 20 | | string Tone, |
| | | 21 | | string Summary, |
| | | 22 | | string? SupplierOrderNumber, |
| | | 23 | | string? SupplierStatusName, |
| | | 24 | | IReadOnlyList<string> Checks); |
| | | 25 | | |
| | | 26 | | /// <summary>Сводка проверки поставщиков после проведения: считается на сервере, клиент только рисует.</summary> |
| | | 27 | | public sealed record SupplierVerificationDto( |
| | | 28 | | bool HasProblems, |
| | | 29 | | int Confirmed, |
| | | 30 | | int Problems, |
| | | 31 | | int NotVerifiable, |
| | | 32 | | string ShortText, |
| | | 33 | | IReadOnlyList<SupplierVerificationRowDto> Rows) |
| | | 34 | | { |
| | | 35 | | public static SupplierVerificationDto? From(SupplierVerificationSummary? summary) |
| | | 36 | | { |
| | | 37 | | if (summary is null || summary.IsEmpty) return null; |
| | | 38 | | var rows = summary.Rows.Select(row => new SupplierVerificationRowDto( |
| | | 39 | | row.OrderId, row.ShipmentNumber, row.Article, row.ProductName, row.ProviderKey, row.ProviderName, |
| | | 40 | | OrderHistoryGridRows.SupplierProviderLogoUrl(row.ProviderKey), |
| | | 41 | | OrderHistoryGridRows.SupplierStageName(row.Stage), row.Status, |
| | | 42 | | SupplierVerificationStatus.Title(row.Status), Tone(row.Status), row.Summary, |
| | | 43 | | row.SupplierOrderNumber, row.SupplierStatusName, row.Checks)).ToList(); |
| | | 44 | | return new SupplierVerificationDto(summary.HasProblems, summary.ConfirmedCount, summary.ProblemCount, |
| | | 45 | | summary.NotVerifiableCount, summary.ToShortText(), rows); |
| | | 46 | | } |
| | | 47 | | |
| | | 48 | | /// <summary>HTML-блок для карточки результата проведения (toast классического потока и SummaryHtml нового).</summar |
| | | 49 | | public static string? ToHtmlBlock(SupplierVerificationSummary? summary) |
| | | 50 | | { |
| | | 51 | | if (summary is null || summary.IsEmpty) return null; |
| | | 52 | | var cls = summary.HasProblems ? "alert-danger" : "alert-success"; |
| | | 53 | | var icon = summary.HasProblems ? "fa-triangle-exclamation" : "fa-circle-check"; |
| | | 54 | | var problemRows = summary.Rows.Where(x => x.IsProblem).Take(10) |
| | | 55 | | .Select(x => $"<li><b>{WebUtility.HtmlEncode(x.ProviderName)}</b> · {WebUtility.HtmlEncode(x.ShipmentNumber |
| | | 56 | | var list = summary.HasProblems ? $"<ul class='mb-0 small'>{string.Join(string.Empty, problemRows)}</ul>" : strin |
| | | 57 | | return $"<div class='alert {cls} mb-0'><div class='fw-semibold'><i class='fas {icon} me-1'></i>{WebUtility.HtmlE |
| | | 58 | | } |
| | | 59 | | |
| | | 60 | | public static string Tone(string status) => status switch |
| | | 61 | | { |
| | | 62 | | SupplierVerificationStatus.Confirmed => "success", |
| | | 63 | | SupplierVerificationStatus.NotVerifiable => "muted", |
| | | 64 | | SupplierVerificationStatus.ApiError => "warning", |
| | | 65 | | _ => "danger" |
| | | 66 | | }; |
| | | 67 | | } |
| | | 68 | | |
| | | 69 | | /// <summary>Достаёт Id заказов Studio2 из клиентских очередей поставщиков (JSON-массивы с полем OrderId).</summary> |
| | | 70 | | public static class SupplierQueueOrderIds |
| | | 71 | | { |
| | 1 | 72 | | private static readonly string[] QueueSuffixes = { "BasketQueueJson", "CheckoutQueueJson" }; |
| | | 73 | | |
| | | 74 | | /// <summary>Заказ → поставщик по ключам очередей (<c>NikeiCheckoutQueueJson</c> → <c>Nikei</c>), для заказов без за |
| | | 75 | | public static Dictionary<int, string> ExtractProviderByOrder(IReadOnlyDictionary<string, string> queues) |
| | | 76 | | { |
| | 1 | 77 | | var result = new Dictionary<int, string>(); |
| | 12 | 78 | | foreach (var (key, json) in queues) |
| | | 79 | | { |
| | 13 | 80 | | var suffix = QueueSuffixes.FirstOrDefault(x => key.EndsWith(x, StringComparison.OrdinalIgnoreCase)); |
| | 5 | 81 | | if (suffix is null) continue; |
| | 4 | 82 | | var providerKey = ProviderKeyFromQueuePrefix(key[..^suffix.Length]); |
| | 17 | 83 | | foreach (var id in Extract(new[] { json })) result.TryAdd(id, providerKey); |
| | | 84 | | } |
| | | 85 | | |
| | 1 | 86 | | return result; |
| | | 87 | | } |
| | | 88 | | |
| | 40 | 89 | | public static string ProviderKeyFromQueuePrefix(string prefix) => prefix.ToLowerInvariant() switch |
| | 40 | 90 | | { |
| | 2 | 91 | | "mikado" => "MikadoMskHod20", |
| | 0 | 92 | | "moskvorechie" => "MoskvorechieIstra", |
| | 38 | 93 | | _ => prefix.Length == 0 ? prefix : char.ToUpperInvariant(prefix[0]) + prefix[1..] |
| | 40 | 94 | | }; |
| | | 95 | | |
| | | 96 | | public static HashSet<int> Extract(IEnumerable<string?> queueJsons) |
| | | 97 | | { |
| | 4 | 98 | | var ids = new HashSet<int>(); |
| | 16 | 99 | | foreach (var json in queueJsons) |
| | | 100 | | { |
| | 4 | 101 | | if (string.IsNullOrWhiteSpace(json)) continue; |
| | | 102 | | try |
| | | 103 | | { |
| | 4 | 104 | | using var document = JsonDocument.Parse(json); |
| | 4 | 105 | | if (document.RootElement.ValueKind != JsonValueKind.Array) continue; |
| | 14 | 106 | | foreach (var item in document.RootElement.EnumerateArray()) |
| | | 107 | | { |
| | 3 | 108 | | if (item.ValueKind != JsonValueKind.Object) continue; |
| | 12 | 109 | | foreach (var property in item.EnumerateObject()) |
| | | 110 | | { |
| | 3 | 111 | | if (string.Equals(property.Name, "OrderId", StringComparison.OrdinalIgnoreCase) |
| | 3 | 112 | | && property.Value.ValueKind == JsonValueKind.Number |
| | 3 | 113 | | && property.Value.TryGetInt32(out var id) && id > 0) |
| | | 114 | | { |
| | 3 | 115 | | ids.Add(id); |
| | | 116 | | } |
| | | 117 | | } |
| | | 118 | | } |
| | 4 | 119 | | } |
| | 0 | 120 | | catch (JsonException) |
| | | 121 | | { |
| | | 122 | | // Битая очередь уже отклонена при разборе в процессоре корзин; для проверки она просто пуста. |
| | 0 | 123 | | } |
| | | 124 | | } |
| | | 125 | | |
| | 4 | 126 | | return ids; |
| | | 127 | | } |
| | | 128 | | } |