При работе с управляемыми формами 1С часто возникает необходимость взаимодействовать между различными формами (например, реализуя заполнение табличной части документа по табличным частям других документов). Например, когда из основной формы документа мы открываем вспомогательную форму для выполнения специфических действий, таких как загрузка данных из внешнего файла. После завершения работы во вспомогательной форме и её закрытия, основная форма должна обновить свои данные, в частности, табличную часть, чтобы отобразить изменения, внесенные вспомогательной формой.
В этой статье мы подробно рассмотрим, как реализовать механизм обновления табличной части родительской формы документа 1С после заполнения её данных из дочерней формы, которая осуществляет загрузку информации из файла Excel на веб-сервере. Мы выясним, какие методы и подходы являются наиболее эффективными в клиент-серверной архитектуре 1С:Предприятие.
Исходная проблема заключается в следующем: из основной формы документа (например, "Заказ клиента") по нажатию кнопки "Заполнить ТЧ" открывается новая форма. Это стандартный сценарий, когда требуется загрузка заказа из Excel в Реализацию. Эта новая форма позволяет выбрать файл Excel, загрузить из него данные, обработать их на сервере и записать в табличную часть основного документа. Однако после закрытия вспомогательной формы, табличная часть основной формы остается пустой или неактуальной, пока пользователь вручную не нажмет кнопку "Перечитать" или не выполнит другое действие, обновляющее данные формы. Наша цель — автоматизировать этот процесс, чтобы изменения были видны сразу после закрытия вспомогательной формы.
Для решения этой проблемы мы используем несколько ключевых механизмов платформы 1С:
Давайте разберем реализацию по шагам, используя фрагменты кода из обсуждаемой темы. Мы проанализируем код как родительской формы документа, так и дочерней формы, отвечающей за загрузку данных.
Начнем с кода, который вызывается при нажатии на кнопку "Заполнить ТЧ" в основной форме документа. Эта процедура отвечает за подготовку и открытие дочерней формы.
&НаКлиенте
Процедура ОчиститьТЧОтвет
// Создаем описание оповещения. После закрытия формы "ЗТЧ_Форма" будет вызвана процедура "ПослеЗаполненияТЧФормы"
// в текущем объекте (ЭтаФорма).
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеЗаполненияТЧФормы",ЭтотОбъект);
// Получаем форму загрузки данных из Excel.
Форма = ПолучитьФорму("Документ.ЗаказКлиента.Форма.ЗТЧ_Форма");
// Передаем ссылку на текущий документ в дочернюю форму, чтобы она знала, какой документ обновлять.
Форма.СсылкаНаДокумент = Объект.Ссылка;
// Устанавливаем описание оповещения о закрытии. Это ключевой механизм для обратной связи.
Форма.ОписаниеОповещенияОЗакрытии = ОписаниеОповещения;
// Открываем дочернюю форму.
Форма.Открыть();
КонецПроцедуры
Здесь ОписаниеОповещения("ПослеЗаполненияТЧФормы", ЭтотОбъект) создает колбэк (функцию обратного вызова). Это означает, что после закрытия формы, которая была открыта с помощью Форма.Открыть(), будет автоматически вызвана процедура ПослеЗаполненияТЧФормы в текущем объекте (в нашем случае, в модуле родительской формы). Мы передаем ссылку на документ (Объект.Ссылка), чтобы дочерняя форма могла получить объект этого документа и изменить его табличную часть. Затем открываем дочернюю форму методом Форма.Открыть().
Обратите внимание, что процедура ОчиститьТЧОтвет вызывается из ЗТЧ_ЗаполнитьТЧПосле, а перед этим может быть выполнена логика по очистке текущей ТЧ или сохранению нового документа. Для простоты мы сосредоточимся на самой механике вызова формы и обновления.
Разработчикам часто требуется качественный шаблон для чтения и загрузки из Excel, чтобы не писать код с нуля.
Теперь рассмотрим код модуля дочерней формы ("Документ.ЗаказКлиента.Форма.ЗТЧ_Форма"), которая отвечает за выбор файла Excel, его загрузку на сервер и заполнение табличной части основного документа.
Процедура ЗаполнитьТЧ вызывается по нажатию кнопки "Заполнить ТЧ" внутри самой дочерней формы:
&НаКлиенте
Процедура ЗаполнитьТЧ(Команда)
// Подключаем расширение для работы с файлами, если необходимо (для веб-клиента).
ПодключитьРасширениеРаботыСФайлами;
// Начинаем создание двоичных данных из выбранного файла.
// После завершения будет вызвана процедура "ДвоичныеДанныеЗагрузка".
НачатьСозданиеДвоичныхДанныхИзФайла(Новый ОписаниеОповещения("ДвоичныеДанныеЗагрузка", ЭтотОбъект), ПутьКФайлу);
КонецПроцедуры
После того как файл выбран и двоичные данные получены (это асинхронная операция в веб-клиенте), срабатывает процедура ДвоичныеДанныеЗагрузка:
&НаКлиенте
Процедура ДвоичныеДанныеЗагрузка(ДвоичныеДанные, ДополнительныеПараметры) Экспорт
// Помещаем двоичные данные файла во временное хранилище на сервере.
// Это позволяет передать большой объем данных между клиентом и сервером.
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);
// Вызываем серверную процедуру для фактической обработки данных и заполнения ТЧ.
// ВАЖНО: Вызов серверной процедуры должен быть до Закрыть(), чтобы серверная логика успела выполниться.
ЗаполнитьТЧ_НаСервере(АдресХранилища);
// Закрываем дочернюю форму, передавая Истина в качестве результата.
// Это значение будет передано в процедуру оповещения родительской формы.
Закрыть(Истина);
КонецПроцедуры
Критически важный момент: Мы вызываем серверную процедуру ЗаполнитьТЧ_НаСервере(АдресХранилища) *перед* вызовом Закрыть(Истина). Это гарантирует, что все операции по обработке файла и записи данных в документ будут выполнены на сервере до того, как дочерняя форма закроется. Параметр Истина в методе Закрыть(Истина) сообщает родительской форме, что операция завершилась успешно.
Теперь рассмотрим саму серверную процедуру, которая выполняет основную работу:
&НаСервере
Процедура ЗаполнитьТЧ_НаСервере(АдресДанных)
// Получаем объект документа по ссылке, переданной из родительской формы.
Докум = СсылкаНаДокумент.ПолучитьОбъект;
// Очищаем существующую табличную часть документа перед заполнением.
Докум.Товары.Очистить;
// Создаем новый ТабличныйДокумент для чтения данных из Excel.
ТабДок = Новый ТабличныйДокумент;
// Получаем двоичные данные файла из временного хранилища на сервере.
Данные = ПолучитьИзВременногоХранилища(АдресДанных);
// Генерируем временное имя файла на сервере с расширением XLSX.
ПутьКФайлуНаСервере = ПолучитьИмяВременногоФайла("xlsx");
Данные.Записать(ПутьКФайлуНаСервере); // Записываем двоичные данные в этот файл.
Попытка
// Пытаемся прочитать файл как XLSX.
ТабДок.Прочитать(ПутьКФайлуНаСервере, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
// Если не удалось прочитать как XLSX, возможно, это старый формат XLS.
Данные = ПолучитьИзВременногоХранилища(АдресДанных); // Повторно получаем данные, если понадобится.
ПутьКФайлуНаСервере = ПолучитьИмяВременногоФайла("xls"); // Генерируем имя с расширением XLS.
Данные.Записать(ПутьКФайлуНаСервере); // Записываем двоичные данные в этот файл.
Попытка
// Пытаемся прочитать файл как XLS.
ТабДок.Прочитать(ПутьКФайлуНаСервере, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Исключение
// Если и это не удалось, выводим сообщение об ошибке и прерываем выполнение.
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Не удалось прочитать указанный файл по причине: " + ОписаниеОшибки;
Сообщение.Сообщить;
Возврат;
КонецПопытки;
КонецПопытки;
// Получаем количество строк в прочитанном табличном документе.
КоличествоСтрок = ТабДок.ВысотаТаблицы;
// Итерируемся по каждой строке Excel-файла для заполнения табличной части документа.
Для НомерСтроки = 1 по КоличествоСтрок Цикл
// Извлекаем данные из ячеек Excel. Пример: колонка 3 для Артикула, колонка 2 для Названия, колонка 4 для Количества.
АртикулТовара = ТабДок.ПолучитьОбласть("R" + Формат(НомерСтроки,"ЧГ=0") + "C" + 3).ТекущаяОбласть.Текст;
АртикулТовара = СтрЗаменить(АртикулТовара, Символы.НПП, ""); // Очистка от неразрывных пробелов.
НазваниеТовара = ТабДок.ПолучитьОбласть("R" + Формат(НомерСтроки,"ЧГ=0") + "C" + 2).ТекущаяОбласть.Текст;
НазваниеТовара = СокрЛП(НазваниеТовара);
Количество = ТабДок.ПолучитьОбласть("R" + Формат(НомерСтроки,"ЧГ=0") + "C" + 4).ТекущаяОбласть.Текст;
// Пропускаем строки без артикула.
Если Не ЗначениеЗаполнено(АртикулТовара) Тогда
Продолжить;
КонецЕсли;
// Ищем номенклатуру по артикулу в справочнике.
НаличиеТовара = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", АртикулТовара);
// Если номенклатура не найдена, выводим сообщение и пропускаем строку.
Если Не ЗначениеЗаполнено(НаличиеТовара) Тогда
Если Строка(АртикулТовара) <> "Артикул" Тогда // Исключаем заголовок
Сообщить("Товар с артикулом '" + АртикулТовара + "' в базе не найден и не был добавлен в таблицу!");
КонецЕсли;
Продолжить;
КонецЕсли;
Номенкл = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", АртикулТовара);
// Добавляем новую строку в табличную часть документа и заполняем ее.
ДокСтр = Докум.Товары.Добавить;
ДокСтр.Номенклатура = Номенкл;
ДокСтр.КоличествоУпаковок = Количество;
КонецЦикла;
// КРИТИЧЕСКИ ВАЖНО: Записываем измененный объект документа в базу данных.
// Иначе изменения не сохранятся и родительская форма не сможет их прочитать.
Докум.Записать(РежимЗаписиДокумента.ОтменаПроведения); // Возможно, достаточно одной Записать(РежимЗаписиДокумента.Запись).
Докум.Записать(РежимЗаписиДокумента.Запись);
КонецПроцедуры
В этой серверной процедуре происходит следующее:
СсылкаНаДокумент.ПолучитьОбъект.Докум.Товары.Очистить.ТабличныйДокумент.ТабДок, извлекаем артикул, название и количество, ищем номенклатуру в справочнике и добавляем новую строку в табличную часть документа Докум.Товары.Докум.Записать(). Это гарантирует, что изменения сохранятся и родительская форма сможет их прочитать.Когда дочерняя форма закрывается, срабатывает процедура оповещения, которую мы установили при открытии формы:
&НаКлиенте
Процедура ПослеЗаполненияТЧФормы(Результат,ДопПараметры) Экспорт
// Проверяем, был ли результат закрытия формы успешным (передано Истина).
Если Результат = Истина Тогда
// Вызываем клиентскую процедуру (которая, в свою очередь, вызовет серверную функцию) для обновления формы.
ОбновитьФорму(Объект.Ссылка);
// Вызываем дополнительную процедуру для обновления визуальных полей формы, если необходимо.
ЗаполнитьПоля;
КонецЕсли;
КонецПроцедуры
В этой процедуре мы проверяем, успешно ли завершилась работа дочерней формы (Результат = Истина). Если да, то вызываем процедуру ОбновитьФорму(Объект.Ссылка), которая отвечает за перечитывание объекта документа, и затем ЗаполнитьПоля, которая, вероятно, обновляет другие элементы формы или выполняет дополнительные вычисления.
Рассмотрим серверную функцию ОбновитьФорму, которая фактически обновляет данные формы:
&НаСервере
Функция ОбновитьФорму(СсылкаНаДок)
// Получаем текущий объект формы.
ТекОбъект = РеквизитФормыВЗначение("Объект");
// Перечитываем объект из базы данных, чтобы получить актуальные изменения.
ТекОбъект.Прочитать;
// Записываем обновленный объект обратно в реквизит формы,
// что приводит к обновлению данных на форме.
ЗначениеВРеквизитФормы(ТекОбъект, "Объект");
КонецФункции
Эта серверная функция отвечает за фактическое перечитывание данных объекта формы из базы данных. После того как объект документа был изменен и записан на сервере (в процедуре ЗаполнитьТЧ_НаСервере), нам нужно, чтобы родительская форма "узнала" об этих изменениях. Для этого мы выполняем следующие действия:
ТекОбъект = РеквизитФормыВЗначение("Объект"): Преобразуем реквизит формы "Объект" в значение типа "ОбъектДокумента" для серверного контекста.ТекОбъект.Прочитать(): Вызываем метод Прочитать() для объекта документа. Этот метод загружает самые актуальные данные объекта из базы данных в память сервера.ЗначениеВРеквизитФормы(ТекОбъект, "Объект"): Записываем обновленный объект обратно в реквизит формы. Это действие приводит к тому, что форма на клиенте обновляет свои элементы управления, основываясь на новых данных из реквизита "Объект", включая табличную часть.После обновления объекта формы, мы вызываем процедуру ЗаполнитьПоля:
&НаКлиенте
Процедура ЗаполнитьПоля
Для Каждого ТекСтрока Из Объект.Товары Цикл
Элементы.Товары.ТекущаяСтрока = ТекСтрока.ПолучитьИдентификатор;
// ЗТЧ_ТоварыНоменклатураПриИзменении вызывается для каждой строки,
// вероятно, для перерасчета или заполнения зависимых полей на форме.
ЗТЧ_ТоварыНоменклатураПриИзменении(Элементы.Товары.ТекущиеДанные);
КонецЦикла;
КонецПроцедуры
ЗаполнитьПоля в данном контексте, скорее всего, выполняет дополнительные действия по обработке только что загруженных строк табличной части. Например, она может вызывать обработчики событий при изменении номенклатуры (ЗТЧ_ТоварыНоменклатураПриИзменении), чтобы пересчитать цены, НДС, суммы, заполнить характеристики или упаковки, и т.д. Это важно, чтобы все зависимые реквизиты строк табличной части были корректно отображены и рассчитаны на форме.
В коде также присутствуют вспомогательные процедуры, которые используются для выбора файла и дополнительной обработки данных:
ОбработатьНачалоВыбораФайла, НачатьПодключениеРасширенияРаботыСФайламиЗавершение, НачатьУстановкуРасширенияРаботыСФайламиЗавершение, ВложениеВыборЗавершение, КаталогДокументовЗавершение, КаталогВыбран: Эти процедуры реализуют стандартный механизм выбора файла в веб-клиенте, включая запрос на установку расширения для работы с файлами.СообщитьПользователю: Вспомогательная процедура для вывода сообщений.ПроверкаДока: Серверная функция, которая проверяет, является ли документ новым.ЗТЧ_ТоварыНоменклатураПриИзменении: Эта объемная процедура (вызываемая в цикле ЗаполнитьПоля) содержит бизнес-логику для перерасчета и заполнения различных полей строки табличной части при изменении номенклатуры. Она обрабатывает НДС, количество, склад, скидки, и другие параметры, характерные для документа "ЗаказКлиента".В процессе работы у автора возник вопрос: "как можно сделать, чтобы сообщения еще выводились? Потому что они из-за прочтения перестали". Эта проблема возникает, когда серверные сообщения, созданные с помощью СообщениеПользователю или Сообщить, не отображаются на клиенте после полного обновления формы (ЭтаФорма.Прочитать() или ЗначениеВРеквизитФормы(ТекОбъект, "Объект")).
Причина в том, что ЭтаФорма.Прочитать() или полный перезапись объекта формы через ЗначениеВРеквизитФормы фактически сбрасывает контекст формы, включая буфер сообщений, которые были собраны на сервере, но еще не отображены на клиенте. Когда форма полностью перезагружается, старые сообщения могут быть потеряны.
Решение: Для сохранения сообщений, их необходимо собрать на сервере (например, в массив строк или массив объектов СообщениеПользователю), вернуть на клиент, а затем явно вывести на клиенте *после* того, как форма будет обновлена. Мы можем передать этот массив сообщений из серверной процедуры в клиентскую через параметры функции или через отдельный реквизит формы.
Пример модификации процедуры ЗаполнитьТЧ_НаСервере для сбора сообщений:
&НаСервере
Функция ЗаполнитьТЧ_НаСервере(АдресДанных)
СписокСообщений = Новый Массив; // Создаем массив для сбора сообщений
Докум = СсылкаНаДокумент.ПолучитьОбъект;
Докум.Товары.Очистить;
// ... (весь код обработки файла) ...
// Вместо Сообщить("Товар не найден...")
Если Не ЗначениеЗаполнено(НаличиеТовара) Тогда
Если Строка(АртикулТовара) <> "Артикул" Тогда
СписокСообщений.Добавить("Товар с артикулом '" + АртикулТовара + "' в базе не найден и не был добавлен в таблицу!");
КонецЕсли;
Продолжить;
КонецЕсли;
// ...
// В конце процедуры возвращаем массив сообщений
Возврат СписокСообщений;
КонецФункции
&НаКлиенте
Процедура ДвоичныеДанныеЗагрузка(ДвоичныеДанные, ДополнительныеПараметры) Экспорт
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);
// Получаем массив сообщений как результат серверной функции
МассивСообщенийОтСервера = ЗаполнитьТЧ_НаСервере(АдресХранилища);
Закрыть(Истина);
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗаполненияТЧФормы(Результат,ДопПараметры) Экспорт
Если Результат = Истина Тогда
ОбновитьФорму(Объект.Ссылка);
ЗаполнитьПоля;
// Допустим, МассивСообщенийОтСервера был передан через ДопПараметры
// или сохранен в реквизите формы
// Для каждого ТекстСообщения Из ЭтотОбъект.МассивСообщений Цикл
// Сообщение = Новый СообщениеПользователю;
// Сообщение.Текст = ТекстСообщения;
// Сообщение.Сообщить();
// КонецЦикла;
КонецЕсли;
КонецПроцедуры
В этом примере мы изменили ЗаполнитьТЧ_НаСервере на функцию, которая возвращает Массив строк с сообщениями. На клиенте, в ДвоичныеДанныеЗагрузка, мы получаем этот массив. Затем, после закрытия дочерней формы и обновления родительской, этот массив может быть передан в процедуру ПослеЗаполненияТЧФормы (например, через ДополнительныеПараметры объекта ОписаниеОповещения) и там явно выведен с помощью СообщениеПользователю.Сообщить().
Таким образом, для обновления табличной части родительской формы 1С после заполнения из другой формы на веб-сервере мы используем комплексный подход. Он включает в себя механизм оповещений о закрытии формы для клиент-клиентского взаимодействия, передачу файлов через временное хранилище, серверную обработку данных и, что самое важное, программное обновление объекта формы родительского документа. Важно помнить о клиент-серверной архитектуре и правильном порядке вызовов, а также об особенностях вывода сообщений при обновлении формы.
Правильная реализация этих шагов позволяет создать удобный и функциональный механизм для автоматического обновления данных в документах, значительно улучшая пользовательский опыт.