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