В разработке на платформе 1С:Предприятие 8.3 часто возникает ситуация, когда при попытке проведения документа в серверной процедуре ОбработкаПроведения параметр Отказ устанавливается в значение Истина. Это штатное поведение системы, однако оно влечет за собой определенную сложность для интерфейсной логики: стандартные обработчики событий формы, такие как ПослеЗаписи, попросту не вызываются. Если нам необходимо вывести пользователю уточняющий вопрос, открыть специальную форму или выполнить иную логику именно после неудачного проведения, мы сталкиваемся с тем, что «чистого» стандартного события для этого не существует. В данной статье мы подробно разберем несколько профессиональных подходов к решению этой задачи.
Для начала проанализируем ситуацию: почему стандартные методы не подходят? Когда пользователь нажимает кнопку «Провести», платформа инициирует транзакцию записи. Если на сервере происходит отказ от проведения, транзакция откатывается. С точки зрения клиентского приложения, цепочка событий обрывается. Событие ПослеЗаписи на клиенте срабатывает только в случае успешного завершения транзакции в базе данных. Таким образом, если документ не провелся, управление возвращается в форму, но контекст выполнения ПослеЗаписи игнорируется. Нам необходимо найти способ «вклиниться» в этот процесс или отследить его завершение со стороны клиента.
Если вы инициируете проведение не через стандартную кнопку командной панели, а через собственную команду или переопределенный алгоритм, решение задачи становится тривиальным. Рассмотрим этот вариант подробнее. Метод управляемой формы Записать() возвращает булево значение: Истина, если запись (и проведение) прошла успешно, и Ложь, если возникли ошибки или Отказ был установлен в значение Истина.
Посмотрим на пример реализации такого кода в модуле формы:
&НаКлиенте
Процедура МояКомандаПроведения(Команда)
ПараметрыЗаписи = Новый Структура;
ПараметрыЗаписи.Вставить("РежимЗаписи", РежимЗаписиДокумента.Проведение);
// Метод возвращает Ложь, если проведение не удалось
Если НЕ Записать(ПараметрыЗаписи) Тогда
// Здесь мы находимся в контексте клиента после неудачной попытки
ТекстВопроса = "Документ не удалось провести. Желаете открыть помощник исправления ошибок?";
Оповещение = Новый ОписаниеОповещения("ПослеОтветаНаВопрос", ЭтотОбъект);
ПоказатьВопрос(Оповещение, ТекстВопроса, РежимДиалогаВопрос.ДаНет);
КонецЕсли;
КонецПроцедуры
Этот способ является наиболее надежным и «чистым», так как мы явно контролируем результат выполнения операции. Однако он требует замены стандартных кнопок формы на пользовательские.
Разберем более сложную, но универсальную ситуацию, когда нам нужно отловить отказ при нажатии стандартных кнопок (например, системной кнопки «Провести»). В крупных типовых решениях, таких как 1С:ERP или 1С:КА, применяется механизм с использованием метода ПодключитьОбработчикОжидания. Суть метода заключается в том, что мы устанавливаем «флаг» начала проведения и запускаем процедуру, которая проверит результат через ничтожно малый промежуток времени.
Выясним причину эффективности этого метода: обработчик ожидания срабатывает только тогда, когда основной поток выполнения (стек вызовов записи) полностью завершен. Это позволяет нам выйти из контекста транзакции.
Шаги реализации:
ПопыткаПроведенияАктивна.ПередЗаписью на клиенте установим этот флаг и подключим обработчик.ПослеЗаписи на клиенте сбросим этот флаг.Истина, значит, ПослеЗаписи не наступило, и проведение не удалось.Проанализируем пример кода:
&НаКлиенте
Перем ПопыткаПроведенияАктивна;
&НаКлиенте
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
Если ПараметрыЗаписи.РежимЗаписи = РежимЗаписиДокумента.Проведение Тогда
ПопыткаПроведенияАктивна = Истина;
// Подключаем однократный обработчик на 0.1 секунды
ПодключитьОбработчикОжидания("ПроверитьРезультатПроведения", 0.1, Истина);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
// Если мы попали сюда, значит проведение успешно
ПопыткаПроведенияАктивна = Ложь;
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьРезультатПроведения()
Если ПопыткаПроведенияАктивна Тогда
// Сюда мы попадем только если ПослеЗаписи не сработало
ПопыткаПроведенияАктивна = Ложь;
// Выполняем нужную логику: вызов вопроса или открытие формы
Сообщить("Внимание: Проведение было отменено. Выполняю анализ причин...");
// ОткрытьФорму("Справочник.ПричиныОшибок.ФормаСписка");
КонецЕсли;
КонецПроцедуры
В современных конфигурациях на платформе 8.3 рекомендуется использовать асинхронные вызовы. Если мы используем метод НачатьЗапись(), система позволяет передать описание оповещения, которое будет выполнено в любом случае после завершения операции, независимо от того, успешно она прошла или нет.
Разберем по шагам:
&НаКлиенте
Процедура ВыполнитьЗаписьАсинхронно()
ПараметрыЗаписи = Новый Структура("РежимЗаписи", РежимЗаписиДокумента.Проведение);
Оповещение = Новый ОписаниеОповещения("ЗаписьЗавершена", ЭтотОбъект);
// Начинаем асинхронную запись
НачатьЗапись(Оповещение, ПараметрыЗаписи);
КонецПроцедуры
&НаКлиенте
Процедура ЗаписьЗавершена(Результат, ДополнительныеПараметры) Экспорт
// Если документ не записан (или не проведен), Результат будет Ложь
Если НЕ Результат Тогда
ПоказатьПредупреждение(, "Проведение не удалось! Проверьте заполнение данных.");
КонецЕсли;
КонецПроцедуры
Рассмотрим ситуацию, когда пользователь нажимает «Провести и закрыть». Если проведение завершается с ошибкой, форма документа остается открытой. Это дает нам возможность использовать Способ №2 (обработчик ожидания) без каких-либо изменений. Флаг ПопыткаПроведенияАктивна останется в значении Истина, так как форма не закрылась и событие ПослеЗаписи не наступило. Это позволяет корректно обрабатывать логику даже в случае попытки закрытия формы.
Иногда недостаточно просто знать, что проведение не удалось — нужно передать специфическую причину отказа с сервера на клиент для отображения в особом окне. Несмотря на то, что транзакция откатывается, мы можем использовать сообщения пользователю. Рассмотрим, как это работает: в процедуре ОбработкаПроведения на сервере мы используем метод ОбщегоНазначения.СообщитьОшибку() или стандартный Сообщить(). На клиенте в обработчике ожидания (из Способа 2) мы можем проанализировать массив сообщений, которые были выведены пользователю, используя метод ПолучитьСообщенияПользователю().
&НаКлиенте
Процедура ПроверитьРезультатПроведения()
Если ПопыткаПроведенияАктивна Тогда
ПопыткаПроведенияАктивна = Ложь;
МассивСообщений = ПолучитьСообщенияПользователю(Истина);
Для Каждого Сообщение Из МассивСообщений Цикл
// Анализируем текст ошибок, чтобы понять, какое окно открыть
Если Найти(Сообщение.Текст, "Недостаточно остатка") > 0 Тогда
// Открываем форму подбора или остатков
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Подводя итог нашему анализу, можно выделить следующие рекомендации:
Записать() или асинхронный метод НачатьЗапись(). Это самый надежный программный путь.ПередЗаписью и ПодключитьОбработчикОжидания. Этот метод позволяет «вырваться» из стека вызовов транзакции и безопасно открыть любое модальное окно или форму.ПослеЗаписи, чтобы избежать ложных срабатываний при последующих действиях пользователя.Мы рассмотрели основные технические приемы, которые позволяют обойти ограничения платформы при обработке отказов проведения. Выбор конкретного метода зависит от ваших задач по кастомизации интерфейса и используемой версии конфигурации.