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

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

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

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

Механизм оптимистической блокировки и свойство ВерсияДанных

Для предотвращения затирания чужих изменений в платформе 1С реализован механизм оптимистической блокировки. Рассмотрим, как он работает:

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

Почему ошибка в разных документах проявляется по-разному?

Многих разработчиков сбивает с толку то, что на некоторых формах удается изменить несколько значений перед падением. Выясним причину такого поведения:

  1. Тип редактируемого реквизита на форме. Если пользователь меняет значение реквизита, который принадлежит непосредственно форме (а не объекту базы через путь Объект.ИмяРеквизита), платформа не инициирует проверку версии основного объекта базы данных.
  2. Отсутствие серверных вызовов на клиенте. Платформа не выполняет постоянных фоновых запросов к базе при простом вводе текста или выборе значений в полях. Пока редактирование происходит исключительно в оперативной памяти клиента, несовпадение версий не фиксируется.
  3. Наличие обработчиков событий ПриИзменении. Если у элемента управления назначен серверный обработчик ПриИзменении, контекстный вызов к серверу передает форму целиком. Сервер сверяет версии объекта, и ошибка срабатывает немедленно. Если же обработчика нет (или он чисто клиентский), пользователь может заполнить несколько полей, пока не нажмет кнопку записи или элемент с серверным действием.

Способ 1. Синхронизация формы через оповещение и метод Прочитать()

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

Для этого в форме печати после выполнения записи вызываем глобальный метод Оповестить():


// В форме печати после записи объекта в базе данных
ПараметрыОповещения = Новый Структура("Ссылка", ДокументСсылка);
Оповестить("ОбновленДокументПослеПечати", ПараметрыОповещения, ЭтаФорма);

В модуле формы самого документа переопределяем обработчик ОбработкаОповещения. В нем проверяем соответствие ссылки и вызываем перечитывание данных:


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

КонецПроцедуры

&НаКлиенте
Процедура ПослеОтветаНаВопросОПеречитывании(Результат, ДополнительныеПараметры) Экспорт
    Если Результат = КодВозвратаДиалога.Да Тогда
        ЭтаФорма.Прочитать();
    КонецЕсли;
КонецПроцедуры

Важный нюанс: метод ОповеститьОбИзменении() обновляет только динамические списки (журналы и формы списков). Для открытой формы объекта всегда требуется явный вызов метода ЭтаФорма.Прочитать().

Способ 2. Архитектурное решение: Дополнительные сведения вместо записи объекта

Проанализируем природу сохраняемых при печати данных. В типовых конфигурациях 1С (ERP, КА, УТ, УНФ) механизмы расширения свойств делятся на две категории:

  1. Дополнительные реквизиты: хранятся в табличной части самого объекта. Любое изменение требует вызова Объект.Записать(), увеличивает ВерсиюДанных и сбрасывает контекст открытой формы.
  2. Дополнительные сведения: хранятся в независимом непериодическом регистре сведений ДополнительныеСведения.

Если служебные отметки о печати вынести в Дополнительные сведения или в собственный независимый регистр сведений, запись будет производиться через менеджер записи регистра без модификации самого документа:


&НаСервере
Процедура ЗафиксироватьФактПечатиВРегистре(СсылкаНаДокумент)
    
    МенеджерЗаписи = РегистрыСведений.СведенияОПечатиДокументов.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.Документ = СсылкаНаДокумент;
    МенеджерЗаписи.ДатаПечати = ТекущаяДатаСеанса();
    МенеджерЗаписи.Пользователь = Пользователи.ТекущийПользователь();
    МенеджерЗаписи.Записать(Истина);
    
КонецПроцедуры

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

Способ 3. Передача изменений в контекст самой формы

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


// В контексте открытой формы документа
&НаКлиенте
Процедура ПослеЗакрытияФормыПечати(РезультатЗакрытия, ДополнительныеПараметры) Экспорт
    
    // Если печать прошла успешно, меняем реквизиты напрямую в Объект
    Объект.Напечатан = Истина;
    
    // Записываем документ средствами самой формы, сохраняя контекст версии
    ЭтаФорма.Записать();
    
КонецПроцедуры

Таким образом, форма сама инициирует запись, получает актуальную версию данных из базы и не вступает в конфликт с собственным состоянием.

← На главную