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