Как в управляемых формах 1С открыть форму существующего документа, изменить его данные и показать пользователю без записи в базу?

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

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

С переходом на управляемый интерфейс (УФ) этот процесс существенно усложнился. Разделение контекста выполнения кода на клиент и сервер, особенности сериализации данных формы, а также сложные алгоритмы инициализации интерфейса в типовых конфигурациях (особенно в «1С:Зарплата и управление персоналом 3» или «1С:ERP») превращают эту задачу в нетривиальную проблему. Попытки сделать все «по старинке» приводят либо к потере изменений при открытии, либо к критическим ошибкам платформы вроде «Нельзя изменять поле, содержащее объект данных формы».

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

---

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

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


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

&НаСервере
Процедура ИзменитьТабельНаСервере(ДанныеФормыОбъекта)
    // Здесь платформа выдаст ошибку: "Нельзя изменять поле, содержащее объект данных формы"
    НоваяСтрока = ДанныеФормыОбъекта.ДанныеОВремени.Добавить(); 
КонецПроцедуры

Эта ошибка возникает из-за того, что свойство Форма.Объект на клиенте имеет тип ДанныеФормыСтруктура. При передаче этого объекта в качестве параметра серверной процедуры происходит неявная сериализация данных. Платформа блокирует прямую модификацию связанных с формой данных вне контекста самой формы, чтобы избежать нарушения целостности структуры.

Вторая проблема — визуальные «артефакты». Даже если обойти ошибку передачи данных (об этом ниже), в сложных конфигурациях измененный документ может открыться со старыми, незаполненными или некорректно отображаемыми данными. Выясним причину: в сложных формах (например, в документе «Табель учета рабочего времени» в ЗУП 3) при открытии выполняется тяжелый код в обработчиках ПриСозданииНаСервере или ПриЧтенииНаСервере. На основе исходного состояния документа из базы данных динамически создаются элементы формы, рассчитываются вспомогательные реквизиты оформления и настраивается видимость колонок. Если вы меняете данные объекта формы уже после выполнения этих обработчиков, форма отобразит данные некорректно, так как интерфейс уже построился по «старым» правилам.

---

Вариант 1. Использование ПолучитьФорму() и КопироватьДанныеФормы() (Для простых документов)

Если изменяемый документ имеет простую структуру данных и его форма не перегружена сложной динамической логикой построения интерфейса в событии ПриСозданииНаСервере, задачу можно решить через использование буферной переменной на клиенте и метода КопироватьДанныеФормы().

Рассмотрим этот метод пошагово:

  1. Получаем форму документа по ссылке, используя уникальный ключ, чтобы гарантировать открытие новой независимой формы.
  2. Копируем состояние основного реквизита формы (Объект) во вспомогательную переменную. Это отвязывает структуру от контекста отображения.
  3. Передаем полученную структуру на сервер, где производим ее модификацию.
  4. Возвращаем измененную структуру на клиент и копируем ее обратно в форму методом КопироватьДанныеФормы().
  5. Устанавливаем свойство формы Модифицированность в значение Истина, чтобы платформа знала, что данные были изменены, и предупредила пользователя при попытке закрыть форму без сохранения.

Пример реализации кода:


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

&НаСервереБезКонтекста
Процедура МодифицироватьДанныеНаСервере(ДанныеФормыОбъекта)
    
    // Работаем с типом ДанныеФормыСтруктура. 
    // Добавим строку в табличную часть "Товары"
    НоваяСтрока = ДанныеФормыОбъекта.Товары.Добавить();
    НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", "777");
    НоваяСтрока.Количество = 10;
    НоваяСтрока.Цена = 150;
    НоваяСтрока.Сумма = 1500;
    
КонецПроцедуры

---

Вариант 2. Использование параметров формы и расширения (Для сложных документов)

В случае работы с комплексными конфигурациями (ЗУП, ERP) описанный выше способ может приводить к пустым табличным частям или ошибкам отображения. Единственный надежный способ встроить свои изменения в логику работы такой формы — выполнить их в момент инициализации объекта на сервере, до того как начнется построение элементов интерфейса.

Наиболее изящное решение этой проблемы — передача структуры изменений через параметры формы и последующий перехват события формы ПриЧтенииНаСервере с помощью механизма Расширений конфигурации.

Рассмотрим по шагам логику этого подхода:

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

Код вызова внешней обработки или общего модуля:


