< Summary

Information
Class: OzonOrdersWeb.Areas.Studio2.AnalysisServices.ProductDetailBuilder
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/AnalysisServices/ProductDetailBuilder.cs
Line coverage
98%
Covered lines: 90
Uncovered lines: 1
Coverable lines: 91
Total lines: 117
Line coverage: 98.9%
Branch coverage
65%
Covered branches: 13
Total branches: 20
Branch coverage: 65%
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(...)65%202098.87%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/AnalysisServices/ProductDetailBuilder.cs

#LineLine coverage
 1using OzonOrdersWeb.Areas.Studio2.DTOs;
 2
 3namespace OzonOrdersWeb.Areas.Studio2.AnalysisServices;
 4
 5/// <summary>Детализация продукта (вкладка «Обзор»/«Прибыль») из узкой проекции заказов одного ProductKey.</summary>
 6public static class ProductDetailBuilder
 7{
 18    private static readonly string[] RussianMonths =
 19        ["", "Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Д
 10
 11    public static ProductDetailAnalysisDto? Build(string productKey, IReadOnlyCollection<AnalyticsOrderRow> orders)
 12    {
 313        if (orders.Count == 0) return null;
 14
 215        var summary = ProductGroupCalculator.Build(orders).FirstOrDefault(g => g.ProductKey == productKey)
 116                      ?? ProductGroupCalculator.Build(orders).First();
 117        var normalizer = new ProductGroupCalculator.ProfitNormalizer(orders);
 18
 119        var clientBreakdown = orders
 320            .Where(o => !string.IsNullOrEmpty(o.ClientName))
 321            .GroupBy(o => o.ClientName!)
 122            .Select(g =>
 123            {
 524                var amount = g.Sum(o => o.ShipmentAmount ?? 0);
 225                return new ClientDetailDto
 226                {
 227                    ClientName = g.Key,
 228                    TotalOrders = g.Count(),
 329                    TotalQuantity = g.Sum(o => o.Quantity ?? 0),
 230                    TotalShipmentAmount = amount,
 231                    AverageCheck = amount / g.Count()
 232                };
 133            })
 234            .OrderByDescending(c => c.TotalShipmentAmount)
 135            .ToList();
 36
 137        var byMonth = orders
 338            .Where(o => o.ProcessingDate.HasValue)
 339            .GroupBy(o => (o.ProcessingDate!.Value.Year, o.ProcessingDate!.Value.Month))
 440            .OrderBy(g => g.Key.Year).ThenBy(g => g.Key.Month)
 141            .ToList();
 42
 143        var timeAnalysis = new List<TimeAnalysisDto>();
 144        decimal? prevAvgCheck = null;
 645        foreach (var g in byMonth)
 46        {
 547            var amount = g.Sum(o => o.ShipmentAmount ?? 0);
 548            var qty = g.Sum(o => o.Quantity ?? 0);
 249            var avgCheck = amount / g.Count();
 250            var trend = "stable";
 251            if (prevAvgCheck.HasValue)
 52            {
 253                if (avgCheck > prevAvgCheck.Value * 1.1m) trend = "up";
 054                else if (avgCheck < prevAvgCheck.Value * 0.9m) trend = "down";
 55            }
 56
 257            prevAvgCheck = avgCheck;
 258            timeAnalysis.Add(new TimeAnalysisDto
 259            {
 260                Period = $"{RussianMonths[g.Key.Month]} {g.Key.Year}",
 261                PeriodSort = g.Key.Year * 100 + g.Key.Month,
 262                OrdersCount = g.Count(),
 263                TotalQuantity = qty,
 264                TotalAmount = amount,
 265                AverageCheck = avgCheck,
 266                AvgPrice = qty > 0 ? amount / qty : 0,
 267                Trend = trend,
 368                TopClients = g.Where(o => !string.IsNullOrEmpty(o.ClientName))
 369                    .GroupBy(o => o.ClientName!)
 670                    .OrderByDescending(cg => cg.Count()).ThenBy(cg => cg.Key, StringComparer.Ordinal)
 371                    .Take(3).Select(cg => cg.Key).ToList()
 272            });
 73        }
 74
 175        var statusDistribution = orders
 376            .Where(o => !string.IsNullOrEmpty(o.AppStatusName))
 377            .GroupBy(o => o.AppStatusName!)
 378            .ToDictionary(g => g.Key, g => g.Count());
 79
 180        var profitByMonth = byMonth
 281            .Select(g => new { Period = $"{RussianMonths[g.Key.Month]} {g.Key.Year}", TotalProfit = g.Sum(normalizer.Nor
 182            .ToList();
 83
 184        var profitByClient = orders
 385            .Where(o => !string.IsNullOrEmpty(o.ClientName))
 386            .GroupBy(o => o.ClientName!)
 287            .Select(g => new { ClientName = g.Key, TotalProfit = g.Sum(normalizer.Normalize) })
 288            .OrderByDescending(x => x.TotalProfit)
 189            .Take(10)
 190            .ToList();
 91
 192        var priceChart = orders
 393            .Where(o => o.ProcessingDate.HasValue && o.Price.HasValue)
 394            .OrderBy(o => o.ProcessingDate)
 395            .Select(o => new PriceChartPointDto
 396            {
 397                Date = o.ProcessingDate!.Value,
 398                Price = o.Price!.Value,
 399                ShipmentAmount = o.ShipmentAmount,
 3100                Profit = ProductGroupCalculator.HasProfit(o) ? normalizer.Normalize(o) : null,
 3101                OrderId = o.Id,
 3102                ShipmentNumber = o.ShipmentNumber ?? string.Empty
 3103            })
 1104            .ToList();
 105
 1106        return new ProductDetailAnalysisDto
 1107        {
 1108            ProductSummary = summary,
 1109            ClientBreakdown = clientBreakdown,
 1110            TimeAnalysis = timeAnalysis,
 1111            StatusDistribution = statusDistribution,
 1112            PriceChart = priceChart,
 1113            ProfitByMonth = profitByMonth,
 1114            ProfitByClient = profitByClient
 1115        };
 116    }
 117}