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