| | | 1 | | using Microsoft.AspNetCore.Mvc.ViewFeatures; |
| | | 2 | | using OzonOrdersWeb.Areas.Studio2.Models.Transactions; |
| | | 3 | | using Services.ApiServices._1CApi.Stock; |
| | | 4 | | |
| | | 5 | | namespace OzonOrdersWeb.Areas.Studio2.Helpers; |
| | | 6 | | |
| | | 7 | | |
| | | 8 | | /// <summary> |
| | | 9 | | /// Единая точка вызова предварительной проверки остатков 1С из контроллеров транзакций. |
| | | 10 | | /// Ограничивает ожидание: с ретраями на 5xx (2/4/8 с) при таймауте клиента 60 с проверка иначе |
| | | 11 | | /// могла бы задержать создание документа на минуты. |
| | | 12 | | /// </summary> |
| | | 13 | | public static class OneCStockGate |
| | | 14 | | { |
| | 0 | 15 | | private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(15); |
| | | 16 | | |
| | | 17 | | /// <summary> |
| | | 18 | | /// Сервис резолвится из HttpContext, а не через конструктор: гейт врезается в шесть контроллеров |
| | | 19 | | /// транзакций, у каждого из которых конструктор уже на два десятка зависимостей. В юнит-тестах, |
| | | 20 | | /// где сервисов нет, гейт просто ничего не проверяет. |
| | | 21 | | /// </summary> |
| | | 22 | | public static Task<StockCheckResult> RunAsync(HttpContext? http, IReadOnlyList<StockCheckLine> lines) => |
| | 0 | 23 | | RunAsync(http?.RequestServices?.GetService<IOneCStockAvailabilityService>(), lines); |
| | | 24 | | |
| | | 25 | | public static async Task<StockCheckResult> RunAsync( |
| | | 26 | | IOneCStockAvailabilityService? service, |
| | | 27 | | IReadOnlyList<StockCheckLine> lines) |
| | | 28 | | { |
| | 0 | 29 | | if (service == null || lines.Count == 0) |
| | 0 | 30 | | return StockCheckResult.Empty; |
| | | 31 | | |
| | 0 | 32 | | using var cts = new CancellationTokenSource(Timeout); |
| | 0 | 33 | | return await service.CheckAsync(lines, cts.Token); |
| | 0 | 34 | | } |
| | | 35 | | |
| | | 36 | | /// <summary>Сигнал только для фактического проведения, не для предпросмотра таблицы.</summary> |
| | | 37 | | public static async Task ReportAsync(HttpContext? http, StockCheckResult result, string? operation = null) |
| | | 38 | | { |
| | 0 | 39 | | var reporter = http?.RequestServices?.GetService<IOneCStockAlertReporter>(); |
| | 0 | 40 | | if (reporter == null) |
| | 0 | 41 | | return; |
| | | 42 | | |
| | | 43 | | try |
| | | 44 | | { |
| | 0 | 45 | | await reporter.ReportAsync(result, operation, http?.RequestAborted ?? CancellationToken.None); |
| | 0 | 46 | | } |
| | 0 | 47 | | catch (OperationCanceledException) when (http?.RequestAborted.IsCancellationRequested == true) |
| | | 48 | | { |
| | 0 | 49 | | throw; |
| | | 50 | | } |
| | 0 | 51 | | catch |
| | | 52 | | { |
| | | 53 | | // Уведомление не должно менять результат проведения документа. |
| | 0 | 54 | | } |
| | 0 | 55 | | } |
| | | 56 | | |
| | | 57 | | /// <summary> |
| | | 58 | | /// Нехватка остатка отключает только проведение в 1С — статусы заказов и сама транзакция |
| | | 59 | | /// создаются, как при снятой галке «Провести в 1С». Возвращает true, если проведение отменено. |
| | | 60 | | /// |
| | | 61 | | /// Результат идёт в отдельный канал TempData["TransactionWarning"]: «Успешно» здесь врёт |
| | | 62 | | /// (документа нет), а «Ошибка операции» — тоже (статусы сменились штатно). |
| | | 63 | | /// </summary> |
| | | 64 | | public static bool TrySkipOneC(ITempDataDictionary tempData, StockCheckResult result) |
| | | 65 | | { |
| | 0 | 66 | | if (!result.HasBlocking) |
| | 0 | 67 | | return false; |
| | | 68 | | |
| | 0 | 69 | | tempData["TransactionErrorCode"] = TransactionErrorCode.OneCInsufficientStock.ToString(); |
| | 0 | 70 | | tempData["TransactionWarning"] = OneCStockShortageHtmlBuilder.Build(result); |
| | 0 | 71 | | return true; |
| | | 72 | | } |
| | | 73 | | |
| | | 74 | | /// <summary> |
| | | 75 | | /// Заказы, которые нельзя включать в документ 1С: одна нехватка может относиться к нескольким |
| | | 76 | | /// заказам, если потребность была агрегирована по одной карточке и складу. |
| | | 77 | | /// </summary> |
| | | 78 | | public static HashSet<int> GetBlockingOrderIds(StockCheckResult result) => |
| | 3 | 79 | | result.Blocking.SelectMany(line => line.OrderIds).ToHashSet(); |
| | | 80 | | |
| | | 81 | | /// <summary> |
| | | 82 | | /// Результат частичного проведения: доступные позиции уже ушли отдельными документами 1С, |
| | | 83 | | /// а дефицитные требуют ручного оприходования и повторного проведения. |
| | | 84 | | /// </summary> |
| | | 85 | | public static string PartialPostingNotice(StockCheckResult result, int postedOrdersCount, bool draftsCreated = false |
| | | 86 | | { |
| | 2 | 87 | | if (!result.HasBlocking) |
| | 0 | 88 | | return string.Empty; |
| | | 89 | | |
| | 2 | 90 | | if (draftsCreated) |
| | 0 | 91 | | return OneCStockShortageHtmlBuilder.Build(result, createdAsDraft: true); |
| | | 92 | | |
| | 2 | 93 | | var prefix = postedOrdersCount > 0 |
| | 2 | 94 | | ? $"<div class='alert alert-warning'><strong>Частичное проведение в 1С</strong><br/>" + |
| | 2 | 95 | | $"В документы 1С включены {postedOrdersCount} {OrdersWord(postedOrdersCount)} с достаточным остатком. " + |
| | 2 | 96 | | "Позиции из списка ниже не включены.</div>" |
| | 2 | 97 | | : "<div class='alert alert-warning'><strong>Документ 1С не создан</strong><br/>" + |
| | 2 | 98 | | "Все позиции имеют нехватку остатка.</div>"; |
| | | 99 | | |
| | 2 | 100 | | return prefix + OneCStockShortageHtmlBuilder.Build(result); |
| | | 101 | | } |
| | | 102 | | |
| | | 103 | | /// <summary> |
| | | 104 | | /// Заглушка «документов в 1С не требовалось» для ветки без проведения. Если проведение отменил |
| | | 105 | | /// гейт, заглушку не показываем: документ как раз требовался, и причина уже описана в |
| | | 106 | | /// предупреждении — иначе рядом висят два противоречащих друг другу сообщения. |
| | | 107 | | /// </summary> |
| | | 108 | | public static string NoDocumentNotice(StockCheckResult result) => |
| | 0 | 109 | | result.HasBlocking |
| | 0 | 110 | | ? string.Empty |
| | 0 | 111 | | : "<div class='alert alert-info'><strong>ℹ️ Информация</strong><br/>Создание документов в 1С не требовалось< |
| | | 112 | | |
| | | 113 | | /// <summary>Приписка к сообщению об успехе, когда 1С не ответила и проверку пришлось пропустить.</summary> |
| | | 114 | | public static string SkippedNotice(StockCheckResult result) => |
| | 0 | 115 | | result.CheckSkipped |
| | 0 | 116 | | ? "<div class='alert alert-warning mt-2'>Проверка остатков 1С не выполнена (1С недоступна) — документ создан |
| | 0 | 117 | | : string.Empty; |
| | | 118 | | |
| | | 119 | | private static string OrdersWord(int count) => |
| | 1 | 120 | | count % 100 is >= 11 and <= 14 |
| | 1 | 121 | | ? "заказов" |
| | 1 | 122 | | : count % 10 == 1 ? "заказ" : count % 10 is >= 2 and <= 4 ? "заказа" : "заказов"; |
| | | 123 | | } |