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

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

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

Почему исходный код работал некорректно?

Автор исходного сообщения столкнулся с распространенной проблемой: его код для условного оформления динамического списка, который опирался на реквизит табличной части, срабатывал только в том случае, если этот реквизит находился в первой строке табличной части документа. Давайте выясним причину такого поведения.

Когда мы используем


ПолеКомпоновкиДанных("ТЧ.РеквизитТЧ.Данные")
в отборе условного оформления динамического списка, мы неявно указываем Системе Компоновки Данных (СКД) динамического списка, что ей необходимо соединить основную таблицу списка (например, таблицу документов) с табличной частью. Если это соединение выполняется без агрегирующих функций (таких как

МАКСИМУМ
,

КОЛИЧЕСТВО
) или явного условия

СГРУППИРОВАТЬ ПО
, СКД может "увидеть" только одну строку табличной части для каждого документа, обычно первую попавшуюся в результате соединения. Таким образом, если нужный реквизит присутствует во второй или последующих строках табличной части, но отсутствует в первой, условие отбора не будет выполнено для всего документа, и условное оформление не сработает.

Приведем пример исходного программного кода, который демонстрировал эту проблему:


СписокНомерОдин = Новый СписокЗначений;
СписокНомерОдин.Добавить(ЗначениеОдин);    // Предположим, ЗначениеОдин - это конкретное значение, отсутствие которого мы проверяем

СписокНомерДва = Новый СписокЗначений;
СписокНомерДва.Добавить(ЗначениеДва);     // Предположим, ЗначениеДва - это конкретное значение, наличие которого мы проверяем

УО = ЭтотОбъект.Список.КомпоновщикНастроек.ФиксированныеНастройки.УсловноеОформление.Элементы;

// Первое правило оформления: если реквизита нет в списке ЗначениеОдин
ЭлементыОформления = УО.Добавить();
ЭлементыОформления.Использование = Истина;

ГруппаОтбора = ЭлементыОформления.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
ГруппаОтбора.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИ;

ЭлементОтбора = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ТЧ.РеквизитТЧ.Данные"); // Вот здесь проблема
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.НЕВСписке;
ЭлементОтбора.Использование = Истина;
ЭлементОтбора.ПравоеЗначение = СписокНомерОдин;    

Оформление = ЭлементыОформления.Оформление;
Оформление.УстановитьЗначениеПараметра(Новый ПараметрКомпоновкиДанных("ЦветФона"), WebЦвета.СветлоЖелтый);

// Второе правило оформления: если реквизит есть в списке ЗначениеДва
ЭлементыОформления = УО.Добавить();
ЭлементыОформления.Использование = Истина;

ГруппаОтбора = ЭлементыОформления.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
ГруппаОтбора.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИ;

ЭлементОтбора = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ТЧ.РеквизитТЧ.Данные"); // И здесь та же проблема
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;
ЭлементОтбора.Использование = Истина;
ЭлементОтбора.ПравоеЗначение = СписокНомерДва;    

Оформление = ЭлементыОформления.Оформление;
Оформление.УстановитьЗначениеПараметра(Новый ПараметрКомпоновкиДанных("ЦветФона"), WebЦвета.СветлоРозовый);

В этом коде


Новый ПолеКомпоновкиДанных("ТЧ.РеквизитТЧ.Данные")
обращается напрямую к полю табличной части. Без явной агрегации или группировки в запросе, СКД не может корректно оценить условие для всего документа, если реквизит может находиться в разных строках табличной части.

Оптимальный подход: Модификация запроса динамического списка

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

