< Summary

Information
Class: Services.SupplierStatusServices.SupplierVerificationHistoryEntry
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/SupplierStatusServices/SupplierOrderVerification.cs
Line coverage
74%
Covered lines: 23
Uncovered lines: 8
Coverable lines: 31
Total lines: 172
Line coverage: 74.1%
Branch coverage
50%
Covered branches: 1
Total branches: 2
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_ProviderKey()100%11100%
get_SupplierDisplayName()100%11100%
get_Stage()100%11100%
get_Status()100%11100%
get_Summary()100%11100%
get_SupplierOrderNumber()100%11100%
get_SupplierStatusName()100%11100%
get_Checks()100%11100%
get_ShipmentNumber()100%11100%
get_VerifiedAt()100%11100%
.cctor()100%11100%
FromRow(...)100%11100%
ToJson()100%11100%
TryParse(...)50%2260%
BuildFilterText()100%210%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/SupplierStatusServices/SupplierOrderVerification.cs

#LineLine coverage
 1using System.Text.Encodings.Web;
 2using System.Text.Json;
 3using System.Text.Unicode;
 4
 5namespace Services.SupplierStatusServices;
 6
 7/// <summary>Итог независимой проверки заказа у API-поставщика после проведения транзакции.</summary>
 8public static class SupplierVerificationStatus
 9{
 10    /// <summary>Поставщик подтвердил позицию/заказ повторным запросом в реальный API.</summary>
 11    public const string Confirmed = "Confirmed";
 12
 13    /// <summary>Поставщик ответил, но данные не сошлись (нет номера, отказ по количеству, другой номер).</summary>
 14    public const string Mismatch = "Mismatch";
 15
 16    /// <summary>Поставщик не вернул позицию/заказ или позиции нет в его корзине.</summary>
 17    public const string NotFound = "NotFound";
 18
 19    /// <summary>API недоступен или вернул ошибку — проверить не удалось.</summary>
 20    public const string ApiError = "ApiError";
 21
 22    /// <summary>Проверить нельзя: у поставщика ещё ничего нет (локальная корзина) либо нет провайдера чтения.</summary>
 23    public const string NotVerifiable = "NotVerifiable";
 24
 25    public static bool IsProblem(string status) => status is Mismatch or NotFound or ApiError;
 26
 27    public static string Title(string status) => status switch
 28    {
 29        Confirmed => "Подтверждено у поставщика",
 30        Mismatch => "Расхождение с поставщиком",
 31        NotFound => "Не найдено у поставщика",
 32        ApiError => "Поставщик не ответил",
 33        NotVerifiable => "Проверка невозможна",
 34        _ => status
 35    };
 36}
 37
 38/// <summary>Результат проверки одного заказа Studio2 у одного поставщика.</summary>
 39public sealed record SupplierVerificationRow(
 40    int OrderId,
 41    string? ShipmentNumber,
 42    string? Article,
 43    string? ProductName,
 44    string ProviderKey,
 45    string ProviderName,
 46    string Stage,
 47    string Status,
 48    string Summary,
 49    string? SupplierOrderNumber,
 50    string? SupplierStatusName,
 51    IReadOnlyList<string> Checks,
 52    DateTime VerifiedAt)
 53{
 54    public bool IsProblem => SupplierVerificationStatus.IsProblem(Status);
 55}
 56
 57/// <summary>Сводка проверки по всему проведению.</summary>
 58public sealed record SupplierVerificationSummary(IReadOnlyList<SupplierVerificationRow> Rows)
 59{
 60    public static readonly SupplierVerificationSummary Empty = new(Array.Empty<SupplierVerificationRow>());
 61
 62    public bool IsEmpty => Rows.Count == 0;
 63
 64    public int ConfirmedCount => Rows.Count(x => x.Status == SupplierVerificationStatus.Confirmed);
 65
 66    public int ProblemCount => Rows.Count(x => x.IsProblem);
 67
 68    public int NotVerifiableCount => Rows.Count(x => x.Status == SupplierVerificationStatus.NotVerifiable);
 69
 70    public bool HasProblems => ProblemCount > 0;
 71
 72    /// <summary>Короткая строка для toast: «Поставщики: подтверждено 3, проблем 1».</summary>
 73    public string ToShortText()
 74    {
 75        if (IsEmpty) return string.Empty;
 76        var parts = new List<string> { $"подтверждено {ConfirmedCount}" };
 77        if (ProblemCount > 0) parts.Add($"проблем {ProblemCount}");
 78        if (NotVerifiableCount > 0) parts.Add($"не проверяется {NotVerifiableCount}");
 79        return "Проверка у поставщиков: " + string.Join(", ", parts);
 80    }
 81}
 82
 83/// <summary>
 84/// Запись в orders_history о проверке заказа у поставщика. Пишется системой (ChangedBy = «System: …»),
 85/// JSON лежит в NewValue. Поля после <see cref="Summary"/> обязаны оставаться nullable со значением по умолчанию:
 86/// уже записанные строки не должны ломаться при расширении формата.
 87/// </summary>
 1388public sealed record SupplierVerificationHistoryEntry(
 1289    string ProviderKey,
 1290    string SupplierDisplayName,
 1291    string Stage,
 1392    string Status,
 1293    string Summary,
 1394    string? SupplierOrderNumber = null,
 1295    string? SupplierStatusName = null,
 1296    IReadOnlyList<string>? Checks = null,
 1297    string? ShipmentNumber = null,
 2598    DateTime? VerifiedAt = null)
 99{
 100    /// <summary>Маркер в orders_history.ColumnName — по нему таймлайн истории рисует проверку поставщика.
 101    /// Rollback игнорирует нераспознанные ColumnName, поэтому откат такие записи не затрагивает.</summary>
 102    public const string HistoryColumnName = "SupplierOrderVerification";
 103
 104    /// <summary>Автор записи: содержит «system», поэтому история показывает её как системную.</summary>
 105    public const string SystemAuthor = "System: проверка поставщика";
 106
 107    // По умолчанию System.Text.Json экранирует кириллицу как \uXXXX — в БД должен лежать читаемый текст.
 1108    public static readonly JsonSerializerOptions JsonOptions = new()
 1109    {
 1110        Encoder = JavaScriptEncoder.Create(UnicodeRanges.All)
 1111    };
 112
 1113    private static readonly JsonSerializerOptions ReadOptions = new() { PropertyNameCaseInsensitive = true };
 114
 12115    public static SupplierVerificationHistoryEntry FromRow(SupplierVerificationRow row) => new(
 12116        row.ProviderKey, row.ProviderName, row.Stage, row.Status, row.Summary, row.SupplierOrderNumber,
 12117        row.SupplierStatusName, row.Checks, row.ShipmentNumber, row.VerifiedAt);
 118
 12119    public string ToJson() => JsonSerializer.Serialize(this, JsonOptions);
 120
 121    public static SupplierVerificationHistoryEntry? TryParse(string? json)
 122    {
 1123        if (string.IsNullOrWhiteSpace(json)) return null;
 124        try
 125        {
 1126            return JsonSerializer.Deserialize<SupplierVerificationHistoryEntry>(json, ReadOptions);
 127        }
 0128        catch (JsonException)
 129        {
 0130            return null;
 131        }
 1132    }
 133
 134    /// <summary>Плоский текст для поиска по истории заказа.</summary>
 0135    public string BuildFilterText() => string.Join(" ", new[]
 0136        {
 0137            SupplierDisplayName, SupplierVerificationStatus.Title(Status), Summary, SupplierOrderNumber,
 0138            SupplierStatusName, ShipmentNumber
 0139        }
 0140        .Where(x => !string.IsNullOrWhiteSpace(x)));
 141}
 142
 143/// <summary>Русские названия провайдеров для сводки и истории.</summary>
 144public static class SupplierProviderNames
 145{
 146    public static string Describe(string providerKey) => providerKey switch
 147    {
 148        "Armtek" => "Армтек RU",
 149        "ArmtekBy" => "Армтек РБ",
 150        "Avd" => "АВД",
 151        "MlAuto" => "ML-Auto РБ",
 152        "MlAutoRu" => "ML-Auto RU",
 153        "ForumAuto" => "Форум-Авто (Союз)",
 154        "ForumAutoInterparts" => "Форум-Авто (Интерпартс)",
 155        "ForumAutoPiter" => "Форум-Авто (Питер)",
 156        "ForumAutoRostov" => "Форум-Авто (Ростов)",
 157        "ForumAutoIstra" => "Форум-Авто (Истра)",
 158        "FavoritParts" => "Фаворит Союз",
 159        "FavoritPartsIstra" => "Фаворит Истра",
 160        "FavoritPartsRostov" => "Фаворит Ростов",
 161        "ProfitLiga" => "Профит-Лига",
 162        "ZZapMoscow" => "ZZap Москва",
 163        "Berg" => "Берг",
 164        "Japarts" => "Japarts",
 165        "Motex" => "МоТехС",
 166        "MoskvorechieIstra" => "Москворечье Истра",
 167        "ShateM" => "Шате-М",
 168        "MikadoMskHod20" => "Микадо МскХод20",
 169        "Nikei" => "Nikei",
 170        _ => string.IsNullOrWhiteSpace(providerKey) ? "API-поставщик" : providerKey
 171    };
 172}