< Summary

Information
Class: Services.ApiServices.OzonApi.OzonSplitPostingMapper
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/OzonApi/OzonSplitPostingMapper.cs
Line coverage
82%
Covered lines: 28
Uncovered lines: 6
Coverable lines: 34
Total lines: 96
Line coverage: 82.3%
Branch coverage
60%
Covered branches: 23
Total branches: 38
Branch coverage: 60.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Map(...)63.33%353082.14%
TryMatchByQuantity(...)50%9880%
get_PostingNumber()100%11100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/OzonApi/OzonSplitPostingMapper.cs

#LineLine coverage
 1using Services.ApiServices.OzonApi.Filters;
 2
 3namespace Services.ApiServices.OzonApi;
 4
 5/// <summary>
 6/// Сопоставление ответа Ozon POST /v1/posting/fbs/split с частями заказа (part1/part2).
 7/// </summary>
 8public static class OzonSplitPostingMapper
 9{
 10    /// <summary>
 11    /// Сопоставляет ответ Ozon split с частями заказа <paramref name="part1"/> и <paramref name="part2"/>.
 12    /// </summary>
 13    /// <remarks>
 14    /// <para>При двух дочерних posting сопоставление по quantity; если не удалось — порядок из ответа API.</para>
 15    /// <para>При одном дочернем posting и наличии <c>parent_posting</c>:
 16    /// сопоставление по quantity parent/child с part1/part2 (в обоих порядках).
 17    /// Если quantities не совпадают с ожидаемыми part1/part2 — выбрасывается
 18    /// <see cref="InvalidOperationException"/> (без тихого fallback), чтобы не записать
 19    /// неверные номера отправлений в БД.</para>
 20    /// </remarks>
 21    public static (string posting1, string posting2) Map(
 22        FbsSplitResponse? result,
 23        int part1,
 24        int part2)
 25    {
 426        if (result == null)
 27        {
 028            throw new InvalidOperationException("Ozon API вернул пустой ответ на разделение отправления.");
 29        }
 30
 431        var childPostings = (result.Postings ?? new List<FbsSplitResultPosting>())
 432            .Where(p => !string.IsNullOrWhiteSpace(p.PostingNumber))
 433            .Select(p => new SplitPostingCandidate(
 434                p.PostingNumber!.Trim(),
 835                p.Products?.Sum(x => x.Quantity) ?? 0))
 436            .ToList();
 37
 438        if (childPostings.Count >= 2)
 39        {
 140            var byQty = TryMatchByQuantity(childPostings, part1, part2);
 141            if (byQty != null)
 42            {
 143                return byQty.Value;
 44            }
 45
 046            return (childPostings[0].PostingNumber, childPostings[1].PostingNumber);
 47        }
 48
 349        if (childPostings.Count == 1)
 50        {
 251            var parentNumber = result.ParentPosting?.PostingNumber?.Trim();
 452            var parentQty = result.ParentPosting?.Products?.Sum(x => x.Quantity) ?? 0;
 253            var child = childPostings[0];
 54
 255            if (!string.IsNullOrEmpty(parentNumber))
 56            {
 257                if (parentQty == part1 && child.Quantity == part2)
 58                {
 159                    return (parentNumber, child.PostingNumber);
 60                }
 61
 162                if (parentQty == part2 && child.Quantity == part1)
 63                {
 064                    return (child.PostingNumber, parentNumber);
 65                }
 66
 167                throw new InvalidOperationException(
 168                    $"Не удалось сопоставить quantities: parent={parentQty}, child={child.Quantity}, " +
 169                    $"ожидалось part1={part1}, part2={part2}.");
 70            }
 71
 072            throw new InvalidOperationException(
 073                "Ozon API вернул одно дочернее отправление без parent_posting — невозможно сопоставить обе части.");
 74        }
 75
 176        throw new InvalidOperationException("Ozon API вернул пустые номера отправлений.");
 77    }
 78
 79    private static (string, string)? TryMatchByQuantity(
 80        List<SplitPostingCandidate> postings,
 81        int part1,
 82        int part2)
 83    {
 284        var match1 = postings.FirstOrDefault(p => p.Quantity == part1);
 385        var match2 = postings.FirstOrDefault(p => p.Quantity == part2 && p.PostingNumber != match1?.PostingNumber);
 86
 187        if (match1 != null && match2 != null)
 88        {
 189            return (match1.PostingNumber, match2.PostingNumber);
 90        }
 91
 092        return null;
 93    }
 94
 1695    private sealed record SplitPostingCandidate(string PostingNumber, int Quantity);
 96}