< Summary

Information
Class: OzonOrdersWeb.Areas.Studio2.Helpers.TransactionStockCheckLineBuilder
Assembly: OzonOrdersWeb
File(s): /home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Helpers/TransactionStockCheckLineBuilder.cs
Line coverage
79%
Covered lines: 51
Uncovered lines: 13
Coverable lines: 64
Total lines: 184
Line coverage: 79.6%
Branch coverage
90%
Covered branches: 40
Total branches: 44
Branch coverage: 90.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
LoadForStockCheckAsync(...)100%210%
QuantitiesFromForm(...)83.33%6687.5%
FromExplicitWarehouse(...)100%11100%
FromExplicitWarehouses(...)100%44100%
FromWarehouseMappings(...)100%11100%
FromEffectiveWarehouse(...)100%11100%
Build(...)92.85%1414100%
BuildStockCheckSenderMap(...)83.33%6687.5%
ResolveQuantity(...)92.85%1414100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/OzonOrdersWeb/Areas/Studio2/Helpers/TransactionStockCheckLineBuilder.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using OzonDomains.Models;
 3using OzonOrdersWeb.Areas.Studio2.Controllers.Base;
 4using OzonRepositories.Context;
 5using Services.ApiServices._1CApi;
 6using Services.ApiServices._1CApi.Stock;
 7
 8namespace OzonOrdersWeb.Areas.Studio2.Helpers;
 9
 10/// <summary>
 11/// Собирает строки для проверки остатков 1С. Резолв склада-отправителя остаётся там, где он живёт
 12/// для каждой транзакции, — сервис проверки получает готовые тройки (заказ, склад, количество).
 13/// </summary>
 14public static class TransactionStockCheckLineBuilder
 15{
 16    /// <summary>
 17    /// Перезагружает заказы со связями, нужными для резолва склада (засыл, склад отгрузки) и
 18    /// производителя. Заказы, пришедшие из формы, этих связей не содержат — без перезагрузки склад
 19    /// не определяется и проверка молча ничего не проверяет.
 20    /// </summary>
 21    public static Task<List<Order>> LoadForStockCheckAsync(ApplicationDbContext context, IEnumerable<int> orderIds)
 22    {
 023        var ids = orderIds.Distinct().ToList();
 024        return context.Orders
 025            .AsNoTracking()
 026            .Include(o => o.OzonClient)
 027            .Include(o => o.ShipmentWarehouse)
 028            .Include(o => o.Delivery)
 029            .Include(o => o.OrderExtensions)
 030            .Include(o => o.EtProducer)
 031            .Include(o => o.Manufacturer)
 032            .Where(o => ids.Contains(o.Id))
 033            .ToListAsync();
 34    }
 35
 36    /// <summary>
 37    /// Количества из формы в ШТУКАХ ЗАКАЗА (без кратности упаковки): пользователь мог изменить их
 38    /// перед отправкой. Кратность применяется позже, по ProductKey перезагруженного заказа.
 39    /// </summary>
 40    public static Dictionary<int, decimal> QuantitiesFromForm(IEnumerable<Order>? formOrders)
 41    {
 142        var result = new Dictionary<int, decimal>();
 143        if (formOrders == null)
 044            return result;
 45
 1046        foreach (var order in formOrders)
 47        {
 448            var qty = order.Quantity ?? 0;
 449            if (qty > 0)
 250                result[order.Id] = qty;
 51        }
 52
 153        return result;
 54    }
 55
 56    /// <summary>Склад выбран пользователем в форме: WarehouseTransfer, ReturnToSupplier.</summary>
 57    public static IReadOnlyList<StockCheckLine> FromExplicitWarehouse(
 58        IEnumerable<Order>? orders,
 59        string? warehouseSender,
 60        IReadOnlyDictionary<int, decimal>? formQuantities = null,
 61        IReadOnlyDictionary<int, decimal>? finalQtyOverrides = null) =>
 1662        Build(orders, _ => warehouseSender, formQuantities, finalQtyOverrides);
 63
 64    /// <summary>
 65    /// Склад выбран пользователем отдельно для каждого заказа. Используется для маршрутов
 66    /// «Возврат поставщику»: остаток проверяется именно на складе-отправителе этой строки.
 67    /// </summary>
 68    public static IReadOnlyList<StockCheckLine> FromExplicitWarehouses(
 69        IEnumerable<Order>? orders,
 70        IReadOnlyDictionary<int, string>? warehouseSenderByOrder,
 71        string? defaultWarehouseSender = null,
 72        IReadOnlyDictionary<int, decimal>? formQuantities = null,
 73        IReadOnlyDictionary<int, decimal>? finalQtyOverrides = null) =>
 274        Build(orders,
 475            order => warehouseSenderByOrder != null && warehouseSenderByOrder.TryGetValue(order.Id, out var warehouse)
 476                ? warehouse
 477                : defaultWarehouseSender,
 278            formQuantities,
 279            finalQtyOverrides);
 80
 81    /// <summary>
 82    /// Склад берётся из засыла / маппинга складов: ShippedToCommissioner, ShippedToSeller, транзит.
 83    /// Использует тот же резолвер, что и создание документа.
 84    /// </summary>
 85    public static IReadOnlyList<StockCheckLine> FromWarehouseMappings(
 86        IEnumerable<Order>? orders,
 87        List<WarehouseMapping>? warehouseMappings,
 88        Dictionary<int, string>? orderSupplierMap,
 89        IReadOnlyDictionary<int, decimal>? formQuantities = null,
 90        IReadOnlyDictionary<int, decimal>? finalQtyOverrides = null) =>
 391        Build(orders,
 392            order => OneCTransferManager.ResolveWarehouseFor1C(order, orderSupplierMap, warehouseMappings),
 393            formQuantities,
 394            finalQtyOverrides);
 95
 96    /// <summary>Склад из засыла заказа или склада отгрузки: ShippedToClient (расходная накладная).</summary>
 97    public static IReadOnlyList<StockCheckLine> FromEffectiveWarehouse(
 98        IEnumerable<Order>? orders,
 99        IReadOnlyDictionary<int, decimal>? formQuantities = null,
 100        IReadOnlyDictionary<int, decimal>? finalQtyOverrides = null) =>
 2101        Build(orders, TransactionWarehouseHelper.GetEffectiveWarehouseName, formQuantities, finalQtyOverrides);
 102
 103    private static IReadOnlyList<StockCheckLine> Build(
 104        IEnumerable<Order>? orders,
 105        Func<Order, string?> warehouseResolver,
 106        IReadOnlyDictionary<int, decimal>? formQuantities,
 107        IReadOnlyDictionary<int, decimal>? finalQtyOverrides)
 108    {
 15109        if (orders == null)
 1110            return Array.Empty<StockCheckLine>();
 111
 14112        var lines = new List<StockCheckLine>();
 66113        foreach (var order in orders)
 114        {
 19115            if (order == null)
 116                continue;
 117
 19118            var quantity = ResolveQuantity(order, formQuantities, finalQtyOverrides);
 19119            if (quantity <= 0)
 120                continue;
 121
 17122            lines.Add(new StockCheckLine(
 17123                order.Id,
 17124                order.Article,
 17125                order.ProductName,
 17126                order.EtProducer?.Name ?? order.Manufacturer?.Name,
 17127                warehouseResolver(order),
 17128                quantity));
 129        }
 130
 14131        return lines;
 132    }
 133
 134    /// <summary>
 135    /// Карта «заказ → склад-отправитель» для ViewData["StockCheckSenderByOrderId"]: предпросмотр на
 136    /// странице должен проверять тот же склад, что и серверный гейт, а в разметке строки стоит
 137    /// «эффективный» склад заказа, а не результат маппинга.
 138    /// </summary>
 139    public static Dictionary<int, string> BuildStockCheckSenderMap(
 140        IEnumerable<Order>? orders,
 141        List<WarehouseMapping>? warehouseMappings)
 142    {
 1143        var map = new Dictionary<int, string>();
 1144        if (orders == null)
 0145            return map;
 146
 6147        foreach (var order in orders)
 148        {
 2149            var sender = OneCTransferManager.ResolveWarehouseFor1C(order, null, warehouseMappings);
 2150            if (!string.IsNullOrWhiteSpace(sender))
 1151                map[order.Id] = sender;
 152        }
 153
 1154        return map;
 155    }
 156
 157    /// <summary>
 158    /// Зеркало расчёта количества в OneCTransferManager.TransferByClientAsync:
 159    /// stockQtyOverrides перекрывает всё, иначе TrueQuantity (то есть штуки заказа × кратность).
 160    /// </summary>
 161    private static decimal ResolveQuantity(
 162        Order order,
 163        IReadOnlyDictionary<int, decimal>? formQuantities,
 164        IReadOnlyDictionary<int, decimal>? finalQtyOverrides)
 165    {
 166        // Готовое количество для 1С (например, остаток WS у транзитного перемещения) — как есть.
 19167        if (finalQtyOverrides != null && finalQtyOverrides.TryGetValue(order.Id, out var finalQty) && finalQty > 0)
 2168            return finalQty;
 169
 170        // Штуки заказа: из формы, если пользователь их правил, иначе из БД. Кратность упаковки
 171        // берём из ProductKey перезагруженного заказа — в форме это скрытое поле, на него полагаться
 172        // нельзя, а ошибка тут занижает потребность в 10 раз (артикул вида 9046708011=TYT[10]).
 17173        var baseQty = formQuantities != null && formQuantities.TryGetValue(order.Id, out var formQty)
 17174            ? formQty
 17175            : order.Quantity ?? 0;
 176
 17177        if (baseQty <= 0)
 2178            return 0;
 179
 15180        return baseQty == decimal.Truncate(baseQty)
 15181            ? Order.ApplyProductKeyMultiplier((int)baseQty, order.ProductKey)
 15182            : baseQty;
 183    }
 184}

