< Summary

Information
Class: OzonOrdersWeb.Areas.Studio2.Controllers.ProfitLigaPriceController
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Controllers/ProfitLigaPriceController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 37
Coverable lines: 37
Total lines: 91
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 18
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
.ctor(...)100%210%
Search()0%7280%
ResolveDestinationAsync()0%7280%

File(s)

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

#LineLine coverage
 1using Microsoft.AspNetCore.Authorization;
 2using Microsoft.AspNetCore.Mvc;
 3using Microsoft.Extensions.Caching.Memory;
 4using OzonOrdersWeb.Filters;
 5using Services.ApiServices.ProfitLigaApi;
 6using Services.ApiServices.TradesoftApi.Models.Response;
 7using Services.ApiSupplierSettingsServices;
 8
 9namespace OzonOrdersWeb.Areas.Studio2.Controllers;
 10
 11/// <summary>Read-only проценка «Профит-Лиги» в общем формате строки транзакции.</summary>
 12[Area("Studio2")]
 13[Authorize(Roles = "User,Admin")]
 14[ServiceFilter(typeof(SupplierQuotationHistoryFilter), Order = 0)]
 15[ServiceFilter(typeof(SupplierDeliverySurchargeFilter), Order = 1)]
 16[ServiceFilter(typeof(SupplierWarehouseRestrictionFilter), Order = 2)]
 17public sealed class ProfitLigaPriceController : Controller
 18{
 19    private readonly ProfitLigaDataManager _profitLiga;
 20    private readonly ApiSupplierSettingsService _settings;
 21    private readonly ProfitLigaPointMappingService _points;
 22
 23    private readonly IMemoryCache _cache;
 24
 25    /// <summary>Проценка повторяется при каждом открытии документа, а цены за минуту не меняются.</summary>
 026    private static readonly TimeSpan QuotationTtl = TimeSpan.FromMinutes(1);
 27
 028    public ProfitLigaPriceController(ProfitLigaDataManager profitLiga, ApiSupplierSettingsService settings,
 029        ProfitLigaPointMappingService points, IMemoryCache cache)
 30    {
 031        _profitLiga = profitLiga;
 032        _settings = settings;
 033        _points = points;
 034        _cache = cache;
 035    }
 36
 37    [HttpGet]
 38    public async Task<IActionResult> Search(string code, string? brand, int? orderId = null,
 39        string? zasylWarehouseName = null)
 40    {
 041        if (string.IsNullOrWhiteSpace(code))
 042            return BadRequest(new { error = "Не указан артикул для поиска цен Профит-Лиги." });
 43
 44        try
 45        {
 046            var settings = await _settings.GetProfitLigaAsync(HttpContext.RequestAborted);
 047            if (!settings.IsQuotationEnabled) return Ok(Array.Empty<PreOrderItem>());
 48
 049            var article = code.Trim();
 50
 51            // Бренд фильтрует только /search/crosses (с replaces=0 — без замен). Если бренд неизвестен,
 52            // остаётся /search/items: он отдаёт все бренды с наличием, и лишние отсекает маппер по артикулу.
 53            // Кэшируем сырой ответ API: адрес доставки зависит от строки документа и подставляется ниже.
 054            var response = await _cache.GetOrCreateAsync(
 055                $"quotation:profitliga:{article}:{brand?.Trim()}",
 056                entry =>
 057                {
 058                    entry.AbsoluteExpirationRelativeToNow = QuotationTtl;
 059                    return string.IsNullOrWhiteSpace(brand)
 060                        ? _profitLiga.SearchItemsAsync(article, HttpContext.RequestAborted)
 061                        : _profitLiga.SearchCrossesAsync(article, brand, replaces: false, HttpContext.RequestAborted);
 062                });
 63
 064            var items = ProfitLigaPriceMapper.MapOriginals(response, article, brand,
 065                settings.SupplierId, settings.Supplier?.CurrencyCode.ToString(),
 066                await ResolveDestinationAsync(orderId, zasylWarehouseName));
 67
 068            return Ok(items);
 69        }
 070        catch (ProfitLigaApiException ex)
 71        {
 072            return BadRequest(new { error = ex.Message });
 73        }
 074    }
 75
 76    /// <summary>Точка, в которую уедет заказ: связь «склад отгрузки → торговая точка» с приоритетом
 77    /// засыла. Без связи колонка остаётся пустой, а строка помечается как ненастроенная.</summary>
 78    private async Task<ProfitLigaQuotationDestination?> ResolveDestinationAsync(int? orderId,
 79        string? zasylWarehouseName)
 80    {
 081        if (!orderId.HasValue || orderId.Value <= 0) return null;
 82
 083        var point = await _points.ResolveAsync(orderId.Value, zasylWarehouseName, HttpContext.RequestAborted);
 084        if (point is null) return null;
 85
 086        var settingsUrl = Url.Action("ProfitLiga", "ApiSupplierSettings", new { area = "Studio2" })
 087                          + $"#profitliga-mapping-{point.ShipmentWarehouseId}";
 088        return new ProfitLigaQuotationDestination(
 089            string.IsNullOrWhiteSpace(point.PointName) ? point.PointCode : point.PointName, settingsUrl);
 090    }
 91}