< Summary

Information
Class: OzonOrdersWeb.TransactionServices.ApiSupplierBasketHistoryEntry
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Services/Transactions/ApiSupplierBasketHistoryEntry.cs
Line coverage
85%
Covered lines: 70
Uncovered lines: 12
Coverable lines: 82
Total lines: 144
Line coverage: 85.3%
Branch coverage
63%
Covered branches: 28
Total branches: 44
Branch coverage: 63.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_ProviderKey()100%11100%
get_SupplierDisplayName()100%11100%
get_LogoUrl()100%11100%
get_Success()100%11100%
get_ErrorMessage()100%11100%
get_ApiItemId()100%11100%
get_Article()100%11100%
get_ProductName()100%11100%
get_Price()100%11100%
get_CurrencyCode()100%11100%
get_Quantity()100%11100%
get_ShipmentNumber()100%11100%
get_ClientName()100%11100%
get_WarehouseName()100%11100%
get_BuyerKunnr()100%11100%
get_DeliveryAddressName()100%11100%
get_CartStatus()100%11100%
get_SupplierOrderNumber()100%11100%
get_SupplierCartUrl()100%11100%
.cctor()100%11100%
TryParse(...)85.71%141495%
ResolveDisplayName(...)28.57%391450%
ResolveLogoUrl(...)75%171266.66%
BuildFilterText()100%11100%
ReadLegacyFields(...)50%2262.5%
ReadString(...)100%22100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Services/Transactions/ApiSupplierBasketHistoryEntry.cs

