< Summary

Information
Class: OzonOrdersWeb.WebServices.SystemAlerts.NomenclatureTelegramNotifier
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/SystemAlerts/NomenclatureTelegramNotifier.cs
Line coverage
91%
Covered lines: 73
Uncovered lines: 7
Coverable lines: 80
Total lines: 113
Line coverage: 91.2%
Branch coverage
75%
Covered branches: 39
Total branches: 52
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.cctor()100%11100%
NotifyAsync()66.66%241873.68%
BuildMessage(...)90%202096.87%
Absolute(...)50%22100%
OrderLabel(...)33.33%66100%
Hashtag(...)90%101092.3%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/SystemAlerts/NomenclatureTelegramNotifier.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using Services.ApiServices._1CApi.Nomenclature;
 3using Services.ApiServices._1CApi.OData;
 4using Services.ApiServices.TelegramBotApi;
 5
 6namespace OzonOrdersWeb.WebServices.SystemAlerts;
 7
 8/// <summary>Немедленные, идемпотентные уведомления о решениях резолвера номенклатуры.</summary>
 119public sealed class NomenclatureTelegramNotifier(
 1110    IServiceScopeFactory scopeFactory,
 1111    TelegramBotConfig telegramConfig,
 1112    OneCODataConfig oneCConfig,
 1113    ILogger<NomenclatureTelegramNotifier> logger) : INomenclatureNotificationSink
 14{
 115    private static readonly ConcurrentDictionary<string, DateTime> Sent = new(StringComparer.Ordinal);
 116    private static readonly TimeSpan IdempotencyTtl = TimeSpan.FromDays(1);
 17
 18    public async Task NotifyAsync(NomenclatureNotification notification, CancellationToken cancellationToken = default)
 19    {
 320        var key = $"{notification.Kind}|{notification.OrderId}|{notification.RefKey?.ToString() ?? notification.Normaliz
 321        var now = DateTime.UtcNow;
 322        if (Sent.TryGetValue(key, out var until) && until > now)
 123            return;
 224        if (!Sent.TryAdd(key, now + IdempotencyTtl))
 025            return;
 26
 27        try
 28        {
 229            using var scope = scopeFactory.CreateScope();
 230            var telegram = scope.ServiceProvider.GetService<ITelegramNotifier>();
 231            if (telegram?.IsConfigured != true)
 032                return;
 33
 234            await telegram.SendAsync(new TelegramMessage(BuildMessage(notification), Channel: TelegramChannel.OneC), can
 135        }
 036        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 37        {
 038            throw;
 39        }
 140        catch (Exception ex)
 41        {
 142            logger.LogWarning(ex, "Не удалось отправить Telegram-уведомление о номенклатуре для заказа {OrderId}", notif
 143        }
 44        finally
 45        {
 746            foreach (var item in Sent.Where(x => x.Value <= now).Take(100))
 047                Sent.TryRemove(item.Key, out _);
 48        }
 349    }
 50
 51    public string BuildMessage(NomenclatureNotification notification)
 52    {
 1153        var (title, icon) = notification.Kind switch
 1154        {
 255            NomenclatureNotificationKind.Created => ("Создана карточка номенклатуры 1С", "✅"),
 156            NomenclatureNotificationKind.BrandMismatchCreated => ("Создана карточка другого бренда", "⚠️"),
 157            NomenclatureNotificationKind.NotFound => ("Номенклатура 1С не найдена", "❌"),
 158            NomenclatureNotificationKind.Duplicate => ("Найдены дубли карточек номенклатуры", "⚠️"),
 159            NomenclatureNotificationKind.InputConflict => ("Конфликт артикула заказа и ProductKey", "❌"),
 160            NomenclatureNotificationKind.ProducerUnknown => ("Бренд товара не распознан", "❌"),
 161            NomenclatureNotificationKind.Ambiguous => ("Неоднозначный поиск номенклатуры", "❌"),
 162            NomenclatureNotificationKind.BrandMismatch => ("В 1С карточка только другого бренда", "❌"),
 263            NomenclatureNotificationKind.CreateFailed => ("Не удалось создать карточку номенклатуры", "❌"),
 064            _ => ("Номенклатура 1С", "❌")
 1165        };
 1166        var builder = new TelegramMessageBuilder().Title(title, icon)
 1167            .Line(Hashtag(notification.Kind))
 1168            .Field("Заказ", OrderLabel(notification))
 1169            .Field("Название", notification.ProductName)
 1170            .Field("Артикул", notification.Article, code: true)
 1171            .Field("ProductKey", notification.ProductKey, code: true)
 1172            .Field("Бренд", notification.ProducerName)
 1173            .Field("Статус", notification.Status.ToString())
 1174            .Field("Причина", notification.Reason)
 1175            .Field("GUID карточки", notification.RefKey?.ToString(), code: true);
 1176        if (notification.ExistingBrands.Count > 0)
 1177            builder.Field("Бренды существующих карточек", string.Join(" / ", notification.ExistingBrands));
 78
 1179        if (notification.OrderId > 0)
 80        {
 1181            builder.Link("Открыть заказ", Absolute(SystemAlertLinks.Orders([notification.OrderId])));
 1182            builder.Link("Почему так нашлось", Absolute($"/Studio2/OneCNomenclature/Explain?orderId={notification.OrderI
 83        }
 1184        if (notification.RefKey.HasValue && Uri.TryCreate(oneCConfig.BaseUrl, UriKind.Absolute, out var oneCBase))
 1185            builder.Link("Карточка 1С", new Uri(oneCBase.ToString().TrimEnd('/') + "/Catalog_%D0%9D%D0%BE%D0%BC%D0%B5%D0
 1186        builder.Link("Системные алерты", Absolute("/Studio2/JobLogs?tab=alerts"));
 1187        return builder.Build();
 88    }
 89
 3390    private string Absolute(string path) => Uri.TryCreate(telegramConfig.SiteBaseUrl, UriKind.Absolute, out var baseUri)
 3391        ? new Uri(baseUri.ToString().TrimEnd('/') + path).ToString()
 3392        : path;
 93
 1194    private static string OrderLabel(NomenclatureNotification notification) => notification.OrderId <= 0
 1195        ? notification.ShipmentNumber ?? string.Empty
 1196        : string.IsNullOrWhiteSpace(notification.ShipmentNumber)
 1197            ? "#" + notification.OrderId
 1198            : $"#{notification.OrderId}, отправление {notification.ShipmentNumber}";
 99
 11100    private static string Hashtag(NomenclatureNotificationKind kind) => kind switch
 11101    {
 2102        NomenclatureNotificationKind.Created => "#1с_номенклатура_создана",
 1103        NomenclatureNotificationKind.BrandMismatchCreated => "#1с_номенклатура_другой_бренд",
 1104        NomenclatureNotificationKind.NotFound => "#1с_номенклатура_не_найдена",
 1105        NomenclatureNotificationKind.Duplicate => "#1с_номенклатура_дубли",
 1106        NomenclatureNotificationKind.InputConflict => "#1с_номенклатура_конфликт_артикула",
 1107        NomenclatureNotificationKind.ProducerUnknown => "#1с_номенклатура_бренд_не_распознан",
 1108        NomenclatureNotificationKind.Ambiguous => "#1с_номенклатура_неоднозначно",
 1109        NomenclatureNotificationKind.BrandMismatch => "#1с_номенклатура_несовпадение_бренда",
 2110        NomenclatureNotificationKind.CreateFailed => "#1с_номенклатура_ошибка_создания",
 0111        _ => "#1с_номенклатура"
 11112    };
 113}