Как предотвратить одновременное создание нескольких документов на основании одного объекта?

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

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

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

Проблема пессимистической блокировки для новых объектов

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

Проанализируем ситуацию: пользователь А нажимает «Создать», открывается пустая форма. В этот же момент пользователь Б нажимает «Создать», и у него также открывается форма. Пока ни один из них не нажал «Записать», система считает их действия независимыми. Чтобы этого избежать, нам нужно реализовать один из следующих механизмов.

Решение 1. Проверка существования документа перед открытием формы

Самый простой и логичный шаг — проверить, не был ли документ создан ранее, прямо в момент нажатия кнопки создания. Если документ уже существует, мы должны не создавать новый, а открывать существующий.

Разберем пример кода, который можно разместить в обработчике команды создания на форме рабочего стола:


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

Однако этого недостаточно. Если два пользователя нажмут кнопку с разницей в миллисекунду, проверка у обоих вернет «Ложь», и формы создания все равно откроются. Перейдем к более надежным методам.

Решение 2. Блокировка «Объекта-основания»

Поскольку у нового документа еще нет ссылки, мы можем временно «занять» документ-основание (в нашем случае — «Предварительную запись»). Пока один пользователь работает с формой создания «Приема», объект «Предварительная запись» должен быть заблокирован для изменения другими пользователями.

Рассмотрим реализацию через попытку блокировки:

  1. При открытии формы нового документа «Прием» вызываем серверную процедуру.
  2. В этой процедуре пытаемся программно заблокировать объект-основание с помощью метода ЗаблокироватьДанныеДляРедактирования.
  3. Если блокировка не удалась — значит, кто-то другой уже создает документ или редактирует основание.

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

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

Решение 3. Использование регистра «Блокируемые объекты»

Если блокировка объекта-основания по каким-то причинам не подходит (например, другим пользователям все равно нужно иметь возможность редактировать другие поля предварительной записи), можно использовать вспомогательный Регистр сведений.

Алгоритм работы следующий:

  1. Создаем непериодический регистр сведений БлокировкиСозданияДокументов с измерением ОбъектОснование и ресурсами Пользователь, ВремяБлокировки.
  2. При попытке создания документа проверяем наличие записи в этом регистре.
  3. Если записи нет — создаем её и открываем форму.
  4. Если запись есть — выводим сообщение: «Пользователь Иван Иванов уже начал оформление по этой записи в 10:45».
  5. При закрытии формы или успешной записи документа — удаляем запись из регистра.

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

Решение 4. Финальный рубеж: Проверка в «ПередЗаписью»

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

Рассмотрим, как это сделать правильно с использованием механизма БлокировкаДанных (DataLock) для исключения ситуации «гонки»:


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

Резюме и рекомендации

Для достижения 100% результата мы рекомендуем комбинировать подходы:

Использование БлокировкаДанных внутри транзакции является наиболее профессиональным и надежным методом решения проблемы «одновременного входа», так как он работает на уровне СУБД и гарантирует, что между проверкой «есть ли документ» и самой записью никто другой не успеет вклиниться.

← На главную