| | | 1 | | using Services.ApiServices._1CApi.Models; |
| | | 2 | | |
| | | 3 | | namespace Services.ApiServices._1CApi.OData; |
| | | 4 | | |
| | | 5 | | /// <summary> |
| | | 6 | | /// Единое место, где причина сбоя 1С попадает в <see cref="OneCResponse.ErrorDetail"/>: менеджеры |
| | | 7 | | /// документов не собирают этот текст сами, чтобы формат был одинаковым для перемещения, расходной и приходной. |
| | | 8 | | /// </summary> |
| | | 9 | | public static class OneCResponseErrors |
| | | 10 | | { |
| | | 11 | | /// <summary>Потолок под <c>BackgroundTransactionResults.ErrorMessage</c> (text) и DetailsJson.</summary> |
| | | 12 | | public const int MaxLength = 8000; |
| | | 13 | | |
| | | 14 | | /// <summary> |
| | | 15 | | /// Полный текст ответа 1С без усечения до 500 символов: статус, код, метод, эндпоинт и причина. |
| | | 16 | | /// Для исключений, не пришедших из ответа 1С (сеть, таймаут), — текст исключения. |
| | | 17 | | /// </summary> |
| | | 18 | | public static string Describe(Exception ex) |
| | | 19 | | { |
| | 4 | 20 | | var text = ex switch |
| | 4 | 21 | | { |
| | 2 | 22 | | OneCDocumentPostingException posting => posting.FullDetail, |
| | 1 | 23 | | OneCODataException { Error: not null } odata => odata.Error.ToDetailText(), |
| | 1 | 24 | | _ => ex.Message |
| | 4 | 25 | | }; |
| | | 26 | | |
| | 4 | 27 | | return text.Length <= MaxLength ? text : text[..MaxLength] + "…"; |
| | | 28 | | } |
| | | 29 | | |
| | | 30 | | /// <summary> |
| | | 31 | | /// Дописывает к краткому тексту ошибки полный ответ 1С из <see cref="OneCResponse.ErrorDetail"/> — |
| | | 32 | | /// только тот, чьей причины в тексте ещё нет (не дублирует, когда <c>PostingError</c> уже содержит её целиком; |
| | | 33 | | /// дописывает, когда причину усекли или подменили догадкой про остаток). Если дописывать нечего, |
| | | 34 | | /// возвращает <paramref name="text"/> как есть, поэтому существующие сводки не меняются. |
| | | 35 | | /// </summary> |
| | | 36 | | public static string? AppendOneCDetails(string? text, IEnumerable<OneCResponse> responses) |
| | | 37 | | { |
| | 13 | 38 | | var extra = responses |
| | 15 | 39 | | .Select(r => r.ErrorDetail) |
| | 15 | 40 | | .Where(d => !string.IsNullOrWhiteSpace(d)) |
| | 7 | 41 | | .Select(d => d!) |
| | 13 | 42 | | .Distinct() |
| | 6 | 43 | | .Where(d => string.IsNullOrEmpty(text) || !text.Contains(Reason(d), StringComparison.Ordinal)) |
| | 13 | 44 | | .ToList(); |
| | | 45 | | |
| | 13 | 46 | | if (extra.Count == 0) |
| | 9 | 47 | | return text; |
| | | 48 | | |
| | 4 | 49 | | var tail = "Ответ 1С: " + string.Join("; ", extra); |
| | 4 | 50 | | return string.IsNullOrWhiteSpace(text) ? tail : $"{text} | {tail}"; |
| | | 51 | | } |
| | | 52 | | |
| | | 53 | | /// <summary>Причина без служебного префикса «HTTP … метод эндпоинт: » (см. <see cref="OneCODataError.ToDetailText"/ |
| | | 54 | | private static string Reason(string detail) |
| | | 55 | | { |
| | 5 | 56 | | var idx = detail.IndexOf(": ", StringComparison.Ordinal); |
| | 5 | 57 | | return idx >= 0 && detail.StartsWith("HTTP ", StringComparison.Ordinal) ? detail[(idx + 2)..] : detail; |
| | | 58 | | } |
| | | 59 | | } |