< Summary

Information
Class: OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridStatusHistoryItemDto
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/Grid/OrdersGridOrderCardService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 6
Coverable lines: 6
Total lines: 226
Line coverage: 0%
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_ChangedAt()100%210%
get_Kind()100%210%
get_Field()100%210%
get_OldValue()100%210%
get_NewValue()100%210%
get_ChangedBy()100%210%

File(s)

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

#LineLine coverage
 1using System.Security.Claims;
 2using Microsoft.EntityFrameworkCore;
 3using OzonDomains;
 4using OzonRepositories.Context;
 5
 6namespace OzonOrdersWeb.WebServices.Orders.Grid;
 7
 8public sealed class OrdersGridOrderCardDto
 9{
 10    public OrderGridRowDto Row { get; set; } = new();
 11
 12    public List<OrdersGridHistoryItemDto> History { get; set; } = [];
 13
 14    public bool HistoryTruncated { get; set; }
 15}
 16
 17public sealed class OrdersGridHistoryItemDto
 18{
 19    public DateTime ChangedAt { get; set; }
 20
 21    public string? Field { get; set; }
 22
 23    public string? OldValue { get; set; }
 24
 25    public string? NewValue { get; set; }
 26
 27    public string? ChangedBy { get; set; }
 28}
 29
 30public sealed class OrdersGridStatusHistoryItemDto
 31{
 032    public DateTime ChangedAt { get; set; }
 33
 34    /// <summary>app | client | market | supplier — какой статус менялся.</summary>
 035    public string Kind { get; set; } = string.Empty;
 36
 037    public string? Field { get; set; }
 38
 039    public string? OldValue { get; set; }
 40
 041    public string? NewValue { get; set; }
 42
 043    public string? ChangedBy { get; set; }
 44}
 45
 46public sealed class OrdersGridOrderTransactionDto
 47{
 48    public int Id { get; set; }
 49
 50    /// <summary>Название типа транзакции («Заказан реализатору»).</summary>
 51    public string Type { get; set; } = string.Empty;
 52
 53    /// <summary>Цвет типа транзакции (#rrggbb) из настроек; null — не задан.</summary>
 54    public string? Color { get; set; }
 55
 56    public DateTime? CreatedAt { get; set; }
 57
 58    /// <summary>Ник пользователя, проведшего транзакцию.</summary>
 59    public string? CreatedBy { get; set; }
 60
 61    public string? Comment { get; set; }
 62
 63    /// <summary>Транзакция, к которой заказ привязан сейчас.</summary>
 64    public bool IsCurrent { get; set; }
 65}
 66
 67public interface IOrdersGridOrderCardService
 68{
 69    /// <summary>Строка грида (все колонки, с гейтингом) и последние изменения по заказу; null — заказ не найден.</summa
 70    Task<OrdersGridOrderCardDto?> GetCardAsync(int orderId, ClaimsPrincipal principal, CancellationToken cancellationTok
 71
 72    /// <summary>Только переходы статусов (наш / клиента / маркетплейса / поставщика) по orders_history, новые сверху; n
 73    Task<List<OrdersGridStatusHistoryItemDto>?> GetStatusHistoryAsync(int orderId, ClaimsPrincipal principal, Cancellati
 74
 75    /// <summary>Транзакции, в которых участвовал заказ (текущая привязка + смены TransactionId в orders_history), новые
 76    Task<List<OrdersGridOrderTransactionDto>?> GetOrderTransactionsAsync(int orderId, ClaimsPrincipal principal, Cancell
 77}
 78
 79/// <summary>Данные боковой карточки заказа: та же строка, что в гриде, плюс лента orders_history.</summary>
 80public sealed class OrdersGridOrderCardService : IOrdersGridOrderCardService
 81{
 82    public const int HistoryLimit = 60;
 83    public const int StatusHistoryLimit = 100;
 84    public const int TransactionsLimit = 50;
 85    private const int TransactionCommentMaxLength = 300;
 86
 87    /// <summary>ColumnName в orders_history → вид статуса.</summary>
 88    private static readonly Dictionary<string, string> StatusKinds = new(StringComparer.OrdinalIgnoreCase)
 89    {
 90        ["AppStatusId"] = "app",
 91        ["ClientStatusId"] = "client",
 92        ["Status"] = "market",
 93        ["SupplierOrderStatus"] = "supplier"
 94    };
 95
 96    private readonly ApplicationDbContext _context;
 97    private readonly IOrdersGridRowHydrator _hydrator;
 98    private readonly IOrdersGridScopedQuery _scoped;
 99
 100    public OrdersGridOrderCardService(ApplicationDbContext context, IOrdersGridRowHydrator hydrator, IOrdersGridScopedQu
 101    {
 102        _context = context;
 103        _hydrator = hydrator;
 104        _scoped = scoped;
 105    }
 106
 107    public async Task<OrdersGridOrderCardDto?> GetCardAsync(int orderId, ClaimsPrincipal principal, CancellationToken ca
 108    {
 109        var rows = await _hydrator.HydrateAsync([orderId], principal, cancellationToken);
 110        if (rows.Count == 0)
 111        {
 112            return null;
 113        }
 114
 115        var history = await _context.OrderHistories.AsNoTracking()
 116            .Where(h => h.OrderId == orderId)
 117            .OrderByDescending(h => h.ChangedAt)
 118            .ThenByDescending(h => h.Id)
 119            .Take(HistoryLimit + 1)
 120            .Select(h => new OrdersGridHistoryItemDto
 121            {
 122                ChangedAt = h.ChangedAt,
 123                Field = h.ColumnDisplayName ?? h.ColumnName,
 124                OldValue = h.OldValue,
 125                NewValue = h.NewValue,
 126                ChangedBy = h.ChangedBy
 127            })
 128            .ToListAsync(cancellationToken);
 129
 130        return new OrdersGridOrderCardDto
 131        {
 132            Row = rows[0],
 133            History = history.Take(HistoryLimit).ToList(),
 134            HistoryTruncated = history.Count > HistoryLimit
 135        };
 136    }
 137
 138    public async Task<List<OrdersGridStatusHistoryItemDto>?> GetStatusHistoryAsync(int orderId, ClaimsPrincipal principa
 139    {
 140        // Доступ к заказу — тем же RLS-запросом, что и список; иначе история чужого заказа читалась бы по Id.
 141        var user = await _scoped.ResolveUserAsync(principal);
 142        if (user is null) return null;
 143        var accessible = await _scoped.FilterAccessibleAsync(user, [orderId], includeArchived: true, cancellationToken);
 144        if (accessible.Count == 0) return null;
 145
 146        var names = StatusKinds.Keys.ToList();
 147        var rows = await _context.OrderHistories.AsNoTracking()
 148            .Where(h => h.OrderId == orderId && h.ColumnName != null && names.Contains(h.ColumnName))
 149            .OrderByDescending(h => h.ChangedAt)
 150            .ThenByDescending(h => h.Id)
 151            .Take(StatusHistoryLimit)
 152            .Select(h => new { h.ChangedAt, h.ColumnName, h.ColumnDisplayName, h.OldValue, h.NewValue, h.ChangedBy })
 153            .ToListAsync(cancellationToken);
 154
 155        return rows.Select(h => new OrdersGridStatusHistoryItemDto
 156        {
 157            ChangedAt = h.ChangedAt,
 158            Kind = StatusKinds.GetValueOrDefault(h.ColumnName!, "app"),
 159            Field = h.ColumnDisplayName ?? h.ColumnName,
 160            OldValue = h.OldValue,
 161            NewValue = h.NewValue,
 162            ChangedBy = h.ChangedBy
 163        }).ToList();
 164    }
 165    public async Task<List<OrdersGridOrderTransactionDto>?> GetOrderTransactionsAsync(int orderId, ClaimsPrincipal princ
 166    {
 167        var user = await _scoped.ResolveUserAsync(principal);
 168        if (user is null) return null;
 169        var accessible = await _scoped.FilterAccessibleAsync(user, [orderId], includeArchived: true, cancellationToken);
 170        if (accessible.Count == 0) return null;
 171
 172        var currentId = await _context.Orders.AsNoTracking()
 173            .Where(o => o.Id == orderId)
 174            .Select(o => o.TransactionId)
 175            .FirstOrDefaultAsync(cancellationToken);
 176
 177        var changes = await _context.OrderHistories.AsNoTracking()
 178            .Where(h => h.OrderId == orderId && h.ColumnName == "TransactionId")
 179            .Select(h => new { h.OldValue, h.NewValue })
 180            .ToListAsync(cancellationToken);
 181
 182        var ids = new HashSet<int>();
 183        if (currentId is > 0) ids.Add(currentId.Value);
 184        foreach (var c in changes)
 185        {
 186            if (int.TryParse(c.NewValue, out var n) && n > 0) ids.Add(n);
 187            if (int.TryParse(c.OldValue, out var o) && o > 0) ids.Add(o);
 188        }
 189
 190        if (ids.Count == 0) return [];
 191
 192        var transactions = await _context.Transactions.AsNoTracking()
 193            .Where(t => ids.Contains(t.Id))
 194            .OrderByDescending(t => t.CreatedDateTime)
 195            .ThenByDescending(t => t.Id)
 196            .Take(TransactionsLimit)
 197            .Select(t => new { t.Id, t.Type, t.CreatedDateTime, t.CreateBy, t.Comment })
 198            .ToListAsync(cancellationToken);
 199
 200        var settings = await _context.TransactionTypeSettings.AsNoTracking()
 201            .ToDictionaryAsync(s => s.TransactionType, s => new { s.Title, s.Color }, cancellationToken);
 202
 203        return transactions.Select(t =>
 204        {
 205            settings.TryGetValue(t.Type ?? TransactionType.All, out var setting);
 206            var comment = t.Comment?.Trim();
 207            if (comment is { Length: > TransactionCommentMaxLength })
 208            {
 209                comment = comment[..TransactionCommentMaxLength] + "…";
 210            }
 211
 212            return new OrdersGridOrderTransactionDto
 213            {
 214                Id = t.Id,
 215                Type = !string.IsNullOrWhiteSpace(setting?.Title)
 216                    ? setting!.Title!
 217                    : t.Type?.GetTransactionDisplayName() ?? "Транзакция",
 218                Color = setting?.Color,
 219                CreatedAt = t.CreatedDateTime,
 220                CreatedBy = t.CreateBy,
 221                Comment = string.IsNullOrWhiteSpace(comment) ? null : comment,
 222                IsCurrent = t.Id == currentId
 223            };
 224        }).ToList();
 225    }
 226}