< Summary

Information
Class: Services.DataServices.OrderCalculationSnapshotService
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/DataServices/OrderCalculationSnapshotService.cs
Line coverage
100%
Covered lines: 88
Uncovered lines: 0
Coverable lines: 88
Total lines: 120
Line coverage: 100%
Branch coverage
55%
Covered branches: 29
Total branches: 52
Branch coverage: 55.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
CaptureAsync()65%4040100%
SafeRateAsync()100%11100%
Rate(...)50%66100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/DataServices/OrderCalculationSnapshotService.cs

#LineLine coverage
 1using System.Text.Json;
 2using Microsoft.EntityFrameworkCore;
 3using OzonDomains.Models;
 4using OzonRepositories.Context;
 5using Services.PriceСlculationServices;
 6
 7namespace Services.DataServices;
 8
 9/// <summary>Создаёт неизменяемые данные для объяснения расчёта в истории заказа.</summary>
 10public sealed class OrderCalculationSnapshotService
 11{
 12    private readonly ApplicationDbContext _context;
 13    private readonly ICurrencyRateFetcher _rates;
 14
 115    public OrderCalculationSnapshotService(ApplicationDbContext context, ICurrencyRateFetcher rates)
 16    {
 117        _context = context;
 118        _rates = rates;
 119    }
 20
 21    public async Task CaptureAsync(IEnumerable<int> orderIds, Guid operationId, string source, string? changedBy, Cancel
 22    {
 223        var ids = orderIds.Distinct().Where(id => id > 0).ToList();
 124        if (ids.Count == 0 || operationId == Guid.Empty) return;
 25
 126        var existing = await _context.OrderCalculationSnapshots
 127            .Where(x => ids.Contains(x.OrderId) && x.OperationId == operationId)
 128            .Select(x => x.OrderId)
 129            .ToListAsync(cancellationToken);
 130        var missing = ids.Except(existing).ToList();
 131        if (missing.Count == 0) return;
 32
 33        // Триггер записывает поля заказа отдельно. После успешного пользовательского
 34        // сохранения объединяем только свежие ещё не связанные строки в одну операцию.
 135        var historyQuery = _context.OrderHistories.Where(x => missing.Contains(x.OrderId)
 136            && x.OperationId == null && x.ChangedAt >= DateTime.Now.AddMinutes(-5));
 237        if (!string.IsNullOrWhiteSpace(changedBy)) historyQuery = historyQuery.Where(x => x.ChangedBy == changedBy);
 138        await historyQuery.ExecuteUpdateAsync(x => x.SetProperty(h => h.OperationId, operationId), cancellationToken);
 39
 140        var orders = await _context.Orders.AsNoTracking()
 141            .Include(x => x.Supplier)
 142            .Include(x => x.ProductInfo)
 143            .Include(x => x.ExtraExpenses).ThenInclude(x => x.ExpenseType)
 144            .Where(x => missing.Contains(x.Id))
 145            .ToListAsync(cancellationToken);
 46
 147        var usd = await SafeRateAsync(_rates.GetUSDRateAsync());
 148        var eur = await SafeRateAsync(_rates.GetEURRateAsync());
 149        var byn = await SafeRateAsync(_rates.GetBYNRateAsync());
 150        var capturedAt = DateTime.UtcNow;
 51
 452        foreach (var order in orders)
 53        {
 154            var supplier = order.Supplier;
 155            var quantity = Math.Max(1, order.Quantity ?? 1);
 156            var currency = supplier?.CurrencyCode.ToString() ?? "RUB";
 157            var weightCurrency = supplier?.WeightFactorCurrencyCode.ToString() ?? "RUB";
 158            var purchase = order.OriginalPurchasePrice ?? 0m;
 159            var factor = supplier?.CostFactor ?? 1m;
 160            var weightFactor = supplier?.WeightFactor ?? 0m;
 161            var weight = order.ProductInfo?.Weight ?? 0m;
 162            var purchaseRate = Rate(currency, usd, eur, byn);
 163            var weightRate = Rate(weightCurrency, usd, eur, byn);
 164            var purchaseInRub = purchase * factor * purchaseRate;
 165            var weightCost = weight * weightFactor * weightRate;
 166            var expenses = order.ExtraExpenses.Select(x => new
 167            {
 168                amount = x.Amount,
 169                type = x.ExpenseType?.Name ?? "Без типа",
 170                comment = x.Comment ?? string.Empty
 171            }).ToList();
 172            var extraTotal = expenses.Sum(x => x.amount);
 173            var extrasPerUnit = extraTotal / quantity;
 174            var costPrice = purchaseInRub + weightCost + extrasPerUnit;
 175            var orderPrice = order.Price ?? 0m;
 176            var currentPriceWithDiscount = order.ProductInfo?.CurrentPriceWithDiscount;
 177            var price = currentPriceWithDiscount is > 0 ? currentPriceWithDiscount.Value : orderPrice;
 178            var minCommission = order.MinOzonCommission ?? 0m;
 179            var maxCommission = order.MaxOzonCommission ?? minCommission;
 180            var minProfit = price - costPrice - maxCommission;
 181            var maxProfit = price - costPrice - minCommission;
 182            var payload = new
 183            {
 184                version = 1,
 185                orderId = order.Id,
 186                shipmentNumber = order.ShipmentNumber,
 187                capturedAt,
 188                supplier = supplier is null ? null : new { id = supplier.Id, name = supplier.Name, site = supplier.Site 
 189                quantity, price, orderPrice, currentPriceWithDiscount, shipmentAmount = price * quantity,
 190                purchasePrice = purchase, currency, costFactor = factor, currencyRate = purchaseRate, purchaseInRub,
 191                weight, weightFactor, weightCurrency, weightRate, weightCost,
 192                expenses, extraTotal, extrasPerUnit, costPrice,
 193                minCommission, maxCommission, minCommissionInfo = order.MinCommissionInfo ?? string.Empty,
 194                maxCommissionInfo = order.MaxCommissionInfo ?? string.Empty,
 195                minProfit, maxProfit,
 196                minMarkup = costPrice == 0 ? (decimal?)null : minProfit / costPrice * 100m,
 197                maxMarkup = costPrice == 0 ? (decimal?)null : maxProfit / costPrice * 100m
 198            };
 199            _context.OrderCalculationSnapshots.Add(new OrderCalculationSnapshot
 1100            {
 1101                OrderId = order.Id,
 1102                OperationId = operationId,
 1103                Source = source,
 1104                CapturedAt = capturedAt,
 1105                PayloadJson = JsonSerializer.Serialize(payload)
 1106            });
 107        }
 1108        await _context.SaveChangesAsync(cancellationToken);
 1109    }
 110
 111    private static async Task<decimal> SafeRateAsync(Task<decimal> source)
 112    {
 3113        try { return await source; } catch { return 1m; }
 3114    }
 115
 2116    private static decimal Rate(string currency, decimal usd, decimal eur, decimal byn) => currency.ToUpperInvariant() s
 2117    {
 2118        "USD" => usd, "EUR" => eur, "BYN" => byn, _ => 1m
 2119    };
 120}