< Summary

Information
Class: OzonOrdersWeb.WebServices.BitrixStock.BitrixStockGridRowDto
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/WebServices/BitrixStock/BitrixStockGridRowBuilder.cs
Line coverage
100%
Covered lines: 17
Uncovered lines: 0
Coverable lines: 17
Total lines: 233
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_ProductId()100%11100%
get_Article()100%11100%
get_MarketPrefix()100%11100%
get_ArticleWithKey()100%11100%
get_Supplier()100%11100%
get_PreviewText()100%11100%
get_Active()100%11100%
get_BitrixStores()100%11100%
get_BitrixTotal()100%11100%
get_OzonStores()100%11100%
get_OzonTotal()100%11100%
get_PriceIndexes()100%11100%
get_TimestampX()100%11100%
get_Price()100%11100%
get_PriceAuthor()100%11100%
get_PriceUpdatedAt()100%11100%
get_PriceHistoryValue()100%11100%

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{
 551    public int ProductId { get; set; }
 552    public string? Article { get; set; }
 553    public string? MarketPrefix { get; set; }
 1154    public string ArticleWithKey { get; set; } = string.Empty;
 555    public string? Supplier { get; set; }
 556    public string? PreviewText { get; set; }
 1057    public string Active { get; set; } = "Y";
 2758    public List<BitrixStockGridStoreDto> BitrixStores { get; set; } = new();
 659    public double BitrixTotal { get; set; }
 1660    public List<BitrixStockGridOzonStoreDto> OzonStores { get; set; } = new();
 561    public double OzonTotal { get; set; }
 762    public List<BitrixStockGridPriceIndexDto> PriceIndexes { get; set; } = new();
 563    public DateTime TimestampX { get; set; }
 564    public double? Price { get; set; }
 565    public string? PriceAuthor { get; set; }
 566    public DateTime? PriceUpdatedAt { get; set; }
 567    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";
 79    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    {
 88        var storeFilter = (selectedStoreTitles ?? Array.Empty<string>()).ToHashSet(StringComparer.OrdinalIgnoreCase);
 89        var relevantMappings = mappings
 90            .Where(m => !selectedClientId.HasValue || m.OzonClientId == selectedClientId.Value)
 91            .Where(m => storeFilter.Count == 0 || storeFilter.Contains(m.BitrixWarehouseName?.Trim() ?? string.Empty))
 92            .ToList();
 93        var linkageMappings = BitrixOzonStockLinkage.GetStockLinkageMappings(relevantMappings);
 94        var mappedWarehouseIds = linkageMappings
 95            .Select(m => long.TryParse(m.OzonWarehouseId?.Trim(), out var id) ? id : (long?)null)
 96            .Where(id => id.HasValue)
 97            .Select(id => id!.Value)
 98            .ToHashSet();
 99        var mappedBitrixStoreNames = linkageMappings
 100            .Select(m => m.BitrixWarehouseName!.Trim())
 101            .ToHashSet(StringComparer.OrdinalIgnoreCase);
 102
 103        return items.Select(item => BuildRow(item, storeFilter, selectedClientId, selectedClientName,
 104            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    {
 116        var linkedOzonStores = (item.OzonStores ?? new List<StockByStore>())
 117            .Where(BitrixOzonStockLinkage.HasValidOzonProductId)
 118            .Where(s => string.IsNullOrWhiteSpace(selectedClientName) ||
 119                        string.Equals(s.ClientName, selectedClientName, StringComparison.OrdinalIgnoreCase))
 120            .Where(s => s.StoreId > 0 && mappedWarehouseIds.Contains(s.StoreId))
 121            .Where(s => s.LinkStores != null && s.LinkStores.Any(ls =>
 122                linkageMappings.Any(m =>
 123                    string.Equals(m.BitrixWarehouseName?.Trim(), ls, StringComparison.OrdinalIgnoreCase) &&
 124                    (!selectedClientId.HasValue || m.OzonClientId == selectedClientId.Value) &&
 125                    long.TryParse(m.OzonWarehouseId?.Trim(), out var wid) && wid == s.StoreId)))
 126            .ToList();
 127
 128        var linkedBitrixStoreNames = linkedOzonStores
 129            .SelectMany(s => s.LinkStores!)
 130            .Concat((item.Stores ?? new List<StockByStore>())
 131                .Where(s => mappedBitrixStoreNames.Contains(s.Title))
 132                .Select(s => s.Title))
 133            .Distinct(StringComparer.OrdinalIgnoreCase)
 134            .ToList();
 135        var colorMap = linkedBitrixStoreNames
 136            .Select((name, index) => (name, color: Palette[index % Palette.Length]))
 137            .ToDictionary(x => x.name, x => x.color, StringComparer.OrdinalIgnoreCase);
 138
 139        var visibleStores = (item.Stores ?? new List<StockByStore>())
 140            .Where(s => storeFilter.Count == 0 || storeFilter.Contains(s.Title))
 141            .ToList();
 142
 143        var row = new BitrixStockGridRowDto
 144        {
 145            ProductId = item.ProductId,
 146            Article = item.Article,
 147            MarketPrefix = item.MarketPrefix,
 148            ArticleWithKey = item.ArticleWithKey,
 149            Supplier = item.Supplier,
 150            PreviewText = item.PreviewText,
 151            Active = item.Active,
 152            TimestampX = item.TimestampX,
 153            Price = item.Price,
 154            PriceAuthor = item.PriceHistory?.Author,
 155            PriceUpdatedAt = item.PriceHistory?.CreateDateTime,
 156            PriceHistoryValue = item.PriceHistory?.PriceValue
 157        };
 158
 159        foreach (var ws in visibleStores)
 160        {
 161            var isLinked = linkedOzonStores.Any(s => s.LinkStores?.Contains(ws.Title) == true);
 162            var isMapped = mappedBitrixStoreNames.Contains(ws.Title);
 163            row.BitrixStores.Add(new BitrixStockGridStoreDto
 164            {
 165                Title = ws.Title,
 166                Amount = ws.Amount,
 167                Color = isLinked && colorMap.TryGetValue(ws.Title, out var c) ? c : isMapped ? MappedColor : NeutralColo
 168                Bold = isLinked || isMapped
 169            });
 170        }
 171        row.BitrixTotal = row.BitrixStores.Sum(s => s.Amount);
 172
 173        var isInactive = string.Equals(item.Active, "N", StringComparison.OrdinalIgnoreCase);
 174        foreach (var ws in linkedOzonStores)
 175        {
 176            var hasMapping = ws.LinkStores != null && ws.LinkStores.Any();
 177            var linkedStore = item.Stores?.FirstOrDefault(st => ws.LinkStores?.Contains(st.Title) == true);
 178            var primaryName = linkedStore?.Title ?? ws.LinkStores?.FirstOrDefault();
 179            var isLinkedOzon = hasMapping && !string.IsNullOrWhiteSpace(primaryName) && colorMap.ContainsKey(primaryName
 180            var color = isLinkedOzon ? colorMap[primaryName!] : NeutralColor;
 181            var differ = linkedStore != null && Math.Abs(linkedStore.Amount - ws.Amount) > 0.0001;
 182            var ozonRounded = (int)Math.Round(ws.Amount);
 183            var linkedRounded = linkedStore != null ? (int)Math.Round(linkedStore.Amount) : (int?)null;
 184
 185            var hint = new List<string> { $"Озон: {ws.Amount}" };
 186            if (linkedStore != null)
 187            {
 188                hint.Add($"Связано с '{linkedStore.Title}': {linkedStore.Amount}");
 189                if (differ) hint.Add("⚠️ Количества отличаются!");
 190            }
 191            else
 192            {
 193                hint.Add("Сопоставление есть, строка Bitrix на товаре не найдена");
 194            }
 195
 196            if (ws.LinkStores?.Any() == true) hint.Add($"Доступные связи: {string.Join(", ", ws.LinkStores)}");
 197
 198            row.OzonStores.Add(new BitrixStockGridOzonStoreDto
 199            {
 200                Title = ws.Title,
 201                Amount = ws.Amount,
 202                ClientName = ws.ClientName,
 203                OfferId = ws.OfferId,
 204                ProductId = ws.ProductId,
 205                StoreId = ws.StoreId,
 206                Color = color,
 207                AmountColor = Math.Abs(ws.Amount) < 0.0001 ? NeutralColor : differ ? DiffColor : color,
 208                Bold = hasMapping && isLinkedOzon,
 209                Hint = string.Join(" | ", hint),
 210                CanRefresh = linkedStore != null && !isInactive,
 211                CanClear = isInactive && ws.Amount != 0 && (linkedStore == null || linkedRounded != ozonRounded),
 212                LinkedStore = linkedStore?.Title,
 213                TargetAmount = linkedStore?.Amount ?? ws.Amount,
 214                AmountsDiffer = differ
 215            });
 216        }
 217        row.OzonTotal = row.OzonStores.Sum(s => s.Amount);
 218
 219        foreach (var pi in item.OzonPriceIndexes ?? new List<OzonPriceIndexes>())
 220        {
 221            if (pi.OzonIndex == null || pi.OzonIndex.PriceIndexValue == 0) continue;
 222            row.PriceIndexes.Add(new BitrixStockGridPriceIndexDto
 223            {
 224                Value = pi.OzonIndex.PriceIndexValue,
 225                MinPrice = pi.OzonIndex.MinPrice,
 226                ColorIndex = pi.ColorIndex ?? string.Empty,
 227                ClientName = pi.ClientName
 228            });
 229        }
 230
 231        return row;
 232    }
 233}