< Summary

Information
Class: Services.ApiServices.ZZapApi.ZZapBuyerContact
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/ZZapApi/ZZapDataManager.cs
Line coverage
100%
Covered lines: 2
Uncovered lines: 0
Coverable lines: 2
Total lines: 181
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Email()100%11100%
get_PhoneCodeCountry()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>
 1609public sealed record ZZapBuyerContact(string Email, string Phone, string ClientName, string NameFirst, string NameLast, 
 6110    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
 24    public ZZapDataManager(ZZapHttpClient client, ZZapConfig config)
 25    {
 26        _client = client;
 27        _config = config;
 28    }
 29
 30    public int DefaultCodeRegion => _config.CodeRegion;
 31
 32    /// <summary>Самый дешёвый метод, требующий валидного ключа: {"online": true}.</summary>
 33    public Task<JObject> GetStatusAsync(CancellationToken ct = default) =>
 34        _client.GetAsync("api/client/v1/status", ct: ct);
 35
 36    public Task<JObject> GetUserAsync(CancellationToken ct = default) =>
 37        _client.GetAsync("api/client/v1/user", ct: ct);
 38
 39    /// <summary>Справочник статусов заказов: code_track → class_track.</summary>
 40    public Task<JObject> GetOrderStatusesAsync(CancellationToken ct = default) =>
 41        _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) =>
 44        _client.GetAsync("api/client/v1/regions", Page(page, pageSize), ct);
 45
 46    public Task<JObject> GetRegionAsync(int codeRegion, CancellationToken ct = default) =>
 47        _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) =>
 53        _client.GetAsync("api/client/v1/search/light", new Dictionary<string, string?>
 54        {
 55            ["partnumber"] = partNumber.Trim(),
 56            ["class_man"] = brand.Trim(),
 57            ["type_request"] = Number(typeRequest),
 58            ["code_region"] = Number(codeRegion ?? _config.CodeRegion),
 59            ["page"] = Number(page),
 60            ["page_size"] = Number(pageSize)
 61        }, ct);
 62
 63    public Task<JObject> GetCartAsync(int page = 1, int pageSize = 100, CancellationToken ct = default) =>
 64        _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) =>
 67        _client.GetAsync("api/client/v1/orders", Page(page, pageSize), ct);
 68
 69    public Task<JObject> GetOrderAsync(long codeOrder, CancellationToken ct = default) =>
 70        _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) =>
 76        _client.PostAsync("api/client/v1/cart", new JObject
 77        {
 78            ["code_doc_b"] = long.TryParse(codeDocB, out var docB) ? docB : codeDocB,
 79            ["qty_order"] = quantity,
 80            ["comment"] = comment ?? string.Empty,
 81            ["code_region"] = codeRegion ?? _config.CodeRegion
 82        }, 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    {
 89        var removed = new List<long>();
 90        foreach (var code in codesOrder.Distinct())
 91        {
 92            var query = new Dictionary<string, string?> { ["code_order"] = code.ToString(CultureInfo.InvariantCulture) }
 93            if (test) query["test"] = "true";
 94            await _client.DeleteAsync("api/client/v1/cart", query, ct);
 95            removed.Add(code);
 96        }
 97        return removed;
 98    }
 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) =>
 104        _client.PostAsync("api/client/v1/cart/order", new JObject
 105        {
 106            ["codes_order"] = string.Join(";", codesOrder),
 107            ["code_region"] = codeRegion ?? _config.CodeRegion,
 108            ["email"] = buyer.Email,
 109            ["phone"] = buyer.Phone,
 110            ["client_name"] = buyer.ClientName,
 111            ["name_first"] = buyer.NameFirst,
 112            ["name_last"] = buyer.NameLast,
 113            ["name_second"] = buyer.NameSecond ?? string.Empty,
 114            ["notify_sms"] = false,
 115            ["comment"] = comment ?? string.Empty
 116        }, 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) =>
 125        _client.PostAsync("api/client/v1/orders", new JObject
 126        {
 127            ["code_doc_b"] = long.TryParse(codeDocB, out var docB) ? docB : codeDocB,
 128            ["qty_order"] = quantity,
 129            ["code_region"] = codeRegion ?? _config.CodeRegion,
 130            ["code_cur"] = RubCurrencyCode,
 131            ["email"] = buyer.Email,
 132            ["phone_code_country"] = buyer.PhoneCodeCountry,
 133            ["phone_code_city"] = buyer.PhoneCodeCity,
 134            ["phone"] = buyer.PhoneLocal.Length > 0 ? buyer.PhoneLocal : buyer.Phone,
 135            ["phone_add"] = buyer.PhoneAdd,
 136            ["client_name"] = buyer.ClientName,
 137            ["name_first"] = buyer.NameFirst,
 138            ["name_last"] = buyer.NameLast,
 139            ["name_second"] = buyer.NameSecond ?? string.Empty,
 140            ["notify_sms"] = false,
 141            ["comment"] = comment ?? string.Empty
 142        }, 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) =>
 150        _client.PostAsync($"api/client/v1/orders/{codeOrder}/status", new JObject
 151        {
 152            ["code_track"] = codeTrack,
 153            ["response"] = response ?? string.Empty
 154        }, TestFlag(test), ct);
 155
 156    /// <summary>Контакт покупателя для оформления берётся из аккаунта ключа (GET /user).</summary>
 157    public async Task<ZZapBuyerContact> GetBuyerContactAsync(CancellationToken ct = default)
 158    {
 159        var user = (await GetUserAsync(ct))["result"] as JObject
 160                   ?? throw new ZZapApiException("ZZap не вернул данные аккаунта.", requestPath: "api/client/v1/user");
 161        string Text(string name) => user.Value<string>(name)?.Trim() ?? string.Empty;
 162        var phone = Text("phone_v2");
 163        if (phone.Length == 0) phone = $"{Text("phone_code_country")}{Text("phone_code_city")}{Text("phone")}";
 164        var contact = new ZZapBuyerContact(Text("email"), phone, Text("company_nick"), Text("name_first"), Text("name_la
 165            Text("phone_code_country"), Text("phone_code_city"), Text("phone"), Text("phone_add"));
 166        if (contact.Email.Length == 0 || contact.Phone.Length == 0 || contact.NameFirst.Length == 0 || contact.NameLast.
 167            throw new ZZapApiException("В аккаунте ZZap не заполнены email, телефон, имя или фамилия — оформить заказ не
 168        return contact;
 169    }
 170
 171    private static Dictionary<string, string?>? TestFlag(bool test) =>
 172        test ? new Dictionary<string, string?> { ["test"] = "true" } : null;
 173
 174    private static Dictionary<string, string?> Page(int page, int pageSize) => new()
 175    {
 176        ["page"] = Number(page),
 177        ["page_size"] = Number(pageSize)
 178    };
 179
 180    private static string Number(int value) => value.ToString(CultureInfo.InvariantCulture);
 181}

Methods/Properties

get_Email()
get_PhoneCodeCountry()