< Summary

Information
Class: OzonOrdersWeb.WebServices.SystemAlerts.OzonApplyFailureRow
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/SystemAlerts/OzonStockApplyAlertSource.cs
Line coverage
100%
Covered lines: 6
Uncovered lines: 0
Coverable lines: 6
Total lines: 105
Line coverage: 100%
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_ReportId()100%11100%
get_OzonClientId()100%11100%
get_ClientName()100%11100%
get_Offer()100%11100%
get_OneCQty()100%11100%
get_Error()100%11100%

File(s)

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

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using Microsoft.Extensions.Options;
 3using OzonRepositories.Context;
 4
 5namespace OzonOrdersWeb.WebServices.SystemAlerts;
 6
 7/// <summary>Строка отчёта BitrixStock, которую Ozon отклонил при накате остатков (этап 2).</summary>
 8public sealed class OzonApplyFailureRow
 9{
 2510    public string ReportId { get; set; } = string.Empty;
 1611    public int OzonClientId { get; set; }
 2212    public string ClientName { get; set; } = string.Empty;
 2513    public string Offer { get; set; } = string.Empty;
 1614    public int OneCQty { get; set; }
 1815    public string? Error { get; set; }
 16}
 17
 18public enum OzonApplyFailureKind
 19{
 20    Other = 0,
 21    Archived,
 22    Attributes
 23}
 24
 25/// <summary>
 26/// Контроль наката остатков в Ozon (этап 2 BitrixStock): товары в архиве и товары с ошибкой атрибутов карточки
 27/// не принимают остаток, пока их не поправят вручную. Лимит частоты обновления сюда не входит — он проходит на следующе
 28/// Смотрим последний отчёт с накатом по каждому клиенту: когда ошибки исчезают из него, алерт закрывается.
 29/// </summary>
 30public sealed class OzonStockApplyAlertSource(ApplicationDbContext context, IOptions<SystemAlertsOptions> options) : ISy
 31{
 32    public const string WindowKey = "ozon-apply-errors";
 33    private const int MaxExamples = 8;
 34
 35    public async Task<IReadOnlyList<SystemAlertFinding>> CollectAsync(CancellationToken cancellationToken = default)
 36    {
 37        var settings = options.Value;
 38        if (!settings.StockChecksEnabled) return [];
 39
 40        var since = DateTime.UtcNow.AddHours(-settings.WindowHoursFor(WindowKey));
 41        var rows = await context.Database.SqlQuery<OzonApplyFailureRow>($@"
 42SELECT l.ReportId AS ReportId, COALESCE(l.OzonClientId, 0) AS OzonClientId, COALESCE(l.OzonClientName, '') AS ClientName
 43       COALESCE(NULLIF(l.OfferId, ''), l.Article) AS Offer, l.OneCQty AS OneCQty, l.OzonApplyError AS Error
 44FROM BitrixStockSyncReportLines l
 45JOIN BitrixStockSyncReports r ON r.ReportId = l.ReportId
 46WHERE l.OzonApplyStatus = 'Failed'
 47  AND r.Status = 'Completed'
 48  AND r.GeneratedAtUtc >= {since}
 49  AND NOT EXISTS (
 50        SELECT 1 FROM BitrixStockSyncReports r2
 51        WHERE r2.OzonClientId = r.OzonClientId AND r2.Status = 'Completed'
 52          AND r2.OzonAppliedSuccessCount + r2.OzonApplyFailedCount > 0
 53          AND r2.GeneratedAtUtc > r.GeneratedAtUtc)").ToListAsync(cancellationToken);
 54        return BuildFindings(rows);
 55    }
 56
 57    public static OzonApplyFailureKind Classify(string? error)
 58    {
 59        if (string.IsNullOrWhiteSpace(error)) return OzonApplyFailureKind.Other;
 60        if (error.Contains("archived product", StringComparison.OrdinalIgnoreCase)) return OzonApplyFailureKind.Archived
 61        if (error.Contains("ошибка атрибутов", StringComparison.OrdinalIgnoreCase) ||
 62            error.Contains("tags validation failed", StringComparison.OrdinalIgnoreCase)) return OzonApplyFailureKind.At
 63        return OzonApplyFailureKind.Other;
 64    }
 65
 66    /// <summary>Группирует отказы по клиенту и типу: на каждую пару — одно предупреждение со ссылкой на отчёт наката.</
 67    public static IReadOnlyList<SystemAlertFinding> BuildFindings(IEnumerable<OzonApplyFailureRow> rows)
 68    {
 69        var findings = new List<SystemAlertFinding>();
 70        var groups = rows
 71            .Select(x => (Row: x, Kind: Classify(x.Error)))
 72            .Where(x => x.Kind != OzonApplyFailureKind.Other)
 73            .GroupBy(x => (x.Row.OzonClientId, x.Kind))
 74            .OrderBy(g => g.Key.OzonClientId).ThenBy(g => g.Key.Kind);
 75
 76        foreach (var group in groups)
 77        {
 78            var failures = group.Select(x => x.Row).ToList();
 79            var offers = failures.Select(x => x.Offer).Where(x => !string.IsNullOrWhiteSpace(x))
 80                .Distinct(StringComparer.OrdinalIgnoreCase).OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList();
 81            var qty = failures.Sum(x => x.OneCQty);
 82            var reportId = failures.Select(x => x.ReportId).OrderByDescending(x => x, StringComparer.Ordinal).First();
 83            var client = failures.Select(x => x.ClientName).FirstOrDefault(x => !string.IsNullOrWhiteSpace(x)) ?? $"клие
 84            var archived = group.Key.Kind == OzonApplyFailureKind.Archived;
 85
 86            var title = archived
 87                ? $"Накат остатков в Ozon: товары в архиве не принимают остаток — {offers.Count} ({client})"
 88                : $"Накат остатков в Ozon: «Не продаётся (ошибка атрибутов)» — {offers.Count} ({client})";
 89            var lines = new List<string>
 90            {
 91                $"Артикулов: {offers.Count}; по 1С к накату: {qty} шт.",
 92                archived
 93                    ? "Ozon: Can't set positive stock to archived product — верните товары из архива или исключите их из
 94                    : "Ozon не принимает остаток из-за ошибки атрибутов карточки — исправьте карточки в кабинете Ozon.",
 95                "Примеры: " + string.Join(", ", offers.Take(MaxExamples)) + (offers.Count > MaxExamples ? "…" : string.E
 96            };
 97
 98            findings.Add(new SystemAlertFinding(
 99                $"ozon-apply-{(archived ? "archived" : "attributes")}-c{group.Key.OzonClientId}", "stock", "warning",
 100                title, lines, SystemAlertLinks.BitrixSyncReportFailures(reportId, archived ? "archived product" : "ошибк
 101        }
 102
 103        return findings;
 104    }
 105}