Как запросить данные у пользователя при проведении документа, не открывая форму из подписки на событие?

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

При разработке прикладных решений в 1С разработчики нередко сталкиваются с необходимостью уточнить у пользователя дополнительные сведения непосредственно перед проведением документа. Типичной ошибкой начинающих специалистов становится попытка вызвать интерфейсные методы, например ОткрытьФормуМодально или ОткрытьФорму, прямо из обработчика подписки на событие объекта (такого как ОбработкаПроведения или ПередЗаписью). Разберем, почему такая реализация принципиально невозможна на уровне платформы, к каким последствиям она приводит и как правильно решить данную задачу без снятия типовой конфигурации с поддержки.

Архитектурные ограничения клиент-серверного взаимодействия

Выясним причину, по которой попытка открыть форму из подписки на событие приводит к ошибке:

  1. Разделение контекстов исполнения. Все подписки на события объектов (ДокументОбъект, СправочникОбъект и др.) выполняются строго на стороне сервера. В серверном контексте графический интерфейс отсутствует в принципе — у сервера «нет экрана», поэтому вызов любых клиентских методов для отображения окон не поддерживается синтаксисом платформы и вызывает исключение.
  2. Блокировки и открытая транзакция. Обработка проведения и события записи объекта всегда выполняются внутри системной транзакции базы данных. Если бы платформе удалось приостановить транзакцию в ожидании действия человека, база данных оставалась бы заблокированной. Это неизбежно приведет к превышению времени ожидания блокировок другими пользователями и взаимным блокировкам (deadlocks).
  3. Фоновое и групповое перепроведение. Документы могут перепроводиться не только вручную из формы, но и программно — например, при групповом перепроведении за период регламентным заданием ночью или в рамках обмена данными. В таких сценариях интерактивный вопрос задать некому.
  4. Запрет модальности. В управляемом приложении по умолчанию включен режим запрета модальных окон, поэтому метод ОткрытьФормуМодально заблокирован на уровне конфигурации. Любой интерфейсный диалог должен выполняться строго асинхронно через ОписаниеОповещения.

Правильное архитектурное решение: перенос логики на клиента

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

Разберем по шагам весь процесс реализации через расширение.

Шаг 1. Перехват события формы в расширении

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

Посмотрим на пример клиентского кода формы документа в расширении:


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

Шаг 2. Обработка результата выбора и повторный запуск записи

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


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

Шаг 3. Передача значений в подписку на событие через ДополнительныеСвойства

Для передачи собранных на клиенте данных в серверный обработчик проведения задействуем коллекцию ДополнительныеСвойства объекта документа. Заполним ее в обработчике формы ПередЗаписьюНаСервере:


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

Шаг 4. Обработка данных в подписке на событие

Теперь в процедуре-обработчике подписки на событие (например, ОбработкаПроведения или ПриЗаписи) извлекаем переданные данные из параметра Источник и безопасно записываем их в целевой регистр сведений внутри общей транзакции документа:


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

Альтернативный подход: добавление реквизитов в документ

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

В этом случае:

  1. Пользователь заполняет аналитику непосредственно в процессе редактирования документа.
  2. Стандартная проверка заполнения платформы предотвратит проведение, если поля пусты, без написания громоздкого кода асинхронных оповещений.
  3. В подписке на событие проведения значения будут доступны напрямую через свойства объекта Источник.Сертификат и Источник.МестоВыдачи.

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

← На главную