< Summary

Information
Class: Services.SupplierStatusServices.Providers.AvdSupplierStatusProvider
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/SupplierStatusServices/Providers/AvdSupplierStatusProvider.cs
Line coverage
100%
Covered lines: 81
Uncovered lines: 0
Coverable lines: 81
Total lines: 160
Line coverage: 100%
Branch coverage
95%
Covered branches: 38
Total branches: 40
Branch coverage: 95%
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%
FetchCartAsync()100%22100%
FetchOrderAsync()96.66%3030100%
ErrorForAll(...)100%11100%
ParseDate(...)75%44100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/SupplierStatusServices/Providers/AvdSupplierStatusProvider.cs

#LineLine coverage
 1using System.Globalization;
 2using Microsoft.Extensions.Logging;
 3using OzonDomains.Models;
 4using OzonDomains.Models.SupplierStatuses;
 5using Services.ApiServices.AvdApi;
 6
 7namespace Services.SupplierStatusServices.Providers;
 8
 9/// <summary>
 10/// Статусы АВД. Корзина у АВД внешняя, поэтому обе стадии читаются из его API:
 11/// GetBasketItem для корзины и GetOrders для заказа. Связь со Studio2-заказом идёт
 12/// только через комментарий позиции — см. <see cref="StudioOrderCommentLink"/>.
 13/// </summary>
 14public sealed class AvdSupplierStatusProvider : ISupplierStatusProvider
 15{
 16    /// <summary>Окно выборки заказов: за пределами него статусы уже не меняются.</summary>
 17    private const int OrdersLookbackDays = 60;
 18
 19    /// <summary>status = 0 в GetOrders означает «все статусы», а не «нулевой статус».</summary>
 20    private const int AllStatuses = 0;
 21
 22    private readonly AvdDataManager _avd;
 23    private readonly ILogger<AvdSupplierStatusProvider> _logger;
 24
 2625    public AvdSupplierStatusProvider(AvdDataManager avd, ILogger<AvdSupplierStatusProvider> logger)
 26    {
 2627        _avd = avd;
 2628        _logger = logger;
 2629    }
 30
 231    public string ProviderKey => ApiSupplierSettings.AvdProviderKey;
 32
 33    public async Task<IReadOnlyList<SupplierStatusSnapshot>> FetchCartAsync(
 34        IReadOnlyList<OrderSupplierStateRef> states, CancellationToken ct)
 35    {
 1036        if (states.Count == 0) return [];
 37
 38        IReadOnlyList<AvdBasketItem> basket;
 39        try
 40        {
 841            basket = await _avd.GetBasketItemsAsync(ct);
 442        }
 443        catch (Exception ex) when (ex is AvdApiException or HttpRequestException or TaskCanceledException)
 44        {
 445            _logger.LogInformation("АВД: не удалось прочитать корзину для синхронизации статусов: {Error}", ex.Message);
 446            return ErrorForAll(states, SupplierStatusKind.Cart, $"АВД: {ex.Message}");
 47        }
 48
 449        var orderIdsInBasket = basket
 450            .Select(x => StudioOrderCommentLink.ExtractOrderId(x.Comment))
 451            .OfType<int>()
 452            .ToHashSet();
 53
 54        // Позиция пропала из корзины АВД — её либо удалили вне Studio2, либо она ушла в заказ.
 55        // Различить эти случаи корзина не даёт, поэтому строку закрываем как Removed;
 56        // если заказ был оформлен через Studio2, стадия уже Ordered и сюда не попадает.
 457        return states
 558            .Select(state => new SupplierStatusSnapshot
 559            {
 560                OrderId = state.OrderId,
 561                Kind = SupplierStatusKind.Cart,
 562                ExternalCode = orderIdsInBasket.Contains(state.OrderId)
 563                    ? SupplierCartStatusCode.Pending
 564                    : SupplierCartStatusCode.Removed,
 565                StatusName = orderIdsInBasket.Contains(state.OrderId)
 566                    ? "В корзине, нужна проверка"
 567                    : "Удалён из корзины"
 568            })
 469            .ToList();
 970    }
 71
 72    public async Task<IReadOnlyList<SupplierStatusSnapshot>> FetchOrderAsync(
 73        IReadOnlyList<OrderSupplierStateRef> states, CancellationToken ct)
 74    {
 1775        if (states.Count == 0) return [];
 76
 77        IReadOnlyList<AvdOrder> orders;
 78        try
 79        {
 1580            var dateEnd = DateTime.Today;
 1581            var dateBegin = dateEnd.AddDays(-OrdersLookbackDays);
 1582            orders = await _avd.GetOrdersParsedAsync(
 1583                dateBegin.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
 1584                dateEnd.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
 1585                AllStatuses, number: null, ct);
 1286        }
 387        catch (Exception ex) when (ex is AvdApiException or HttpRequestException or TaskCanceledException)
 88        {
 389            _logger.LogInformation("АВД: не удалось прочитать заказы для синхронизации статусов: {Error}", ex.Message);
 390            return ErrorForAll(states, SupplierStatusKind.Order, $"АВД: {ex.Message}");
 91        }
 92
 93        // Одна строка ответа несёт и данные заказа, и данные позиции. Разбираем по комментарию:
 94        // при нескольких строках на заказ берём самую свежую по StatusDate.
 1295        var byOrderId = new Dictionary<int, (AvdOrder Order, AvdOrderItem Item, DateTime? StatusDate)>();
 5296        foreach (var order in orders)
 97        {
 6298            foreach (var item in order.Items)
 99            {
 17100                var studioOrderId = StudioOrderCommentLink.ExtractOrderId(item.Comment);
 17101                if (studioOrderId is null) continue;
 102
 15103                var statusDate = ParseDate(item.StatusDate);
 15104                if (byOrderId.TryGetValue(studioOrderId.Value, out var known) &&
 15105                    known.StatusDate >= statusDate)
 106                {
 107                    continue;
 108                }
 109
 13110                byOrderId[studioOrderId.Value] = (order, item, statusDate);
 111            }
 112        }
 113
 12114        var result = new List<SupplierStatusSnapshot>(states.Count);
 52115        foreach (var state in states)
 116        {
 14117            if (!byOrderId.TryGetValue(state.OrderId, out var match))
 118            {
 119                // Заказ ещё не появился в выдаче GetOrders (АВД проводит его не мгновенно).
 120                // Ничего не меняем: пустой снимок оставит текущий статус как есть.
 121                continue;
 122            }
 123
 12124            var (order, item, statusDate) = match;
 12125            result.Add(new SupplierStatusSnapshot
 12126            {
 12127                OrderId = state.OrderId,
 12128                Kind = SupplierStatusKind.Order,
 12129                ExternalCode = item.StatusId?.ToString(CultureInfo.InvariantCulture),
 12130                StatusName = string.IsNullOrWhiteSpace(item.Status) ? "Без статуса" : item.Status,
 12131                ExternalOrderNumber = string.IsNullOrWhiteSpace(order.OrderNumber) ? null : order.OrderNumber,
 12132                ExternalOrderId = string.IsNullOrWhiteSpace(order.OrderId) ? null : order.OrderId,
 12133                ExternalLineId = string.IsNullOrWhiteSpace(item.Id) ? null : item.Id,
 12134                QuantityConfirmed = item.QuantityBuyin,
 12135                QuantityShipped = item.QuantityDone ?? item.QuantityCome,
 12136                QuantityRejected = item.QuantityDeny,
 12137                StatusChangedAt = statusDate
 12138            });
 139        }
 140
 12141        return result;
 16142    }
 143
 144    private static List<SupplierStatusSnapshot> ErrorForAll(
 145        IReadOnlyList<OrderSupplierStateRef> states, string kind, string error) =>
 7146        states
 9147            .Select(state => new SupplierStatusSnapshot
 9148            {
 9149                OrderId = state.OrderId,
 9150                Kind = kind,
 9151                StatusName = string.Empty,
 9152                Error = error
 9153            })
 7154            .ToList();
 155
 156    private static DateTime? ParseDate(string? value) =>
 15157        DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed)
 15158            ? parsed
 15159            : DateTime.TryParse(value, out var fallback) ? fallback : null;
 160}