< Summary

Information
Class: OzonOrdersWeb.WebServices.SupplierCart.SupplierCartGridRows
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/SupplierCart/SupplierCartGridRows.cs
Line coverage
63%
Covered lines: 402
Uncovered lines: 231
Coverable lines: 633
Total lines: 884
Line coverage: 63.5%
Branch coverage
56%
Covered branches: 186
Total branches: 328
Branch coverage: 56.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
IsKnownRoute(...)100%22100%
NormalizeRoute(...)100%44100%
OrderStatusTone(...)100%4040100%
CartStatusName(...)78.57%161480%
CartStatusTone(...)100%1010100%
Build(...)27.04%741612221.16%
Cart(...)87.5%88100%
StatusCart(...)100%11100%
Armtek(...)87.5%242498.41%
MlAuto(...)57.69%262694.36%
Nikei(...)80%1010100%
ZZap(...)91.66%1212100%
AvdOrders(...)71.42%1414100%
ForumAutoOrders(...)0%620%
FavoritPartsOrders(...)0%272160%
ProfitLigaOrder(...)25%5458.06%
Links(...)75%44100%
Studio(...)75%44100%
Id(...)50%44100%
Blank(...)100%22100%
FirstNonEmpty(...)100%11100%
Qty(...)100%210%
Local(...)100%11100%
ParseRu(...)50%6680%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/SupplierCart/SupplierCartGridRows.cs

