< Summary

Information
Class: Services.ApiServices.ZZapApi.ZZapDataManager
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/ZZapApi/ZZapDataManager.cs
Line coverage
100%
Covered lines: 91
Uncovered lines: 0
Coverable lines: 91
Total lines: 181
Line coverage: 100%
Branch coverage
85%
Covered branches: 41
Total branches: 48
Branch coverage: 85.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_DefaultCodeRegion()100%11100%
GetStatusAsync(...)100%11100%
GetUserAsync(...)100%11100%
GetOrderStatusesAsync(...)100%11100%
GetRegionsAsync(...)100%11100%
GetRegionAsync(...)100%11100%
SearchLightAsync(...)50%22100%
GetCartAsync(...)100%11100%
GetOrdersAsync(...)100%11100%
GetOrderAsync(...)100%11100%
AddToCartAsync(...)100%66100%
RemoveFromCartAsync()100%44100%
CreateOrderFromCartAsync(...)66.66%66100%
CreateOrderDirectAsync(...)70%1010100%
SetOrderStatusAsync(...)50%22100%
GetBuyerContactAsync()100%1212100%
Text()100%44100%
TestFlag(...)100%22100%
Page(...)100%11100%
Number(...)100%11100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/ZZapApi/ZZapDataManager.cs

#LineLine coverage
 1using System.Globalization;
 2using Newtonsoft.Json.Linq;
 3
 4namespace Services.ApiServices.ZZapApi;
 5
 6/// <summary>Реквизиты покупателя для POST /cart/order.</summary>
 7/// <summary>Реквизиты покупателя. /cart/order принимает телефон одной строкой, а /orders — по частям
 8/// (код страны, код города, номер), поэтому храним и то и другое.</summary>
 9public sealed record ZZapBuyerContact(string Email, string Phone, string ClientName, string NameFirst, string NameLast, 
 10    string PhoneCodeCountry = "", string PhoneCodeCity = "", string PhoneLocal = "", string PhoneAdd = "");
 11
 12/// <summary>Read-операции клиентского API ZZap. Методы отдают конверт ответа как есть:
 13/// консоль показывает его целиком, а типизированный разбор появится вместе с проценкой.</summary>
 14public sealed class ZZapDataManager
 15{
 16    /// <summary>type_request для /search/light: 4 — любые предложения только по запрошенному номеру,
 17    /// 5 — только новые. Замены и кроссы этот метод не возвращает по определению.</summary>
 18    public const int SearchLightAnyCondition = 4;
 19    public const int SearchLightNewOnly = 5;
 20
 21    private readonly ZZapHttpClient _client;
 22    private readonly ZZapConfig _config;
 23
 10624    public ZZapDataManager(ZZapHttpClient client, ZZapConfig config)
 25    {
 10626        _client = client;
 10627        _config = config;
 10628    }
 29
 130    public int DefaultCodeRegion => _config.CodeRegion;
 31
 32    /// <summary>Самый дешёвый метод, требующий валидного ключа: {"online": true}.</summary>
 33    public Task<JObject> GetStatusAsync(CancellationToken ct = default) =>
 434        _client.GetAsync("api/client/v1/status", ct: ct);
 35
 36    public Task<JObject> GetUserAsync(CancellationToken ct = default) =>
 1837        _client.GetAsync("api/client/v1/user", ct: ct);
 38
 39    /// <summary>Справочник статусов заказов: code_track → class_track.</summary>
 40    public Task<JObject> GetOrderStatusesAsync(CancellationToken ct = default) =>
 141        _client.GetAsync("api/client/v1/orders/status", Page(1, 100), ct);
 42
 43    public Task<JObject> GetRegionsAsync(int page = 1, int pageSize = 100, CancellationToken ct = default) =>
 144        _client.GetAsync("api/client/v1/regions", Page(page, pageSize), ct);
 45
 46    public Task<JObject> GetRegionAsync(int codeRegion, CancellationToken ct = default) =>
 147        _client.GetAsync($"api/client/v1/regions/{codeRegion}", ct: ct);
 48
 49    /// <summary>Поиск только по запрошенному номеру (без замен) — режим, рекомендованный ZZap для интеграций
 50    /// и единственный, укладывающийся в контракт проценки «оригиналы без кроссов».</summary>
 51    public Task<JObject> SearchLightAsync(string partNumber, string brand, int typeRequest = SearchLightNewOnly,
 52        int? codeRegion = null, int page = 1, int pageSize = 100, CancellationToken ct = default) =>
 153        _client.GetAsync("api/client/v1/search/light", new Dictionary<string, string?>
 154        {
 155            ["partnumber"] = partNumber.Trim(),
 156            ["class_man"] = brand.Trim(),
 157            ["type_request"] = Number(typeRequest),
 158            ["code_region"] = Number(codeRegion ?? _config.CodeRegion),
 159            ["page"] = Number(page),
 160            ["page_size"] = Number(pageSize)
 161        }, ct);
 62
 63    public Task<JObject> GetCartAsync(int page = 1, int pageSize = 100, CancellationToken ct = default) =>
 1564        _client.GetAsync("api/client/v1/cart", Page(page, pageSize), ct);
 65
 66    public Task<JObject> GetOrdersAsync(int page = 1, int pageSize = 50, CancellationToken ct = default) =>
 267        _client.GetAsync("api/client/v1/orders", Page(page, pageSize), ct);
 68
 69    public Task<JObject> GetOrderAsync(long codeOrder, CancellationToken ct = default) =>
 2670        _client.GetAsync($"api/client/v1/orders/{codeOrder}", ct: ct);
 71
 72    /// <summary>Кладёт предложение в корзину ZZap. Ответ — code_order: код позиции корзины, который после
 73    /// оформления становится номером заказа. С test=true запрос только проверяется.</summary>
 74    public Task<JObject> AddToCartAsync(string codeDocB, int quantity, string? comment, int? codeRegion = null,
 75        bool test = false, CancellationToken ct = default) =>
 976        _client.PostAsync("api/client/v1/cart", new JObject
 977        {
 978            ["code_doc_b"] = long.TryParse(codeDocB, out var docB) ? docB : codeDocB,
 979            ["qty_order"] = quantity,
 980            ["comment"] = comment ?? string.Empty,
 981            ["code_region"] = codeRegion ?? _config.CodeRegion
 982        }, TestFlag(test), ct);
 83
 84    /// <summary>Удаляет позиции корзины. По одной за запрос: при нескольких code_order в query ZZap
 85    /// отвечает всеми кодами, а фактически удаляет только одну позицию (проверено живым вызовом).</summary>
 86    public async Task<IReadOnlyList<long>> RemoveFromCartAsync(IReadOnlyCollection<long> codesOrder, bool test = false,
 87        CancellationToken ct = default)
 88    {
 889        var removed = new List<long>();
 3390        foreach (var code in codesOrder.Distinct())
 91        {
 992            var query = new Dictionary<string, string?> { ["code_order"] = code.ToString(CultureInfo.InvariantCulture) }
 1193            if (test) query["test"] = "true";
 994            await _client.DeleteAsync("api/client/v1/cart", query, ct);
 895            removed.Add(code);
 96        }
 797        return removed;
 798    }
 99
 100    /// <summary>Оформляет выбранные позиции корзины. ZZap требует контакт покупателя целиком:
 101    /// email, телефон, имя и фамилия — иначе 400 ещё в test-режиме.</summary>
 102    public Task<JObject> CreateOrderFromCartAsync(IReadOnlyCollection<long> codesOrder, ZZapBuyerContact buyer,
 103        int? codeRegion = null, string? comment = null, bool test = false, CancellationToken ct = default) =>
 6104        _client.PostAsync("api/client/v1/cart/order", new JObject
 6105        {
 6106            ["codes_order"] = string.Join(";", codesOrder),
 6107            ["code_region"] = codeRegion ?? _config.CodeRegion,
 6108            ["email"] = buyer.Email,
 6109            ["phone"] = buyer.Phone,
 6110            ["client_name"] = buyer.ClientName,
 6111            ["name_first"] = buyer.NameFirst,
 6112            ["name_last"] = buyer.NameLast,
 6113            ["name_second"] = buyer.NameSecond ?? string.Empty,
 6114            ["notify_sms"] = false,
 6115            ["comment"] = comment ?? string.Empty
 6116        }, TestFlag(test), ct);
 117
 118    /// <summary>Код валюты заказа: ZZap отдаёт цены только в рублях (class_cur «р.»), code_cur=1.</summary>
 119    public const int RubCurrencyCode = 1;
 120
 121    /// <summary>Прямой заказ предложения без корзины: POST /orders → code_order. В отличие от /cart/order
 122    /// требует code_cur и телефон по частям (код страны, код города, номер).</summary>
 123    public Task<JObject> CreateOrderDirectAsync(string codeDocB, int quantity, ZZapBuyerContact buyer, int? codeRegion =
 124        string? comment = null, bool test = false, CancellationToken ct = default) =>
 8125        _client.PostAsync("api/client/v1/orders", new JObject
 8126        {
 8127            ["code_doc_b"] = long.TryParse(codeDocB, out var docB) ? docB : codeDocB,
 8128            ["qty_order"] = quantity,
 8129            ["code_region"] = codeRegion ?? _config.CodeRegion,
 8130            ["code_cur"] = RubCurrencyCode,
 8131            ["email"] = buyer.Email,
 8132            ["phone_code_country"] = buyer.PhoneCodeCountry,
 8133            ["phone_code_city"] = buyer.PhoneCodeCity,
 8134            ["phone"] = buyer.PhoneLocal.Length > 0 ? buyer.PhoneLocal : buyer.Phone,
 8135            ["phone_add"] = buyer.PhoneAdd,
 8136            ["client_name"] = buyer.ClientName,
 8137            ["name_first"] = buyer.NameFirst,
 8138            ["name_last"] = buyer.NameLast,
 8139            ["name_second"] = buyer.NameSecond ?? string.Empty,
 8140            ["notify_sms"] = false,
 8141            ["comment"] = comment ?? string.Empty
 8142        }, TestFlag(test), ct);
 143
 144    /// <summary>Отмена заказа покупателем: code_track 10 из справочника /orders/status.</summary>
 145    public const int CancelledByBuyerTrackCode = 10;
 146
 147    /// <summary>Смена статуса заказа покупателем. response — комментарий, который увидит продавец.</summary>
 148    public Task<JObject> SetOrderStatusAsync(long codeOrder, int codeTrack, string? response, bool test = false,
 149        CancellationToken ct = default) =>
 4150        _client.PostAsync($"api/client/v1/orders/{codeOrder}/status", new JObject
 4151        {
 4152            ["code_track"] = codeTrack,
 4153            ["response"] = response ?? string.Empty
 4154        }, TestFlag(test), ct);
 155
 156    /// <summary>Контакт покупателя для оформления берётся из аккаунта ключа (GET /user).</summary>
 157    public async Task<ZZapBuyerContact> GetBuyerContactAsync(CancellationToken ct = default)
 158    {
 17159        var user = (await GetUserAsync(ct))["result"] as JObject
 17160                   ?? throw new ZZapApiException("ZZap не вернул данные аккаунта.", requestPath: "api/client/v1/user");
 153161        string Text(string name) => user.Value<string>(name)?.Trim() ?? string.Empty;
 15162        var phone = Text("phone_v2");
 16163        if (phone.Length == 0) phone = $"{Text("phone_code_country")}{Text("phone_code_city")}{Text("phone")}";
 15164        var contact = new ZZapBuyerContact(Text("email"), phone, Text("company_nick"), Text("name_first"), Text("name_la
 15165            Text("phone_code_country"), Text("phone_code_city"), Text("phone"), Text("phone_add"));
 15166        if (contact.Email.Length == 0 || contact.Phone.Length == 0 || contact.NameFirst.Length == 0 || contact.NameLast.
 1167            throw new ZZapApiException("В аккаунте ZZap не заполнены email, телефон, имя или фамилия — оформить заказ не
 14168        return contact;
 14169    }
 170
 171    private static Dictionary<string, string?>? TestFlag(bool test) =>
 27172        test ? new Dictionary<string, string?> { ["test"] = "true" } : null;
 173
 19174    private static Dictionary<string, string?> Page(int page, int pageSize) => new()
 19175    {
 19176        ["page"] = Number(page),
 19177        ["page_size"] = Number(pageSize)
 19178    };
 179
 42180    private static string Number(int value) => value.ToString(CultureInfo.InvariantCulture);
 181}

Methods/Properties

.ctor(Services.ApiServices.ZZapApi.ZZapHttpClient,Services.ApiServices.ZZapApi.ZZapConfig)
get_DefaultCodeRegion()
GetStatusAsync(System.Threading.CancellationToken)
GetUserAsync(System.Threading.CancellationToken)
GetOrderStatusesAsync(System.Threading.CancellationToken)
GetRegionsAsync(System.Int32,System.Int32,System.Threading.CancellationToken)
GetRegionAsync(System.Int32,System.Threading.CancellationToken)
SearchLightAsync(System.String,System.String,System.Int32,System.Nullable`1<System.Int32>,System.Int32,System.Int32,System.Threading.CancellationToken)
GetCartAsync(System.Int32,System.Int32,System.Threading.CancellationToken)
GetOrdersAsync(System.Int32,System.Int32,System.Threading.CancellationToken)
GetOrderAsync(System.Int64,System.Threading.CancellationToken)
AddToCartAsync(System.String,System.Int32,System.String,System.Nullable`1<System.Int32>,System.Boolean,System.Threading.CancellationToken)
RemoveFromCartAsync()
CreateOrderFromCartAsync(System.Collections.Generic.IReadOnlyCollection`1<System.Int64>,Services.ApiServices.ZZapApi.ZZapBuyerContact,System.Nullable`1<System.Int32>,System.String,System.Boolean,System.Threading.CancellationToken)
CreateOrderDirectAsync(System.String,System.Int32,Services.ApiServices.ZZapApi.ZZapBuyerContact,System.Nullable`1<System.Int32>,System.String,System.Boolean,System.Threading.CancellationToken)
SetOrderStatusAsync(System.Int64,System.Int32,System.String,System.Boolean,System.Threading.CancellationToken)
GetBuyerContactAsync()
Text()
TestFlag(System.Boolean)
Page(System.Int32,System.Int32)
Number(System.Int32)