Разберем распространенную и крайне неприятную проблему, с которой часто сталкиваются разработчики при формировании печатных форм, отчетов или обработок в системе 1С:Предприятие. Представьте ситуацию: вы выводите строки в табличный документ в цикле, заполняя параметры области макета на основе некоторых условий. Но при формировании документа выясняется, что если для текущей строки условие заполнения параметра не сработало (вернуло Ложь), в ячейку все равно выводится значение из предыдущей строки! Происходит так называемое «залипание» или кэширование данных.
В этой статье мы подробно проанализируем причины такого поведения платформы 1С и рассмотрим несколько эффективных способов решения проблемы с примерами готового кода.
Чтобы понять, почему в табличном документе дублируются данные из предыдущих итераций цикла, давайте заглянем «под капот» объектной модели 1С.
Когда мы получаем область макета с помощью метода ПолучитьОбласть(), платформа создает в оперативной памяти программный объект типа ОбластьЯчеекТабличногоДокумента. Если этот метод вызывается один раз до начала цикла обхода данных, мы работаем с одним и тем же экземпляром объекта в памяти на протяжении всего цикла.
Присваивая значения свойствам объекта Параметры внутри цикла, мы перезаписываем только те параметры, для которых явно сработало условие Если...Тогда. Те же параметры, которые в текущей итерации под условие не попали, остаются нетронутыми — они продолжают хранить значения, присвоенные им на предыдущем шаге цикла. При вызове метода Вывести() платформа берет текущее состояние объекта области и копирует его в результирующий табличный документ вместе со всеми «старыми» данными.
Таким образом, наша главная задача — гарантировать, что перед выводом очередной строки макета все её параметры будут либо очищены, либо заполнены актуальными (в том числе пустыми) значениями.
---
Посмотрим на пример, который считается наиболее лаконичным и надежным с точки зрения производительности. Перед тем как приступать к проверке условий и заполнению параметров, мы принудительно очищаем их в самом начале тела цикла, присваивая им пустые строки или значения по умолчанию.
Рассмотрим подробнее программный код реализации этого метода:
// Получаем область макета до начала цикла
ОбластьСтрокаДоНадбавок = Макет.ПолучитьОбласть("Строка");
Пока ВыборкаСтрок.Следующий() Цикл
// Шаг 1. Сбрасываем значения параметров перед каждым заполнением
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавка = "";
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавкаЧасовая = "";
// Шаг 2. Заполняем параметры на основании условий
Если ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Месячная
ИЛИ ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Дневная Тогда
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавка = ПолучитьСтрокуИзДвухЧисел(МинСт, МаксСт);
ИначеЕсли ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Часовая Тогда
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавкаЧасовая = ПолучитьСтрокуИзДвухЧисел(МинСт, МаксСт);
КонецЕсли;
// Шаг 3. Выводим подготовленную область в табличный документ
ТабДок.Вывести(ОбластьСтрокаДоНадбавок);
КонецЦикла;
Преимущества этого подхода: мы пишем минимальное количество кода, структура условий Если...ИначеЕсли остается простой и понятной, а параметры гарантированно очищаются на каждой итерации.
---
Если по какой-то причине вы не хотите объявлять очистку переменных в начале цикла, можно пойти по пути явного указания альтернативных значений во всех ветках алгоритма. Разберем два варианта реализации такого подхода.
Мы можем разделить проверку условий для каждого параметра на независимые конструкции Если...Иначе. Если условие для записи значения выполняется — записываем его, во всех остальных случаях (ветка Иначе) — записываем пустую строку.
Пока ВыборкаСтрок.Следующий() Цикл
// Условие для месячной/дневной тарифной ставки
Если ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Месячная
ИЛИ ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Дневная Тогда
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавка = ПолучитьСтрокуИзДвухЧисел(МинСт, МаксСт);
Иначе
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавка = "";
КонецЕсли;
// Условие для часовой тарифной ставки
Если ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Часовая Тогда
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавкаЧасовая = ПолучитьСтрокуИзДвухЧисел(МинСт, МаксСт);
Иначе
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавкаЧасовая = "";
КонецЕсли;
ТабДок.Вывести(ОбластьСтрокаДоНадбавок);
КонецЦикла;
Мы также можем использовать единую логическую цепочку, но при этом внутри каждого условия мы должны явно прописать состояние для всех связанных параметров:
Пока ВыборкаСтрок.Следующий() Цикл
Если ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Месячная
ИЛИ ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Дневная Тогда
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавка = ПолучитьСтрокуИзДвухЧисел(МинСт, МаксСт);
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавкаЧасовая = 0; // или ""
ИначеЕсли ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Часовая Тогда
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавкаЧасовая = ПолучитьСтрокуИзДвухЧисел(МинСт, МаксСт);
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавка = 0; // или ""
Иначе // Если не подошло ни одно из условий
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавкаЧасовая = 0;
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавка = 0;
КонецЕсли;
ТабДок.Вывести(ОбластьСтрокаДоНадбавок);
КонецЦикла;
Важное примечание: Этот способ более громоздкий и требует от разработчика повышенного внимания. Если в макет добавится новый параметр, придется дописывать его очистку во все существующие ветки условий, что повышает риск возникновения ошибок.
---
Существует еще один простой, но кардинальный метод решения проблемы «памяти» параметров — получать область макета заново на каждом шаге цикла.
Пока ВыборкаСтрок.Следующий() Цикл
// Получаем НОВЫЙ экземпляр области на каждой итерации
ОбластьСтрокаДоНадбавок = Макет.ПолучитьОбласть("Строка");
Если ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Месячная
ИЛИ ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Дневная Тогда
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавка = ПолучитьСтрокуИзДвухЧисел(МинСт, МаксСт);
ИначеЕсли ВыборкаСтрок.ВидТарифнойСтавки = Перечисления.ВидыТарифныхСтавок.Часовая Тогда
ОбластьСтрокаДоНадбавок.Параметры.ОкладТарифнаяСтавкаЧасовая = ПолучитьСтрокуИзДвухЧисел(МинСт, МаксСт);
КонецЕсли;
ТабДок.Вывести(ОбластьСтрокаДоНадбавок);
КонецЦикла;
Проанализируем ситуацию: поскольку метод ПолучитьОбласть() вызывается внутри цикла, переменная ОбластьСтрокаДоНадбавок каждый раз инициализируется «чистым» объектом, в котором все параметры имеют значения по умолчанию. Нам больше не нужно заботиться о ручном обнулении параметров.
Внимание! У этого способа есть существенный недостаток. Многократный вызов метода ПолучитьОбласть() создает дополнительную нагрузку на сервер приложений 1С. Если вы формируете документ на сотни или тысячи строк, этот подход может заметно снизить скорость работы алгоритма. Используйте его только для небольших печатных форм.
---
Помимо стандартных условных операторов, в арсенале разработчика 1С есть еще несколько удобных инструментов управления данными в макетах.
Заполнить() для параметров области.
Если у вас есть структура или выборка из запроса, имена полей которой совпадают с именами параметров в макете, вы можете использовать метод Область.Параметры.Заполнить(ИсточникДанных). Перед этим достаточно подготовить структуру, заполнив её поля значениями по умолчанию, а затем переопределить только нужные свойства. Это позволяет избежать ручного написания множества строк кода присваивания.
0 вместо пустой строки "". При этом, чтобы пустые ячейки не рябили нулями, настройте свойство ячейки Формат в Конфигураторе. Достаточно указать параметр форматной строки ЧН= (Представление ноля — пустая строка). Платформа сама скроет нулевые значения при выводе, и визуально документ будет выглядеть аккуратно без усложнения программного кода.
СтрокаОклад и СтрокаЧасовая). В коде вам останется лишь выбирать, какую именно область получить и вывести для текущего сотрудника. Это полностью исключит пересечение несовместимых параметров в одной строке.
Подводя итог, отметим: наиболее производительным и лаконичным решением является Способ 1 (предварительная инициализация параметров в начале цикла). Он сочетает в себе высокую скорость работы алгоритма и простоту поддержки кода в будущем.