Как не выводить область или избежать ее дублирования в печатной форме 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке печатных форм в системе «1С:Предприятие 8» программисты часто сталкиваются с ситуацией, когда данные для вывода хранятся в виде плоской таблицы (например, табличной части документа или таблицы значений с начислениями). При обычном обходе такой коллекции в цикле одна и та же область макета (например, шапка раздела, строка с показателем или специфическая надбавка) может выводиться несколько раз. Это приводит к визуальному искажению печатной формы и некорректному отображению данных.

В этой статье мы подробно разберем, как решить проблему избыточного вывода областей макета. Мы проанализируем различные подходы — от простых логических флагов до глубокой оптимизации запросов и алгоритмов работы с коллекциями данных в соответствии со стандартами разработки фирмы «1С».

Постановка задачи

Представим типовой сценарий: у нас есть таблица начислений сотрудников. Нам необходимо вывести в печатную форму область Вредность только один раз для конкретного сотрудника, указав размер его надбавки за вредные условия труда. Если у сотрудника есть такая надбавка, мы выводим ее реальный размер. Если надбавки нет (или сотрудник отсутствует в таблице), область все равно должна быть выведена один раз, но со значением "0%".

Посмотрим на исходный (проблемный) вариант кода, с которого часто начинают начинающие разработчики:


Обл = Макет.ПолучитьОбласть("Вредность");
Для Каждого СтрокаТаб Из ТаблицаНачислений Цикл
    Если СтрокаТаб.Сотрудник.ФизическоеЛицо.Наименование = ПараметрыМакета.Работник Тогда
        Если СтрокаТаб.Начисление = "Надбавка за вредность" Тогда
            Обл.Параметры.Размер = СтрокаТаб.Размер;
            ТабличныйДокумент.Вывести(Обл);
        Иначе
            Обл.Параметры.Размер = "0%";
            //ТабличныйДокумент.Вывести(Обл); // Приводит к дублированию или неверному выводу
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

В чем здесь проблема? Поскольку один и тот же сотрудник может встречаться в таблице начислений несколько раз (по разным видам расчетных листков или начислений), проверка выполняется на каждом шаге цикла. В результате область макета выводится в табличный документ столько раз, сколько строк по данному сотруднику найдено в таблице. Выясним, как решить эту проблему правильно.

Решение №1. Использование булевого флага (классический подход)

Самый очевидный способ избежать повторного вывода области — контролировать факт ее вывода с помощью вспомогательной переменной булевого типа. Назовем ее ОбластьВыводилась.

Разберем этот алгоритм по шагам:

  1. До начала цикла инициализируем переменную ОбластьВыводилась значением Ложь.
  2. Внутри цикла проверяем: если условие для вывода выполняется и область еще не выводилась (Не ОбластьВыводилась), то мы заполняем параметры области, выводим ее в ТабличныйДокумент и устанавливаем флаг в значение Истина.
  3. При последующих итерациях цикла условие Если Не ОбластьВыводилась вернет Ложь, и повторного вывода не произойдет.

Посмотрим на практическую реализацию этого метода:


Обл = Макет.ПолучитьОбласть("Вредность");
ОбластьВыводилась = Ложь;

Для Каждого СтрокаТаб Из ТаблицаНачислений Цикл
    Если Не ОбластьВыводилась Тогда
        Если СтрокаТаб.Сотрудник.ФизическоеЛицо.Наименование = ПараметрыМакета.Работник Тогда
            Если СтрокаТаб.Начисление = "Надбавка за вредность" Тогда
                Обл.Параметры.Размер = СтрокаТаб.Размер;
            Иначе
                Обл.Параметры.Размер = "0%";
            КонецЕсли;
            
            ТабличныйДокумент.Вывести(Обл);
            ОбластьВыводилась = Истина; // Гарантируем, что вывод произойдет ровно один раз
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

Этот вариант прост для понимания и быстро решает локальную задачу. Однако у него есть существенный недостаток: если в таблице начислений первой строкой сотрудника идет начисление, отличное от вредности (например, «Оклад»), программа сразу же выведет "0%" и установит флаг в значение Истина. Если во второй строке будет находиться реальная «Надбавка за вредность», она уже не попадет в печатную форму, так как сработает ограничение флага. Поэтому перейдем к более надежным методам.

Решение №2. Поиск данных и вывод области после цикла

Чтобы избежать логических ошибок, связанных с порядком строк в коллекции, правильнее сначала собрать и обработать всю необходимую информацию, определить финальное значение параметра и только после этого вывести область в табличный документ.

Рассмотрим пример реализации с использованием оператора Прервать:


Обл = Макет.ПолучитьОбласть("Вредность");
// Устанавливаем значение по умолчанию
Обл.Параметры.Размер = "0%"; 

Для Каждого СтрокаТаб Из ТаблицаНачислений Цикл
    Если СтрокаТаб.Сотрудник.ФизическоеЛицо.Наименование = ПараметрыМакета.Работник Тогда
        Если СтрокаТаб.Начисление = "Надбавка за вредность" Тогда
            Обл.Параметры.Размер = СтрокаТаб.Размер;
            Прервать; // Мы нашли то, что искали, дальнейший перебор не имеет смысла
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

// Выводим область ровно один раз после того, как все данные проверены
ТабличныйДокумент.Вывести(Обл);

