< Summary

Information
Class: OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridStatusRefreshResult
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/Grid/OrdersGridStatusRefreshService.cs
Line coverage
100%
Covered lines: 3
Uncovered lines: 0
Coverable lines: 3
Total lines: 112
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Message()100%11100%
get_Error()100%11100%
get_Rows()100%11100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/Grid/OrdersGridStatusRefreshService.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using System.Security.Claims;
 3using Microsoft.Extensions.Caching.Memory;
 4
 5namespace OzonOrdersWeb.WebServices.Orders.Grid;
 6
 7public sealed class OrdersGridRefreshStatusesRequest
 8{
 9    public List<int> Ids { get; set; } = [];
 10}
 11
 12public sealed class OrdersGridStatusRefreshResult
 13{
 714    public string? Message { get; set; }
 15
 1316    public string? Error { get; set; }
 17
 18    /// <summary>Свежие строки грида по доступным пользователю заказам (после синхронизации с Ozon).</summary>
 1819    public List<OrderGridRowDto> Rows { get; set; } = [];
 20}
 21
 22public interface IOrdersGridStatusRefreshService
 23{
 24    Task<OrdersGridStatusRefreshResult> RefreshAsync(IReadOnlyList<int> ids, ClaimsPrincipal principal, CancellationToke
 25}
 26
 27/// <summary>
 28/// Ручное «Обновить» статусов из грида: синхронно запрашивает статусы в Ozon и сразу возвращает
 29/// перечитанные строки, чтобы грид обновил их на месте, без перехода на страницу старого списка.
 30/// </summary>
 31public sealed class OrdersGridStatusRefreshService : IOrdersGridStatusRefreshService
 32{
 33    public const int MaxIds = 100;
 34
 35    /// <summary>Повторно опрашивать Ozon по одному и тому же заказу не чаще, чем раз в это время.</summary>
 36    public static readonly TimeSpan OrderCooldown = TimeSpan.FromSeconds(15);
 37
 38    // Один пользователь — один опрос одновременно (двойной клик / спам по меню не множит запросы к Ozon).
 39    private static readonly ConcurrentDictionary<string, byte> InFlight = new();
 40
 41    private readonly IOrdersClientStatusSyncService _statusSync;
 42    private readonly IOrdersGridRowHydrator _hydrator;
 43    private readonly IOrdersGridScopedQuery _scoped;
 44    private readonly IMemoryCache _cache;
 45
 46    public OrdersGridStatusRefreshService(
 47        IOrdersClientStatusSyncService statusSync,
 48        IOrdersGridRowHydrator hydrator,
 49        IOrdersGridScopedQuery scoped,
 50        IMemoryCache cache)
 51    {
 52        _cache = cache;
 53        _statusSync = statusSync;
 54        _hydrator = hydrator;
 55        _scoped = scoped;
 56    }
 57
 58    public async Task<OrdersGridStatusRefreshResult> RefreshAsync(
 59        IReadOnlyList<int> ids, ClaimsPrincipal principal, CancellationToken cancellationToken)
 60    {
 61        var distinct = ids.Distinct().Take(MaxIds).ToList();
 62        if (distinct.Count == 0)
 63        {
 64            return new OrdersGridStatusRefreshResult { Error = "Не переданы заказы для обновления." };
 65        }
 66
 67        var user = await _scoped.ResolveUserAsync(principal);
 68        if (user is null)
 69        {
 70            return new OrdersGridStatusRefreshResult { Error = "Пользователь не найден." };
 71        }
 72
 73        // RLS: чужие заказы не синхронизируем и не отдаём.
 74        var accessible = await _scoped.FilterAccessibleAsync(user, distinct, includeArchived: true, cancellationToken);
 75        if (accessible.Count == 0)
 76        {
 77            return new OrdersGridStatusRefreshResult { Error = "Нет доступных заказов для обновления." };
 78        }
 79
 80        var userKey = principal.FindFirstValue(ClaimTypes.NameIdentifier) ?? principal.Identity?.Name ?? "anonymous";
 81        if (!InFlight.TryAdd(userKey, 0))
 82        {
 83            return new OrdersGridStatusRefreshResult { Error = "Обновление уже выполняется — дождитесь завершения." };
 84        }
 85
 86        try
 87        {
 88            // Недавно опрошенные заказы повторно к Ozon не отправляем — отдаём текущие строки из БД.
 89            var toSync = accessible.Where(id => !_cache.TryGetValue(CooldownKey(id), out _)).ToList();
 90            foreach (var id in toSync)
 91                _cache.Set(CooldownKey(id), true, OrderCooldown);
 92
 93            OrdersClientStatusSyncResult? sync = null;
 94            if (toSync.Count > 0)
 95                sync = await _statusSync.SyncOrderStatusesAsync(toSync, cancellationToken);
 96
 97            var rows = await _hydrator.HydrateAsync(accessible, principal, cancellationToken);
 98            return new OrdersGridStatusRefreshResult
 99            {
 100                Message = sync?.Message ?? "Статусы обновлялись только что — изменений нет.",
 101                Error = sync?.Error,
 102                Rows = rows
 103            };
 104        }
 105        finally
 106        {
 107            InFlight.TryRemove(userKey, out _);
 108        }
 109    }
 110
 111    private static string CooldownKey(int orderId) => $"og-status-refresh:{orderId}";
 112}

Methods/Properties

get_Message()
get_Error()
get_Rows()