Рассмотрим несколько вариантов модификации запроса (отладить их поможет удобная консоль запросов — для этого есть универсальная консоль запросов и компоновки данных):

  1. Использование ЛЕВОЕ СОЕДИНЕНИЕ с агрегирующей функцией

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

    
    МАКСИМУМ
    
    или
    
    КОЛИЧЕСТВО
    
    , чтобы определить, есть ли хоть одна строка в табличной части, соответствующая нашему условию. После этого нам обязательно потребуется сгруппировать результат по ссылкам на документы, чтобы каждая строка в динамическом списке соответствовала одному документу.

    Предположим, у нас есть документ "ЗаказКлиента" с табличной частью "Товары" и реквизитом "Буковка", который может принимать значения "А", "Б", "В". Мы хотим раскрасить документы, у которых в табличной части "Товары" есть строка, где "Буковка" = "Б".

    
    ВЫБРАТЬ
        ДокументЗаказКлиента.Ссылка КАК Ссылка,
        ДокументЗаказКлиента.Номер КАК Номер,
        ДокументЗаказКлиента.Дата КАК Дата,
        ЕСТЬNULL(МАКСИМУМ(ВЫБОР КОГДА Товары.Буковка = &ЗначениеБуковка ТОГДА ИСТИНА ИНАЧЕ ЛОЖЬ КОНЕЦ), ЛОЖЬ) КАК ЕстьБуковкаБВТЧ
    ИЗ
        Документ.ЗаказКлиента КАК ДокументЗаказКлиента
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента.Товары КАК Товары
        ПО ДокументЗаказКлиента.Ссылка = Товары.Ссылка
    СГРУППИРОВАТЬ ПО
        ДокументЗаказКлиента.Ссылка,
        ДокументЗаказКлиента.Номер,
        ДокументЗаказКлиента.Дата
    

    Разберем этот запрос подробнее:

    • 
      ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента.Товары КАК Товары
      
      : Мы соединяем каждый документ с его табличной частью.
      
      ЛЕВОЕ СОЕДИНЕНИЕ
      
      гарантирует, что даже если у документа нет строк в табличной части или нет строк, удовлетворяющих условию, сам документ всё равно попадет в результат.
    • 
      МАКСИМУМ(ВЫБОР КОГДА Товары.Буковка = &ЗначениеБуковка ТОГДА ИСТИНА ИНАЧЕ ЛОЖЬ КОНЕЦ)
      
      : Для каждой группы строк (для каждого документа), которая сформировалась после соединения с табличной частью, мы вычисляем максимальное значение. Если хотя бы в одной строке табличной части
      
      Буковка = &ЗначениеБуковка
      
      , то
      
      ВЫБОР КОГДА
      
      вернет
      
      ИСТИНА
      
      .
      
      МАКСИМУМ
      
      из
      
      ИСТИНА
      
      и
      
      ЛОЖЬ
      
      всегда будет
      
      ИСТИНА
      
      . Если таких строк нет, то
      
      МАКСИМУМ
      
      вернет
      
      ЛОЖЬ
      
      .
    • 
      ЕСТЬNULL(..., ЛОЖЬ)
      
      : Если документ не имеет ни одной строки в табличной части (или если
      
      ЛЕВОЕ СОЕДИНЕНИЕ
      
      не нашло ни одной подходящей строки по каким-либо причинам), то
      
      МАКСИМУМ
      
      вернет
      
      NULL
      
      .
      
      ЕСТЬNULL
      
      преобразует этот
      
      NULL
      
      в
      
      ЛОЖЬ
      
      , что является корректным поведением.
    • 
      СГРУППИРОВАТЬ ПО
      
      : Это ключевой момент. Мы группируем результат по уникальным ссылкам на документы и другим полям, которые мы хотим видеть в списке, чтобы каждый документ был представлен одной строкой, а не несколькими (по количеству строк в его ТЧ).
  2. Использование подзапроса для проверки существования

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

    
    NULL
    
    , но может быть чуть менее производительным на очень больших объемах данных, чем агрегация, в зависимости от СУБД.

    
    ВЫБРАТЬ
        ДокументЗаказКлиента.Ссылка КАК Ссылка,
        ДокументЗаказКлиента.Номер КАК Номер,
        ДокументЗаказКлиента.Дата КАК Дата,
        (ВЫБОР КОГДА (ВЫБРАТЬ ПЕРВЫЕ 1 ИСТИНА
                       ИЗ Документ.ЗаказКлиента.Товары КАК ТЧВложенный
                       ГДЕ ТЧВложенный.Ссылка = ДокументЗаказКлиента.Ссылка
                         И ТЧВложенный.Буковка = &ЗначениеБуковка) ЕСТЬ НЕ NULL
         ТОГДА ИСТИНА
         ИНАЧЕ ЛОЖЬ
         КОНЕЦ) КАК ЕстьБуковкаБВТЧ
    ИЗ
        Документ.ЗаказКлиента КАК ДокументЗаказКлиента
    

    Здесь мы используем вложенный запрос:

    • 
      (ВЫБРАТЬ ПЕРВЫЕ 1 ИСТИНА ...)
      
      : Внутри основного запроса для каждого документа выполняется подзапрос, который пытается выбрать
      
      ПЕРВЫЕ 1 ИСТИНА
      
      из его табличной части, если найдется строка, удовлетворяющая условию
      
      ТЧВложенный.Буковка = &ЗначениеБуковка
      
      .
    • 
      ЕСТЬ НЕ NULL
      
      : Если подзапрос нашел хотя бы одну такую строку, он вернет
      
      ИСТИНА
      
      , и условие
      
      ЕСТЬ НЕ NULL
      
      будет истинно. В противном случае подзапрос не вернет ничего (
      
      NULL
      
      ), и условие будет ложно.
    • 
      ВЫБОР КОГДА ... ТОГДА ИСТИНА ИНАЧЕ ЛОЖЬ КОНЕЦ
      
      : Преобразует результат подзапроса в булево значение
      
      ИСТИНА
      
      или
      
      ЛОЖЬ
      
      для нашего нового поля
      
      ЕстьБуковкаБВТЧ
      
      .
  3. Проверка заполненности табличной части в целом

    Если нам нужно просто определить, заполнена ли табличная часть документа хоть какими-либо строками, мы можем использовать упрощенный вариант с

    
    КОЛИЧЕСТВО
    
    :

    
    ВЫБРАТЬ
        ДокументЗаказКлиента.Ссылка,
        ...
        ВЫБОР КОГДА КОЛИЧЕСТВО(Товары.НомерСтроки) > 0 ТОГДА ИСТИНА ИНАЧЕ ЛОЖЬ КОНЕЦ КАК ТабличнаяЧастьЗаполнена
    ИЗ
        Документ.ЗаказКлиента КАК ДокументЗаказКлиента
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента.Товары КАК Товары
        ПО ДокументЗаказКлиента.Ссылка = Товары.Ссылка
    СГРУППИРОВАТЬ ПО
        ДокументЗаказКлиента.Ссылка
    

    В этом случае,

    
    КОЛИЧЕСТВО(Товары.НомерСтроки)
    
    посчитает все строки табличной части для каждого документа, и мы проверяем, больше ли это число нуля.

