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