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