| | | 1 | | using System.Net; |
| | | 2 | | using OzonDomains; |
| | | 3 | | using OzonDomains.Models; |
| | | 4 | | using OzonOrdersWeb.Areas.Studio2.Controllers.Base; |
| | | 5 | | using OzonOrdersWeb.Areas.Studio2.Helpers; |
| | | 6 | | using OzonOrdersWeb.Areas.Studio2.Models.Transactions; |
| | | 7 | | using OzonOrdersWeb.WebServices.Transactions.Grid.Definitions; |
| | | 8 | | using Services.ApiServices._1CApi; |
| | | 9 | | using Services.ApiServices._1CApi.DTO; |
| | | 10 | | using Services.ApiServices._1CApi.Models; |
| | | 11 | | using Services.BackgroundJobsServices; |
| | | 12 | | using Services.DataServices; |
| | | 13 | | |
| | | 14 | | namespace OzonOrdersWeb.WebServices.Transactions.Grid.Commit.Steps; |
| | | 15 | | |
| | | 16 | | /// <summary> |
| | | 17 | | /// Документ 1С по <see cref="TransactionDefinition.OneCDocument"/>: |
| | | 18 | | /// перемещение (склады на документ / на заказ / по маппингу засыла / «перевёрнутое»), приходная, расходная накладная. |
| | | 19 | | /// Дефицитные позиции (после OneCStockCheckStep) — отдельным черновиком. Поведение при ошибке повторяет классические |
| | | 20 | | /// контроллеры: перемещение/расходная откатывают статусы, приходная — нет (заказ отгружен, документа просто нет). |
| | | 21 | | /// </summary> |
| | | 22 | | public sealed class OneCDocumentStep : ITransactionCommitStep |
| | | 23 | | { |
| | | 24 | | private const string DraftReason = "Недостаточно остатка в 1С. Проведите после оприходования товара."; |
| | | 25 | | |
| | | 26 | | private readonly OrdersDataServices _orders; |
| | | 27 | | private readonly OneCTransferManager? _transfer; |
| | | 28 | | private readonly OneCReceiptManager? _receipt; |
| | | 29 | | private readonly OneCExpenseManager? _expense; |
| | | 30 | | private readonly WarehouseMappingDataServices? _mappings; |
| | | 31 | | private readonly WarehouseSelectionResolver? _warehouseResolver; |
| | | 32 | | private readonly ILogger<OneCDocumentStep> _logger; |
| | | 33 | | |
| | 0 | 34 | | public OneCDocumentStep( |
| | 0 | 35 | | OrdersDataServices orders, |
| | 0 | 36 | | ILogger<OneCDocumentStep> logger, |
| | 0 | 37 | | OneCTransferManager? transfer = null, |
| | 0 | 38 | | OneCReceiptManager? receipt = null, |
| | 0 | 39 | | OneCExpenseManager? expense = null, |
| | 0 | 40 | | WarehouseMappingDataServices? mappings = null, |
| | 0 | 41 | | WarehouseSelectionResolver? warehouseResolver = null) |
| | | 42 | | { |
| | 0 | 43 | | _orders = orders; |
| | 0 | 44 | | _logger = logger; |
| | 0 | 45 | | _transfer = transfer; |
| | 0 | 46 | | _receipt = receipt; |
| | 0 | 47 | | _expense = expense; |
| | 0 | 48 | | _mappings = mappings; |
| | 0 | 49 | | _warehouseResolver = warehouseResolver; |
| | 0 | 50 | | } |
| | | 51 | | |
| | 0 | 52 | | public string Key => TransactionCommitStepKeys.OneCDocument; |
| | | 53 | | |
| | | 54 | | public async Task ExecuteAsync(TransactionCommitContext ctx) |
| | | 55 | | { |
| | 0 | 56 | | var d = ctx.Definition; |
| | 0 | 57 | | if (!ctx.ProcessIn1C || ctx.OneCHandledExternally || d.OneCDocument == OneCDocumentKind.None) |
| | | 58 | | { |
| | 0 | 59 | | return; |
| | | 60 | | } |
| | | 61 | | |
| | 0 | 62 | | var forOneC = ctx.OrdersToTransaction |
| | | 63 | | .Where(o => !ctx.StatusOnlyOrderIds.Contains(o.Id) && (o.Quantity ?? 0) > 0) |
| | 0 | 64 | | .ToList(); |
| | 0 | 65 | | if (forOneC.Count == 0) |
| | | 66 | | { |
| | 0 | 67 | | if (ctx.OrdersToTransaction.Count > 0) |
| | | 68 | | { |
| | 0 | 69 | | ctx.SummaryBlocks.Add("<div class='alert alert-info mb-0'><strong>Документ 1С не создавался</strong><br/ |
| | | 70 | | } |
| | | 71 | | |
| | 0 | 72 | | return; |
| | | 73 | | } |
| | | 74 | | |
| | 0 | 75 | | await ctx.StageAsync("onec", "Создаём документы в 1С", forOneC.Count == 1 ? "1 заказ" : $"заказов: {forOneC.Coun |
| | | 76 | | var originalStatuses = forOneC.ToDictionary(o => o.Id, o => ctx.FromStatusByOrderId.TryGetValue(o.Id, out var s) |
| | | 77 | | |
| | | 78 | | try |
| | | 79 | | { |
| | 0 | 80 | | switch (d.OneCDocument) |
| | | 81 | | { |
| | | 82 | | case OneCDocumentKind.Transfer: |
| | 0 | 83 | | await TransferAsync(ctx, forOneC); |
| | 0 | 84 | | break; |
| | | 85 | | case OneCDocumentKind.TransferReversed: |
| | 0 | 86 | | await TransferReversedAsync(ctx, forOneC); |
| | 0 | 87 | | break; |
| | | 88 | | case OneCDocumentKind.TransferByWarehouses: |
| | 0 | 89 | | await TransferByWarehousesAsync(ctx, forOneC); |
| | 0 | 90 | | break; |
| | | 91 | | case OneCDocumentKind.Receipt: |
| | 0 | 92 | | await ReceiptAsync(ctx, forOneC); |
| | 0 | 93 | | return; // приход не откатывает статусы и не прерывает проведение |
| | | 94 | | case OneCDocumentKind.Expense: |
| | 0 | 95 | | await ExpenseAsync(ctx, forOneC); |
| | | 96 | | break; |
| | | 97 | | } |
| | 0 | 98 | | } |
| | 0 | 99 | | catch (TransactionException) |
| | | 100 | | { |
| | 0 | 101 | | throw; |
| | | 102 | | } |
| | 0 | 103 | | catch (Exception ex) |
| | | 104 | | { |
| | 0 | 105 | | _logger.LogError(ex, "TransactionsGrid {Slug}: ошибка 1С", d.Slug); |
| | 0 | 106 | | await RestoreStatusesAsync(ctx, forOneC, originalStatuses); |
| | 0 | 107 | | throw new TransactionException(TransactionErrorCode.OneCIntegrationFailed, $"Ошибка обработки в 1С: {ex.Mess |
| | | 108 | | } |
| | | 109 | | |
| | | 110 | | var failed = ctx.OneCResponses.Where(r => !r.Success).ToList(); |
| | 0 | 111 | | if (failed.Count > 0) |
| | | 112 | | { |
| | 0 | 113 | | await RestoreStatusesAsync(ctx, forOneC, originalStatuses); |
| | | 114 | | var messages = string.Join("; ", failed.Select(r => r.Message).Distinct().Take(3).Select(m => WebUtility.Htm |
| | 0 | 115 | | throw new TransactionException(TransactionErrorCode.OneCIntegrationFailed, |
| | 0 | 116 | | $"1С: успешно {ctx.OneCResponses.Count - failed.Count} из {ctx.OneCResponses.Count} документов, с ошибка |
| | | 117 | | } |
| | | 118 | | |
| | 0 | 119 | | AppendSummary(ctx, forOneC); |
| | 0 | 120 | | } |
| | | 121 | | |
| | | 122 | | // ---------- Перемещение: склады на документ или на заказ ---------- |
| | | 123 | | |
| | | 124 | | private async Task TransferAsync(TransactionCommitContext ctx, List<Order> forOneC) |
| | | 125 | | { |
| | 0 | 126 | | var manager = _transfer ?? throw new TransactionException(TransactionErrorCode.OneCUnavailable, "Перемещения 1С |
| | 0 | 127 | | var d = ctx.Definition; |
| | 0 | 128 | | var doc = ctx.Request.Document; |
| | 0 | 129 | | var senderByOrder = OneCStockCheckStep.SenderByOrder(ctx); |
| | 0 | 130 | | var recipientByOrder = OneCStockCheckStep.RecipientByOrder(ctx); |
| | 0 | 131 | | var perOrderSender = d.WarehouseMode == WarehouseMode.PerOrderBoth; |
| | 0 | 132 | | var perOrderRecipient = d.WarehouseMode is WarehouseMode.PerOrderBoth or WarehouseMode.PerOrderRecipient; |
| | | 133 | | |
| | 0 | 134 | | var globalSender = doc.SenderWarehouse?.Trim(); |
| | 0 | 135 | | var globalRecipient = doc.RecipientWarehouse?.Trim(); |
| | | 136 | | |
| | 0 | 137 | | if (d.WarehouseMode == WarehouseMode.GlobalPair && !ctx.User.IsAdmin && _warehouseResolver != null) |
| | | 138 | | { |
| | | 139 | | // Не-администратору склад-отправитель определяется автоматически (как в WarehouseTransfer). |
| | 0 | 140 | | var resolved = await _warehouseResolver.ResolveSenderWarehouseAsync(forOneC); |
| | 0 | 141 | | if (resolved.HasErrors || string.IsNullOrWhiteSpace(resolved.SenderWarehouseName)) |
| | | 142 | | { |
| | 0 | 143 | | throw new TransactionException(TransactionErrorCode.OneCSenderWarehouseMissing, string.Join(" ", resolve |
| | | 144 | | } |
| | | 145 | | |
| | 0 | 146 | | globalSender = resolved.SenderWarehouseName; |
| | | 147 | | } |
| | | 148 | | |
| | 0 | 149 | | if (string.IsNullOrWhiteSpace(globalSender) && !perOrderSender) |
| | | 150 | | { |
| | 0 | 151 | | throw new TransactionException(TransactionErrorCode.OneCSenderWarehouseMissing, "Не указан склад отправителя |
| | | 152 | | } |
| | | 153 | | |
| | 0 | 154 | | if (string.IsNullOrWhiteSpace(globalRecipient) && !perOrderRecipient) |
| | | 155 | | { |
| | 0 | 156 | | throw new TransactionException(TransactionErrorCode.OneCRecipientWarehouseMissing, "Не указан склад получате |
| | | 157 | | } |
| | | 158 | | |
| | 0 | 159 | | if (!perOrderSender && !perOrderRecipient && string.Equals(globalSender, globalRecipient, StringComparison.Ordin |
| | | 160 | | { |
| | 0 | 161 | | throw new TransactionException(TransactionErrorCode.OneCWarehousesMustDiffer, "Склад отправителя и получател |
| | | 162 | | } |
| | | 163 | | |
| | 0 | 164 | | if (perOrderRecipient) |
| | | 165 | | { |
| | | 166 | | var withoutRecipient = forOneC.Where(o => !recipientByOrder.ContainsKey(o.Id)).Select(o => o.ShipmentNumber) |
| | 0 | 167 | | if (withoutRecipient.Count > 0) |
| | | 168 | | { |
| | 0 | 169 | | throw new TransactionException(TransactionErrorCode.OneCRecipientWarehouseMissing, $"Не удалось определи |
| | | 170 | | } |
| | | 171 | | } |
| | | 172 | | |
| | 0 | 173 | | if (perOrderSender) |
| | | 174 | | { |
| | | 175 | | var withoutSender = forOneC.Where(o => !senderByOrder.ContainsKey(o.Id)).Select(o => o.ShipmentNumber).ToLis |
| | 0 | 176 | | if (withoutSender.Count > 0) |
| | | 177 | | { |
| | 0 | 178 | | throw new TransactionException(TransactionErrorCode.OneCSenderWarehouseMissing, $"Не удалось определить |
| | | 179 | | } |
| | | 180 | | } |
| | | 181 | | |
| | 0 | 182 | | string SenderOf(Order o) => perOrderSender ? senderByOrder[o.Id] : globalSender!; |
| | 0 | 183 | | string RecipientOf(Order o) => perOrderRecipient ? recipientByOrder[o.Id] : globalRecipient!; |
| | | 184 | | |
| | 0 | 185 | | var groups = forOneC.GroupBy(o => SenderOf(o).ToUpperInvariant() + "" + RecipientOf(o).ToUpperInvariant()).ToLi |
| | 0 | 186 | | foreach (var group in groups) |
| | | 187 | | { |
| | 0 | 188 | | var first = group.First(); |
| | 0 | 189 | | var sender = SenderOf(first); |
| | 0 | 190 | | var recipient = RecipientOf(first); |
| | 0 | 191 | | if (string.Equals(sender, recipient, StringComparison.OrdinalIgnoreCase)) |
| | | 192 | | { |
| | 0 | 193 | | throw new TransactionException(TransactionErrorCode.OneCWarehousesMustDiffer, $"Склад отправителя и полу |
| | | 194 | | } |
| | | 195 | | |
| | 0 | 196 | | var groupOrders = group.ToList(); |
| | 0 | 197 | | var result = new List<OneCResponse>(); |
| | 0 | 198 | | var toPost = groupOrders.Where(o => !ctx.StockBlockedOrderIds.Contains(o.Id)).ToList(); |
| | 0 | 199 | | var toDraft = groupOrders.Where(o => ctx.StockBlockedOrderIds.Contains(o.Id)).ToList(); |
| | 0 | 200 | | if (toPost.Count > 0) |
| | | 201 | | { |
| | 0 | 202 | | result.AddRange(await manager.TransferStockWithCustomWarehouses(toPost, sender, recipient, d.Type, doc.O |
| | | 203 | | } |
| | | 204 | | |
| | 0 | 205 | | if (toDraft.Count > 0) |
| | | 206 | | { |
| | 0 | 207 | | result.AddRange(await manager.TransferStockWithCustomWarehouses(toDraft, sender, recipient, d.Type, doc. |
| | | 208 | | } |
| | | 209 | | |
| | 0 | 210 | | ctx.OneCGroups.Add((sender, recipient, groupOrders, result)); |
| | 0 | 211 | | ctx.OneCResponses.AddRange(result); |
| | 0 | 212 | | } |
| | | 213 | | |
| | 0 | 214 | | if (groups.Count == 1) |
| | | 215 | | { |
| | | 216 | | // Одно направление — история и комментарий как у простого перемещения. |
| | 0 | 217 | | ctx.OneCSender = ctx.OneCGroups[0].Sender; |
| | 0 | 218 | | ctx.OneCRecipient = ctx.OneCGroups[0].Recipient; |
| | 0 | 219 | | if (!perOrderSender && !perOrderRecipient) |
| | | 220 | | { |
| | 0 | 221 | | ctx.OneCGroups.Clear(); |
| | | 222 | | } |
| | | 223 | | } |
| | | 224 | | else |
| | | 225 | | { |
| | | 226 | | ctx.OneCSender = string.Join(", ", ctx.OneCGroups.Select(g => g.Sender).Distinct(StringComparer.OrdinalIgnor |
| | | 227 | | ctx.OneCRecipient = string.Join(", ", ctx.OneCGroups.Select(g => g.Recipient).Distinct(StringComparer.Ordina |
| | | 228 | | } |
| | 0 | 229 | | } |
| | | 230 | | |
| | | 231 | | // ---------- Перемещение по маппингу (ShippedToSeller) ---------- |
| | | 232 | | |
| | | 233 | | private async Task TransferByWarehousesAsync(TransactionCommitContext ctx, List<Order> forOneC) |
| | | 234 | | { |
| | 0 | 235 | | var manager = _transfer ?? throw new TransactionException(TransactionErrorCode.OneCUnavailable, "Перемещения 1С |
| | 0 | 236 | | var mappings = _mappings != null ? await _mappings.GetWarehouseMappings() : null; |
| | | 237 | | var zasylRaw = OneCStockCheckStep.ZasylByOrder(ctx).ToDictionary(kv => kv.Key, kv => (string?)kv.Value); |
| | 0 | 238 | | var senderMap = ShippedToSellerZasylResolver.BuildSenderMap(forOneC, mappings, zasylRaw); |
| | 0 | 239 | | var map = senderMap.Count > 0 ? new Dictionary<int, string>(senderMap) : null; |
| | | 240 | | |
| | 0 | 241 | | await PostWithDraftsAsync(ctx, forOneC, (orders, draft) => manager.TransferStockByWarehouses( |
| | 0 | 242 | | orders, ctx.Definition.Type, map, ctx.Request.Document.OperationDate, ctx.User.Name, ctx.Request.OneCOrganiz |
| | 0 | 243 | | } |
| | | 244 | | |
| | | 245 | | // ---------- Перемещение с «перевёрнутыми» складами (ShippedToCommissioner, ReturnToWarehouse) ---------- |
| | | 246 | | |
| | | 247 | | private async Task TransferReversedAsync(TransactionCommitContext ctx, List<Order> forOneC) |
| | | 248 | | { |
| | 0 | 249 | | var manager = _transfer ?? throw new TransactionException(TransactionErrorCode.OneCUnavailable, "Перемещения 1С |
| | 0 | 250 | | var map = OneCStockCheckStep.ZasylByOrder(ctx); |
| | 0 | 251 | | if (ctx.Definition.Type == TransactionType.ShippedToCommissioner && _mappings != null) |
| | | 252 | | { |
| | 0 | 253 | | var mappings = await _mappings.GetWarehouseMappings(); |
| | 0 | 254 | | foreach (var o in forOneC) |
| | | 255 | | { |
| | 0 | 256 | | var sender = ShippedToCommissionerWarehouseResolver.ResolveSenderWarehouse(o, mappings); |
| | 0 | 257 | | if (!string.IsNullOrWhiteSpace(sender)) |
| | | 258 | | { |
| | 0 | 259 | | map[o.Id] = sender; |
| | | 260 | | } |
| | | 261 | | } |
| | | 262 | | } |
| | | 263 | | |
| | 0 | 264 | | await PostWithDraftsAsync(ctx, forOneC, (orders, draft) => manager.TransferStockByWarehousesWithReversedWarehous |
| | 0 | 265 | | orders, ctx.Definition.Type, map, ctx.Request.Document.OperationDate, ctx.User.Name, ctx.Request.OneCOrganiz |
| | 0 | 266 | | } |
| | | 267 | | |
| | | 268 | | private static async Task PostWithDraftsAsync(TransactionCommitContext ctx, List<Order> forOneC, Func<List<Order>, b |
| | | 269 | | { |
| | 0 | 270 | | var toPost = forOneC.Where(o => !ctx.StockBlockedOrderIds.Contains(o.Id)).ToList(); |
| | 0 | 271 | | var toDraft = forOneC.Where(o => ctx.StockBlockedOrderIds.Contains(o.Id)).ToList(); |
| | 0 | 272 | | if (toPost.Count > 0) |
| | | 273 | | { |
| | 0 | 274 | | ctx.OneCResponses.AddRange(await post(toPost, false)); |
| | | 275 | | } |
| | | 276 | | |
| | 0 | 277 | | if (toDraft.Count > 0) |
| | | 278 | | { |
| | 0 | 279 | | ctx.OneCResponses.AddRange(await post(toDraft, true)); |
| | | 280 | | } |
| | 0 | 281 | | } |
| | | 282 | | |
| | | 283 | | // ---------- Приходная накладная ---------- |
| | | 284 | | |
| | | 285 | | private async Task ReceiptAsync(TransactionCommitContext ctx, List<Order> forOneC) |
| | | 286 | | { |
| | 0 | 287 | | var manager = _receipt ?? throw new TransactionException(TransactionErrorCode.OneCUnavailable, "Приходные наклад |
| | 0 | 288 | | var doc = ctx.Request.Document; |
| | 0 | 289 | | var dto = new ShippedBySupplierTransactionDto |
| | 0 | 290 | | { |
| | 0 | 291 | | NDS = doc.Nds ? "true" : "false", |
| | 0 | 292 | | ContractLink = doc.ContractLink, |
| | 0 | 293 | | NumberVh = doc.NumberVh, |
| | 0 | 294 | | DateVh = doc.DateVh ?? DateTime.Now, |
| | 0 | 295 | | TransactionComment = doc.TransactionComment, |
| | 0 | 296 | | VatRate = doc.VatRate, |
| | | 297 | | OrderCountryMap = ctx.Edits.Values.Where(e => !string.IsNullOrWhiteSpace(e.Country)).ToDictionary(e => e.Id, |
| | | 298 | | OrderGtdMap = ctx.Edits.Values.Where(e => !string.IsNullOrWhiteSpace(e.Gtd)).ToDictionary(e => e.Id, e => e. |
| | 0 | 299 | | }; |
| | | 300 | | |
| | | 301 | | try |
| | | 302 | | { |
| | 0 | 303 | | ctx.OneCResponses.AddRange(await manager.CreateReceipts(forOneC, dto, ctx.User.Name, orgInnOverride: ctx.Req |
| | 0 | 304 | | } |
| | 0 | 305 | | catch (Exception ex) |
| | | 306 | | { |
| | | 307 | | // Как в классическом контроллере: статус не откатываем, заказ отгружен — просто нет документа. |
| | 0 | 308 | | ctx.OneCResponses.Add(new OneCResponse { Success = false, Message = $"Исключение при создании документа 1С: |
| | 0 | 309 | | } |
| | | 310 | | |
| | | 311 | | var failed = ctx.OneCResponses.Where(r => !r.Success).ToList(); |
| | 0 | 312 | | if (failed.Count > 0) |
| | | 313 | | { |
| | | 314 | | var lines = failed.Select(r => r.Message).Distinct().Select(m => $"<li class='small'>{WebUtility.HtmlEncode( |
| | 0 | 315 | | ctx.SummaryBlocks.Add($"<div class='alert alert-warning'><strong>⚠️ 1С: документ не создан ({failed.Count} о |
| | 0 | 316 | | ctx.Warnings.Add("Приходная накладная в 1С не создана — см. детали в результате."); |
| | | 317 | | } |
| | | 318 | | |
| | | 319 | | if (ctx.OneCResponses.Any(r => r.Success)) |
| | | 320 | | { |
| | 0 | 321 | | ctx.SummaryBlocks.Add(OneCTransactionSummaryHelper.BuildAlertSummary(forOneC, ctx.OneCResponses)); |
| | | 322 | | } |
| | | 323 | | |
| | 0 | 324 | | AppendCommentDetails(ctx, forOneC, null, null); |
| | 0 | 325 | | } |
| | | 326 | | |
| | | 327 | | // ---------- Расходная накладная ---------- |
| | | 328 | | |
| | | 329 | | private async Task ExpenseAsync(TransactionCommitContext ctx, List<Order> forOneC) |
| | | 330 | | { |
| | 0 | 331 | | var manager = _expense ?? throw new TransactionException(TransactionErrorCode.OneCUnavailable, "Расходные наклад |
| | 0 | 332 | | var doc = ctx.Request.Document; |
| | 0 | 333 | | await PostWithDraftsAsync(ctx, forOneC, (orders, draft) => manager.SaleStockByWarehouses( |
| | 0 | 334 | | orders, ctx.Definition.Type, doc.ContractLink, doc.TransactionComment, doc.VatRate, doc.OperationDate, ctx.U |
| | 0 | 335 | | ctx.Request.OneCOrganizationInn, draft, draft ? DraftReason : null)); |
| | 0 | 336 | | } |
| | | 337 | | |
| | | 338 | | // ---------- Общее ---------- |
| | | 339 | | |
| | | 340 | | private async Task RestoreStatusesAsync(TransactionCommitContext ctx, List<Order> orders, Dictionary<int, int> origi |
| | | 341 | | { |
| | | 342 | | try |
| | | 343 | | { |
| | 0 | 344 | | var statuses = ctx.Resolve<AppStatusDataServices>(); |
| | 0 | 345 | | foreach (var o in orders) |
| | | 346 | | { |
| | 0 | 347 | | if (originalStatuses.TryGetValue(o.Id, out var id) && id > 0) |
| | | 348 | | { |
| | 0 | 349 | | var st = await statuses.GetAppStatusAsync(id); |
| | 0 | 350 | | if (st != null) |
| | | 351 | | { |
| | 0 | 352 | | o.AppStatus = st; |
| | 0 | 353 | | o.AppStatusId = st.Id; |
| | | 354 | | } |
| | | 355 | | } |
| | 0 | 356 | | } |
| | | 357 | | |
| | 0 | 358 | | await _orders.UpdateOrders(orders); |
| | 0 | 359 | | ctx.Warnings.Add($"Исходный статус восстановлен для {orders.Count} заказов."); |
| | 0 | 360 | | } |
| | 0 | 361 | | catch (Exception ex) |
| | | 362 | | { |
| | 0 | 363 | | _logger.LogError(ex, "TransactionsGrid: не удалось откатить статусы после ошибки 1С"); |
| | 0 | 364 | | } |
| | 0 | 365 | | } |
| | | 366 | | |
| | | 367 | | private void AppendSummary(TransactionCommitContext ctx, List<Order> forOneC) |
| | | 368 | | { |
| | 0 | 369 | | var posted = ctx.OneCResponses.Where(r => r.Success && !r.IsDraft).ToList(); |
| | 0 | 370 | | var drafts = ctx.OneCResponses.Where(r => r.IsDraft).ToList(); |
| | 0 | 371 | | var html = ctx.OneCGroups.Count > 0 |
| | 0 | 372 | | ? OneCTransactionSummaryHelper.BuildAlertSummary(forOneC, ctx.OneCResponses, ctx.OneCSender, ctx.OneCRecipie |
| | 0 | 373 | | : OneCTransactionSummaryHelper.WrapSection("Проведённые документы 1С", OneCTransactionSummaryHelper.BuildAle |
| | 0 | 374 | | + (drafts.Count > 0 ? OneCTransactionSummaryHelper.WrapSection("Черновики 1С по дефицитным позициям", OneC |
| | | 375 | | |
| | 0 | 376 | | if (ctx.StockCheck != null) |
| | | 377 | | { |
| | 0 | 378 | | html += OneCStockGate.PartialPostingNotice(ctx.StockCheck, forOneC.Count - forOneC.Count(o => ctx.StockBlock |
| | 0 | 379 | | + OneCStockGate.SkippedNotice(ctx.StockCheck); |
| | | 380 | | } |
| | | 381 | | |
| | 0 | 382 | | ctx.SummaryBlocks.Add(html); |
| | 0 | 383 | | AppendCommentDetails(ctx, forOneC, ctx.OneCSender, ctx.OneCRecipient); |
| | 0 | 384 | | } |
| | | 385 | | |
| | | 386 | | private static void AppendCommentDetails(TransactionCommitContext ctx, List<Order> forOneC, string? sender, string? |
| | | 387 | | { |
| | 0 | 388 | | if (ctx.OneCResponses.Count == 0) |
| | | 389 | | { |
| | 0 | 390 | | return; |
| | | 391 | | } |
| | | 392 | | |
| | 0 | 393 | | var flow = ctx.CommentFlow; |
| | 0 | 394 | | if (flow.ScheduleAiComment && flow.Builder != null) |
| | | 395 | | { |
| | 0 | 396 | | flow.Builder.WithOneCResults(ctx.OneCResponses, sender, recipient); |
| | 0 | 397 | | return; |
| | | 398 | | } |
| | | 399 | | |
| | 0 | 400 | | ctx.TechnicalComment += OneCTransactionSummaryHelper.BuildCommentDetails(forOneC, ctx.OneCResponses, sender, rec |
| | 0 | 401 | | } |
| | | 402 | | |
| | | 403 | | private static string GroupsHtml(TransactionCommitContext ctx) |
| | | 404 | | { |
| | 0 | 405 | | var lines = ctx.OneCGroups.Select(g => |
| | 0 | 406 | | { |
| | 0 | 407 | | var route = WebUtility.HtmlEncode($"{g.Sender} → {g.Recipient}"); |
| | 0 | 408 | | var numbers = string.Join(", ", g.Orders.Select(o => WebUtility.HtmlEncode(o.ShipmentNumber ?? string.Empty) |
| | 0 | 409 | | var docs = g.Result.Where(r => r.Success).Select(r => |
| | 0 | 410 | | { |
| | 0 | 411 | | var label = WebUtility.HtmlEncode(string.IsNullOrWhiteSpace(r.DocumentNumber) ? "Документ 1С" : $"№{r.Do |
| | 0 | 412 | | var link = !string.IsNullOrWhiteSpace(r.TransactionId) && Guid.TryParse(r.TransactionId, out _) |
| | 0 | 413 | | ? $"<a href='/Studio2/OneCDocument/Details?refKey={WebUtility.HtmlEncode(r.TransactionId)}' target=' |
| | 0 | 414 | | : label; |
| | 0 | 415 | | var status = r.IsDraft ? "<span class='badge bg-warning-subtle text-warning'>черновик</span>" |
| | 0 | 416 | | : r.IsPosted ? "<span class='badge bg-success-subtle text-success'>проведён</span>" |
| | 0 | 417 | | : "<span class='badge bg-warning-subtle text-warning'>не проведён</span>"; |
| | 0 | 418 | | var reason = r.IsDraft ? " — недостаточно остатка, проведите после оприходования" |
| | 0 | 419 | | : !string.IsNullOrWhiteSpace(r.PostingError) ? $" — {WebUtility.HtmlEncode(Services.ApiServices._1CA |
| | 0 | 420 | | return $"<li>{status} {link}{reason}</li>"; |
| | 0 | 421 | | }).ToList(); |
| | 0 | 422 | | return $"<li class='mb-2'><strong>{route}</strong>: {g.Orders.Count} заказ(ов) ({numbers})" + |
| | 0 | 423 | | (docs.Count > 0 ? $"<ul class='mb-0 mt-1'>{string.Join(string.Empty, docs)}</ul>" : string.Empty) + " |
| | 0 | 424 | | }); |
| | 0 | 425 | | return $"<div class='alert alert-info mt-2'><strong>Что создано в 1С по направлениям</strong><ul class='mb-0'>{s |
| | | 426 | | } |
| | | 427 | | } |