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