Как уловить и красиво вывести прикладную ошибку при программном проведении документа в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При интерактивной работе в среде «1С:Предприятие» пользователи привыкли видеть понятные и структурированные сообщения об ошибках проведения. Например: «Недостаточно товара на складе "Основной" в количестве 5 шт.» или «Найден детальный документ на текущий финансовый год!». За это отвечает встроенный механизм сообщений и стандартный обработчик проведения.

Однако ситуация резко меняется, когда мы пытаемся провести этот же документ программно (например, в рамках интеграции через HTTP/Web-сервисы, при работе фонового задания или при обмене данными). Вместо красивого и понятного текста ошибки система генерирует громоздкое системное исключение: Ошибка при вызове метода контекста (Записать): Ошибка при выполнении обработчика - 'ОбработкаПроведения'....

В этой статье мы подробно разберем, почему так происходит, и рассмотрим лучшие практики перехвата, очистки от системного "шума" и вывода реальных прикладных ошибок проведения.

В чем кроется причина проблемы?

Давайте выясним, как ведет себя платформа в различных режимах. Когда в процедуре ОбработкаПроведения мы пишем Отказ = Истина; и выводим сообщение через метод Сообщить() или объект СообщениеПользователю, интерактивный сеанс просто прерывает проведение и показывает пользователю накопленные сообщения.

При программном вызове метода ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение) платформа «1С» реагирует жестче. Если в процессе проведения транзакции параметр Отказ устанавливается в значение Истина, то:

  1. Вся транзакция проведения полностью откатывается (ролбэк).
  2. Платформа принудительно вызывает системное исключение (Runtime Error).
  3. Детальные прикладные сообщения, которые мы отправляли пользователю, «застревают» в специальном буфере (очереди сообщений), а вызывающий код получает лишь общее сообщение об аварийном завершении метода Записать().

Проанализируем основные способы решения этой задачи, начиная от простых обходных путей до профессиональных стандартов разработки.

Решение 1. Чтение очереди сообщений через ПолучитьСообщенияПользователю() (Рекомендуемый способ)

Это самый надежный, правильный и независимый от транзакций способ. Платформа спроектирована так, что даже после полного отката транзакции проведения все сообщения, созданные через объект СообщениеПользователю (или обычный метод Сообщить()), остаются в специальном кэше сеанса. Мы можем извлечь их в блоке Исключение.

Рассмотрим пример кода в вызывающем модуле (например, в обработчике HTTP-сервиса):


Попытка
    // Пытаемся провести документ
    ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
    // Сюда мы попадем, если Отказ = Истина в ОбработкеПроведения
    ТекстОшибки = "Не удалось провести документ по следующим причинам:" + Символы.ПС;
    
    // Получаем все прикладные сообщения, накопленные в этом сеансе.
    // Параметр "Истина" обязателен - он очищает очередь, чтобы сообщения не дублировались при следующем вызове.
    МассивСообщений = ПолучитьСообщенияПользователю(Истина);
    
    Если МассивСообщений.Количество() > 0 Тогда
        Для Каждого ТекущееСообщение Из МассивСообщений Цикл
            ТекстОшибки = ТекстОшибки + "- " + ТекущееСообщение.Текст + Символы.ПС;
        КонецЦикла;
    Иначе
        // Если очередь сообщений пуста, берем стандартное описание ошибки платформы
        ТекстОшибки = ТекстОшибки + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
    EndЕсли;
    
    // Записываем ошибку в лог или возвращаем клиенту HTTP-сервиса
    ЗаписьЖурналаРегистрации("ПроведениеДокумента", УровеньЖурналаРегистрации.Ошибка,,, ТекстОшибки);
КонецПопытки;

Преимущества метода: нам не нужно переписывать логику сотен документов. Метод работает со стандартным механизмом проведения любых типовых и нетиповых конфигураций.

Решение 2. Использование механизма ДополнительныеСвойства

Иногда требуется передать структурированную ошибку (например, код ошибки или массив битых ссылок) напрямую из модуля объекта обратно в вызывающий код. Для этого можно использовать коллекцию ДополнительныеСвойства объекта документа.

Шаг 1. В модуле документа (в процедуре ОбработкаПроведения):


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    Если ПроверитьОшибкиПроведения() Тогда
        Отказ = Истина;
        
        // Записываем структуру ошибки в дополнительные свойства объекта
        ПричинаОтказа = Новый Структура;
        ПричинаОтказа.Вставить("КодОшибки", "ERR_DUPLICATE_DOC");
        ПричинаОтказа.Вставить("Описание", "Найден детальный документ на текущий финансовый год!");
        
        ДополнительныеСвойства.Вставить("РезультатПроведения", ПричинаОтказа);
        Возврат;
    КонецЕсли;
    
КонецПроцедуры

Шаг 2. В модуле вызова:


