< Summary

Information
Class: OzonOrdersWeb.WebServices.SystemAlerts.OneCStockAlertReporter
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/SystemAlerts/OneCStockAlertReporter.cs
Line coverage
83%
Covered lines: 44
Uncovered lines: 9
Coverable lines: 53
Total lines: 87
Line coverage: 83%
Branch coverage
72%
Covered branches: 16
Total branches: 22
Branch coverage: 72.7%
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%
ReportAsync()75%301661.9%
BuildMessage(...)83.33%6693.75%
Absolute(...)50%22100%
SignalMessage(...)50%22100%

File(s)

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

#LineLine coverage
 1using System.Collections.Concurrent;
 2using Services.ApiServices._1CApi.Stock;
 3using Services.ApiServices.TelegramBotApi;
 4using Services.SystemMonitoringServices;
 5
 6namespace OzonOrdersWeb.WebServices.SystemAlerts;
 7
 8/// <summary>Немедленно сообщает о дефиците, который реально блокирует проведение документа 1С.</summary>
 29public sealed class OneCStockAlertReporter(
 210    IServiceScopeFactory scopeFactory,
 211    IsolatedSystemMonitoringSignalWriter signals,
 212    TelegramBotConfig telegramConfig,
 213    ILogger<OneCStockAlertReporter> logger) : IOneCStockAlertReporter
 14{
 115    private static readonly ConcurrentDictionary<string, DateTime> Sent = new(StringComparer.Ordinal);
 116    private static readonly TimeSpan IdempotencyTtl = TimeSpan.FromMinutes(30);
 17
 18    public async Task ReportAsync(StockCheckResult result, string? operation = null, CancellationToken cancellationToken
 19    {
 220        if (!result.HasBlocking)
 121            return;
 22
 123        var key = "shortage|" + string.Join(";", result.Blocking
 124            .OrderBy(x => x.Article, StringComparer.OrdinalIgnoreCase)
 125            .ThenBy(x => x.WarehouseName, StringComparer.OrdinalIgnoreCase)
 226            .Select(x => $"{x.Article}|{x.WarehouseName}|{string.Join(',', x.OrderIds.Order())}"));
 127        var now = DateTime.UtcNow;
 128        if (Sent.TryGetValue(key, out var until) && until > now)
 029            return;
 130        if (!Sent.TryAdd(key, now + IdempotencyTtl))
 031            return;
 32
 33        try
 34        {
 135            await signals.RecordAsync("onec-stock", "failure", "shortage", SignalMessage(result, operation), cancellatio
 36
 137            using var scope = scopeFactory.CreateScope();
 138            var telegram = scope.ServiceProvider.GetService<ITelegramNotifier>();
 139            if (telegram?.IsConfigured == true)
 140                await telegram.SendAsync(new TelegramMessage(BuildMessage(result, operation), Channel: TelegramChannel.O
 141        }
 042        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
 43        {
 044            throw;
 45        }
 046        catch (Exception ex)
 47        {
 048            logger.LogWarning(ex, "Не удалось отправить уведомление о нехватке остатков 1С");
 049        }
 50        finally
 51        {
 352            foreach (var item in Sent.Where(x => x.Value <= now).Take(100))
 053                Sent.TryRemove(item.Key, out _);
 54        }
 255    }
 56
 57    internal string BuildMessage(StockCheckResult result, string? operation)
 58    {
 159        var lines = result.Blocking;
 260        var orderIds = lines.SelectMany(x => x.OrderIds).Distinct().Order().ToList();
 161        var builder = new TelegramMessageBuilder()
 162            .Title("Нехватка остатка перед проведением документа 1С", "⚠️")
 163            .Line("#1с_остатки_нехватка")
 164            .Field("Операция", operation)
 165            .Field("Позиции", lines.Count.ToString());
 66
 467        foreach (var line in lines.Take(20))
 168            builder.Line($"{line.Article}: склад {line.WarehouseName}, нужно {line.Required:0.###}, остаток {line.Availa
 169        if (lines.Count > 20)
 070            builder.Line($"…и ещё {lines.Count - 20} поз.");
 171        if (orderIds.Count > 0)
 72        {
 273            builder.Field("Заказы", string.Join(", ", orderIds.Take(30).Select(id => "#" + id)));
 174            builder.Link("Открыть заказы", Absolute(SystemAlertLinks.Orders(orderIds)));
 75        }
 176        builder.Link("Системные алерты", Absolute("/Studio2/JobLogs?tab=alerts"));
 177        return builder.Build();
 78    }
 79
 280    private string Absolute(string path) => Uri.TryCreate(telegramConfig.SiteBaseUrl, UriKind.Absolute, out var baseUri)
 281        ? new Uri(baseUri.ToString().TrimEnd('/') + path).ToString()
 282        : path;
 83
 84    private static string SignalMessage(StockCheckResult result, string? operation) =>
 185        $"{operation ?? "Документ 1С"}: " + string.Join("; ", result.Blocking.Take(10).Select(x =>
 286            $"{x.Article}, {x.WarehouseName}: нужно {x.Required:0.###}, остаток {x.Available:0.###}"));
 87}