После того как мы модифицировали запрос динамического списка и добавили вычисляемое поле (например,


ЕстьБуковкаБВТЧ
), мы можем использовать это поле для настройки условного оформления или даже для реализации таких элементов, как флажок в динамическом списке.

Программная настройка условного оформления динамического списка

Теперь, когда у нас есть вычисляемое поле в запросе динамического списка (например,


ЕстьБуковкаБВТЧ
, которое принимает значения

ИСТИНА
или

ЛОЖЬ
), мы можем программно настроить условное оформление. Если код получается слишком сложным, может помочь декомпиляция условного оформления — для этого подойдёт инструмент пошаговой отладки кода в режиме Предприятия. Мы будем опираться на код, представленный автором, но изменим его для использования нашего нового вычисляемого поля.

Представим, что мы хотим, чтобы документы, у которых


ЕстьБуковкаБВТЧ
=

ИСТИНА
, были окрашены в светло-желтый цвет, а документы, у которых

ЕстьБуковкаБВТЧ
=

ЛОЖЬ
, — в светло-розовый.

  1. Инициализация списков значений (если они нужны для других условий или логики)

    В данном случае, если наше вычисляемое поле возвращает просто

    
    ИСТИНА
    
    или
    
    ЛОЖЬ
    
    , эти списки могут быть не нужны для прямого отбора, но мы их сохраним для демонстрации изначальной структуры.

    
    // Эти списки могут быть нужны, если вы хотите сравнивать поле с набором значений
    // Для нашего булевого поля 'ЕстьБуковкаБВТЧ' обычно достаточно прямого сравнения
    СписокНомерОдин = Новый СписокЗначений;
    СписокНомерОдин.Добавить(ЗначениеОдин);    
            
    СписокНомерДва = Новый СписокЗначений;
    СписокНомерДва.Добавить(ЗначениеДва); 
    
  2. Получение коллекции элементов условного оформления

    Мы получаем доступ к коллекции элементов условного оформления динамического списка. Убедитесь, что вы делаете это в подходящем месте, например, в обработчике события формы

    
    ПриСозданииНаСервере
    
    или
    
    ПриОткрытии
    
    .

    
    УО = ЭтотОбъект.Список.КомпоновщикНастроек.ФиксированныеНастройки.УсловноеОформление.Элементы;
    
  3. Создание нового элемента оформления для первого условия (например, для

    
    ИСТИНА
    
    )

    Добавляем новый элемент и активируем его.

    
    ЭлементыОформления = УО.Добавить();
    ЭлементыОформления.Использование = Истина;
    
  4. Настройка группы отбора

    Мы создаем группу отбора, чтобы внутри нее определить условия. В данном случае, нам достаточно одного условия, поэтому тип группы может быть

    
    ГруппаИ
    
    .

    
    ГруппаОтбора = ЭлементыОформления.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
    ГруппаОтбора.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИ;
    
  5. Установка элемента отбора на вычисляемое поле

    Здесь мы указываем, что отбор будет производиться по нашему новому вычисляемому полю

    
    ЕстьБуковкаБВТЧ
    
    .

    
    ЭлементОтбора = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ЕстьБуковкаБВТЧ"); // Имя нашего нового вычисляемого поля из запроса
    ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно; // Мы проверяем, равно ли оно ИСТИНА
    ЭлементОтбора.Использование = Истина;
    ЭлементОтбора.ПравоеЗначение = Истина;    // Устанавливаем значение, по которому будет происходить отбор
    

    Обратите внимание: если ваше поле возвращает не булево значение, а, например, строку или число, то

    
    ВидСравнения
    
    и
    
    ПравоеЗначение
    
    нужно будет изменить соответствующим образом (например,
    
    ВСписке
    
    с
    
    СписокНомерДва
    
    ).

  6. Настройка оформления для первого условия

    Устанавливаем параметры оформления, например, цвет фона.

    
    Оформление = ЭлементыОформления.Оформление;
    Оформление.УстановитьЗначениеПараметра(Новый ПараметрКомпоновкиДанных("ЦветФона"), WebЦвета.СветлоЖелтый);
    
  7. Повторение шагов для второго условия (например, для

    
    ЛОЖЬ
    
    )

    Если нам нужно применить другое оформление для обратного условия, мы повторяем шаги 3-6.

    
    ЭлементыОформления = УО.Добавить();
    ЭлементыОформления.Использование = Истина;
            
    ГруппаОтбора = ЭлементыОформления.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
    ГруппаОтбора.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИ;
            
    ЭлементОтбора = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ЕстьБуковкаБВТЧ");
    ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно; // Или НЕ Равно, если удобнее
    ЭлементОтбора.Использование = Истина;
    ЭлементОтбора.ПравоеЗначение = Ложь; // Устанавливаем значение ЛОЖЬ
            
    Оформление = ЭлементыОформления.Оформление;
    Оформление.УстановитьЗначениеПараметра(Новый ПараметрКомпоновкиДанных("ЦветФона"), WebЦвета.СветлоРозовый);
    

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

