Как запретить повторное создание документа Заказ поставщику, если он уже был создан на основании?

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

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

Почему стандартные методы не всегда подходят?

Рассмотрим сначала типичную ошибку начинающих разработчиков. Часто пытаются использовать стандартное событие ОбработкаПроверкиЗаполнения. Однако это системное событие предназначено для проверки заполненности конкретных реквизитов (проверка на пустое значение), а не для выполнения сложной бизнес-логики с запросами к базе данных. Проверка существования другого документа — это внешнее условие, которое не относится к структуре самого создаваемого объекта.

Также неэффективным является решение через событие ПриИзменении в форме. Если мы будем просто очищать поле заказа при его заполнении, пользователь сможет нажать кнопку создания снова, так как поле станет пустым. Проанализируем более надежные подходы.

Вариант 1. Проверка существования документа с помощью запроса

Наиболее правильным и надежным методом является выполнение запроса к базе данных перед созданием нового объекта. Нам нужно проверить, есть ли в системе документы нужного типа, у которых реквизит ДокументОснование ссылается на наш текущий документ. Разберем по шагам, как это реализовать в коде.

Допустим, мы создаем ЗаказПоставщику из формы документа ЗаказКлиента. Мы можем написать процедуру, которая будет вызываться при нажатии кнопки создания:


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

Обратите внимание на важное условие в запросе: И НЕ ЗаказПоставщику.ПометкаУдаления. Если документ был создан, но затем помечен на удаление, система не должна блокировать создание нового корректного заказа. Выясним причину, почему это критично: если пользователь совершил ошибку и удалил старый заказ, он должен иметь возможность создать новый.

Вариант 2. Реализация логики в момент открытия формы нового документа

Иногда удобнее выполнять проверку непосредственно при попытке открыть форму нового (создаваемого) документа. В этом случае мы используем обработчик ПриСозданииНаСервере в модуле формы Заказа поставщику. Если проверка не проходит, мы устанавливаем параметр Отказ = Истина.

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


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

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

КонецПроцедуры

При таком подходе пользователь даже не увидит пустую форму документа, если заказ уже существует. Однако стоит помнить, что СообщениеПользователю внутри ПриСозданииНаСервере с параметром Отказ = Истина может не отобразиться корректно, если форма закрывается сразу. В таких случаях сообщение лучше выводить во "владельца" формы или использовать оповещения.

Усложненная ситуация: Многопользовательская среда и блокировки

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

Перед тем как выполнять проверку запросом, нужно заблокировать документ-основание. Посмотрим, как это выглядит в коде:


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.ЗаказКлиента");
ЭлементБлокировки.УстановитьПараметр("Ссылка", СсылкаНаЗаказКлиента);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();

// Теперь выполняем запрос...
// Пока мы не закончим, другой сеанс будет ждать на методе Заблокировать()

Использование механизма блокировок гарантирует, что в один момент времени только один процесс может проверять и создавать подчиненный документ для конкретного основания.

Гибкая проверка: Частичные заказы

Бывают случаи, когда на один Заказ клиента можно создать несколько Заказов поставщикам (например, если товары заказываются у разных поставщиков по частям). В такой ситуации проверка на "просто наличие хотя бы одного документа" не сработает.

В этом случае вместо простого поиска ссылки в таблице документов, мы должны проанализировать остатки по регистрам накопления. Например, если в системе есть регистр ЗаказыКПоступлению, мы сравниваем количество товара в заказе-основании и количество уже заказанного товара у поставщиков. Если разница равна нулю — только тогда запрещаем создание нового документа.

Улучшение пользовательского интерфейса (UX)

Вместо того чтобы просто выдавать ошибку "Заказ уже создан", хорошим тоном считается предложить пользователю открыть существующий документ. Рассмотрим, как это можно сделать:

  1. При нажатии кнопки "Создать" сначала ищем существующий документ.
  2. Если нашли — выводим вопрос пользователю: "Заказ уже существует. Открыть его?".
  3. Если пользователь соглашается — открываем форму существующего документа через ПоказатьЗначение().

Такой подход делает работу в программе более комфортной и интуитивно понятной, избавляя пользователя от лишних действий по поиску документов в списках.

← На главную