< Summary

Information
Class: OzonOrdersWeb.WebServices.BitrixStock.BitrixStockGridRowBuilder
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/BitrixStock/BitrixStockGridRowBuilder.cs
Line coverage
99%
Covered lines: 111
Uncovered lines: 1
Coverable lines: 112
Total lines: 233
Line coverage: 99.1%
Branch coverage
64%
Covered branches: 73
Total branches: 114
Branch coverage: 64%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Build(...)71.42%1414100%
BuildRow(...)63%10010098.94%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/BitrixStock/BitrixStockGridRowBuilder.cs

#LineLine coverage
 1using OzonDomains.Models;
 2using OzonDomains.Models.BitrixModels;
 3using OzonOrdersWeb.Areas.Studio2.ViewModels.Bitrix;
 4using Services.BitrixStockServices;
 5
 6namespace OzonOrdersWeb.WebServices.BitrixStock;
 7
 8/// <summary>Склад Bitrix в строке грида «Остатки»: цвет/жирность — по связкам с Ozon (как раньше в Razor).</summary>
 9public sealed class BitrixStockGridStoreDto
 10{
 11    public string Title { get; set; } = string.Empty;
 12    public double Amount { get; set; }
 13    public string Color { get; set; } = BitrixStockGridRowBuilder.NeutralColor;
 14    public bool Bold { get; set; }
 15}
 16
 17/// <summary>Склад Ozon в строке: остаток, связанный склад Bitrix, доступность наката/обнуления.</summary>
 18public sealed class BitrixStockGridOzonStoreDto
 19{
 20    public string Title { get; set; } = string.Empty;
 21    public double Amount { get; set; }
 22    public string? ClientName { get; set; }
 23    public string OfferId { get; set; } = string.Empty;
 24    public string? ProductId { get; set; }
 25    public long StoreId { get; set; }
 26    public string Color { get; set; } = BitrixStockGridRowBuilder.NeutralColor;
 27    public string AmountColor { get; set; } = BitrixStockGridRowBuilder.NeutralColor;
 28    public bool Bold { get; set; }
 29    /// <summary>Подсказка кнопки наката: остатки Ozon/связанного склада, расхождение, доступные связи.</summary>
 30    public string Hint { get; set; } = string.Empty;
 31    /// <summary>Накат доступен: есть связанный склад Bitrix и товар активен.</summary>
 32    public bool CanRefresh { get; set; }
 33    /// <summary>Обнуление неактивного товара: Ozon ≠ 0 и расходится со связанным складом.</summary>
 34    public bool CanClear { get; set; }
 35    public string? LinkedStore { get; set; }
 36    public double TargetAmount { get; set; }
 37    public bool AmountsDiffer { get; set; }
 38}
 39
 40public sealed class BitrixStockGridPriceIndexDto
 41{
 42    public double Value { get; set; }
 43    public decimal MinPrice { get; set; }
 44    public string ColorIndex { get; set; } = string.Empty;
 45    public string? ClientName { get; set; }
 46}
 47
 48/// <summary>Строка грида «Остатки» (/Studio2/BitrixStock).</summary>
 49public sealed class BitrixStockGridRowDto
 50{
 51    public int ProductId { get; set; }
 52    public string? Article { get; set; }
 53    public string? MarketPrefix { get; set; }
 54    public string ArticleWithKey { get; set; } = string.Empty;
 55    public string? Supplier { get; set; }
 56    public string? PreviewText { get; set; }
 57    public string Active { get; set; } = "Y";
 58    public List<BitrixStockGridStoreDto> BitrixStores { get; set; } = new();
 59    public double BitrixTotal { get; set; }
 60    public List<BitrixStockGridOzonStoreDto> OzonStores { get; set; } = new();
 61    public double OzonTotal { get; set; }
 62    public List<BitrixStockGridPriceIndexDto> PriceIndexes { get; set; } = new();
 63    public DateTime TimestampX { get; set; }
 64    public double? Price { get; set; }
 65    public string? PriceAuthor { get; set; }
 66    public DateTime? PriceUpdatedAt { get; set; }
 67    public decimal? PriceHistoryValue { get; set; }
 68}
 69
 70/// <summary>
 71/// Строки грида «Остатки»: логика, которая раньше жила в Razor каждой строки Index.cshtml — видимые склады
 72/// Bitrix по фильтру, связки Bitrix → Ozon по WarehouseMapping (цвета пар складов), кнопки наката/обнуления.
 73/// </summary>
 74public static class BitrixStockGridRowBuilder
 75{
 76    public const string NeutralColor = "#9ca3af";
 77    private const string MappedColor = "#0d6efd";
 78    private const string DiffColor = "orange";
 179    private static readonly string[] Palette = { "#2563eb", "#7c3aed", "#0d9488", "#ea580c", "#db2777", "#16a34a", "#089
 80
 81    public static List<BitrixStockGridRowDto> Build(
 82        IEnumerable<RemainingStockBitrix> items,
 83        IReadOnlyCollection<WarehouseMapping> mappings,
 84        IReadOnlyCollection<string>? selectedStoreTitles,
 85        int? selectedClientId,
 86        string? selectedClientName)
 87    {
 588        var storeFilter = (selectedStoreTitles ?? Array.Empty<string>()).ToHashSet(StringComparer.OrdinalIgnoreCase);
 589        var relevantMappings = mappings
 490            .Where(m => !selectedClientId.HasValue || m.OzonClientId == selectedClientId.Value)
 491            .Where(m => storeFilter.Count == 0 || storeFilter.Contains(m.BitrixWarehouseName?.Trim() ?? string.Empty))
 592            .ToList();
 593        var linkageMappings = BitrixOzonStockLinkage.GetStockLinkageMappings(relevantMappings);
 594        var mappedWarehouseIds = linkageMappings
 495            .Select(m => long.TryParse(m.OzonWarehouseId?.Trim(), out var id) ? id : (long?)null)
 496            .Where(id => id.HasValue)
 497            .Select(id => id!.Value)
 598            .ToHashSet();
 599        var mappedBitrixStoreNames = linkageMappings
 4100            .Select(m => m.BitrixWarehouseName!.Trim())
 5101            .ToHashSet(StringComparer.OrdinalIgnoreCase);
 102
 10103        return items.Select(item => BuildRow(item, storeFilter, selectedClientId, selectedClientName,
 10104            linkageMappings, mappedWarehouseIds, mappedBitrixStoreNames)).ToList();
 105    }
 106
 107    private static BitrixStockGridRowDto BuildRow(
 108        RemainingStockBitrix item,
 109        HashSet<string> storeFilter,
 110        int? selectedClientId,
 111        string? selectedClientName,
 112        List<WarehouseMapping> linkageMappings,
 113        HashSet<long> mappedWarehouseIds,
 114        HashSet<string> mappedBitrixStoreNames)
 115    {
 5116        var linkedOzonStores = (item.OzonStores ?? new List<StockByStore>())
 5117            .Where(BitrixOzonStockLinkage.HasValidOzonProductId)
 10118            .Where(s => string.IsNullOrWhiteSpace(selectedClientName) ||
 10119                        string.Equals(s.ClientName, selectedClientName, StringComparison.OrdinalIgnoreCase))
 8120            .Where(s => s.StoreId > 0 && mappedWarehouseIds.Contains(s.StoreId))
 3121            .Where(s => s.LinkStores != null && s.LinkStores.Any(ls =>
 6122                linkageMappings.Any(m =>
 9123                    string.Equals(m.BitrixWarehouseName?.Trim(), ls, StringComparison.OrdinalIgnoreCase) &&
 9124                    (!selectedClientId.HasValue || m.OzonClientId == selectedClientId.Value) &&
 9125                    long.TryParse(m.OzonWarehouseId?.Trim(), out var wid) && wid == s.StoreId)))
 5126            .ToList();
 127
 5128        var linkedBitrixStoreNames = linkedOzonStores
 3129            .SelectMany(s => s.LinkStores!)
 5130            .Concat((item.Stores ?? new List<StockByStore>())
 15131                .Where(s => mappedBitrixStoreNames.Contains(s.Title))
 4132                .Select(s => s.Title))
 5133            .Distinct(StringComparer.OrdinalIgnoreCase)
 5134            .ToList();
 5135        var colorMap = linkedBitrixStoreNames
 4136            .Select((name, index) => (name, color: Palette[index % Palette.Length]))
 13137            .ToDictionary(x => x.name, x => x.color, StringComparer.OrdinalIgnoreCase);
 138
 5139        var visibleStores = (item.Stores ?? new List<StockByStore>())
 15140            .Where(s => storeFilter.Count == 0 || storeFilter.Contains(s.Title))
 5141            .ToList();
 142
 5143        var row = new BitrixStockGridRowDto
 5144        {
 5145            ProductId = item.ProductId,
 5146            Article = item.Article,
 5147            MarketPrefix = item.MarketPrefix,
 5148            ArticleWithKey = item.ArticleWithKey,
 5149            Supplier = item.Supplier,
 5150            PreviewText = item.PreviewText,
 5151            Active = item.Active,
 5152            TimestampX = item.TimestampX,
 5153            Price = item.Price,
 5154            PriceAuthor = item.PriceHistory?.Author,
 5155            PriceUpdatedAt = item.PriceHistory?.CreateDateTime,
 5156            PriceHistoryValue = item.PriceHistory?.PriceValue
 5157        };
 158
 38159        foreach (var ws in visibleStores)
 160        {
 22161            var isLinked = linkedOzonStores.Any(s => s.LinkStores?.Contains(ws.Title) == true);
 14162            var isMapped = mappedBitrixStoreNames.Contains(ws.Title);
 14163            row.BitrixStores.Add(new BitrixStockGridStoreDto
 14164            {
 14165                Title = ws.Title,
 14166                Amount = ws.Amount,
 14167                Color = isLinked && colorMap.TryGetValue(ws.Title, out var c) ? c : isMapped ? MappedColor : NeutralColo
 14168                Bold = isLinked || isMapped
 14169            });
 170        }
 19171        row.BitrixTotal = row.BitrixStores.Sum(s => s.Amount);
 172
 5173        var isInactive = string.Equals(item.Active, "N", StringComparison.OrdinalIgnoreCase);
 16174        foreach (var ws in linkedOzonStores)
 175        {
 3176            var hasMapping = ws.LinkStores != null && ws.LinkStores.Any();
 6177            var linkedStore = item.Stores?.FirstOrDefault(st => ws.LinkStores?.Contains(st.Title) == true);
 3178            var primaryName = linkedStore?.Title ?? ws.LinkStores?.FirstOrDefault();
 3179            var isLinkedOzon = hasMapping && !string.IsNullOrWhiteSpace(primaryName) && colorMap.ContainsKey(primaryName
 3180            var color = isLinkedOzon ? colorMap[primaryName!] : NeutralColor;
 3181            var differ = linkedStore != null && Math.Abs(linkedStore.Amount - ws.Amount) > 0.0001;
 3182            var ozonRounded = (int)Math.Round(ws.Amount);
 3183            var linkedRounded = linkedStore != null ? (int)Math.Round(linkedStore.Amount) : (int?)null;
 184
 3185            var hint = new List<string> { $"Озон: {ws.Amount}" };
 3186            if (linkedStore != null)
 187            {
 3188                hint.Add($"Связано с '{linkedStore.Title}': {linkedStore.Amount}");
 6189                if (differ) hint.Add("⚠️ Количества отличаются!");
 190            }
 191            else
 192            {
 0193                hint.Add("Сопоставление есть, строка Bitrix на товаре не найдена");
 194            }
 195
 6196            if (ws.LinkStores?.Any() == true) hint.Add($"Доступные связи: {string.Join(", ", ws.LinkStores)}");
 197
 3198            row.OzonStores.Add(new BitrixStockGridOzonStoreDto
 3199            {
 3200                Title = ws.Title,
 3201                Amount = ws.Amount,
 3202                ClientName = ws.ClientName,
 3203                OfferId = ws.OfferId,
 3204                ProductId = ws.ProductId,
 3205                StoreId = ws.StoreId,
 3206                Color = color,
 3207                AmountColor = Math.Abs(ws.Amount) < 0.0001 ? NeutralColor : differ ? DiffColor : color,
 3208                Bold = hasMapping && isLinkedOzon,
 3209                Hint = string.Join(" | ", hint),
 3210                CanRefresh = linkedStore != null && !isInactive,
 3211                CanClear = isInactive && ws.Amount != 0 && (linkedStore == null || linkedRounded != ozonRounded),
 3212                LinkedStore = linkedStore?.Title,
 3213                TargetAmount = linkedStore?.Amount ?? ws.Amount,
 3214                AmountsDiffer = differ
 3215            });
 216        }
 8217        row.OzonTotal = row.OzonStores.Sum(s => s.Amount);
 218
 14219        foreach (var pi in item.OzonPriceIndexes ?? new List<OzonPriceIndexes>())
 220        {
 2221            if (pi.OzonIndex == null || pi.OzonIndex.PriceIndexValue == 0) continue;
 1222            row.PriceIndexes.Add(new BitrixStockGridPriceIndexDto
 1223            {
 1224                Value = pi.OzonIndex.PriceIndexValue,
 1225                MinPrice = pi.OzonIndex.MinPrice,
 1226                ColorIndex = pi.ColorIndex ?? string.Empty,
 1227                ClientName = pi.ClientName
 1228            });
 229        }
 230
 5231        return row;
 232    }
 233}