При разработке внешних печатных форм (ВПФ) и обработок в среде 1С:Предприятие 8.3 на управляемых формах начинающие разработчики часто сталкиваются с фундаментальными особенностями клиент-серверной архитектуры платформы. Две самые распространенные проблемы в этой области — невозможность прямой передачи серверных объектов (таких как ТаблицаЗначений) на клиент и загадочная ошибка «Печатная форма недоступна» при попытке вывести данные в табличный документ.
В этой статье мы подробно разберем, как устроена работа с коллекциями данных при печати, почему возникает ошибка доступа к печатной форме, и по шагам реализуем правильный, отказоустойчивый алгоритм вывода данных из таблицы значений в макет.
---
Для начала разберем архитектурный барьер платформы 1С. Объект типа ТаблицаЗначений является исключительно серверным типом данных. Он не может существовать на клиенте (в тонком или веб-клиенте) по соображениям производительности и безопасности: передача тяжелых таблиц с индексами и типизацией по сети перегружала бы каналы связи.
Если мы попытаемся передать переменную, содержащую ТаблицаЗначений, из серверного контекста в клиентскую процедуру напрямую, платформа вызовет ошибку сериализации. Как же передать эти данные во вторую процедуру, если логика работы (например, вызов общей формы печати БСП) требует выполнения части кода на клиенте?
Рассмотрим три надежных способа решения этой задачи.
Временное хранилище — это специальная область оперативной памяти сервера, доступ к которой осуществляется по уникальному строковому адресу (URL). Этот адрес имеет тип Строка, поэтому его можно легко передавать с сервера на клиент и обратно.
Алгоритм работы выглядит следующим образом:
Посмотрим на программную реализацию этого подхода:
// Серверная функция, которая формирует данные и сохраняет их в память
&НаСервере
Функция ПодготовитьДанныеДляПечатиНаСервере()
// 1. Создаем и заполняем таблицу значений
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаДанных.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
// Пример заполнения
НоваяСтрока = ТаблицаДанных.Добавить();
НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", "12345");
НоваяСтрока.Количество = 10;
// 2. Помещаем таблицу во временное хранилище.
// Обязательно передаем УникальныйИдентификатор формы вторым параметром!
АдресХранилища = ПоместитьВоВременноеХранилище(ТаблицаДанных, ЭтаФорма.УникальныйИдентификатор);
Возврат АдресХранилища;
КонецФункции
На клиенте мы получаем только строку-адрес и передаем её дальше без потери данных:
&НаКлиенте
Процедура ВыполнитьКоманду(ИдентификаторКоманды, ОбъектыНазначенияМассив) Экспорт
// Получаем адрес временного хранилища с сервера
АдресДанных = ПодготовитьДанныеДляПечатиНаСервере();
// Передаем адрес в процедуру печати
ВывестиВОбщуюФормуПечатьДокументов(АдресДанных);
КонецПроцедуры
Если данные таблицы должны не только передаваться в печать, но и отображаться пользователю на форме (например, в табличном поле), то правильным решением будет создание реквизита формы с типом Таблица значений. На клиенте этот объект автоматически преобразуется платформой в тип ДанныеФормыКоллекция, который доступен в любом контексте.
При работе на сервере мы можем легко конвертировать этот реквизит в полноценную таблицу значений для сложных расчетов и обратно:
&НаСервере
Процедура ОбработатьТаблицуНаСервере()
// Преобразуем реквизит формы во временный объект ТаблицаЗначений
ТЗ = РеквизитФормыВЗначение("ИмяРеквизитаФормыТаблица");
// Выполняем любые операции (сортировка, группировка, поиск), которые недоступны на клиенте
ТЗ.Свернуть("Номенклатура", "Количество");
// Записываем измененные данные обратно в реквизит формы
ЗначениеВРеквизитФормы(ТЗ, "ИмяРеквизитаФормыТаблица");
КонецПроцедуры
---
Теперь проанализируем проблемы, с которыми столкнулся автор темы в Сообщении 16. При попытке вывести строки в табличный документ возникла ошибка, которая в интерфейсе БСП (Библиотеки Стандартных Подсистем) маскируется под общим сообщением «Печатная форма недоступна».
Выясним причину этой ошибки. На самом деле БСП перехватывает любые фатальные сбои кода (исключения) при формировании табличного документа на сервере и выдает пользователю эту заглушку, чтобы сеанс работы не завершился аварийно. В предоставленном автором коде было допущено сразу три серьезных ошибки.
В коде автора написано:
ОблСтрока.Номенклатура = ТаблицаСНедостаточнымКоличеством[Индекс].Номенклатура;
Объект типа ОбластьМакетаТабличногоДокумента не имеет динамических свойств, соответствующих именам параметров. Все параметры области находятся исключительно в коллекции Параметры. Правильное обращение к ним выглядит так:
ОблСтрока.Параметры.Номенклатура = ...
В коде автора используется обход коллекции по индексу:
Для Индекс = 0 по ТаблицаСНедостаточнымКоличеством.Количество Цикл
Здесь кроются две системные ошибки:
Количество — это метод (функция), а не реквизит. Правильно писать Количество() с круглыми скобками.Посмотрим, где у автора вызывается метод вывода:
Для Индекс = 0 по ТаблицаСНедостаточнымКоличеством.Количество Цикл
// Заполнение параметра
КонецЦикла;
ТабДокумент.Вывести(ОблСтрока); // Метод вызван ПОСЛЕ цикла!
При такой структуре кода параметры области будут перезаписываться на каждой итерации цикла, а в результирующий табличный документ выведется всего одна-единственная строка — последняя из таблицы. Метод Вывести() должен вызываться внутри цикла, сразу после того, как были заполнены параметры для текущей строки.
---
В Сообщении 15 один из участников дискуссии утверждает: «А зачем табдок на сервере формировать? Вызовы сервера необходимо минимизировать».
Давайте разберем это заблуждение. Проектирование интерфейсов действительно требует минимизации серверных вызовов, но **только не в случае работы с макетами печати**. Объект Макет (точнее, метод получения макета из метаданных объекта метаданных ПолучитьМакет()) физически недоступен на клиенте. Попытка вызвать этот метод на стороне тонкого клиента приведет к ошибке компиляции. Таким образом, любое формирование печатных форм, заполнение параметров областей и генерация табличного документа всегда выполняются строго на стороне сервера.
---
Сведем все полученные знания воедино и напишем чистый, производительный и отказоустойчивый код для нашей внешней обработки. Мы применим способ передачи таблицы значений через временное хранилище и исправим алгоритм вывода макета.
Разберем готовый код по шагам:
&НаКлиенте
Процедура ВывестиВОбщуюФормуПечатьДокументов(АдресДанных)
// Получаем сформированный табличный документ с сервера.
// Передаем адрес временного хранилища, где лежит наша ТЗ.
ТабДокумент = ВывестиВОбщуюФормуПечатьДокументовНаСервере(АдресДанных);
// Стандартный алгоритм БСП для вывода табличного документа в общую форму печати
КоллекцияПечатныхФорм = УправлениеПечатьюКлиент.НоваяКоллекцияПечатныхФорм("Макет");
ПечатнаяФорма = УправлениеПечатьюКлиент.ОписаниеПечатнойФормы(КоллекцияПечатныхФорм, "Макет");
ПечатнаяФорма.СинонимМакета = "Ведомость недостатков";
ПечатнаяФорма.ТабличныйДокумент = ТабДокумент;
ПечатнаяФорма.ИмяФайлаПечатнойФормы = "ВедомостьНедостатков";
ОбластиОбъектов = Новый СписокЗначений;
УправлениеПечатьюКлиент.ПечатьДокументов(КоллекцияПечатныхФорм, ОбластиОбъектов);
КонецПроцедуры
&НаСервере
Функция ВывестиВОбщуюФормуПечатьДокументовНаСервере(АдресДанных)
ТабДокумент = Новый ТабличныйДокумент;
// 1. Извлекаем Таблицу Значений из временного хранилища по переданному адресу
ТаблицаСНедостатками = ПолучитьИзВременногоХранилища(АдресДанных);
// Проверка на случай непредвиденного удаления данных из памяти
Если ТаблицаСНедостатками = Неопределено Тогда
ВызватьИсключение "Ошибка получения данных для печати! Повторите попытку.";
КонецЕсли;
// 2. Получаем макет внешней обработки
Макет = РеквизитФормыВЗначение("Объект").ПолучитьМакет("Макет");
Шапка = Макет.ПолучитьОбласть("Шапка");
ОблСтрока = Макет.ПолучитьОбласть("ОбластьСтроки");
// Выводим шапку документа
ТабДокумент.Вывести(Шапка);
// 3. Безопасный обход таблицы значений с использованием цикла "Для Каждого"
// Этот подход полностью исключает ошибку выхода за границы индекса
Для Каждого СтрокаТЗ Из ТаблицаСНедостатками Цикл
// Обращаемся к параметрам области через свойство "Параметры"
ОблСтрока.Параметры.Номенклатура = СтрокаТЗ.Номенклатура;
ОблСтрока.Параметры.Количество = СтрокаТЗ.Количество;
// Метод Вывести() вызываем СТРОГО внутри цикла для каждой строки
ТабДокумент.Вывести(ОблСтрока);
КонецЦикла;
Возврат ТабДокумент;
КонецФункции
---
Чтобы ваши печатные формы всегда работали стабильно и не вызывали ошибок доступа, запомните следующие правила:
ПоместитьВоВременноеХранилище() с обязательной привязкой к УникальныйИдентификатор формы.Для Индекс = 0 По Количество()). Обходите таблицы конструкцией Для Каждого Строка Из Таблица Цикл — это защитит программу от падений из-за выхода за границы массива.Параметры (например, Область.Параметры.ИмяПараметра). Прямое присвоение свойству области вызовет ошибку «Поле объекта не обнаружено».ТабДокумент.Вывести() для строк должен находиться внутри цикла, иначе в документе отобразится только последняя запись.