< Summary

Information
Class: Services.OneCServices.OneCPostingSummary
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/OneCServices/OneCPostingSummaryBuilder.cs
Line coverage
100%
Covered lines: 15
Uncovered lines: 0
Coverable lines: 15
Total lines: 235
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_Success()100%11100%
get_OrganizationName()100%11100%
get_OrganizationInn()100%11100%
get_SenderName()100%11100%
get_SenderInn()100%11100%
get_WarehouseSender()100%11100%
get_WarehouseRecipient()100%11100%
get_OrdersCount()100%11100%
get_ProductsCount()100%11100%
get_ArticlesSummary()100%11100%
get_DocumentsCount()100%11100%
get_DocumentNumbers()100%11100%
get_DocumentGuids()100%11100%
get_IsPosted()100%11100%
get_ErrorSummary()100%11100%

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{
 7316    public bool Success { get; set; }
 3117    public string? OrganizationName { get; set; }
 2518    public string? OrganizationInn { get; set; }
 2519    public string? SenderName { get; set; }
 2420    public string? SenderInn { get; set; }
 3021    public string? WarehouseSender { get; set; }
 1822    public string? WarehouseRecipient { get; set; }
 6723    public int OrdersCount { get; set; }
 6624    public int ProductsCount { get; set; }
 7125    public string? ArticlesSummary { get; set; }
 6826    public int DocumentsCount { get; set; }
 6127    public string? DocumentNumbers { get; set; }
 5728    public string? DocumentGuids { get; set; }
 6929    public bool IsPosted { get; set; }
 16430    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    {
 44        transferResult ??= Array.Empty<OneCResponse>();
 45        orders ??= new List<Order>();
 46
 47        var successful = transferResult.Where(r => r.Success).ToList();
 48        var failed = transferResult.Where(r => !r.Success).ToList();
 49
 50        var summary = new OneCPostingSummary
 51        {
 52            Success = successful.Count > 0 && failed.Count == 0,
 53            OrdersCount = orders.Count,
 54            DocumentsCount = successful.Count
 55        };
 56
 57        var distinctOrganizations = orders
 58            .Select(o => o.OzonClient?.Name?.Trim())
 59            .Where(x => !string.IsNullOrEmpty(x))
 60            .Distinct(StringComparer.OrdinalIgnoreCase)
 61            .ToList();
 62        var distinctOrganizationInns = orders
 63            .Select(o => o.OzonClient?.INNCode?.Trim())
 64            .Where(x => !string.IsNullOrEmpty(x))
 65            .Distinct(StringComparer.OrdinalIgnoreCase)
 66            .ToList();
 67
 68        if (distinctOrganizations.Count > 0)
 69        {
 70            summary.OrganizationName = string.Join(", ", distinctOrganizations);
 71            if (distinctOrganizationInns.Count > 0)
 72                summary.OrganizationInn = string.Join(", ", distinctOrganizationInns);
 73        }
 74
 75        var distinctSenders = orders
 76            .Select(o => o.Supplier?.Name?.Trim())
 77            .Where(x => !string.IsNullOrEmpty(x))
 78            .Distinct(StringComparer.OrdinalIgnoreCase)
 79            .ToList();
 80        var distinctSenderInns = orders
 81            .Select(o => o.Supplier?.INNCode?.Trim())
 82            .Where(x => !string.IsNullOrEmpty(x))
 83            .Distinct(StringComparer.OrdinalIgnoreCase)
 84            .ToList();
 85
 86        if (distinctSenders.Count > 0)
 87        {
 88            summary.SenderName = string.Join(", ", distinctSenders);
 89            if (distinctSenderInns.Count > 0)
 90                summary.SenderInn = string.Join(", ", distinctSenderInns);
 91        }
 92
 93        var senderWarehouses = !string.IsNullOrEmpty(warehouseSender)
 94            ? new List<string> { warehouseSender.Trim() }
 95            : orders
 96                .Select(GetEffectiveWarehouse)
 97                .Where(x => !string.IsNullOrEmpty(x))
 98                .Distinct(StringComparer.OrdinalIgnoreCase)
 99                .ToList();
 100        if (senderWarehouses.Count > 0)
 101            summary.WarehouseSender = string.Join(", ", senderWarehouses);
 102
 103        var recipientWarehouses = !string.IsNullOrEmpty(warehouseRecipient)
 104            ? new List<string> { warehouseRecipient.Trim() }
 105            : orders
 106                .Select(o => o.OzonClient?.WarehouseName?.Trim())
 107                .Where(x => !string.IsNullOrEmpty(x))
 108                .Select(x => x!)
 109                .Distinct(StringComparer.OrdinalIgnoreCase)
 110                .ToList();
 111        if (recipientWarehouses.Count > 0)
 112            summary.WarehouseRecipient = string.Join(", ", recipientWarehouses);
 113
 114        var productGroups = orders
 115            .Where(o => !string.IsNullOrEmpty(o.Article) && o.Quantity > 0)
 116            .GroupBy(o => o.Article!.Trim())
 117            .Select(g => new { Article = g.Key, Quantity = g.Sum(o => o.Quantity ?? 0) })
 118            .OrderByDescending(g => g.Quantity)
 119            .ToList();
 120
 121        if (productGroups.Count > 0)
 122        {
 123            summary.ProductsCount = productGroups.Sum(x => x.Quantity);
 124            var articleSummaries = productGroups
 125                .Take(4)
 126                .Select(x => $"{x.Article}×{x.Quantity}")
 127                .ToList();
 128            summary.ArticlesSummary = string.Join(", ", articleSummaries);
 129            var remaining = productGroups.Count - articleSummaries.Count;
 130            if (remaining > 0)
 131                summary.ArticlesSummary += $" +{remaining}";
 132        }
 133
 134        // Читаемые номера ("НФY1-001620")
 135        var docNumbers = successful
 136            .Select(s => !string.IsNullOrWhiteSpace(s.DocumentNumber) ? s.DocumentNumber : s.TransactionId)
 137            .Where(id => !string.IsNullOrWhiteSpace(id))
 138            .ToList();
 139        if (docNumbers.Count > 0)
 140            summary.DocumentNumbers = string.Join(", ", docNumbers);
 141
 142        // GUID Ref_Key для поиска и перехода в 1С Fresh
 143        var guids = successful
 144            .Select(s => s.TransactionId)
 145            .Where(id => !string.IsNullOrWhiteSpace(id))
 146            .ToList();
 147        if (guids.Count > 0)
 148            summary.DocumentGuids = string.Join(", ", guids);
 149
 150        // Все ли документы проведены
 151        summary.IsPosted = successful.Count > 0 && successful.All(s => s.IsPosted);
 152
 153        var missingNotes = successful
 154            .Where(s => !string.IsNullOrWhiteSpace(s.MissingNomenclature))
 155            .Select(s => s.MissingNomenclature)
 156            .Distinct()
 157            .ToList();
 158        if (missingNotes.Count > 0)
 159        {
 160            var draftNote = "Черновик — не найдена номенклатура: " + string.Join("; ", missingNotes);
 161            summary.ErrorSummary = string.IsNullOrWhiteSpace(summary.ErrorSummary)
 162                ? draftNote
 163                : $"{summary.ErrorSummary}; {draftNote}";
 164        }
 165
 166        // Документ создан (Success=true), но не проведён (IsPosted=false) — например, нехватка остатка
 167        // у только что созданной номенклатуры. PostingError — единственное место с причиной отказа
 168        // (детальный текст 1С или наша подсказка про остаток), поэтому его нужно явно перенести
 169        // в ErrorSummary, иначе причина теряется и в истории заказа, и в фоновых job-логах.
 170        var postingErrorNotes = successful
 171            .Where(s => !s.IsPosted && !string.IsNullOrWhiteSpace(s.PostingError))
 172            .Select(s => s.PostingError)
 173            .Distinct()
 174            .ToList();
 175        if (postingErrorNotes.Count > 0)
 176        {
 177            var postingNote = OneCResponseErrors.AppendOneCDetails(
 178                "Не проведено: " + string.Join("; ", postingErrorNotes),
 179                successful.Where(s => !s.IsPosted));
 180            summary.ErrorSummary = string.IsNullOrWhiteSpace(summary.ErrorSummary)
 181                ? postingNote
 182                : $"{summary.ErrorSummary}; {postingNote}";
 183        }
 184
 185        if (failed.Count > 0)
 186        {
 187            var errors = failed.Select(f => f.Message).Where(m => !string.IsNullOrWhiteSpace(m)).Distinct().Take(5);
 188            var errorText = string.Join("; ", errors);
 189            summary.ErrorSummary = string.IsNullOrWhiteSpace(summary.ErrorSummary)
 190                ? errorText
 191                : $"{summary.ErrorSummary}; {errorText}";
 192            if (successful.Count > 0)
 193                summary.Success = false;
 194        }
 195
 196        summary.ErrorSummary = TruncateErrorSummary(summary.ErrorSummary);
 197
 198        return summary;
 199    }
 200
 201    public static string BuildHistoryBatchKey(int orderId, DateTime changedAt, string? changedBy)
 202    {
 203        var rounded = new DateTime(
 204            changedAt.Ticks - changedAt.Ticks % TimeSpan.TicksPerMinute,
 205            changedAt.Kind);
 206        return $"{orderId}:{rounded:yyyyMMddHHmm}:{changedBy ?? ""}";
 207    }
 208
 209    private static string GetEffectiveWarehouse(Order order)
 210    {
 211        var zasyl = order.OrderExtensions?
 212            .FirstOrDefault(e => string.Equals(e.Key, "Засыл", StringComparison.OrdinalIgnoreCase))
 213            ?.Value?.Trim();
 214
 215        if (!string.IsNullOrWhiteSpace(zasyl))
 216            return zasyl;
 217
 218        return order.ShipmentWarehouse?.Name?.Trim() ?? string.Empty;
 219    }
 220
 221    private static string? TruncateErrorSummary(string? value)
 222    {
 223        if (string.IsNullOrWhiteSpace(value))
 224            return value;
 225
 226        if (value.Length <= ErrorSummaryMaxLength)
 227            return value;
 228
 229        var maxContentLength = ErrorSummaryMaxLength - ErrorSummaryTruncatedSuffix.Length;
 230        if (maxContentLength <= 0)
 231            return ErrorSummaryTruncatedSuffix[..ErrorSummaryMaxLength];
 232
 233        return value[..maxContentLength] + ErrorSummaryTruncatedSuffix;
 234    }
 235}