| | | 1 | | using OzonDomains.Models; |
| | | 2 | | |
| | | 3 | | namespace OzonOrdersWeb.WebServices.TransactionJobs; |
| | | 4 | | |
| | | 5 | | /// <summary>Подпись и тон бейджа (accent | success | warning | danger | info | purple | muted).</summary> |
| | | 6 | | public sealed record ExecutionDetailsLabel(string Code, string Text, string Tone); |
| | | 7 | | |
| | | 8 | | public sealed record ExecutionStepRow( |
| | | 9 | | long Id, int Number, string Name, string NameCode, string Status, string StatusCode, string StatusTone, |
| | | 10 | | DateTime StartedAt, DateTime? CompletedAt, long? DurationMs, string? Input, string? Output, string? Error); |
| | | 11 | | |
| | | 12 | | public sealed record ExecutionReportLoadRow( |
| | | 13 | | long Id, string Client, string? ReportCode, string? FileName, string? FileUrl, DateTime PeriodFrom, DateTime PeriodT |
| | | 14 | | string Status, string StatusCode, string StatusTone, int? TotalRows, int? RowsWithData, int? UniqueOrders, string? E |
| | | 15 | | |
| | | 16 | | public sealed record ExecutionResultRow( |
| | | 17 | | long Id, int OrderId, string? ShipmentNumber, string? Warehouse, int? TransactionId, string? OneCDocumentId, |
| | | 18 | | string? OneCDocumentNumber, bool IsPosted, bool IsDraft, string Status, string StatusCode, string StatusTone, |
| | | 19 | | string? Comment, string? Error); |
| | | 20 | | |
| | | 21 | | public sealed record ExecutionDetailsSummary( |
| | | 22 | | string Schedule, string JobType, string Trigger, string Status, string StatusTone, DateTime StartedAt, |
| | | 23 | | DateTime? CompletedAt, long? DurationMs, int TotalOrders, int MatchedOrders, int ProcessedOrders, |
| | | 24 | | int SuccessfulOrders, int FailedOrders, string? Error); |
| | | 25 | | |
| | | 26 | | public sealed record ExecutionDetailsGridData( |
| | | 27 | | ExecutionDetailsSummary Summary, |
| | | 28 | | IReadOnlyList<ExecutionStepRow> Steps, |
| | | 29 | | IReadOnlyList<ExecutionReportLoadRow> Loads, |
| | | 30 | | IReadOnlyList<ExecutionResultRow> Results, |
| | | 31 | | int DraftCount); |
| | | 32 | | |
| | | 33 | | /// <summary> |
| | | 34 | | /// Данные страницы «Детали выполнения» для гридов AG Grid: переводы статусов/этапов и цвета бейджей раньше жили в |
| | | 35 | | /// ExecutionDetails.cshtml — теперь они в строках, по ним работают фильтры и сортировка гридов. |
| | | 36 | | /// </summary> |
| | | 37 | | public static class ExecutionDetailsGridRows |
| | | 38 | | { |
| | | 39 | | public static ExecutionDetailsGridData Build( |
| | | 40 | | BackgroundJobExecution execution, BackgroundJobSchedule? schedule, |
| | | 41 | | IEnumerable<BackgroundJobStep> steps, IEnumerable<BackgroundReportLoad> loads, |
| | | 42 | | IEnumerable<BackgroundTransactionResult> results) |
| | | 43 | | { |
| | 3 | 44 | | var stepRows = steps.OrderBy(s => s.StepNumber).Select(Step).ToList(); |
| | 1 | 45 | | var loadRows = loads.Select(Load).ToList(); |
| | 1 | 46 | | var resultRows = results.Select(Result).ToList(); |
| | | 47 | | |
| | 1 | 48 | | var summary = new ExecutionDetailsSummary( |
| | 1 | 49 | | schedule?.Name ?? "-", |
| | 1 | 50 | | JobType(execution.JobType), |
| | 1 | 51 | | TriggerType(execution.TriggerType), |
| | 1 | 52 | | ExecutionStatus(execution.Status).Text, |
| | 1 | 53 | | ExecutionStatus(execution.Status).Tone, |
| | 1 | 54 | | Utc(execution.StartedAt), |
| | 1 | 55 | | execution.CompletedAt.HasValue ? Utc(execution.CompletedAt.Value) : null, |
| | 1 | 56 | | execution.DurationMs, |
| | 1 | 57 | | execution.TotalOrders, |
| | 1 | 58 | | execution.MatchedOrders, |
| | 1 | 59 | | execution.ProcessedOrders, |
| | 1 | 60 | | execution.SuccessfulOrders, |
| | 1 | 61 | | execution.FailedOrders, |
| | 1 | 62 | | string.IsNullOrWhiteSpace(execution.ErrorMessage) ? null : execution.ErrorMessage); |
| | | 63 | | |
| | 3 | 64 | | return new ExecutionDetailsGridData(summary, stepRows, loadRows, resultRows, resultRows.Count(r => r.IsDraft)); |
| | | 65 | | } |
| | | 66 | | |
| | | 67 | | public static ExecutionStepRow Step(BackgroundJobStep s) |
| | | 68 | | { |
| | 3 | 69 | | var status = ExecutionStatus(s.Status); |
| | 3 | 70 | | return new ExecutionStepRow( |
| | 3 | 71 | | s.Id, s.StepNumber, StepName(s.StepName), s.StepName, status.Text, status.Code, status.Tone, |
| | 3 | 72 | | Utc(s.StartedAt), s.CompletedAt.HasValue ? Utc(s.CompletedAt.Value) : null, s.DurationMs, |
| | 3 | 73 | | Blank(s.InputData), Blank(s.OutputData), Blank(s.ErrorMessage)); |
| | | 74 | | } |
| | | 75 | | |
| | | 76 | | public static ExecutionReportLoadRow Load(BackgroundReportLoad r) |
| | | 77 | | { |
| | 0 | 78 | | var status = LoadStatus(r.Status); |
| | 0 | 79 | | return new ExecutionReportLoadRow( |
| | 0 | 80 | | r.Id, r.OzonClient?.Name ?? "-", r.ReportCode, r.FileName, Blank(r.FileUrl), |
| | 0 | 81 | | Utc(r.ReportPeriodFrom), Utc(r.ReportPeriodTo), status.Text, status.Code, status.Tone, |
| | 0 | 82 | | r.TotalRows, r.RowsWithRequiredData, r.UniqueOrderNumbers, Blank(r.ErrorMessage)); |
| | | 83 | | } |
| | | 84 | | |
| | | 85 | | public static ExecutionResultRow Result(BackgroundTransactionResult r) |
| | | 86 | | { |
| | 5 | 87 | | var status = ResultStatus(r.Status); |
| | 5 | 88 | | return new ExecutionResultRow( |
| | 5 | 89 | | r.Id, r.OrderId, r.Order?.ShipmentNumber, r.WarehouseName, r.TransactionId, r.OneCDocumentId, |
| | 5 | 90 | | Blank(r.OneCDocumentNumber), r.IsPosted, IsDraft(r), status.Text, status.Code, status.Tone, |
| | 5 | 91 | | Blank(r.Comment), Blank(r.ErrorMessage)); |
| | | 92 | | } |
| | | 93 | | |
| | | 94 | | /// <summary>Документ записан в 1С, но не проведён.</summary> |
| | | 95 | | public static bool IsDraft(BackgroundTransactionResult r) |
| | 5 | 96 | | => !r.IsPosted && (!string.IsNullOrEmpty(r.OneCDocumentId) || !string.IsNullOrEmpty(r.OneCDocumentNumber)); |
| | | 97 | | |
| | 9 | 98 | | public static ExecutionDetailsLabel ExecutionStatus(string? status) => status switch |
| | 9 | 99 | | { |
| | 1 | 100 | | "Running" => new(status, "Выполняется", "warning"), |
| | 1 | 101 | | "Completed" => new(status, "Завершено", "success"), |
| | 2 | 102 | | "PartialSuccess" => new(status, "Частично успешно", "warning"), |
| | 1 | 103 | | "Success" => new(status, "Успешно", "success"), |
| | 1 | 104 | | "Failed" => new(status, "Ошибка", "danger"), |
| | 0 | 105 | | "Skipped" => new(status, "Пропущено", "warning"), |
| | 3 | 106 | | _ => new(status ?? "", status ?? "-", "muted") |
| | 9 | 107 | | }; |
| | | 108 | | |
| | 0 | 109 | | public static ExecutionDetailsLabel LoadStatus(string? status) => status switch |
| | 0 | 110 | | { |
| | 0 | 111 | | "Requested" => new(status, "Запрошен", "warning"), |
| | 0 | 112 | | "Ready" => new(status, "Готов", "warning"), |
| | 0 | 113 | | "Downloaded" => new(status, "Загружен", "success"), |
| | 0 | 114 | | "Failed" => new(status, "Ошибка", "danger"), |
| | 0 | 115 | | _ => new(status ?? "", status ?? "-", "muted") |
| | 0 | 116 | | }; |
| | | 117 | | |
| | 5 | 118 | | public static ExecutionDetailsLabel ResultStatus(string? status) => status switch |
| | 5 | 119 | | { |
| | 0 | 120 | | "Created" => new(status, "Создано", "success"), |
| | 1 | 121 | | "PostedTo1C" => new(status, "Проведено в 1С", "success"), |
| | 1 | 122 | | "DraftIn1C" => new(status, "Черновик в 1С", "warning"), |
| | 0 | 123 | | "SystemOnly" => new(status, "Только в системе", "accent"), |
| | 0 | 124 | | "Skipped" => new(status, "Пропущено", "warning"), |
| | 1 | 125 | | "Failed" => new(status, "Ошибка", "danger"), |
| | 2 | 126 | | _ => new(status ?? "", status ?? "-", "muted") |
| | 5 | 127 | | }; |
| | | 128 | | |
| | 1 | 129 | | public static string TriggerType(string? triggerType) => triggerType switch |
| | 1 | 130 | | { |
| | 0 | 131 | | "Scheduled" => "По расписанию", |
| | 1 | 132 | | "Manual" => "Вручную", |
| | 0 | 133 | | "Retry" => "Повтор", |
| | 0 | 134 | | _ => triggerType ?? "-" |
| | 1 | 135 | | }; |
| | | 136 | | |
| | 1 | 137 | | public static string JobType(string? jobType) => jobType switch |
| | 1 | 138 | | { |
| | 0 | 139 | | "ShippedToCommissioner" => "Отгрузка комиссионеру", |
| | 1 | 140 | | "CustomerReturn" => "Возврат покупателя", |
| | 0 | 141 | | "ShippedToClient" => "Отгрузка клиенту", |
| | 0 | 142 | | _ => jobType ?? "-" |
| | 1 | 143 | | }; |
| | | 144 | | |
| | 3 | 145 | | public static string StepName(string? stepName) => stepName switch |
| | 3 | 146 | | { |
| | 0 | 147 | | "LoadReport" => "Загрузка отчёта", |
| | 0 | 148 | | "ParseReport" => "Парсинг отчёта", |
| | 0 | 149 | | "FilterByDate" => "Фильтрация по дате", |
| | 1 | 150 | | "MatchOrders" => "Сопоставление заказов", |
| | 0 | 151 | | "ExcludeProcessedOrders" => "Исключение уже проведённых", |
| | 0 | 152 | | "FilterEligibleOrders" => "Фильтрация подходящих заказов", |
| | 0 | 153 | | "GroupByWarehouse" => "Группировка по складам", |
| | 0 | 154 | | "CreateTransactions" => "Создание транзакций", |
| | 0 | 155 | | "PostTo1C" => "Проведение в 1С", |
| | 0 | 156 | | "Finalize" => "Финализация", |
| | 2 | 157 | | _ => stepName ?? "-" |
| | 3 | 158 | | }; |
| | | 159 | | |
| | 24 | 160 | | private static string? Blank(string? value) => string.IsNullOrWhiteSpace(value) ? null : value; |
| | | 161 | | |
| | | 162 | | /// <summary>Время в БД — UTC; клиент показывает его в МСК. Kind нормализуется, чтобы JSON получил суффикс Z.</summa |
| | 4 | 163 | | private static DateTime Utc(DateTime value) => value.Kind switch |
| | 4 | 164 | | { |
| | 3 | 165 | | DateTimeKind.Utc => value, |
| | 0 | 166 | | DateTimeKind.Local => value.ToUniversalTime(), |
| | 1 | 167 | | _ => DateTime.SpecifyKind(value, DateTimeKind.Utc) |
| | 4 | 168 | | }; |
| | | 169 | | } |