| | | 1 | | using System.Text; |
| | | 2 | | using Microsoft.AspNetCore.Authorization; |
| | | 3 | | using Microsoft.AspNetCore.Mvc; |
| | | 4 | | using Microsoft.Extensions.DependencyInjection; |
| | | 5 | | using Microsoft.AspNetCore.Mvc.Rendering; |
| | | 6 | | using Microsoft.EntityFrameworkCore; |
| | | 7 | | using Newtonsoft.Json; |
| | | 8 | | using OServices.FiltersServices.FilterModels; |
| | | 9 | | using OzonDomains; |
| | | 10 | | using OzonDomains.Models; |
| | | 11 | | using OzonOrdersWeb.Areas.Studio2.Controllers.Base; |
| | | 12 | | using OzonOrdersWeb.Areas.Studio2.Helpers; |
| | | 13 | | using OzonOrdersWeb.Areas.Studio2.Controllers.Transactions; |
| | | 14 | | using OzonOrdersWeb.Areas.Studio2.Models.Transactions; |
| | | 15 | | using OzonOrdersWeb.Areas.Studio2.ViewModels; |
| | | 16 | | using OzonOrdersWeb.Areas.Studio2.ViewModels.ShippedToClientTransaction; |
| | | 17 | | using OzonOrdersWeb.ViewModels.OrderViewModels; |
| | | 18 | | using OzonRepositories.Context; |
| | | 19 | | using OzonRepositories.Data.Bitrix; |
| | | 20 | | using PartsInfo.HttpUtils; |
| | | 21 | | using Services.ApiServices; |
| | | 22 | | using Services.ApiServices._1CApi; |
| | | 23 | | using Services.ApiServices._1CApi.Models; |
| | | 24 | | using Services.ApiServices._1CApi.Stock; |
| | | 25 | | using Services.CacheServices.Cache.OzonOrdersCache; |
| | | 26 | | using Services.CacheServices.Cache.UserCacheService; |
| | | 27 | | using Services.DataServices; |
| | | 28 | | using Services.FiltersServices.SortModels; |
| | | 29 | | using OzonOrdersWeb.WebServices.Currency; |
| | | 30 | | using Services.SignalRServices; |
| | | 31 | | using Services.TransactionUtilsServices; |
| | | 32 | | using OzonOrdersWeb.TransactionServices; |
| | | 33 | | using Services.CacheServices.Cache; |
| | | 34 | | |
| | | 35 | | |
| | | 36 | | [Authorize(Roles = "User,Admin")] |
| | | 37 | | [Area("Studio2")] |
| | | 38 | | public class ShippedToClientTransactionController : Controller |
| | | 39 | | { |
| | 0 | 40 | | private static readonly TimeZoneInfo RussianTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Russian Standard Time"); |
| | | 41 | | |
| | | 42 | | private readonly ApplicationDbContext _context; |
| | | 43 | | private readonly AppStatusDataServices _appStatusServices; |
| | | 44 | | private readonly OrdersDataServices _orderServices; |
| | | 45 | | private readonly TransactionManager _transaction; |
| | | 46 | | private readonly TransactionCache _transactionCache; |
| | | 47 | | |
| | | 48 | | private readonly SupplierDataServices _supplierDataServices; |
| | | 49 | | private readonly IUserCacheService _userCacheService; |
| | | 50 | | private readonly ICurrencyRatesViewBagService _currencyRatesViewBagService; |
| | | 51 | | private readonly BitrixStockRepository _bitrixStockRepository; |
| | | 52 | | private readonly CacheUpdater<Order> _cacheUpdater; |
| | | 53 | | private readonly OrderCache _cache; |
| | | 54 | | private readonly ProxyHttpClientService _proxyHttpClientService; |
| | | 55 | | private readonly OneCTransferManager _oneCTransferManager; |
| | | 56 | | private readonly OneCExpenseManager _oneCExpenseManager; |
| | | 57 | | private readonly OneCDataManager _oneCDataManager; |
| | | 58 | | private readonly WarehouseDataServices _warehouseDataServices; |
| | | 59 | | private readonly WarehouseMappingDataServices _warehouseMappingDataServices; |
| | | 60 | | private readonly StatusTransitionDataServices _statusTransitionDataServices; |
| | | 61 | | |
| | 0 | 62 | | public ShippedToClientTransactionController( |
| | 0 | 63 | | ApplicationDbContext context, |
| | 0 | 64 | | AppStatusDataServices appStatusServices, |
| | 0 | 65 | | OrdersDataServices orderServices, |
| | 0 | 66 | | TransactionManager transaction, |
| | 0 | 67 | | TransactionCache transactionCache, |
| | 0 | 68 | | SupplierDataServices supplierDataServices, |
| | 0 | 69 | | IUserCacheService userCacheService, |
| | 0 | 70 | | ICurrencyRatesViewBagService currencyRatesViewBagService, |
| | 0 | 71 | | BitrixStockRepository bitrixStockRepository, |
| | 0 | 72 | | CacheUpdater<Order> cacheUpdater, |
| | 0 | 73 | | OrderCache cache, |
| | 0 | 74 | | ProxyHttpClientService proxyHttpClientService, |
| | 0 | 75 | | OneCTransferManager oneCTransferManager, |
| | 0 | 76 | | OneCExpenseManager oneCExpenseManager, |
| | 0 | 77 | | OneCDataManager oneCDataManager, |
| | 0 | 78 | | WarehouseDataServices warehouseDataServices, |
| | 0 | 79 | | WarehouseMappingDataServices warehouseMappingDataServices, |
| | 0 | 80 | | StatusTransitionDataServices statusTransitionDataServices) |
| | | 81 | | { |
| | 0 | 82 | | _context = context; |
| | 0 | 83 | | _appStatusServices = appStatusServices; |
| | 0 | 84 | | _orderServices = orderServices; |
| | 0 | 85 | | _transaction = transaction; |
| | 0 | 86 | | _transactionCache = transactionCache; |
| | 0 | 87 | | _supplierDataServices = supplierDataServices; |
| | 0 | 88 | | _userCacheService = userCacheService; |
| | 0 | 89 | | _currencyRatesViewBagService = currencyRatesViewBagService; |
| | 0 | 90 | | _bitrixStockRepository = bitrixStockRepository; |
| | 0 | 91 | | _cacheUpdater = cacheUpdater; |
| | 0 | 92 | | _cache = cache; |
| | 0 | 93 | | _proxyHttpClientService = proxyHttpClientService; |
| | 0 | 94 | | _oneCTransferManager = oneCTransferManager; |
| | 0 | 95 | | _oneCExpenseManager = oneCExpenseManager; |
| | 0 | 96 | | _oneCDataManager = oneCDataManager; |
| | 0 | 97 | | _warehouseDataServices = warehouseDataServices; |
| | 0 | 98 | | _warehouseMappingDataServices = warehouseMappingDataServices; |
| | 0 | 99 | | _statusTransitionDataServices = statusTransitionDataServices; |
| | 0 | 100 | | } |
| | | 101 | | |
| | | 102 | | |
| | | 103 | | public async Task<IActionResult> CreateShippedToClientTransactionFromFile(string ids, int page, DateTime? oneCOperat |
| | | 104 | | { |
| | 0 | 105 | | var effectiveOneCOperationDate = GetEffectiveOneCOperationDate(oneCOperationDate); |
| | | 106 | | // Получаем статус "Отгружен клиенту" (основной статус для этой транзакции) |
| | 0 | 107 | | var appStatus = await _appStatusServices.GetAppStatusAsync(new AppStatus { Name = "Отгружен клиенту" }); |
| | 0 | 108 | | if (appStatus == null) |
| | | 109 | | { |
| | 0 | 110 | | appStatus = new AppStatus { Name = "Отгружен клиенту" }; |
| | 0 | 111 | | _context.AppStatuses.Add(appStatus); |
| | 0 | 112 | | await _context.SaveChangesAsync(); |
| | | 113 | | } |
| | | 114 | | |
| | | 115 | | // Получаем статус "Отменен" |
| | 0 | 116 | | var cancelledStatus = await _appStatusServices.GetAppStatusAsync(new AppStatus() { Name = "Отменен" }); |
| | | 117 | | |
| | | 118 | | // Формируем список статусов без дубликатов |
| | 0 | 119 | | var filteredStatuses = new List<AppStatus>(); |
| | 0 | 120 | | if (appStatus != null) |
| | 0 | 121 | | filteredStatuses.Add(appStatus); |
| | 0 | 122 | | if (cancelledStatus != null) |
| | 0 | 123 | | filteredStatuses.Add(cancelledStatus); |
| | | 124 | | |
| | | 125 | | // Убираем дубликаты по ID |
| | | 126 | | filteredStatuses = filteredStatuses.GroupBy(s => s.Id).Select(g => g.First()).ToList(); |
| | | 127 | | |
| | 0 | 128 | | ViewBag.AppStatuses = new SelectList(filteredStatuses, "Id", "Name"); |
| | | 129 | | ViewBag.StatusColors = filteredStatuses.ToDictionary(s => s.Id.ToString(), s => s.GetStatusColor()); |
| | | 130 | | |
| | 0 | 131 | | if (string.IsNullOrWhiteSpace(ids)) |
| | 0 | 132 | | return BadRequest("Не выбраны заказы для транзакции."); |
| | | 133 | | |
| | 0 | 134 | | int[] idArray = ids.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); |
| | | 135 | | |
| | 0 | 136 | | if (idArray.Length == 0) |
| | 0 | 137 | | return BadRequest("Не выбраны заказы для транзакции."); |
| | | 138 | | |
| | 0 | 139 | | List<Order> ordersToTransaction = new(); |
| | | 140 | | |
| | | 141 | | // ВАЖНО: Загружаем заказы БЕЗ ТРЕКИНГА - изменения статусов для отображения |
| | | 142 | | // НЕ сохранятся в БД при отмене транзакции |
| | 0 | 143 | | var orders = await _orderServices.GetOrdersNoTracking(idArray); |
| | | 144 | | |
| | 0 | 145 | | foreach (var order in orders) |
| | | 146 | | { |
| | 0 | 147 | | if (order == null) |
| | | 148 | | { |
| | | 149 | | continue; |
| | | 150 | | } |
| | | 151 | | |
| | | 152 | | // Добавляем в транзакцию все заказы, у которых статус отсутствует |
| | | 153 | | // или отличается от "Отгружен клиенту" |
| | 0 | 154 | | if (order.AppStatus == null || order.AppStatus.Name != "Отгружен клиенту") |
| | | 155 | | { |
| | | 156 | | // Если заказ отменен - оставляем статус "Отменен" |
| | | 157 | | // (статус будет установлен позже в CreateShippedToClientTransactionFromFileView) |
| | 0 | 158 | | ordersToTransaction.Add(order); |
| | | 159 | | } |
| | | 160 | | } |
| | | 161 | | |
| | 0 | 162 | | if (ordersToTransaction.Any()) |
| | | 163 | | { |
| | | 164 | | // Группировка заказов по складам с пометкой проблемных групп. |
| | 0 | 165 | | var groupedByWarehouse = ordersToTransaction |
| | | 166 | | .Select(o => new |
| | | 167 | | { |
| | | 168 | | Order = o, |
| | | 169 | | EffectiveWarehouse = TransactionWarehouseHelper.GetEffectiveWarehouseName(o), |
| | | 170 | | SupplierName = o.Supplier?.Name?.Trim() |
| | | 171 | | }) |
| | | 172 | | .GroupBy(x => string.IsNullOrWhiteSpace(x.EffectiveWarehouse) |
| | | 173 | | ? "Склад не определен" |
| | | 174 | | : x.EffectiveWarehouse!) |
| | | 175 | | .OrderBy(g => g.Key) |
| | 0 | 176 | | .ToList(); |
| | | 177 | | |
| | 0 | 178 | | var pages = new List<WarehouseTransactionPage>(); |
| | | 179 | | |
| | 0 | 180 | | foreach (var group in groupedByWarehouse) |
| | | 181 | | { |
| | 0 | 182 | | var warehouseName = group.Key; |
| | | 183 | | var groupIds = string.Join(",", group.Select(x => x.Order.Id)); |
| | 0 | 184 | | var missingWarehouse = warehouseName == "Склад не определен"; |
| | | 185 | | var missingSupplierCount = group.Count(x => string.IsNullOrWhiteSpace(x.SupplierName)); |
| | 0 | 186 | | var warnings = new List<string>(); |
| | | 187 | | |
| | 0 | 188 | | if (missingWarehouse) |
| | | 189 | | { |
| | 0 | 190 | | warnings.Add("У части заказов не определен эффективный склад"); |
| | | 191 | | } |
| | | 192 | | |
| | 0 | 193 | | if (missingSupplierCount > 0) |
| | | 194 | | { |
| | 0 | 195 | | warnings.Add($"Не определен поставщик у заказов: {missingSupplierCount}"); |
| | | 196 | | } |
| | | 197 | | |
| | 0 | 198 | | var url = missingWarehouse |
| | 0 | 199 | | ? string.Empty |
| | 0 | 200 | | : Url.Action( |
| | 0 | 201 | | "CreateShippedToClientTransactionFromFileView", |
| | 0 | 202 | | "ShippedToClientTransaction", |
| | 0 | 203 | | new { ids = groupIds, warehouse = warehouseName, page, oneCOperationDate = effectiveOneCOperatio |
| | 0 | 204 | | ) ?? string.Empty; |
| | | 205 | | |
| | 0 | 206 | | pages.Add(new WarehouseTransactionPage |
| | 0 | 207 | | { |
| | 0 | 208 | | WarehouseName = warehouseName, |
| | 0 | 209 | | Url = url, |
| | 0 | 210 | | OrdersCount = group.Count(), |
| | 0 | 211 | | HasWarnings = warnings.Count > 0, |
| | 0 | 212 | | WarningText = string.Join(". ", warnings), |
| | 0 | 213 | | CanOpen = !missingWarehouse && !string.IsNullOrWhiteSpace(url), |
| | 0 | 214 | | GroupKey = $"shipped-to-client:{warehouseName}:{groupIds}" |
| | 0 | 215 | | }); |
| | | 216 | | } |
| | | 217 | | |
| | | 218 | | // Модель для представления |
| | 0 | 219 | | var model = new MultipleTransactionPagesViewModel |
| | 0 | 220 | | { |
| | 0 | 221 | | WarehousePages = pages, |
| | 0 | 222 | | OneCOperationDate = effectiveOneCOperationDate |
| | 0 | 223 | | }; |
| | | 224 | | |
| | 0 | 225 | | return View("MultipleTransactionPagesByWarehouse", model); |
| | | 226 | | } |
| | | 227 | | |
| | | 228 | | // === ОПТИМИЗАЦИЯ: non-DB параллельно, DB последовательно === |
| | 0 | 229 | | var userTask = _userCacheService.GetCachedUserAsync(User); |
| | 0 | 230 | | var rateTasks = _currencyRatesViewBagService.StartFetch(); |
| | | 231 | | |
| | 0 | 232 | | var suppliers = await _supplierDataServices.GetSuppliers(); |
| | 0 | 233 | | var articles = await _orderServices.GetArticleCountsForOrdersAsync(ordersToTransaction); |
| | 0 | 234 | | var cities = await _orderServices.GetDeliveryCityCountsForOrdersAsync(ordersToTransaction); |
| | 0 | 235 | | var numbers = await _orderServices.GetShipmentBaseCountsForOrdersAsync(ordersToTransaction); |
| | 0 | 236 | | var warehouses = await _warehouseDataServices.GetWarehouses(); |
| | | 237 | | |
| | 0 | 238 | | await Task.WhenAll(userTask, rateTasks.Usd, rateTasks.Eur, rateTasks.Byn); |
| | | 239 | | |
| | 0 | 240 | | var rates = rateTasks.ToSnapshot(); |
| | 0 | 241 | | var pageViewModel = new CreateShippedToClientTransactionModel |
| | 0 | 242 | | { |
| | 0 | 243 | | RedirectPage = page, |
| | 0 | 244 | | OneCOperationDate = effectiveOneCOperationDate, |
| | | 245 | | Orders = ordersToTransaction.OrderBy(o => o.ShipmentNumber).ToList(), |
| | 0 | 246 | | User = userTask.Result!, |
| | | 247 | | Suppliers = suppliers.OrderBy(s => s.Name).ToList(), |
| | 0 | 248 | | RateUSD = rates.RateUsd, |
| | 0 | 249 | | RateEUR = rates.RateEur, |
| | 0 | 250 | | RateBYN = rates.RateByn, |
| | 0 | 251 | | UniqueArticles = articles, |
| | 0 | 252 | | UniqueDeliveryCitys = cities, |
| | 0 | 253 | | UniqueNumbers = numbers, |
| | 0 | 254 | | Warehouses = warehouses, |
| | 0 | 255 | | AppStatus = appStatus!, |
| | 0 | 256 | | Treaties = await _oneCDataManager.GetTreatyListAsync(GetClientInn(ordersToTransaction)) |
| | 0 | 257 | | }; |
| | 0 | 258 | | await AttachTrackingKeyAsync(pageViewModel, TransactionType.ShippedToClient); |
| | | 259 | | |
| | 0 | 260 | | if (pageViewModel.User.AccessProfileId != null) |
| | 0 | 261 | | pageViewModel.User.AccessProfile = _context.AccessProfiles.Find(pageViewModel.User.AccessProfileId); |
| | | 262 | | |
| | 0 | 263 | | await AttachStockCheckSenderMapAsync(pageViewModel.Orders); |
| | 0 | 264 | | RunBackgroundCacheTask(pageViewModel.Orders); |
| | 0 | 265 | | return View("CreateShippedToClientTransaction", pageViewModel); |
| | 0 | 266 | | } |
| | | 267 | | |
| | | 268 | | public async Task<IActionResult> CreateShippedToClientTransactionFromFileView(string ids, int page, DateTime? oneCOp |
| | | 269 | | { |
| | 0 | 270 | | var effectiveOneCOperationDate = GetEffectiveOneCOperationDate(oneCOperationDate); |
| | 0 | 271 | | var transitions = await _statusTransitionDataServices.GetByTransactionType(TransactionType.ShippedToClient); |
| | 0 | 272 | | var validSourceStatusIds = transitions.Any() |
| | | 273 | | ? transitions.Select(t => t.FromStatusId).ToHashSet() |
| | 0 | 274 | | : null; |
| | | 275 | | |
| | | 276 | | // Получаем статус "Отгружен клиенту" (основной статус для этой транзакции) |
| | 0 | 277 | | var appStatusShippedToClient = |
| | 0 | 278 | | await _appStatusServices.GetAppStatusAsync(new AppStatus { Name = "Отгружен клиенту" }); |
| | 0 | 279 | | if (appStatusShippedToClient == null) |
| | | 280 | | { |
| | 0 | 281 | | appStatusShippedToClient = new AppStatus { Name = "Отгружен клиенту" }; |
| | 0 | 282 | | _context.AppStatuses.Add(appStatusShippedToClient); |
| | 0 | 283 | | await _context.SaveChangesAsync(); |
| | | 284 | | } |
| | | 285 | | |
| | | 286 | | // Получаем статус "Заказан реализатору" |
| | 0 | 287 | | var orderedToSellerStatus = |
| | 0 | 288 | | await _appStatusServices.GetAppStatusAsync(new AppStatus() { Name = "Заказан реализатору" }); |
| | | 289 | | |
| | | 290 | | // Получаем статус "Отменен" |
| | 0 | 291 | | var cancelledStatus = await _appStatusServices.GetAppStatusAsync(new AppStatus() { Name = "Отменен" }); |
| | | 292 | | |
| | | 293 | | // Формируем список статусов без дубликатов |
| | 0 | 294 | | var filteredStatuses = new List<AppStatus>(); |
| | 0 | 295 | | if (appStatusShippedToClient != null) |
| | 0 | 296 | | filteredStatuses.Add(appStatusShippedToClient); |
| | 0 | 297 | | if (orderedToSellerStatus != null) |
| | 0 | 298 | | filteredStatuses.Add(orderedToSellerStatus); |
| | 0 | 299 | | if (cancelledStatus != null) |
| | 0 | 300 | | filteredStatuses.Add(cancelledStatus); |
| | | 301 | | |
| | | 302 | | // Убираем дубликаты по ID |
| | | 303 | | filteredStatuses = filteredStatuses.GroupBy(s => s.Id).Select(g => g.First()).ToList(); |
| | | 304 | | |
| | 0 | 305 | | ViewBag.AppStatuses = new SelectList(filteredStatuses, "Id", "Name"); |
| | | 306 | | ViewBag.StatusColors = filteredStatuses.ToDictionary(s => s.Id.ToString(), s => s.GetStatusColor()); |
| | | 307 | | // Устанавливаем начальный статус как "Отгружен клиенту" |
| | 0 | 308 | | ViewBag.StatusId = appStatusShippedToClient?.Id ?? |
| | | 309 | | filteredStatuses.FirstOrDefault(s => s.Name == "Отгружен клиенту")?.Id; |
| | | 310 | | |
| | 0 | 311 | | if (string.IsNullOrWhiteSpace(ids)) |
| | 0 | 312 | | return BadRequest("Не выбраны заказы для транзакции."); |
| | | 313 | | |
| | 0 | 314 | | int[] idArray = ids.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); |
| | | 315 | | |
| | 0 | 316 | | if (idArray.Length == 0) |
| | 0 | 317 | | return BadRequest("Не выбраны заказы для транзакции."); |
| | | 318 | | |
| | 0 | 319 | | List<Order> ordersToTransaction = new(); |
| | | 320 | | |
| | 0 | 321 | | var appStatus = appStatusShippedToClient; |
| | | 322 | | |
| | | 323 | | // ВАЖНО: Загружаем заказы БЕЗ ТРЕКИНГА - изменения для отображения |
| | | 324 | | // НЕ сохранятся в БД при отмене транзакции |
| | 0 | 325 | | var orders = await _orderServices.GetOrdersNoTracking(idArray); |
| | | 326 | | |
| | 0 | 327 | | var warningMessages = new List<string>(); |
| | | 328 | | |
| | 0 | 329 | | foreach (var order in orders) |
| | | 330 | | { |
| | 0 | 331 | | var allowedStatus = validSourceStatusIds != null |
| | 0 | 332 | | ? order.AppStatusId.HasValue && validSourceStatusIds.Contains(order.AppStatusId.Value) |
| | 0 | 333 | | : order.AppStatus?.Name == "Заказан реализатору"; |
| | | 334 | | |
| | 0 | 335 | | if (!allowedStatus) |
| | | 336 | | { |
| | 0 | 337 | | warningMessages.Add( |
| | 0 | 338 | | $"Заказ {order.ShipmentNumber} пропущен: статус '{order.AppStatus?.Name}' не подходит для транзакции |
| | 0 | 339 | | continue; |
| | | 340 | | } |
| | | 341 | | |
| | | 342 | | // В представлении показываем планируемый статус через ViewBag.StatusId, |
| | | 343 | | // поэтому здесь не меняем статус заказа в базе |
| | 0 | 344 | | order.UpdatedBy = User.Identity?.Name; |
| | 0 | 345 | | ordersToTransaction.Add(order); |
| | | 346 | | } |
| | | 347 | | |
| | 0 | 348 | | ViewData["TransactionWarningMessage"] = BuildSkippedOrdersWarningHtml(warningMessages); |
| | | 349 | | |
| | | 350 | | // === ОПТИМИЗАЦИЯ: non-DB параллельно, DB последовательно === |
| | 0 | 351 | | var userTask2 = _userCacheService.GetCachedUserAsync(User); |
| | 0 | 352 | | var rateTasks2 = _currencyRatesViewBagService.StartFetch(); |
| | 0 | 353 | | var zasylTask2 = _oneCDataManager.GetWarehouseListAsync(); |
| | | 354 | | |
| | 0 | 355 | | var suppliers2 = await _supplierDataServices.GetSuppliers(); |
| | 0 | 356 | | var articles2 = await _orderServices.GetArticleCountsForOrdersAsync(ordersToTransaction); |
| | 0 | 357 | | var cities2 = await _orderServices.GetDeliveryCityCountsForOrdersAsync(ordersToTransaction); |
| | 0 | 358 | | var numbers2 = await _orderServices.GetShipmentBaseCountsForOrdersAsync(ordersToTransaction); |
| | 0 | 359 | | var warehouses2 = await _warehouseDataServices.GetWarehouses(); |
| | | 360 | | |
| | 0 | 361 | | await Task.WhenAll(userTask2, rateTasks2.Usd, rateTasks2.Eur, rateTasks2.Byn, zasylTask2); |
| | | 362 | | |
| | 0 | 363 | | var rates2 = rateTasks2.ToSnapshot(); |
| | 0 | 364 | | var pageViewModel = new CreateShippedToClientTransactionModel |
| | 0 | 365 | | { |
| | 0 | 366 | | RedirectPage = page, |
| | 0 | 367 | | OneCOperationDate = effectiveOneCOperationDate, |
| | | 368 | | Orders = ordersToTransaction.OrderBy(o => o.ShipmentNumber).ToList(), |
| | 0 | 369 | | User = userTask2.Result!, |
| | | 370 | | Suppliers = suppliers2.OrderBy(s => s.Name).ToList(), |
| | 0 | 371 | | RateUSD = rates2.RateUsd, |
| | 0 | 372 | | RateEUR = rates2.RateEur, |
| | 0 | 373 | | RateBYN = rates2.RateByn, |
| | 0 | 374 | | UniqueArticles = articles2, |
| | 0 | 375 | | UniqueDeliveryCitys = cities2, |
| | 0 | 376 | | UniqueNumbers = numbers2, |
| | 0 | 377 | | AppStatus = appStatus!, |
| | | 378 | | ZasylValues = zasylTask2.Result.Select(w => w.Name).Where(n => !string.IsNullOrWhiteSpace(n)).Select(n => n! |
| | 0 | 379 | | Warehouses = warehouses2, |
| | 0 | 380 | | Treaties = await _oneCDataManager.GetTreatyListAsync(GetClientInn(ordersToTransaction)) |
| | 0 | 381 | | }; |
| | 0 | 382 | | await AttachTrackingKeyAsync(pageViewModel, TransactionType.ShippedToClient); |
| | | 383 | | |
| | 0 | 384 | | if (pageViewModel.User.AccessProfileId != null) |
| | 0 | 385 | | pageViewModel.User.AccessProfile = _context.AccessProfiles.Find(pageViewModel.User.AccessProfileId); |
| | | 386 | | |
| | 0 | 387 | | if (pageViewModel.User.AccessProfile?.AvailableOrderColumns?.Contains("Себестоимость") == true && ordersToTransa |
| | | 388 | | { |
| | 0 | 389 | | await _orderServices.LoadExtraExpensesForOrders(ordersToTransaction); |
| | | 390 | | } |
| | | 391 | | |
| | 0 | 392 | | await AttachStockCheckSenderMapAsync(pageViewModel.Orders); |
| | 0 | 393 | | RunBackgroundCacheTask(pageViewModel.Orders); |
| | 0 | 394 | | return View("CreateShippedToClientTransaction", pageViewModel); |
| | 0 | 395 | | } |
| | | 396 | | |
| | | 397 | | |
| | | 398 | | // ============================== |
| | | 399 | | // GET: CreateShippedToClientTransaction |
| | | 400 | | // ============================== |
| | | 401 | | public async Task<IActionResult> CreateShippedToClientTransaction(string ids, int page, DateTime? oneCOperationDate |
| | | 402 | | { |
| | 0 | 403 | | var effectiveOneCOperationDate = GetEffectiveOneCOperationDate(oneCOperationDate); |
| | 0 | 404 | | var transitions = await _statusTransitionDataServices.GetByTransactionType(TransactionType.ShippedToClient); |
| | | 405 | | List<AppStatus> filteredStatuses; |
| | | 406 | | AppStatus appStatus; |
| | 0 | 407 | | HashSet<int>? validSourceStatusIds = null; |
| | | 408 | | |
| | 0 | 409 | | if (transitions.Any()) |
| | | 410 | | { |
| | | 411 | | validSourceStatusIds = transitions.Select(t => t.FromStatusId).ToHashSet(); |
| | 0 | 412 | | appStatus = transitions.First().ToStatus!; |
| | 0 | 413 | | var orderedToSellerStatus = await _appStatusServices.GetAppStatusAsync(new AppStatus() { Name = "Заказан реа |
| | 0 | 414 | | var cancelledStatus = await _appStatusServices.GetAppStatusAsync(new AppStatus() { Name = "Отменен" }); |
| | 0 | 415 | | filteredStatuses = new List<AppStatus> { appStatus }; |
| | 0 | 416 | | if (orderedToSellerStatus != null) filteredStatuses.Add(orderedToSellerStatus); |
| | 0 | 417 | | if (cancelledStatus != null) filteredStatuses.Add(cancelledStatus); |
| | | 418 | | filteredStatuses = filteredStatuses.GroupBy(s => s.Id).Select(g => g.First()).ToList(); |
| | 0 | 419 | | } |
| | | 420 | | else |
| | | 421 | | { |
| | 0 | 422 | | var appStatusShippedToClient = await _appStatusServices.GetAppStatusAsync(new AppStatus { Name = "Отгружен к |
| | 0 | 423 | | if (appStatusShippedToClient == null) |
| | | 424 | | { |
| | 0 | 425 | | appStatusShippedToClient = new AppStatus { Name = "Отгружен клиенту" }; |
| | 0 | 426 | | _context.AppStatuses.Add(appStatusShippedToClient); |
| | 0 | 427 | | await _context.SaveChangesAsync(); |
| | | 428 | | } |
| | 0 | 429 | | var orderedToSellerStatus = await _appStatusServices.GetAppStatusAsync(new AppStatus() { Name = "Заказан реа |
| | 0 | 430 | | var cancelledStatus = await _appStatusServices.GetAppStatusAsync(new AppStatus() { Name = "Отменен" }); |
| | 0 | 431 | | filteredStatuses = new List<AppStatus>(); |
| | 0 | 432 | | if (appStatusShippedToClient != null) filteredStatuses.Add(appStatusShippedToClient); |
| | 0 | 433 | | if (orderedToSellerStatus != null) filteredStatuses.Add(orderedToSellerStatus); |
| | 0 | 434 | | if (cancelledStatus != null) filteredStatuses.Add(cancelledStatus); |
| | | 435 | | filteredStatuses = filteredStatuses.GroupBy(s => s.Id).Select(g => g.First()).ToList(); |
| | 0 | 436 | | appStatus = appStatusShippedToClient!; |
| | 0 | 437 | | } |
| | | 438 | | |
| | 0 | 439 | | ViewBag.AppStatuses = new SelectList(filteredStatuses, "Id", "Name"); |
| | | 440 | | ViewBag.StatusColors = filteredStatuses.ToDictionary(s => s.Id.ToString(), s => s.GetStatusColor()); |
| | | 441 | | ViewBag.StatusId = appStatus?.Id ?? filteredStatuses.FirstOrDefault(s => s.Name == "Отгружен клиенту")?.Id; |
| | | 442 | | |
| | 0 | 443 | | if (string.IsNullOrWhiteSpace(ids)) |
| | 0 | 444 | | return BadRequest("Не выбраны заказы для транзакции."); |
| | | 445 | | |
| | 0 | 446 | | int[] idArray = ids.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); |
| | | 447 | | |
| | 0 | 448 | | if (idArray.Length == 0) |
| | 0 | 449 | | return BadRequest("Не выбраны заказы для транзакции."); |
| | | 450 | | |
| | 0 | 451 | | List<Order> ordersToTransaction = new(); |
| | 0 | 452 | | List<string> warningMessages = new(); |
| | 0 | 453 | | var orderStatusWarnings = new Dictionary<int, string>(); |
| | | 454 | | |
| | | 455 | | // ВАЖНО: Загружаем заказы БЕЗ ТРЕКИНГА - изменения для отображения |
| | | 456 | | // НЕ сохранятся в БД при отмене транзакции |
| | 0 | 457 | | var ordersFromDb = await _orderServices.GetOrdersNoTracking(idArray); |
| | | 458 | | |
| | 0 | 459 | | foreach (var order in ordersFromDb) |
| | | 460 | | { |
| | 0 | 461 | | bool allowedStatus = validSourceStatusIds != null |
| | 0 | 462 | | ? (order.AppStatusId.HasValue && validSourceStatusIds.Contains(order.AppStatusId.Value)) |
| | 0 | 463 | | : order.AppStatus?.Name == "Заказан реализатору"; |
| | 0 | 464 | | if (allowedStatus) |
| | | 465 | | { |
| | 0 | 466 | | order.UpdatedBy = User.Identity?.Name; |
| | 0 | 467 | | ordersToTransaction.Add(order); |
| | | 468 | | } |
| | | 469 | | else |
| | | 470 | | { |
| | 0 | 471 | | var warning = $"Статус '{order.AppStatus?.Name}' не подходит для проведения."; |
| | 0 | 472 | | warningMessages.Add($"Заказ {order.ShipmentNumber} пропущен: {warning}"); |
| | 0 | 473 | | orderStatusWarnings[order.Id] = warning; |
| | 0 | 474 | | order.UpdatedBy = User.Identity?.Name; |
| | 0 | 475 | | ordersToTransaction.Add(order); |
| | | 476 | | } |
| | | 477 | | } |
| | | 478 | | |
| | 0 | 479 | | ViewData["TransactionWarningMessage"] = BuildSkippedOrdersWarningHtml(warningMessages); |
| | | 480 | | |
| | 0 | 481 | | var conditionsMessage = transitions.Any() |
| | 0 | 482 | | ? string.Join("<br/>", transitions |
| | | 483 | | .Select(t => t.Conditions) |
| | | 484 | | .Where(c => !string.IsNullOrWhiteSpace(c)) |
| | | 485 | | .Select(c => System.Net.WebUtility.HtmlEncode(c!.Trim())) |
| | 0 | 486 | | .Distinct()) |
| | 0 | 487 | | : null; |
| | 0 | 488 | | ViewData["TransactionConditionsMessage"] = conditionsMessage; |
| | | 489 | | |
| | | 490 | | // === ОПТИМИЗАЦИЯ: non-DB параллельно, DB последовательно === |
| | 0 | 491 | | var userTask3 = _userCacheService.GetCachedUserAsync(User); |
| | 0 | 492 | | var rateTasks3 = _currencyRatesViewBagService.StartFetch(); |
| | | 493 | | |
| | 0 | 494 | | var suppliers3 = await _supplierDataServices.GetSuppliers(); |
| | 0 | 495 | | var articles3 = await _orderServices.GetArticleCountsForOrdersAsync(ordersToTransaction); |
| | 0 | 496 | | var cities3 = await _orderServices.GetDeliveryCityCountsForOrdersAsync(ordersToTransaction); |
| | 0 | 497 | | var numbers3 = await _orderServices.GetShipmentBaseCountsForOrdersAsync(ordersToTransaction); |
| | 0 | 498 | | var warehouses3 = await _warehouseDataServices.GetWarehouses(); |
| | | 499 | | |
| | 0 | 500 | | await Task.WhenAll(userTask3, rateTasks3.Usd, rateTasks3.Eur, rateTasks3.Byn); |
| | | 501 | | |
| | | 502 | | // Если ошибок нет |
| | 0 | 503 | | if (!ordersToTransaction.Any()) |
| | | 504 | | { |
| | 0 | 505 | | ViewData["TransactionErrorCode"] = TransactionErrorCode.NoOrdersForTransaction.ToString(); |
| | 0 | 506 | | ViewData["ErrorReason"] = "Нет заказов со статусом 'Заказан реализатору'."; |
| | | 507 | | } |
| | | 508 | | |
| | 0 | 509 | | var rates3 = rateTasks3.ToSnapshot(); |
| | 0 | 510 | | var pageViewModel = new CreateShippedToClientTransactionModel |
| | 0 | 511 | | { |
| | 0 | 512 | | RedirectPage = page, |
| | 0 | 513 | | OneCOperationDate = effectiveOneCOperationDate, |
| | | 514 | | Orders = ordersToTransaction.OrderBy(o => o.ShipmentNumber).ToList(), |
| | 0 | 515 | | User = userTask3.Result!, |
| | | 516 | | Suppliers = suppliers3.OrderBy(s => s.Name).ToList(), |
| | 0 | 517 | | RateUSD = rates3.RateUsd, |
| | 0 | 518 | | RateEUR = rates3.RateEur, |
| | 0 | 519 | | RateBYN = rates3.RateByn, |
| | 0 | 520 | | UniqueArticles = articles3, |
| | 0 | 521 | | UniqueDeliveryCitys = cities3, |
| | 0 | 522 | | UniqueNumbers = numbers3, |
| | 0 | 523 | | Warehouses = warehouses3, |
| | 0 | 524 | | OrderStatusWarnings = orderStatusWarnings, |
| | 0 | 525 | | AppStatus = appStatus!, |
| | 0 | 526 | | Treaties = await _oneCDataManager.GetTreatyListAsync(GetClientInn(ordersToTransaction)) |
| | 0 | 527 | | }; |
| | | 528 | | |
| | 0 | 529 | | if (pageViewModel.User.AccessProfileId != null) |
| | 0 | 530 | | pageViewModel.User.AccessProfile = _context.AccessProfiles.Find(pageViewModel.User.AccessProfileId); |
| | | 531 | | |
| | 0 | 532 | | if (pageViewModel.User.AccessProfile?.AvailableOrderColumns?.Contains("Себестоимость") == true && ordersToTransa |
| | | 533 | | { |
| | 0 | 534 | | await _orderServices.LoadExtraExpensesForOrders(ordersToTransaction); |
| | | 535 | | } |
| | | 536 | | |
| | 0 | 537 | | await AttachStockCheckSenderMapAsync(pageViewModel.Orders); |
| | 0 | 538 | | RunBackgroundCacheTask(pageViewModel.Orders); |
| | 0 | 539 | | return View(pageViewModel); |
| | 0 | 540 | | } |
| | | 541 | | |
| | | 542 | | private async Task AttachStockCheckSenderMapAsync(IEnumerable<Order> orders) |
| | | 543 | | { |
| | 0 | 544 | | var warehouseMappings = await _warehouseMappingDataServices.GetWarehouseMappings(); |
| | 0 | 545 | | ViewData["StockCheckSenderByOrderId"] = TransactionStockCheckLineBuilder.BuildStockCheckSenderMap( |
| | 0 | 546 | | orders, |
| | 0 | 547 | | warehouseMappings); |
| | 0 | 548 | | } |
| | | 549 | | |
| | | 550 | | |
| | | 551 | | private static string? GetClientInn(List<Order> orders) => |
| | 0 | 552 | | orders |
| | 0 | 553 | | .Where(o => o.OzonClient != null && !string.IsNullOrWhiteSpace(o.OzonClient.INNCode)) |
| | 0 | 554 | | .Select(o => o.OzonClient!.INNCode) |
| | 0 | 555 | | .FirstOrDefault(); |
| | | 556 | | |
| | | 557 | | private void RunBackgroundCacheTask(List<Order> orders) { |
| | 0 | 558 | | _ = Task.Run(async () => |
| | 0 | 559 | | { |
| | 0 | 560 | | foreach (var order in orders) |
| | 0 | 561 | | { |
| | 0 | 562 | | try |
| | 0 | 563 | | { |
| | 0 | 564 | | if (order.EtProducer != null && !string.IsNullOrEmpty(order.Article)) |
| | 0 | 565 | | { |
| | 0 | 566 | | _ = await _proxyHttpClientService.GetJsonAsync( |
| | 0 | 567 | | $"https://api.interparts.ru/detail-full-info/{order.EtProducer.Name}/{order.Article}"); |
| | 0 | 568 | | |
| | 0 | 569 | | _ = await _proxyHttpClientService.GetJsonAsync( |
| | 0 | 570 | | $"https://api.interparts.ru/product-information/product/?article_number={order.Article}&manu |
| | 0 | 571 | | |
| | 0 | 572 | | Console.WriteLine($"Кэширован для проведения: {order.EtProducer.Name}/{order.Article}"); |
| | 0 | 573 | | } |
| | 0 | 574 | | } |
| | 0 | 575 | | catch (Exception ex) |
| | 0 | 576 | | { |
| | 0 | 577 | | Console.WriteLine($"Ошибка в фоне: {ex.Message}"); |
| | 0 | 578 | | } |
| | 0 | 579 | | } |
| | 0 | 580 | | }); |
| | 0 | 581 | | } |
| | | 582 | | |
| | | 583 | | // ============================== |
| | | 584 | | // POST: CreateShippedToClientTransaction |
| | | 585 | | // ============================== |
| | | 586 | | [HttpPost] |
| | | 587 | | [ValidateAntiForgeryToken] |
| | | 588 | | public async Task<IActionResult> CreateShippedToClientTransaction( |
| | | 589 | | List<Order> orders, |
| | | 590 | | string userName, |
| | | 591 | | string? comment, |
| | | 592 | | int page, |
| | | 593 | | string? deletedOrders, |
| | | 594 | | Dictionary<int, string?>? orderSupplierMap, |
| | | 595 | | bool processIn1C = false, |
| | | 596 | | string? contractLink = null, |
| | | 597 | | string? transactionComment = null, |
| | | 598 | | int? vatRate = null, |
| | | 599 | | DateTime? oneCOperationDate = null) |
| | | 600 | | { |
| | 0 | 601 | | var aiFlow = TransactionCommentAiSchedulingHelper.BeginFlow( |
| | 0 | 602 | | comment, |
| | 0 | 603 | | TransactionType.ShippedToClient, |
| | 0 | 604 | | userName, |
| | 0 | 605 | | processIn1C); |
| | 0 | 606 | | comment = aiFlow.UserCommentOnly; |
| | | 607 | | |
| | | 608 | | // «Засыл» из формы является складом-источником с приоритетом над ShipmentWarehouse. |
| | | 609 | | // Сохраняем его до серверной проверки остатков и создания расходной накладной, иначе |
| | | 610 | | // выбранный на этой же странице m2out применялся только к следующей транзакции. |
| | 0 | 611 | | if (orderSupplierMap is { Count: > 0 }) |
| | 0 | 612 | | await _orderServices.SetZasylAsync(orderSupplierMap); |
| | | 613 | | |
| | 0 | 614 | | var trackingKey = Request.Form["TrackingKey"].FirstOrDefault(); |
| | 0 | 615 | | var oneCOrganizationInn = Request.Form["oneCOrganizationInn"].FirstOrDefault(); |
| | 0 | 616 | | var missingOrdersResult = TransactionErrorResponseHelper.EnsureOrdersProvided( |
| | 0 | 617 | | this, |
| | 0 | 618 | | orders, |
| | 0 | 619 | | () => RedirectToAction("Index")); |
| | 0 | 620 | | if (missingOrdersResult != null) |
| | | 621 | | { |
| | 0 | 622 | | return missingOrdersResult; |
| | | 623 | | } |
| | | 624 | | |
| | 0 | 625 | | ArgumentNullException.ThrowIfNull(orders); |
| | | 626 | | |
| | 0 | 627 | | if (deletedOrders != null) |
| | | 628 | | { |
| | 0 | 629 | | var deletedOrderIds = DeletedOrderIds.Parse(deletedOrders); |
| | 0 | 630 | | orders = orders.Where(o => !deletedOrderIds.Contains(o.Id)).ToList(); |
| | | 631 | | } |
| | | 632 | | |
| | | 633 | | // Очищаем пустые дополнительные расходы перед сохранением |
| | | 634 | | // Это гарантирует, что пустые элементы не попадут в базу данных |
| | 0 | 635 | | foreach (var order in orders) |
| | | 636 | | { |
| | 0 | 637 | | if (order.ExtraExpenses != null && order.ExtraExpenses.Any()) |
| | | 638 | | { |
| | | 639 | | // Удаляем все расходы, где не заполнены оба поля (тип и сумма) |
| | 0 | 640 | | order.ExtraExpenses = order.ExtraExpenses |
| | | 641 | | .Where(e => e != null && e.ExpenseTypeId > 0 && e.Amount > 0) |
| | 0 | 642 | | .ToList(); |
| | | 643 | | |
| | | 644 | | // Если список стал пустым, устанавливаем null |
| | 0 | 645 | | if (!order.ExtraExpenses.Any()) |
| | | 646 | | { |
| | 0 | 647 | | order.ExtraExpenses = []; |
| | | 648 | | } |
| | | 649 | | } |
| | | 650 | | } |
| | | 651 | | |
| | 0 | 652 | | var stockCheck = StockCheckResult.Empty; |
| | | 653 | | |
| | | 654 | | try |
| | | 655 | | { |
| | 0 | 656 | | var processedOrdersForRedirect = new List<Order>(); |
| | 0 | 657 | | var postTransitions = await _statusTransitionDataServices.GetByTransactionType(TransactionType.ShippedToClie |
| | 0 | 658 | | if (orders.Count != 0) |
| | | 659 | | { |
| | 0 | 660 | | DateTime createAt = DateTime.Now; |
| | 0 | 661 | | var changeCount = 0; |
| | 0 | 662 | | long transactionId = 0; |
| | 0 | 663 | | var dateTime = ""; |
| | 0 | 664 | | List<Order> ordersToUpdate = new List<Order>(); |
| | 0 | 665 | | List<string> ordersNotFoundInOzone = []; |
| | | 666 | | var orderIds = orders.Select(o => o.Id).Distinct().ToList(); |
| | | 667 | | |
| | | 668 | | // Сохраняем исходные статусы заказов для отката при ошибке 1С. |
| | 0 | 669 | | var originalStatuses = new Dictionary<int, AppStatus>(); |
| | | 670 | | var validSourceStatusIds = postTransitions.Any() ? postTransitions.Select(t => t.FromStatusId).ToHashSet |
| | 0 | 671 | | var currentSourceStatusByOrderId = await _context.Orders.AsNoTracking() |
| | 0 | 672 | | .Where(o => orderIds.Contains(o.Id)) |
| | 0 | 673 | | .Select(o => new { o.Id, o.AppStatusId, StatusName = o.AppStatus != null ? o.AppStatus.Name : null } |
| | | 674 | | .ToDictionaryAsync(x => x.Id, x => new { x.AppStatusId, x.StatusName }); |
| | | 675 | | |
| | 0 | 676 | | var skippedOrders = new List<string>(); |
| | 0 | 677 | | var eligibleOrders = new List<Order>(); |
| | | 678 | | |
| | 0 | 679 | | foreach (var order in orders) |
| | | 680 | | { |
| | 0 | 681 | | var currentStatus = currentSourceStatusByOrderId.GetValueOrDefault(order.Id); |
| | 0 | 682 | | var allowedStatus = validSourceStatusIds != null |
| | 0 | 683 | | ? currentStatus?.AppStatusId.HasValue == true && validSourceStatusIds.Contains(currentStatus.App |
| | 0 | 684 | | : string.Equals(currentStatus?.StatusName, "Заказан реализатору", StringComparison.OrdinalIgnore |
| | | 685 | | |
| | 0 | 686 | | if (!allowedStatus) |
| | | 687 | | { |
| | 0 | 688 | | skippedOrders.Add($"{order.ShipmentNumber} ({currentStatus?.StatusName ?? "без статуса"})"); |
| | 0 | 689 | | continue; |
| | | 690 | | } |
| | | 691 | | |
| | 0 | 692 | | eligibleOrders.Add(order); |
| | | 693 | | } |
| | | 694 | | |
| | 0 | 695 | | orders = eligibleOrders; |
| | | 696 | | |
| | | 697 | | // Остатки спрашиваем до изменения статусов: при нехватке 1С создаст расходную |
| | | 698 | | // накладную, но откажет в проведении, не указав проблемные позиции. |
| | 0 | 699 | | if (processIn1C) |
| | | 700 | | { |
| | 0 | 701 | | var stockOrders = await TransactionStockCheckLineBuilder.LoadForStockCheckAsync( |
| | | 702 | | _context, orders.Select(o => o.Id)); |
| | 0 | 703 | | var warehouseMappings = await _warehouseMappingDataServices.GetWarehouseMappings(); |
| | | 704 | | |
| | 0 | 705 | | stockCheck = await OneCStockGate.RunAsync( |
| | 0 | 706 | | HttpContext, |
| | 0 | 707 | | TransactionStockCheckLineBuilder.FromWarehouseMappings( |
| | 0 | 708 | | stockOrders, |
| | 0 | 709 | | warehouseMappings, |
| | 0 | 710 | | null, |
| | 0 | 711 | | TransactionStockCheckLineBuilder.QuantitiesFromForm(orders))); |
| | 0 | 712 | | await OneCStockGate.ReportAsync(HttpContext, stockCheck, "Отгружен клиенту"); |
| | | 713 | | |
| | 0 | 714 | | } |
| | | 715 | | |
| | 0 | 716 | | foreach (var order in orders) |
| | | 717 | | { |
| | 0 | 718 | | var fromDb = await _orderServices.GetOrder(order.Id); |
| | 0 | 719 | | if (fromDb?.AppStatus != null) |
| | 0 | 720 | | originalStatuses[order.Id] = fromDb.AppStatus; |
| | 0 | 721 | | } |
| | | 722 | | |
| | 0 | 723 | | var submittedFields = SubmittedOrderFields.FromForm(Request.HasFormContentType ? Request.Form : null); |
| | 0 | 724 | | foreach (var order in orders) |
| | | 725 | | { |
| | 0 | 726 | | ordersToUpdate.Add(await _orderServices.TransactOrder(order, submittedFields.For(order.Id))); |
| | | 727 | | } |
| | | 728 | | |
| | 0 | 729 | | AppStatus appStatus = postTransitions.Any() |
| | 0 | 730 | | ? postTransitions.First().ToStatus! |
| | 0 | 731 | | : (await _appStatusServices.GetAppStatusAsync(new AppStatus() { Name = "Отгружен клиенту" }))!; |
| | | 732 | | HashSet<int>? validTargetStatusIds = postTransitions.Any() ? postTransitions.Select(t => t.ToStatusId).T |
| | 0 | 733 | | var oldStatus = |
| | 0 | 734 | | await _appStatusServices.GetAppStatusAsync(new AppStatus() { Name = "Заказан реализатору" }); |
| | 0 | 735 | | var backStatus = |
| | 0 | 736 | | await _appStatusServices.GetAppStatusAsync(new AppStatus() { Name = "Утерян реализатором" }); |
| | 0 | 737 | | if (backStatus == null) |
| | | 738 | | { |
| | 0 | 739 | | AppStatus newStatus = new AppStatus() |
| | 0 | 740 | | { |
| | 0 | 741 | | Name = "Утерян реализатором" |
| | 0 | 742 | | }; |
| | 0 | 743 | | _context.AppStatuses.Add(newStatus); |
| | 0 | 744 | | await _context.SaveChangesAsync(); |
| | 0 | 745 | | backStatus = newStatus; |
| | 0 | 746 | | } |
| | | 747 | | |
| | 0 | 748 | | var cancelledStatus = await _appStatusServices.GetAppStatusAsync(new AppStatus() { Name = "Отменен" }); |
| | | 749 | | |
| | 0 | 750 | | List<Order> ordersToTransaction = validTargetStatusIds != null |
| | 0 | 751 | | ? ordersToUpdate.Where(o => o.AppStatus != null && validTargetStatusIds.Contains(o.AppStatus.Id) && |
| | 0 | 752 | | : ordersToUpdate.Where(o => ((appStatus != null && o.AppStatus?.Id == appStatus.Id) || (cancelledSta |
| | 0 | 753 | | processedOrdersForRedirect = ordersToTransaction.ToList(); |
| | 0 | 754 | | var blockingOrderIds = OneCStockGate.GetBlockingOrderIds(stockCheck); |
| | 0 | 755 | | var ordersForOneC = ordersToTransaction |
| | 0 | 756 | | .Where(order => !blockingOrderIds.Contains(order.Id)) |
| | 0 | 757 | | .ToList(); |
| | 0 | 758 | | var ordersForOneCDraft = ordersToTransaction |
| | 0 | 759 | | .Where(order => blockingOrderIds.Contains(order.Id)) |
| | 0 | 760 | | .ToList(); |
| | | 761 | | |
| | 0 | 762 | | var skippedOrdersMessage = skippedOrders.Any() |
| | 0 | 763 | | ? "<div class='alert alert-warning'><strong>Некоторые заказы пропущены</strong><br/>" + |
| | 0 | 764 | | string.Join("<br/>", skippedOrders.Select(System.Net.WebUtility.HtmlEncode)) + |
| | 0 | 765 | | "</div>" |
| | 0 | 766 | | : string.Empty; |
| | | 767 | | |
| | 0 | 768 | | if (!ordersToTransaction.Any()) |
| | | 769 | | { |
| | 0 | 770 | | TempData["TransactionResult"] = string.IsNullOrWhiteSpace(skippedOrdersMessage) |
| | 0 | 771 | | ? "<div class='alert alert-warning'><strong>Нет подходящих заказов для проведения</strong></div> |
| | 0 | 772 | | : skippedOrdersMessage; |
| | 0 | 773 | | await _orderServices.SetZasylAsync(orderSupplierMap ?? new Dictionary<int, string?>()); |
| | 0 | 774 | | return Json(new { redirectTo = Url.Action("Index", "Orders", new { sortOrder = GetSortStateCookie(), |
| | | 775 | | } |
| | | 776 | | List<Order> ordersToBack = ordersToUpdate.Where(o => o.Quantity <= 0).ToList(); |
| | 0 | 777 | | foreach (var order in ordersToBack) |
| | | 778 | | { |
| | 0 | 779 | | order.AppStatus = backStatus; |
| | | 780 | | } |
| | | 781 | | |
| | 0 | 782 | | int ordersToBackResult = await _orderServices.UpdateOrders(ordersToBack); |
| | | 783 | | |
| | | 784 | | |
| | 0 | 785 | | string oneCResult = ""; |
| | 0 | 786 | | IEnumerable<string> oneCHash = []; |
| | 0 | 787 | | List<OneCResponse> transferResult = new List<OneCResponse>(); |
| | 0 | 788 | | List<OneCResponse> draftTransferResult = new List<OneCResponse>(); |
| | 0 | 789 | | if (processIn1C && ordersForOneC.Any()) |
| | | 790 | | { |
| | | 791 | | try |
| | | 792 | | { |
| | 0 | 793 | | transferResult = |
| | 0 | 794 | | await _oneCExpenseManager.SaleStockByWarehouses(ordersForOneC, |
| | 0 | 795 | | TransactionType.ShippedToClient, |
| | 0 | 796 | | contractLink, |
| | 0 | 797 | | transactionComment, |
| | 0 | 798 | | vatRate, |
| | 0 | 799 | | oneCOperationDate, |
| | 0 | 800 | | userName, |
| | 0 | 801 | | oneCOrganizationInn); |
| | 0 | 802 | | } |
| | 0 | 803 | | catch (Exception e) |
| | | 804 | | { |
| | 0 | 805 | | string msg = $"<div class='alert alert-danger'>" + |
| | 0 | 806 | | $"<strong>⚠️ Ошибка обработки в 1С</strong><br/>" + |
| | 0 | 807 | | $"{e.Message}</div>"; |
| | 0 | 808 | | foreach (var item in ordersForOneC) |
| | | 809 | | { |
| | 0 | 810 | | item.AppStatus = originalStatuses.TryGetValue(item.Id, out var orig) ? orig : oldStatus; |
| | | 811 | | } |
| | | 812 | | |
| | 0 | 813 | | int ordersToOldResult = await _orderServices.UpdateOrders(ordersForOneC); |
| | 0 | 814 | | if (ordersToOldResult > 0) |
| | | 815 | | { |
| | 0 | 816 | | msg += $"<div class='alert alert-info'>" + |
| | 0 | 817 | | $"<strong>📊 Обновление статусов</strong><br/>" + |
| | 0 | 818 | | $"Исходный статус восстановлен для <strong>{ordersToOldResult}</strong> заказов" + |
| | 0 | 819 | | $"</div>"; |
| | | 820 | | } |
| | | 821 | | |
| | 0 | 822 | | TempData["TransactionResult"] = msg; |
| | 0 | 823 | | await _orderServices.SetZasylAsync(orderSupplierMap ?? new Dictionary<int, string?>()); |
| | 0 | 824 | | return Json(new |
| | 0 | 825 | | { |
| | 0 | 826 | | redirectTo = BuildOrdersIndexRedirectUrl(page, ordersToTransaction) |
| | 0 | 827 | | }); |
| | | 828 | | } |
| | | 829 | | |
| | | 830 | | var successfulTransfers = transferResult.Where(tr => tr.Success).ToList(); |
| | | 831 | | var failedTransfers = transferResult.Where(tr => !tr.Success).ToList(); |
| | | 832 | | |
| | 0 | 833 | | if (failedTransfers.Count > 0) |
| | | 834 | | { |
| | | 835 | | // Есть ошибки |
| | | 836 | | var errorMessages = failedTransfers.Select(tr => tr.Message).Distinct().Take(3); |
| | 0 | 837 | | var errorCount = failedTransfers.Count; |
| | 0 | 838 | | var successCount = successfulTransfers.Count; |
| | 0 | 839 | | var totalCount = transferResult.Count; |
| | | 840 | | |
| | 0 | 841 | | string msg = $"<div class='alert alert-warning'>" + |
| | 0 | 842 | | $"<strong>⚠️ Частичный результат обработки в 1С</strong><br/>" + |
| | 0 | 843 | | $"Успешно: {successCount} из {totalCount} документов<br/>" + |
| | 0 | 844 | | $"С ошибками: {errorCount} документов<br/>" + |
| | 0 | 845 | | $"<small>Ошибки: {string.Join("; ", errorMessages)}" + |
| | 0 | 846 | | (errorCount > 3 ? "..." : "") + "</small>" + |
| | 0 | 847 | | "</div>"; |
| | | 848 | | |
| | | 849 | | |
| | 0 | 850 | | foreach (var item in ordersForOneC) |
| | | 851 | | { |
| | 0 | 852 | | item.AppStatus = originalStatuses.TryGetValue(item.Id, out var orig) ? orig : oldStatus; |
| | | 853 | | } |
| | | 854 | | |
| | 0 | 855 | | int ordersToOldResult = await _orderServices.UpdateOrders(ordersForOneC); |
| | 0 | 856 | | if (ordersToOldResult > 0) |
| | | 857 | | { |
| | 0 | 858 | | msg += $"<div class='alert alert-info'>" + |
| | 0 | 859 | | $"<strong>📊 Обновление статусов</strong><br/>" + |
| | 0 | 860 | | $"Исходный статус восстановлен для <strong>{ordersToOldResult}</strong> заказов" + |
| | 0 | 861 | | $"</div>"; |
| | | 862 | | } |
| | | 863 | | |
| | 0 | 864 | | TempData["TransactionResult"] = msg; |
| | 0 | 865 | | await _orderServices.SetZasylAsync(orderSupplierMap ?? new Dictionary<int, string?>()); |
| | 0 | 866 | | return Json(new |
| | 0 | 867 | | { |
| | 0 | 868 | | redirectTo = BuildOrdersIndexRedirectUrl(page, ordersToTransaction) |
| | 0 | 869 | | }); |
| | | 870 | | } |
| | | 871 | | else |
| | | 872 | | { |
| | 0 | 873 | | oneCHash = successfulTransfers |
| | | 874 | | .Select(h => h.TransactionId) |
| | | 875 | | .Where(id => id != null) |
| | | 876 | | .Select(id => id!); |
| | 0 | 877 | | if (ordersForOneCDraft.Any()) |
| | | 878 | | { |
| | 0 | 879 | | draftTransferResult = await _oneCExpenseManager.SaleStockByWarehouses( |
| | 0 | 880 | | ordersForOneCDraft, |
| | 0 | 881 | | TransactionType.ShippedToClient, |
| | 0 | 882 | | contractLink, |
| | 0 | 883 | | transactionComment, |
| | 0 | 884 | | vatRate, |
| | 0 | 885 | | oneCOperationDate, |
| | 0 | 886 | | userName, |
| | 0 | 887 | | oneCOrganizationInn, |
| | 0 | 888 | | createAsDraft: true, |
| | 0 | 889 | | draftReason: "недостаточно остатка в 1С. Проведите после оприходования товара."); |
| | 0 | 890 | | transferResult.AddRange(draftTransferResult); |
| | | 891 | | } |
| | | 892 | | |
| | 0 | 893 | | oneCResult = OneCTransactionSummaryHelper.WrapSection( |
| | 0 | 894 | | "Проведённые документы 1С", |
| | 0 | 895 | | OneCTransactionSummaryHelper.BuildAlertSummary(ordersForOneC, successfulTransfe |
| | 0 | 896 | | + OneCTransactionSummaryHelper.WrapSection( |
| | 0 | 897 | | "Черновики 1С по дефицитным позициям", |
| | 0 | 898 | | OneCTransactionSummaryHelper.BuildAlertSummary(ordersForOneCDraft, draftTransfe |
| | 0 | 899 | | + OneCStockGate.PartialPostingNotice( |
| | 0 | 900 | | stockCheck, |
| | 0 | 901 | | ordersForOneC.Count, |
| | | 902 | | draftTransferResult.Any(result => result.Success)) |
| | 0 | 903 | | + OneCStockGate.SkippedNotice(stockCheck); |
| | | 904 | | } |
| | 0 | 905 | | } |
| | | 906 | | else |
| | | 907 | | { |
| | 0 | 908 | | if (processIn1C && ordersForOneCDraft.Any()) |
| | | 909 | | { |
| | 0 | 910 | | draftTransferResult = await _oneCExpenseManager.SaleStockByWarehouses( |
| | 0 | 911 | | ordersForOneCDraft, |
| | 0 | 912 | | TransactionType.ShippedToClient, |
| | 0 | 913 | | contractLink, |
| | 0 | 914 | | transactionComment, |
| | 0 | 915 | | vatRate, |
| | 0 | 916 | | oneCOperationDate, |
| | 0 | 917 | | userName, |
| | 0 | 918 | | oneCOrganizationInn, |
| | 0 | 919 | | createAsDraft: true, |
| | 0 | 920 | | draftReason: "недостаточно остатка в 1С. Проведите после оприходования товара."); |
| | 0 | 921 | | transferResult.AddRange(draftTransferResult); |
| | | 922 | | } |
| | | 923 | | |
| | 0 | 924 | | oneCResult = stockCheck.HasBlocking |
| | 0 | 925 | | ? OneCTransactionSummaryHelper.WrapSection( |
| | 0 | 926 | | "Черновики 1С по дефицитным позициям", |
| | 0 | 927 | | OneCTransactionSummaryHelper.BuildAlertSummary(ordersForOneCDraft, draftTransferResult)) |
| | 0 | 928 | | + OneCStockGate.PartialPostingNotice( |
| | 0 | 929 | | stockCheck, |
| | 0 | 930 | | 0, |
| | | 931 | | draftTransferResult.Any(result => result.Success)) |
| | 0 | 932 | | : OneCStockGate.NoDocumentNotice(stockCheck); |
| | | 933 | | } |
| | | 934 | | |
| | 0 | 935 | | if (ordersToBackResult > 0) |
| | | 936 | | { |
| | 0 | 937 | | oneCResult += $"<div class='alert alert-info'>" + |
| | 0 | 938 | | $"<strong>📊 Обновление статусов</strong><br/>" + |
| | 0 | 939 | | $"Статус 'Утерян реализатором' установлен для <strong>{ordersToBackResult}</strong> за |
| | 0 | 940 | | $"</div>"; |
| | | 941 | | } |
| | | 942 | | |
| | 0 | 943 | | await NotificationService.NotifyAllAsync(oneCResult); |
| | 0 | 944 | | var historyWindowStart = DateTime.Now; |
| | 0 | 945 | | var commentWithOneC = comment; |
| | 0 | 946 | | TransactionCommentAiSchedulingHelper.AppendOneCCommentDetails( |
| | 0 | 947 | | aiFlow, |
| | 0 | 948 | | ref commentWithOneC, |
| | 0 | 949 | | ordersToTransaction, |
| | 0 | 950 | | transferResult); |
| | 0 | 951 | | var commentForDb = TransactionCommentAiSchedulingHelper.ResolveDatabaseComment(aiFlow, commentWithOneC); |
| | | 952 | | |
| | 0 | 953 | | (changeCount, dateTime, transactionId) = await _transaction.CreateShippedToClientTransaction(ordersToTra |
| | 0 | 954 | | userName, |
| | 0 | 955 | | createAt, |
| | 0 | 956 | | commentForDb); |
| | | 957 | | int cancelCount = ordersToTransaction.Count(o => o.AppStatus?.Name == "Отменен"); |
| | | 958 | | |
| | 0 | 959 | | if (changeCount > 0) |
| | | 960 | | { |
| | 0 | 961 | | if (processIn1C && transferResult.Count > 0) |
| | | 962 | | { |
| | 0 | 963 | | await OrderHistoryOneCPostingRecorder.TryRecordExpenseAsync( |
| | 0 | 964 | | HttpContext.RequestServices, |
| | 0 | 965 | | ordersForOneC, |
| | | 966 | | transferResult.Where(result => !result.IsDraft).ToList(), |
| | 0 | 967 | | userName, |
| | 0 | 968 | | transactionId > 0 ? (int?)transactionId : null, |
| | 0 | 969 | | historyWindowStart); |
| | | 970 | | } |
| | | 971 | | |
| | | 972 | | var fromStatusByOrderId = originalStatuses.ToDictionary(x => x.Key, x => x.Value.Id); |
| | 0 | 973 | | var statusTransitionHistoryService = HttpContext.RequestServices.GetService<IStatusTransitionHistory |
| | 0 | 974 | | if (postTransitions.Any() && statusTransitionHistoryService != null) |
| | | 975 | | { |
| | 0 | 976 | | var user = await _userCacheService.GetCachedUserAsync(User); |
| | 0 | 977 | | await statusTransitionHistoryService.RecordStatusTransitionHistoryAsync( |
| | 0 | 978 | | TransactionType.ShippedToClient, |
| | 0 | 979 | | ordersToTransaction, |
| | 0 | 980 | | fromStatusByOrderId, |
| | 0 | 981 | | postTransitions, |
| | 0 | 982 | | commentForDb, |
| | 0 | 983 | | user?.AccessProfileId, |
| | 0 | 984 | | createAt, |
| | 0 | 985 | | null); |
| | | 986 | | } |
| | | 987 | | |
| | 0 | 988 | | await _cacheUpdater.Update(_cache); |
| | | 989 | | var statusNameForMessage = string.Join(", ", ordersToTransaction.Select(o => o.AppStatus?.Name).Wher |
| | 0 | 990 | | if (string.IsNullOrEmpty(statusNameForMessage) && postTransitions.Any()) |
| | | 991 | | statusNameForMessage = string.Join(", ", postTransitions.Select(t => t.ToStatus?.Name).Where(n = |
| | 0 | 992 | | if (string.IsNullOrEmpty(statusNameForMessage)) statusNameForMessage = "Отгружен клиенту"; |
| | 0 | 993 | | string msg = TransactionConfig.BuildStandardSuccessMessage( |
| | 0 | 994 | | changeCount, |
| | 0 | 995 | | cancelCount, |
| | 0 | 996 | | dateTime, |
| | 0 | 997 | | userName, |
| | 0 | 998 | | commentForDb, |
| | 0 | 999 | | ordersToTransaction, |
| | 0 | 1000 | | statusNameForMessage, |
| | 0 | 1001 | | oneCResult); |
| | 0 | 1002 | | if (!string.IsNullOrWhiteSpace(skippedOrdersMessage)) |
| | | 1003 | | { |
| | 0 | 1004 | | msg += skippedOrdersMessage; |
| | | 1005 | | } |
| | 0 | 1006 | | await NotificationService.NotifyAllAsync(msg); |
| | 0 | 1007 | | TempData["TransactionResult"] = msg; |
| | 0 | 1008 | | await CompleteTrackingAsync(trackingKey, transactionId, ordersToTransaction.Count, commentForDb); |
| | | 1009 | | |
| | 0 | 1010 | | if (aiFlow.ScheduleAiComment && aiFlow.Builder != null && transactionId > 0) |
| | | 1011 | | { |
| | 0 | 1012 | | await TransactionCommentAiSchedulingHelper.TryScheduleAsync( |
| | 0 | 1013 | | HttpContext.RequestServices, |
| | 0 | 1014 | | aiFlow.Builder, |
| | 0 | 1015 | | true, |
| | 0 | 1016 | | (int)transactionId, |
| | 0 | 1017 | | ordersToTransaction, |
| | 0 | 1018 | | fromStatusByOrderId, |
| | 0 | 1019 | | _context, |
| | 0 | 1020 | | ct: HttpContext.RequestAborted); |
| | | 1021 | | } |
| | 0 | 1022 | | } |
| | | 1023 | | |
| | 0 | 1024 | | await _transactionCache.Update(); |
| | | 1025 | | |
| | 0 | 1026 | | if (ordersNotFoundInOzone.Count > 0) |
| | | 1027 | | { |
| | 0 | 1028 | | TempData["OrdersNotFoundInOzone"] = |
| | | 1029 | | $"Заказы не найдены в системе Ozon - {ordersNotFoundInOzone.Aggregate((x, y) => x + ", " + y)}"; |
| | | 1030 | | } |
| | 0 | 1031 | | } |
| | | 1032 | | |
| | 0 | 1033 | | ClearSelectedIdsSession(); |
| | 0 | 1034 | | foreach (var order in orders) |
| | | 1035 | | { |
| | 0 | 1036 | | var cookieKey = $"PurchasePrice_{order.Id}"; |
| | 0 | 1037 | | if (Request.Cookies.ContainsKey(cookieKey)) |
| | | 1038 | | { |
| | 0 | 1039 | | Response.Cookies.Delete(cookieKey); |
| | | 1040 | | } |
| | | 1041 | | } |
| | | 1042 | | |
| | 0 | 1043 | | await _orderServices.SetZasylAsync(orderSupplierMap ?? new Dictionary<int, string?>()); |
| | 0 | 1044 | | return Json(new |
| | 0 | 1045 | | { redirectTo = BuildOrdersIndexRedirectUrl(page, processedOrdersForRedirect) }); |
| | | 1046 | | } |
| | 0 | 1047 | | catch (TransactionException ex) |
| | | 1048 | | { |
| | 0 | 1049 | | TempData["TransactionErrorCode"] = ex.Code.ToString(); |
| | 0 | 1050 | | TempData["ErorrResult"] = ex.Message; |
| | 0 | 1051 | | await _orderServices.SetZasylAsync(orderSupplierMap ?? new Dictionary<int, string?>()); |
| | 0 | 1052 | | return Json(new |
| | 0 | 1053 | | { redirectTo = Url.Action("Index", "Orders", new { sortOrder = GetSortStateCookie(), page }) }); |
| | | 1054 | | } |
| | 0 | 1055 | | catch (Exception ex) |
| | | 1056 | | { |
| | 0 | 1057 | | TempData["TransactionErrorCode"] = TransactionErrorCode.Unknown.ToString(); |
| | 0 | 1058 | | TempData["ErorrResult"] = $"Не удалось провести выбранные заказы ({ex.Message})"; |
| | 0 | 1059 | | await _orderServices.SetZasylAsync(orderSupplierMap ?? new Dictionary<int, string?>()); |
| | 0 | 1060 | | return Json(new |
| | 0 | 1061 | | { redirectTo = Url.Action("Index", "Orders", new { sortOrder = GetSortStateCookie(), page }) }); |
| | | 1062 | | } |
| | 0 | 1063 | | } |
| | | 1064 | | |
| | | 1065 | | [HttpPost] |
| | | 1066 | | public IActionResult ClearSelectedIdsSession() |
| | | 1067 | | { |
| | 0 | 1068 | | HttpContext.Session.SetString("selectedIds", JsonConvert.SerializeObject(new List<int>())); |
| | 0 | 1069 | | return Ok(); |
| | | 1070 | | } |
| | | 1071 | | |
| | | 1072 | | private static DateTime GetDefaultOneCOperationDate() |
| | | 1073 | | { |
| | 0 | 1074 | | return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, RussianTimeZone); |
| | | 1075 | | } |
| | | 1076 | | |
| | | 1077 | | private static DateTime GetEffectiveOneCOperationDate(DateTime? oneCOperationDate) |
| | | 1078 | | { |
| | 0 | 1079 | | return oneCOperationDate ?? GetDefaultOneCOperationDate(); |
| | | 1080 | | } |
| | | 1081 | | |
| | | 1082 | | private async Task AttachTrackingKeyAsync(MultiplayEditOrderViewModel model, TransactionType transactionType) |
| | | 1083 | | { |
| | 0 | 1084 | | if (model.Orders == null || !model.Orders.Any()) |
| | 0 | 1085 | | return; |
| | 0 | 1086 | | var trackingService = HttpContext.RequestServices.GetService<ITransactionTimeTrackingService>(); |
| | 0 | 1087 | | if (trackingService == null) |
| | 0 | 1088 | | return; |
| | 0 | 1089 | | model.TrackingKey = await trackingService.StartSessionAsync( |
| | 0 | 1090 | | User.Identity?.Name ?? "unknown", |
| | 0 | 1091 | | model.User?.AccessProfileId, |
| | 0 | 1092 | | transactionType, |
| | | 1093 | | model.Orders.Select(o => o.Id), |
| | 0 | 1094 | | $"{Request.Path}{Request.QueryString}", |
| | 0 | 1095 | | HttpContext.Connection.RemoteIpAddress?.ToString(), |
| | 0 | 1096 | | Request.Headers.UserAgent.ToString()); |
| | 0 | 1097 | | } |
| | | 1098 | | |
| | | 1099 | | private async Task CompleteTrackingAsync(string? trackingKey, long transactionId, int ordersCount, string? comment) |
| | | 1100 | | { |
| | 0 | 1101 | | if (string.IsNullOrWhiteSpace(trackingKey) || transactionId <= 0) |
| | 0 | 1102 | | return; |
| | 0 | 1103 | | var trackingService = HttpContext.RequestServices.GetService<ITransactionTimeTrackingService>(); |
| | 0 | 1104 | | if (trackingService == null) |
| | 0 | 1105 | | return; |
| | 0 | 1106 | | await trackingService.CompleteSessionAsync( |
| | 0 | 1107 | | trackingKey, |
| | 0 | 1108 | | transactionId, |
| | 0 | 1109 | | ordersCount, |
| | 0 | 1110 | | comment, |
| | 0 | 1111 | | $"/Studio2/Transaction/GetTransaction?transactionId={transactionId}"); |
| | 0 | 1112 | | } |
| | | 1113 | | |
| | | 1114 | | private string BuildOrdersIndexRedirectUrl(int page, IEnumerable<Order> orders) |
| | | 1115 | | { |
| | 0 | 1116 | | var shipmentNumbers = orders |
| | 0 | 1117 | | .Select(o => o.ShipmentNumber?.Trim()) |
| | 0 | 1118 | | .Where(n => !string.IsNullOrWhiteSpace(n)) |
| | 0 | 1119 | | .Distinct(StringComparer.OrdinalIgnoreCase) |
| | 0 | 1120 | | .ToList(); |
| | | 1121 | | |
| | 0 | 1122 | | var filterData = new OrderFilterModel |
| | 0 | 1123 | | { |
| | 0 | 1124 | | ShipmentNumber = shipmentNumbers.Any() ? string.Join(",", shipmentNumbers) : null, |
| | 0 | 1125 | | GroupByShipment = true, |
| | 0 | 1126 | | IncludeArchived = false, |
| | 0 | 1127 | | SyncClientStatusesOnLoad = false, |
| | 0 | 1128 | | OnlyWithZasyl = false |
| | 0 | 1129 | | }; |
| | | 1130 | | |
| | 0 | 1131 | | Response.Cookies.Append("FilterData", JsonConvert.SerializeObject(filterData)); |
| | | 1132 | | |
| | 0 | 1133 | | return Url.Action("Index", "Orders", new |
| | 0 | 1134 | | { |
| | 0 | 1135 | | sortOrder = GetSortStateCookie(), |
| | 0 | 1136 | | page |
| | 0 | 1137 | | }) |
| | 0 | 1138 | | ?? "/Studio2/Orders"; |
| | | 1139 | | } |
| | | 1140 | | |
| | | 1141 | | private static string? BuildSkippedOrdersWarningHtml(IEnumerable<string> warningMessages) |
| | | 1142 | | { |
| | 0 | 1143 | | var warnings = warningMessages |
| | 0 | 1144 | | .Where(m => !string.IsNullOrWhiteSpace(m)) |
| | 0 | 1145 | | .Distinct(StringComparer.OrdinalIgnoreCase) |
| | 0 | 1146 | | .ToList(); |
| | | 1147 | | |
| | 0 | 1148 | | if (!warnings.Any()) |
| | | 1149 | | { |
| | 0 | 1150 | | return null; |
| | | 1151 | | } |
| | | 1152 | | |
| | 0 | 1153 | | return "<ul class='mb-0'>" + string.Join("", warnings.Select(w => $"<li>{System.Net.WebUtility.HtmlEncode(w)}</l |
| | | 1154 | | } |
| | | 1155 | | |
| | | 1156 | | private OrderSortState GetSortStateCookie() |
| | | 1157 | | { |
| | 0 | 1158 | | var cookie = Request.Cookies["SortState"]; |
| | 0 | 1159 | | if (!string.IsNullOrEmpty(cookie) && Enum.TryParse<OrderSortState>(cookie, out var saved)) |
| | 0 | 1160 | | return saved; |
| | 0 | 1161 | | return OrderSortState.StandardState; |
| | | 1162 | | } |
| | | 1163 | | } |