Как программно записать или провести документ из формы без ошибки «Данные были изменены другим пользователем»

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

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

Выясняем причину возникновения ошибки

Для начала проанализируем ситуацию, описанную в вопросе. Программист пытается выполнить следующий код:


Объект.Реализация = НовДок.Ссылка;
Объект.Ссылка.ПолучитьОбъект().Записать();

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

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

Решение №1: Использование метода Записать() управляемой формы

Это самый правильный и рекомендуемый способ. Вместо того чтобы пытаться работать с «объектом-копией», мы воспользуемся встроенным методом расширения управляемой формы. Рассмотрим подробнее, как это реализовать.

Метод формы Записать() выполняет запись данных, которые находятся в основном реквизите формы, синхронизируя их с базой данных и обновляя версию объекта в самой форме. Если нам нужно не просто сохранить изменения, а именно провести документ, мы должны использовать параметры записи.

Посмотрим на пример кода, который можно разместить в клиентской процедуре формы:


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

В данном случае платформа сама позаботится о том, чтобы данные из формы попали на сервер, превратились в объект, записались и вернулись обратно в форму с обновленным номером версии и статусом проведения. Ошибка конфликта версий здесь исключена.

Решение №2: Использование связки ЗначениеВРеквизитФормы

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

Проанализируем алгоритм действий на сервере:

  1. Конвертируем ДанныеФормыСтруктура (наш реквизит Объект) в реальный ДокументОбъект с помощью функции РеквизитФормыВЗначение.
  2. Вносим необходимые изменения в полученный объект.
  3. Вызываем метод Записать(РежимЗаписиДокумента.Проведение).
  4. Важный шаг: После записи обновляем данные формы из измененного объекта с помощью ЗначениеВРеквизитФормы.

Посмотрим на пример реализации:


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

Решение №3: Метод Прочитать() для принудительного обновления

Если по какой-то причине вы всё же изменили объект в базе данных «со стороны» (например, через фоновое задание или вызвав ПолучитьОбъект().Записать() в процедуре, не связанной с формой), и форма теперь выдает ошибку при попытке закрытия или записи, вы можете принудительно обновить данные в форме.

Для этого используется метод Прочитать(). Он перечитывает данные из базы и сбрасывает признак модифицированности формы. Рассмотрим ситуацию:


&НаСервере
Процедура ОбновитьДанныеФормыИзБазы()
    // Этот метод обновит реквизит Объект актуальными данными из БД
    ЭтотОбъект.Прочитать();
КонецПроцедуры

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

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

Подводя итог, разберем основные правила программной работы с документами в контексте формы:

Соблюдение этих архитектурных правил платформы 1С:Предприятие 8.3 позволит вам создавать стабильный и отзывчивый интерфейс без досадных ошибок блокировок и потерь данных пользователей.

← На главную