< Summary

Information
Class: OzonOrdersWeb.Areas.Studio2.Controllers.Base.TransactionConfig
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Controllers/Base/TransactionConfig.cs
Line coverage
38%
Covered lines: 27
Uncovered lines: 44
Coverable lines: 71
Total lines: 209
Line coverage: 38%
Branch coverage
32%
Covered branches: 9
Total branches: 28
Branch coverage: 32.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Controllers/Base/TransactionConfig.cs

#LineLine coverage
 1using OzonDomains;
 2using OzonDomains.Models;
 3using OzonOrdersWeb.ViewModels.OrderViewModels;
 4using Services.TransactionUtilsServices;
 5using System.Net;
 6using System.Text;
 7
 8namespace OzonOrdersWeb.Areas.Studio2.Controllers.Base
 9{
 10    /// <summary>
 11    /// Конфигурация транзакции, определяющая специфичное поведение для каждого типа транзакции
 12    /// </summary>
 13    public class TransactionConfig
 14    {
 15        /// <summary>
 16        /// Тип транзакции из enum
 17        /// </summary>
 018        public TransactionType TransactionType { get; set; }
 19
 20        /// <summary>
 21        /// Основной статус для транзакции (например, "Заказан поставщику")
 22        /// </summary>
 023        public string PrimaryStatusName { get; set; } = string.Empty;
 24
 25        /// <summary>
 26        /// Дополнительные статусы, доступные при создании транзакции
 27        /// </summary>
 028        public List<string> AdditionalStatusNames { get; set; } = new();
 29
 30        /// <summary>
 31        /// Статусы, требуемые для исходных заказов (если пусто - без проверки)
 32        /// </summary>
 033        public List<string> RequiredSourceStatuses { get; set; } = new();
 34
 35        /// <summary>
 36        /// Требуется ли интеграция с 1С
 37        /// </summary>
 038        public bool Requires1CIntegration { get; set; }
 39
 40        /// <summary>
 41        /// Загружать ли данные из Bitrix (цены, остатки)
 42        /// </summary>
 043        public bool LoadBitrixData { get; set; }
 44
 45        /// <summary>
 46        /// Метод TransactionManager для создания транзакции
 47        /// </summary>
 048        public Func<TransactionManager, List<Order>, string, DateTime, string, Task<(int, string, int)>>? CreateTransact
 49
 50        /// <summary>
 51        /// Имя View для отображения формы транзакции
 52        /// </summary>
 053        public string ViewName { get; set; } = string.Empty;
 54
 55        /// <summary>
 56        /// Фильтр заказов при загрузке (по умолчанию: стандартный фильтр)
 57        /// </summary>
 058        public Func<Order, bool>? OrderFilter { get; set; }
 59
 60        /// <summary>
 61        /// Дополнительная настройка ViewModel (опционально)
 62        /// </summary>
 063        public Action<MultiplayEditOrderViewModel>? ConfigureViewModel { get; set; }
 64
 65        /// <summary>
 66        /// Асинхронная настройка ViewModel после загрузки заказов (опционально).
 67        /// </summary>
 068        public Func<MultiplayEditOrderViewModel, Task>? ConfigureViewModelAsync { get; set; }
 69
 70        /// <summary>
 71        /// Включать ли отмененные заказы в транзакцию (по умолчанию: true)
 72        /// </summary>
 073        public bool IncludeCancelledOrders { get; set; } = true;
 74
 75        /// <summary>
 76        /// Функция для формирования сообщения об успехе
 77        /// </summary>
 078        public Func<int, int, string, string, string, List<Order>, string>? SuccessMessageBuilder { get; set; }
 79
 80        /// <summary>
 81        /// Заполнять ViewData["BitrixWarehousesList"] для карточки выбора складов (при GET).
 82        /// </summary>
 083        public bool LoadWarehouseListForSelector { get; set; }
 84
 85        /// <summary>
 86        /// При processIn1C использовать склады warehouseSender/warehouseRecipient и TransferStockWithCustomWarehouses.
 87        /// </summary>
 088        public bool UseCustomWarehousesFor1C { get; set; }
 89
 90        /// <summary>
 91        /// Спрашивать у 1С остатки на складе-отправителе перед проведением и блокировать транзакцию
 92        /// при нехватке. Включать только для транзакций, списывающих товар с НАШЕГО склада:
 93        /// у CustomerReturn и ReturnToWarehouse источник — склад комиссионера.
 94        /// </summary>
 095        public bool CheckStockBefore1C { get; set; }
 96
 97        /// <summary>
 98        /// Использовать модель StatusTransition для определения допустимых исходных и целевых статусов.
 99        /// Если true и в БД есть переходы для данного типа транзакции — они используются.
 100        /// Иначе используются PrimaryStatusName, RequiredSourceStatuses, AdditionalStatusNames.
 101        /// </summary>
 0102        public bool UseStatusTransitions { get; set; } = true;
 103
 104        /// <summary>
 105        /// Включать ли статус "К отмене" в список доступных статусов (для транзакций с разбиением количества)
 106        /// </summary>
 0107        public bool IncludeToCancellationStatus { get; set; }
 108
 109        /// <summary>
 110        /// Включать ли статус "Не готов" в список доступных статусов.
 111        /// Используется для сценариев, где заказ ещё не готов к дальнейшей обработке.
 112        /// </summary>
 0113        public bool IncludeNotReadyStatus { get; set; }
 114
 115        /// <summary>
 116        /// Показывать ли панель "Отменить в Ozon" для заказов, которым выставляется статус "Отменен".
 117        /// Панель раскрывается только когда per-row select статуса переключён на "Отменен";
 118        /// при сохранении вызывает Ozon API отмены отправления.
 119        /// </summary>
 0120        public bool SupportsOzonCancelPanel { get; set; }
 121
 122        /// <summary>
 123        /// Дополнительный хук, вызываемый после подготовки заказов методом TransactOrder,
 124        /// но до формирования списка ordersToTransaction и создания транзакции.
 125        /// Используется для специфичных сценариев (например, разбиение количества по статусам).
 126        /// </summary>
 0127        public Func<List<Order>, Task>? AfterTransactOrdersAsync { get; set; }
 128
 129        /// <summary>
 130        /// Очищать поле цены закупки в форме транзакции (без подстановки исторических значений).
 131        /// </summary>
 0132        public bool ClearPurchasePriceOnLoad { get; set; }
 133
 134        /// <summary>
 135        /// Стандартный фильтр заказов (исключает отмененные покупателем)
 136        /// </summary>
 0137        public static Func<Order, bool> StandardOrderFilter => order =>
 0138        {
 0139            bool notCancelledByBuyer = order.Status != "Отменен покупателем";
 0140            bool notUnspecifiedCancelled = !(order.AppStatus?.Name == "Не указан" && order.Status == "Отменён");
 0141            bool notCancelledCancelled = !(order.AppStatus?.Name == "Отменен" && order.Status == "Отменён");
 0142            return notCancelledByBuyer && notUnspecifiedCancelled && notCancelledCancelled;
 0143        };
 144
 145        /// <summary>
 146        /// Создает стандартное сообщение об успехе
 147        /// </summary>
 148        public static string BuildStandardSuccessMessage(
 149            int changeCount, int cancelCount, string dateTime,
 150            string userName, string comment, List<Order> orders, string statusName, string? additionalHtml = null,
 151            IEnumerable<string>? explicitOrderNumbers = null)
 152        {
 13153            var safeStatusName = WebUtility.HtmlEncode(statusName ?? string.Empty);
 13154            var safeDateTime = WebUtility.HtmlEncode(dateTime ?? string.Empty);
 13155            var safeUserName = WebUtility.HtmlEncode(userName ?? string.Empty);
 13156            var safeComment = string.IsNullOrWhiteSpace(comment) ? "—" : WebUtility.HtmlEncode(comment);
 13157            var orderNumbers = (explicitOrderNumbers ?? orders.Select(o => o.ShipmentNumber))
 0158                .Where(n => !string.IsNullOrWhiteSpace(n))
 0159                .Select(n => n!.Trim())
 13160                .Distinct(StringComparer.OrdinalIgnoreCase)
 13161                .ToList();
 162
 13163            var previewOrders = orderNumbers.Take(20).ToList();
 13164            var hiddenOrdersCount = Math.Max(orderNumbers.Count - previewOrders.Count, 0);
 165
 13166            var sb = new StringBuilder();
 13167            sb.Append("<div class='tx-result-compact'>");
 13168            sb.Append("<div class='tx-result-summary'>");
 13169            sb.Append("<div class='tx-result-primary'>");
 13170            sb.Append("<span class='tx-result-count'><i class='fas fa-circle-check'></i>");
 13171            sb.Append($"<b>{changeCount}</b> обновлено</span>");
 13172            if (cancelCount > 0)
 0173                sb.Append($"<span class='tx-result-cancelled'><b>{cancelCount}</b> отменено</span>");
 13174            sb.Append($"<span class='tx-result-status'>{safeStatusName}</span>");
 13175            sb.Append("</div>");
 13176            sb.Append("<div class='tx-result-meta'>");
 13177            sb.Append($"<span><i class='far fa-clock'></i>{safeDateTime}</span>");
 13178            sb.Append($"<span><i class='far fa-user'></i>{safeUserName}</span>");
 13179            sb.Append("</div></div>");
 13180            sb.Append($"<div class='tx-result-comment'><i class='far fa-comment'></i>{safeComment}</div>");
 181
 13182            if (previewOrders.Count > 0)
 183            {
 0184                var ordersInline = string.Join(" ", previewOrders.Select(WebUtility.HtmlEncode));
 0185                if (hiddenOrdersCount > 0)
 186                {
 0187                    ordersInline += $" +{hiddenOrdersCount}";
 188                }
 189
 0190                sb.Append("<details class='tx-result-details tx-result-orders'>");
 0191                sb.Append($"<summary>Заказы: {orderNumbers.Count}</summary>");
 0192                sb.Append($"<div class='tx-result-details-body'>{ordersInline}</div>");
 0193                sb.Append("</details>");
 194            }
 195
 13196            if (!string.IsNullOrWhiteSpace(additionalHtml))
 197            {
 0198                sb.Append("<details class='tx-result-details tx-result-extra'>");
 0199                sb.Append("<summary><i class='fas fa-file-circle-check'></i>Документы 1С и детали</summary>");
 0200                sb.Append("<div class='tx-result-details-body'>");
 0201                sb.Append(additionalHtml);
 0202                sb.Append("</div></details>");
 203            }
 204
 13205            sb.Append("</div>");
 13206            return sb.ToString();
 207        }
 208    }
 209}