Как реализовать ввод только одного документа на основании и открывать существующий при повторной попытке?

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

В процессе разработки бизнес-логики в 1С:Предприятие 8.3 часто возникает задача жесткого ограничения связи между объектами. Стандартный механизм «создания на основании» позволяет плодить неограниченное количество подчиненных документов, что может привести к дублированию данных и ошибкам в учете. Рассмотрим, как программно запретить создание второго экземпляра документа и сделать так, чтобы система вместо открытия пустой формы нового документа автоматически перенаправляла пользователя к уже существующему.

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

Метод 1: Подмена объекта в форме при создании

Разберем самый популярный способ, который обсуждался на форуме. Суть заключается в том, что когда форма нового документа начинает инициализироваться на сервере, мы проверяем наличие связи с основанием. Если связь найдена, мы «подкладываем» данные существующего документа в текущую форму.

Для реализации этого метода нам потребуется отредактировать модуль формы создаваемого документа (Документ2). Основная работа будет происходить в обработчике ПриСозданииНаСервере.

Рассмотрим пример кода:


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

&НаСервереБезКонтекста
Функция НайтиДокументНаОсновании(ДокОснование)
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |    Документ2.Ссылка КАК Ссылка
        |ИЗ
        |    Документ.Документ2 КАК Документ2
        |ГДЕ
        |    Документ2.ДокументОснование = &ДокОснование И НЕ Документ2.ПометкаУдаления";
    
    Запрос.УстановитьПараметр("ДокОснование", ДокОснование);
    
    РезультатЗапроса = Запрос.Выполнить();
    Если РезультатЗапроса.Пустой() Тогда
        Возврат Неопределено;
    КонецЕсли;
    
    Выборка = РезультатЗапроса.Выбрать();
    Выборка.Следующий();
    Возврат Выборка.Ссылка;
КонецФункции

Проанализируем ситуацию: использование функции ЗначениеВРеквизитФормы позволяет мгновенно отобразить данные старого документа. Однако у этого метода есть «подводные камни». Например, форма сохраняет состояние «модифицированности», и при закрытии система может спросить пользователя о сохранении изменений, даже если он ничего не правил. Также заголовок окна может продолжать отображать текст «(создание)».

Метод 2: Использование модуля менеджера (Рекомендуемый способ)

Более элегантный и современный подход для управляемого приложения — использование обработчика ОбработкаПолученияФормы в модуле менеджера документа Документ2. Этот метод позволяет перехватить само событие открытия формы еще до того, как она будет создана в памяти.

Разберем по шагам, почему это лучше:

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

Пример реализации в модуле менеджера:


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

Метод 3: Защита на уровне базы данных (Регистр сведений)

Нужно понимать, что проверки в экранных формах защищают только от действий пользователя через интерфейс. Если документ будет создаваться программно (например, фоновым заданием или внешней обработкой), форма не откроется, и проверка не сработает. Чтобы гарантировать уникальность связи на 100%, выясним причину возникновения дублей и применим метод блокировки на уровне СУБД.

Для этого создадим независимый Регистр сведений, например, СвязиДокументов:

В модуле объекта Документ2 в процедуре ПередЗаписью проанализируем ситуацию:


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

Метод 4: Улучшение взаимодействия с пользователем (UX)

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

В обработчике ОбработкаЗаполнения модуля объекта можно выполнить проверку и, если документ найден, вызвать исключение с понятным текстом. Однако это просто прервет операцию. Лучше использовать Оповещения.

Проанализируем сценарий работы через общую команду:

  1. Создаем свою команду «Создать Документ2» вместо стандартной.
  2. В коде команды на клиенте вызываем серверную функцию проверки.
  3. Если документ найден, спрашиваем: «Документ уже существует. Открыть его для редактирования?».
  4. Если пользователь соглашается, используем ПоказатьЗначение или ОткрытьФорму с ключом найденной ссылки.

Пример клиентского кода команды:


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

&НаКлиенте
Процедура ПослеВопросаОбОткрытии(Результат, ДополнительныеПараметры) Экспорт
    Если Результат = КодВозвратаДиалога.Да Тогда
        ПоказатьЗначение(, ДополнительныеПараметры);
    КонецЕсли;
КонецПроцедуры

Подведем итоги

Мы рассмотрели несколько способов решения задачи. Если вам нужно быстрое решение для интерфейса — используйте ОбработкаПолученияФормы в модуле менеджера. Если требуется обеспечить железную уникальность данных, которую нельзя обойти программно — добавьте проверку в модуле объекта и используйте вспомогательный регистр сведений. Сочетание этих методов обеспечит стабильную работу системы и отсутствие «мусора» в базе данных 1С.

← На главную