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