#LineLine coverage
 1using System.Text.Encodings.Web;
 2using System.Text.Json;
 3using System.Text.Unicode;
 4
 5namespace OzonOrdersWeb.TransactionServices;
 6
 7/// <summary>Статус позиции в корзине поставщика на момент записи в историю.</summary>
 8public static class ApiSupplierBasketCartStatus
 9{
 10    public const string Pending = "Pending";
 11    public const string Ready = "Ready";
 12    public const string Ordered = "Ordered";
 13    public const string Failed = "Failed";
 14}
 15
 16/// <summary>
 17/// Запись в orders_history о размещении позиции в корзине API-поставщика. Форма общая
 18/// (ProviderKey/LogoUrl), чтобы таймлайн истории заказа умел показывать любого поставщика с реальным
 19/// API-заказом, не только АВД.
 20/// </summary>
 21/// <remarks>
 22/// Поля после <paramref name="ShipmentNumber"/> добавлены позже и обязаны оставаться nullable
 23/// со значением по умолчанию: позиционные вызовы и уже записанные в БД строки не должны ломаться.
 24/// </remarks>
 11825public sealed record ApiSupplierBasketHistoryEntry(
 12226    string ProviderKey,
 14527    string SupplierDisplayName,
 13728    string LogoUrl,
 11629    bool Success,
 13230    string? ErrorMessage,
 10831    string? ApiItemId,
 14432    string? Article,
 14433    string? ProductName,
 10534    decimal? Price,
 10535    string? CurrencyCode,
 10636    decimal? Quantity,
 10637    string? ShipmentNumber,
 10538    string? ClientName = null,
 10539    string? WarehouseName = null,
 10540    string? BuyerKunnr = null,
 10541    string? DeliveryAddressName = null,
 11042    string? CartStatus = null,
 10943    string? SupplierOrderNumber = null,
 22244    string? SupplierCartUrl = null)
 45{
 46    /// <summary>Маркер в orders_history.ColumnName — по нему таймлайн истории заказа распознаёт эту запись
 47    /// и не путает её с обычным изменением поля заказа (rollback тоже игнорирует нераспознанные ColumnName).</summary>
 48    public const string HistoryColumnName = "ApiSupplierBasketPlacement";
 49
 50    // По умолчанию System.Text.Json экранирует кириллицу как \uXXXX — в БД должен лежать читаемый текст.
 151    public static readonly JsonSerializerOptions JsonOptions = new()
 152    {
 153        Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
 154    };
 55
 156    private static readonly JsonSerializerOptions ReadOptions = new() { PropertyNameCaseInsensitive = true };
 57
 58    /// <summary>
 59    /// Разбирает NewValue из истории. Здесь и только здесь живёт совместимость со старыми форматами:
 60    /// до объединения Armtek писал под тем же ColumnName свой набор полей (pin/brand/error) и без
 61    /// названия поставщика с логотипом.
 62    /// </summary>
 63    public static ApiSupplierBasketHistoryEntry? TryParse(string? json)
 64    {
 1765        if (string.IsNullOrWhiteSpace(json))
 266            return null;
 67
 68        ApiSupplierBasketHistoryEntry? entry;
 69        try
 70        {
 1571            entry = JsonSerializer.Deserialize<ApiSupplierBasketHistoryEntry>(json, ReadOptions);
 1372        }
 273        catch (JsonException)
 74        {
 275            return null;
 76        }
 77
 1378        if (entry is null)
 079            return null;
 80
 1381        var legacy = ReadLegacyFields(json);
 1382        return entry with
 1383        {
 1384            SupplierDisplayName = string.IsNullOrWhiteSpace(entry.SupplierDisplayName)
 1385                ? ResolveDisplayName(entry.ProviderKey)
 1386                : entry.SupplierDisplayName,
 1387            LogoUrl = string.IsNullOrWhiteSpace(entry.LogoUrl) ? ResolveLogoUrl(entry.ProviderKey) : entry.LogoUrl,
 1388            Article = string.IsNullOrWhiteSpace(entry.Article) ? legacy.Pin : entry.Article,
 1389            ProductName = string.IsNullOrWhiteSpace(entry.ProductName) ? legacy.Brand : entry.ProductName,
 1390            ErrorMessage = string.IsNullOrWhiteSpace(entry.ErrorMessage) ? legacy.Error : entry.ErrorMessage
 1391        };
 292    }
 93
 294    public static string ResolveDisplayName(string? providerKey) => providerKey switch
 295    {
 096        "Avd" => "АВД",
 197        "Armtek" => "Армтек RU",
 198        "ArmtekBy" => "Армтек BY",
 099        "Japarts" => "Japarts",
 0100        "Berg" => "Берг",
 0101        "ZZapMoscow" => "ZZap Москва",
 0102        _ => string.IsNullOrWhiteSpace(providerKey) ? "API-поставщик" : providerKey
 2103    };
 104
 11105    public static string ResolveLogoUrl(string? providerKey) => providerKey switch
 11106    {
 5107        "Armtek" or "ArmtekBy" => "/images/armtek-logo.svg",
 1108        "Avd" => "/images/avd-logo.svg",
 0109        "Japarts" => "/images/japarts-logo.jpg",
 0110        "Berg" => "/images/berg-logo-2026.png",
 0111        "ZZapMoscow" => "/images/zzap-logo.svg",
 5112        _ => string.Empty
 11113    };
 114
 115    /// <summary>Плоский текст для data-new: фильтры истории заказа ищут по нему, иначе строка пропадает при поиске.</su
 1116    public string BuildFilterText() => string.Join(" ", new[]
 1117        {
 1118            SupplierDisplayName, Article, ProductName, ShipmentNumber, ClientName,
 1119            WarehouseName, BuyerKunnr, DeliveryAddressName, SupplierOrderNumber, ApiItemId, ErrorMessage
 1120        }
 12121        .Where(x => !string.IsNullOrWhiteSpace(x)));
 122
 123    private static (string? Pin, string? Brand, string? Error) ReadLegacyFields(string json)
 124    {
 125        try
 126        {
 13127            using var document = JsonDocument.Parse(json);
 13128            if (document.RootElement.ValueKind != JsonValueKind.Object)
 0129                return (null, null, null);
 13130            return (ReadString(document.RootElement, "pin"), ReadString(document.RootElement, "brand"),
 13131                ReadString(document.RootElement, "error"));
 132        }
 0133        catch (JsonException)
 134        {
 0135            return (null, null, null);
 136        }
 13137    }
 138
 139    // Старые payload-ы писали Pin/Brand с большой буквы, а error — с маленькой, поэтому имя ищем без учёта регистра.
 39140    private static string? ReadString(JsonElement root, string name) => root.EnumerateObject()
 633141        .Where(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && x.Value.ValueKind == JsonValueKin
 5142        .Select(x => x.Value.GetString())
 39143        .FirstOrDefault();
 144}