< Summary

Information
Class: Services.ApiServices.BergApi.BergDataManager
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/BergApi/BergDataManager.cs
Line coverage
82%
Covered lines: 81
Uncovered lines: 17
Coverable lines: 98
Total lines: 161
Line coverage: 82.6%
Branch coverage
81%
Covered branches: 60
Total branches: 74
Branch coverage: 81%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetBrandsAsync(...)100%210%
GetActiveShipmentAddressesAsync(...)100%210%
GetStatesAsync(...)50%22100%
GetActiveOrderStatesAsync(...)100%210%
GetStatesDictionaryAsync()0%620%
GetStockAsync(...)0%2040%
PlaceOrderAsync()50%2280%
BuildOrderBody(...)88.88%1818100%
BuildOrderItem(...)100%88100%
ParsePlaceOrderResponse(...)100%66100%
ParseOrders(...)50%22100%
ParseMessages(...)66.66%66100%
ParseOrder(...)50%22100%
ParseOrderItem(...)100%1818100%
ParseDate(...)100%44100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/BergApi/BergDataManager.cs

#LineLine coverage
 1using System.Globalization;
 2using Newtonsoft.Json.Linq;
 3
 4namespace Services.ApiServices.BergApi;
 5
 6/// <summary>Операции Berg API: чтение справочников и остатков, размещение заказа, статусы.</summary>
 7public sealed class BergDataManager
 8{
 9    private readonly BergHttpClient _client;
 8010    public BergDataManager(BergHttpClient client) => _client = client;
 11
 012    public Task<JToken> GetBrandsAsync(CancellationToken ct = default) => _client.GetAsync("references/brands.json", ct:
 013    public Task<JToken> GetActiveShipmentAddressesAsync(CancellationToken ct = default) => _client.GetAsync("references/
 14
 15    /// <summary>Статусы по идентификаторам заказов. Параметр называется <c>orders[]</c>:
 16    /// с любым другим именем Berg отвечает ERR_STATES_ORDERS_IDS_INVALID.</summary>
 17    public Task<JToken> GetStatesAsync(IEnumerable<string>? orderIds = null, CancellationToken ct = default)
 18    {
 2219        var query = (orderIds ?? Enumerable.Empty<string>())
 2320            .Where(x => !string.IsNullOrWhiteSpace(x))
 2321            .Select((x, index) => new KeyValuePair<string, string?>($"orders[{index}]", x.Trim()))
 6822            .ToDictionary(x => x.Key, x => x.Value);
 2223        return _client.GetAsync("ordering/states.json", query, ct);
 24    }
 25
 026    public Task<JToken> GetActiveOrderStatesAsync(CancellationToken ct = default) => _client.GetAsync("ordering/states/a
 27
 28    /// <summary>Справочник статусов заказов. Это <c>references/states.json</c>, а не
 29    /// <c>ordering/states.json</c> — последний отдаёт заказы по идентификаторам.</summary>
 30    public async Task<IReadOnlyList<BergState>> GetStatesDictionaryAsync(CancellationToken ct = default)
 31    {
 032        var response = await _client.GetAsync("references/states.json", ct: ct);
 033        return (response["states"] as JArray ?? new JArray())
 034            .OfType<JObject>()
 35            .Select(x => new BergState(x.Value<int?>("id") ?? 0, x.Value<string>("name") ?? string.Empty, x.Value<int?>(
 36            .Where(x => x.Id > 0)
 037            .ToList();
 038    }
 39
 40    public Task<JToken> GetStockAsync(string article, string? brand, bool includeAnalogs, long? addressId = null, Cancel
 41    {
 042        var query = new Dictionary<string, string?>
 043        {
 044            ["items[0][resource_article]"] = article,
 045            ["items[0][brand_name]"] = brand,
 046            ["analogs"] = includeAnalogs ? "1" : "0",
 047            ["address_id"] = addressId?.ToString(CultureInfo.InvariantCulture)
 048        };
 049        return _client.GetAsync("ordering/get_stock.json", query, ct);
 50    }
 51
 52    /// <summary>Размещает заказ. У Berg нет корзины: заказ уходит одним вызовом целиком.</summary>
 53    public async Task<BergPlaceOrderResult> PlaceOrderAsync(BergOrderRequest request, CancellationToken ct = default)
 54    {
 355        if (request.Items.Count == 0)
 056            throw new BergApiException("Заказ Berg без позиций.");
 57
 358        var response = await _client.PostAsync("ordering/place_order.json", BuildOrderBody(request), ct);
 359        return ParsePlaceOrderResponse(response);
 360    }
 61
 62    /// <summary>Тело запроса place_order. Публичный, потому что заказ сохраняется в BergOrders
 63    /// ровно в том виде, в каком ушёл поставщику.</summary>
 64    public static JObject BuildOrderBody(BergOrderRequest request)
 65    {
 866        var order = new JObject
 867        {
 868            ["dispatch_type"] = request.DispatchType,
 869            ["dispatch_at"] = request.DispatchAt.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
 870            ["dispatch_time"] = request.DispatchTime,
 871            ["items"] = new JArray(request.Items.Select(BuildOrderItem))
 872        };
 73
 1174        if (request.IsTest) order["is_test"] = 1;
 1475        if (request.Reference.HasValue) order["reference"] = request.Reference.Value;
 976        if (request.PaymentType.HasValue) order["payment_type"] = request.PaymentType.Value;
 77        // Адрес из справочника Berg принимает только вместе с доставкой: при самовывозе
 78        // он отвечает DELIVERY_ADDRESS_FOR_DISPATCH_SELF.
 879        if (request.ShipmentAddressId.HasValue && request.DispatchType != BergDispatchType.SelfPickup)
 780            order["shipment_address_id"] = request.ShipmentAddressId.Value;
 881        if (!string.IsNullOrWhiteSpace(request.Person)) order["person"] = request.Person;
 882        if (!string.IsNullOrWhiteSpace(request.Phone)) order["phone"] = request.Phone;
 1483        if (!string.IsNullOrWhiteSpace(request.Comment)) order["comment"] = request.Comment;
 84
 885        var body = new JObject { ["order"] = order };
 986        if (request.Force) body["force"] = 1;
 887        return body;
 88    }
 89
 90    private static JObject BuildOrderItem(BergOrderItemRequest item)
 91    {
 892        var node = new JObject
 893        {
 894            ["resource_id"] = item.ResourceId,
 895            ["warehouse_id"] = item.WarehouseId,
 896            ["quantity"] = item.Quantity
 897        };
 1498        if (item.MaxPrice.HasValue) node["max_price"] = item.MaxPrice.Value;
 999        if (item.DeliveryType.HasValue) node["delivery_type"] = item.DeliveryType.Value;
 9100        if (item.IsTransit == true) node["is_transit"] = 1;
 14101        if (!string.IsNullOrWhiteSpace(item.Comment)) node["comment"] = item.Comment;
 8102        return node;
 103    }
 104
 105    internal static BergPlaceOrderResult ParsePlaceOrderResponse(JToken response)
 106    {
 6107        var order = response["order"] as JObject ?? response as JObject;
 6108        var parsed = order is null || order["id"] is null ? null : ParseOrder(order);
 6109        return new BergPlaceOrderResult(parsed, ParseMessages(response["errors"]), ParseMessages(response["warnings"]));
 110    }
 111
 112    /// <summary>Разбирает <c>{"orders":[…]}</c> из ordering/states и ordering/states/active.</summary>
 113    public static IReadOnlyList<BergOrderState> ParseOrders(JToken response) =>
 18114        (response["orders"] as JArray ?? new JArray()).OfType<JObject>().Select(ParseOrder).ToList();
 115
 116    private static IReadOnlyList<BergMessage> ParseMessages(JToken? node) =>
 12117        (node as JArray ?? new JArray()).OfType<JObject>()
 3118            .Select(x => new BergMessage(x.Value<string>("code") ?? string.Empty, x.Value<string>("text") ?? string.Empt
 12119            .ToList();
 120
 24121    private static BergOrderState ParseOrder(JObject order) => new(
 24122        order.Value<long?>("id") ?? 0,
 24123        order.Value<long?>("reference"),
 24124        order.Value<bool?>("is_test") ?? false,
 24125        order.Value<int?>("dispatch_type"),
 24126        ParseDate(order["dispatch_at"]),
 24127        order.Value<int?>("dispatch_time"),
 24128        order.Value<int?>("payment_type"),
 24129        order.Value<string>("shipment_address"),
 24130        order.Value<long?>("shipment_address_id"),
 24131        order.Value<string>("person"),
 24132        order.Value<string>("phone"),
 24133        order.Value<string>("comment"),
 24134        ParseDate(order["created_at"]),
 24135        (order["items"] as JArray ?? new JArray()).OfType<JObject>().Select(ParseOrderItem).ToList());
 136
 25137    private static BergOrderItemState ParseOrderItem(JObject item) => new(
 25138        item.Value<int?>("sequence") ?? 0,
 25139        item.SelectToken("resource.id")?.Value<long?>(),
 25140        item.SelectToken("resource.article")?.Value<string>(),
 25141        item.SelectToken("resource.brand.name")?.Value<string>(),
 25142        item.SelectToken("resource.name")?.Value<string>(),
 25143        item.SelectToken("warehouse.id")?.Value<long?>(),
 25144        item.SelectToken("warehouse.name")?.Value<string>(),
 25145        item.Value<decimal?>("quantity"),
 25146        item.Value<decimal?>("price"),
 25147        item.SelectToken("state.id")?.Value<int?>(),
 25148        item.SelectToken("state.name")?.Value<string>(),
 25149        item.SelectToken("state.type")?.Value<int?>(),
 25150        ParseDate(item["average_time"]),
 25151        ParseDate(item["assured_time"]),
 25152        item.Value<string>("comment"));
 153
 154    private static DateTime? ParseDate(JToken? token)
 155    {
 98156        var raw = token?.Value<string>();
 98157        return DateTime.TryParse(raw, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal | DateTimeStyles.AdjustTo
 98158            ? parsed
 98159            : null;
 160    }
 161}