В практике разработки на платформе 1С часто возникает задача: если документ не проходит определенные проверки (например, по лимитам отгрузки или наличию товара) и мы отменяем его запись через Отказ = Истина, нам всё равно нужно зафиксировать этот факт в базе данных. Проблема заключается в том, что стандартные процедуры ПередЗаписью и ПриЗаписи модуля объекта выполняются внутри неявной транзакции. Если транзакция откатывается, все изменения, сделанные в ней (включая записи в независимые регистры сведений), также бесследно исчезают из базы данных.
Рассмотрим подробнее, как обойти это ограничение платформы и реализовать надежное логирование отказов, которое будет работать как при интерактивной работе пользователя, так и при программном создании документов.
Это один из самых популярных и технически изящных способов. Когда мы запускаем фоновое задание, оно выполняется в отдельном сеансе на сервере 1С. У этого сеанса своя собственная транзакция, которая никак не связана с транзакцией основного процесса, вызвавшего Отказ = Истина.
Проанализируем алгоритм реализации: в процедуре ПриЗаписи (или ОбработкаПроведения), когда мы понимаем, что условия не выполнены, мы подготавливаем структуру с данными для логирования и передаем её в фоновое задание.
Пример кода для модуля объекта:
Процедура ПриЗаписи(Отказ)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Проверяем наше условие (например, превышение веса доставки)
Если ВесДоставки > МаксимальныйВес Тогда
// Подготавливаем данные для записи в регистр
ПараметрыЛога = Новый Структура;
ПараметрыЛога.Вставить("ДокументСсылка", ЭтотОбъект.Ссылка);
ПараметрыЛога.Вставить("Причина", "Превышение веса: " + Строка(ВесДоставки));
ПараметрыЛога.Вставить("Период", ТекущаяДата());
// Запускаем фоновое задание
ФоновыеЗадания.Выполнить("МойОбщийМодуль.ЗаписатьОтказВРегистр", ПараметрыЛога);
// Отменяем запись документа
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Заказ не может быть принят: превышен лимит веса!";
Сообщение.Сообщить();
Отказ = Истина;
КонецЕсли;
КонецПроцедуры
Важно помнить: фоновое задание не увидит изменений документа, которые еще не зафиксированы в базе. Поэтому все необходимые данные (номер, сумма, причина) нужно передавать в параметрах ПараметрыЛога. В общем модуле МойОбщийМодуль процедура ЗаписатьОтказВРегистр должна быть экспортной и выполнять обычную запись в независимый регистр сведений.
Если нам нужна максимальная производительность и мы не хотим плодить фоновые задания на каждый чих, стоит рассмотреть Журнал регистрации. Записи в Журнал регистрации (метод ЗаписатьВЖурналРегистрации) не входят в транзакцию базы данных. Это означает, что даже если транзакция полностью откатилась, запись в журнале останется.
Разберем по шагам, как превратить это в полноценный отчет:
ЗаписатьВЖурналРегистрации, передавая в параметре "Данные" структуру или строку JSON с деталями ошибки.Этот метод гарантирует 100% сохранение информации даже при критических сбоях сервера БД, так как Журнал регистрации — это файлы на диске сервера 1С (или отдельная таблица, управляемая самим процессом 1С, а не транзакцией документа).
Для высоконагруженных систем или случаев, когда нужна мгновенная фиксация данных в регистре (без ожидания регламентного задания), можно использовать механизм HTTP-сервисов. Мы создаем в нашей базе простой HTTP-сервис, который принимает данные об отказе и записывает их в регистр.
В коде документа мы выполняем HTTP-запрос к своей же базе. Поскольку HTTP-запрос — это новый сеанс, он создаст свою транзакцию, зафиксирует запись в регистре и вернет управление основному коду, который затем спокойно выполнит Отказ = Истина.
Преимущества этого подхода:
Иногда лучшим техническим решением является изменение самой логики бизнес-процесса. Вместо того чтобы использовать Отказ = Истина, мы можем позволить документу записаться всегда, но менять его состояние.
Рассмотрим этот подход подробнее:
Статус (например, перечисление: Черновик, КПроверке, ОшибкаПроверки, Проведен).ПередЗаписью вместо установки Отказ = Истина мы меняем РежимЗаписи на Запись (отменяя проведение) и устанавливаем статус ОшибкаПроверки.Проанализируем ситуацию: документ физически присутствует в базе, информация о том, почему он "не влез" в доставку, сохранена в его реквизитах. При этом движений по регистрам накопления (складам, взаиморасчетам) нет, так как проведение не состоялось. Это избавляет нас от любых проблем с транзакциями, так как сама транзакция успешно завершается.
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
Если Не ПроверитьЛимитыДоставки() Тогда
// Вместо Отказ = Истина
РежимЗаписи = РежимЗаписиДокумента.Запись;
ЭтотОбъект.Статус = Перечисления.СтатусыЗаказов.ОшибкаВалидации;
ЭтотОбъект.ПричинаОтказа = "Превышение веса!";
// Выводим предупреждение
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Заказ записан как ошибочный!");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Для SQL-версий 1С существует "хакерский", но эффективный метод. Можно открыть новое соединение с базой данных через ADO (ActiveX Data Objects) и выполнить прямой SQL-запрос INSERT в таблицу нужного регистра сведений. Поскольку это соединение устанавливается напрямую к SQL-серверу мимо менеджера транзакций 1С, данные будут зафиксированы моментально.
Однако мы должны использовать этот метод с большой осторожностью: нужно точно знать имя таблицы в SQL (оно может измениться после реструктуризации) и соблюдать структуру полей (UUID и прочее). В современных версиях платформы 8.3 для этих целей лучше использовать механизм ВнешниеИсточникиДанных, настроенный на ту же самую базу.
Выясним причину, по которой стоит выбрать тот или иной метод:
Каждый из этих методов позволяет обойти ограничение транзакционности и обеспечить бизнес-пользователей необходимой аналитикой по отказам в системе.