Попытка
    ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
    // Проверяем, записал ли документ свою ошибку в свойства
    Если ДокОбъект.ДополнительныеСвойства.Свойство("РезультатПроведения") Тогда
        ДанныеОшибки = ДокОбъект.ДополнительныеСвойства.РезультатПроведения;
        ВызватьИсключение Шаблон("[%1] %2", ДанныеОшибки.КодОшибки, ДанныеОшибки.Описание);
    Иначе
        // Резервный вариант, если упало по системной ошибке (например, СУБД)
        ВызватьИсключение КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
    КонецЕсли;
КонецПопытки;

Важное предупреждение: При падении транзакции некоторые свойства объекта в памяти могут быть сброшены или перезаписаны платформой. Поэтому для гарантированной передачи контекста лучше комбинировать данный метод с первым вариантом.

Решение 3. Очистка системного "шума" через КраткоеПредставлениеОшибки()

Если в коде проведения вызывается явное исключение (ВызватьИсключение "Текст ошибки"), стандартная функция ОписаниеОшибки() вернет огромный стек вызовов с указанием модулей и номеров строк. Пользователю или внешней системе (через API) эта техническая информация не нужна.

Давайте посмотрим, как получить только чистое описание ошибки. С версии платформы 8.3 рекомендуется использовать связку функций:

Посмотрим на пример обработки:


Попытка
    ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
    Инфо = ИнформацияОбОшибке();
    
    // Получаем чистое описание без путей к модулям и номеров строк
    ЧистаяОшибка = КраткоеПредставлениеОшибки(Инфо); 
    
    // Дополнительно анализируем первопричину (если было вложенное исключение)
    Если Инфо.Причина <> Неопределено Тогда
        ЧистаяОшибка = ЧистаяОшибка + " Причина: " + Инфо.Причина.Описание;
    КонецЕсли;
КонецПопытки;

Решение 4. Использование стандартных механизмов БСП (Библиотека стандартных подсистем)

Если вы работаете в современной типовой конфигурации (например, 1С:ERP, КА, УТ, УНФ), вам не нужно писать велосипеды для безопасного проведения документов. В составе БСП есть замечательный общий модуль ОбщегоНазначения.

Давайте разберем, как провести документ одной строкой кода с автоматическим сбором всех ошибок:


// Помещаем документ в массив для пакетной обработки
МассивДокументов = Новый Массив;
МассивДокументов.Добавить(СсылкаНаДокумент);

// Вызываем метод БСП, который берет на себя безопасное проведение и сбор ошибок
ТаблицаОшибок = ОбщегоНазначения.ПровестиДокументы(МассивДокументов);

Если ТаблицаОшибок.Количество() > 0 Тогда
    Для Каждого СтрокаОшибки Из ТаблицаОшибок Цикл
        // Здесь мы гарантированно получим очищенный, структурированный текст ошибок проведения
        Сообщить(СтрокаОшибки.ТекстОшибки);
    КонецЦикла;
КонецЕсли;

Архитектурно верный шаблон для API и HTTP-сервисов

При проектировании интеграционных шлюзов крайне важно отдавать вызывающей системе понятный ответ. Падение HTTP-сервиса с ошибкой 500 (Internal Server Error) из-за неудачного проведения — признак плохого тона в разработке.

Давайте спроектируем правильный шаблон метода POST/PUT для HTTP-сервиса в 1С:


Функция ПровестиДокументHTTP(Запрос)
    
    Ответ = Новый HTTPСервисОтвет;
    Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
    
    Попытка
        // Находим и заполняем объект документа
        ДокументОбъект = ПолучитьОбъектИзЗапроса(Запрос);
        
        // Пытаемся провести
        ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
        
        // Формируем успешный ответ
        Результат = Новый Структура("Success, DocumentNumber", Истина, ДокументОбъект.Номер);
        Ответ.СтатусКода = 200;
        Ответ.УстановитьТелоИзСтроки(ПревратитьВJSON(Результат));
        
    Исключение
        // Собираем все прикладные ошибки проведения из очереди сеанса
        СписокОшибок = Новый Массив;
        МассивСообщений = ПолучитьСообщенияПользователю(Истина);
        
        Для Каждого Сообщение Из МассивСообщений Цикл
            СписокОшибок.Добавить(Сообщение.Текст);
        КонецЦикла;
        
        // Если в очереди пусто, берем системное представление ошибки
        Если СписокОшибок.Количество() = 0 Тогда
            СписокОшибок.Добавить(КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
        КонецЕсли;
        
        // Формируем структуру ответа об ошибке (Bad Request)
        РезультатОшибки = Новый Структура;
        РезультатОшибки.Вставить("Success", Ложь);
        РезультатОшибки.Вставить("Errors", СписокОшибок);
        
        Ответ.СтатусКода = 400; // Клиент передал некорректные данные для проведения
        Ответ.УстановитьТелоИзСтроки(ПревратитьВJSON(РезультатОшибки));
    КонецПопытки;
    
    Возврат Ответ;
    
КонецФункции

Применяя данные подходы, вы сможете создавать надежные и отказоустойчивые интеграционные механизмы, которые будут общаться с внешними системами и разработчиками на понятном, человеческом языке ошибок без лишнего технического мусора.

← На главную