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