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