< Summary

Information
Class: Services.ApiServices._1CApi.Nomenclature.ProducerValueResolver
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/1CApi/Nomenclature/ProducerValueResolver.cs
Line coverage
95%
Covered lines: 41
Uncovered lines: 2
Coverable lines: 43
Total lines: 91
Line coverage: 95.3%
Branch coverage
81%
Covered branches: 18
Total branches: 22
Branch coverage: 81.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%22100%
FindForGroupsAsync()100%22100%
FindByNameAsync()50%2280%
Invalidate()100%11100%
GetValuesAsync()87.5%8894.44%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/1CApi/Nomenclature/ProducerValueResolver.cs

#LineLine coverage
 1using OzonDomains.Nomenclature;
 2using Services.ApiServices._1CApi.OData;
 3using Services.ApiServices._1CApi.OData.Models;
 4
 5namespace Services.ApiServices._1CApi.Nomenclature;
 6
 7/// <summary>Значение реквизита «Производитель» в 1С.</summary>
 8public sealed record ProducerValue(Guid RefKey, string Name);
 9
 10/// <summary>
 11/// Подбирает существующее значение реквизита «Производитель» для группы бренда, чтобы новая карточка получила его,
 12/// а не отдельное значение с другим написанием (Febi рядом с FEBI BILSTEIN, KYB рядом с Kayaba — так появлялись дубли).
 13/// Значения читаются из 1С один раз в час.
 14/// </summary>
 3415public sealed class ProducerValueResolver(IOneCODataClient client, IProducerDirectoryProvider directoryProvider, TimeSpa
 16{
 3417    private readonly TimeSpan _ttl = ttl ?? TimeSpan.FromHours(1);
 3418    private readonly SemaphoreSlim _gate = new(1, 1);
 19    private IReadOnlyList<ProducerValue>? _values;
 3420    private DateTime _expiresUtc = DateTime.MinValue;
 21
 22    /// <param name="groups">Группы бренда заказа.</param>
 23    /// <param name="usageByProducerNorm">Сколько карточек у каждого написания (ключ — <c>ArticleKey.Key(имя)</c>): берё
 24    /// <param name="preferredName">Имя из заказа — решает ничью.</param>
 25    /// <returns>null — в 1С нет значения этой группы (нужно создавать новое прежним способом).</returns>
 26    public async Task<ProducerValue?> FindForGroupsAsync(
 27        IReadOnlySet<string> groups,
 28        IReadOnlyDictionary<string, int>? usageByProducerNorm = null,
 29        string? preferredName = null,
 30        CancellationToken cancellationToken = default)
 31    {
 1732        if (groups.Count == 0)
 133            return null;
 34
 1635        var directory = await directoryProvider.GetAsync(cancellationToken);
 1636        var values = await GetValuesAsync(cancellationToken);
 1637        var preferredNorm = ArticleKey.Key(preferredName);
 38
 1639        return values
 3740            .Where(v => directory.GroupsOfName(v.Name).Overlaps(groups))
 1641            .OrderByDescending(v => usageByProducerNorm != null && usageByProducerNorm.TryGetValue(ArticleKey.Key(v.Name
 1542            .ThenByDescending(v => preferredNorm.Length > 0 && ArticleKey.Key(v.Name) == preferredNorm)
 1543            .ThenBy(v => v.Name, StringComparer.OrdinalIgnoreCase)
 1644            .FirstOrDefault();
 1745    }
 46
 47    /// <summary>Значение по имени (для ручной связи брендов): точное совпадение ключа имени.</summary>
 48    public async Task<ProducerValue?> FindByNameAsync(string? name, CancellationToken cancellationToken = default)
 49    {
 650        var norm = ArticleKey.Key(name);
 651        if (norm.Length == 0)
 052            return null;
 53
 654        var values = await GetValuesAsync(cancellationToken);
 1855        return values.FirstOrDefault(v => ArticleKey.Key(v.Name) == norm);
 656    }
 57
 158    public void Invalidate() => _expiresUtc = DateTime.MinValue;
 59
 60    private async Task<IReadOnlyList<ProducerValue>> GetValuesAsync(CancellationToken cancellationToken)
 61    {
 2262        var current = _values;
 2263        if (current != null && DateTime.UtcNow < _expiresUtc)
 564            return current;
 65
 1766        await _gate.WaitAsync(cancellationToken);
 67        try
 68        {
 1769            if (_values != null && DateTime.UtcNow < _expiresUtc)
 070                return _values;
 71
 1772            var raw = await client.GetListAsync<ODataPropertyValue>(OneCNomenclatureService.PropertyValuesEndpoint,
 1773                new Dictionary<string, string>
 1774                {
 1775                    ["$select"] = "Ref_Key,Description,Owner_Key,DeletionMark",
 1776                    ["$filter"] = $"Owner_Key eq guid'{OneCNomenclatureService.ProducerPropertyKey}' and DeletionMark eq
 1777                }, cancellationToken);
 78
 1779            _values = raw
 3980                .Where(v => v.RefKey != Guid.Empty && !string.IsNullOrWhiteSpace(v.Description))
 3981                .Select(v => new ProducerValue(v.RefKey, v.Description!.Trim()))
 1782                .ToList();
 1783            _expiresUtc = DateTime.UtcNow + _ttl;
 1784            return _values;
 85        }
 86        finally
 87        {
 1788            _gate.Release();
 89        }
 2290    }
 91}