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