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