#LineLine coverage
 1using System.Globalization;
 2using OzonDomains.Models;
 3using OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart;
 4
 5namespace OzonOrdersWeb.WebServices.SupplierCart;
 6
 7/// <summary>Заказ Studio2, связанный со строкой корзины или заказом поставщика.</summary>
 8public sealed record SupplierCartStudioOrderCell(int Id, string Number, string? Client);
 9
 10/// <summary>Вариант выбора в ячейке (адрес доставки Armtek / ML-Auto).</summary>
 11public sealed record SupplierCartGridOption(string Value, string Name);
 12
 13/// <summary>Плашка над гридом (ошибка API, несвязанные адреса, лимит выборки). Tone — тон темы (danger|warning|info).</
 14public sealed record SupplierCartGridNotice(string Tone, string Text, string? LinkText = null, string? LinkAction = null
 15
 16/// <summary>
 17/// Строка грида «Корзина»: общий вид строки для всех API-поставщиков. Id — идентификатор, который ждут POST-действия
 18/// поставщика (LocalCartItemId, ApiItemId или code_order ZZap), поэтому выбор строк не зависит от DOM.
 19/// </summary>
 20public sealed record SupplierCartGridCartRow
 21{
 22    public string Id { get; init; } = string.Empty;
 23    public bool Selectable { get; init; }
 24    public string? ImageUrl { get; init; }
 25    public string Product { get; init; } = string.Empty;
 26    public string? Article { get; init; }
 27    public string? Brand { get; init; }
 28    public SupplierCartStudioOrderCell? StudioOrder { get; init; }
 29    public string? MissingOrderHint { get; init; }
 30    public DateTime? AddedAt { get; init; }
 31    public string? Warehouse { get; init; }
 32    public string? WarehouseMeta { get; init; }
 33    public int? Period { get; init; }
 34    public decimal? Quantity { get; init; }
 35    public decimal? Price { get; init; }
 36    public decimal? Sum { get; init; }
 37    public string Currency { get; init; } = "RUB";
 38    public string? Status { get; init; }
 39    public string? StatusName { get; init; }
 40    public string? StatusTone { get; init; }
 41    public string? StatusTitle { get; init; }
 42    public string? Comment { get; init; }
 43    public string? CommentTone { get; init; }
 44    /// <summary>Адрес доставки Armtek («KUNRG|KUNZA») или ML-Auto (Id адреса).</summary>
 45    public string? DeliveryValue { get; init; }
 46    public string? DeliveryName { get; init; }
 47    /// <summary>Склад отгрузки Studio2 связан с адресом поставщика в настройках.</summary>
 48    public bool? DeliveryLinked { get; init; }
 49    public bool CanRequote { get; init; }
 50    public bool ApiOnly { get; init; }
 51    public string? Point { get; init; }
 52    public string? LocationCode { get; init; }
 53    public long? ZZapCode { get; init; }
 54    /// <summary>Есть ли позиция в API-корзине ZZap (null — ZZap не ответил).</summary>
 55    public bool? InZZap { get; init; }
 56    public string? ZZapInfo { get; init; }
 57}
 58
 59/// <summary>Позиция заказа поставщика (раскрытие заказа).</summary>
 60public sealed record SupplierCartGridOrderItem
 61{
 62    public string? Article { get; init; }
 63    public string? Brand { get; init; }
 64    public string? Name { get; init; }
 65    public decimal? Quantity { get; init; }
 66    public decimal? Reserved { get; init; }
 67    public decimal? Confirmed { get; init; }
 68    public decimal? Cancelled { get; init; }
 69    public decimal? Price { get; init; }
 70    public decimal? Sum { get; init; }
 71    public string? Status { get; init; }
 72    public string? StatusTone { get; init; }
 73    public string? StatusTitle { get; init; }
 74    public DateTime? DeliveryAt { get; init; }
 75    public string? DeliveryText { get; init; }
 76    public string? Warehouse { get; init; }
 77    public string? Country { get; init; }
 78    public string? Comment { get; init; }
 79    public bool Rejected { get; init; }
 80}
 81
 82/// <summary>Строка грида «Заказы»: заказ (или позиция заказа, если поставщик ведёт статус на позиции).</summary>
 83public sealed record SupplierCartGridOrderRow
 84{
 85    public string Id { get; init; } = string.Empty;
 86    public string Number { get; init; } = string.Empty;
 87    public string? NumberMeta { get; init; }
 88    public bool IsTest { get; init; }
 89    public DateTime? CreatedAt { get; init; }
 90    public string? CreatedText { get; init; }
 91    public string? Status { get; init; }
 92    public string? StatusTone { get; init; }
 93    public IReadOnlyList<SupplierCartGridNotice>? StatusNotes { get; init; }
 94    public decimal? Amount { get; init; }
 95    public string Currency { get; init; } = "RUB";
 96    public int? ItemsCount { get; init; }
 97    public decimal? Quantity { get; init; }
 98    public string? QuantityText { get; init; }
 99    public decimal? Price { get; init; }
 100    public string? Product { get; init; }
 101    public string? Article { get; init; }
 102    public string? Brand { get; init; }
 103    public string? Supplier { get; init; }
 104    public string? SupplierMeta { get; init; }
 105    public string? Delivery { get; init; }
 106    public string? DeliveryMeta { get; init; }
 107    public string? Payment { get; init; }
 108    public string? Buyer { get; init; }
 109    public string? BuyerCode { get; init; }
 110    public string? OrderType { get; init; }
 111    public string? Invoice { get; init; }
 112    public string? Contract { get; init; }
 113    public string? Credit { get; init; }
 114    public string? MinSum { get; init; }
 115    public string? Comment { get; init; }
 116    public string? Source { get; init; }
 117    public string? Point { get; init; }
 118    public DateTime? UpdatedAt { get; init; }
 119    public string? UpdatedText { get; init; }
 120    /// <summary>Группа строк одного заказа (АВД: позиции одного заказа) — итог и детали заказа.</summary>
 121    public decimal? OrderTotal { get; init; }
 122    public string? OrderApiId { get; init; }
 123    public string? User { get; init; }
 124    public string? Brands { get; init; }
 125    public IReadOnlyList<SupplierCartStudioOrderCell> StudioOrders { get; init; } = Array.Empty<SupplierCartStudioOrderC
 126    public bool CanCancel { get; init; }
 127    public IReadOnlyList<SupplierCartGridOrderItem> Items { get; init; } = Array.Empty<SupplierCartGridOrderItem>();
 128}
 129
 130/// <summary>Данные вкладки поставщика для гридов «Корзина» и «Заказы».</summary>
 131public sealed record SupplierCartGridPayload
 132{
 133    public string Provider { get; init; } = string.Empty;
 134    public string ProviderKey { get; init; } = string.Empty;
 135    public string DisplayName { get; init; } = string.Empty;
 136    public string Currency { get; init; } = "RUB";
 137    public IReadOnlyList<SupplierCartGridCartRow> Cart { get; init; } = Array.Empty<SupplierCartGridCartRow>();
 138    public IReadOnlyList<SupplierCartGridOrderRow> Orders { get; init; } = Array.Empty<SupplierCartGridOrderRow>();
 139    /// <summary>Число заказов для бейджа вкладки (у Nikei и Berg строки грида — позиции, а не заказы).</summary>
 140    public int OrderCount { get; init; }
 141    public string? Error { get; init; }
 142    public string? OrdersError { get; init; }
 143    public IReadOnlyList<SupplierCartGridNotice> CartNotices { get; init; } = Array.Empty<SupplierCartGridNotice>();
 144    public IReadOnlyList<SupplierCartGridNotice> OrderNotices { get; init; } = Array.Empty<SupplierCartGridNotice>();
 145    public IReadOnlyList<SupplierCartGridOption> DeliveryOptions { get; init; } = Array.Empty<SupplierCartGridOption>();
 146}
 147
 148/// <summary>
 149/// Строки гридов страницы «Корзина поставщиков» (/Studio2/SupplierCart) из <see cref="SupplierCartSection"/>.
 150/// Сюда переехала логика, которая раньше жила в Razor: тоны статусов, имена статусов корзины, суммы АВД,
 151/// выбор строки, связь заказов с Studio2, фолбэки «Studio2 ?? API». Грид сортирует и фильтрует по этим полям.
 152/// </summary>
 153public static class SupplierCartGridRows
 154{
 155    public const string ToneSuccess = "success";
 156    public const string ToneInfo = "info";
 157    public const string ToneWarning = "warning";
 158    public const string ToneDanger = "danger";
 159    public const string ToneMuted = "muted";
 160
 161    public const string SourceStudio2 = "studio2";
 162    public const string SourceApi = "api";
 163
 164    private const string DefaultMissingHint = "Проверьте комментарий позиции";
 165    private const string UnlinkedMissingHint = "Заказ Studio2 удалён или не привязан";
 166    private const string ZZapMissingHint = "Добавлено на сайте ZZap, не из проценки Studio2";
 167
 168    /// <summary>Маршрут вкладки (query provider) → ключ поставщика и название. Один источник для вкладок и JSON.
 169    /// Currency — валюта, которую страница всегда показывала для поставщика; null — валюта секции.</summary>
 1170    public static readonly IReadOnlyList<(string Route, string ProviderKey, string Name, string? Currency)> Providers =
 1171    [
 1172        ("avd", ApiSupplierSettings.AvdProviderKey, "АВД", null),
 1173        ("armtek", "Armtek", "Армтек RU", null),
 1174        ("armtekby", "ArmtekBy", "Армтек BY", null),
 1175        ("mlauto", "MlAuto", "ML-Auto BY", null),
 1176        ("mlautoru", "MlAutoRu", "ML-Auto RU", null),
 1177        ("forumauto", ApiSupplierSettings.ForumAutoProviderKey, "Форум-Авто (Союз)", null),
 1178        ("forumautointerparts", ApiSupplierSettings.ForumAutoInterpartsProviderKey, "Форум-Авто (Интерпартс)", null),
 1179        ("forumautopiter", ApiSupplierSettings.ForumAutoPiterProviderKey, "Форум-Авто (Питер)", null),
 1180        ("forumautorostov", ApiSupplierSettings.ForumAutoRostovProviderKey, "Форум-Авто (Ростов)", null),
 1181        ("forumautoistra", ApiSupplierSettings.ForumAutoIstraProviderKey, "Форум-Авто (Истра)", null),
 1182        ("favoritparts", ApiSupplierSettings.FavoritPartsProviderKey, "Фаворит Союз", null),
 1183        ("favoritpartsistra", ApiSupplierSettings.FavoritPartsIstraProviderKey, "Фаворит Истра", null),
 1184        ("favoritpartsrostov", ApiSupplierSettings.FavoritPartsRostovProviderKey, "Фаворит Ростов", null),
 1185        ("moskvorechieistra", ApiSupplierSettings.MoskvorechieIstraProviderKey, "Москворечье (Истра)", "RUB"),
 1186        ("motex", ApiSupplierSettings.MotexProviderKey, "МоТехС", "BYN"),
 1187        ("shatem", ApiSupplierSettings.ShateMProviderKey, "Шате‑М", "BYN"),
 1188        ("profitliga", ApiSupplierSettings.ProfitLigaProviderKey, "Профит-Лига", "RUB"),
 1189        ("nikei", ApiSupplierSettings.NikeiProviderKey, "Nikei", "RUB"),
 1190        ("mikadomskhod20", ApiSupplierSettings.MikadoMskHod20ProviderKey, "Микадо МскХод20", "RUB"),
 1191        ("zzapmoscow", ApiSupplierSettings.ZZapMoscowProviderKey, "ZZap Москва", "RUB"),
 1192        ("japarts", ApiSupplierSettings.JapartsProviderKey, "Japarts", "RUB"),
 1193        ("berg", ApiSupplierSettings.BergProviderKey, "Берг", "RUB")
 1194    ];
 195
 196    public static bool IsKnownRoute(string? route) =>
 122197        route is not null && Providers.Any(p => string.Equals(p.Route, route, StringComparison.OrdinalIgnoreCase));
 198
 199    /// <summary>Нормализует маршрут вкладки: неизвестный или пустой — АВД.</summary>
 200    public static string NormalizeRoute(string? route)
 201    {
 10202        var value = route?.Trim().ToLowerInvariant();
 10203        return IsKnownRoute(value) ? value! : "avd";
 204    }
 205
 206    /// <summary>Тон статуса заказа поставщика по тексту статуса (русские формулировки разных API).</summary>
 207    public static string OrderStatusTone(string? status)
 208    {
 135209        var value = (status ?? string.Empty).ToLowerInvariant();
 198210        if (value.Contains("отмен") || value.Contains("отказ") || value.Contains("аннул") || value.Contains("deny")) ret
 74211        if (value.Contains("выдан") || value.Contains("готов") || value.Contains("получ") || value.Contains("заверш") ||
 71212        if (value.Contains("отправ") || value.Contains("отгруж") || value.Contains("в пути") || value.Contains("доставк"
 136213        if (value.Contains("ожид") || value.Contains("обработ") || value.Contains("принят") || value.Contains("подтверж"
 2214        return ToneMuted;
 215    }
 216
 217    /// <summary>Имя статуса строки корзины Studio2 (общие коды Pending/Ready/… у всех гибридных корзин).</summary>
 12218    public static string CartStatusName(string? status) => status switch
 12219    {
 3220        "Pending" => "Ожидает проверки",
 2221        "Ready" => "Проверено",
 1222        "Processing" => "Оформляется",
 0223        "Ordered" => "Оформлено",
 2224        "Failed" => "Ошибка",
 0225        "Removed" => "Удалено",
 4226        _ => status ?? string.Empty
 12227    };
 228
 12229    public static string CartStatusTone(string? status) => status switch
 12230    {
 3231        "Pending" => ToneWarning,
 2232        "Ready" or "Ordered" => ToneSuccess,
 1233        "Processing" => ToneInfo,
 2234        "Failed" => ToneDanger,
 4235        _ => ToneMuted
 12236    };
 237
 238    public static SupplierCartGridPayload Build(string route, SupplierCartSection section, Func<string, string> imageUrl
 239    {
 6240        route = NormalizeRoute(route);
 68241        var currency = Providers.First(p => p.Route == route).Currency ?? section.CurrencyCode;
 6242        var payload = new SupplierCartGridPayload
 6243        {
 6244            Provider = route,
 6245            ProviderKey = section.ProviderKey,
 6246            DisplayName = section.DisplayName,
 6247            Currency = currency,
 6248            Error = section.Error,
 6249            OrdersError = section.OrdersError
 6250        };
 251
 6252        var result = route switch
 6253        {
 1254            "avd" => payload with
 1255            {
 2256                Cart = section.Rows.Select(r => Cart(r, imageUrl, currency) with
 2257                {
 2258                    Id = r.ApiItemId,
 2259                    Selectable = long.TryParse(r.ApiItemId, out _),
 2260                    AddedAt = r.AddedAt,
 2261                    Warehouse = $"№ {r.ApiItemId} · {r.ApiSupplierName}",
 2262                    WarehouseMeta = Blank(r.ApiRegion),
 2263                    Period = r.ApiPeriod
 2264                }).ToList(),
 1265                Orders = AvdOrders(section),
 1266                OrderCount = section.Orders.Count
 1267            },
 1268            "armtek" or "armtekby" => Armtek(payload, section, imageUrl, currency),
 1269            "mlauto" or "mlautoru" => MlAuto(payload, section, imageUrl, currency, route == "mlauto"),
 0270            "forumauto" or "forumautointerparts" or "forumautopiter" or "forumautorostov" or "forumautoistra" => payload
 0271            {
 0272                Cart = section.Rows.Select(r => StatusCart(r, imageUrl, currency) with
 0273                {
 0274                    Id = Id(r.LocalCartItemId), AddedAt = r.AddedAt, Period = r.ApiPeriod
 0275                }).ToList(),
 0276                Orders = ForumAutoOrders(section, currency),
 0277                OrderCount = section.ForumAutoApiOrders.Count
 0278            },
 0279            "favoritparts" or "favoritpartsistra" or "favoritpartsrostov" => payload with
 0280            {
 0281                Cart = section.Rows.Select(r => StatusCart(r, imageUrl, currency) with
 0282                {
 0283                    Id = r.ApiItemId, AddedAt = r.AddedAt, Period = r.ApiPeriod
 0284                }).ToList(),
 0285                Orders = FavoritPartsOrders(section, currency),
 0286                OrderCount = section.FavoritPartsApiOrders.Count
 0287            },
 0288            "moskvorechieistra" => payload with
 0289            {
 0290                Cart = section.Rows.Select(r => StatusCart(r, imageUrl, currency) with
 0291                {
 0292                    Id = Id(r.LocalCartItemId), AddedAt = r.AddedAt, Warehouse = Blank(r.ApiRegion), Period = r.ApiPerio
 0293                }).ToList(),
 0294                Orders = section.MoskvorechieIstraOrders.Select(o => new SupplierCartGridOrderRow
 0295                {
 0296                    Id = o.Id.ToString(CultureInfo.InvariantCulture),
 0297                    Number = o.Number ?? $"API #{o.Id}",
 0298                    CreatedAt = Local(o.CreatedAt),
 0299                    Status = o.Status,
 0300                    StatusTone = ToneSuccess,
 0301                    ItemsCount = o.ItemsCount,
 0302                    Amount = o.Amount,
 0303                    Currency = currency
 0304                }).ToList(),
 0305                OrderCount = section.MoskvorechieIstraOrders.Count
 0306            },
 0307            "motex" => payload with
 0308            {
 0309                Cart = section.Rows.Select(r => StatusCart(r, imageUrl, currency) with
 0310                {
 0311                    Id = Id(r.LocalCartItemId), Warehouse = Blank(r.ApiRegion)
 0312                }).ToList(),
 0313                Orders = section.MotexOrders.Select(o => new SupplierCartGridOrderRow
 0314                {
 0315                    Id = o.Id.ToString(CultureInfo.InvariantCulture),
 0316                    Number = o.ExternalId ?? "—",
 0317                    NumberMeta = o.ClientOrderNum,
 0318                    CreatedAt = Local(o.CreatedAt),
 0319                    Status = o.Status,
 0320                    StatusTone = OrderStatusTone(o.Status),
 0321                    ItemsCount = o.ItemsCount,
 0322                    Amount = o.Amount,
 0323                    Currency = currency
 0324                }).ToList(),
 0325                OrderCount = section.MotexOrders.Count
 0326            },
 0327            "shatem" => payload with
 0328            {
 0329                Cart = section.Rows.Select(r => StatusCart(r, imageUrl, currency) with
 0330                {
 0331                    Id = r.ApiItemId, Warehouse = Blank(r.ApiRegion), LocationCode = Blank(r.ApiRegion)
 0332                }).ToList(),
 0333                Orders = section.ShateMOrders.Select(o => new SupplierCartGridOrderRow
 0334                {
 0335                    Id = o.Id.ToString(CultureInfo.InvariantCulture),
 0336                    Number = o.ExternalId ?? "—",
 0337                    CreatedAt = Local(o.CreatedAt),
 0338                    Status = o.Status,
 0339                    StatusTone = OrderStatusTone(o.Status),
 0340                    ItemsCount = o.ItemsCount,
 0341                    Amount = o.Amount,
 0342                    Currency = currency
 0343                }).ToList(),
 0344                OrderCount = section.ShateMOrders.Count
 0345            },
 1346            "profitliga" => payload with
 1347            {
 0348                Cart = section.Rows.Select(r => StatusCart(r, imageUrl, currency) with
 0349                {
 0350                    Id = Id(r.LocalCartItemId), Warehouse = Blank(r.ApiRegion), Point = Blank(r.ProfitLigaPointName),
 0351                    Comment = Blank(r.ApiComment), CommentTone = ToneDanger
 0352                }).ToList(),
 1353                Orders = section.ProfitLigaOrders.Select(ProfitLigaOrder(currency)).ToList(),
 1354                OrderCount = section.ProfitLigaOrders.Count
 1355            },
 1356            "nikei" => Nikei(payload, section, imageUrl, currency),
 0357            "mikadomskhod20" => payload with
 0358            {
 0359                Cart = section.Rows.Select(r => StatusCart(r, imageUrl, currency) with
 0360                {
 0361                    Id = Id(r.LocalCartItemId), Warehouse = Blank(r.ApiRegion),
 0362                    Comment = Blank(r.ApiComment), CommentTone = ToneDanger
 0363                }).ToList()
 0364            },
 1365            "zzapmoscow" => ZZap(payload, section, imageUrl, currency),
 0366            "japarts" => payload with
 0367            {
 0368                Cart = section.Rows.Select(r => Cart(r, imageUrl, currency) with
 0369                {
 0370                    Id = r.ApiItemId,
 0371                    Period = r.ApiPeriod,
 0372                    Warehouse = Blank(r.ApiRegion),
 0373                    // Japarts не хранит связь в Studio2: без номера отправления показываем комментарий позиции.
 0374                    MissingOrderHint = Blank(r.ApiComment) ?? DefaultMissingHint
 0375                }).ToList(),
 0376                Orders = section.JapartsOrders.Select(o => new SupplierCartGridOrderRow
 0377                {
 0378                    Id = $"{o.GlobalId}|{o.OrderNumber}|{o.Article}",
 0379                    Number = o.OrderNumber,
 0380                    NumberMeta = Blank(o.GlobalId),
 0381                    Product = Blank(o.ProductName),
 0382                    Article = o.Article,
 0383                    Brand = o.Brand,
 0384                    Quantity = o.QuantityOrdered,
 0385                    QuantityText = $"{Qty(o.QuantityOrdered)} / {Qty(o.QuantityBought)}",
 0386                    Price = o.Price,
 0387                    Status = o.Status,
 0388                    StatusTone = OrderStatusTone(o.Status),
 0389                    UpdatedAt = o.LastUpdated,
 0390                    Comment = Blank(o.Comment),
 0391                    Currency = currency
 0392                }).ToList(),
 0393                OrderCount = section.JapartsOrders.Count
 0394            },
 0395            "berg" => payload with
 0396            {
 0397                Cart = section.Rows.Select(r => Cart(r, imageUrl, currency) with
 0398                {
 0399                    Id = Id(r.LocalCartItemId),
 0400                    AddedAt = r.AddedAt?.ToLocalTime(),
 0401                    Warehouse = Blank(r.ApiSupplierName),
 0402                    Comment = Blank(r.ApiComment),
 0403                    CommentTone = ToneDanger
 0404                }).ToList(),
 0405                Orders = section.BergOrders.SelectMany(o => o.Items.Select(line => new SupplierCartGridOrderRow
 0406                {
 0407                    Id = $"{o.Id}-{line.Sequence}",
 0408                    Number = o.Id.ToString(CultureInfo.InvariantCulture),
 0409                    IsTest = o.IsTest,
 0410                    CreatedAt = o.CreatedAt?.ToLocalTime(),
 0411                    Product = Blank(line.Name),
 0412                    Article = line.Article,
 0413                    Brand = line.Brand,
 0414                    Quantity = line.Quantity,
 0415                    Price = line.Price,
 0416                    Amount = line.Price.HasValue && line.Quantity.HasValue ? line.Price * line.Quantity : null,
 0417                    Status = line.StateName,
 0418                    StatusTone = OrderStatusTone(line.StateName),
 0419                    Supplier = Blank(line.WarehouseName),
 0420                    StudioOrders = Studio(line.StudioOrderId, line.StudioOrderNumber, null),
 0421                    Currency = currency
 0422                })).ToList(),
 0423                OrderCount = section.BergOrders.Count
 0424            },
 0425            _ => payload
 6426        };
 427
 428        // У гибридных корзин ошибка секции — предупреждение о строках поставщика, а не отказ API: корзина Studio2 работ
 6429        return route is "profitliga" or "nikei" or "zzapmoscow" && result.Error is not null
 6430            ? result with { Error = null, CartNotices = result.CartNotices.Prepend(new SupplierCartGridNotice(ToneWarnin
 6431            : result;
 432    }
 433
 434    // ---------- Корзина ----------
 435
 436    /// <summary>Общие ячейки строки корзины (бывший партиал _CartItemCells): картинка, товар, заказ Studio2.</summary>
 9437    private static SupplierCartGridCartRow Cart(SupplierCartRow r, Func<string, string> imageUrl, string currency) => ne
 9438    {
 9439        ImageUrl = string.IsNullOrWhiteSpace(r.StudioProductImageUrl) ? null : imageUrl(r.StudioProductImageUrl),
 9440        Product = FirstNonEmpty(r.StudioProductName, r.ApiItemName) ?? string.Empty,
 9441        Article = FirstNonEmpty(r.StudioArticle, r.ApiItemNumber),
 9442        Brand = FirstNonEmpty(r.StudioManufacturer, r.ApiCatalogName),
 9443        StudioOrder = r.StudioOrderId is int id ? new SupplierCartStudioOrderCell(id, r.StudioOrderNumber ?? id.ToString
 9444        MissingOrderHint = DefaultMissingHint,
 9445        Selectable = true,
 9446        Quantity = r.ApiQuantity,
 9447        Price = r.ApiPrice,
 9448        Sum = r.Sum,
 9449        Currency = currency
 9450    };
 451
 452    /// <summary>Строка гибридной корзины: статус позиции Studio2 хранится в ApiSupplierName.</summary>
 453    private static SupplierCartGridCartRow StatusCart(SupplierCartRow r, Func<string, string> imageUrl, string currency)
 7454        Cart(r, imageUrl, currency) with
 7455        {
 7456            Status = r.ApiSupplierName,
 7457            StatusName = CartStatusName(r.ApiSupplierName),
 7458            StatusTone = CartStatusTone(r.ApiSupplierName)
 7459        };
 460
 461    private static SupplierCartGridPayload Armtek(SupplierCartGridPayload payload, SupplierCartSection section, Func<str
 462    {
 2463        var options = section.ArmtekDeliveryOptions.Select(o => new SupplierCartGridOption(o.Value, o.DisplayName)).ToLi
 2464        var known = options.Select(o => o.Value).ToHashSet(StringComparer.Ordinal);
 1465        var extra = new List<SupplierCartGridOption>();
 1466        var cart = section.Rows.Select(r =>
 1467        {
 2468            var value = string.IsNullOrWhiteSpace(r.ArmtekBuyerKunnr) || string.IsNullOrWhiteSpace(r.ArmtekDeliveryAddre
 2469                ? null
 2470                : $"{r.ArmtekBuyerKunnr}|{r.ArmtekDeliveryAddressKunnr}";
 2471            var name = r.ArmtekDeliveryAddressName ?? r.ArmtekDeliveryAddressKunnr;
 1472            // Адрес строки, которого нет в справочнике покупателей, остаётся выбранным — как отдельный вариант списка.
 2473            if (value is not null && !known.Contains(value) && extra.All(x => x.Value != value))
 1474                extra.Add(new SupplierCartGridOption(value, $"{name} (нет в справочнике)"));
 2475            return StatusCart(r, imageUrl, currency) with
 2476            {
 2477                Id = Id(r.LocalCartItemId),
 2478                AddedAt = r.AddedAt,
 2479                Warehouse = Blank(r.ApiRegion),
 2480                WarehouseMeta = Blank($"{r.ApiCatalogName} {r.ApiItemNumber}".Trim()),
 2481                Period = r.ApiPeriod,
 2482                MissingOrderHint = UnlinkedMissingHint,
 2483                DeliveryValue = value,
 2484                DeliveryName = value is null ? null : $"{r.ArmtekBuyerKunnr} · {name}",
 2485                DeliveryLinked = r.ArmtekDeliveryMappingLinked,
 2486                CanRequote = string.Equals(r.ApiSupplierName, ArmtekCartItemStatus.Failed, StringComparison.OrdinalIgnor
 2487                StatusTitle = Blank(r.ApiComment)
 2488            };
 1489        }).ToList();
 490
 1491        var notices = new List<SupplierCartGridNotice>();
 1492        if (!string.IsNullOrWhiteSpace(section.ArmtekDeliveryOptionsError))
 0493            notices.Add(new SupplierCartGridNotice(ToneWarning, section.ArmtekDeliveryOptionsError));
 3494        if (cart.Any(r => r.DeliveryValue is null))
 1495            notices.Add(new SupplierCartGridNotice(ToneWarning,
 1496                "Для части позиций не выбран клиент и адрес доставки Armtek — оформление таких строк заблокировано. Выбе
 1497                "настройках API-поставщика", section.ProviderKey == "ArmtekBy" ? "ArmtekBy" : "Armtek"));
 498
 1499        return payload with
 1500        {
 1501            Cart = cart,
 1502            CartNotices = notices,
 1503            DeliveryOptions = extra.Concat(options).ToList(),
 1504            Orders = section.ArmtekApiOrders.Select(o => new SupplierCartGridOrderRow
 1505            {
 1506                Id = $"{o.OrderNumber}|{o.BuyerCode}",
 1507                Number = o.OrderNumber,
 1508                CreatedAt = ParseRu(o.OrderDate),
 1509                CreatedText = o.OrderDate,
 1510                Buyer = Blank(o.BuyerName),
 1511                BuyerCode = Blank(o.BuyerCode),
 1512                OrderType = Blank(o.OrderType),
 1513                Status = o.Status,
 1514                StatusTone = OrderStatusTone(o.Status),
 1515                Amount = o.Amount,
 1516                Currency = string.IsNullOrWhiteSpace(o.Currency) ? currency : o.Currency,
 1517                Delivery = Blank(o.DeliveryMethod),
 1518                DeliveryMeta = Blank($"{o.DeliveryFrom} — {o.DeliveryTo}".Trim(' ', '—')),
 1519                Invoice = Blank(o.Invoice),
 1520                Contract = Blank(o.Contract),
 1521                Credit = Blank(o.CreditStatus),
 1522                MinSum = Blank(o.MinSumStatus),
 1523                Comment = Blank(o.Comment) ?? Blank(o.ExpeditionComment),
 1524                StudioOrders = Links(section, o.OrderNumber)
 1525            }).ToList(),
 1526            OrderCount = section.ArmtekApiOrders.Count
 1527        };
 528    }
 529
 530    private static SupplierCartGridPayload MlAuto(SupplierCartGridPayload payload, SupplierCartSection section, Func<str
 531    {
 1532        var cart = section.Rows.Select(r => StatusCart(r, imageUrl, currency) with
 1533        {
 1534            Id = Id(r.LocalCartItemId),
 1535            Warehouse = Blank(r.ApiRegion),
 1536            WarehouseMeta = $"Склад отгрузки: {r.MlAutoShipmentWarehouseName ?? "не указан"}",
 1537            Period = r.ApiPeriod,
 1538            MissingOrderHint = UnlinkedMissingHint,
 1539            DeliveryValue = Blank(r.MlAutoDeliveryAddressId),
 1540            DeliveryName = Blank(r.MlAutoDeliveryAddressName),
 1541            DeliveryLinked = r.MlAutoDeliveryMappingLinked,
 1542            CanRequote = isBy && string.Equals(r.ApiSupplierName, MlAutoCartItemStatus.Failed, StringComparison.OrdinalI
 1543        }).ToList();
 544
 1545        var cartNotices = new List<SupplierCartGridNotice>();
 1546        if (section.Error is not null) cartNotices.Add(new SupplierCartGridNotice(ToneWarning, section.Error));
 1547        if (isBy && section.Rows.Any(x => !x.MlAutoDeliveryMappingLinked && string.IsNullOrWhiteSpace(x.MlAutoDeliveryAd
 0548            cartNotices.Add(new SupplierCartGridNotice(ToneWarning,
 0549                "Для части позиций склад отгрузки Studio2 не связан с адресом ML-Auto. Выберите адрес вручную в колонке 
 0550                "настройках ML-Auto", "MlAuto"));
 551
 1552        var orderNotices = new List<SupplierCartGridNotice>();
 1553        if (isBy && section.MlAutoApiOrders.Count >= 60)
 1554            orderNotices.Add(new SupplierCartGridNotice(ToneInfo, "Показаны 60 самых свежих заказов периода. Сузьте диап
 555
 2556        var localByNumber = section.MlAutoOrders.ToLookup(x => x.OrderNumber, StringComparer.Ordinal);
 1557        var orders = section.MlAutoApiOrders.Select(o =>
 1558        {
 60559            var local = localByNumber[o.Number].FirstOrDefault();
 60560            var notes = o.DetailsError is null ? null : new[] { new SupplierCartGridNotice(ToneDanger, "Состав не загруж
 60561            return new SupplierCartGridOrderRow
 60562            {
 60563                Id = o.Number,
 60564                Number = o.Number,
 60565                NumberMeta = local is null ? null : $"Studio2: {local.DeliveryAddressName}",
 60566                CreatedAt = o.CreatedAt,
 60567                Status = o.Status,
 60568                StatusTone = OrderStatusTone(o.Status),
 60569                StatusNotes = notes,
 60570                Payment = o.PaymentType == "—" ? null : o.PaymentType,
 60571                ItemsCount = o.ItemsCount,
 60572                Quantity = o.Quantity,
 60573                Amount = o.Amount,
 60574                Currency = currency,
 60575                Delivery = o.DeliveryRange == "—" ? null : o.DeliveryRange,
 60576                Comment = Blank(o.Comment),
 60577                Source = local is null ? SourceApi : SourceStudio2,
 120578                Brands = string.Join(", ", o.Items.Select(x => x.Brand).Where(x => !string.IsNullOrWhiteSpace(x)).Distin
 60579                StudioOrders = Links(section, o.Number),
 60580                Items = o.Items.Select(i => new SupplierCartGridOrderItem
 60581                {
 60582                    Article = i.Pin,
 60583                    Brand = i.Brand,
 60584                    Quantity = i.Quantity,
 60585                    Price = i.Price,
 60586                    Sum = i.Sum,
 60587                    Status = i.Status,
 60588                    StatusTone = OrderStatusTone(i.Status),
 60589                    StatusTitle = $"Код статуса ML-Auto: {i.StatusCode}",
 60590                    DeliveryAt = i.EstimatedDelivery,
 60591                    Comment = Blank(i.Comment),
 60592                    Rejected = i.Rejected
 60593                }).ToList()
 60594            };
 1595        }).ToList();
 596
 1597        return payload with
 1598        {
 1599            Error = null,
 1600            Cart = cart,
 1601            CartNotices = cartNotices,
 1602            OrderNotices = orderNotices,
 0603            DeliveryOptions = section.MlAutoDeliveryOptions.Select(o => new SupplierCartGridOption(o.Id, o.DisplayName))
 1604            Orders = orders,
 1605            OrderCount = section.MlAutoApiOrders.Count
 1606        };
 607    }
 608
 609    private static SupplierCartGridPayload Nikei(SupplierCartGridPayload payload, SupplierCartSection section, Func<stri
 610    {
 1611        var index = 0;
 1612        var cart = section.Rows.Select(r =>
 1613        {
 3614            index++;
 3615            var apiOnly = r.LocalCartItemId is null;
 3616            var row = StatusCart(r, imageUrl, currency) with
 3617            {
 3618                // Строки, добавленные в корзину Nikei вне Studio2, нельзя ни выбрать, ни оформить.
 3619                Id = apiOnly ? $"api-{index}" : Id(r.LocalCartItemId),
 3620                Selectable = !apiOnly,
 3621                ApiOnly = apiOnly,
 3622                Warehouse = Blank(r.ApiRegion),
 3623                Period = r.ApiPeriod,
 3624                Comment = Blank(r.ApiComment),
 3625                CommentTone = apiOnly ? ToneMuted : ToneDanger
 3626            };
 3627            return apiOnly
 3628                ? row with { Status = "ApiOnly", StatusName = "Только Nikei", StatusTone = ToneMuted, MissingOrderHint =
 3629                : row;
 1630        }).ToList();
 631
 1632        return payload with
 1633        {
 1634            Cart = cart,
 3635            Orders = section.NikeiOrders.Select((o, i) => new SupplierCartGridOrderRow
 3636            {
 3637                Id = $"{o.Invoice}-{o.Id?.ToString(CultureInfo.InvariantCulture) ?? i.ToString(CultureInfo.InvariantCult
 3638                Number = o.Invoice,
 3639                CreatedAt = o.CreatedAt,
 3640                Product = Blank(o.ProductName),
 3641                Article = o.Article,
 3642                Brand = o.Brand,
 3643                Quantity = o.Quantity,
 3644                Price = o.Price,
 3645                Delivery = Blank(o.ShippingName),
 3646                DeliveryMeta = Blank(o.ShippingAddress),
 3647                Status = o.Status,
 3648                StatusTone = OrderStatusTone(o.Status),
 3649                StudioOrders = Studio(o.StudioOrderId, o.StudioOrderNumber, null),
 3650                Currency = currency
 3651            }).ToList(),
 3652            OrderCount = section.NikeiOrders.Select(x => x.Invoice).Distinct().Count()
 1653        };
 654    }
 655
 656    private static SupplierCartGridPayload ZZap(SupplierCartGridPayload payload, SupplierCartSection section, Func<strin
 657    {
 1658        var cart = section.Rows.Select(r =>
 1659        {
 2660            var row = StatusCart(r, imageUrl, currency) with
 2661            {
 2662                Id = r.ZZapCodeOrder?.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
 2663                Selectable = r.ZZapCodeOrder.HasValue,
 2664                Warehouse = Blank(r.ApiRegion),
 2665                ZZapCode = r.ZZapCodeOrder,
 2666                InZZap = r.ZZapInApiCart,
 2667                ZZapInfo = r.ZZapInApiCart == true ? r.ZZapApiDeliveryText ?? "Позиция есть в корзине ZZap" : null,
 2668                Period = r.ApiPeriod,
 2669                MissingOrderHint = ZZapMissingHint,
 2670                Comment = Blank(r.ApiComment),
 2671                CommentTone = ToneDanger
 2672            };
 2673            return r.ApiSupplierName == "ApiOnly"
 2674                ? row with
 2675                {
 2676                    StatusName = "Только в ZZap",
 2677                    StatusTone = ToneMuted,
 2678                    StatusTitle = "Позиция добавлена в корзину на сайте ZZap; проверка и оформление из Studio2 недоступн
 2679                    ApiOnly = true
 2680                }
 2681                : row;
 1682        }).ToList();
 683
 1684        var orderNotices = section.OrdersError is null
 1685            ? Array.Empty<SupplierCartGridNotice>()
 1686            : new[] { new SupplierCartGridNotice(ToneWarning, $"ZZap не ответил, показаны только заказы Studio2: {sectio
 687
 1688        return payload with
 1689        {
 1690            Cart = cart,
 1691            OrdersError = null,
 1692            OrderNotices = orderNotices,
 1693            Orders = section.ZZapOrders.Select(o => new SupplierCartGridOrderRow
 1694            {
 1695                Id = o.CodeOrder.ToString(CultureInfo.InvariantCulture),
 1696                Number = o.CodeOrder.ToString(CultureInfo.InvariantCulture),
 1697                CreatedAt = o.CreatedAt,
 1698                Product = Blank(o.ProductName),
 1699                Article = o.Article,
 1700                Brand = o.Brand,
 1701                Supplier = Blank(o.SellerName),
 1702                SupplierMeta = Blank(o.SellerPhone),
 1703                QuantityText = o.Quantity,
 1704                Price = o.Price,
 1705                Status = o.Status,
 1706                StatusTone = OrderStatusTone(o.Status),
 1707                Comment = Blank(o.Message),
 1708                UpdatedAt = o.StatusChangedAt,
 1709                StudioOrders = Studio(o.StudioOrderId, o.StudioOrderNumber, o.StudioClientName),
 1710                CanCancel = o.CanCancel,
 1711                Currency = currency
 1712            }).ToList(),
 1713            OrderCount = section.ZZapOrders.Count
 1714        };
 715    }
 716
 717    // ---------- Заказы ----------
 718
 719    /// <summary>Заказы АВД: строка — позиция заказа; итог, счёт и пользователь заказа — в каждой его строке (группировк
 720    private static IReadOnlyList<SupplierCartGridOrderRow> AvdOrders(SupplierCartSection section) =>
 1721        section.Orders.SelectMany(order =>
 1722        {
 3723            var user = order.Items.Select(item => item.User).FirstOrDefault(u => !string.IsNullOrWhiteSpace(u));
 1724            return order.Items.Select((item, index) =>
 1725            {
 2726                var price = item.Price ?? item.DealerPrice;
 2727                var status = string.IsNullOrWhiteSpace(item.Status) ? "Не указан" : item.Status;
 2728                return new SupplierCartGridOrderRow
 2729                {
 2730                    Id = $"{order.OrderId}-{(string.IsNullOrEmpty(item.Id) ? index.ToString(CultureInfo.InvariantCulture
 2731                    Number = order.OrderNumber,
 2732                    NumberMeta = Blank(order.OrderInfo),
 2733                    CreatedAt = ParseRu(order.OrderDate),
 2734                    CreatedText = order.OrderDate,
 2735                    Product = Blank(item.ItemName),
 2736                    Article = Blank(item.ItemNumber),
 2737                    Brand = Blank(item.CatalogName),
 2738                    Supplier = Blank(item.SupplierName),
 2739                    Price = price,
 2740                    Quantity = item.Quantity,
 2741                    Amount = price.HasValue && item.Quantity.HasValue ? price * item.Quantity : null,
 2742                    Status = status,
 2743                    StatusTone = string.IsNullOrWhiteSpace(item.Status) ? ToneMuted : OrderStatusTone(item.Status),
 2744                    UpdatedAt = ParseRu(item.StatusDate),
 2745                    UpdatedText = Blank(item.StatusDate),
 2746                    OrderTotal = order.Summa,
 2747                    OrderApiId = order.OrderId,
 2748                    Invoice = Blank(order.SchetNumber),
 2749                    User = user,
 2750                    ItemsCount = order.Items.Count,
 2751                    Currency = "RUB"
 2752                };
 1753            });
 1754        }).ToList();
 755
 756    private static IReadOnlyList<SupplierCartGridOrderRow> ForumAutoOrders(SupplierCartSection section, string currency)
 0757        section.ForumAutoApiOrders.Select(o =>
 0758        {
 0759            var key = o.Did.ToString(CultureInfo.InvariantCulture);
 0760            return new SupplierCartGridOrderRow
 0761            {
 0762                Id = key,
 0763                Number = $"№{o.Did}",
 0764                CreatedAt = o.CreatedAt,
 0765                Status = o.Status,
 0766                StatusTone = OrderStatusTone(o.Status),
 0767                StatusNotes = o.DetailsError is null ? null : new[] { new SupplierCartGridNotice(ToneDanger, "Ошибка заг
 0768                Delivery = Blank(o.DeliveryAt),
 0769                ItemsCount = o.Items.Count,
 0770                StudioOrders = Links(section, key),
 0771                Brands = string.Join(", ", o.Items.Select(x => x.Brand).Where(x => !string.IsNullOrWhiteSpace(x)).Distin
 0772                Currency = currency,
 0773                Items = o.Items.Select(i => new SupplierCartGridOrderItem
 0774                {
 0775                    Article = i.Article,
 0776                    Brand = i.Brand,
 0777                    Quantity = i.Ordered,
 0778                    Reserved = i.Reserved,
 0779                    Confirmed = i.Confirmed,
 0780                    Price = i.Price,
 0781                    Country = Blank(i.Country)
 0782                }).ToList()
 0783            };
 0784        }).ToList();
 785
 786    private static IReadOnlyList<SupplierCartGridOrderRow> FavoritPartsOrders(SupplierCartSection section, string curren
 0787        section.FavoritPartsApiOrders.Select(o =>
 0788        {
 0789            var notes = new List<SupplierCartGridNotice>();
 0790            if (o.DetailsError is not null) notes.Add(new SupplierCartGridNotice(ToneDanger, "Ошибка загрузки", o.Detail
 0791            if (!string.IsNullOrWhiteSpace(o.PostError)) notes.Add(new SupplierCartGridNotice(ToneDanger, "Не проведён у
 0792            var cancelled = o.Items.Sum(x => x.CountCancel);
 0793            if (cancelled > 0) notes.Add(new SupplierCartGridNotice(ToneWarning, $"Снято: {Qty(cancelled)} шт."));
 0794            return new SupplierCartGridOrderRow
 0795            {
 0796                Id = o.Id.ToString(),
 0797                Number = string.IsNullOrWhiteSpace(o.Number) ? $"№{o.Id}" : $"№{o.Number}",
 0798                CreatedAt = o.CreatedAt?.ToLocalTime(),
 0799                Status = o.Status,
 0800                StatusTone = OrderStatusTone(o.Status),
 0801                StatusNotes = notes.Count == 0 ? null : notes,
 0802                Delivery = o.ShippingDate?.ToString("dd.MM.yyyy", CultureInfo.InvariantCulture),
 0803                ItemsCount = o.Items.Count,
 0804                Amount = o.Sum,
 0805                Point = Blank(o.TradePointName),
 0806                StudioOrders = Links(section, o.Id.ToString()),
 0807                Brands = string.Join(", ", o.Items.Select(x => x.Brand).Where(x => !string.IsNullOrWhiteSpace(x)).Distin
 0808                Currency = currency,
 0809                Items = o.Items.Select(i => new SupplierCartGridOrderItem
 0810                {
 0811                    Article = i.Number,
 0812                    Brand = i.Brand,
 0813                    Quantity = i.Count,
 0814                    Cancelled = i.CountCancel > 0 ? i.CountCancel : null,
 0815                    Price = i.Price
 0816                }).ToList()
 0817            };
 0818        }).ToList();
 819
 1820    private static Func<ProfitLigaApiOrderRow, SupplierCartGridOrderRow> ProfitLigaOrder(string currency) => o =>
 1821    {
 1822        // Профит-Лига возвращает служебный код «Created» вместо русского статуса.
 1823        var status = string.Equals(o.Status, "Created", StringComparison.OrdinalIgnoreCase) ? "Создан" : o.Status;
 1824        return new SupplierCartGridOrderRow
 1825        {
 1826            Id = o.Id.ToString(CultureInfo.InvariantCulture),
 1827            Number = o.OrderNumber,
 1828            CreatedAt = Local(o.CreatedAt),
 1829            Point = Blank(o.PointName),
 1830            Delivery = Blank(o.DeliveryDate),
 1831            Status = status,
 1832            StatusTone = OrderStatusTone(status),
 1833            ItemsCount = o.ItemsCount,
 1834            Amount = o.Amount,
 1835            Currency = currency,
 0836            Items = o.Items.Select(i => new SupplierCartGridOrderItem
 0837            {
 0838                Article = i.Article,
 0839                Brand = i.Brand,
 0840                Name = Blank(i.ProductName),
 0841                Quantity = i.Quantity,
 0842                Price = i.Price,
 0843                Sum = i.Price * i.Quantity,
 0844                Status = Blank(i.Status),
 0845                StatusTone = OrderStatusTone(i.Status),
 0846                Warehouse = Blank(i.WarehouseName),
 0847                DeliveryText = Blank(i.ShowDate)
 0848            }).ToList()
 1849        };
 1850    };
 851
 852    // ---------- Утилиты ----------
 853
 854    private static IReadOnlyList<SupplierCartStudioOrderCell> Links(SupplierCartSection section, string supplierOrder) =
 61855        section.StudioOrderLinksBySupplierOrder.TryGetValue(supplierOrder, out var links)
 1856            ? links.Select(l => new SupplierCartStudioOrderCell(l.OrderId, l.ShipmentNumber ?? l.OrderId.ToString(Cultur
 61857            : Array.Empty<SupplierCartStudioOrderCell>();
 858
 859    private static IReadOnlyList<SupplierCartStudioOrderCell> Studio(int? id, string? number, string? client) =>
 4860        id is int value
 4861            ? new[] { new SupplierCartStudioOrderCell(value, number ?? value.ToString(CultureInfo.InvariantCulture), cli
 4862            : Array.Empty<SupplierCartStudioOrderCell>();
 863
 3864    private static string Id(long? value) => value?.ToString(CultureInfo.InvariantCulture) ?? string.Empty;
 865
 231866    private static string? Blank(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim();
 867
 80868    private static string? FirstNonEmpty(params string?[] values) => values.Select(Blank).FirstOrDefault(v => v is not n
 869
 0870    private static string Qty(decimal value) => value.ToString("0.##", CultureInfo.GetCultureInfo("ru-RU"));
 871
 872    /// <summary>Время API, сохранённое в UTC, — к локальному времени сервера (как раньше показывала страница).</summary
 1873    private static DateTime Local(DateTime value) => DateTime.SpecifyKind(value.ToLocalTime(), DateTimeKind.Unspecified)
 874
 875    /// <summary>Даты АВД и Armtek приходят строкой; для сортировки и фильтра по дате разбираем её, текст остаётся как е
 876    public static DateTime? ParseRu(string? value)
 877    {
 7878        if (string.IsNullOrWhiteSpace(value)) return null;
 3879        var ru = CultureInfo.GetCultureInfo("ru-RU");
 3880        string[] formats = ["dd.MM.yyyy HH:mm:ss", "dd.MM.yyyy HH:mm", "dd.MM.yyyy", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-ddT
 6881        if (DateTime.TryParseExact(value.Trim(), formats, ru, DateTimeStyles.None, out var exact)) return exact;
 0882        return DateTime.TryParse(value, ru, DateTimeStyles.None, out var parsed) ? parsed : null;
 883    }
 884}

Methods/Properties

.cctor()
IsKnownRoute(System.String)
NormalizeRoute(System.String)
OrderStatusTone(System.String)
CartStatusName(System.String)
CartStatusTone(System.String)
Build(System.String,OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart.SupplierCartSection,System.Func`2<System.String,System.String>)
Cart(OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart.SupplierCartRow,System.Func`2<System.String,System.String>,System.String)
StatusCart(OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart.SupplierCartRow,System.Func`2<System.String,System.String>,System.String)
Armtek(OzonOrdersWeb.WebServices.SupplierCart.SupplierCartGridPayload,OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart.SupplierCartSection,System.Func`2<System.String,System.String>,System.String)
MlAuto(OzonOrdersWeb.WebServices.SupplierCart.SupplierCartGridPayload,OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart.SupplierCartSection,System.Func`2<System.String,System.String>,System.String,System.Boolean)
Nikei(OzonOrdersWeb.WebServices.SupplierCart.SupplierCartGridPayload,OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart.SupplierCartSection,System.Func`2<System.String,System.String>,System.String)
ZZap(OzonOrdersWeb.WebServices.SupplierCart.SupplierCartGridPayload,OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart.SupplierCartSection,System.Func`2<System.String,System.String>,System.String)
AvdOrders(OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart.SupplierCartSection)
ForumAutoOrders(OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart.SupplierCartSection,System.String)
FavoritPartsOrders(OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart.SupplierCartSection,System.String)
ProfitLigaOrder(System.String)
Links(OzonOrdersWeb.Areas.Studio2.ViewModels.SupplierCart.SupplierCartSection,System.String)
Studio(System.Nullable`1<System.Int32>,System.String,System.String)
Id(System.Nullable`1<System.Int64>)
Blank(System.String)
FirstNonEmpty(System.String[])
Qty(System.Decimal)
Local(System.DateTime)
ParseRu(System.String)