&НаКлиенте
Процедура ИзменитьСложныйДокументЧерезПараметры(СсылкаНаДокумент)
    
    // Формируем список изменений (например, новые строки для табеля)
    МассивНовыхСтрок = Новый Массив;
    
    СтрокаИзменений = Новый Структура;
    СтрокаИзменений.Вставить("Сотрудник", СсылкаНаСотрудника);
    СтрокаИзменений.Вставить("ВидВремени", Справочники.ВидыИспользованияРабочегоВремени.Явка);
    СтрокаИзменений.Вставить("Часы", 8);
    МассивНовыхСтрок.Добавить(СтрокаИзменений);
    
    // Передаем параметры открытия
    ПараметрыФормы = Новый Структура;
    ПараметрыФормы.Вставить("Ключ", СсылкаНаДокумент);
    ПараметрыФормы.Вставить("ДополнительныеДанныеЗаполнения", МассивНовыхСтрок);
    
    ОткрытьФорму("Документ.ТабельУчетаРабочегоВремени.Форма.ФормаДокумента", ПараметрыФормы);
    
КонецПроцедуры

Код модуля управляемой формы в расширении конфигурации:


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

---

Вариант 3. Использование стандартных механизмов БСП (Режим «НеЗаписывать»)

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

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


НоваяКоманда = ТаблицаКоманд.Добавить();
НоваяКоманда.Представление = "Заполнить табель (без записи)";
НоваяКоманда.Идентификатор = "ЗаполнитьТабельБезЗаписи";
НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыЗаполнениеФормы();
НоваяКоманда.РежимЗаписи = "НеЗаписывать"; // Критически важный параметр!

При использовании типа команды ЗаполнениеФормы и режима записи "НеЗаписывать" платформа не будет принудительно сохранять документ в базу перед запуском вашей обработки. В модуль обработки в процедуру ВыполнитьКоманду() будет передана ссылка на саму форму документа (параметр ЭтаФорма). Вы сможете напрямую манипулировать данными формы на клиенте или сервере, а документ останется открытым и модифицированным на экране пользователя.

---

Вариант 4. Оптимальная передача данных через Временное хранилище

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

Посмотрим на пример реализации этой схемы заполнения:


&НаКлиенте
Процедура ИзменитьЧерезВременноеХранилище(СсылкаНаДокумент)
    
    // Готовим структуру изменений на клиенте
    АдресХранилища = ПоместитьДанныеВХранилищеНаСервере();
    
    ПараметрыФормы = Новый Структура;
    ПараметрыФормы.Вставить("Ключ", СсылкаНаДокумент);
    ПараметрыФормы.Вставить("АдресПараметровМодификации", АдресХранилища);
    
    // Открываем форму, передавая адрес во временном хранилище
    ОткрытьФорму("Документ.НачислениеЗарплаты.Форма.ФормаДокумента", ПараметрыФормы);
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПоместитьДанныеВХранилищеНаСервере()
    
    ТаблицаДанных = Новый ТаблицаЗначений;
    ТаблицаДанных.Колонки.Добавить("Сотрудник");
    ТаблицаДанных.Колонки.Добавить("Размер");
    
    // Заполняем таблицу необходимыми данными...
    
    // Помещаем таблицу во временное хранилище и возвращаем адрес
    Возврат ПоместитьВоВременноеХранилище(ТаблицаДанных, Новый УникальныйИдентификатор);
    
КонецФункции

В коде формы документа (в расширении) в процедуре ПриЧтенииНаСервере мы извлекаем таблицу из хранилища по переданному адресу и производим быструю модификацию объекта на стороне сервера:


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

---

Важная деталь: Вызов экспортных методов пересчета формы

В комплексных документах ручное заполнение табличной части (даже выполненное корректно на сервере) часто приводит к тому, что зависимые поля (суммы НДФЛ, взносы, итоги по документу) остаются нерассчитанными. Вручную дублировать сложные формулы расчета в коде своей обработки не имеет смысла — это приведет к ошибкам при первом же обновлении конфигурации.

Чтобы избежать этой проблемы, после заполнения данных формы и выполнения КопироватьДанныеФормы() найдите и вызовите **экспортные методы расчета самой формы** (если они предусмотрены разработчиками конфигурации). Например:


// Копируем модифицированные данные в форму
КопироватьДанныеФормы(ИзмененныйОбъект, ФормаДокумента.Объект);

// Вызываем штатный пересчет налогов и итогов, заложенный разработчиками формы
ФормаДокумента.РассчитатьДокументНаКлиенте(); 

ФормаДокумента.Модифицированность = Истина;
ФормаДокумента.Открыть();

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

← На главную