При интерактивной работе в среде «1С:Предприятие» пользователи привыкли видеть понятные и структурированные сообщения об ошибках проведения. Например: «Недостаточно товара на складе "Основной" в количестве 5 шт.» или «Найден детальный документ на текущий финансовый год!». За это отвечает встроенный механизм сообщений и стандартный обработчик проведения.
Однако ситуация резко меняется, когда мы пытаемся провести этот же документ программно (например, в рамках интеграции через HTTP/Web-сервисы, при работе фонового задания или при обмене данными). Вместо красивого и понятного текста ошибки система генерирует громоздкое системное исключение: Ошибка при вызове метода контекста (Записать): Ошибка при выполнении обработчика - 'ОбработкаПроведения'....
В этой статье мы подробно разберем, почему так происходит, и рассмотрим лучшие практики перехвата, очистки от системного "шума" и вывода реальных прикладных ошибок проведения.
Давайте выясним, как ведет себя платформа в различных режимах. Когда в процедуре ОбработкаПроведения мы пишем Отказ = Истина; и выводим сообщение через метод Сообщить() или объект СообщениеПользователю, интерактивный сеанс просто прерывает проведение и показывает пользователю накопленные сообщения.
При программном вызове метода ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение) платформа «1С» реагирует жестче. Если в процессе проведения транзакции параметр Отказ устанавливается в значение Истина, то:
Записать().Проанализируем основные способы решения этой задачи, начиная от простых обходных путей до профессиональных стандартов разработки.
Это самый надежный, правильный и независимый от транзакций способ. Платформа спроектирована так, что даже после полного отката транзакции проведения все сообщения, созданные через объект СообщениеПользователю (или обычный метод Сообщить()), остаются в специальном кэше сеанса. Мы можем извлечь их в блоке Исключение.
Рассмотрим пример кода в вызывающем модуле (например, в обработчике HTTP-сервиса):
Попытка
// Пытаемся провести документ
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// Сюда мы попадем, если Отказ = Истина в ОбработкеПроведения
ТекстОшибки = "Не удалось провести документ по следующим причинам:" + Символы.ПС;
// Получаем все прикладные сообщения, накопленные в этом сеансе.
// Параметр "Истина" обязателен - он очищает очередь, чтобы сообщения не дублировались при следующем вызове.
МассивСообщений = ПолучитьСообщенияПользователю(Истина);
Если МассивСообщений.Количество() > 0 Тогда
Для Каждого ТекущееСообщение Из МассивСообщений Цикл
ТекстОшибки = ТекстОшибки + "- " + ТекущееСообщение.Текст + Символы.ПС;
КонецЦикла;
Иначе
// Если очередь сообщений пуста, берем стандартное описание ошибки платформы
ТекстОшибки = ТекстОшибки + КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
EndЕсли;
// Записываем ошибку в лог или возвращаем клиенту HTTP-сервиса
ЗаписьЖурналаРегистрации("ПроведениеДокумента", УровеньЖурналаРегистрации.Ошибка,,, ТекстОшибки);
КонецПопытки;
Преимущества метода: нам не нужно переписывать логику сотен документов. Метод работает со стандартным механизмом проведения любых типовых и нетиповых конфигураций.
Иногда требуется передать структурированную ошибку (например, код ошибки или массив битых ссылок) напрямую из модуля объекта обратно в вызывающий код. Для этого можно использовать коллекцию ДополнительныеСвойства объекта документа.
Шаг 1. В модуле документа (в процедуре ОбработкаПроведения):
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
Если ПроверитьОшибкиПроведения() Тогда
Отказ = Истина;
// Записываем структуру ошибки в дополнительные свойства объекта
ПричинаОтказа = Новый Структура;
ПричинаОтказа.Вставить("КодОшибки", "ERR_DUPLICATE_DOC");
ПричинаОтказа.Вставить("Описание", "Найден детальный документ на текущий финансовый год!");
ДополнительныеСвойства.Вставить("РезультатПроведения", ПричинаОтказа);
Возврат;
КонецЕсли;
КонецПроцедуры
Шаг 2. В модуле вызова:
Попытка
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
// Проверяем, записал ли документ свою ошибку в свойства
Если ДокОбъект.ДополнительныеСвойства.Свойство("РезультатПроведения") Тогда
ДанныеОшибки = ДокОбъект.ДополнительныеСвойства.РезультатПроведения;
ВызватьИсключение Шаблон("[%1] %2", ДанныеОшибки.КодОшибки, ДанныеОшибки.Описание);
Иначе
// Резервный вариант, если упало по системной ошибке (например, СУБД)
ВызватьИсключение КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецЕсли;
КонецПопытки;
Важное предупреждение: При падении транзакции некоторые свойства объекта в памяти могут быть сброшены или перезаписаны платформой. Поэтому для гарантированной передачи контекста лучше комбинировать данный метод с первым вариантом.
Если в коде проведения вызывается явное исключение (ВызватьИсключение "Текст ошибки"), стандартная функция ОписаниеОшибки() вернет огромный стек вызовов с указанием модулей и номеров строк. Пользователю или внешней системе (через API) эта техническая информация не нужна.
Давайте посмотрим, как получить только чистое описание ошибки. С версии платформы 8.3 рекомендуется использовать связку функций:
ИнформацияОбОшибке() — возвращает специальный объект с описанием исключения.КраткоеПредставлениеОшибки() — вычленяет из объекта только суть ошибки, убирая технический стек.Посмотрим на пример обработки:
Попытка
ДокОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
Инфо = ИнформацияОбОшибке();
// Получаем чистое описание без путей к модулям и номеров строк
ЧистаяОшибка = КраткоеПредставлениеОшибки(Инфо);
// Дополнительно анализируем первопричину (если было вложенное исключение)
Если Инфо.Причина <> Неопределено Тогда
ЧистаяОшибка = ЧистаяОшибка + " Причина: " + Инфо.Причина.Описание;
КонецЕсли;
КонецПопытки;
Если вы работаете в современной типовой конфигурации (например, 1С:ERP, КА, УТ, УНФ), вам не нужно писать велосипеды для безопасного проведения документов. В составе БСП есть замечательный общий модуль ОбщегоНазначения.
Давайте разберем, как провести документ одной строкой кода с автоматическим сбором всех ошибок:
// Помещаем документ в массив для пакетной обработки
МассивДокументов = Новый Массив;
МассивДокументов.Добавить(СсылкаНаДокумент);
// Вызываем метод БСП, который берет на себя безопасное проведение и сбор ошибок
ТаблицаОшибок = ОбщегоНазначения.ПровестиДокументы(МассивДокументов);
Если ТаблицаОшибок.Количество() > 0 Тогда
Для Каждого СтрокаОшибки Из ТаблицаОшибок Цикл
// Здесь мы гарантированно получим очищенный, структурированный текст ошибок проведения
Сообщить(СтрокаОшибки.ТекстОшибки);
КонецЦикла;
КонецЕсли;
При проектировании интеграционных шлюзов крайне важно отдавать вызывающей системе понятный ответ. Падение 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(РезультатОшибки));
КонецПопытки;
Возврат Ответ;
КонецФункции
Применяя данные подходы, вы сможете создавать надежные и отказоустойчивые интеграционные механизмы, которые будут общаться с внешними системами и разработчиками на понятном, человеческом языке ошибок без лишнего технического мусора.