< Summary

Information
Class: OzonOrdersWeb.Areas.Studio2.Controllers.MlAutoRuPriceController
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Controllers/MlAutoRuPriceController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 100
Coverable lines: 100
Total lines: 155
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 90
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
Search()0%812280%
GetWarehousesAsync()0%620%
<GetWarehousesAsync()0%620%
GetSettingsUrl(...)0%620%
ResolveDeliveryDestinationAsync()0%620%
<ResolveDeliveryDestinationAsync()0%620%
GetValue(...)0%2040%
Map(...)0%600240%
BuildDescription(...)0%4260%
CleanText(...)0%620%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Controllers/MlAutoRuPriceController.cs

#LineLine coverage
 1using System.Globalization;
 2using System.Text.RegularExpressions;
 3using Microsoft.AspNetCore.Authorization;
 4using Microsoft.AspNetCore.Mvc;
 5using Microsoft.EntityFrameworkCore;
 6using Microsoft.Extensions.Caching.Memory;
 7using Newtonsoft.Json.Linq;
 8using OzonDomains;
 9using Services.ApiServices.MlAutoApi;
 10using Services.ApiServices.TradesoftApi.Models.Response;
 11using Services.ApiSupplierSettingsServices;
 12using OzonOrdersWeb.Filters;
 13
 14namespace OzonOrdersWeb.Areas.Studio2.Controllers;
 15
 16[Area("Studio2")]
 17[Authorize(Roles = "User,Admin")]
 18[ServiceFilter(typeof(SupplierQuotationHistoryFilter), Order = 0)]
 19[ServiceFilter(typeof(SupplierDeliverySurchargeFilter), Order = 1)]
 20[ServiceFilter(typeof(SupplierWarehouseRestrictionFilter), Order = 2)]
 21public sealed class MlAutoRuPriceController : Controller
 22{
 23    private readonly MlAutoRuDataManager _mlAuto;
 24    private readonly ApiSupplierSettingsService _settings;
 25    private readonly IMemoryCache _cache;
 26    private readonly MlAutoRuDeliveryAddressMappingService _deliveryMappings;
 27
 028    public MlAutoRuPriceController(MlAutoRuDataManager mlAuto, ApiSupplierSettingsService settings, IMemoryCache cache,
 029        MlAutoRuDeliveryAddressMappingService deliveryMappings)
 30    {
 031        _mlAuto = mlAuto;
 032        _settings = settings;
 033        _cache = cache;
 034        _deliveryMappings = deliveryMappings;
 035    }
 36
 37    [HttpGet]
 38    public async Task<IActionResult> Search(string code, string? brand, int? orderId = null, string? zasylWarehouseName 
 39    {
 040        if (string.IsNullOrWhiteSpace(code) || string.IsNullOrWhiteSpace(brand))
 041            return Ok(Array.Empty<PreOrderItem>());
 42        try
 43        {
 044            var settings = await _settings.GetMlAutoRuAsync(HttpContext.RequestAborted);
 045            if (!settings.IsQuotationEnabled) return Ok(Array.Empty<PreOrderItem>());
 046            var warehouse = orderId.HasValue
 047                ? await _deliveryMappings.ResolveWarehouseAsync(orderId.Value, zasylWarehouseName, HttpContext.RequestAb
 048            var deliveryMapping = orderId.HasValue
 049                ? await _deliveryMappings.ResolveAsync(orderId.Value, zasylWarehouseName, HttpContext.RequestAborted) : 
 050            var apiDeliveryDestination = deliveryMapping is null
 051                ? null
 052                : await ResolveDeliveryDestinationAsync(deliveryMapping.DeliveryAddressId, HttpContext.RequestAborted);
 053            var deliveryDestination = settings.DeliveryAddress ?? apiDeliveryDestination;
 054            var warehouses = await GetWarehousesAsync(HttpContext.RequestAborted);
 055            var response = await _mlAuto.SearchAsync(code.Trim(), brand.Trim(), HttpContext.RequestAborted);
 056            var items = (response as JArray ?? new JArray()).OfType<JObject>()
 057                .Where(x => string.Equals(x.Value<string>("ANALOG"), "NO", StringComparison.OrdinalIgnoreCase))
 058                .Select(x => Map(x, warehouses, settings.SupplierId, settings.Supplier?.CurrencyCode, deliveryMapping, d
 059                    deliveryMapping is null ? null : Url.Action("MlAutoRu", "ApiSupplierSettings", new { area = "Studio2
 060                .Where(x => x is not null).Cast<PreOrderItem>().ToList();
 061            var warning = orderId.HasValue && deliveryMapping is null
 062                ? warehouse.IsUnknownZasyl
 063                    ? $"Засыл «{warehouse.WarehouseName}» не найден среди складов Studio2. Проценка ML-Auto RU выполнена
 064                    : "Адрес доставки ML-Auto RU не связан с эффективным складом заказа. Проценка выполнена без адреса д
 065                : null;
 066            var settingsUrl = orderId.HasValue && deliveryMapping is null
 067                ? GetSettingsUrl(warehouse.WarehouseId) : null;
 068            return Ok(warning is null ? items : new { items, warning, settingsUrl });
 69        }
 070        catch (MlAutoApiException ex) { return Ok(new { items = Array.Empty<PreOrderItem>(), error = ex.Message }); }
 071        catch (Exception ex) when (ex is HttpRequestException or TimeoutException or TaskCanceledException)
 72        {
 073            return Ok(new { items = Array.Empty<PreOrderItem>(), error = "Сервис ML-Auto RU временно недоступен." });
 74        }
 075    }
 76
 77    private async Task<IReadOnlyDictionary<string, string>> GetWarehousesAsync(CancellationToken ct) =>
 078        await _cache.GetOrCreateAsync("api-supplier:ml-auto-ru:warehouses", async entry =>
 079        {
 080            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
 081            try
 082            {
 083                var response = await _mlAuto.GetStoragesAsync(ct);
 084                return (response as JArray ?? new JArray()).OfType<JObject>()
 085                    .Select(x => new { Code = x.Value<string>("STORAGE_CODE") ?? x.Value<string>("CODE") ?? x.Value<stri
 086                    .Where(x => !string.IsNullOrWhiteSpace(x.Code) && !string.IsNullOrWhiteSpace(x.Name))
 087                    .GroupBy(x => x.Code!, StringComparer.OrdinalIgnoreCase).ToDictionary(x => x.Key, x => x.First().Nam
 088            }
 089            catch (MlAutoApiException) { return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); }
 090        }) ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 91
 92    private string? GetSettingsUrl(int? warehouseId) =>
 093        Url.Action("MlAutoRu", "ApiSupplierSettings", new { area = "Studio2" }) +
 094        (warehouseId.HasValue ? $"#mlautoru-mapping-{warehouseId.Value}" : string.Empty);
 95
 96    private async Task<string?> ResolveDeliveryDestinationAsync(string addressId, CancellationToken cancellationToken)
 97    {
 098        var addresses = await _cache.GetOrCreateAsync("api-supplier:ml-auto-ru:delivery-addresses", async entry =>
 099        {
 0100            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
 0101            var response = await _mlAuto.GetDeliveryAddressesAsync(cancellationToken);
 0102            return (response is JContainer container ? container.DescendantsAndSelf() : [response]).OfType<JObject>()
 0103                .Select(x => new { Id = GetValue(x, "ADDRESS_ID", "ID"), Name = GetValue(x, "ADDRESS_NAME", "NAME", "ADD
 0104                .Where(x => !string.IsNullOrWhiteSpace(x.Id) && !string.IsNullOrWhiteSpace(x.Name))
 0105                .GroupBy(x => x.Id!, StringComparer.OrdinalIgnoreCase)
 0106                .ToDictionary(x => x.Key, x => x.First().Name!, StringComparer.OrdinalIgnoreCase);
 0107        });
 0108        return addresses?.GetValueOrDefault(addressId);
 0109    }
 110
 0111    private static string? GetValue(JObject source, params string[] names) => names
 0112        .Select(name => source.GetValue(name, StringComparison.OrdinalIgnoreCase)?.ToString()?.Trim())
 0113        .FirstOrDefault(value => !string.IsNullOrWhiteSpace(value));
 114
 115    private static PreOrderItem? Map(JObject offer, IReadOnlyDictionary<string, string> warehouses, int? supplierId, Cur
 116        MlAutoDeliveryAddressSelection? deliveryMapping, string? deliveryDestination, string? settingsUrl)
 117    {
 0118        if (!decimal.TryParse(offer.Value<string>("PRICE")?.Replace(',', '.'), NumberStyles.Number, CultureInfo.Invarian
 0119        var quantity = MlAutoValueParser.ParseQuantity(offer.Value<string>("QUANTITY")) ?? 0m;
 0120        var min = int.TryParse(offer.Value<string>("MIN"), NumberStyles.Integer, CultureInfo.InvariantCulture, out var p
 0121        var storageCode = offer.Value<string>("STORAGE_CODE") ?? string.Empty;
 0122        var conditions = CleanText(warehouses.GetValueOrDefault(storageCode));
 0123        var chance = CleanText(offer.Value<string>("CHANCE"));
 124        // Код склада дописываем через пробел: API отдаёт для всех предложений одну локацию,
 125        // без кода склады неразличимы в колонке «Направление».
 0126        var direction = string.IsNullOrWhiteSpace(storageCode) ? "Россия" : $"Россия {storageCode}";
 0127        var delivery = DateTime.TryParseExact(offer.Value<string>("DATE"), "yyyyMMddHHmmss", CultureInfo.InvariantCultur
 0128        var rub = Math.Round(sourcePrice, 0, MidpointRounding.AwayFromZero);
 0129        var itemId = string.Join("|", offer.Value<string>("PIN"), storageCode, sourcePrice.ToString(CultureInfo.Invarian
 0130        return new PreOrderItem
 0131        {
 0132            Caption = offer.Value<string>("NAME") ?? offer.Value<string>("PIN"), Producer = offer.Value<string>("BRAND")
 0133            Rest = quantity.ToString("0.##", CultureInfo.InvariantCulture), MinQuantity = min, DeliveryDays = delivery.T
 0134            Price = rub.ToString("0", CultureInfo.InvariantCulture), CostPrice = rub.ToString("0", CultureInfo.Invariant
 0135            SourcePrice = sourcePrice, SourceCurrencyCode = "RUB", Direction = direction, ItemId = itemId, ItemHash = it
 0136            PreferredSupplierId = supplierId, PreferredSupplierCurrencyCode = supplierCurrency?.ToString(),
 0137            Description = BuildDescription(offer, quantity, storageCode, conditions),
 0138            ProviderData = new Dictionary<string, string?>
 0139            {
 0140                ["providerKey"] = "MlAutoRu", ["storageCode"] = storageCode, ["deliveryAtUtc"] = offer.Value<string>("DA
 0141                ["deliveryMappingLinked"] = deliveryMapping is null ? "false" : "true", ["deliveryMappingAddressId"] = d
 0142                ["deliveryMappingAddress"] = deliveryDestination ?? (deliveryMapping is null ? null : $"Адрес ML-Auto RU
 0143            },
 0144            PriceDescription = $"Цена ML-Auto RU: {sourcePrice:0.##} RUB; в таблице: {rub:0} RUB"
 0145        };
 146    }
 147
 0148    private static string BuildDescription(JObject offer, decimal quantity, string storageCode, string? conditions) => s
 0149    {
 0150        $"Артикул: {offer.Value<string>("PIN")}", $"Бренд: {offer.Value<string>("BRAND")}", "Локация: Россия", $"Код скл
 0151        $"Вероятность: {offer.Value<string>("CHANCE") ?? "—"}%", $"Возврат: {offer.Value<string>("RETURN_PERIOD") ?? "—"
 0152    }.Where(x => !string.IsNullOrWhiteSpace(x))!);
 153
 0154    private static string? CleanText(string? value) => string.IsNullOrWhiteSpace(value) ? null : Regex.Replace(value, "<
 155}