< Summary

Information
Class: Services.SupplierStatusServices.Providers.MotexSupplierStatusProvider
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/SupplierStatusServices/Providers/MotexSupplierStatusProvider.cs
Line coverage
80%
Covered lines: 100
Uncovered lines: 24
Coverable lines: 124
Total lines: 206
Line coverage: 80.6%
Branch coverage
50%
Covered branches: 58
Total branches: 116
Branch coverage: 50%
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_ProviderKey()100%11100%
FetchCartAsync(...)100%11100%
FetchOrderAsync()49.07%26810876.08%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/SupplierStatusServices/Providers/MotexSupplierStatusProvider.cs

#LineLine coverage
 1using Microsoft.EntityFrameworkCore;
 2using Microsoft.Extensions.Logging;
 3using Newtonsoft.Json.Linq;
 4using OzonDomains.Models;
 5using OzonDomains.Models.SupplierStatuses;
 6using OzonRepositories.Context;
 7using Services.ApiServices.MotexApi;
 8
 9namespace Services.SupplierStatusServices.Providers;
 10
 11/// <summary>
 12/// Статусы Motex (МоТехС). Корзина локальная — читается из MotexCartItems (<see cref="LocalCartStatusReader"/>).
 13/// Статусы оформленных заказов опрашиваются через Motex API (POST /api/v1/Orders/GetOrdersByIds
 14/// или GET /api/v1/Orders/GetOrderDetails).
 15/// </summary>
 16public sealed class MotexSupplierStatusProvider : ISupplierStatusProvider
 17{
 18    private readonly ApplicationDbContext _db;
 19    private readonly MotexDataManager _motex;
 20    private readonly LocalCartStatusReader _localCart;
 21    private readonly ILogger<MotexSupplierStatusProvider> _logger;
 22
 323    public MotexSupplierStatusProvider(
 324        ApplicationDbContext db,
 325        MotexDataManager motex,
 326        LocalCartStatusReader localCart,
 327        ILogger<MotexSupplierStatusProvider> logger)
 28    {
 329        _db = db;
 330        _motex = motex;
 331        _localCart = localCart;
 332        _logger = logger;
 333    }
 34
 135    public string ProviderKey => ApiSupplierSettings.MotexProviderKey;
 36
 37    public Task<IReadOnlyList<SupplierStatusSnapshot>> FetchCartAsync(
 38        IReadOnlyList<OrderSupplierStateRef> states, CancellationToken ct) =>
 139        _localCart.ReadAsync(ProviderKey, states, ct);
 40
 41    public async Task<IReadOnlyList<SupplierStatusSnapshot>> FetchOrderAsync(
 42        IReadOnlyList<OrderSupplierStateRef> states, CancellationToken ct)
 43    {
 244        if (states.Count == 0) return [];
 45
 446        var orderIds = states.Select(x => x.OrderId).Distinct().ToList();
 247        var localItems = await _db.MotexOrderItems
 248            .Include(x => x.MotexOrder)
 249            .Where(x => orderIds.Contains(x.StudioOrderId))
 250            .ToListAsync(ct);
 51
 252        var latestItems = localItems
 153            .GroupBy(x => x.StudioOrderId)
 554            .ToDictionary(g => g.Key, g => g.OrderByDescending(x => x.Id).First());
 55
 256        var externalIds = states
 257            .Select(x => x.ExternalOrderId)
 158            .Concat(latestItems.Values.Select(x => x.MotexOrder?.ExternalOrderId))
 359            .Where(id => !string.IsNullOrWhiteSpace(id))
 360            .Select(id => id!)
 261            .Distinct(StringComparer.OrdinalIgnoreCase)
 262            .ToList();
 63
 264        var byId = new Dictionary<string, JObject>(StringComparer.OrdinalIgnoreCase);
 265        var byClientNum = new Dictionary<string, JObject>(StringComparer.OrdinalIgnoreCase);
 66
 267        if (externalIds.Count > 0)
 68        {
 69            try
 70            {
 271                var response = await _motex.GetOrdersByIdsAsync(externalIds, ct);
 172                var ordersArray = (response["orders"] as JArray ?? response as JArray ?? new JArray()).OfType<JObject>()
 473                foreach (var order in ordersArray)
 74                {
 175                    var id = order.Value<string>("id");
 276                    if (!string.IsNullOrWhiteSpace(id)) byId[id] = order;
 177                    var cNum = order.Value<string>("clientOrderNum");
 278                    if (!string.IsNullOrWhiteSpace(cNum)) byClientNum[cNum] = order;
 79                }
 180            }
 181            catch (Exception ex) when (ex is MotexApiException or HttpRequestException or TaskCanceledException)
 82            {
 183                _logger.LogInformation("МоТехС: не удалось получить статусы заказов: {Error}", ex.Message);
 284                return states.Select(x => new SupplierStatusSnapshot
 285                {
 286                    OrderId = x.OrderId,
 287                    Kind = SupplierStatusKind.Order,
 288                    StatusName = string.Empty,
 289                    Error = $"МоТехС: {ex.Message}"
 290                }).ToList();
 91            }
 92        }
 93
 94        // Если у каких-то заказов известен только clientOrderNum (без externalOrderId), пробуем GetOrderDetails
 195        var missingClientNums = latestItems.Values
 196            .Select(x => x.MotexOrder)
 197            .Where(o => o is not null && string.IsNullOrWhiteSpace(o.ExternalOrderId) && !string.IsNullOrWhiteSpace(o.Cl
 098            .Select(o => o!.ClientOrderNum)
 199            .Distinct(StringComparer.OrdinalIgnoreCase)
 0100            .Where(num => !byClientNum.ContainsKey(num))
 1101            .Take(10)
 1102            .ToList();
 103
 2104        foreach (var clientNum in missingClientNums)
 105        {
 106            try
 107            {
 0108                var details = await _motex.GetOrderDetailsAsync(null, clientNum, ct);
 0109                var orderObj = details["order"] as JObject ?? details as JObject;
 0110                if (orderObj is not null)
 111                {
 0112                    var id = orderObj.Value<string>("id");
 0113                    if (!string.IsNullOrWhiteSpace(id)) byId[id] = orderObj;
 0114                    byClientNum[clientNum] = orderObj;
 115                }
 0116            }
 0117            catch (Exception ex) when (ex is MotexApiException or HttpRequestException or TaskCanceledException)
 118            {
 0119                _logger.LogInformation("МоТехС: не удалось получить детали заказа {ClientOrderNum}: {Error}", clientNum,
 0120            }
 0121        }
 122
 1123        var result = new List<SupplierStatusSnapshot>();
 1124        var localOrdersChanged = false;
 125
 4126        foreach (var state in states)
 127        {
 1128            latestItems.TryGetValue(state.OrderId, out var item);
 1129            var localOrder = item?.MotexOrder;
 130
 1131            JObject? apiOrder = null;
 1132            if (!string.IsNullOrWhiteSpace(state.ExternalOrderId) && byId.TryGetValue(state.ExternalOrderId, out var f1)
 1133                apiOrder = f1;
 0134            else if (!string.IsNullOrWhiteSpace(state.ExternalOrderNumber))
 135            {
 0136                if (byId.TryGetValue(state.ExternalOrderNumber, out var f2))
 0137                    apiOrder = f2;
 0138                else if (byClientNum.TryGetValue(state.ExternalOrderNumber, out var f3))
 0139                    apiOrder = f3;
 140            }
 141
 1142            if (apiOrder is null && localOrder is not null)
 143            {
 0144                if (!string.IsNullOrWhiteSpace(localOrder.ExternalOrderId) && byId.TryGetValue(localOrder.ExternalOrderI
 0145                    apiOrder = f4;
 0146                else if (!string.IsNullOrWhiteSpace(localOrder.ClientOrderNum) && byClientNum.TryGetValue(localOrder.Cli
 0147                    apiOrder = f5;
 148            }
 149
 1150            if (apiOrder is null)
 151                continue;
 152
 1153            var externalId = apiOrder.Value<string>("id") ?? localOrder?.ExternalOrderId;
 1154            var clientNum = apiOrder.Value<string>("clientOrderNum") ?? localOrder?.ClientOrderNum;
 155
 1156            var apiItems = (apiOrder["orderItems"] as JArray)?.OfType<JObject>().ToList();
 1157            var matchedItem = item is not null && apiItems is not null
 1158                ? apiItems.FirstOrDefault(i =>
 1159                    string.Equals(i.Value<string>("code"), item.Code, StringComparison.OrdinalIgnoreCase) &&
 1160                    string.Equals(i.Value<string>("brand"), item.Brand, StringComparison.OrdinalIgnoreCase))
 1161                  ?? (apiItems.Count == 1 ? apiItems[0] : null)
 1162                : apiItems?.FirstOrDefault();
 163
 1164            var status = matchedItem?.Value<string>("status")
 1165                ?? apiOrder.Value<string>("status")
 1166                ?? localOrder?.Status
 1167                ?? "—";
 168
 1169            if (localOrder is not null)
 170            {
 1171                if (!string.IsNullOrWhiteSpace(externalId) && string.IsNullOrWhiteSpace(localOrder.ExternalOrderId))
 172                {
 0173                    localOrder.ExternalOrderId = externalId;
 0174                    localOrdersChanged = true;
 175                }
 1176                if (!string.IsNullOrWhiteSpace(status) && localOrder.Status != status)
 177                {
 1178                    localOrder.Status = status;
 1179                    localOrdersChanged = true;
 180                }
 181            }
 182
 1183            var lineId = matchedItem?.Value<string>("id");
 1184            var qntConfirmed = matchedItem?.Value<decimal?>("qnt_ordered") ?? matchedItem?.Value<decimal?>("quantity");
 185
 1186            result.Add(new SupplierStatusSnapshot
 1187            {
 1188                OrderId = state.OrderId,
 1189                Kind = SupplierStatusKind.Order,
 1190                ExternalCode = status,
 1191                StatusName = status,
 1192                ExternalOrderNumber = externalId ?? clientNum ?? state.ExternalOrderNumber,
 1193                ExternalOrderId = externalId ?? state.ExternalOrderId,
 1194                ExternalLineId = lineId ?? state.ExternalLineId,
 1195                QuantityConfirmed = qntConfirmed
 1196            });
 197        }
 198
 1199        if (localOrdersChanged)
 200        {
 1201            await _db.SaveChangesAsync(ct);
 202        }
 203
 1204        return result;
 2205    }
 206}