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

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

В разработке на платформе «1С:Предприятие 8» (особенно в управляемом приложении) часто возникает классическая задача: запретить повторный ввод документа на основании одного и того же источника. Например, для одного документа-основания разрешено создавать только один подчиненный документ определенного типа. Если пользователь пытается ввести второй подчиненный документ, система должна не просто выдать ошибку, а сразу открыть форму уже существующего документа. Это значительно повышает удобство работы (UX) и защищает базу данных от дублирования информации.

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

В чем сложность стандартного механизма?

Когда пользователь нажимает кнопку «Создать на основании», платформа 1С выполняет следующие шаги:

  1. Инициализирует в памяти сервера новый (еще не записанный) объект документа.
  2. Вызывает процедуру ОбработкаЗаполнения в модуле этого объекта.
  3. Передает созданный объект на клиент и пытается открыть его форму по умолчанию.

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

Поэтому правильное решение задачи должно лежать на стыке клиентской логики (для удобства пользователя) и серверной защиты данных.

Решение 1. Перенаправление в обработчике управляемой формы «ПриОткрытии» (Рекомендуемый UX-подход)

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

Если дубликат найден, мы устанавливаем параметр Отказ = Истина в процедуре ПриОткрытии (это мгновенно и безболезненно для пользователя отменяет вывод формы создаваемого дубля на экран) и сразу же программно открываем форму существующего документа.

Рассмотрим по шагам реализацию этого механизма в модуле формы нового документа-приемника:


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

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

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

Решение 2. Перенос проверки «на шаг назад» (Использование собственной команды)

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

Если мы хотим сэкономить ресурсы сервера, мы можем перенести проверку на уровень документа-источника:

  1. Отключим стандартную команду создания на основании для нашего документа-приемника.
  2. Создадим собственную общую команду или команду документа-источника (например, «Создать [ИмяДокумента]»).
  3. В обработчике команды на клиенте сначала вызовем серверную функцию проверки наличия созданного ранее подчиненного документа.
  4. Если документ найден — сразу откроем его форму через ОткрытьФорму с параметром Ключ.
  5. Если документа нет — откроем форму создания нового документа, передав в параметрах структуру заполнения.

Рассмотрим пример модуля такой команды:


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

Решение 3. Использование механизмов БСП (Библиотека стандартных подсистем)

Если ваша конфигурация разработана на основе БСП (как большинство типовых конфигураций 1С: БП 3.0, УТ 11, ERP, ЗУП), то для ввода на основании обычно применяется механизм подсистемы «Подключаемые команды».

В этом случае разработчику не обязательно создавать команду вручную. Достаточно переопределить поведение команды в модуле менеджера документа-приемника в процедуре ПриОпределенииКомандПодключения. БСП позволяет задать для подключаемой команды собственный клиентский обработчик, в котором прописывается аналогичная логика предварительной проверки существования документа.

Решение 4. Разделение логики: интерфейс (UX) против безопасности базы данных

Важно понимать, что проверка в форме (Решение 1) отлично работает для интерактивных действий пользователя. Однако формы не создаются и не открываются, если другой программист попытается создать ваш документ программно (например, через регламентное задание, интеграцию, загрузку из XML/JSON или Web-сервис).

Например, следующий программный код обойдет все проверки в форме:


НовыйДокумент = Документы.ИмяВашегоДокумента.СоздатьДокумент();
НовыйДокумент.Заполнить(ДокументОснование);
НовыйДокумент.Записать(); // Здесь форма не открывается, и дубль запишется!

Чтобы гарантировать целостность данных на уровне СУБД, необходимо дублировать проверку в модуле объекта в обработчике события ПередЗаписью. При этом важно отключать проверку в режиме загрузки данных, чтобы не нарушать обмен данными.

Реализуем жесткую блокировку на уровне модуля объекта документа-приемника:


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

Резюме и лучшие практики

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

← На главную