< Summary

Information
Class: Services.OneCServices.OneCPostingSummaryBuilder
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/OneCServices/OneCPostingSummaryBuilder.cs
Line coverage
99%
Covered lines: 135
Uncovered lines: 1
Coverable lines: 136
Total lines: 235
Line coverage: 99.2%
Branch coverage
89%
Covered branches: 84
Total branches: 94
Branch coverage: 89.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Build(...)91.66%7272100%
BuildHistoryBatchKey(...)100%22100%
GetEffectiveWarehouse(...)78.57%1414100%
TruncateErrorSummary(...)83.33%6687.5%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/OneCServices/OneCPostingSummaryBuilder.cs

#LineLine coverage
 1using OzonDomains.Models;
 2using Services.ApiServices._1CApi.Models;
 3using Services.ApiServices._1CApi.OData;
 4
 5namespace Services.OneCServices;
 6
 7public static class OneCPostingOperationKinds
 8{
 9    public const string Movement = "Movement";
 10    public const string Receipt = "Receipt";
 11    public const string Expense = "Expense";
 12}
 13
 14public sealed class OneCPostingSummary
 15{
 16    public bool Success { get; set; }
 17    public string? OrganizationName { get; set; }
 18    public string? OrganizationInn { get; set; }
 19    public string? SenderName { get; set; }
 20    public string? SenderInn { get; set; }
 21    public string? WarehouseSender { get; set; }
 22    public string? WarehouseRecipient { get; set; }
 23    public int OrdersCount { get; set; }
 24    public int ProductsCount { get; set; }
 25    public string? ArticlesSummary { get; set; }
 26    public int DocumentsCount { get; set; }
 27    public string? DocumentNumbers { get; set; }
 28    public string? DocumentGuids { get; set; }
 29    public bool IsPosted { get; set; }
 30    public string? ErrorSummary { get; set; }
 31}
 32
 33public static class OneCPostingSummaryBuilder
 34{
 35    private const int ErrorSummaryMaxLength = 2000;
 36    private const string ErrorSummaryTruncatedSuffix = "… [truncated]";
 37
 38    public static OneCPostingSummary Build(
 39        List<Order> orders,
 40        IReadOnlyList<OneCResponse> transferResult,
 41        string? warehouseSender = null,
 42        string? warehouseRecipient = null)
 43    {
 5544        transferResult ??= Array.Empty<OneCResponse>();
 5545        orders ??= new List<Order>();
 46
 11747        var successful = transferResult.Where(r => r.Success).ToList();
 11748        var failed = transferResult.Where(r => !r.Success).ToList();
 49
 5550        var summary = new OneCPostingSummary
 5551        {
 5552            Success = successful.Count > 0 && failed.Count == 0,
 5553            OrdersCount = orders.Count,
 5554            DocumentsCount = successful.Count
 5555        };
 56
 5557        var distinctOrganizations = orders
 6858            .Select(o => o.OzonClient?.Name?.Trim())
 6859            .Where(x => !string.IsNullOrEmpty(x))
 5560            .Distinct(StringComparer.OrdinalIgnoreCase)
 5561            .ToList();
 5562        var distinctOrganizationInns = orders
 6863            .Select(o => o.OzonClient?.INNCode?.Trim())
 6864            .Where(x => !string.IsNullOrEmpty(x))
 5565            .Distinct(StringComparer.OrdinalIgnoreCase)
 5566            .ToList();
 67
 5568        if (distinctOrganizations.Count > 0)
 69        {
 1570            summary.OrganizationName = string.Join(", ", distinctOrganizations);
 1571            if (distinctOrganizationInns.Count > 0)
 1272                summary.OrganizationInn = string.Join(", ", distinctOrganizationInns);
 73        }
 74
 5575        var distinctSenders = orders
 6876            .Select(o => o.Supplier?.Name?.Trim())
 6877            .Where(x => !string.IsNullOrEmpty(x))
 5578            .Distinct(StringComparer.OrdinalIgnoreCase)
 5579            .ToList();
 5580        var distinctSenderInns = orders
 6881            .Select(o => o.Supplier?.INNCode?.Trim())
 6882            .Where(x => !string.IsNullOrEmpty(x))
 5583            .Distinct(StringComparer.OrdinalIgnoreCase)
 5584            .ToList();
 85
 5586        if (distinctSenders.Count > 0)
 87        {
 1288            summary.SenderName = string.Join(", ", distinctSenders);
 1289            if (distinctSenderInns.Count > 0)
 1290                summary.SenderInn = string.Join(", ", distinctSenderInns);
 91        }
 92
 5593        var senderWarehouses = !string.IsNullOrEmpty(warehouseSender)
 5594            ? new List<string> { warehouseSender.Trim() }
 5595            : orders
 5596                .Select(GetEffectiveWarehouse)
 6497                .Where(x => !string.IsNullOrEmpty(x))
 5598                .Distinct(StringComparer.OrdinalIgnoreCase)
 5599                .ToList();
 55100        if (senderWarehouses.Count > 0)
 15101            summary.WarehouseSender = string.Join(", ", senderWarehouses);
 102
 55103        var recipientWarehouses = !string.IsNullOrEmpty(warehouseRecipient)
 55104            ? new List<string> { warehouseRecipient.Trim() }
 55105            : orders
 64106                .Select(o => o.OzonClient?.WarehouseName?.Trim())
 64107                .Where(x => !string.IsNullOrEmpty(x))
 1108                .Select(x => x!)
 55109                .Distinct(StringComparer.OrdinalIgnoreCase)
 55110                .ToList();
 55111        if (recipientWarehouses.Count > 0)
 5112            summary.WarehouseRecipient = string.Join(", ", recipientWarehouses);
 113
 55114        var productGroups = orders
 68115            .Where(o => !string.IsNullOrEmpty(o.Article) && o.Quantity > 0)
 66116            .GroupBy(o => o.Article!.Trim())
 125117            .Select(g => new { Article = g.Key, Quantity = g.Sum(o => o.Quantity ?? 0) })
 59118            .OrderByDescending(g => g.Quantity)
 55119            .ToList();
 120
 55121        if (productGroups.Count > 0)
 122        {
 111123            summary.ProductsCount = productGroups.Sum(x => x.Quantity);
 52124            var articleSummaries = productGroups
 52125                .Take(4)
 57126                .Select(x => $"{x.Article}×{x.Quantity}")
 52127                .ToList();
 52128            summary.ArticlesSummary = string.Join(", ", articleSummaries);
 52129            var remaining = productGroups.Count - articleSummaries.Count;
 52130            if (remaining > 0)
 1131                summary.ArticlesSummary += $" +{remaining}";
 132        }
 133
 134        // Читаемые номера ("НФY1-001620")
 55135        var docNumbers = successful
 49136            .Select(s => !string.IsNullOrWhiteSpace(s.DocumentNumber) ? s.DocumentNumber : s.TransactionId)
 49137            .Where(id => !string.IsNullOrWhiteSpace(id))
 55138            .ToList();
 55139        if (docNumbers.Count > 0)
 45140            summary.DocumentNumbers = string.Join(", ", docNumbers);
 141
 142        // GUID Ref_Key для поиска и перехода в 1С Fresh
 55143        var guids = successful
 49144            .Select(s => s.TransactionId)
 49145            .Where(id => !string.IsNullOrWhiteSpace(id))
 55146            .ToList();
 55147        if (guids.Count > 0)
 45148            summary.DocumentGuids = string.Join(", ", guids);
 149
 150        // Все ли документы проведены
 104151        summary.IsPosted = successful.Count > 0 && successful.All(s => s.IsPosted);
 152
 55153        var missingNotes = successful
 49154            .Where(s => !string.IsNullOrWhiteSpace(s.MissingNomenclature))
 2155            .Select(s => s.MissingNomenclature)
 55156            .Distinct()
 55157            .ToList();
 55158        if (missingNotes.Count > 0)
 159        {
 2160            var draftNote = "Черновик — не найдена номенклатура: " + string.Join("; ", missingNotes);
 2161            summary.ErrorSummary = string.IsNullOrWhiteSpace(summary.ErrorSummary)
 2162                ? draftNote
 2163                : $"{summary.ErrorSummary}; {draftNote}";
 164        }
 165
 166        // Документ создан (Success=true), но не проведён (IsPosted=false) — например, нехватка остатка
 167        // у только что созданной номенклатуры. PostingError — единственное место с причиной отказа
 168        // (детальный текст 1С или наша подсказка про остаток), поэтому его нужно явно перенести
 169        // в ErrorSummary, иначе причина теряется и в истории заказа, и в фоновых job-логах.
 55170        var postingErrorNotes = successful
 49171            .Where(s => !s.IsPosted && !string.IsNullOrWhiteSpace(s.PostingError))
 3172            .Select(s => s.PostingError)
 55173            .Distinct()
 55174            .ToList();
 55175        if (postingErrorNotes.Count > 0)
 176        {
 3177            var postingNote = OneCResponseErrors.AppendOneCDetails(
 3178                "Не проведено: " + string.Join("; ", postingErrorNotes),
 6179                successful.Where(s => !s.IsPosted));
 3180            summary.ErrorSummary = string.IsNullOrWhiteSpace(summary.ErrorSummary)
 3181                ? postingNote
 3182                : $"{summary.ErrorSummary}; {postingNote}";
 183        }
 184
 55185        if (failed.Count > 0)
 186        {
 35187            var errors = failed.Select(f => f.Message).Where(m => !string.IsNullOrWhiteSpace(m)).Distinct().Take(5);
 9188            var errorText = string.Join("; ", errors);
 9189            summary.ErrorSummary = string.IsNullOrWhiteSpace(summary.ErrorSummary)
 9190                ? errorText
 9191                : $"{summary.ErrorSummary}; {errorText}";
 9192            if (successful.Count > 0)
 2193                summary.Success = false;
 194        }
 195
 55196        summary.ErrorSummary = TruncateErrorSummary(summary.ErrorSummary);
 197
 55198        return summary;
 199    }
 200
 201    public static string BuildHistoryBatchKey(int orderId, DateTime changedAt, string? changedBy)
 202    {
 22203        var rounded = new DateTime(
 22204            changedAt.Ticks - changedAt.Ticks % TimeSpan.TicksPerMinute,
 22205            changedAt.Kind);
 22206        return $"{orderId}:{rounded:yyyyMMddHHmm}:{changedBy ?? ""}";
 207    }
 208
 209    private static string GetEffectiveWarehouse(Order order)
 210    {
 64211        var zasyl = order.OrderExtensions?
 2212            .FirstOrDefault(e => string.Equals(e.Key, "Засыл", StringComparison.OrdinalIgnoreCase))
 64213            ?.Value?.Trim();
 214
 64215        if (!string.IsNullOrWhiteSpace(zasyl))
 2216            return zasyl;
 217
 62218        return order.ShipmentWarehouse?.Name?.Trim() ?? string.Empty;
 219    }
 220
 221    private static string? TruncateErrorSummary(string? value)
 222    {
 55223        if (string.IsNullOrWhiteSpace(value))
 41224            return value;
 225
 14226        if (value.Length <= ErrorSummaryMaxLength)
 13227            return value;
 228
 1229        var maxContentLength = ErrorSummaryMaxLength - ErrorSummaryTruncatedSuffix.Length;
 1230        if (maxContentLength <= 0)
 0231            return ErrorSummaryTruncatedSuffix[..ErrorSummaryMaxLength];
 232
 1233        return value[..maxContentLength] + ErrorSummaryTruncatedSuffix;
 234    }
 235}