< Summary

Information
Class: Services.BitrixStockServices.BitrixStockArticleVariants
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/BitrixStockServices/BitrixStockArticleVariants.cs
Line coverage
96%
Covered lines: 32
Uncovered lines: 1
Coverable lines: 33
Total lines: 92
Line coverage: 96.9%
Branch coverage
96%
Covered branches: 27
Total branches: 28
Branch coverage: 96.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Normalize(...)100%66100%
SameArticle(...)100%22100%
BuildPrefixPatterns(...)100%66100%
SelectMatching(...)92.85%141492.3%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/BitrixStockServices/BitrixStockArticleVariants.cs

#LineLine coverage
 1namespace Services.BitrixStockServices;
 2
 3/// <summary>
 4/// Разные написания одного артикула, отличающиеся только разделителями.
 5/// В 1С и в карточках Ozon один и тот же товар встречается и как «8934833060A0», и как
 6/// «89348-33060-A0» — это разные карточки номенклатуры, поэтому история синхронизации,
 7/// искавшая точным сравнением, по одному написанию не находила отчёты, записанные под другим.
 8/// </summary>
 9public static class BitrixStockArticleVariants
 10{
 111    private static readonly char[] Separators = { '-', ' ', '_' };
 12
 13    /// <summary>Длина префикса, по которому подбираются кандидаты из индекса Article.</summary>
 14    internal const int PrefixLength = 4;
 15
 16    /// <summary>Артикул без разделителей и в верхнем регистре: «89348-33060-A0» → «8934833060A0».</summary>
 17    public static string Normalize(string? article)
 18    {
 2819        if (string.IsNullOrWhiteSpace(article))
 420            return string.Empty;
 21
 2422        Span<char> buffer = stackalloc char[article.Length];
 2423        var len = 0;
 55624        foreach (var ch in article)
 25        {
 25426            if (Array.IndexOf(Separators, ch) >= 0)
 27                continue;
 23228            buffer[len++] = char.ToUpperInvariant(ch);
 29        }
 30
 2431        return new string(buffer[..len]);
 32    }
 33
 34    /// <summary>Совпадают ли два написания с точностью до разделителей.</summary>
 35    public static bool SameArticle(string? left, string? right)
 36    {
 337        var normalized = Normalize(left);
 338        return normalized.Length > 0 && normalized == Normalize(right);
 39    }
 40
 41    /// <summary>
 42    /// LIKE-паттерны для отбора кандидатов из индекса <c>IX_BitrixStockSyncReportLines_Article</c>.
 43    /// Полный скан 2,7 млн строк с REPLACE() недопустим, поэтому кандидаты берутся префиксным
 44    /// range-scan'ом. Разделитель может стоять и внутри префикса («IF-3400K» → «IF34…» не подойдёт),
 45    /// поэтому кроме «чистого» префикса отдаются варианты с разделителем после каждого его символа.
 46    /// </summary>
 47    public static IReadOnlyList<string> BuildPrefixPatterns(string? article)
 48    {
 449        var normalized = Normalize(article);
 450        if (normalized.Length == 0)
 151            return Array.Empty<string>();
 52
 353        var prefix = normalized.Length <= PrefixLength ? normalized : normalized[..PrefixLength];
 54        // Артикул с % или \ сломал бы LIKE-паттерн — экранируем, прежде чем строить варианты.
 355        prefix = prefix.Replace("\\", "\\\\").Replace("%", "\\%");
 356        var patterns = new List<string> { prefix + "%" };
 57
 2258        for (var i = 1; i < prefix.Length; i++)
 859            patterns.Add($"{prefix[..i]}_{prefix[i..]}%");
 60
 361        return patterns;
 62    }
 63
 64    /// <summary>
 65    /// Оставляет из кандидатов те, что совпадают с искомым артикулом с точностью до разделителей.
 66    /// Исходное написание всегда попадает в результат — даже если в базе его ещё нет.
 67    /// </summary>
 68    public static List<string> SelectMatching(string article, IEnumerable<string?> candidates)
 69    {
 270        var normalized = Normalize(article);
 271        var result = new List<string>();
 272        var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 73
 274        if (!string.IsNullOrWhiteSpace(article) && seen.Add(article.Trim()))
 275            result.Add(article.Trim());
 76
 277        if (normalized.Length == 0)
 078            return result;
 79
 1480        foreach (var candidate in candidates)
 81        {
 582            if (string.IsNullOrWhiteSpace(candidate))
 83                continue;
 84
 385            var trimmed = candidate.Trim();
 386            if (Normalize(trimmed) == normalized && seen.Add(trimmed))
 187                result.Add(trimmed);
 88        }
 89
 290        return result;
 91    }
 92}