| | | 1 | | using System.Text.Json; |
| | | 2 | | using System.Globalization; |
| | | 3 | | using Microsoft.AspNetCore.Mvc; |
| | | 4 | | using Microsoft.EntityFrameworkCore; |
| | | 5 | | using Microsoft.Extensions.DependencyInjection; |
| | | 6 | | using OzonDomains; |
| | | 7 | | using OzonDomains.Models; |
| | | 8 | | using OzonDomains.Models.SupplierStatuses; |
| | | 9 | | using OzonOrdersWeb.Areas.Studio2.Controllers.Base; |
| | | 10 | | using OzonRepositories.Context; |
| | | 11 | | using PartsInfo.HttpUtils; |
| | | 12 | | using Services.ApiServices; |
| | | 13 | | using Services.ApiServices.OzonApi; |
| | | 14 | | using Services.CacheServices.Cache.OzonOrdersCache; |
| | | 15 | | using Services.CacheServices.Cache.UserCacheService; |
| | | 16 | | using Services.DataServices; |
| | | 17 | | using OzonOrdersWeb.WebServices.Currency; |
| | | 18 | | using Services.TransactionUtilsServices; |
| | | 19 | | using Services.CacheServices.Cache; |
| | | 20 | | using OzonOrdersWeb.TransactionServices; |
| | | 21 | | using MikadoCartService = OzonOrdersWeb.TransactionServices.MikadoCartService; |
| | | 22 | | using MikadoQueueItem = OzonOrdersWeb.TransactionServices.MikadoQueueItem; |
| | | 23 | | using ZZapCartService = OzonOrdersWeb.TransactionServices.ZZapCartService; |
| | | 24 | | using ZZapCartQueueItem = OzonOrdersWeb.TransactionServices.ZZapCartQueueItem; |
| | | 25 | | using ZZapCartResult = OzonOrdersWeb.TransactionServices.ZZapCartResult; |
| | | 26 | | using Services.ApiServices.JapartsApi; |
| | | 27 | | using Services.ApiServices.BergApi; |
| | | 28 | | using Services.SupplierStatusServices; |
| | | 29 | | using OzonOrdersWeb.WebServices.Transactions; |
| | | 30 | | |
| | | 31 | | namespace OzonOrdersWeb.Areas.Studio2.Controllers.Transactions; |
| | | 32 | | |
| | | 33 | | /// <summary> |
| | | 34 | | /// Контроллер транзакции "Заказан поставщику" |
| | | 35 | | /// </summary> |
| | | 36 | | public class OrderToSupplierTransactionController : BaseTransactionController |
| | | 37 | | { |
| | | 38 | | private const string ArmtekDirectCheckoutResultKey = "ArmtekDirectCheckoutResult"; |
| | | 39 | | private const string MlAutoDirectCheckoutResultKey = "MlAutoDirectCheckoutResult"; |
| | | 40 | | private const string MlAutoRuDirectCheckoutResultKey = "MlAutoRuDirectCheckoutResult"; |
| | | 41 | | private const string AvdDirectCheckoutResultKey = "AvdDirectCheckoutResult"; |
| | | 42 | | private const string ForumAutoDirectCheckoutResultKey = "ForumAutoDirectCheckoutResult"; |
| | | 43 | | private const string FavoritPartsDirectCheckoutResultKey = "FavoritPartsDirectCheckoutResult"; |
| | | 44 | | private const string FavoritPartsIstraDirectCheckoutResultKey = "FavoritPartsIstraDirectCheckoutResult"; |
| | | 45 | | private const string FavoritPartsRostovDirectCheckoutResultKey = "FavoritPartsRostovDirectCheckoutResult"; |
| | | 46 | | private const string ProfitLigaDirectCheckoutResultKey = "ProfitLigaDirectCheckoutResult"; |
| | | 47 | | private const string MoskvorechieIstraDirectCheckoutResultKey = "MoskvorechieIstraDirectCheckoutResult"; |
| | | 48 | | private const string MotexDirectCheckoutResultKey = "MotexDirectCheckoutResult"; |
| | | 49 | | private const string ZZapMoscowDirectCheckoutResultKey = "ZZapMoscowDirectCheckoutResult"; |
| | | 50 | | private const string JapartsDirectCheckoutResultKey = "JapartsDirectCheckoutResult"; |
| | | 51 | | private const string BergDirectCheckoutResultKey = "BergDirectCheckoutResult"; |
| | | 52 | | private readonly OrderToSupplierOzonShipService _orderToSupplierOzonShipService; |
| | | 53 | | private readonly AvdBasketPlacementService _avdBasketPlacementService; |
| | | 54 | | private readonly JapartsCartService _japartsCartService; |
| | | 55 | | private readonly BergCartService _bergCartService; |
| | | 56 | | private readonly ArmtekCartService _armtekCartService; |
| | | 57 | | private readonly MlAutoCartService _mlAutoCartService; |
| | | 58 | | private readonly MlAutoRuCartService _mlAutoRuCartService; |
| | | 59 | | private readonly ForumAutoCartService _forumAutoCartService; |
| | | 60 | | private readonly FavoritPartsCartService _favoritPartsCartService; |
| | | 61 | | private readonly FavoritPartsIstraCartService _favoritPartsIstraCartService; |
| | | 62 | | private readonly FavoritPartsRostovCartService _favoritPartsRostovCartService; |
| | | 63 | | private readonly MoskvorechieIstraCartService _moskvorechieIstraCartService; |
| | | 64 | | private readonly ProfitLigaCartService _profitLigaCartService; |
| | | 65 | | private readonly NikeiCartService _nikeiCartService; |
| | | 66 | | private readonly MotexCartService _motexCartService; |
| | | 67 | | private readonly ShateMCartService _shateMCartService; |
| | | 68 | | private readonly MikadoCartService _mikadoCartService; |
| | | 69 | | private readonly ZZapCartService _zzapCartService; |
| | | 70 | | |
| | | 71 | | public OrderToSupplierTransactionController( |
| | | 72 | | ApplicationDbContext context, |
| | | 73 | | AppStatusDataServices appStatusServices, |
| | | 74 | | OrdersDataServices orderServices, |
| | | 75 | | TransactionManager transactionManager, |
| | | 76 | | TransactionCache transactionCache, |
| | | 77 | | SupplierDataServices supplierDataServices, |
| | | 78 | | IUserCacheService userCacheService, |
| | | 79 | | ICurrencyRatesViewBagService currencyRatesViewBagService, |
| | | 80 | | CacheUpdater<Order> cacheUpdater, |
| | | 81 | | OrderCache orderCache, |
| | | 82 | | OneCDataManager oneCDataManager, |
| | | 83 | | WarehouseDataServices warehouseDataServices, |
| | | 84 | | ProxyHttpClientService proxyHttpClientService, |
| | | 85 | | OrderCartServices orderCartServices, |
| | | 86 | | ExpenseTypeDataServices expenseTypeDataServices, |
| | | 87 | | OrderExtraExpenseDataServices orderExtraExpenseDataServices, |
| | | 88 | | StatusTransitionDataServices statusTransitionDataServices, |
| | | 89 | | OrderToSupplierOzonShipService orderToSupplierOzonShipService, |
| | | 90 | | AvdBasketPlacementService avdBasketPlacementService, |
| | | 91 | | JapartsCartService japartsCartService, |
| | | 92 | | BergCartService bergCartService, |
| | | 93 | | ArmtekCartService armtekCartService, MlAutoCartService mlAutoCartService, MlAutoRuCartService mlAutoRuCartServic |
| | | 94 | | ForumAutoCartService forumAutoCartService, FavoritPartsCartService favoritPartsCartService, FavoritPartsIstraCar |
| | | 95 | | OzonOrderCancellationService? ozonCancellationService = null) |
| | 0 | 96 | | : base(context, appStatusServices, orderServices, transactionManager, transactionCache, |
| | 0 | 97 | | supplierDataServices, userCacheService, currencyRatesViewBagService, cacheUpdater, orderCache, |
| | 0 | 98 | | oneCDataManager, warehouseDataServices, proxyHttpClientService: proxyHttpClientService, |
| | 0 | 99 | | orderCartServices: orderCartServices, expenseTypeDataServices: expenseTypeDataServices, |
| | 0 | 100 | | orderExtraExpenseDataServices: orderExtraExpenseDataServices, |
| | 0 | 101 | | statusTransitionDataServices: statusTransitionDataServices, |
| | 0 | 102 | | ozonCancellationService: ozonCancellationService) |
| | | 103 | | { |
| | 0 | 104 | | _orderToSupplierOzonShipService = orderToSupplierOzonShipService; |
| | 0 | 105 | | _avdBasketPlacementService = avdBasketPlacementService; |
| | 0 | 106 | | _japartsCartService = japartsCartService; |
| | 0 | 107 | | _bergCartService = bergCartService; |
| | 0 | 108 | | _armtekCartService = armtekCartService; |
| | 0 | 109 | | _mlAutoCartService = mlAutoCartService; |
| | 0 | 110 | | _mlAutoRuCartService = mlAutoRuCartService; |
| | 0 | 111 | | _forumAutoCartService = forumAutoCartService; |
| | 0 | 112 | | _favoritPartsCartService = favoritPartsCartService; |
| | 0 | 113 | | _favoritPartsIstraCartService = favoritPartsIstraCartService; |
| | 0 | 114 | | _favoritPartsRostovCartService = favoritPartsRostovCartService; |
| | 0 | 115 | | _moskvorechieIstraCartService = moskvorechieIstraCartService; |
| | 0 | 116 | | _profitLigaCartService = profitLigaCartService; |
| | 0 | 117 | | _nikeiCartService = nikeiCartService; |
| | 0 | 118 | | _motexCartService = motexCartService; |
| | 0 | 119 | | _shateMCartService = shateMCartService; |
| | 0 | 120 | | _mikadoCartService = mikadoCartService; |
| | 0 | 121 | | _zzapCartService = zzapCartService; |
| | 0 | 122 | | } |
| | | 123 | | |
| | 0 | 124 | | protected override TransactionConfig GetTransactionConfig() => new() |
| | 0 | 125 | | { |
| | 0 | 126 | | TransactionType = TransactionType.OrderedToSupplier, |
| | 0 | 127 | | PrimaryStatusName = "Заказан поставщику", |
| | 0 | 128 | | AdditionalStatusNames = new List<string> { "Заказан реализатору" }, |
| | 0 | 129 | | UseStatusTransitions = true, |
| | 0 | 130 | | RequiredSourceStatuses = new List<string> { "Не указан", "Заказан поставщику", "Заказан реализатору", "К отмене" |
| | 0 | 131 | | ClearPurchasePriceOnLoad = true, |
| | 0 | 132 | | ViewName = "CreateOrderToSupplierTransactionV2", |
| | 0 | 133 | | IncludeCancelledOrders = true, |
| | 0 | 134 | | IncludeToCancellationStatus = true, |
| | 0 | 135 | | IncludeNotReadyStatus = true, |
| | 0 | 136 | | SupportsOzonCancelPanel = true, |
| | 0 | 137 | | OrderFilter = TransactionConfig.StandardOrderFilter, |
| | 0 | 138 | | CreateTransactionMethod = (tm, orders, userName, createAt, comment) => |
| | 0 | 139 | | tm.CreateOrderToSupplierTransaction(orders, userName, createAt, comment), |
| | 0 | 140 | | SuccessMessageBuilder = (changeCount, cancelCount, dateTime, userName, comment, orders) => |
| | 0 | 141 | | { |
| | 0 | 142 | | // Базовое стандартное сообщение |
| | 0 | 143 | | var baseMsg = TransactionConfig.BuildStandardSuccessMessage( |
| | 0 | 144 | | changeCount, cancelCount, dateTime, userName, comment, orders, "Заказан поставщику"); |
| | 0 | 145 | | |
| | 0 | 146 | | // Дополнительная информация про разбиение заказов и статус "К отмене" |
| | 0 | 147 | | return TransactionConfig.BuildStandardSuccessMessage( |
| | 0 | 148 | | changeCount, |
| | 0 | 149 | | cancelCount, |
| | 0 | 150 | | dateTime, |
| | 0 | 151 | | userName, |
| | 0 | 152 | | comment, |
| | 0 | 153 | | orders, |
| | 0 | 154 | | "Заказан поставщику", |
| | 0 | 155 | | "<div class='alert alert-warning mb-0'>" + |
| | 0 | 156 | | "<strong>Внимание по количеству</strong><br/>" + |
| | 0 | 157 | | "Часть заказов с уменьшенным количеством была автоматически разделена, " + |
| | 0 | 158 | | "а остатки переведены в статус <b>К отмене</b>." + |
| | 0 | 159 | | "</div>"); |
| | 0 | 160 | | }, |
| | 0 | 161 | | // Специальная логика только для транзакции "Заказан поставщику": |
| | 0 | 162 | | // при уменьшении количества создаём часть "к отмене". |
| | 0 | 163 | | AfterTransactOrdersAsync = async updatedOrders => |
| | 0 | 164 | | await ApplyQuantitySplitCancellationLogic(updatedOrders) |
| | 0 | 165 | | }; |
| | | 166 | | |
| | | 167 | | /// <summary> |
| | | 168 | | /// Специальная логика только для OrderToSupplierTransaction: |
| | | 169 | | /// - если количество уменьшено (SplitOrder создал /1 и /2), то: |
| | | 170 | | /// * часть /1 переводим в статус "К отмене" |
| | | 171 | | /// * часть /2 остаётся для статуса "Заказан поставщику" |
| | | 172 | | /// - если количество уменьшено до 0, то заказ переводим в статус "К отмене". |
| | | 173 | | /// </summary> |
| | | 174 | | private async Task ApplyQuantitySplitCancellationLogic(List<Order> updatedOrders) |
| | | 175 | | { |
| | 0 | 176 | | if (updatedOrders == null || updatedOrders.Count == 0) |
| | | 177 | | { |
| | 0 | 178 | | return; |
| | | 179 | | } |
| | | 180 | | |
| | | 181 | | // Статус "К отмене" |
| | 0 | 182 | | var toCancellationStatus = await GetOrCreateAppStatus("К отмене"); |
| | | 183 | | |
| | 0 | 184 | | var now = DateTime.Now; |
| | | 185 | | |
| | | 186 | | // 1) Полное уменьшение количества до 0 — переводим заказ в "К отмене" |
| | | 187 | | foreach (var order in updatedOrders.Where(o => (o.Quantity ?? 0) == 0)) |
| | | 188 | | { |
| | 0 | 189 | | order.AppStatusId = toCancellationStatus.Id; |
| | 0 | 190 | | order.AppStatus = toCancellationStatus; |
| | 0 | 191 | | order.LastStatusChangeDate = now; |
| | 0 | 192 | | order.IsVerified = true; |
| | | 193 | | } |
| | | 194 | | |
| | | 195 | | // 2) Частичное уменьшение: SplitOrder создаёт пару /1 и /2. |
| | | 196 | | // updatedOrders содержит только вторую часть (/2) с новым количеством. |
| | 0 | 197 | | var secondParts = updatedOrders |
| | | 198 | | .Where(o => !string.IsNullOrEmpty(o.ShipmentNumber) && |
| | | 199 | | o.ShipmentNumber!.EndsWith("/2")) |
| | 0 | 200 | | .ToList(); |
| | | 201 | | |
| | 0 | 202 | | if (secondParts.Count > 0) |
| | | 203 | | { |
| | | 204 | | // Для каждого /2 находим соответствующий /1 и переводим его в "К отмене" |
| | 0 | 205 | | var firstShipmentNumbers = secondParts |
| | | 206 | | .Select(o => o.ShipmentNumber![..^2] + "/1") |
| | 0 | 207 | | .Distinct() |
| | 0 | 208 | | .ToList(); |
| | | 209 | | |
| | 0 | 210 | | var firstParts = await Context.Orders |
| | 0 | 211 | | .Where(o => o.ShipmentNumber != null && |
| | 0 | 212 | | firstShipmentNumbers.Contains(o.ShipmentNumber)) |
| | 0 | 213 | | .ToListAsync(); |
| | | 214 | | |
| | | 215 | | |
| | 0 | 216 | | foreach (var original in firstParts) |
| | | 217 | | { |
| | | 218 | | // Если после разбиения в первой части что‑то осталось, помечаем её "К отмене" |
| | 0 | 219 | | if (original.Quantity is > 0) |
| | | 220 | | { |
| | 0 | 221 | | original.AppStatusId = toCancellationStatus.Id; |
| | 0 | 222 | | original.AppStatus = toCancellationStatus; |
| | 0 | 223 | | original.LastStatusChangeDate = now; |
| | 0 | 224 | | original.IsVerified = true; |
| | | 225 | | } |
| | | 226 | | } |
| | | 227 | | } |
| | | 228 | | |
| | 0 | 229 | | await Context.SaveChangesAsync(); |
| | 0 | 230 | | } |
| | | 231 | | |
| | | 232 | | public Task<IActionResult> CreateOrderToSupplierTransactionV2(string ids, int page) |
| | 0 | 233 | | => CreateTransactionBase(ids, page); |
| | | 234 | | |
| | | 235 | | [HttpPost] |
| | | 236 | | [ValidateAntiForgeryToken] |
| | | 237 | | public Task<IActionResult> CreateOrderToSupplierTransactionV2( |
| | | 238 | | List<Order> orders, string userName, string? comment, |
| | | 239 | | int page, string? deletedOrders, Dictionary<int, string?>? orderSupplierMap) |
| | 0 | 240 | | => CreateTransactionPostBase(orders, userName, comment, page, deletedOrders, orderSupplierMap); |
| | | 241 | | |
| | | 242 | | [HttpPost] |
| | | 243 | | [ValidateAntiForgeryToken] |
| | | 244 | | public async Task<IActionResult> PreviewArmtekOrder([FromForm] string? armtekCheckoutQueueJson, CancellationToken ct |
| | | 245 | | { |
| | 0 | 246 | | if (string.IsNullOrWhiteSpace(armtekCheckoutQueueJson)) |
| | 0 | 247 | | return BadRequest(new { error = "Не выбраны позиции Armtek для оформления." }); |
| | | 248 | | try |
| | | 249 | | { |
| | 0 | 250 | | var queue = ParseArmtekBasketQueue(armtekCheckoutQueueJson); |
| | 0 | 251 | | var result = await _armtekCartService.BuildDirectOrderPreviewAsync(queue, ct); |
| | 0 | 252 | | return result.Errors.Count > 0 |
| | 0 | 253 | | ? BadRequest(new { error = string.Join(" ", result.Errors) }) |
| | 0 | 254 | | : Json(new { orders = result.Previews }); |
| | | 255 | | } |
| | 0 | 256 | | catch (JsonException) |
| | | 257 | | { |
| | 0 | 258 | | return BadRequest(new { error = "Не удалось прочитать выбранные позиции Armtek." }); |
| | | 259 | | } |
| | 0 | 260 | | } |
| | | 261 | | |
| | | 262 | | protected override async Task<string?> ExecutePreTransactionAsync(List<Order> orders) |
| | | 263 | | { |
| | | 264 | | // Классический поток не умеет запрашивать подтверждение: повторный заказ у поставщика здесь блокируется всегда. |
| | | 265 | | // Подтвердить повтор можно только в новом гриде проведения (TransactionsGrid). |
| | | 266 | | var permittedForGuard = orders.Select(x => x.Id).ToHashSet(); |
| | 0 | 267 | | var formQueues = Request.Form |
| | | 268 | | .Where(x => x.Key.EndsWith("QueueJson", StringComparison.OrdinalIgnoreCase)) |
| | | 269 | | .Select(x => new KeyValuePair<string, string>(x.Key, x.Value.ToString())); |
| | 0 | 270 | | var guardTargets = SupplierQueueTargets.Extract(formQueues).Where(x => permittedForGuard.Contains(x.OrderId)).To |
| | 0 | 271 | | var supplierGuard = HttpContext.RequestServices.GetRequiredService<ISupplierOrderGuard>(); |
| | 0 | 272 | | var guardResult = await supplierGuard.EvaluateAsync(guardTargets, null, User.Identity?.Name, HttpContext.Request |
| | 0 | 273 | | if (!guardResult.IsAllowed) |
| | 0 | 274 | | return guardResult.BlockingMessage().Replace(" Чтобы оформить повторно, подтвердите это явно.", |
| | 0 | 275 | | " Повторное оформление возможно только в новом гриде проведения с явным подтверждением.", StringComparis |
| | | 276 | | |
| | | 277 | | // Намерения (Placing) фиксируются до обращения к поставщикам; после ответа — Placed/Failed. |
| | | 278 | | // Шате-М в классическом потоке оформляется после записи транзакции и намерений не ведёт: завершить их здесь зна |
| | | 279 | | // пометить заказ Failed до отправки. |
| | 0 | 280 | | var dispatchTargets = guardTargets |
| | | 281 | | .Where(x => !string.Equals(x.ProviderKey, ApiSupplierSettings.ShateMProviderKey, StringComparison.OrdinalIgn |
| | 0 | 282 | | .ToList(); |
| | 0 | 283 | | var dispatchStart = await supplierGuard.BeginDispatchAsync(Guid.NewGuid().ToString("N"), "Legacy", dispatchTarge |
| | 0 | 284 | | guardResult, User.Identity?.Name, HttpContext.RequestAborted); |
| | 0 | 285 | | if (!dispatchStart.CanProceed) return dispatchStart.Message; |
| | | 286 | | |
| | 0 | 287 | | var error = await ExecutePreTransactionCoreAsync(orders); |
| | 0 | 288 | | await supplierGuard.CompleteDispatchAsync(dispatchStart.Dispatch!); |
| | 0 | 289 | | return error; |
| | 0 | 290 | | } |
| | | 291 | | |
| | | 292 | | private async Task<string?> ExecutePreTransactionCoreAsync(List<Order> orders) |
| | | 293 | | { |
| | 0 | 294 | | var raw = Request.Form["ArmtekCheckoutQueueJson"].ToString(); |
| | 0 | 295 | | if (string.IsNullOrWhiteSpace(raw)) raw = "[]"; |
| | | 296 | | List<ArmtekCartQueueItem> queue; |
| | 0 | 297 | | try { queue = ParseArmtekBasketQueue(raw); } |
| | 0 | 298 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции Armtek."; } |
| | | 299 | | |
| | | 300 | | var permittedOrderIds = orders.Select(x => x.Id).ToHashSet(); |
| | | 301 | | // Пустая очередь Armtek означает только, что прямой заказ выбран для другого поставщика. |
| | 0 | 302 | | if (queue.Count > 0) |
| | | 303 | | { |
| | 0 | 304 | | if (queue.Any(x => !permittedOrderIds.Contains(x.OrderId))) |
| | 0 | 305 | | return "Состав предложений Armtek не соответствует проводимым заказам. Обновите страницу и повторите поп |
| | | 306 | | try |
| | | 307 | | { |
| | 0 | 308 | | var result = await _armtekCartService.CheckoutDirectAsync(queue, User.Identity?.Name ?? "unknown", HttpC |
| | 0 | 309 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 310 | | HttpContext.Items[ArmtekDirectCheckoutResultKey] = result; |
| | 0 | 311 | | } |
| | 0 | 312 | | catch (Exception ex) when (ex is Services.ApiServices.ArmtekApi.ArmtekApiException or HttpRequestException) |
| | | 313 | | { |
| | 0 | 314 | | return $"Армтек: {ex.Message}"; |
| | | 315 | | } |
| | | 316 | | } |
| | | 317 | | |
| | 0 | 318 | | var user = User.Identity?.Name ?? "unknown"; |
| | 0 | 319 | | var mlAutoRaw = Request.Form["MlAutoCheckoutQueueJson"].ToString(); |
| | 0 | 320 | | if (!string.IsNullOrWhiteSpace(mlAutoRaw)) |
| | | 321 | | { |
| | | 322 | | try |
| | | 323 | | { |
| | 0 | 324 | | var directQueue = ParseMlAutoBasketQueue(mlAutoRaw); |
| | 0 | 325 | | if (directQueue.Count > 0) |
| | | 326 | | { |
| | 0 | 327 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) return "Состав предложений ML-Auto |
| | 0 | 328 | | var result = await _mlAutoCartService.CheckoutDirectAsync(directQueue, user, HttpContext.RequestAbor |
| | 0 | 329 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 330 | | HttpContext.Items[MlAutoDirectCheckoutResultKey] = result; |
| | | 331 | | } |
| | 0 | 332 | | } |
| | 0 | 333 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции ML-Auto BY."; } |
| | 0 | 334 | | catch (Services.ApiServices.MlAutoApi.MlAutoApiException ex) { return $"ML-Auto BY: {ex.Message}"; } |
| | | 335 | | } |
| | | 336 | | |
| | 0 | 337 | | var mlAutoRuRaw = Request.Form["MlAutoRuCheckoutQueueJson"].ToString(); |
| | 0 | 338 | | if (!string.IsNullOrWhiteSpace(mlAutoRuRaw)) |
| | | 339 | | { |
| | | 340 | | try |
| | | 341 | | { |
| | | 342 | | var directQueue = ParseMlAutoBasketQueue(mlAutoRuRaw).Select(x => new MlAutoRuCartQueueItem(x.OrderId, x |
| | 0 | 343 | | if (directQueue.Count > 0) |
| | | 344 | | { |
| | 0 | 345 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) return "Состав предложений ML-Auto |
| | 0 | 346 | | var result = await _mlAutoRuCartService.CheckoutDirectAsync(directQueue, user, HttpContext.RequestAb |
| | 0 | 347 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 348 | | HttpContext.Items[MlAutoRuDirectCheckoutResultKey] = result; |
| | | 349 | | } |
| | 0 | 350 | | } |
| | 0 | 351 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции ML-Auto RU."; } |
| | 0 | 352 | | catch (Services.ApiServices.MlAutoApi.MlAutoApiException ex) { return $"ML-Auto RU: {ex.Message}"; } |
| | | 353 | | } |
| | | 354 | | |
| | 0 | 355 | | var avdRaw = Request.Form["AvdCheckoutQueueJson"].ToString(); |
| | 0 | 356 | | if (!string.IsNullOrWhiteSpace(avdRaw)) |
| | | 357 | | { |
| | | 358 | | try |
| | | 359 | | { |
| | 0 | 360 | | var directQueue = JsonSerializer.Deserialize<List<AvdBasketQueueItem>>(avdRaw, new JsonSerializerOptions |
| | 0 | 361 | | if (directQueue.Count > 0) |
| | | 362 | | { |
| | 0 | 363 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) return "Состав предложений АВД не |
| | 0 | 364 | | var result = await _avdBasketPlacementService.CheckoutDirectAsync(directQueue, user, HttpContext.Req |
| | 0 | 365 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 366 | | HttpContext.Items[AvdDirectCheckoutResultKey] = result; |
| | | 367 | | } |
| | 0 | 368 | | } |
| | 0 | 369 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции АВД."; } |
| | 0 | 370 | | catch (Services.ApiServices.AvdApi.AvdApiException ex) { return $"АВД: {ex.Message}"; } |
| | | 371 | | } |
| | | 372 | | |
| | 0 | 373 | | var japartsRaw = Request.Form["JapartsCheckoutQueueJson"]; |
| | 0 | 374 | | if (japartsRaw.Count > 0 && !japartsRaw.All(string.IsNullOrWhiteSpace)) |
| | | 375 | | { |
| | | 376 | | List<JapartsCartQueueItem> directQueue; |
| | 0 | 377 | | try { directQueue = ParseJapartsBasketQueue(japartsRaw).Items; } |
| | 0 | 378 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции Japarts."; } |
| | 0 | 379 | | if (directQueue.Count > 0) |
| | | 380 | | { |
| | 0 | 381 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) |
| | 0 | 382 | | return "Состав предложений Japarts не соответствует проводимым заказам. Обновите страницу и повторит |
| | 0 | 383 | | var result = await _japartsCartService.CheckoutDirectAsync(directQueue, user, HttpContext.RequestAborted |
| | 0 | 384 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 385 | | HttpContext.Items[JapartsDirectCheckoutResultKey] = result; |
| | | 386 | | } |
| | | 387 | | } |
| | | 388 | | |
| | 0 | 389 | | var bergRaw = Request.Form["BergCheckoutQueueJson"]; |
| | 0 | 390 | | if (bergRaw.Count > 0 && !bergRaw.All(string.IsNullOrWhiteSpace)) |
| | | 391 | | { |
| | | 392 | | List<BergCartQueueItem> directQueue; |
| | 0 | 393 | | try { directQueue = ParseBergBasketQueue(bergRaw).Items; } |
| | 0 | 394 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции Berg."; } |
| | 0 | 395 | | if (directQueue.Count > 0) |
| | | 396 | | { |
| | 0 | 397 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) |
| | 0 | 398 | | return "Состав предложений Berg не соответствует проводимым заказам. Обновите страницу и повторите п |
| | 0 | 399 | | var result = await _bergCartService.CheckoutDirectAsync(directQueue, user, HttpContext.RequestAborted); |
| | 0 | 400 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 401 | | HttpContext.Items[BergDirectCheckoutResultKey] = result; |
| | | 402 | | } |
| | | 403 | | } |
| | | 404 | | |
| | 0 | 405 | | var forumAutoRaw = Request.Form["ForumAutoCheckoutQueueJson"].ToString(); |
| | 0 | 406 | | if (!string.IsNullOrWhiteSpace(forumAutoRaw)) |
| | | 407 | | { |
| | | 408 | | try |
| | | 409 | | { |
| | 0 | 410 | | var directQueue = ParseForumAutoBasketQueue(forumAutoRaw); |
| | 0 | 411 | | if (directQueue.Count > 0) |
| | | 412 | | { |
| | 0 | 413 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) |
| | 0 | 414 | | return "Состав предложений Форум-Авто не соответствует проводимым заказам. Обновите страницу и п |
| | 0 | 415 | | var result = await _forumAutoCartService.CheckoutDirectAsync(directQueue, user, HttpContext.RequestA |
| | 0 | 416 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 417 | | HttpContext.Items[ForumAutoDirectCheckoutResultKey] = result; |
| | | 418 | | } |
| | 0 | 419 | | } |
| | 0 | 420 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции Форум-Авто."; } |
| | 0 | 421 | | catch (Services.ApiServices.ForumAutoApi.ForumAutoApiException ex) { return $"Форум-Авто: {ex.Message}"; } |
| | | 422 | | } |
| | | 423 | | |
| | 0 | 424 | | var favoritPartsRaw = Request.Form["FavoritPartsCheckoutQueueJson"].ToString(); |
| | 0 | 425 | | if (!string.IsNullOrWhiteSpace(favoritPartsRaw)) |
| | | 426 | | { |
| | | 427 | | try |
| | | 428 | | { |
| | 0 | 429 | | var directQueue = ParseFavoritPartsBasketQueue(favoritPartsRaw); |
| | 0 | 430 | | if (directQueue.Count > 0) |
| | | 431 | | { |
| | 0 | 432 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) |
| | 0 | 433 | | return "Состав предложений FavoritParts не соответствует проводимым заказам. Обновите страницу и |
| | 0 | 434 | | var result = await _favoritPartsCartService.CheckoutDirectAsync(directQueue, User.Identity?.Name ?? |
| | 0 | 435 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 436 | | HttpContext.Items[FavoritPartsDirectCheckoutResultKey] = result; |
| | | 437 | | } |
| | 0 | 438 | | } |
| | 0 | 439 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции FavoritParts."; } |
| | 0 | 440 | | catch (Services.ApiServices.FavoritPartsApi.FavoritPartsApiException ex) { return $"FavoritParts: {ex.Messag |
| | | 441 | | } |
| | | 442 | | |
| | 0 | 443 | | var favoritPartsIstraRaw = Request.Form["FavoritPartsIstraCheckoutQueueJson"].ToString(); |
| | 0 | 444 | | if (!string.IsNullOrWhiteSpace(favoritPartsIstraRaw)) |
| | | 445 | | { |
| | | 446 | | try |
| | | 447 | | { |
| | 0 | 448 | | var directQueue = ParseFavoritPartsBasketQueue(favoritPartsIstraRaw); |
| | 0 | 449 | | if (directQueue.Count > 0) |
| | | 450 | | { |
| | 0 | 451 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) |
| | 0 | 452 | | return "Состав предложений Фаворит Истра не соответствует проводимым заказам. Обновите страницу |
| | 0 | 453 | | var result = await _favoritPartsIstraCartService.CheckoutDirectAsync(directQueue, User.Identity?.Nam |
| | 0 | 454 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 455 | | HttpContext.Items[FavoritPartsIstraDirectCheckoutResultKey] = result; |
| | | 456 | | } |
| | 0 | 457 | | } |
| | 0 | 458 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции Фаворит Истра."; } |
| | 0 | 459 | | catch (Services.ApiServices.FavoritPartsApi.FavoritPartsApiException ex) { return $"Фаворит Истра: {ex.Messa |
| | | 460 | | } |
| | | 461 | | |
| | 0 | 462 | | var favoritPartsRostovRaw = Request.Form["FavoritPartsRostovCheckoutQueueJson"].ToString(); |
| | 0 | 463 | | if (!string.IsNullOrWhiteSpace(favoritPartsRostovRaw)) |
| | | 464 | | { |
| | | 465 | | try |
| | | 466 | | { |
| | 0 | 467 | | var directQueue = ParseFavoritPartsBasketQueue(favoritPartsRostovRaw); |
| | 0 | 468 | | if (directQueue.Count > 0) |
| | | 469 | | { |
| | 0 | 470 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) |
| | 0 | 471 | | return "Состав предложений Фаворит Ростов не соответствует проводимым заказам. Обновите страницу |
| | 0 | 472 | | var result = await _favoritPartsRostovCartService.CheckoutDirectAsync(directQueue, User.Identity?.Na |
| | 0 | 473 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 474 | | HttpContext.Items[FavoritPartsRostovDirectCheckoutResultKey] = result; |
| | | 475 | | } |
| | 0 | 476 | | } |
| | 0 | 477 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции Фаворит Ростов."; } |
| | 0 | 478 | | catch (Services.ApiServices.FavoritPartsApi.FavoritPartsApiException ex) { return $"Фаворит Ростов: {ex.Mess |
| | | 479 | | } |
| | | 480 | | |
| | 0 | 481 | | var moskvorechieRaw = Request.Form["MoskvorechieCheckoutQueueJson"].ToString(); |
| | 0 | 482 | | if (!string.IsNullOrWhiteSpace(moskvorechieRaw)) |
| | | 483 | | { |
| | | 484 | | try |
| | | 485 | | { |
| | 0 | 486 | | var directQueue = ParseMoskvorechieBasketQueue(moskvorechieRaw); |
| | 0 | 487 | | if (directQueue.Count > 0) |
| | | 488 | | { |
| | 0 | 489 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) return "Состав предложений Москвор |
| | 0 | 490 | | var result = await _moskvorechieIstraCartService.CheckoutDirectAsync(directQueue, user, HttpContext. |
| | 0 | 491 | | if (result.Failed > 0) return string.Join(" ", result.Messages); |
| | 0 | 492 | | HttpContext.Items[MoskvorechieIstraDirectCheckoutResultKey] = result; |
| | | 493 | | } |
| | 0 | 494 | | } |
| | 0 | 495 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции Москворечья."; } |
| | 0 | 496 | | catch (Services.ApiServices.MoskvorechieApi.MoskvorechieApiException ex) { return $"Москворечье: {ex.Message |
| | | 497 | | } |
| | | 498 | | |
| | 0 | 499 | | var profitLigaRaw = Request.Form["ProfitLigaCheckoutQueueJson"].ToString(); |
| | 0 | 500 | | if (!string.IsNullOrWhiteSpace(profitLigaRaw)) |
| | | 501 | | { |
| | | 502 | | try |
| | | 503 | | { |
| | 0 | 504 | | var directQueue = ParseProfitLigaBasketQueue(profitLigaRaw); |
| | 0 | 505 | | if (directQueue.Count > 0) |
| | | 506 | | { |
| | 0 | 507 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) |
| | 0 | 508 | | return "Состав предложений Профит-Лиги не соответствует проводимым заказам. Обновите страницу и |
| | 0 | 509 | | var result = await _profitLigaCartService.CheckoutDirectAsync(directQueue, user, HttpContext.Request |
| | 0 | 510 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 511 | | HttpContext.Items[ProfitLigaDirectCheckoutResultKey] = result; |
| | | 512 | | } |
| | 0 | 513 | | } |
| | 0 | 514 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции Профит-Лиги."; } |
| | 0 | 515 | | catch (Services.ApiServices.ProfitLigaApi.ProfitLigaApiException ex) { return $"Профит-Лига: {ex.Message}"; |
| | | 516 | | } |
| | | 517 | | |
| | 0 | 518 | | var motexRaw = Request.Form["MotexCheckoutQueueJson"].ToString(); |
| | 0 | 519 | | if (!string.IsNullOrWhiteSpace(motexRaw)) |
| | | 520 | | { |
| | | 521 | | try |
| | | 522 | | { |
| | 0 | 523 | | var directQueue = ParseMotexBasketQueue(motexRaw); |
| | 0 | 524 | | if (directQueue.Count > 0) |
| | | 525 | | { |
| | 0 | 526 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) return "Состав предложений МоТехС |
| | 0 | 527 | | var result = await _motexCartService.CheckoutDirectAsync(directQueue, user, HttpContext.RequestAbort |
| | 0 | 528 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 529 | | HttpContext.Items[MotexDirectCheckoutResultKey] = result; |
| | | 530 | | } |
| | 0 | 531 | | } |
| | 0 | 532 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции МоТехС."; } |
| | | 533 | | } |
| | | 534 | | |
| | 0 | 535 | | var zzapRaw = Request.Form["ZZapMoscowCheckoutQueueJson"].ToString(); |
| | 0 | 536 | | if (!string.IsNullOrWhiteSpace(zzapRaw)) |
| | | 537 | | { |
| | | 538 | | try |
| | | 539 | | { |
| | 0 | 540 | | var directQueue = ParseZZapBasketQueue(zzapRaw); |
| | 0 | 541 | | if (directQueue.Count > 0) |
| | | 542 | | { |
| | 0 | 543 | | if (directQueue.Any(x => !permittedOrderIds.Contains(x.OrderId))) return "Состав предложений ZZap не |
| | 0 | 544 | | var result = await _zzapCartService.CheckoutDirectAsync(directQueue, user, HttpContext.RequestAborte |
| | 0 | 545 | | if (!result.IsSuccess) return string.Join(" ", result.Messages); |
| | 0 | 546 | | HttpContext.Items[ZZapMoscowDirectCheckoutResultKey] = result; |
| | | 547 | | } |
| | 0 | 548 | | } |
| | 0 | 549 | | catch (JsonException) { return "Не удалось прочитать выбранные позиции ZZap."; } |
| | | 550 | | } |
| | | 551 | | |
| | 0 | 552 | | return null; |
| | 0 | 553 | | } |
| | | 554 | | |
| | | 555 | | protected override async Task<string?> GetPostTransactionSuccessAppendixAsync( |
| | | 556 | | List<Order> ordersToTransaction, |
| | | 557 | | TransactionConfig config) |
| | | 558 | | { |
| | 0 | 559 | | if (config.TransactionType != TransactionType.OrderedToSupplier) |
| | 0 | 560 | | return null; |
| | | 561 | | |
| | 0 | 562 | | var orderIds = ordersToTransaction |
| | | 563 | | .Select(o => o.Id) |
| | | 564 | | .Where(id => id > 0) |
| | 0 | 565 | | .Distinct() |
| | 0 | 566 | | .ToList(); |
| | | 567 | | |
| | 0 | 568 | | var blocks = new List<string>(); |
| | 0 | 569 | | var profitLigaOrderIds = orderIds; |
| | | 570 | | |
| | 0 | 571 | | var shouldShipInOzon = Request.Form.TryGetValue("shipInOzon", out var rawValues) && |
| | | 572 | | rawValues.Any(v => string.Equals(v, "true", StringComparison.OrdinalIgnoreCase)); |
| | 0 | 573 | | if (shouldShipInOzon && orderIds.Count > 0) |
| | | 574 | | { |
| | 0 | 575 | | await ReportStageAsync("ozon-ship", "Отгружаем в Ozon", |
| | 0 | 576 | | orderIds.Count == 1 ? "1 отправление" : $"отправлений: {orderIds.Count}"); |
| | 0 | 577 | | var shipResult = await _orderToSupplierOzonShipService.ShipAsync(orderIds, HttpContext.RequestAborted); |
| | 0 | 578 | | HttpContext.Items[TransactionCommentAiHttpContextKeys.OzonShipResult] = shipResult; |
| | 0 | 579 | | profitLigaOrderIds = shipResult.EligibleOrderIds.ToList(); |
| | 0 | 580 | | var shipBlock = shipResult.ToHtmlBlock(); |
| | 0 | 581 | | if (!string.IsNullOrWhiteSpace(shipBlock)) |
| | 0 | 582 | | blocks.Add(shipBlock); |
| | | 583 | | } |
| | | 584 | | |
| | 0 | 585 | | await ReportStageAsync("carts", "Размещаем позиции в корзинах поставщиков"); |
| | 0 | 586 | | var avdBlock = await GetAvdBasketAppendixAsync(orderIds); |
| | 0 | 587 | | if (!string.IsNullOrWhiteSpace(avdBlock)) |
| | 0 | 588 | | blocks.Add(avdBlock); |
| | 0 | 589 | | var japartsBlock = await GetJapartsBasketAppendixAsync(orderIds); |
| | 0 | 590 | | var bergBlock = await GetBergBasketAppendixAsync(orderIds); |
| | 0 | 591 | | if (!string.IsNullOrWhiteSpace(japartsBlock)) blocks.Add(japartsBlock); |
| | 0 | 592 | | if (!string.IsNullOrWhiteSpace(bergBlock)) blocks.Add(bergBlock); |
| | 0 | 593 | | var armtekBlock = await GetArmtekBasketAppendixAsync(orderIds); |
| | 0 | 594 | | if (!string.IsNullOrWhiteSpace(armtekBlock)) blocks.Add(armtekBlock); |
| | 0 | 595 | | if (HttpContext.Items.TryGetValue(ArmtekDirectCheckoutResultKey, out var directResultObject) && |
| | 0 | 596 | | directResultObject is ArmtekDirectCheckoutResult directResult) |
| | | 597 | | { |
| | 0 | 598 | | var lines = string.Join(string.Empty, directResult.Messages.Select(message => |
| | | 599 | | $"<li>{System.Net.WebUtility.HtmlEncode(message)}</li>")); |
| | 0 | 600 | | blocks.Add($"<div class='alert alert-success mb-0'><strong>Заказ Armtek оформлен.</strong><ul class='mb-0'>{ |
| | | 601 | | } |
| | 0 | 602 | | AppendDirectCheckoutBlock(blocks, MlAutoDirectCheckoutResultKey, "Заказ ML-Auto BY оформлен."); |
| | 0 | 603 | | AppendDirectCheckoutBlock(blocks, MlAutoRuDirectCheckoutResultKey, "Заказ ML-Auto RU оформлен."); |
| | 0 | 604 | | AppendDirectCheckoutBlock(blocks, AvdDirectCheckoutResultKey, "Заказ АВД оформлен."); |
| | 0 | 605 | | AppendDirectCheckoutBlock(blocks, ForumAutoDirectCheckoutResultKey, "Заказ Форум-Авто оформлен."); |
| | 0 | 606 | | AppendDirectCheckoutBlock(blocks, FavoritPartsDirectCheckoutResultKey, "Заказ FavoritParts оформлен."); |
| | 0 | 607 | | AppendDirectCheckoutBlock(blocks, FavoritPartsIstraDirectCheckoutResultKey, "Заказ Фаворит Истра оформлен."); |
| | 0 | 608 | | AppendDirectCheckoutBlock(blocks, FavoritPartsRostovDirectCheckoutResultKey, "Заказ Фаворит Ростов оформлен."); |
| | 0 | 609 | | AppendDirectCheckoutBlock(blocks, MoskvorechieIstraDirectCheckoutResultKey, "Заказ Москворечья оформлен."); |
| | 0 | 610 | | AppendDirectCheckoutBlock(blocks, MotexDirectCheckoutResultKey, "Заказ МоТехС оформлен."); |
| | 0 | 611 | | AppendDirectCheckoutBlock(blocks, ZZapMoscowDirectCheckoutResultKey, "Заказ ZZap оформлен."); |
| | 0 | 612 | | AppendDirectCheckoutBlock(blocks, JapartsDirectCheckoutResultKey, "Заказ Japarts оформлен."); |
| | 0 | 613 | | AppendDirectCheckoutBlock(blocks, BergDirectCheckoutResultKey, "Заказ Berg оформлен."); |
| | 0 | 614 | | var mlAutoBlock = await GetMlAutoBasketAppendixAsync(orderIds); |
| | 0 | 615 | | if (!string.IsNullOrWhiteSpace(mlAutoBlock)) blocks.Add(mlAutoBlock); |
| | 0 | 616 | | var mlAutoRuBlock = await GetMlAutoRuBasketAppendixAsync(orderIds); |
| | 0 | 617 | | if (!string.IsNullOrWhiteSpace(mlAutoRuBlock)) blocks.Add(mlAutoRuBlock); |
| | 0 | 618 | | var forumAutoBlock = await GetForumAutoBasketAppendixAsync(orderIds); |
| | 0 | 619 | | if (!string.IsNullOrWhiteSpace(forumAutoBlock)) blocks.Add(forumAutoBlock); |
| | 0 | 620 | | var favoritPartsBlock = await GetFavoritPartsBasketAppendixAsync(orderIds); |
| | 0 | 621 | | if (!string.IsNullOrWhiteSpace(favoritPartsBlock)) blocks.Add(favoritPartsBlock); |
| | 0 | 622 | | var favoritPartsIstraBlock = await GetFavoritPartsIstraBasketAppendixAsync(orderIds); |
| | 0 | 623 | | var favoritPartsRostovBlock = await GetFavoritPartsRostovBasketAppendixAsync(orderIds); |
| | 0 | 624 | | if (!string.IsNullOrWhiteSpace(favoritPartsIstraBlock)) blocks.Add(favoritPartsIstraBlock); |
| | 0 | 625 | | if (!string.IsNullOrWhiteSpace(favoritPartsRostovBlock)) blocks.Add(favoritPartsRostovBlock); |
| | 0 | 626 | | AppendDirectCheckoutBlock(blocks, ProfitLigaDirectCheckoutResultKey, "Заказ Профит-Лиги оформлен."); |
| | 0 | 627 | | var profitLigaBlock = await GetProfitLigaBasketAppendixAsync(profitLigaOrderIds, |
| | 0 | 628 | | orderIds.Except(profitLigaOrderIds).ToHashSet()); |
| | 0 | 629 | | if (!string.IsNullOrWhiteSpace(profitLigaBlock)) blocks.Add(profitLigaBlock); |
| | 0 | 630 | | var nikeiBlock = await GetNikeiBasketAppendixAsync(orderIds); |
| | 0 | 631 | | if (!string.IsNullOrWhiteSpace(nikeiBlock)) blocks.Add(nikeiBlock); |
| | 0 | 632 | | var mikadoBlock = await GetMikadoBasketAppendixAsync(orderIds); |
| | 0 | 633 | | var zzapBlock = await GetZZapBasketAppendixAsync(orderIds); |
| | 0 | 634 | | if (!string.IsNullOrWhiteSpace(zzapBlock)) blocks.Add(zzapBlock); |
| | 0 | 635 | | if (!string.IsNullOrWhiteSpace(mikadoBlock)) blocks.Add(mikadoBlock); |
| | 0 | 636 | | var moskvorechieBlock = await GetMoskvorechieBasketAppendixAsync(orderIds); |
| | 0 | 637 | | if (!string.IsNullOrWhiteSpace(moskvorechieBlock)) blocks.Add(moskvorechieBlock); |
| | 0 | 638 | | var motexBlock = await GetMotexBasketAppendixAsync(orderIds); |
| | 0 | 639 | | if (!string.IsNullOrWhiteSpace(motexBlock)) blocks.Add(motexBlock); |
| | 0 | 640 | | var shateMBlock = await GetShateMBasketAppendixAsync(orderIds); |
| | 0 | 641 | | if (!string.IsNullOrWhiteSpace(shateMBlock)) blocks.Add(shateMBlock); |
| | 0 | 642 | | var shateMDirectBlock = await GetShateMDirectCheckoutAppendixAsync(orderIds); |
| | 0 | 643 | | if (!string.IsNullOrWhiteSpace(shateMDirectBlock)) blocks.Add(shateMDirectBlock); |
| | | 644 | | |
| | 0 | 645 | | var verificationBlock = await GetSupplierVerificationBlockAsync(orderIds); |
| | 0 | 646 | | if (!string.IsNullOrWhiteSpace(verificationBlock)) blocks.Add(verificationBlock); |
| | | 647 | | |
| | 0 | 648 | | return blocks.Count == 0 ? null : string.Join("<br/>", blocks); |
| | 0 | 649 | | } |
| | | 650 | | |
| | | 651 | | /// <summary> |
| | | 652 | | /// Независимая проверка у API-поставщиков, что заказы реально созданы (только сигнал: транзакция уже проведена). |
| | | 653 | | /// Пишет системную запись в историю заказа и возвращает HTML-блок для карточки результата. |
| | | 654 | | /// </summary> |
| | | 655 | | private async Task<string?> GetSupplierVerificationBlockAsync(IReadOnlyCollection<int> orderIds) |
| | | 656 | | { |
| | 0 | 657 | | var queueJsons = Request.Form |
| | | 658 | | .Where(x => x.Key.EndsWith("QueueJson", StringComparison.OrdinalIgnoreCase)) |
| | | 659 | | .SelectMany(x => x.Value.AsEnumerable()); |
| | 0 | 660 | | var required = SupplierQueueOrderIds.Extract(queueJsons).Where(orderIds.Contains).ToList(); |
| | 0 | 661 | | if (required.Count == 0) return null; |
| | | 662 | | |
| | 0 | 663 | | await ReportStageAsync("supplier-verify", "Проверяем заказы у поставщиков", $"позиций: {required.Count}"); |
| | 0 | 664 | | var verification = HttpContext.RequestServices.GetRequiredService<ISupplierOrderVerificationService>(); |
| | 0 | 665 | | var summary = await verification.VerifyAsync(orderIds, required, ct: HttpContext.RequestAborted); |
| | 0 | 666 | | return SupplierVerificationDto.ToHtmlBlock(summary); |
| | 0 | 667 | | } |
| | | 668 | | |
| | | 669 | | private void AppendDirectCheckoutBlock(List<string> blocks, string key, string title) |
| | | 670 | | { |
| | 0 | 671 | | if (!HttpContext.Items.TryGetValue(key, out var result)) return; |
| | 0 | 672 | | var messages = result switch |
| | 0 | 673 | | { |
| | 0 | 674 | | MlAutoDirectCheckoutResult value => value.Messages, |
| | 0 | 675 | | MlAutoRuDirectCheckoutResult value => value.Messages, |
| | 0 | 676 | | AvdDirectCheckoutResult value => value.Messages, |
| | 0 | 677 | | ForumAutoDirectCheckoutResult value => value.Messages, |
| | 0 | 678 | | FavoritPartsDirectCheckoutResult value => value.Messages, |
| | 0 | 679 | | ProfitLigaDirectCheckoutResult value => value.Messages, |
| | 0 | 680 | | MoskvorechieIstraCartResult value => value.Messages, |
| | 0 | 681 | | MotexCartResult value => value.Messages, |
| | 0 | 682 | | ZZapCartResult value => value.Messages, |
| | 0 | 683 | | JapartsCartResult value => value.Messages, |
| | 0 | 684 | | BergCartResult value => value.Messages, |
| | 0 | 685 | | _ => null |
| | 0 | 686 | | }; |
| | 0 | 687 | | if (messages is null) return; |
| | 0 | 688 | | var hasPostError = messages.Any(m => m.Contains("не проведён", StringComparison.OrdinalIgnoreCase) |
| | 0 | 689 | | || m.Contains("не проведен", StringComparison.OrdinalIgnoreCase)); |
| | 0 | 690 | | var alertClass = hasPostError ? "alert alert-warning" : "alert alert-success"; |
| | 0 | 691 | | var headerTitle = hasPostError ? $"{title} (внимание: есть непроведённые заказы)" : title; |
| | 0 | 692 | | var lines = string.Join(string.Empty, messages.Select(message => $"<li>{System.Net.WebUtility.HtmlEncode(message |
| | 0 | 693 | | blocks.Add($"<div class='{alertClass} mb-0'><strong>{headerTitle}</strong><ul class='mb-0'>{lines}</ul></div>"); |
| | 0 | 694 | | } |
| | | 695 | | |
| | | 696 | | private async Task<string?> GetMlAutoBasketAppendixAsync(List<int> orderIds) |
| | | 697 | | { |
| | 0 | 698 | | var raw = Request.Form["MlAutoBasketQueueJson"].ToString(); |
| | 0 | 699 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 700 | | List<MlAutoCartQueueItem> queue; |
| | 0 | 701 | | try { queue = ParseMlAutoBasketQueue(raw); } |
| | 0 | 702 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина ML-Auto не обработана.</st |
| | 0 | 703 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 704 | | if (selected.Count == 0) return null; |
| | | 705 | | if (selected.GroupBy(x => x.OrderId).Any(x => x.Count() > 1)) return "<div class='alert alert-warning mb-0'><str |
| | 0 | 706 | | return (await _mlAutoCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext.RequestAbort |
| | 0 | 707 | | } |
| | | 708 | | |
| | | 709 | | /// <summary>Браузер может сериализовать цену и количество ML-Auto как число, строку либо null.</summary> |
| | | 710 | | private static List<MlAutoCartQueueItem> ParseMlAutoBasketQueue(string raw) |
| | | 711 | | { |
| | 0 | 712 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 713 | | if (document.RootElement.ValueKind != JsonValueKind.Array) |
| | 0 | 714 | | throw new JsonException("ML-Auto basket queue must be an array."); |
| | | 715 | | |
| | 0 | 716 | | return document.RootElement.EnumerateArray() |
| | 0 | 717 | | .Where(item => item.ValueKind == JsonValueKind.Object) |
| | 0 | 718 | | .Select(item => new MlAutoCartQueueItem( |
| | 0 | 719 | | ReadInt(item, "orderId"), ReadString(item, "pin"), ReadString(item, "brand"), |
| | 0 | 720 | | ReadNullableString(item, "storageCode"), ReadNullableString(item, "warehouseName"), |
| | 0 | 721 | | ReadNullableString(item, "productName"), ReadNullableString(item, "shipmentNumber"), |
| | 0 | 722 | | ReadNullableString(item, "clientName"), ReadDecimal(item, "quantity"), |
| | 0 | 723 | | ReadDecimal(item, "minimumQuantity"), ReadNullableDecimal(item, "sourcePrice"), ReadNullableString(item, |
| | 0 | 724 | | ReadNullableString(item, "deliveryAtUtc"), ReadNullableString(item, "zasylWarehouseName"), |
| | 0 | 725 | | ReadNullableInt(item, "deliveryDaysMin"), ReadNullableInt(item, "deliveryDaysMax"), ReadNullableString(i |
| | 0 | 726 | | .ToList(); |
| | 0 | 727 | | } |
| | | 728 | | |
| | | 729 | | private async Task<string?> GetMlAutoRuBasketAppendixAsync(List<int> orderIds) |
| | | 730 | | { |
| | 0 | 731 | | var raw = Request.Form["MlAutoRuBasketQueueJson"].ToString(); |
| | 0 | 732 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 733 | | List<MlAutoRuCartQueueItem> queue; |
| | | 734 | | try { queue = ParseMlAutoBasketQueue(raw).Select(x => new MlAutoRuCartQueueItem(x.OrderId, x.Pin, x.Brand, x.Sto |
| | 0 | 735 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина ML-Auto RU не обработана.< |
| | 0 | 736 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 737 | | if (selected.Count == 0) return null; |
| | | 738 | | if (selected.GroupBy(x => x.OrderId).Any(x => x.Count() > 1)) return "<div class='alert alert-warning mb-0'><str |
| | 0 | 739 | | return (await _mlAutoRuCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext.RequestAbo |
| | 0 | 740 | | } |
| | | 741 | | |
| | | 742 | | /// <summary>Размещение отложенной корзины АВД — последний шаг после проведения, позиции берём только из уже успешно |
| | | 743 | | private async Task<string?> GetJapartsBasketAppendixAsync(List<int> orderIds) |
| | | 744 | | { |
| | 0 | 745 | | var rawValues = Request.Form["JapartsBasketQueueJson"]; |
| | 0 | 746 | | if (rawValues.Count == 0 || rawValues.All(string.IsNullOrWhiteSpace)) return null; |
| | | 747 | | List<JapartsCartQueueItem> queue; |
| | | 748 | | int skipped; |
| | 0 | 749 | | try { (queue, skipped) = ParseJapartsBasketQueue(rawValues); } |
| | 0 | 750 | | catch (JsonException exception) |
| | | 751 | | { |
| | 0 | 752 | | return "<div class='alert alert-warning mb-0'><strong>Корзина Japarts не обработана.</strong> " + |
| | 0 | 753 | | $"Не удалось прочитать очередь выбранных позиций: {System.Net.WebUtility.HtmlEncode(exception.Message |
| | | 754 | | } |
| | 0 | 755 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 756 | | if (selected.Count == 0) |
| | 0 | 757 | | return skipped == 0 |
| | 0 | 758 | | ? null |
| | 0 | 759 | | : $"<div class='alert alert-warning mb-0'><strong>Корзина Japarts не обработана.</strong> Позиций в очер |
| | | 760 | | if (selected.GroupBy(x => x.OrderId).Any(x => x.Count() > 1)) |
| | 0 | 761 | | return "<div class='alert alert-warning mb-0'><strong>Корзина Japarts не обработана.</strong> Для одного зак |
| | | 762 | | |
| | 0 | 763 | | var result = await _japartsCartService.PlaceQueuedItemsAsync(selected, User.Identity?.Name ?? "unknown", |
| | 0 | 764 | | HttpContext.RequestAborted); |
| | | 765 | | var lines = string.Join(string.Empty, result.Messages.Select(x => $"<li>{System.Net.WebUtility.HtmlEncode(x)}</l |
| | 0 | 766 | | return $"<div class='alert alert-{(result.IsSuccess ? "info" : "warning")} mb-0'><strong>Japarts.</strong><ul cl |
| | 0 | 767 | | "<div class='mt-2'>Заказ у поставщика не создан: оформите его вручную на странице " + |
| | 0 | 768 | | $"<a href='{Url.Action("Index", "SupplierCart", new { area = "Studio2", provider = "japarts", supplierTab |
| | 0 | 769 | | } |
| | | 770 | | |
| | | 771 | | /// <summary>Отложенная корзина Berg: у поставщика корзины нет, поэтому позиции ложатся |
| | | 772 | | /// в локальную таблицу, а заказ оформляется отдельным действием на странице корзины.</summary> |
| | | 773 | | private async Task<string?> GetBergBasketAppendixAsync(List<int> orderIds) |
| | | 774 | | { |
| | 0 | 775 | | var rawValues = Request.Form["BergBasketQueueJson"]; |
| | 0 | 776 | | if (rawValues.Count == 0 || rawValues.All(string.IsNullOrWhiteSpace)) return null; |
| | | 777 | | List<BergCartQueueItem> queue; |
| | | 778 | | int skipped; |
| | 0 | 779 | | try { (queue, skipped) = ParseBergBasketQueue(rawValues); } |
| | 0 | 780 | | catch (JsonException exception) |
| | | 781 | | { |
| | 0 | 782 | | return "<div class='alert alert-warning mb-0'><strong>Корзина Berg не обработана.</strong> " + |
| | 0 | 783 | | $"Не удалось прочитать очередь выбранных позиций: {System.Net.WebUtility.HtmlEncode(exception.Message |
| | | 784 | | } |
| | 0 | 785 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 786 | | if (selected.Count == 0) |
| | 0 | 787 | | return skipped == 0 |
| | 0 | 788 | | ? null |
| | 0 | 789 | | : $"<div class='alert alert-warning mb-0'><strong>Корзина Berg не обработана.</strong> Позиций в очереди |
| | | 790 | | if (selected.GroupBy(x => x.OrderId).Any(x => x.Count() > 1)) |
| | 0 | 791 | | return "<div class='alert alert-warning mb-0'><strong>Корзина Berg не обработана.</strong> Для одного заказа |
| | | 792 | | |
| | 0 | 793 | | var result = await _bergCartService.PlaceQueuedItemsAsync(selected, User.Identity?.Name ?? "unknown", |
| | 0 | 794 | | HttpContext.RequestAborted); |
| | | 795 | | var lines = string.Join(string.Empty, result.Messages.Select(x => $"<li>{System.Net.WebUtility.HtmlEncode(x)}</l |
| | 0 | 796 | | return $"<div class='alert alert-{(result.IsSuccess ? "info" : "warning")} mb-0'><strong>Берг.</strong><ul class |
| | 0 | 797 | | "<div class='mt-2'>Заказ у поставщика не создан: оформите его на странице " + |
| | 0 | 798 | | $"<a href='{Url.Action("Index", "SupplierCart", new { area = "Studio2", provider = "berg", supplierTab = |
| | 0 | 799 | | } |
| | | 800 | | |
| | | 801 | | private static (List<BergCartQueueItem> Items, int Skipped) ParseBergBasketQueue(IEnumerable<string> rawValues) |
| | | 802 | | { |
| | 0 | 803 | | var queue = new List<BergCartQueueItem>(); |
| | 0 | 804 | | var skipped = 0; |
| | 0 | 805 | | foreach (var raw in rawValues.Where(value => !string.IsNullOrWhiteSpace(value))) |
| | | 806 | | { |
| | 0 | 807 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 808 | | var elements = document.RootElement.ValueKind switch |
| | 0 | 809 | | { |
| | 0 | 810 | | JsonValueKind.Array => document.RootElement.EnumerateArray().ToArray(), |
| | 0 | 811 | | JsonValueKind.Object => [document.RootElement], |
| | 0 | 812 | | _ => throw new JsonException("очередь должна быть массивом или объектом.") |
| | 0 | 813 | | }; |
| | 0 | 814 | | foreach (var element in elements) |
| | | 815 | | { |
| | 0 | 816 | | var item = ReadBergQueueItem(element); |
| | 0 | 817 | | if (item is null) skipped++; |
| | 0 | 818 | | else queue.Add(item); |
| | | 819 | | } |
| | | 820 | | } |
| | | 821 | | |
| | 0 | 822 | | return (queue.Distinct().ToList(), skipped); |
| | | 823 | | } |
| | | 824 | | |
| | | 825 | | /// <summary> |
| | | 826 | | /// Предложение Berg адресуется парой resource_id + warehouse_id. Фронтенд шлёт их отдельными |
| | | 827 | | /// полями, но itemId вида «resource|warehouse|price|days» остаётся запасным источником: |
| | | 828 | | /// он приходит из проценки и переживает обновление страницы. |
| | | 829 | | /// </summary> |
| | | 830 | | private static BergCartQueueItem? ReadBergQueueItem(JsonElement element) |
| | | 831 | | { |
| | 0 | 832 | | if (element.ValueKind != JsonValueKind.Object) return null; |
| | 0 | 833 | | var orderId = ReadJapartsQueueDecimal(element, "orderId"); |
| | 0 | 834 | | if (orderId is null or <= 0 || orderId != decimal.Truncate(orderId.Value)) return null; |
| | | 835 | | |
| | 0 | 836 | | var resourceId = (long?)ReadJapartsQueueDecimal(element, "resourceId"); |
| | 0 | 837 | | var warehouseId = (long?)ReadJapartsQueueDecimal(element, "warehouseId"); |
| | 0 | 838 | | if (resourceId is null or <= 0 || warehouseId is null or <= 0) |
| | | 839 | | { |
| | 0 | 840 | | var parts = (ReadJapartsQueueString(element, "itemId") ?? string.Empty).Split('|'); |
| | 0 | 841 | | if (parts.Length < 2 || !long.TryParse(parts[0], out var parsedResource) || |
| | 0 | 842 | | !long.TryParse(parts[1], out var parsedWarehouse)) |
| | 0 | 843 | | return null; |
| | 0 | 844 | | resourceId = parsedResource; |
| | 0 | 845 | | warehouseId = parsedWarehouse; |
| | | 846 | | } |
| | | 847 | | |
| | 0 | 848 | | var quantity = ReadJapartsQueueDecimal(element, "quantity"); |
| | 0 | 849 | | var multi = (int?)ReadJapartsQueueDecimal(element, "minQuantity"); |
| | 0 | 850 | | return new BergCartQueueItem((int)orderId.Value, resourceId.Value, warehouseId.Value, |
| | 0 | 851 | | quantity is null or <= 0 ? 1m : quantity.Value, |
| | 0 | 852 | | ReadJapartsQueueDecimal(element, "price"), ReadJapartsQueueString(element, "article"), |
| | 0 | 853 | | ReadJapartsQueueString(element, "brand"), ReadJapartsQueueString(element, "productName"), |
| | 0 | 854 | | ReadJapartsQueueString(element, "shipmentNumber"), ReadJapartsQueueString(element, "clientName"), |
| | 0 | 855 | | multi is null or <= 0 ? 1 : multi.Value, |
| | 0 | 856 | | (int?)ReadJapartsQueueDecimal(element, "deliveryDaysMin"), (int?)ReadJapartsQueueDecimal(element, "deliveryD |
| | 0 | 857 | | ReadJapartsQueueString(element, "direction")); |
| | | 858 | | } |
| | | 859 | | |
| | | 860 | | /// <summary>Форма может прислать несколько значений скрытого поля; каждое значение разбираем отдельно, не склеивая |
| | | 861 | | /// через запятую. Числа читаем терпимо — браузер отдаёт их то числом, то строкой, а NaN сериализует в null, — иначе |
| | | 862 | | /// одна кривая позиция роняла разбор всей очереди. Второе значение кортежа — сколько позиций пропущено как неполные |
| | | 863 | | private static (List<JapartsCartQueueItem> Items, int Skipped) ParseJapartsBasketQueue(IEnumerable<string> rawValues |
| | | 864 | | { |
| | 0 | 865 | | var queue = new List<JapartsCartQueueItem>(); |
| | 0 | 866 | | var skipped = 0; |
| | 0 | 867 | | foreach (var raw in rawValues.Where(value => !string.IsNullOrWhiteSpace(value))) |
| | | 868 | | { |
| | 0 | 869 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 870 | | var elements = document.RootElement.ValueKind switch |
| | 0 | 871 | | { |
| | 0 | 872 | | JsonValueKind.Array => document.RootElement.EnumerateArray().ToArray(), |
| | 0 | 873 | | JsonValueKind.Object => [document.RootElement], |
| | 0 | 874 | | _ => throw new JsonException("очередь должна быть массивом или объектом.") |
| | 0 | 875 | | }; |
| | 0 | 876 | | foreach (var element in elements) |
| | | 877 | | { |
| | 0 | 878 | | var item = ReadJapartsQueueItem(element); |
| | 0 | 879 | | if (item is null) skipped++; |
| | 0 | 880 | | else queue.Add(item); |
| | | 881 | | } |
| | | 882 | | } |
| | | 883 | | |
| | 0 | 884 | | return (queue.Distinct().ToList(), skipped); |
| | | 885 | | } |
| | | 886 | | |
| | | 887 | | private static JapartsCartQueueItem? ReadJapartsQueueItem(JsonElement element) |
| | | 888 | | { |
| | 0 | 889 | | if (element.ValueKind != JsonValueKind.Object) return null; |
| | 0 | 890 | | var orderId = ReadJapartsQueueDecimal(element, "orderId"); |
| | 0 | 891 | | var priceId = ReadJapartsQueueString(element, "priceId") |
| | 0 | 892 | | ?? ReadJapartsQueueString(element, "itemHash") |
| | 0 | 893 | | ?? ReadJapartsQueueString(element, "itemId"); |
| | 0 | 894 | | if (orderId is null or <= 0 || orderId != decimal.Truncate(orderId.Value) || string.IsNullOrWhiteSpace(priceId)) |
| | 0 | 895 | | return null; |
| | 0 | 896 | | var quantity = ReadJapartsQueueDecimal(element, "quantity"); |
| | 0 | 897 | | return new JapartsCartQueueItem((int)orderId.Value, priceId!, quantity is null or <= 0 ? 1m : quantity.Value, |
| | 0 | 898 | | ReadJapartsQueueDecimal(element, "price"), ReadJapartsQueueString(element, "article"), |
| | 0 | 899 | | ReadJapartsQueueString(element, "productName"), ReadJapartsQueueString(element, "shipmentNumber"), |
| | 0 | 900 | | ReadJapartsQueueString(element, "clientName"), |
| | 0 | 901 | | (int?)ReadJapartsQueueDecimal(element, "deliveryDaysMin"), (int?)ReadJapartsQueueDecimal(element, "deliveryD |
| | 0 | 902 | | ReadJapartsQueueString(element, "direction")); |
| | | 903 | | } |
| | | 904 | | |
| | | 905 | | private static string? ReadJapartsQueueString(JsonElement element, string name) |
| | | 906 | | { |
| | 0 | 907 | | if (!element.TryGetProperty(name, out var value)) return null; |
| | 0 | 908 | | var text = value.ValueKind switch |
| | 0 | 909 | | { |
| | 0 | 910 | | JsonValueKind.String => value.GetString(), |
| | 0 | 911 | | JsonValueKind.Number => value.GetRawText(), |
| | 0 | 912 | | _ => null |
| | 0 | 913 | | }; |
| | 0 | 914 | | return string.IsNullOrWhiteSpace(text) ? null : text.Trim(); |
| | | 915 | | } |
| | | 916 | | |
| | | 917 | | private static decimal? ReadJapartsQueueDecimal(JsonElement element, string name) |
| | | 918 | | { |
| | 0 | 919 | | if (!element.TryGetProperty(name, out var value)) return null; |
| | 0 | 920 | | return value.ValueKind switch |
| | 0 | 921 | | { |
| | 0 | 922 | | JsonValueKind.Number when value.TryGetDecimal(out var number) => number, |
| | 0 | 923 | | JsonValueKind.String when decimal.TryParse(value.GetString(), NumberStyles.Any, |
| | 0 | 924 | | CultureInfo.InvariantCulture, out var parsed) => parsed, |
| | 0 | 925 | | _ => null |
| | 0 | 926 | | }; |
| | | 927 | | } |
| | | 928 | | |
| | | 929 | | private async Task<string?> GetAvdBasketAppendixAsync(List<int> orderIds) |
| | | 930 | | { |
| | 0 | 931 | | var queueJson = Request.Form["AvdBasketQueueJson"].ToString(); |
| | 0 | 932 | | if (string.IsNullOrWhiteSpace(queueJson)) |
| | 0 | 933 | | return null; |
| | | 934 | | |
| | | 935 | | List<AvdBasketQueueItem>? queue; |
| | | 936 | | try |
| | | 937 | | { |
| | 0 | 938 | | queue = JsonSerializer.Deserialize<List<AvdBasketQueueItem>>(queueJson, |
| | 0 | 939 | | new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); |
| | 0 | 940 | | } |
| | 0 | 941 | | catch (JsonException) |
| | | 942 | | { |
| | 0 | 943 | | return "<div class='alert alert-warning mb-0'><strong>Корзина АВД не обработана.</strong> " + |
| | 0 | 944 | | "Не удалось прочитать очередь выбранных позиций.</div>"; |
| | | 945 | | } |
| | | 946 | | |
| | 0 | 947 | | var selectedItems = (queue ?? new List<AvdBasketQueueItem>()) |
| | 0 | 948 | | .Where(x => orderIds.Contains(x.OrderId)) |
| | 0 | 949 | | .ToList(); |
| | 0 | 950 | | if (selectedItems.Count == 0) |
| | 0 | 951 | | return null; |
| | | 952 | | |
| | | 953 | | if (selectedItems.Any(x => string.IsNullOrWhiteSpace(x.ItemHash) || x.Quantity <= 0)) |
| | | 954 | | { |
| | 0 | 955 | | return "<div class='alert alert-warning mb-0'><strong>Корзина АВД не обработана.</strong> " + |
| | 0 | 956 | | "У выбранной позиции отсутствует hash предложения или указано некорректное количество.</div>"; |
| | | 957 | | } |
| | | 958 | | |
| | | 959 | | if (selectedItems.GroupBy(x => x.OrderId).Any(group => group.Count() > 1)) |
| | | 960 | | { |
| | 0 | 961 | | return "<div class='alert alert-warning mb-0'><strong>Корзина АВД не обработана.</strong> " + |
| | 0 | 962 | | "Для одного заказа выбрано несколько предложений.</div>"; |
| | | 963 | | } |
| | | 964 | | |
| | 0 | 965 | | var changedBy = User?.Identity?.Name ?? "unknown"; |
| | 0 | 966 | | var result = await _avdBasketPlacementService.PlaceQueuedItemsAsync(selectedItems, changedBy, HttpContext.Reques |
| | 0 | 967 | | return result.ToHtmlBlock(); |
| | 0 | 968 | | } |
| | | 969 | | |
| | | 970 | | private async Task<string?> GetArmtekBasketAppendixAsync(List<int> orderIds) |
| | | 971 | | { |
| | 0 | 972 | | var raw = Request.Form["ArmtekBasketQueueJson"].ToString(); |
| | 0 | 973 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 974 | | List<ArmtekCartQueueItem> queue; |
| | 0 | 975 | | try { queue = ParseArmtekBasketQueue(raw); } |
| | 0 | 976 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина Армтек не обработана.</str |
| | 0 | 977 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 978 | | if (selected.Count == 0) return null; |
| | | 979 | | if (selected.GroupBy(x => new { x.ProviderKey, x.OrderId }).Any(x => x.Count() > 1)) |
| | 0 | 980 | | return "<div class='alert alert-warning mb-0'><strong>Корзина Армтек не обработана.</strong> Для заказа выбр |
| | 0 | 981 | | var result = await _armtekCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext.Request |
| | 0 | 982 | | return result.ToHtmlBlock(); |
| | 0 | 983 | | } |
| | | 984 | | |
| | | 985 | | /// <summary>Браузер передаёт снимок предложения: API может отдать цену/количество числом либо строкой.</summary> |
| | | 986 | | private static List<ArmtekCartQueueItem> ParseArmtekBasketQueue(string raw) |
| | | 987 | | { |
| | 0 | 988 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 989 | | if (document.RootElement.ValueKind != JsonValueKind.Array) |
| | 0 | 990 | | throw new JsonException("Armtek basket queue must be an array."); |
| | | 991 | | |
| | 0 | 992 | | return document.RootElement.EnumerateArray() |
| | 0 | 993 | | .Where(item => item.ValueKind == JsonValueKind.Object) |
| | 0 | 994 | | .Select(item => new ArmtekCartQueueItem( |
| | 0 | 995 | | ReadInt(item, "orderId"), ReadString(item, "providerKey"), ReadString(item, "artId"), |
| | 0 | 996 | | ReadString(item, "pin"), ReadString(item, "brand"), ReadNullableString(item, "keyzak"), |
| | 0 | 997 | | ReadNullableString(item, "warehouseName"), ReadNullableString(item, "partnerWarehouseCode"), |
| | 0 | 998 | | ReadNullableString(item, "productName"), ReadNullableString(item, "shipmentNumber"), ReadNullableString( |
| | 0 | 999 | | ReadNullableDecimal(item, "sourcePrice"), ReadNullableString(item, "currencyCode"), |
| | 0 | 1000 | | ReadNullableString(item, "deliveryAtUtc"), ReadNullableString(item, "zasylWarehouseName"), |
| | 0 | 1001 | | ReadNullableInt(item, "deliveryDaysMin"), ReadNullableInt(item, "deliveryDaysMax"), ReadNullableString(i |
| | 0 | 1002 | | .ToList(); |
| | 0 | 1003 | | } |
| | | 1004 | | |
| | | 1005 | | private async Task<string?> GetForumAutoBasketAppendixAsync(List<int> orderIds) |
| | | 1006 | | { |
| | 0 | 1007 | | var raw = Request.Form["ForumAutoBasketQueueJson"].ToString(); |
| | 0 | 1008 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 1009 | | List<ForumAutoCartQueueItem> queue; |
| | 0 | 1010 | | try { queue = ParseForumAutoBasketQueue(raw); } |
| | 0 | 1011 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина Форум-Авто не обработана.< |
| | 0 | 1012 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1013 | | if (selected.Count == 0) return null; |
| | | 1014 | | if (selected.GroupBy(x => new { x.ProviderKey, x.OrderId }).Any(x => x.Count() > 1)) |
| | 0 | 1015 | | return "<div class='alert alert-warning mb-0'><strong>Корзина Форум-Авто не обработана.</strong> Для заказа |
| | 0 | 1016 | | var result = await _forumAutoCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext.Requ |
| | 0 | 1017 | | return result.ToHtmlBlock(); |
| | 0 | 1018 | | } |
| | | 1019 | | |
| | | 1020 | | /// <summary>Браузер передаёт снимок проценки: цена и количество могут прийти числом либо строкой.</summary> |
| | | 1021 | | private static List<ForumAutoCartQueueItem> ParseForumAutoBasketQueue(string raw) |
| | | 1022 | | { |
| | 0 | 1023 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 1024 | | if (document.RootElement.ValueKind != JsonValueKind.Array) |
| | 0 | 1025 | | throw new JsonException("Forum-Auto basket queue must be an array."); |
| | | 1026 | | |
| | 0 | 1027 | | return document.RootElement.EnumerateArray() |
| | 0 | 1028 | | .Where(item => item.ValueKind == JsonValueKind.Object) |
| | 0 | 1029 | | .Select(item => new ForumAutoCartQueueItem( |
| | 0 | 1030 | | ReadInt(item, "orderId"), ReadString(item, "providerKey"), ReadString(item, "tid"), |
| | 0 | 1031 | | ReadString(item, "article"), ReadString(item, "brand"), ReadNullableString(item, "productName"), |
| | 0 | 1032 | | ReadNullableString(item, "shipmentNumber"), ReadNullableString(item, "clientName"), ReadDecimal(item, "q |
| | 0 | 1033 | | ReadNullableDecimal(item, "sourcePrice"), ReadNullableString(item, "currencyCode"), |
| | 0 | 1034 | | ReadNullableString(item, "deliveryAtUtc"), |
| | 0 | 1035 | | ReadNullableInt(item, "deliveryDaysMin"), ReadNullableInt(item, "deliveryDaysMax"), ReadNullableString(i |
| | 0 | 1036 | | .ToList(); |
| | 0 | 1037 | | } |
| | | 1038 | | |
| | | 1039 | | private async Task<string?> GetFavoritPartsBasketAppendixAsync(List<int> orderIds) |
| | | 1040 | | { |
| | 0 | 1041 | | var raw = Request.Form["FavoritPartsBasketQueueJson"].ToString(); |
| | 0 | 1042 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 1043 | | List<FavoritPartsQueueItem> queue; |
| | 0 | 1044 | | try { queue = ParseFavoritPartsBasketQueue(raw); } |
| | 0 | 1045 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина FavoritParts не обработана |
| | 0 | 1046 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1047 | | if (selected.Count == 0) return null; |
| | 0 | 1048 | | var result = await _favoritPartsCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext.R |
| | 0 | 1049 | | return result.ToHtmlBlock(); |
| | 0 | 1050 | | } |
| | | 1051 | | |
| | | 1052 | | private async Task<string?> GetFavoritPartsIstraBasketAppendixAsync(List<int> orderIds) |
| | | 1053 | | { |
| | 0 | 1054 | | var raw = Request.Form["FavoritPartsIstraBasketQueueJson"].ToString(); |
| | 0 | 1055 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 1056 | | List<FavoritPartsQueueItem> queue; |
| | 0 | 1057 | | try { queue = ParseFavoritPartsBasketQueue(raw); } |
| | 0 | 1058 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина Фаворит Истра не обработан |
| | 0 | 1059 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1060 | | if (selected.Count == 0) return null; |
| | 0 | 1061 | | return (await _favoritPartsIstraCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext.R |
| | 0 | 1062 | | } |
| | | 1063 | | |
| | | 1064 | | private async Task<string?> GetFavoritPartsRostovBasketAppendixAsync(List<int> orderIds) |
| | | 1065 | | { |
| | 0 | 1066 | | var raw = Request.Form["FavoritPartsRostovBasketQueueJson"].ToString(); |
| | 0 | 1067 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 1068 | | List<FavoritPartsQueueItem> queue; |
| | 0 | 1069 | | try { queue = ParseFavoritPartsBasketQueue(raw); } |
| | 0 | 1070 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина Фаворит Ростов не обработа |
| | 0 | 1071 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1072 | | if (selected.Count == 0) return null; |
| | 0 | 1073 | | return (await _favoritPartsRostovCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext. |
| | 0 | 1074 | | } |
| | | 1075 | | |
| | | 1076 | | private async Task<string?> GetProfitLigaBasketAppendixAsync(IReadOnlyCollection<int> orderIds, |
| | | 1077 | | IReadOnlySet<int> ozonFailedOrderIds) |
| | | 1078 | | { |
| | 0 | 1079 | | var raw = Request.Form["ProfitLigaBasketQueueJson"].ToString(); |
| | 0 | 1080 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 1081 | | List<ProfitLigaQueueItem> queue; |
| | 0 | 1082 | | try { queue = ParseProfitLigaBasketQueue(raw); } |
| | 0 | 1083 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина Профит-Лиги не обработана. |
| | 0 | 1084 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1085 | | var blocked = queue.Where(x => ozonFailedOrderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1086 | | var blocks = new List<string>(); |
| | 0 | 1087 | | if (blocked.Count > 0) |
| | | 1088 | | { |
| | | 1089 | | var shipments = string.Join(", ", blocked.Select(x => x.ShipmentNumber ?? $"ID {x.OrderId}").Distinct()); |
| | 0 | 1090 | | blocks.Add("<div class='alert alert-warning mb-0'><strong>Корзина Профит-Лиги: позиции не добавлены.</strong |
| | 0 | 1091 | | $"Отгрузка Ozon завершилась ошибкой: {System.Net.WebUtility.HtmlEncode(shipments)}.</div>"); |
| | | 1092 | | } |
| | 0 | 1093 | | if (selected.Count == 0) return blocks.Count == 0 ? null : string.Join("<br/>", blocks); |
| | | 1094 | | if (selected.GroupBy(x => x.OrderId).Any(x => x.Count() > 1)) |
| | 0 | 1095 | | return "<div class='alert alert-warning mb-0'><strong>Корзина Профит-Лиги не обработана.</strong> Для заказа |
| | 0 | 1096 | | blocks.Add((await _profitLigaCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext.Requ |
| | | 1097 | | return string.Join("<br/>", blocks.Where(x => !string.IsNullOrWhiteSpace(x))); |
| | 0 | 1098 | | } |
| | | 1099 | | |
| | | 1100 | | private async Task<string?> GetNikeiBasketAppendixAsync(List<int> orderIds) |
| | | 1101 | | { |
| | 0 | 1102 | | var raw = Request.Form["NikeiBasketQueueJson"].ToString(); |
| | 0 | 1103 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 1104 | | List<NikeiCartQueueItem> queue; |
| | 0 | 1105 | | try { queue = ParseNikeiBasketQueue(raw); } |
| | 0 | 1106 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина Nikei не обработана.</stro |
| | 0 | 1107 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1108 | | if (selected.Count == 0) return null; |
| | | 1109 | | if (selected.GroupBy(x => x.OrderId).Any(x => x.Count() > 1)) |
| | 0 | 1110 | | return "<div class='alert alert-warning mb-0'><strong>Корзина Nikei не обработана.</strong> Для заказа выбра |
| | 0 | 1111 | | return (await _nikeiCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext.RequestAborte |
| | 0 | 1112 | | } |
| | | 1113 | | |
| | | 1114 | | private async Task<string?> GetMoskvorechieBasketAppendixAsync(List<int> orderIds) |
| | | 1115 | | { |
| | 0 | 1116 | | var raw = Request.Form["MoskvorechieBasketQueueJson"].ToString(); |
| | 0 | 1117 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 1118 | | List<MoskvorechieIstraQueueItem> queue; |
| | 0 | 1119 | | try { queue = ParseMoskvorechieBasketQueue(raw); } |
| | 0 | 1120 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина Москворечья не обработана. |
| | 0 | 1121 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1122 | | if (selected.Count == 0) return null; |
| | 0 | 1123 | | return (await _moskvorechieIstraCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext.R |
| | 0 | 1124 | | } |
| | | 1125 | | |
| | | 1126 | | private async Task<string?> GetMikadoBasketAppendixAsync(List<int> orderIds) |
| | | 1127 | | { |
| | 0 | 1128 | | var raw = Request.Form["MikadoBasketQueueJson"].ToString(); |
| | 0 | 1129 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 1130 | | try |
| | | 1131 | | { |
| | 0 | 1132 | | using var document = JsonDocument.Parse(raw); |
| | | 1133 | | var queue = document.RootElement.EnumerateArray().Where(x => x.ValueKind == JsonValueKind.Object) |
| | | 1134 | | .Select(x => new MikadoQueueItem(ReadInt(x, "orderId"), ReadString(x, "orderCode"), ReadInt(x, "stockId" |
| | 0 | 1135 | | if (queue.Count == 0) return null; |
| | 0 | 1136 | | var result = await _mikadoCartService.QueueAsync(queue, User.Identity?.Name ?? "unknown", HttpContext.Reques |
| | 0 | 1137 | | var messages = string.Join("<br/>", result.Messages.Select(System.Net.WebUtility.HtmlEncode)); |
| | 0 | 1138 | | return $"<div class='alert alert-{(result.Failed > 0 ? "warning" : "success")} mb-0'><strong>Корзина Микадо: |
| | | 1139 | | } |
| | 0 | 1140 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина Микадо не обработана.</str |
| | 0 | 1141 | | } |
| | | 1142 | | |
| | | 1143 | | private async Task<string?> GetMotexBasketAppendixAsync(List<int> orderIds) |
| | | 1144 | | { |
| | 0 | 1145 | | var raw = Request.Form["MotexBasketQueueJson"].ToString(); |
| | 0 | 1146 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 1147 | | List<MotexCartQueueItem> queue; |
| | 0 | 1148 | | try { queue = ParseMotexBasketQueue(raw); } |
| | 0 | 1149 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина МоТехС не обработана.</str |
| | 0 | 1150 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1151 | | if (selected.Count == 0) return null; |
| | 0 | 1152 | | return (await _motexCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext.RequestAborte |
| | 0 | 1153 | | } |
| | | 1154 | | |
| | | 1155 | | private async Task<string?> GetShateMBasketAppendixAsync(List<int> orderIds) |
| | | 1156 | | { |
| | 0 | 1157 | | var raw = Request.Form["ShateMBasketQueueJson"].ToString(); |
| | 0 | 1158 | | if (string.IsNullOrWhiteSpace(raw) || raw == "[]") return null; |
| | | 1159 | | try |
| | | 1160 | | { |
| | 0 | 1161 | | var queue = ParseShateMBasketQueue(raw); |
| | 0 | 1162 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1163 | | if (selected.Count == 0) return null; |
| | 0 | 1164 | | var options = ParseShateMOptions(Request.Form["ShateMCheckoutOptionsJson"].ToString()); |
| | 0 | 1165 | | return (await _shateMCartService.QueueAsync(selected, options, User.Identity?.Name ?? "unknown", HttpContext |
| | | 1166 | | } |
| | 0 | 1167 | | catch (JsonException ex) { return $"<div class='alert alert-warning mb-0'><strong>Корзина Шате‑М не обработана.< |
| | 0 | 1168 | | } |
| | | 1169 | | |
| | | 1170 | | private async Task<string?> GetShateMDirectCheckoutAppendixAsync(List<int> orderIds) |
| | | 1171 | | { |
| | 0 | 1172 | | var raw = Request.Form["ShateMCheckoutQueueJson"].ToString(); |
| | 0 | 1173 | | if (string.IsNullOrWhiteSpace(raw) || raw == "[]") return null; |
| | | 1174 | | try |
| | | 1175 | | { |
| | 0 | 1176 | | var queue = ParseShateMBasketQueue(raw); |
| | 0 | 1177 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1178 | | if (selected.Count == 0) return null; |
| | 0 | 1179 | | var options = ParseShateMOptions(Request.Form["ShateMCheckoutOptionsJson"].ToString()); |
| | 0 | 1180 | | return (await _shateMCartService.CheckoutDirectAsync(selected, options, User.Identity?.Name ?? "unknown", Ht |
| | | 1181 | | } |
| | 0 | 1182 | | catch (JsonException ex) { return $"<div class='alert alert-warning mb-0'><strong>Заказ Шате‑М не оформлен.</str |
| | 0 | 1183 | | } |
| | | 1184 | | |
| | | 1185 | | private static ShateMCheckoutOptions ParseShateMOptions(string raw) |
| | | 1186 | | { |
| | 0 | 1187 | | if (string.IsNullOrWhiteSpace(raw)) |
| | 0 | 1188 | | throw new JsonException("Не переданы параметры оформления Шате‑М."); |
| | | 1189 | | |
| | 0 | 1190 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 1191 | | if (document.RootElement.ValueKind != JsonValueKind.Object) |
| | 0 | 1192 | | throw new JsonException("Параметры оформления Шате‑М должны быть JSON-объектом."); |
| | | 1193 | | |
| | 0 | 1194 | | var root = document.RootElement; |
| | 0 | 1195 | | var agreementCode = ReadString(root, "agreementCode"); |
| | 0 | 1196 | | var deliveryAddressCode = ReadString(root, "deliveryAddressCode"); |
| | 0 | 1197 | | var deliveryType = ReadString(root, "deliveryType"); |
| | 0 | 1198 | | var deliveryAtRaw = ReadString(root, "deliveryAtUtc"); |
| | 0 | 1199 | | if (!TryParseShateMDeliveryDate(deliveryAtRaw, out var deliveryAt)) |
| | 0 | 1200 | | throw new JsonException("Некорректная дата доставки Шате‑М."); |
| | | 1201 | | |
| | 0 | 1202 | | return new ShateMCheckoutOptions( |
| | 0 | 1203 | | agreementCode, |
| | 0 | 1204 | | deliveryAddressCode, |
| | 0 | 1205 | | deliveryType, |
| | 0 | 1206 | | deliveryAt, |
| | 0 | 1207 | | ReadBool(root, "agreeWithTermsOfDelivery"), |
| | 0 | 1208 | | ReadBool(root, "agreeWithPersonalDataProcessingPolicyAndUserAgreement")); |
| | 0 | 1209 | | } |
| | | 1210 | | |
| | | 1211 | | /// <summary>Смещение слота сохраняется: Шате‑М считает днём доставки календарную дату в присланном смещении.</summa |
| | | 1212 | | internal static bool TryParseShateMDeliveryDate(string value, out DateTimeOffset result) |
| | | 1213 | | { |
| | 3 | 1214 | | var styles = DateTimeStyles.AllowWhiteSpaces; |
| | 3 | 1215 | | return DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, styles, out result) |
| | 3 | 1216 | | || DateTimeOffset.TryParse(value, CultureInfo.GetCultureInfo("ru-RU"), styles, out result); |
| | | 1217 | | } |
| | | 1218 | | |
| | | 1219 | | /// <summary>Снимок проценки Шате‑М содержит артикул и priceId как число либо строку.</summary> |
| | | 1220 | | private static List<ShateMCartQueueItem> ParseShateMBasketQueue(string raw) |
| | | 1221 | | { |
| | 0 | 1222 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 1223 | | if (document.RootElement.ValueKind != JsonValueKind.Array) |
| | 0 | 1224 | | throw new JsonException("Очередь Шате‑М должна быть JSON-массивом."); |
| | | 1225 | | |
| | 0 | 1226 | | return document.RootElement.EnumerateArray() |
| | 0 | 1227 | | .Where(item => item.ValueKind == JsonValueKind.Object) |
| | 0 | 1228 | | .Select(item => new ShateMCartQueueItem( |
| | 0 | 1229 | | ReadInt(item, "orderId"), |
| | 0 | 1230 | | ReadString(item, "article"), |
| | 0 | 1231 | | ReadString(item, "brand"), |
| | 0 | 1232 | | ReadNullableString(item, "productName"), |
| | 0 | 1233 | | ReadNullableString(item, "priceId"), |
| | 0 | 1234 | | ReadNullableString(item, "locationCode"), |
| | 0 | 1235 | | ReadNullableString(item, "direction"), |
| | 0 | 1236 | | ReadDecimal(item, "quantity"), |
| | 0 | 1237 | | ReadNullableDecimal(item, "sourcePrice"), |
| | 0 | 1238 | | ReadNullableString(item, "currencyCode"), |
| | 0 | 1239 | | ReadNullableInt(item, "deliveryDaysMin"), |
| | 0 | 1240 | | ReadNullableInt(item, "deliveryDaysMax"))) |
| | 0 | 1241 | | .ToList(); |
| | 0 | 1242 | | } |
| | | 1243 | | |
| | | 1244 | | private async Task<string?> GetZZapBasketAppendixAsync(List<int> orderIds) |
| | | 1245 | | { |
| | 0 | 1246 | | var raw = Request.Form["ZZapMoscowBasketQueueJson"].ToString(); |
| | 0 | 1247 | | if (string.IsNullOrWhiteSpace(raw)) return null; |
| | | 1248 | | List<ZZapCartQueueItem> queue; |
| | 0 | 1249 | | try { queue = ParseZZapBasketQueue(raw); } |
| | 0 | 1250 | | catch (JsonException) { return "<div class='alert alert-warning mb-0'><strong>Корзина ZZap не обработана.</stron |
| | 0 | 1251 | | var selected = queue.Where(x => orderIds.Contains(x.OrderId)).ToList(); |
| | 0 | 1252 | | if (selected.Count == 0) return null; |
| | 0 | 1253 | | var result = await _zzapCartService.QueueAsync(selected, User.Identity?.Name ?? "unknown", HttpContext.RequestAb |
| | 0 | 1254 | | return result.ToHtmlBlock(); |
| | 0 | 1255 | | } |
| | | 1256 | | |
| | | 1257 | | /// <summary>Снимок предложения ZZap из проценки: code_doc_b — строка, чтобы не потерять точность int64 в JS.</summa |
| | | 1258 | | private static List<ZZapCartQueueItem> ParseZZapBasketQueue(string raw) |
| | | 1259 | | { |
| | 0 | 1260 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 1261 | | if (document.RootElement.ValueKind != JsonValueKind.Array) throw new JsonException("ZZap basket queue must be an |
| | 0 | 1262 | | return document.RootElement.EnumerateArray().Where(x => x.ValueKind == JsonValueKind.Object).Select(x => new ZZa |
| | 0 | 1263 | | ReadInt(x, "orderId"), ReadString(x, "codeDocB"), ReadString(x, "article"), ReadString(x, "brand"), ReadNull |
| | 0 | 1264 | | ReadNullableString(x, "sellerName"), ReadNullableString(x, "sellerUserKey"), ReadNullableString(x, "sellerLo |
| | 0 | 1265 | | ReadInt(x, "quantity"), ReadInt(x, "minimumQuantity"), ReadNullableDecimal(x, "sourcePrice"), ReadInt(x, "de |
| | 0 | 1266 | | ReadNullableInt(x, "codeRegion"), ReadNullableString(x, "direction"), ReadNullableInt(x, "deliveryDaysMin"), |
| | 0 | 1267 | | ReadNullableInt(x, "deliveryDaysMax"), ReadNullableString(x, "shipmentNumber"), ReadNullableString(x, "clien |
| | 0 | 1268 | | ReadNullableDecimal(x, "minSumOrder"), ReadNullableInt(x, "qtyMax"))).ToList(); |
| | 0 | 1269 | | } |
| | | 1270 | | |
| | | 1271 | | private static List<MotexCartQueueItem> ParseMotexBasketQueue(string raw) |
| | | 1272 | | { |
| | 0 | 1273 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 1274 | | if (document.RootElement.ValueKind != JsonValueKind.Array) throw new JsonException("Motex basket queue must be a |
| | 0 | 1275 | | return document.RootElement.EnumerateArray().Where(x => x.ValueKind == JsonValueKind.Object).Select(x => new Mot |
| | 0 | 1276 | | ReadInt(x, "orderId"), ReadString(x, "article"), ReadString(x, "brand"), ReadNullableString(x, "productName" |
| | 0 | 1277 | | ReadNullableString(x, "storeCode"), ReadNullableString(x, "storeInfo"), ReadNullableString(x, "shipmentNumbe |
| | 0 | 1278 | | ReadDecimal(x, "quantity"), ReadNullableDecimal(x, "sourcePrice"), ReadNullableString(x, "currencyCode"), |
| | 0 | 1279 | | ReadNullableString(x, "deliveryAtUtc"), ReadNullableInt(x, "deliveryDaysMin"), ReadNullableInt(x, "deliveryD |
| | 0 | 1280 | | } |
| | | 1281 | | |
| | | 1282 | | /// <summary>Браузер передаёт снимок проценки: цена и количество могут прийти числом либо строкой.</summary> |
| | | 1283 | | private static List<ProfitLigaQueueItem> ParseProfitLigaBasketQueue(string raw) |
| | | 1284 | | { |
| | 0 | 1285 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 1286 | | if (document.RootElement.ValueKind != JsonValueKind.Array) |
| | 0 | 1287 | | throw new JsonException("ProfitLiga basket queue must be an array."); |
| | | 1288 | | |
| | 0 | 1289 | | return document.RootElement.EnumerateArray() |
| | 0 | 1290 | | .Where(item => item.ValueKind == JsonValueKind.Object) |
| | 0 | 1291 | | .Select(item => new ProfitLigaQueueItem( |
| | 0 | 1292 | | ReadInt(item, "orderId"), ReadString(item, "articleId"), ReadString(item, "warehouseId"), |
| | 0 | 1293 | | ReadNullableString(item, "productCode"), ReadString(item, "article"), ReadString(item, "brand"), |
| | 0 | 1294 | | ReadNullableString(item, "productName"), ReadNullableString(item, "warehouseName"), |
| | 0 | 1295 | | ReadNullableString(item, "shipmentNumber"), ReadNullableString(item, "clientName"), |
| | 0 | 1296 | | ReadDecimal(item, "quantity"), ReadNullableInt(item, "multi") ?? 0, |
| | 0 | 1297 | | ReadNullableDecimal(item, "sourcePrice"), ReadNullableString(item, "currencyCode"), |
| | 0 | 1298 | | ReadNullableString(item, "zasylWarehouseName"), ReadNullableInt(item, "deliveryDaysMin"), |
| | 0 | 1299 | | ReadNullableInt(item, "deliveryDaysMax"), ReadNullableString(item, "direction"))) |
| | 0 | 1300 | | .ToList(); |
| | 0 | 1301 | | } |
| | | 1302 | | |
| | | 1303 | | private static List<NikeiCartQueueItem> ParseNikeiBasketQueue(string raw) |
| | | 1304 | | { |
| | 0 | 1305 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 1306 | | if (document.RootElement.ValueKind != JsonValueKind.Array) |
| | 0 | 1307 | | throw new JsonException("Nikei basket queue must be an array."); |
| | 0 | 1308 | | return document.RootElement.EnumerateArray().Where(item => item.ValueKind == JsonValueKind.Object) |
| | 0 | 1309 | | .Select(item => new NikeiCartQueueItem(ReadInt(item, "orderId"), ReadString(item, "toCart"), |
| | 0 | 1310 | | ReadNullableString(item, "productKey"), ReadString(item, "article"), ReadString(item, "brand"), |
| | 0 | 1311 | | ReadNullableString(item, "productName"), ReadNullableString(item, "warehouseName"), ReadInt(item, "quant |
| | 0 | 1312 | | ReadNullableInt(item, "multiplicity") ?? 1, ReadNullableInt(item, "stock"), ReadNullableDecimal(item, "s |
| | 0 | 1313 | | ReadNullableInt(item, "deliveryDaysMin"), ReadNullableInt(item, "deliveryDaysMax"), |
| | 0 | 1314 | | ReadNullableString(item, "shipmentNumber"), ReadNullableString(item, "clientName"))).ToList(); |
| | 0 | 1315 | | } |
| | | 1316 | | |
| | | 1317 | | private static List<MoskvorechieIstraQueueItem> ParseMoskvorechieBasketQueue(string raw) |
| | | 1318 | | { |
| | 0 | 1319 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 1320 | | if (document.RootElement.ValueKind != JsonValueKind.Array) |
| | 0 | 1321 | | throw new JsonException("Moskvorechie basket queue must be an array."); |
| | 0 | 1322 | | return document.RootElement.EnumerateArray().Where(item => item.ValueKind == JsonValueKind.Object) |
| | 0 | 1323 | | .Select(item => new MoskvorechieIstraQueueItem(ReadInt(item, "orderId"), ReadString(item, "offerId"), |
| | 0 | 1324 | | ReadString(item, "article"), ReadString(item, "brand"), ReadNullableString(item, "productName"), |
| | 0 | 1325 | | ReadNullableString(item, "shipmentNumber"), ReadNullableString(item, "clientName"), ReadDecimal(item, "q |
| | 0 | 1326 | | ReadDecimal(item, "minimumQuantity"), ReadNullableDecimal(item, "sourcePrice"), ReadNullableInt(item, "d |
| | 0 | 1327 | | ReadNullableInt(item, "deliveryDaysMax"), ReadNullableString(item, "direction"))).ToList(); |
| | 0 | 1328 | | } |
| | | 1329 | | |
| | | 1330 | | /// <summary>Браузер передаёт снимок проценки: цена и количество могут прийти числом либо строкой.</summary> |
| | | 1331 | | private static List<FavoritPartsQueueItem> ParseFavoritPartsBasketQueue(string raw) |
| | | 1332 | | { |
| | 0 | 1333 | | using var document = JsonDocument.Parse(raw); |
| | 0 | 1334 | | if (document.RootElement.ValueKind != JsonValueKind.Array) |
| | 0 | 1335 | | throw new JsonException("FavoritParts basket queue must be an array."); |
| | | 1336 | | |
| | 0 | 1337 | | return document.RootElement.EnumerateArray() |
| | 0 | 1338 | | .Where(item => item.ValueKind == JsonValueKind.Object) |
| | 0 | 1339 | | .Select(item => new FavoritPartsQueueItem( |
| | 0 | 1340 | | ReadInt(item, "orderId"), ReadString(item, "goodsId"), ReadString(item, "warehouseGroupId"), |
| | 0 | 1341 | | ReadString(item, "article"), ReadString(item, "brand"), ReadNullableString(item, "productName"), |
| | 0 | 1342 | | ReadNullableString(item, "shipmentNumber"), ReadNullableString(item, "clientName"), ReadDecimal(item, "q |
| | 0 | 1343 | | ReadNullableDecimal(item, "sourcePrice"), ReadNullableString(item, "currencyCode"), |
| | 0 | 1344 | | ReadNullableInt(item, "deliveryDaysMin"), ReadNullableInt(item, "deliveryDaysMax"), ReadNullableString(i |
| | 0 | 1345 | | .ToList(); |
| | 0 | 1346 | | } |
| | | 1347 | | |
| | 0 | 1348 | | private static string ReadString(JsonElement item, string name) => ReadNullableString(item, name) ?? string.Empty; |
| | | 1349 | | private static string? ReadNullableString(JsonElement item, string name) => |
| | 0 | 1350 | | !item.TryGetProperty(name, out var value) || value.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined ? |
| | 0 | 1351 | | value.ValueKind == JsonValueKind.String ? value.GetString() : value.GetRawText(); |
| | | 1352 | | private static int ReadInt(JsonElement item, string name) => |
| | 0 | 1353 | | int.TryParse(ReadNullableString(item, name), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) |
| | | 1354 | | private static int? ReadNullableInt(JsonElement item, string name) => |
| | 0 | 1355 | | int.TryParse(ReadNullableString(item, name), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) |
| | 0 | 1356 | | private static decimal ReadDecimal(JsonElement item, string name) => ReadNullableDecimal(item, name) ?? 0m; |
| | | 1357 | | private static decimal? ReadNullableDecimal(JsonElement item, string name) => |
| | 0 | 1358 | | decimal.TryParse(ReadNullableString(item, name)?.Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCultu |
| | | 1359 | | private static bool ReadBool(JsonElement item, string name) => |
| | 0 | 1360 | | bool.TryParse(ReadNullableString(item, name), out var value) && value; |
| | | 1361 | | } |