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