| | | 1 | | using Microsoft.Extensions.Logging; |
| | | 2 | | using Services.ApiServices.ArmtekApi.Models.Envelope; |
| | | 3 | | using Services.ApiServices.ArmtekApi.Models.Response; |
| | | 4 | | |
| | | 5 | | namespace Services.ApiServices.ArmtekApi; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// Бизнес-уровень интеграции с Armtek Web-Services — read-методы, createOrder и editOrder (см. docs/api/armtek.md). |
| | | 9 | | /// Никогда не бросает исключение из-за бизнес-MESSAGES внутри валидного конверта (например, |
| | | 10 | | /// "заказ типа Возврат не обрабатывается этим методом" на getOrder) — всегда возвращает |
| | | 11 | | /// ArmtekResponse<T> целиком, вызывающий код сам решает, что делать с HasErrors/частичным Resp. |
| | | 12 | | /// ArmtekApiException прокидывается наружу только из ArmtekHttpClient (транспортные сбои). |
| | | 13 | | /// </summary> |
| | | 14 | | public class ArmtekDataManager |
| | | 15 | | { |
| | | 16 | | private readonly ArmtekHttpClient _client; |
| | | 17 | | private readonly ArmtekConfig _config; |
| | | 18 | | private readonly ILogger<ArmtekDataManager> _logger; |
| | | 19 | | private readonly ArmtekBrandResolver? _brandResolver; |
| | | 20 | | |
| | 137 | 21 | | public ArmtekDataManager(ArmtekHttpClient client, ArmtekConfig config, ILogger<ArmtekDataManager> logger, |
| | 137 | 22 | | ArmtekBrandResolver? brandResolver = null) |
| | | 23 | | { |
| | 137 | 24 | | _client = client; |
| | 137 | 25 | | _config = config; |
| | 137 | 26 | | _logger = logger; |
| | 137 | 27 | | _brandResolver = brandResolver; |
| | 137 | 28 | | } |
| | | 29 | | |
| | 106 | 30 | | private string ResolveVkorg(string? vkorg) => string.IsNullOrWhiteSpace(vkorg) ? _config.DefaultVkorg : vkorg; |
| | | 31 | | |
| | | 32 | | // ---- ws_ping ---- |
| | | 33 | | |
| | | 34 | | /// <summary>Проверка доступности сервиса. GET ws_ping/index.</summary> |
| | | 35 | | public Task<ArmtekResponse<ArmtekPingResponse>> PingAsync(CancellationToken ct = default) |
| | 0 | 36 | | => _client.GetAsync<ArmtekPingResponse>("ws_ping/index", ct: ct); |
| | | 37 | | |
| | | 38 | | // ---- ws_user ---- |
| | | 39 | | |
| | | 40 | | /// <summary>Список сбытовых организаций клиента. GET ws_user/getUserVkorgList.</summary> |
| | | 41 | | public Task<ArmtekResponse<List<ArmtekVkorgItem>>> GetUserVkorgListAsync(CancellationToken ct = default) |
| | 1 | 42 | | => _client.GetAsync<List<ArmtekVkorgItem>>("ws_user/getUserVkorgList", ct: ct); |
| | | 43 | | |
| | | 44 | | /// <summary>Структура клиента (покупатели/грузополучатели/адреса/договоры/контакты). POST ws_user/getUserInfo.</sum |
| | | 45 | | public Task<ArmtekResponse<ArmtekUserInfoResponse>> GetUserInfoAsync(string? vkorg = null, bool structure = true, Ca |
| | | 46 | | { |
| | 14 | 47 | | var form = new Dictionary<string, string> |
| | 14 | 48 | | { |
| | 14 | 49 | | ["VKORG"] = ResolveVkorg(vkorg), |
| | 14 | 50 | | ["STRUCTURE"] = structure ? "1" : "0" |
| | 14 | 51 | | }; |
| | 14 | 52 | | return _client.PostFormAsync<ArmtekUserInfoResponse>("ws_user/getUserInfo", form, ct); |
| | | 53 | | } |
| | | 54 | | |
| | | 55 | | /// <summary>Справочник брендов. GET ws_user/getBrandList.</summary> |
| | | 56 | | public Task<ArmtekResponse<List<string>>> GetBrandListAsync(CancellationToken ct = default) |
| | 1 | 57 | | => _client.GetAsync<List<string>>("ws_user/getBrandList", ct: ct); |
| | | 58 | | |
| | | 59 | | /// <summary>Список доступных складов. POST ws_user/getStoreList.</summary> |
| | | 60 | | public Task<ArmtekResponse<List<ArmtekStoreItem>>> GetStoreListAsync(string? vkorg = null, CancellationToken ct = de |
| | | 61 | | { |
| | 4 | 62 | | var form = new Dictionary<string, string> { ["VKORG"] = ResolveVkorg(vkorg) }; |
| | 4 | 63 | | return _client.PostFormAsync<List<ArmtekStoreItem>>("ws_user/getStoreList", form, ct); |
| | | 64 | | } |
| | | 65 | | |
| | | 66 | | // ---- ws_search ---- |
| | | 67 | | |
| | | 68 | | /// <summary>Проценка товара (цена и наличие) — включая аналоги при queryType=2. POST ws_search/search.</summary> |
| | | 69 | | public Task<ArmtekResponse<List<ArmtekSearchItem>>> SearchAsync( |
| | | 70 | | string kunnrRg, |
| | | 71 | | string pin, |
| | | 72 | | string? brand = null, |
| | | 73 | | string? vkorg = null, |
| | | 74 | | int? queryType = null, |
| | | 75 | | string? program = null, |
| | | 76 | | string? kunnrZa = null, |
| | | 77 | | bool? incotermsSelfPickup = null, |
| | | 78 | | string? vbeln = null, |
| | | 79 | | CancellationToken ct = default) |
| | | 80 | | { |
| | 5 | 81 | | return SearchCoreAsync(kunnrRg, pin, brand, vkorg, queryType, program, kunnrZa, incotermsSelfPickup, vbeln, ct); |
| | | 82 | | } |
| | | 83 | | |
| | | 84 | | private async Task<ArmtekResponse<List<ArmtekSearchItem>>> SearchCoreAsync( |
| | | 85 | | string kunnrRg, string pin, string? brand, string? vkorg, int? queryType, string? program, |
| | | 86 | | string? kunnrZa, bool? incotermsSelfPickup, string? vbeln, CancellationToken ct) |
| | | 87 | | { |
| | 5 | 88 | | var resolvedBrand = await ResolveBrandAsync(brand, ct); |
| | 5 | 89 | | var form = new Dictionary<string, string> |
| | 5 | 90 | | { |
| | 5 | 91 | | ["VKORG"] = ResolveVkorg(vkorg), |
| | 5 | 92 | | ["KUNNR_RG"] = kunnrRg, |
| | 5 | 93 | | ["PIN"] = pin |
| | 5 | 94 | | }; |
| | 8 | 95 | | if (!string.IsNullOrWhiteSpace(resolvedBrand)) form["BRAND"] = resolvedBrand; |
| | 6 | 96 | | if (queryType is not null) form["QUERY_TYPE"] = queryType.Value.ToString(); |
| | 5 | 97 | | if (!string.IsNullOrWhiteSpace(program)) form["PROGRAM"] = program; |
| | 6 | 98 | | if (!string.IsNullOrWhiteSpace(kunnrZa)) form["KUNNR_ZA"] = kunnrZa; |
| | 7 | 99 | | if (incotermsSelfPickup is not null) form["INCOTERMS"] = incotermsSelfPickup.Value ? "1" : "0"; |
| | 5 | 100 | | if (!string.IsNullOrWhiteSpace(vbeln)) form["VBELN"] = vbeln; |
| | | 101 | | |
| | 5 | 102 | | return await _client.PostFormAsync<List<ArmtekSearchItem>>("ws_search/search", form, ct); |
| | 5 | 103 | | } |
| | | 104 | | |
| | | 105 | | /// <summary>Возвращает точное написание бренда из справочника Armtek для входного бренда Studio2.</summary> |
| | | 106 | | public async Task<string?> ResolveBrandAsync(string? brand, CancellationToken ct = default) |
| | | 107 | | { |
| | 6 | 108 | | if (string.IsNullOrWhiteSpace(brand) || _brandResolver is null) |
| | 5 | 109 | | return brand; |
| | | 110 | | |
| | | 111 | | try |
| | | 112 | | { |
| | 1 | 113 | | return await _brandResolver.ResolveAsync(brand, ct); |
| | | 114 | | } |
| | 1 | 115 | | catch (Exception ex) |
| | | 116 | | { |
| | 1 | 117 | | _logger.LogInformation("Armtek: не удалось сопоставить бренд {Brand} со справочником API ({Message}); исполь |
| | 1 | 118 | | return brand; |
| | | 119 | | } |
| | 6 | 120 | | } |
| | | 121 | | |
| | | 122 | | /// <summary>Безопасная проверка заказа: Армтек рассчитывает распределение, но не создаёт заказ.</summary> |
| | | 123 | | public Task<ArmtekResponse<ArmtekCreateOrderResponse>> CreateTestOrderAsync( |
| | | 124 | | string kunrg, IReadOnlyCollection<ArmtekOrderRequestItem> items, string? vkorg = null, |
| | | 125 | | string? kunza = null, bool? incotermsSelfPickup = null, CancellationToken ct = default, string? orderComment = n |
| | 17 | 126 | | => CreateOrderCoreAsync("ws_order/createTestOrder", kunrg, items, vkorg, kunza, incotermsSelfPickup, ct, orderCo |
| | | 127 | | |
| | | 128 | | /// <summary> |
| | | 129 | | /// Создаёт реальный заказ Армтек. Вызывать только после явного подтверждения пользователя. |
| | | 130 | | /// <paramref name="orderComment"/> уходит в <c>TEXT_ORD</c> (до 100 символов) — метка Studio2 для сверки заказа |
| | | 131 | | /// перед повтором; комментарий позиции — <see cref="ArmtekOrderRequestItem.Comment"/> (<c>COMMENT</c>). |
| | | 132 | | /// </summary> |
| | | 133 | | public Task<ArmtekResponse<ArmtekCreateOrderResponse>> CreateOrderAsync( |
| | | 134 | | string kunrg, IReadOnlyCollection<ArmtekOrderRequestItem> items, string? vkorg = null, |
| | | 135 | | string? kunza = null, bool? incotermsSelfPickup = null, CancellationToken ct = default, string? orderComment = n |
| | 16 | 136 | | => CreateOrderCoreAsync("ws_order/createOrder", kunrg, items, vkorg, kunza, incotermsSelfPickup, ct, orderCommen |
| | | 137 | | |
| | | 138 | | private Task<ArmtekResponse<ArmtekCreateOrderResponse>> CreateOrderCoreAsync( |
| | | 139 | | string endpoint, string kunrg, IReadOnlyCollection<ArmtekOrderRequestItem> items, string? vkorg, |
| | | 140 | | string? kunza, bool? incotermsSelfPickup, CancellationToken ct, string? orderComment = null) |
| | | 141 | | { |
| | 33 | 142 | | var form = new Dictionary<string, string> |
| | 33 | 143 | | { |
| | 33 | 144 | | ["VKORG"] = ResolveVkorg(vkorg), |
| | 33 | 145 | | ["KUNRG"] = kunrg, |
| | 33 | 146 | | // KEYZAK проценки может относиться к партнёрскому складу. Без DBTYP=3 Armtek проверяет |
| | 33 | 147 | | // только основной склад и возвращает «ничего не найдено» для реально доступной позиции. |
| | 33 | 148 | | ["DBTYP"] = "3" |
| | 33 | 149 | | }; |
| | | 150 | | // KUNZA/INCOTERMS должны совпадать с параметрами проценки: иначе Армтек считает цену и склад по другому адресу. |
| | 64 | 151 | | if (!string.IsNullOrWhiteSpace(kunza)) form["KUNZA"] = kunza; |
| | 64 | 152 | | if (incotermsSelfPickup is not null) form["INCOTERMS"] = incotermsSelfPickup.Value ? "1" : "0"; |
| | | 153 | | // TEXT_ORD — комментарий к заказу, до 100 символов (docs/api/armtek.md). |
| | 47 | 154 | | if (!string.IsNullOrWhiteSpace(orderComment)) form["TEXT_ORD"] = orderComment.Length <= 100 ? orderComment : ord |
| | 33 | 155 | | var index = 0; |
| | 132 | 156 | | foreach (var item in items) |
| | | 157 | | { |
| | 33 | 158 | | form[$"ITEMS[{index}][PIN]"] = item.Pin; |
| | 33 | 159 | | form[$"ITEMS[{index}][BRAND]"] = item.Brand; |
| | | 160 | | // Armtek принимает "2", но отвергает decimal-формат с незначащими нулями "2.000". |
| | 33 | 161 | | form[$"ITEMS[{index}][KWMENG]"] = item.Quantity.ToString("0.####", System.Globalization.CultureInfo.Invarian |
| | 64 | 162 | | if (!string.IsNullOrWhiteSpace(item.Keyzak)) form[$"ITEMS[{index}][KEYZAK]"] = item.Keyzak; |
| | | 163 | | // Armtek отклоняет "489.0000" как некорректный параметр, хотя "489" и "18.06" допустимы. |
| | 64 | 164 | | if (item.PriceMax is decimal priceMax) form[$"ITEMS[{index}][PRICEMAX]"] = priceMax.ToString("0.####", Syste |
| | 35 | 165 | | if (!string.IsNullOrWhiteSpace(item.DateMax)) form[$"ITEMS[{index}][DATEMAX]"] = item.DateMax; |
| | 62 | 166 | | if (!string.IsNullOrWhiteSpace(item.Comment)) form[$"ITEMS[{index}][COMMENT]"] = item.Comment.Length <= 100 |
| | 33 | 167 | | index++; |
| | | 168 | | } |
| | 33 | 169 | | return _client.PostFormAsync<ArmtekCreateOrderResponse>(endpoint, form, ct); |
| | | 170 | | } |
| | | 171 | | |
| | | 172 | | /// <summary>Поиск по ассортименту без цен/остатков/привязки к покупателю. POST ws_search/assortment_search.</summar |
| | | 173 | | public Task<ArmtekResponse<List<ArmtekAssortmentItem>>> AssortmentSearchAsync( |
| | | 174 | | string pin, string? vkorg = null, string? program = null, CancellationToken ct = default) |
| | | 175 | | { |
| | 2 | 176 | | var form = new Dictionary<string, string> |
| | 2 | 177 | | { |
| | 2 | 178 | | ["VKORG"] = ResolveVkorg(vkorg), |
| | 2 | 179 | | ["PIN"] = pin |
| | 2 | 180 | | }; |
| | 2 | 181 | | if (!string.IsNullOrWhiteSpace(program)) form["PROGRAM"] = program; |
| | | 182 | | |
| | 2 | 183 | | return _client.PostFormAsync<List<ArmtekAssortmentItem>>("ws_search/assortment_search", form, ct); |
| | | 184 | | } |
| | | 185 | | |
| | | 186 | | // ---- ws_order ---- |
| | | 187 | | |
| | | 188 | | /// <summary> |
| | | 189 | | /// Данные по заказу. GET ws_order/getOrder. Обрабатывает только "прямые" заказы |
| | | 190 | | /// (отгрузка/доставка со складов АРМТЕК/ЦЗ) — для заказов типа "Возврат" сервис |
| | | 191 | | /// отвечает MESSAGES[E] и RESP:null (см. ArmtekResponse<T>.HasErrors), не исключением; |
| | | 192 | | /// в этом случае вызывающий код должен использовать GetRefundAsync. |
| | | 193 | | /// </summary> |
| | | 194 | | public Task<ArmtekResponse<ArmtekGetOrderResponse>> GetOrderAsync( |
| | | 195 | | string kunrg, string order, string? vkorg = null, bool? edit = null, CancellationToken ct = default) |
| | | 196 | | { |
| | 3 | 197 | | var query = new Dictionary<string, string> |
| | 3 | 198 | | { |
| | 3 | 199 | | ["VKORG"] = ResolveVkorg(vkorg), |
| | 3 | 200 | | ["KUNRG"] = kunrg, |
| | 3 | 201 | | ["ORDER"] = order |
| | 3 | 202 | | }; |
| | 3 | 203 | | if (edit is not null) query["EDIT"] = edit.Value ? "1" : "0"; |
| | | 204 | | |
| | 3 | 205 | | return _client.GetAsync<ArmtekGetOrderResponse>("ws_order/getOrder", query, ct); |
| | | 206 | | } |
| | | 207 | | |
| | | 208 | | /// <summary> |
| | | 209 | | /// Меняет количество или отклоняет позиции заказа. POST ws_order/editOrder; <paramref name="abgru"/> — код причины |
| | | 210 | | /// из <c>getOrder.ABGRU_ITEMS</c> (запрашивать с <c>EDIT=1</c>). Ответ — пустой RESP, результат в MESSAGES. |
| | | 211 | | /// Меняет реальный заказ у Армтек: dry-run у метода нет. |
| | | 212 | | /// </summary> |
| | | 213 | | public Task<ArmtekResponse<Newtonsoft.Json.Linq.JToken>> EditOrderAsync( |
| | | 214 | | string kunrg, string order, IReadOnlyCollection<(string Posnr, decimal Quantity)> positions, string abgru, |
| | | 215 | | string? vkorg = null, string? note = null, CancellationToken ct = default) |
| | | 216 | | { |
| | 0 | 217 | | var form = new Dictionary<string, string> |
| | 0 | 218 | | { |
| | 0 | 219 | | ["VKORG"] = ResolveVkorg(vkorg), |
| | 0 | 220 | | ["KUNRG"] = kunrg, |
| | 0 | 221 | | ["ORDER"] = order |
| | 0 | 222 | | }; |
| | 0 | 223 | | var index = 0; |
| | 0 | 224 | | foreach (var (posnr, quantity) in positions) |
| | | 225 | | { |
| | 0 | 226 | | form[$"POSITION_INPUT[{index}][POSNR]"] = posnr; |
| | 0 | 227 | | form[$"POSITION_INPUT[{index}][KWMENG]"] = quantity.ToString("0.####", System.Globalization.CultureInfo.Inva |
| | 0 | 228 | | form[$"POSITION_INPUT[{index}][ABGRU]"] = abgru; |
| | 0 | 229 | | if (!string.IsNullOrWhiteSpace(note)) form[$"POSITION_INPUT[{index}][NOTE]"] = note.Length <= 100 ? note : n |
| | 0 | 230 | | index++; |
| | | 231 | | } |
| | 0 | 232 | | return _client.PostFormAsync<Newtonsoft.Json.Linq.JToken>("ws_order/editOrder", form, ct); |
| | | 233 | | } |
| | | 234 | | |
| | | 235 | | /// <summary> |
| | | 236 | | /// Данные по заказу v2, с детальной расшифровкой статусов при detailedStatus=true. |
| | | 237 | | /// GET ws_order/getOrder2. При таймауте API (STATUS-запрос требует больше расчётов на |
| | | 238 | | /// стороне Армтек) MESSAGES содержит ошибку, но HEADER/ITEMS в Resp приходят частично — |
| | | 239 | | /// не бросает исключение, вызывающий код читает HasErrors и частичный Resp по отдельности. |
| | | 240 | | /// </summary> |
| | | 241 | | public Task<ArmtekResponse<ArmtekGetOrder2Response>> GetOrder2Async( |
| | | 242 | | string kunrg, string order, string? vkorg = null, bool detailedStatus = true, CancellationToken ct = default) |
| | | 243 | | { |
| | 6 | 244 | | var query = new Dictionary<string, string> |
| | 6 | 245 | | { |
| | 6 | 246 | | ["VKORG"] = ResolveVkorg(vkorg), |
| | 6 | 247 | | ["KUNRG"] = kunrg, |
| | 6 | 248 | | ["ORDER"] = order, |
| | 6 | 249 | | ["STATUS"] = detailedStatus ? "1" : "0" |
| | 6 | 250 | | }; |
| | | 251 | | |
| | 6 | 252 | | return _client.GetAsync<ArmtekGetOrder2Response>("ws_order/getOrder2", query, ct); |
| | | 253 | | } |
| | | 254 | | |
| | | 255 | | /// <summary>Данные по возврату (заказ с ORDER_TYPE="Возврат"). GET ws_order/getRefund.</summary> |
| | | 256 | | public Task<ArmtekResponse<ArmtekGetRefundResponse>> GetRefundAsync( |
| | | 257 | | string kunrg, string returnNumber, string? vkorg = null, CancellationToken ct = default) |
| | | 258 | | { |
| | 2 | 259 | | var query = new Dictionary<string, string> |
| | 2 | 260 | | { |
| | 2 | 261 | | ["VKORG"] = ResolveVkorg(vkorg), |
| | 2 | 262 | | ["KUNRG"] = kunrg, |
| | 2 | 263 | | ["RETURN"] = returnNumber |
| | 2 | 264 | | }; |
| | | 265 | | |
| | 2 | 266 | | return _client.GetAsync<ArmtekGetRefundResponse>("ws_order/getRefund", query, ct); |
| | | 267 | | } |
| | | 268 | | |
| | | 269 | | // ---- ws_invoice ---- |
| | | 270 | | |
| | | 271 | | /// <summary> |
| | | 272 | | /// Данные по фактуре. GET ws_invoice/getInvoice. Один из invoice/zzsf обязателен — |
| | | 273 | | /// в отличие от MESSAGES[E] от самого API, это программерская ошибка вызова, а не |
| | | 274 | | /// бизнес-сценарий, поэтому кидается ArgumentException до похода в сеть. |
| | | 275 | | /// </summary> |
| | | 276 | | public Task<ArmtekResponse<ArmtekGetInvoiceResponse>> GetInvoiceAsync( |
| | | 277 | | string kunrg, |
| | | 278 | | string? invoice = null, |
| | | 279 | | string? zzsf = null, |
| | | 280 | | string? vkorg = null, |
| | | 281 | | bool refundFlag = false, |
| | | 282 | | bool? noCollapse = null, |
| | | 283 | | CancellationToken ct = default) |
| | | 284 | | { |
| | 19 | 285 | | if (string.IsNullOrWhiteSpace(invoice) && string.IsNullOrWhiteSpace(zzsf)) |
| | 1 | 286 | | throw new ArgumentException("Нужно указать либо invoice, либо zzsf.", nameof(invoice)); |
| | | 287 | | |
| | 18 | 288 | | var query = new Dictionary<string, string> |
| | 18 | 289 | | { |
| | 18 | 290 | | ["VKORG"] = ResolveVkorg(vkorg), |
| | 18 | 291 | | ["KUNRG"] = kunrg |
| | 18 | 292 | | }; |
| | 35 | 293 | | if (!string.IsNullOrWhiteSpace(invoice)) query["INVOICE"] = invoice; |
| | 19 | 294 | | if (!string.IsNullOrWhiteSpace(zzsf)) query["ZZSF"] = zzsf; |
| | 20 | 295 | | if (refundFlag) query["REFLAG"] = "1"; |
| | 18 | 296 | | if (noCollapse is not null) query["NOCOLLAPS"] = noCollapse.Value ? "1" : "0"; |
| | | 297 | | |
| | 18 | 298 | | return _client.GetAsync<ArmtekGetInvoiceResponse>("ws_invoice/getInvoice", query, ct); |
| | | 299 | | } |
| | | 300 | | |
| | | 301 | | // ---- ws_reports ---- |
| | | 302 | | |
| | | 303 | | /// <summary> |
| | | 304 | | /// Отчёт по заказам за период. POST ws_reports/getOrderReportByDate. Если заказов за период |
| | | 305 | | /// нет, RESP приходит null (в MESSAGES информационное "Список пуст") — не бросает исключение. |
| | | 306 | | /// </summary> |
| | | 307 | | public Task<ArmtekResponse<ArmtekGetOrderReportByDateResponse>> GetOrderReportByDateAsync( |
| | | 308 | | string kunnrRg, |
| | | 309 | | string? vkorg = null, |
| | | 310 | | DateTime? createdFrom = null, |
| | | 311 | | DateTime? createdTo = null, |
| | | 312 | | DateTime? deliveryFrom = null, |
| | | 313 | | DateTime? deliveryTo = null, |
| | | 314 | | bool? includeSales = null, |
| | | 315 | | bool? includeReturns = null, |
| | | 316 | | bool? currentLoginOnly = null, |
| | | 317 | | CancellationToken ct = default) |
| | | 318 | | { |
| | 15 | 319 | | var form = BuildReportForm(kunnrRg, vkorg, createdFrom, createdTo, deliveryFrom, deliveryTo, includeSales, inclu |
| | 15 | 320 | | return _client.PostFormAsync<ArmtekGetOrderReportByDateResponse>("ws_reports/getOrderReportByDate", form, ct); |
| | | 321 | | } |
| | | 322 | | |
| | | 323 | | /// <summary>Отчёт по заказам в разрезе позиций за период. POST ws_reports/getOrderPositionsReportByDate.</summary> |
| | | 324 | | public Task<ArmtekResponse<ArmtekGetOrderPositionsReportByDateResponse>> GetOrderPositionsReportByDateAsync( |
| | | 325 | | string kunnrRg, |
| | | 326 | | string? vkorg = null, |
| | | 327 | | DateTime? createdFrom = null, |
| | | 328 | | DateTime? createdTo = null, |
| | | 329 | | DateTime? deliveryFrom = null, |
| | | 330 | | DateTime? deliveryTo = null, |
| | | 331 | | bool? includeSales = null, |
| | | 332 | | bool? includeReturns = null, |
| | | 333 | | bool? currentLoginOnly = null, |
| | | 334 | | CancellationToken ct = default) |
| | | 335 | | { |
| | 1 | 336 | | var form = BuildReportForm(kunnrRg, vkorg, createdFrom, createdTo, deliveryFrom, deliveryTo, includeSales, inclu |
| | 1 | 337 | | return _client.PostFormAsync<ArmtekGetOrderPositionsReportByDateResponse>("ws_reports/getOrderPositionsReportByD |
| | | 338 | | } |
| | | 339 | | |
| | | 340 | | /// <summary>Отчёт по заказам в разрезе позиций (v2, с детализацией процессинга). POST ws_reports/getOrderPositionsR |
| | | 341 | | public Task<ArmtekResponse<ArmtekGetOrderPositionsReportByDate2Response>> GetOrderPositionsReportByDate2Async( |
| | | 342 | | string kunnrRg, |
| | | 343 | | string? vkorg = null, |
| | | 344 | | DateTime? createdFrom = null, |
| | | 345 | | DateTime? createdTo = null, |
| | | 346 | | DateTime? deliveryFrom = null, |
| | | 347 | | DateTime? deliveryTo = null, |
| | | 348 | | bool? includeSales = null, |
| | | 349 | | bool? includeReturns = null, |
| | | 350 | | bool? currentLoginOnly = null, |
| | | 351 | | bool? rejectedOnly = null, |
| | | 352 | | CancellationToken ct = default) |
| | | 353 | | { |
| | 3 | 354 | | var form = BuildReportForm(kunnrRg, vkorg, createdFrom, createdTo, deliveryFrom, deliveryTo, includeSales, inclu |
| | 4 | 355 | | if (rejectedOnly is not null) form["Rejected"] = rejectedOnly.Value ? "1" : "0"; |
| | | 356 | | |
| | 3 | 357 | | return _client.PostFormAsync<ArmtekGetOrderPositionsReportByDate2Response>("ws_reports/getOrderPositionsReportBy |
| | | 358 | | } |
| | | 359 | | |
| | | 360 | | private Dictionary<string, string> BuildReportForm( |
| | | 361 | | string kunnrRg, |
| | | 362 | | string? vkorg, |
| | | 363 | | DateTime? createdFrom, |
| | | 364 | | DateTime? createdTo, |
| | | 365 | | DateTime? deliveryFrom, |
| | | 366 | | DateTime? deliveryTo, |
| | | 367 | | bool? includeSales, |
| | | 368 | | bool? includeReturns, |
| | | 369 | | bool? currentLoginOnly) |
| | | 370 | | { |
| | 19 | 371 | | var form = new Dictionary<string, string> |
| | 19 | 372 | | { |
| | 19 | 373 | | ["VKORG"] = ResolveVkorg(vkorg), |
| | 19 | 374 | | ["KUNNR_RG"] = kunnrRg |
| | 19 | 375 | | }; |
| | 32 | 376 | | if (createdFrom is not null) form["SCRDATE"] = FormatDate(createdFrom.Value); |
| | 32 | 377 | | if (createdTo is not null) form["ECRDATE"] = FormatDate(createdTo.Value); |
| | 19 | 378 | | if (deliveryFrom is not null) form["SDLDATE"] = FormatDate(deliveryFrom.Value); |
| | 19 | 379 | | if (deliveryTo is not null) form["EDLDATE"] = FormatDate(deliveryTo.Value); |
| | 23 | 380 | | if (includeSales is not null) form["TYPEZK_SALE"] = includeSales.Value ? "1" : "0"; |
| | 19 | 381 | | if (includeReturns is not null) form["TYPEZK_RETN"] = includeReturns.Value ? "1" : "0"; |
| | 23 | 382 | | if (currentLoginOnly is not null) form["KURR_LOGIN"] = currentLoginOnly.Value ? "1" : "0"; |
| | 19 | 383 | | return form; |
| | | 384 | | } |
| | | 385 | | |
| | 26 | 386 | | private static string FormatDate(DateTime date) => date.ToString("yyyyMMdd"); |
| | | 387 | | } |