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