< Summary

Information
Class: Services.ApiServices._1CApi.OData.OneCDocumentPostingMessage
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/1CApi/OData/OneCDocumentPostingMessage.cs
Line coverage
100%
Covered lines: 26
Uncovered lines: 0
Coverable lines: 26
Total lines: 65
Line coverage: 100%
Branch coverage
100%
Covered branches: 12
Total branches: 12
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
IsUninformative(...)100%22100%
DocumentTitle(...)100%44100%
Build(...)100%66100%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/ApiServices/1CApi/OData/OneCDocumentPostingMessage.cs

#LineLine coverage
 1using System.Text.RegularExpressions;
 2
 3namespace Services.ApiServices._1CApi.OData;
 4
 5/// <summary>
 6/// Собирает человекочитаемое сообщение об отказе 1С провести документ.
 7///
 8/// Отказ проведения — ожидаемый бизнес-исход (чаще всего не хватает остатка), и его текст
 9/// целиком уходит пользователю в алерт транзакции через <c>OneCResponse.PostingError</c>.
 10/// Поэтому сообщение не должно содержать техники: имени OData-эндпоинта, HTTP-тела ответа
 11/// и голой фразы 1С «Не удалось провести "…"!», по которой непонятно, что делать дальше.
 12/// </summary>
 13public static class OneCDocumentPostingMessage
 14{
 15    /// <summary>Названия документов в винительном падеже: «1С не провела <i>перемещение запасов</i>».</summary>
 116    private static readonly Dictionary<string, string> DocumentTitles = new(StringComparer.OrdinalIgnoreCase)
 117    {
 118        ["Document_ПеремещениеЗапасов"] = "перемещение запасов",
 119        ["Document_РасходнаяНакладная"] = "расходную накладную",
 120        ["Document_ПриходнаяНакладная"] = "приходную накладную",
 121        ["Document_ИнвентаризацияЗапасов"] = "инвентаризацию запасов",
 122        ["Document_ЗаказКлиента"] = "заказ клиента"
 123    };
 24
 25    private const string DefaultTitle = "документ";
 26
 27    /// <summary>
 28    /// Типовой отказ 1С без объяснения причины: «Не удалось провести "Перемещение запасов 4043 от 07.09.2026"!».
 29    /// Имя документа в кавычках вытаскиваем и показываем отдельно.
 30    /// </summary>
 131    private static readonly Regex GenericRefusalRegex = new(
 132        @"^Не\s+удалось\s+провести\s+[«""'](?<name>.+?)[»""']\s*!?\s*$",
 133        RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
 34
 35    /// <summary>1С не назвала причину: пусто или только «Не удалось провести "…"!».</summary>
 36    public static bool IsUninformative(string? detail)
 437        => string.IsNullOrWhiteSpace(detail) || GenericRefusalRegex.IsMatch(detail.Trim());
 38
 39    /// <summary>Название документа 1С для текста сообщения.</summary>
 40    public static string DocumentTitle(string? endpoint)
 2241        => !string.IsNullOrWhiteSpace(endpoint) && DocumentTitles.TryGetValue(endpoint.Trim(), out var title)
 2242            ? title
 2243            : DefaultTitle;
 44
 45    /// <summary>
 46    /// Формирует сообщение для пользователя по эндпоинту документа и разобранной причине от 1С.
 47    /// </summary>
 48    public static string Build(string? endpoint, string? detail)
 49    {
 1650        var title = DocumentTitle(endpoint);
 1651        var trimmed = detail?.Trim();
 52
 1653        if (string.IsNullOrEmpty(trimmed))
 354            return $"1С не провела {title}: причину 1С не сообщила. " +
 355                   "Документ сохранён непроведённым — откройте его в 1С и проведите вручную.";
 56
 1357        var generic = GenericRefusalRegex.Match(trimmed);
 1358        if (generic.Success)
 459            return $"1С не провела {title} «{generic.Groups["name"].Value.Trim()}»: подробностей 1С не вернула, " +
 460                   "чаще всего не хватает остатка на складе. " +
 461                   "Документ сохранён непроведённым — проверьте остатки в 1С и проведите его вручную.";
 62
 963        return $"1С не провела {title}: {trimmed}";
 64    }
 65}