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