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