< Summary

Information
Class: OzonOrdersWeb.WebServices.TransactionTooltipService
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/TransactionTooltipService.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 64
Coverable lines: 64
Total lines: 121
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 36
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%
GetTransactionTooltipsAsync()0%620%
<GetTransactionTooltipsAsync()100%210%
InvalidateCache()100%210%
BuildTooltipsByTransactionType(...)0%272160%
StatusBadgeHtml(...)0%2040%
IsLightColor(...)0%210140%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/TransactionTooltipService.cs

#LineLine coverage
 1using System.Net;
 2using Microsoft.Extensions.Caching.Memory;
 3using OzonDomains;
 4using OzonDomains.Models;
 5using Services.DataServices;
 6
 7namespace OzonOrdersWeb.WebServices;
 8
 9/// <summary>
 10/// Формирует HTML-подсказки для пунктов меню транзакций на основе StatusTransition.
 11/// Результат кэшируется; кэш сбрасывается при изменении переходов в БД.
 12/// </summary>
 13public class TransactionTooltipService
 14{
 15    public const string CacheKey = "TransactionTooltips";
 016    private static readonly TimeSpan CacheSlidingExpiration = TimeSpan.FromMinutes(2);
 17
 18    private readonly StatusTransitionDataServices _statusTransitionDataServices;
 19    private readonly IMemoryCache _cache;
 20
 021    public TransactionTooltipService(
 022        StatusTransitionDataServices statusTransitionDataServices,
 023        IMemoryCache cache)
 24    {
 025        _statusTransitionDataServices = statusTransitionDataServices;
 026        _cache = cache;
 027    }
 28
 29    /// <summary>
 30    /// Возвращает словарь: (int)TransactionType -> HTML для data-tooltip.
 31    /// Пустой словарь или отсутствие ключа — использовать статический вывод на клиенте.
 32    /// </summary>
 33    public async Task<IReadOnlyDictionary<int, string>> GetTransactionTooltipsAsync()
 34    {
 035        return await _cache.GetOrCreateAsync(CacheKey, async entry =>
 036        {
 037            entry.SlidingExpiration = CacheSlidingExpiration;
 038            var list = await _statusTransitionDataServices.GetActive();
 039            return BuildTooltipsByTransactionType(list);
 040        }) ?? new Dictionary<int, string>();
 041    }
 42
 43    /// <summary>
 44    /// Сбрасывает кэш подсказок (вызывать после Create/Edit/Delete в StatusTransition).
 45    /// </summary>
 46    public void InvalidateCache()
 47    {
 048        _cache.Remove(CacheKey);
 049    }
 50
 51    private static IReadOnlyDictionary<int, string> BuildTooltipsByTransactionType(List<StatusTransition> allActive)
 52    {
 053        var result = new Dictionary<int, string>();
 054        var grouped = allActive
 055            .Where(t => t.FromStatus != null && t.ToStatus != null)
 056            .GroupBy(t => t.TransactionType);
 57
 058        foreach (var group in grouped)
 59        {
 060            var fromStatuses = group
 061                .Select(t => t.FromStatus!)
 062                .DistinctBy(s => s.Id)
 063                .OrderBy(s => s.Name)
 064                .ToList();
 065            var toStatuses = group
 066                .Select(t => t.ToStatus!)
 067                .DistinctBy(s => s.Id)
 068                .OrderBy(s => s.Name)
 069                .ToList();
 070            if (fromStatuses.Count == 0 || toStatuses.Count == 0)
 71                continue;
 72
 73            // Статусы, для которых есть активный повторяемый самопереход (перезаказ)
 074            var repeatableSelfStatusIds = group
 075                .Where(t => t.FromStatusId == t.ToStatusId && t.IsRepeatable)
 076                .Select(t => t.FromStatusId)
 077                .ToHashSet();
 78
 079            var conditionPart = "<div style='margin:2px 0;'><b>Условие:</b> статус " +
 080                string.Join(", ", fromStatuses.Select(s => StatusBadgeHtml(s.Name ?? "", s.GetStatusColor(), repeatableS
 81
 082            var descriptions = group
 083                .Select(t => t.Description)
 084                .Where(d => !string.IsNullOrWhiteSpace(d))
 085                .Select(d => d!.Trim())
 086                .Distinct()
 087                .ToList();
 088            var descriptionPart = descriptions.Count > 0
 089                ? "<div style='margin:2px 0;'><b>Описание:</b> " +
 090                  string.Join("<br/>", descriptions.Select(WebUtility.HtmlEncode)) + "</div>"
 091                : "";
 92
 093            var newStatusPart = "<div style='margin:2px 0;'><b>Новый статус:</b> " +
 094                string.Join(" или ", toStatuses.Select(s => StatusBadgeHtml(s.Name ?? "", s.GetStatusColor(), repeatable
 95
 096            result[(int)group.Key] = conditionPart + descriptionPart + newStatusPart;
 97        }
 98
 099        return result;
 100    }
 101
 102    private static string StatusBadgeHtml(string name, string color, bool isRepeatableSelf = false)
 103    {
 0104        var escaped = WebUtility.HtmlEncode(name);
 0105        var textColor = IsLightColor(color) ? "#000" : "#fff";
 0106        var badge = $"<span style='display:inline-block;padding:1px 5px;border-radius:4px;font-size:11px;background-colo
 0107        return isRepeatableSelf ? badge + " <span style='font-size:11px;color:#6c757d;'>(Перезаказ)</span>" : badge;
 108    }
 109
 110    private static bool IsLightColor(string color)
 111    {
 0112        if (string.IsNullOrEmpty(color) || !color.StartsWith("#") || color.Length < 7)
 0113            return true;
 0114        if (color.Length >= 7 &&
 0115            int.TryParse(color.AsSpan(1, 2), System.Globalization.NumberStyles.HexNumber, null, out var r) &&
 0116            int.TryParse(color.AsSpan(3, 2), System.Globalization.NumberStyles.HexNumber, null, out var g) &&
 0117            int.TryParse(color.AsSpan(5, 2), System.Globalization.NumberStyles.HexNumber, null, out var b))
 0118            return (r * 299 + g * 587 + b * 114) / 1000 > 155;
 0119        return true;
 120    }
 121}