< Summary

Information
Class: OzonOrdersWeb.WebServices.Orders.Grid.OrdersGridOrderCardService
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/Orders/Grid/OrdersGridOrderCardService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 108
Coverable lines: 108
Total lines: 226
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 46
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%
GetCardAsync()0%620%
GetStatusHistoryAsync()0%2040%
GetOrderTransactionsAsync()0%420200%

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{
 32    public DateTime ChangedAt { get; set; }
 33
 34    /// <summary>app | client | market | supplier — какой статус менялся.</summary>
 35    public string Kind { get; set; } = string.Empty;
 36
 37    public string? Field { get; set; }
 38
 39    public string? OldValue { get; set; }
 40
 41    public string? NewValue { get; set; }
 42
 43    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>
 088    private static readonly Dictionary<string, string> StatusKinds = new(StringComparer.OrdinalIgnoreCase)
 089    {
 090        ["AppStatusId"] = "app",
 091        ["ClientStatusId"] = "client",
 092        ["Status"] = "market",
 093        ["SupplierOrderStatus"] = "supplier"
 094    };
 95
 96    private readonly ApplicationDbContext _context;
 97    private readonly IOrdersGridRowHydrator _hydrator;
 98    private readonly IOrdersGridScopedQuery _scoped;
 99
 0100    public OrdersGridOrderCardService(ApplicationDbContext context, IOrdersGridRowHydrator hydrator, IOrdersGridScopedQu
 101    {
 0102        _context = context;
 0103        _hydrator = hydrator;
 0104        _scoped = scoped;
 0105    }
 106
 107    public async Task<OrdersGridOrderCardDto?> GetCardAsync(int orderId, ClaimsPrincipal principal, CancellationToken ca
 108    {
 0109        var rows = await _hydrator.HydrateAsync([orderId], principal, cancellationToken);
 0110        if (rows.Count == 0)
 111        {
 0112            return null;
 113        }
 114
 0115        var history = await _context.OrderHistories.AsNoTracking()
 0116            .Where(h => h.OrderId == orderId)
 0117            .OrderByDescending(h => h.ChangedAt)
 0118            .ThenByDescending(h => h.Id)
 0119            .Take(HistoryLimit + 1)
 0120            .Select(h => new OrdersGridHistoryItemDto
 0121            {
 0122                ChangedAt = h.ChangedAt,
 0123                Field = h.ColumnDisplayName ?? h.ColumnName,
 0124                OldValue = h.OldValue,
 0125                NewValue = h.NewValue,
 0126                ChangedBy = h.ChangedBy
 0127            })
 0128            .ToListAsync(cancellationToken);
 129
 0130        return new OrdersGridOrderCardDto
 0131        {
 0132            Row = rows[0],
 0133            History = history.Take(HistoryLimit).ToList(),
 0134            HistoryTruncated = history.Count > HistoryLimit
 0135        };
 0136    }
 137
 138    public async Task<List<OrdersGridStatusHistoryItemDto>?> GetStatusHistoryAsync(int orderId, ClaimsPrincipal principa
 139    {
 140        // Доступ к заказу — тем же RLS-запросом, что и список; иначе история чужого заказа читалась бы по Id.
 0141        var user = await _scoped.ResolveUserAsync(principal);
 0142        if (user is null) return null;
 0143        var accessible = await _scoped.FilterAccessibleAsync(user, [orderId], includeArchived: true, cancellationToken);
 0144        if (accessible.Count == 0) return null;
 145
 0146        var names = StatusKinds.Keys.ToList();
 0147        var rows = await _context.OrderHistories.AsNoTracking()
 0148            .Where(h => h.OrderId == orderId && h.ColumnName != null && names.Contains(h.ColumnName))
 0149            .OrderByDescending(h => h.ChangedAt)
 0150            .ThenByDescending(h => h.Id)
 0151            .Take(StatusHistoryLimit)
 0152            .Select(h => new { h.ChangedAt, h.ColumnName, h.ColumnDisplayName, h.OldValue, h.NewValue, h.ChangedBy })
 0153            .ToListAsync(cancellationToken);
 154
 0155        return rows.Select(h => new OrdersGridStatusHistoryItemDto
 0156        {
 0157            ChangedAt = h.ChangedAt,
 0158            Kind = StatusKinds.GetValueOrDefault(h.ColumnName!, "app"),
 0159            Field = h.ColumnDisplayName ?? h.ColumnName,
 0160            OldValue = h.OldValue,
 0161            NewValue = h.NewValue,
 0162            ChangedBy = h.ChangedBy
 0163        }).ToList();
 0164    }
 165    public async Task<List<OrdersGridOrderTransactionDto>?> GetOrderTransactionsAsync(int orderId, ClaimsPrincipal princ
 166    {
 0167        var user = await _scoped.ResolveUserAsync(principal);
 0168        if (user is null) return null;
 0169        var accessible = await _scoped.FilterAccessibleAsync(user, [orderId], includeArchived: true, cancellationToken);
 0170        if (accessible.Count == 0) return null;
 171
 0172        var currentId = await _context.Orders.AsNoTracking()
 0173            .Where(o => o.Id == orderId)
 0174            .Select(o => o.TransactionId)
 0175            .FirstOrDefaultAsync(cancellationToken);
 176
 0177        var changes = await _context.OrderHistories.AsNoTracking()
 0178            .Where(h => h.OrderId == orderId && h.ColumnName == "TransactionId")
 0179            .Select(h => new { h.OldValue, h.NewValue })
 0180            .ToListAsync(cancellationToken);
 181
 0182        var ids = new HashSet<int>();
 0183        if (currentId is > 0) ids.Add(currentId.Value);
 0184        foreach (var c in changes)
 185        {
 0186            if (int.TryParse(c.NewValue, out var n) && n > 0) ids.Add(n);
 0187            if (int.TryParse(c.OldValue, out var o) && o > 0) ids.Add(o);
 188        }
 189
 0190        if (ids.Count == 0) return [];
 191
 0192        var transactions = await _context.Transactions.AsNoTracking()
 0193            .Where(t => ids.Contains(t.Id))
 0194            .OrderByDescending(t => t.CreatedDateTime)
 0195            .ThenByDescending(t => t.Id)
 0196            .Take(TransactionsLimit)
 0197            .Select(t => new { t.Id, t.Type, t.CreatedDateTime, t.CreateBy, t.Comment })
 0198            .ToListAsync(cancellationToken);
 199
 0200        var settings = await _context.TransactionTypeSettings.AsNoTracking()
 0201            .ToDictionaryAsync(s => s.TransactionType, s => new { s.Title, s.Color }, cancellationToken);
 202
 0203        return transactions.Select(t =>
 0204        {
 0205            settings.TryGetValue(t.Type ?? TransactionType.All, out var setting);
 0206            var comment = t.Comment?.Trim();
 0207            if (comment is { Length: > TransactionCommentMaxLength })
 0208            {
 0209                comment = comment[..TransactionCommentMaxLength] + "…";
 0210            }
 0211
 0212            return new OrdersGridOrderTransactionDto
 0213            {
 0214                Id = t.Id,
 0215                Type = !string.IsNullOrWhiteSpace(setting?.Title)
 0216                    ? setting!.Title!
 0217                    : t.Type?.GetTransactionDisplayName() ?? "Транзакция",
 0218                Color = setting?.Color,
 0219                CreatedAt = t.CreatedDateTime,
 0220                CreatedBy = t.CreateBy,
 0221                Comment = string.IsNullOrWhiteSpace(comment) ? null : comment,
 0222                IsCurrent = t.Id == currentId
 0223            };
 0224        }).ToList();
 0225    }
 226}