Преимущества этого подхода: мы гарантируем ровно один вызов метода Вывести(). Код стал более читаемым, исчезла необходимость в поддержке лишних булевых переменных состояния.

Решение №3. Использование встроенных методов поиска коллекций

Вместо ручного перебора таблицы значений в цикле Для Каждого более эффективно использовать специализированные методы поиска платформы 1С, такие как НайтиСтроки().

Для этого в нашей таблице начислений должна быть колонка, по которой мы можем однозначно идентифицировать сотрудника (лучше всего использовать ссылку на справочник Сотрудники или строковое представление, если поиск идет по имени). Рассмотрим пример:


Обл = Макет.ПолучитьОбласть("Вредность");

// Формируем структуру для отбора строк по сотруднику и нужному начислению
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Сотрудник", ПараметрыМакета.РаботникСсылка);
ПараметрыОтбора.Вставить("Начисление", "Надбавка за вредность");

НайденныеСтроки = ТаблицаНачислений.НайтиСтроки(ПараметрыОтбора);

Если НайденныеСтроки.Количество() > 0 Тогда
    // Берем значение из первой найденной строки
    Обл.Параметры.Размер = НайденныеСтроки[0].Размер;
Иначе
    Обл.Параметры.Размер = "0%";
КонецЕсли;

ТабличныйДокумент.Вывести(Обл);

Метод НайтиСтроки() работает значительно быстрее ручного перебора за счет оптимизации внутренних механизмов платформы. К тому же этот подход делает код лаконичным и исключает вложенные конструкции Если...КонецЕсли.


Критический анализ производительности и стандарты разработки 1С

Посмотрим на представленные выше решения с точки зрения профессиональной разработки на платформе «1С:Предприятие 8». В коде из первого сообщения форума содержится критическая архитектурная ошибка:

СтрокаТаб.Сотрудник.ФизическоеЛицо.Наименование

Почему обращение через несколько точек («разыменование ссылок») в цикле — это плохо?

При каждом проходе цикла система вынуждена делать неявный (скрытый) SQL-запрос к базе данных. Сначала платформа считывает данные объекта Сотрудник, затем обращается к таблице ФизическоеЛицо, и только потом получает текстовое поле Наименование. Если в вашей таблице начислений будет 100 или 1000 строк, сервер сделает тысячи дополнительных запросов к СУБД. Это приведет к колоссальному падению производительности и зависанию процесса формирования печатной формы.

Как оптимизировать этот процесс?

1. Использование запросов (Лучшая практика)

Логика подготовки данных должна быть полностью отделена от логики их визуального представления. Все необходимые данные для печатной формы нужно получать одним пакетным запросом перед началом вывода макета. В запросе можно сразу связать таблицы, выполнить группировку, исключить дубли и отобрать только нужные сведения.

Пример оптимальной структуры кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Начисления.Размер КАК Размер
    |ИЗ
    |	РегистрРасчета.Начисления КАК Начисления
    |ГДЕ
    |	Начисления.Сотрудник = &Сотрудник
    |	И Начисления.ВидРасчета = &ВидРасчетаВредность";

Запрос.УстановитьПараметр("Сотрудник", ПараметрыМакета.Сотрудник);
Запрос.УстановитьПараметр("ВидРасчетаВредность", ПланыВидовРасчета.Начисления.НадбавкаЗаВредность);

Выборка = Запрос.Выполнить().Выбрать();

Обл = Макет.ПолучитьОбласть("Вредность");
Если Выборка.Следующий() Тогда
    Обл.Параметры.Размер = Выборка.Размер;
Иначе
    Обл.Параметры.Размер = "0%";
КонецЕсли;

ТабличныйДокумент.Вывести(Обл);

2. Использование коллекции «Соответствие» (Map)

Если вам все же приходится работать с готовой коллекцией данных на клиенте или сервере без возможности написать прямой запрос, воспользуйтесь коллекцией Соответствие. Это позволит предварительно подготовить данные и обращаться к ним по ключу (ссылке на сотрудника) за константное время O(1), избегая вложенных поисков:


ТаблицаВредности = Новый Соответствие;

// В один проход заполняем соответствие
Для Каждого СтрокаТЧ Из ТаблицаНачислений Цикл
    Если СтрокаТЧ.Начисление = "Надбавка за вредность" Тогда
        ТаблицаВредности.Вставить(СтрокаТЧ.Сотрудник, СтрокаТЧ.Размер);
    КонецЕсли;
КонецЦикла;

// При выводе области макета мгновенно получаем значение по ключу-ссылке
РазмерВредности = ТаблицаВредности[ПараметрыМакета.Сотрудник];

Обл = Макет.ПолучитьОбласть("Вредность");
Обл.Параметры.Размер = ?(РазмерВредности <> Неопределено, РазмерВредности, "0%");
ТабличныйДокумент.Вывести(Обл);

Резюме

Для предотвращения дублирования областей макета при выводе печатных форм используйте принцип «сначала подготовка данных — затем вывод». Старайтесь избегать использования громоздких циклов с проверками флагов непосредственно в процессе заполнения табличного документа. Всегда помните о производительности: не допускайте обращений к реквизитам через точку внутри циклов и отдавайте предпочтение использованию запросов или коллекций быстрого доступа (Соответствие).

← На главную