Важные нюансы и рекомендации

При работе с условным оформлением динамических списков, особенно при модификации запросов, следует учитывать несколько важных аспектов:

  1. Производительность. Добавление соединений и агрегирующих функций в запрос динамического списка, особенно для больших табличных частей или объемных баз данных, может существенно сказаться на производительности. Всегда анализируйте план запроса и проверяйте время выполнения. Если запрос становится слишком тяжелым, рассмотрите возможность кеширования данных или оптимизации структуры запроса. Иногда можно создать регистр сведений, который будет хранить агрегированные данные о наличии реквизитов в ТЧ, и обновлять его при записи документов.

  2. Использование события ПриПолученииДанныхНаСервере. Для очень сложных сценариев, когда возможностей запроса СКД или стандартного условного оформления недостаточно, можно использовать обработчик события динамического списка

    
    ПриПолученииДанныхНаСервере
    
    . Этот обработчик позволяет программно изменять данные и применять собственное оформление к полученным строкам списка *после* того, как они были выбраны и отформатированы динамическим списком. Он вызывается на сервере для каждой порции данных. Это дает максимальную гибкость, но требует большего контроля и может быть менее производительным, чем хорошо оптимизированный запрос СКД.

  3. Работа с метаданными. Если вы создаете универсальные процедуры или обработки, которые должны работать с разными документами и их табличными частями, можно использовать метаданные для проверки наличия реквизитов. Например,

    
    Метаданные.Документы.<ИмяДокумента>.ТабличныеЧасти.<ИмяТабличнойЧасти>.Реквизиты.Найти(<ИмяРеквизита>)
    
    поможет определить, существует ли нужный реквизит. Это может быть полезно для динамического формирования запросов или отчетов.

  4. Интерактивная настройка. Для простых условий условное оформление динамических списков можно настраивать непосредственно в режиме конфигуратора через свойства формы динамического списка, без написания кода. Это удобно и быстро для стандартных задач, но не дает такой гибкости, как программное изменение запроса и оформления.

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

← На главную