< Summary

Information
Class: Services.ApiServices.NikeiApi.NikeiPriceMapper
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/NikeiApi/NikeiPriceMapper.cs
Line coverage
100%
Covered lines: 79
Uncovered lines: 0
Coverable lines: 79
Total lines: 128
Line coverage: 100%
Branch coverage
84%
Covered branches: 59
Total branches: 70
Branch coverage: 84.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
MapOriginals(...)83.33%1212100%
MapOffer(...)83.33%1818100%
BuildProviderData(...)100%1010100%
BuildDescription(...)71.42%1414100%
Text(...)100%66100%
Normalize(...)50%22100%
BrandMatches(...)87.5%88100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/NikeiApi/NikeiPriceMapper.cs

#LineLine coverage
 1using System.Globalization;
 2using Newtonsoft.Json.Linq;
 3using OzonDomains.Models;
 4using Services.ApiServices.TradesoftApi.Models.Response;
 5
 6namespace Services.ApiServices.NikeiApi;
 7
 8/// <summary>Преобразует точную выдачу Nikei в предложения общей таблицы проценки.</summary>
 9public static class NikeiPriceMapper
 10{
 11    public const string SupplierTitle = "Nikei";
 12    public const string SupplierSiteUrl = "https://nikei.ru/";
 13
 14    public static IReadOnlyList<PreOrderItem> MapOriginals(JToken? response, string article, string brand,
 15        ApiSupplierSettings settings)
 16    {
 317        var wantedArticle = Normalize(article);
 318        var wantedBrand = Normalize(brand);
 319        if (response is not JObject payload || payload["parts"] is not JArray parts) return [];
 20
 321        var items = new List<PreOrderItem>();
 1622        foreach (var token in parts.OfType<JObject>())
 23        {
 524            var offerArticle = Text(token, "article");
 525            var offerBrand = Text(token, "brand");
 526            if (Normalize(offerArticle) != wantedArticle || !BrandMatches(offerBrand, wantedBrand)) continue;
 27
 328            var mapped = MapOffer(token, offerArticle!, offerBrand!, settings);
 629            if (mapped is not null) items.Add(mapped);
 30        }
 31
 32        // API иногда повторяет одно предложение разными to_cart-токенами. product_key по документации
 33        // служит идентификатором одинаковых позиций, поэтому в общей таблице оставляем одну строку.
 634        return items.DistinctBy(x => x.ItemHash, StringComparer.Ordinal).ToList();
 35    }
 36
 37    private static PreOrderItem? MapOffer(JObject offer, string article, string brand, ApiSupplierSettings settings)
 38    {
 339        var price = offer.Value<decimal?>("price");
 340        if (price is null || price <= 0m) return null;
 41
 342        var delivery = offer["deliverytime"] as JObject;
 343        var deliveryMin = Math.Max(0, delivery?.Value<int?>("min") ?? 0);
 344        var deliveryMax = Math.Max(deliveryMin, delivery?.Value<int?>("max") ?? deliveryMin);
 345        var stock = offer.Value<int?>("stock") ?? -1;
 346        var multiplicity = Math.Max(1, offer.Value<int?>("multiplicity") ?? 1);
 347        var warehouse = Text(offer, "warehouse");
 348        var productKey = Text(offer, "product_key");
 349        var itemId = productKey ?? string.Join('|', article, brand, warehouse, price.Value.ToString(CultureInfo.Invarian
 50
 351        return new PreOrderItem
 352        {
 353            Caption = Text(offer, "name") ?? article,
 354            Producer = brand,
 355            Code = article,
 356            Rest = stock < 0 ? "не указано" : stock.ToString(CultureInfo.InvariantCulture),
 357            MinQuantity = multiplicity,
 358            DeliveryDays = deliveryMax.ToString(CultureInfo.InvariantCulture),
 359            DeliveryDaysMin = deliveryMin,
 360            DeliveryDaysMax = deliveryMax,
 361            Price = price.Value.ToString("0.##", CultureInfo.InvariantCulture),
 362            CostPrice = price.Value.ToString("0.##", CultureInfo.InvariantCulture),
 363            CostPriceFormatted = $"{price.Value:0.##} RUB",
 364            CurrencyCode = "RUB",
 365            SourcePrice = price,
 366            SourceCurrencyCode = "RUB",
 367            Direction = warehouse ?? "Nikei",
 368            Title = SupplierTitle,
 369            SiteUrl = SupplierSiteUrl,
 370            ItemId = itemId,
 371            ItemHash = itemId,
 372            Description = BuildDescription(offer, multiplicity),
 373            PriceDescription = $"Цена Nikei: {price.Value:0.##} RUB",
 374            PreferredSupplierId = settings.SupplierId,
 375            PreferredSupplierCurrencyCode = settings.Supplier?.CurrencyCode.ToString(),
 376            ProviderData = BuildProviderData(offer, stock, multiplicity)
 377        };
 78    }
 79
 80    private static Dictionary<string, string?> BuildProviderData(JObject offer, int stock, int multiplicity)
 81    {
 382        var returnable = offer.Value<bool?>("returnable");
 383        return new Dictionary<string, string?>
 384        {
 385            ["providerKey"] = "Nikei",
 386            ["nikeiToCart"] = Text(offer, "to_cart"),
 387            ["nikeiProductKey"] = Text(offer, "product_key"),
 388            ["nikeiStock"] = stock >= 0 ? stock.ToString(CultureInfo.InvariantCulture) : null,
 389            ["nikeiMultiplicity"] = multiplicity.ToString(CultureInfo.InvariantCulture),
 390            ["nikeiPercent"] = offer.Value<int?>("percent")?.ToString(CultureInfo.InvariantCulture),
 391            ["nikeiReturnable"] = returnable?.ToString().ToLowerInvariant(),
 392            ["nikeiReturnCost"] = offer.Value<int?>("return_cost")?.ToString(CultureInfo.InvariantCulture),
 393            ["nikeiUnsafe"] = offer.Value<bool?>("unsafe")?.ToString().ToLowerInvariant()
 394        };
 95    }
 96
 97    private static string BuildDescription(JObject offer, int multiplicity)
 98    {
 399        var parts = new List<string>();
 3100        var comment = Text(offer, "comment");
 3101        if (!string.IsNullOrWhiteSpace(comment)) parts.Add(comment);
 3102        var percent = offer.Value<int?>("percent");
 4103        if (percent is not null) parts.Add($"Процент выдачи: {percent}%");
 4104        if (multiplicity > 1) parts.Add($"Кратность: {multiplicity}");
 3105        if (offer.Value<bool?>("returnable") == true)
 106        {
 1107            var returnCost = offer.Value<int?>("return_cost");
 1108            parts.Add(returnCost > 0 ? $"Возврат: удержание {returnCost}%" : "Возврат разрешён");
 109        }
 2110        else if (offer.Value<bool?>("returnable") == false) parts.Add("Возврат не предусмотрен");
 3111        if (offer.Value<bool?>("unsafe") == true) parts.Add("Возможна неточная замена");
 3112        return string.Join("; ", parts);
 113    }
 114
 28115    private static string? Text(JObject value, string property) => value.Value<string>(property)?.Trim() is { Length: > 
 116
 117    private static string Normalize(string? value) =>
 15118        new string((value ?? string.Empty).Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();
 119
 120    /// <summary>В заказе бренд часто приходит кратко (Febi), а Nikei отвечает официальным именем FEBI BILSTEIN.</summar
 121    private static bool BrandMatches(string? offerBrand, string requestedBrand)
 122    {
 4123        var actual = Normalize(offerBrand);
 4124        return actual == requestedBrand
 4125               || (requestedBrand.Length >= 3 && actual.StartsWith(requestedBrand, StringComparison.Ordinal))
 4126               || (actual.Length >= 3 && requestedBrand.StartsWith(actual, StringComparison.Ordinal));
 127    }
 128}