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