Methods/Properties

LoadForStockCheckAsync(OzonRepositories.Context.ApplicationDbContext,System.Collections.Generic.IEnumerable`1<System.Int32>)
QuantitiesFromForm(System.Collections.Generic.IEnumerable`1<OzonDomains.Models.Order>)
FromExplicitWarehouse(System.Collections.Generic.IEnumerable`1<OzonDomains.Models.Order>,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>)
FromExplicitWarehouses(System.Collections.Generic.IEnumerable`1<OzonDomains.Models.Order>,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.String>,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>)
FromWarehouseMappings(System.Collections.Generic.IEnumerable`1<OzonDomains.Models.Order>,System.Collections.Generic.List`1<OzonDomains.Models.WarehouseMapping>,System.Collections.Generic.Dictionary`2<System.Int32,System.String>,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>)
FromEffectiveWarehouse(System.Collections.Generic.IEnumerable`1<OzonDomains.Models.Order>,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>)
Build(System.Collections.Generic.IEnumerable`1<OzonDomains.Models.Order>,System.Func`2<OzonDomains.Models.Order,System.String>,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>)
BuildStockCheckSenderMap(System.Collections.Generic.IEnumerable`1<OzonDomains.Models.Order>,System.Collections.Generic.List`1<OzonDomains.Models.WarehouseMapping>)
ResolveQuantity(OzonDomains.Models.Order,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>,System.Collections.Generic.IReadOnlyDictionary`2<System.Int32,System.Decimal>)