< Summary

Information
Class: Services.ApiSupplierSettingsServices.SupplierQuotationHistoryService
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiSupplierSettings/SupplierQuotationHistoryService.cs
Line coverage
98%
Covered lines: 60
Uncovered lines: 1
Coverable lines: 61
Total lines: 109
Line coverage: 98.3%
Branch coverage
71%
Covered branches: 20
Total branches: 28
Branch coverage: 71.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
RecordAsync()72.72%222297.77%
Map(...)50%22100%
Truncate(...)75%44100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiSupplierSettings/SupplierQuotationHistoryService.cs

#LineLine coverage
 1using System.Diagnostics;
 2using System.Text.Json;
 3using Microsoft.EntityFrameworkCore;
 4using Microsoft.Extensions.Logging;
 5using OzonDomains.Models;
 6using OzonRepositories.Context;
 7using Services.ApiServices.TradesoftApi.Models.Response;
 8
 9namespace Services.ApiSupplierSettingsServices;
 10
 11public interface ISupplierQuotationHistoryService
 12{
 13    Task RecordAsync(string providerKey, int? orderId, string article, string? brand, int? supplierId,
 14        IEnumerable<PreOrderItem>? offers, DateTime requestedAtUtc, Stopwatch stopwatch, string? error = null,
 15        string? requestParametersJson = null,
 16        CancellationToken cancellationToken = default);
 17}
 18
 19/// <summary>Сохраняет историю проценки, не влияя на пользовательский ответ при сбое журнала.</summary>
 20public sealed class SupplierQuotationHistoryService : ISupplierQuotationHistoryService
 21{
 122    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
 23    private readonly ApplicationDbContext _context;
 24    private readonly ILogger<SupplierQuotationHistoryService> _logger;
 25
 626    public SupplierQuotationHistoryService(ApplicationDbContext context, ILogger<SupplierQuotationHistoryService> logger
 27    {
 628        _context = context;
 629        _logger = logger;
 630    }
 31
 32    public async Task RecordAsync(string providerKey, int? orderId, string article, string? brand, int? supplierId,
 33        IEnumerable<PreOrderItem>? offers, DateTime requestedAtUtc, Stopwatch stopwatch, string? error = null,
 34        string? requestParametersJson = null,
 35        CancellationToken cancellationToken = default)
 36    {
 37        try
 38        {
 639            if (!orderId.HasValue || orderId.Value <= 0)
 040                return;
 41
 642            var productId = await _context.Orders.AsNoTracking()
 643                .Where(x => x.Id == orderId.Value)
 644                .Select(x => x.ProductInfoId)
 645                .SingleOrDefaultAsync(cancellationToken);
 546            if (!productId.HasValue)
 47            {
 148                _logger.LogWarning("Проценка {ProviderKey} заказа {OrderId} не сохранена: у заказа нет ProductInfoId.", 
 149                return;
 50            }
 51
 452            if (!supplierId.HasValue)
 53            {
 354                supplierId = await _context.ApiSupplierSettings.AsNoTracking()
 355                    .Where(x => x.ProviderKey == providerKey)
 356                    .Select(x => x.SupplierId)
 357                    .SingleOrDefaultAsync(cancellationToken);
 58            }
 59
 460            var items = offers?.ToList() ?? [];
 361            var completedAtUtc = DateTime.UtcNow;
 362            var request = new SupplierQuotationRequest
 363            {
 364                ProductId = productId.Value,
 365                OrderId = orderId.Value,
 366                ProviderKey = providerKey,
 367                SupplierId = supplierId,
 368                Article = article.Trim(),
 369                Brand = string.IsNullOrWhiteSpace(brand) ? null : brand.Trim(),
 370                Status = error is not null ? "Failed" : items.Count == 0 ? "Empty" : "Success",
 371                RequestedAtUtc = requestedAtUtc,
 372                CompletedAtUtc = completedAtUtc,
 373                DurationMs = stopwatch.ElapsedMilliseconds,
 374                OffersCount = items.Count,
 375                ErrorMessage = Truncate(error, 2000),
 376                RequestParametersJson = requestParametersJson ?? JsonSerializer.Serialize(new { article = article.Trim()
 377                Offers = items.Select(Map).ToList()
 378            };
 379            await _context.SupplierQuotationRequests.AddAsync(request, cancellationToken);
 380            await _context.SaveChangesAsync(cancellationToken);
 381        }
 82        // Пользователь ушёл со страницы или отменил проценку — HttpContext.RequestAborted гасит
 83        // запрос к БД на полпути, и история не пишется. Это штатный исход, а не сбой журнала:
 84        // уровень Warning и выше попадает в errors-*.log и заводит автоматический issue.
 285        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 86        {
 187            _logger.LogInformation(
 188                "История проценки {ProviderKey} для заказа {OrderId} не сохранена: запрос отменён клиентом.",
 189                providerKey, orderId);
 190        }
 191        catch (Exception ex)
 92        {
 193            _logger.LogError(ex, "Не удалось сохранить историю проценки {ProviderKey} для заказа {OrderId}.", providerKe
 194        }
 695    }
 96
 197    private static SupplierQuotationOffer Map(PreOrderItem item) => new()
 198    {
 199        Caption = Truncate(item.Caption, 255), Direction = Truncate(item.Direction, 255), Producer = Truncate(item.Produ
 1100        Rest = Truncate(item.Rest, 80), MinQuantity = item.MinQuantity, DeliveryDaysMin = item.DeliveryDaysMin, Delivery
 1101        DeliveryDays = Truncate(item.DeliveryDays, 80), ExtraDeliveryHours = item.ExtraDeliveryHours, CurrencyCode = Tru
 1102        CostPrice = Truncate(item.CostPrice, 80), CostPriceFormatted = Truncate(item.CostPriceFormatted, 80), SourcePric
 1103        SourceCurrencyCode = Truncate(item.SourceCurrencyCode, 32), ItemId = Truncate(item.ItemId, 255), ItemHash = Trun
 1104        SiteUrl = Truncate(item.SiteUrl, 1000), Title = Truncate(item.Title, 255), Description = item.Description, Price
 1105        ProviderDataJson = item.ProviderData is null ? null : JsonSerializer.Serialize(item.ProviderData, JsonOptions)
 1106    };
 107
 18108    private static string? Truncate(string? value, int length) => string.IsNullOrEmpty(value) || value.Length <= length 
 109}