При работе в 1С часто возникает задача распечатать не весь объем данных из формы списка, а только те, которые были предварительно отфильтрованы пользователем. При этом у нас может быть уже настроен собственный макет печати, который необходимо использовать. В данной статье мы подробно рассмотрим, как решить эту проблему, включая шаги по получению отфильтрованных данных и их корректной передаче для печати.
Предположим, у нас есть форма списка с динамическим списком, например, список документов или справочников. Пользователь применяет к этому списку различные фильтры – по цеху, по дате, по ответственному и так далее. После применения фильтрации необходимо, чтобы на печать вышли только видимые на форме отфильтрованные строки, а не весь объем данных, который хранится в базе. При этом мы хотим использовать предварительно разработанный макет печати, который содержит специфические области и параметры для вывода информации (например, данные по цеху, месяцу и расчеты).
Стоит отметить, что простое использование стандартной команды "Все действия" -> "Вывести список" не всегда подходит, поскольку оно не позволяет применять сложный макет печати и может не учитывать специфические требования к оформлению, прописанные в нашем макете. Наша цель – сохранить примененные фильтры и вывести данные через наш собственный макет.
Первым и наиболее важным шагом является программное получение данных, которые уже отображаются в динамическом списке после применения всех пользовательских фильтров, сортировок и условного оформления. Платформа 1С:Предприятие 8.3 (начиная с версии 8.3.6) предоставляет удобные механизмы для работы с динамическими списками через систему компоновки данных (СКД).
Мы будем использовать методы динамического списка: ПолучитьИсполняемуюСхемуКомпоновкиДанных() и ПолучитьИсполняемыеНастройкиКомпоновкиДанных(). Эти методы позволяют нам получить текущие схему и настройки СКД, которые применяются к динамическому списку на форме, включая все активные отборы, сортировки и условное оформление, заданные пользователем. Таким образом, мы получаем "слепок" текущего состояния списка.
Далее, используя эти схему и настройки, мы можем скомпоновать макет СКД и выполнить его для получения результата в виде коллекции значений. Эта коллекция значений будет содержать только те данные, которые видны пользователю в отфильтрованном списке. Разберем подробнее этот процесс на примере:
Предположим, у нас есть элемент формы с именем Список, который является динамическим списком. Для получения отфильтрованных данных мы можем использовать следующий код, например, в обработчике команды печати на клиенте:
// В обработчике команды формы, например, "Печать"
// Эта функция возвращает отфильтрованные данные
Функция ПолучитьОтфильтрованныеДанныеСписка()
// Получаем текущую схему компоновки данных динамического списка
Схема = Элементы.Список.ПолучитьИсполняемуюСхемуКомпоновкиДанных();
// Получаем текущие настройки компоновки данных динамического списка
// Они включают отборы, сортировки, условное оформление, заданные пользователем
Настройки = Элементы.Список.ПолучитьИсполняемыеНастройкиКомпоновкиДанных();
// Создаем компоновщик макета СКД
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
// Выполняем компоновку макета, передавая схему, настройки и генератор
// Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений") указывает,
// что результатом выполнения будет коллекция значений (по сути, ТаблицаЗначений)
МакетКомпоновки = КомпоновщикМакета.Выполнить(Схема, Настройки, , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
// Создаем процессор компоновки данных
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
// Инициализируем процессор компоновки полученным макетом
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
// Создаем процессор вывода результата компоновки в коллекцию значений
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
// Создаем ТаблицуЗначений, куда будут выведены отфильтрованные данные
РезультатКомпоновки = Новый ТаблицаЗначений;
// Выполняем вывод данных
ПроцессорВывода.УстановитьОбъект(РезультатКомпоновки);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
Возврат РезультатКомпоновки; // Возвращаем ТаблицуЗначений с отфильтрованными данными
КонецФункции
Эта функция ПолучитьОтфильтрованныеДанныеСписка() вернет нам объект типа ТаблицаЗначений, который будет содержать исключительно те строки и колонки, которые видны в отфильтрованном динамическом списке. Важно, что полученные схема и настройки учитывают не только фильтры, но и видимость колонок, а также примененный поиск. Если вам нужны все колонки списка, а не только видимые пользователю, возможно, потребуется дополнительная настройка схемы перед компоновкой.
После того как мы получили отфильтрованные данные в виде ТаблицыЗначений на клиенте (в функции, как показано выше), возникает следующая распространённая проблема: как передать эту ТаблицуЗначений в процедуру печати, которая обычно выполняется на сервере (например, в модуле менеджера или в серверной процедуре формы)?
При попытке прямой передачи ТаблицыЗначений из клиентского контекста в серверный (например, в вызове ПечатьНаСервере(ТаблицаСДанными)) мы столкнёмся с ошибкой: "Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа." или "Ошибка преобразования данных XDTO: Значение недопустимого типа.". Это происходит потому, что ТаблицаЗначений является мутабельным (изменяемым) типом данных, и платформа 1С не поддерживает её прямую сериализацию и десериализацию через механизм XDTO при передаче между клиентом и сервером без специальных преобразований.
Мы рассмотрим несколько способов решения этой проблемы.
Один из наиболее чистых и рекомендуемых способов – это преобразование ТаблицыЗначений в массив структур на клиенте, а затем обратное преобразование из массива в ТаблицуЗначений на сервере. Этот подход поддерживается функциями из Библиотеки стандартных подсистем (БСП), если она интегрирована в вашу конфигурацию.
ТаблицыЗначений в Массив.ОбщегоНазначения.ТаблицаЗначенийВМассив() (если БСП доступна) или реализуйте аналогичную логику вручную. Каждая строка ТаблицыЗначений будет преобразована в структуру, а все структуры будут собраны в массив. Массивы структур являются сериализуемым типом и могут быть переданы на сервер.Пример клиентского кода:
// Клиентский контекст
Процедура КомандаПечатьНаКлиенте()
// 1. Получаем отфильтрованные данные на клиенте
ОтфильтрованныеДанные = ПолучитьОтфильтрованныеДанныеСписка();
// 2. Преобразуем ТаблицуЗначений в Массив структур
// Предполагаем наличие ОбщегоНазначения из БСП или аналогичной функции
МассивДанныхДляПередачи = ОбщегоНазначения.ТаблицаЗначенийВМассив(ОтфильтрованныеДанные);
// 3. Вызываем серверную процедуру, передавая Массив
ПечатьНаСервере(МассивДанныхДляПередачи);
КонецПроцедуры
Массива в ТаблицуЗначений.ТаблицуЗначений с помощью ОбщегоНазначения.МассивВТаблицуЗначений() или аналогичной функции.Пример серверного кода:
// Серверный контекст
Процедура ПечатьНаСервере(МассивДанных)
// 1. Преобразуем Массив структур обратно в ТаблицуЗначений
// Предполагаем наличие ОбщегоНазначения из БСП или аналогичной функции
ТаблицаДанныхДляПечати = ОбщегоНазначения.МассивВТаблицуЗначений(МассивДанных);
// Теперь ТаблицаДанныхДляПечати содержит отфильтрованные данные
// и может быть использована для заполнения макета печати.
// ... (дальнейшая логика заполнения макета и вывода на печать)
КонецПроцедуры
Использование временного хранилища является, пожалуй, самым "лаконичным" и простым способом передачи любых сложных, несериализуемых объектов между клиентом и сервером. Платформа 1С предоставляет механизм временного хранилища, который позволяет помещать в него объекты на одной стороне (клиент/сервер) и извлекать их на другой по уникальному адресу.
ТаблицыЗначений во временное хранилище.ПоместитьВоВременноеХранилище(), которая вернет уникальный адрес (строку) размещенного объекта. Этот адрес уже является сериализуемым типом данных и может быть передан на сервер.Пример клиентского кода:
// Клиентский контекст
Процедура КомандаПечатьНаКлиенте()
// 1. Получаем отфильтрованные данные на клиенте
ОтфильтрованныеДанные = ПолучитьОтфильтрованныеДанныеСписка();
// 2. Помещаем ТаблицуЗначений во временное хранилище
// Временное хранилище автоматически обрабатывает сериализацию/десериализацию.
АдресВХранилище = ПоместитьВоВременноеХранилище(ОтфильтрованныеДанные, УникальныйИдентификатор); // УникальныйИдентификатор - можно не указывать
// 3. Вызываем серверную процедуру, передавая АдресВХранилище
ПечатьНаСервере(АдресВХранилище);
КонецПроцедуры
ТаблицыЗначений из временного хранилища.ПолучитьИзВременногоХранилища() для извлечения объекта.Пример серверного кода:
// Серверный контекст
Процедура ПечатьНаСервере(АдресВХранилище)
// 1. Извлекаем ТаблицуЗначений из временного хранилища по адресу
ТаблицаДанныхДляПечати = ПолучитьИзВременногоХранилища(АдресВХранилище);
// Теперь ТаблицаДанныхДляПечати содержит отфильтрованные данные
// и может быть использована для заполнения макета печати.
// ... (дальнейшая логика заполнения макета и вывода на печать)
// Важно: При необходимости очистить временное хранилище
// УдалитьИзВременногоХранилища(АдресВХранилище);
КонецПроцедуры
Этот метод часто предпочтителен из-за своей простоты и универсальности, так как он позволяет передавать практически любые объекты.
Хотя этот способ менее типичен для передачи данных именно для печати, он также позволяет обмениваться сложными данными. Он заключается в том, чтобы добавить новый реквизит формы с типом ТаблицаЗначений, затем заполнить его на сервере и использовать через форму.
ДанныеДляПечати, с типом ТаблицаЗначений.ТаблицуЗначений и заполнить ее, а затем присвоить реквизиту формы. Для этого можно использовать методы ИзменитьРеквизиты() и ЗначениеВРеквизитФормы().Пример серверного кода (предполагая, что мы уже на сервере и имеем доступ к данным):
// Серверный контекст (в серверной процедуре, которая имеет доступ к данным формы)
Процедура СформироватьДанныеДляПечатиНаСервере()
// ... Получаем ТаблицуДанныхДляПечати (например, из запроса или СКД)
// Добавляем реквизит формы, если его еще нет
Если Не ЭтоРеквизитФормы("ДанныеДляПечати") Тогда
МассивРеквизитов = Новый Массив;
МассивРеквизитов.Добавить(Новый РеквизитФормы("ДанныеДляПечати", Новый ОписаниеТипов("ТаблицаЗначений")));
ИзменитьРеквизиты(МассивРеквизитов);
КонецЕсли;
// Присваиваем ТаблицуЗначений реквизиту формы
ЗначениеВРеквизитФормы(ТаблицаДанныхДляПечати, "ДанныеДляПечати");
КонецПроцедуры
После этого на клиенте можно будет обращаться к Элементы.ДанныеДляПечати или ЭтотОбъект.ДанныеДляПечати, чтобы получить ТаблицуЗначений. Однако для одноразовой операции печати это может быть избыточным.
Важное примечание по ошибке "Процедура или функция с указанным именем не определена (Печать2НаСервере)": Эта ошибка, упомянутая в сообщении 6, указывает на проблему с видимостью или существованием серверной процедуры. Убедитесь, что серверная процедура, которую вы вызываете с клиента, объявлена с директивой &НаСервере или &НаСервереБезКонтекста в модуле формы или в общем модуле, который доступен серверу.
// Пример правильного объявления серверной процедуры в модуле формы:
&НаСервере
Процедура ПечатьНаСервере(ПараметрДанных) // ПараметрДанных может быть МассивДанных или АдресВХранилище
// ... логика печати
КонецПроцедуры
После того как мы успешно получили отфильтрованные данные в виде ТаблицыЗначений на сервере, мы можем приступить к заполнению нашего предварительно созданного макета печати. Макеты печатных форм создаются через конструктор и позволяют управлять именованными областями.
Предположим, у нас есть макет с несколькими областями: "Заголовок", "ШапкаТаблицы", "СтрокаТаблицы", "Подвал".
// Серверный контекст
Процедура ЗаполнитьИВывестиМакет(ТаблицаДанныхДляПечати)
Макет = ПолучитьМакет("МакетПечатнойФормы"); // Или Макет = Справочники.МойСправочник.ПолучитьМакет("МойМакет")
// Создаем новый табличный документ для вывода результата
ТабличныйДокумент = Новый ТабличныйДокумент;
ТабличныйДокумент.ИмяПараметровПечати = "ПАРАМЕТРЫ_ПЕЧАТИ_МоейФормы"; // Для сохранения настроек пользователя
// ... (далее заполнение макета)
КонецПроцедуры
// Продолжение процедуры ЗаполнитьИВывестиМакет()
// Область заголовка (например, с общими данными, которые не фильтруются)
ОбластьЗаголовок = Макет.ПолучитьОбласть("Заголовок");
ОбластьЗаголовок.Параметры.ТекстЗаголовка = "Отчет по цеху за период"; // Заполняем параметр
// Здесь же можно заполнить параметры по цеху, месяцу, если они передаются отдельно
// ОбластьЗаголовок.Параметры.Цех = "Цех №" + ПараметрыПечати.Цех;
ТабличныйДокумент.Вывести(ОбластьЗаголовок);
// Область шапки таблицы
ОбластьШапкаТаблицы = Макет.ПолучитьОбласть("ШапкаТаблицы");
ТабличныйДокумент.Вывести(ОбластьШапкаТаблицы);
// Область строки таблицы - основная логика вывода отфильтрованных данных
ОбластьСтрока = Макет.ПолучитьОбласть("СтрокаТаблицы");
Для Каждого СтрокаДанных Из ТаблицаДанныхДляПечати Цикл
// Заполняем параметры области из текущей строки ТаблицыЗначений
ОбластьСтрока.Параметры.Заполнить(СтрокаДанных); // Автоматически заполняет параметры, имена которых совпадают с именами колонок
// Если имена параметров в макете отличаются от имен колонок в ТаблицеЗначений,
// можно заполнять явно:
// ОбластьСтрока.Параметры.ИмяКолонкиМакета = СтрокаДанных.ИмяКолонкиИзТаблицы;
ТабличныйДокумент.Вывести(ОбластьСтрока);
КонецЦикла;
// Область подвала (например, с итоговыми данными)
ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");
// ОбластьПодвал.Параметры.Итого = РассчитатьИтог(ТаблицаДанныхДляПечати);
ТабличныйДокумент.Вывести(ОбластьПодвал);
// Показать сформированный табличный документ
ТабличныйДокумент.Показать();
КонецПроцедуры
ТаблицеЗначений, которую вы передаете для печати. Если они не совпадают, используйте явное присвоение ОбластьСтрока.Параметры.ИмяПараметраВМакете = СтрокаДанных.ИмяКолонкиВТаблице;.ТаблицеЗначений.ПечатьПоВнешнемуИсточнику() из общего модуля ДополнительныеОтчетыИОбработки (если БСП интегрирована).Для успешной реализации печати отфильтрованных данных с использованием макета критически важно понимать архитектуру работы с динамическими списками, СКД и клиент-серверное взаимодействие. Разберем основные моменты:
F9) и табло (Shift+F9) в отладчике 1С. Это поможет вам отследить состав ТаблицыЗначений, проверить правильность заполнения параметров макета и выявить ошибки на ранних этапах. В частности, после вызова ПолучитьОтфильтрованныеДанныеСписка() установите точку останова и проанализируйте содержимое РезультатКомпоновки, чтобы убедиться, что оно содержит именно те данные, которые вам нужны.Решение этой задачи требует понимания нескольких подсистем 1С, но, следуя описанным шагам и рекомендациям, вы сможете успешно реализовать вывод на печать отфильтрованных данных с использованием ваших собственных макетов.