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

Программист 1С v8.3 (Обычные формы) Управленческий учет
← На главную

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

Анализ проблемы: почему возникают дубли?

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

Решение №1: Проверка существования документа через запрос

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


// Формируем уникальную строку содержания для поиска
НомерДоговора = ?(Организация = Справочники.Организации.ОсновнаяОрганизация, "ВПА-", Организация.Префикс + "-") 
               + Прав(Номер, 5) + "-" + Формат(Дата, "ДФ=yy") + " от " + Формат(Дата, "ДФ=dd.MM.yyyy");
СтрокаДляПоиска = "Платный ДКП договор №" + НомерДоговора;

ЗапросПроверки = Новый Запрос;
ЗапросПроверки.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |    РеализацияТоваровТовары.Ссылка КАК Ссылка
    |ИЗ
    |    Документ.РеализацияТоваров.Товары КАК РеализацияТоваровТовары
    |ГДЕ
    |    РеализацияТоваровТовары.Содержание = &Содержание
    |    И РеализацияТоваровТовары.Ссылка.Контрагент = &Контрагент
    |    И РеализацияТоваровТовары.Ссылка.Проведен = ИСТИНА";

ЗапросПроверки.УстановитьПараметр("Содержание", СтрокаДляПоиска);
ЗапросПроверки.УстановитьПараметр("Контрагент", Контрагент);

РезультатПроверки = ЗапросПроверки.Выполнить();

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

В данном примере мы используем поиск по табличной части Товары и реквизиту Содержание. Это решение работает, но оно не является идеальным, так как поиск по строке — операция медленная и ненадежная (пользователь может случайно изменить строку в документе, и проверка перестанет работать).

Решение №2: Использование связей между документами (Реквизит-основание)

Более профессиональный подход заключается в добавлении в документ-источник (например, в Заказ) специального реквизита, в котором будет храниться ссылка на созданную реализацию. Разберем этот метод по шагам:

  1. Добавим в документ, из которого вызывается создание ДКП, реквизит ДокументОформленияДКП с типом ДокументСсылка.РеализацияТоваров.
  2. В начале процедуры создания проверим: Если ЗначениеЗаполнено(Объект.ДокументОформленияДКП) Тогда....
  3. Если реквизит заполнен, вместо создания нового документа мы можем просто открыть форму уже существующего.
  4. Если не заполнен — создаем документ и записываем ссылку на него обратно в документ-источник.

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

Решение №3: Защита на уровне интерфейса (Блокировка кнопки)

Чтобы физически не дать пользователю нажать кнопку 10 раз подряд, мы должны управлять доступностью элементов формы. Проанализируем, как это реализовать в коде обработчика:


&НаКлиенте
Процедура СформироватьДКП(Команда)
    
    // Блокируем кнопку сразу после нажатия
    Элементы.СформироватьДКП.Доступность = Ложь;
    
    Попытка
        СформироватьДокументыНаСервере();
        Сообщить("Пакет документов успешно сформирован.");
    Исключение
        // В случае ошибки возвращаем доступность кнопки
        Элементы.СформироватьДКП.Доступность = Истина;
        ВызватьИсключение; // Пробрасываем ошибку дальше для системы
    КонецПопытки;
    
КонецПроцедуры

Такой подход исключает "дребезг" нажатий на стороне клиента, пока сервер обрабатывает запрос.

Решение №4: Обеспечение атомарности операции через транзакции

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


НачатьТранзакцию();

Попытка
    // 1. Создание Реализации
    ДокРеализация = Документы.РеализацияТоваров.СоздатьДокумент();
    // ... заполнение реквизитов ...
    ДокРеализация.Записать(РежимЗаписиДокумента.Проведение);

    // 2. Создание Счета на оплату на основании реализации
    ДокСчет = Документы.СчетНаОплату.СоздатьДокумент();
    ДокСчет.Заполнить(ДокРеализация.Ссылка); 
    ДокСчет.Записать(РежимЗаписиДокумента.Запись);

    // Если всё прошло успешно — фиксируем изменения в БД
    ЗафиксироватьТранзакцию();
Исключение
    // Если на любом этапе возникла ошибка — откатываем всё назад
    ОтменитьТранзакцию();
    Сообщить("Ошибка при создании пакета документов: " + ОписаниеОшибки());
КонецПопытки;

Критический анализ ошибок в исходном коде

Проанализируем фрагмент кода автора: СпрNom = Справочники.Номенклатура.НайтиПоКоду("ЦБАА0003041");. Это пример «жесткого кодирования» (hardcode), который крайне не рекомендуется использовать по следующим причинам:

Как правильно: Необходимо создать Предопределенный элемент в конфигураторе или использовать Регистр сведений для хранения настроек. Если это невозможно, следует хотя бы проверять результат поиска:


СпрНом = Справочники.Номенклатура.НайтиПоКоду("ЦБАА0003041");
Если СпрНом.Пустая() Тогда
    Сообщить("Критическая ошибка: Номенклатура для оформления ДКП не найдена в справочнике!");
    Возврат;
КонецЕсли;

Информирование пользователя

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


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

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

← На главную