При работе с системой 1С часто возникает задача формирования отчетов, где данные по каждому документу или логическому блоку должны быть представлены обособленно. Иногда требуется, чтобы каждый документ начинался с новой страницы в рамках одного большого печатного документа, а иногда – чтобы каждый документ открывался в совершенно новой вкладке или окне пользовательского интерфейса. Давайте разберем эти два подхода подробно.
Этот подход является идеальным, когда мы стремимся сформировать один общий табличный документ (например, для последующей печати), но при этом хотим, чтобы каждый новый документ или группа данных всегда начиналась с новой страницы.
Один из самых элегантных и простых способов автоматического разбиения на страницы заключается в использовании встроенного свойства макета. Рассмотрим подробнее, как мы можем это сделать:
Откройте макет: Для начала нам потребуется открыть макет, который вы используете для формирования вашего отчета. Это может быть макет общего модуля, обработки или отчета.
Выберите область: В макете определите ту область, которая служит заголовком или первой строкой каждого нового документа. Например, если у вас есть область с именем СтрокаДокумент, которая содержит информацию о документе (номер, дата и т.д.), то именно ее мы и будем использовать.
Установите свойство: В окне свойств выбранной области (обычно оно открывается двойным щелчком по области или через контекстное меню) найдите параметр "Начало страницы" и установите для него значение "Истина" (поставьте соответствующий флажок).
Механизм работы: Теперь, когда платформа 1С будет выводить эту область с установленным флагом, она автоматически вставит разрыв страницы перед ней. Это произойдет, если только эта область не является самой первой в общем документе. Таким образом, каждый новый документ будет начинаться с чистого листа.
Пример кода (концептуальный):
// Предполагаем, что "Документ" здесь - это переменная типа ТабличныйДокумент
ДокументОтчета = Новый ТабличныйДокумент;
// Получаем области макета.
// Для области "СтрокаДокумент" мы заранее установили флаг "НачалоСтраницы".
ОбластьДок = Макет.ПолучитьОбласть("СтрокаДокумент");
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
// Проходим по выборке документов
Пока ВыборкаДокументов.Следующий() Цикл
// Перед выводом ОбластьДок для каждого нового документа
// система автоматически вставит разрыв страницы благодаря флагу "НачалоСтраницы"
ОбластьДок.Параметры.Заполнить(ВыборкаДокументов);
ДокументОтчета.Вывести(ОбластьДок);
// Теперь выводим строки табличной части текущего документа
Для Каждого СтрокаТЧ Из ВыборкаДокументов.Ссылка.Товары Цикл // Предполагаем, что есть табличная часть "Товары"
ОбластьСтрока.Параметры.Номенклатура = СтрокаТЧ.Номенклатура.Наименование;
ОбластьСтрока.Параметры.Колво = СтрокаТЧ.Количество;
ДокументОтчета.Вывести(ОбластьСтрока, 1); // Указываем уровень группировки, если нужно
КонецЦикла;
КонецЦикла;
// После формирования всех страниц, показываем итоговый ТабличныйДокумент
ДокументОтчета.Показать("Отчет по документам с разбивкой по страницам");
Важные моменты: Мы должны убедиться, что область с установленным флагом "Начало страницы" действительно является первой, которая выводится для каждого нового логического блока или документа. Также стоит проверить, что она не вложена в другие области с аналогичными свойствами, которые могут влиять на поведение разбиения страниц.
Если автоматическое разбиение с помощью свойства "Начало страницы" по какой-то причине не подходит, или нам требуется более гранулированный контроль над разрывами страниц, мы можем применить метод ВывестиГоризонтальныйРазделительСтраниц() объекта ТабличныйДокумент.
Принцип работы: Этот метод выполняет одну простую, но важную функцию – он принудительно вставляет горизонтальный разрыв страницы в тот момент, когда мы его вызываем в коде.
Когда вызывать: Наиболее логичное место для вызова этого метода – после того, как все данные по текущему документу (или логическому блоку) были полностью выведены в ТабличныйДокумент, но до того, как мы начнем выводить данные следующего.
Исключение для первого документа: Мы должны помнить, что для самого первого документа в нашей выборке разделитель страниц не нужен, поскольку он и так будет начинаться с начала документа. Вызов разделителя должен производиться только перед вторым, третьим и всеми последующими документами.
Пример кода:
// Создаем один экземпляр ТабличногоДокумента, в который будем выводить весь отчет
ДокументОтчета = Новый ТабличныйДокумент;
// Получаем области из макета
Макет = ПолучитьМакет("МакетДокумента"); // Имя вашего макета
ОбластьДок = Макет.ПолучитьОбласть("СтрокаДокумент");
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
ОбластьПодСтрокой = Макет.ПолучитьОбласть("ПодСтрокой"); // Дополнительная область
// Флаг для отслеживания первого документа
ЭтоПервыйДокумент = Истина;
// Предполагаем, что Выборка - это результат запроса, содержащий документы и их данные
Пока Выборка.Следующий() Цикл
// Проверяем, не является ли текущий документ первым
Если Не ЭтоПервыйДокумент Тогда
// Если это не первый документ, то перед его выводом вставляем разделитель страниц
ДокументОтчета.ВывестиГоризонтальныйРазделительСтраниц();
КонецЕсли;
// После обработки первого документа устанавливаем флаг в Ложь
ЭтоПервыйДокумент = Ложь;
// Выводим заголовок текущего документа
ОбластьДок.Параметры.Заполнить(Выборка); // Заполняем параметры из текущей выборки
ДокументОтчета.Вывести(ОбластьДок);
// Выводим строки табличной части "Товары" текущего документа
// Предполагается, что Выборка.ДокСсылка.Товары доступна и содержит табличную часть
Для Каждого СтрокаТЧ Из Выборка.ДокСсылка.Товары Цикл
ОбластьСтрока.Параметры.Номенклатура = СтрокаТЧ.Наименование;
ОбластьСтрока.Параметры.Колво = СтрокаТЧ.Количество;
ДокументОтчета.Вывести(ОбластьСтрока, 1);
КонецЦикла;
// Выводим дополнительные детальные записи
// Здесь предполагается, что "ВыборкаДетальныеЗаписи" как-то связана с текущим документом
// и содержит данные для области "ПодСтрокой". Это может быть отдельный запрос
// или данные, полученные ранее для текущего документа.
Для Каждого Стр Из ВыборкаДетальныеЗаписи.Спецификация.ПриходМатериалов Цикл // Пример из исходного сообщения
ОбластьПодСтрокой.Параметры.Материал = Стр.Наименование;
ОбластьПодСтрокой.Параметры.Сумма = Стр.Количество * Выборка.Цена; // Пример использования данных из Выборка
ДокументОтчета.Вывести(ОбластьПодСтрокой, 2);
КонецЦикла;
КонецЦикла;
// В конце показываем сформированный ТабличныйДокумент
ДокументОтчета.Показать("Отчет с разбивкой по документам");
Важно: При использовании этого метода критически важно работать с одним и тем же экземпляром объекта ТабличныйДокумент (в нашем примере это ДокументОтчета) на протяжении всего процесса формирования отчета. Если мы создадим новый ТабличныйДокумент внутри цикла или будем вызывать разделитель для одного объекта, а выводить области в другой, желаемый результат не будет достигнут. Также всегда стоит проверить пользовательские настройки печати в режиме предприятия, так как они могут влиять на отображение разрывов страниц.
Данный подход полностью соответствует уточненному запросу автора темы. Если требуется, чтобы каждый сформированный отчет по отдельному документу открывался в отдельном окне или вкладке в интерфейсе 1С, то нам необходимо генерировать отдельный экземпляр ТабличныйДокумент для каждого документа из нашей выборки. Затем каждый такой сформированный документ показывается пользователю индивидуально.
Определяем массив документов: На первом шаге мы собираем список ссылок на все документы, для которых планируем сформировать отдельные отчеты. Это может быть массив ссылок, полученный из табличной части формы, из результата запроса или из другого источника.
Цикл по документам: Затем мы организуем цикл, который будет последовательно обрабатывать каждый документ из нашего массива.
Создание нового ТабличногоДокумента: Ключевой момент: внутри этого цикла, для каждого документа, мы создаем совершенно новый, независимый экземпляр объекта ТабличныйДокумент. Это гарантирует, что каждый отчет будет формироваться с нуля и будет независим от предыдущих.
Формирование отчета для одного документа: В созданный на предыдущем шаге ТабличныйДокумент мы выводим все необходимые области макета, которые относятся исключительно к текущему обрабатываемому документу.
Отображение отчета: После того как отчет для конкретного документа полностью сформирован, мы используем метод ТабличныйДокумент.Показать("ЗаголовокОтчета"). Каждый такой вызов будет открывать сформированный отчет в отдельном окне или вкладке в зависимости от настроек клиента 1С.
Рассмотрим пример, который показывает, как реализовать этот подход. Обычно это требует взаимодействия между клиентским и серверным контекстом, так как формирование ТабличногоДокумента происходит на сервере, а его отображение – на клиенте.
// ************ КЛИЕНТСКАЯ ПРОЦЕДУРА ************
&НаКлиенте
Процедура Сформировать(Команда)
// 1. Получаем массив ссылок на документы, для которых нужно сформировать отчеты.
// Эту функцию вам нужно будет реализовать, чтобы она возвращала нужные ссылки.
МассивДокументовДляОтчета = ПолучитьМассивДокументовИзВыборкиНаСервере();
// 2. Итерируем по каждому документу в массиве
Для Каждого СсылкаНаДокумент Из МассивДокументовДляОтчета Цикл
// 3. Для каждого документа вызываем серверную функцию,
// которая сформирует ТабличныйДокумент для ЭТОГО КОНКРЕТНОГО объекта.
ТабличныйДокументДляПоказа = СформироватьОтчетНаСервере(СсылкаНаДокумент);
// 4. Если ТабличныйДокумент успешно сформирован (не пустой и не Неопределено)
Если ТабличныйДокументДляПоказа <> Неопределено Тогда
// 5. Показываем сформированный ТабличныйДокумент.
// Каждый вызов Показать() откроет отчет в отдельном окне или вкладке.
// Желательно использовать уникальный заголовок, чтобы различать вкладки.
ТабличныйДокументДляПоказа.Показать("Отчет по документу № " + СсылкаНаДокумент.Номер);
Иначе
Сообщить("Не удалось сформировать отчет для документа: " + СсылкаНаДокумент);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// ************ СЕРВЕРНЫЕ ФУНКЦИИ ************
// Вспомогательная функция, которая на сервере получает массив ссылок на документы.
// Вы можете адаптировать этот запрос под свои нужды (фильтры, условия и т.д.).
&НаСервере
Функция ПолучитьМассивДокументовИзВыборкиНаСервере()
МассивДокументов = Новый Массив;
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| ДокументРеализацияТоваровУслуг.Ссылка,
| ДокументРеализацияТоваровУслуг.Номер
|ИЗ
| Документ.РеализацияТоваровУслуг КАК ДокументРеализацияТоваровУслуг
|ГДЕ
| ДокументРеализацияТоваровУслуг.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
|УПОРЯДОЧИТЬ ПО
| ДокументРеализацияТоваровУслуг.Дата";
Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ТекущаяДата() - 30 * День)); // За последний месяц
Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ТекущаяДата()));
ВыборкаЗапроса = Запрос.Выполнить().Выбрать();
Пока ВыборкаЗапроса.Следующий() Цикл
МассивДокументов.Добавить(ВыборкаЗапроса.Ссылка);
// Для примера ограничим количество, чтобы не открывать слишком много окон
Если МассивДокументов.Количество() > 5 Тогда
Прервать;
КонецЕсли;
КонецЦикла;
Возврат МассивДокументов;
КонецФункции
// Основная серверная функция, которая формирует ОДИН ТабличныйДокумент для ОДНОГО переданного документа.
&НаСервере
Функция СформироватьОтчетНаСервере(СсылкаНаДокумент)
Перем ТабДок;
Попытка
// 1. Создаем НОВЫЙ ТабличныйДокумент для каждого вызова этой функции
ТабДок = Новый ТабличныйДокумент;
// 2. Получаем макет, который будет использоваться для вывода
МакетОтчета = ПолучитьМакет("МакетОтчетаДляДокумента"); // Укажите имя вашего макета
// 3. Получаем области макета
ОбластьЗаголовокДокумента = МакетОтчета.ПолучитьОбласть("ЗаголовокДокумента");
ОбластьСтрокаТовара = МакетОтчета.ПолучитьОбласть("СтрокаТовара");
ОбластьДетализация = МакетОтчета.ПолучитьОбласть("Детализация"); // Дополнительная область
// 4. Получаем сам объект документа по ссылке для удобства заполнения параметров
ОбъектДокумент = СсылкаНаДокумент.ПолучитьОбъект();
Если ОбъектДокумент = Неопределено Тогда
Сообщить("Документ по ссылке не найден: " + СсылкаНаДокумент, СтатусСообщения.Внимание);
Возврат Неопределено;
КонецЕсли;
// 5. Заполняем параметры области заголовка документа и выводим ее
ОбластьЗаголовокДокумента.Параметры.Заполнить(ОбъектДокумент);
ТабДок.Вывести(ОбластьЗаголовокДокумента);
// 6. Выводим строки табличной части "Товары" текущего документа
Для Каждого СтрокаТЧ Из ОбъектДокумент.Товары Цикл
ОбластьСтрокаТовара.Параметры.Номенклатура = СтрокаТЧ.Номенклатура.Наименование;
ОбластьСтрокаТовара.Параметры.Количество = СтрокаТЧ.Количество;
ОбластьСтрокаТовара.Параметры.Цена = СтрокаТЧ.Цена;
ОбластьСтрокаТовара.Параметры.Сумма = СтрокаТЧ.Сумма;
ТабДок.Вывести(ОбластьСтрокаТовара);
КонецЦикла;
// 7. Выводим дополнительные детальные записи, если они есть.
// Этот блок требует адаптации под вашу конкретную логику получения данных,
// возможно, через отдельный запрос, фильтрованный по текущему документу.
// Пример (псевдокод, нужно заменить на реальную выборку):
// ЗапросДетализации = Новый Запрос;
// ЗапросДетализации.Текст = "ВЫБРАТЬ ... ИЗ ... ГДЕ ... СсылкаНаОсновнойДокумент = &Ссылка";
// ЗапросДетализации.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
// ВыборкаДетальныхЗаписей = ЗапросДетализации.Выполнить().Выбрать();
//
// Пока ВыборкаДетальныхЗаписей.Следующий() Цикл
// ОбластьДетализация.Параметры.Заполнить(ВыборкаДетальныхЗаписей);
// ТабДок.Вывести(ОбластьДетализация);
// КонецЦикла;
// 8. Возвращаем сформированный ТабличныйДокумент на клиент
Возврат ТабДок;
Исключение
Сообщить("Ошибка при формировании отчета для документа " + СсылкаНаДокумент + ": " + ОписаниеОшибки(), СтатусСообщения.Ошибка);
Возврат Неопределено;
КонецПопытки;
КонецФункции
Объяснение: В клиентской процедуре Сформировать мы сначала получаем массив ссылок на документы. Далее, в цикле для каждой ссылки, мы вызываем серверную функцию СформироватьОтчетНаСервере. Эта серверная функция формирует отдельный экземпляр ТабличныйДокумент для каждого конкретного документа, заполняет его данными и возвращает на клиент. На клиенте, после получения сформированного ТабличныйДокумент, мы вызываем его метод Показать(). Каждый такой вызов открывает новый отчет в отдельной вкладке или окне.
Примечание о параметрах: Мы должны обеспечить, чтобы все запросы и выборки данных внутри серверной функции СформироватьОтчетНаСервере были корректно отфильтрованы по текущему обрабатываемому документу (СсылкаНаДокумент), а не по какой-либо общей выборке, чтобы избежать смешивания данных.
При реализации вывода отчетов в 1С мы можем столкнуться с рядом моментов, которые стоит учитывать, чтобы избежать ошибок и добиться желаемого результата:
Различие "лист" и "вкладка": Это наиболее частая причина недопонимания. Важно четко различать, нужна ли вам новая физическая страница внутри одного печатного документа (подход 1) или новый логический элемент интерфейса 1С (отдельная вкладка/окно) для каждого отчета (подход 2). Эти задачи решаются принципиально разными способами.
Экземпляры ТабличныйДокумент:
ТабличныйДокумент на протяжении всего процесса его формирования.ТабличныйДокумент.Производительность: При формировании и открытии большого количества отдельных вкладок или окон (например, сотни или тысячи документов) клиент 1С может столкнуться со значительными проблемами производительности и потребления памяти. Это может привести к замедлению работы или даже к сбою приложения. В таких случаях стоит рассмотреть альтернативные решения, например, формирование одного большого документа с разрывами страниц, сохранение отчетов в отдельные файлы или интерактивный вывод отчета по одному документу по требованию.
Настройки печати пользователя: В режиме предприятия пользователи могут иметь индивидуальные настройки печати или предварительного просмотра, которые способны повлиять на отображение программно вставленных разрывов страниц. Всегда рекомендуется проверять результат в стандартном режиме просмотра отчетов 1С.
Передача данных между клиентом и сервером: При работе в клиент-серверной архитектуре помните, что объекты ТабличныйДокумент успешно передаются между клиентом и сервером. Однако передача больших массивов ссылок документов или других объемных данных должна быть оптимизирована, чтобы минимизировать сетевой трафик и задержки.
Выбирайте подход, который наиболее точно соответствует вашей задаче, и внимательно следите за созданием и использованием экземпляров объекта ТабличныйДокумент.