Динамические столбцы#

Концепция динамических столбцов#

Динамические столбцы — это колонки, формируемые при отображении данных, без изменения физической структуры базы данных. Они позволяют гибко адаптировать интерфейс под текущий контекст (тип объекта, период, права пользователя) без изменения физической структуры базы данных.

Существует два основных способа реализации динамических столбцов:

  • Реляционная реализация — для данных только на чтение (Read-Only), не зависящих от кэша.

  • DynMetaBuilder и DynRecBuilder — для интерактивных интерфейсов, редактируемых данных или сложной логики.

Реализация динамических столбцов через SQL#

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

Пример:

s"""
,(select string_agg(cast(t.id as varchar), ', ')
  from Oil_Task t
  where t.idResource  = ${rv.idResource()}
    and (t.dBegin < $sMainFromTab.dEndTime and t.dExec > $sMainFromTab.dBegTime)
    and t.idObjectType = :${Oil_TaskMonitorPkg().sfltidObjectType}
    and t.idStateMC >= 200
    and t.idDepOwner = :super$$idGlobalDepOwner
  group by t.idResource
  ) as \"$sNameFieldForGST[${rv.id()}]\"
"""

Результат после подстановки параметров:

(select string_agg(cast(t.id as varchar), ', ')
  from Oil_Task t
  where t.idResource  = 115
    and (t.dBegin < 2023-08-25 12:00:00 and t.dExec > 2023-08-25 14:00:00)
    and t.idObjectType = 225
    and t.idStateMC >= 200
    and t.idDepOwner = 336
  group by t.idResource
  ) as idFlyOverWay[13]"

Мы получим столбец с названием idFlyOverWay[13], результатом будет значение подзапроса.

Примечание

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

Пример задания параметров для всех атрибутов idFlyOverWay:

<attr name="idFlyOverWay">
  <style attr="sAvailStyle"/>
  <grid columnWidth="6" isColumnWidthFixed="true"/>
</attr>

Пример установки параметров атрибута по его индексу:

for ((svCaption, nvIdx) <- Pro_ResLoadChartPkg().getPeriodCaptions(svExpandTypeMC, dvPeriodBegin, dvPeriodEnd, periods).zipWithIndex) {
  selection.attrs(s"""nWreQty[$nvIdx]""").caption = s"Плановая|${svCaption.get}"
  selection.attrs(s"""nWreQty[$nvIdx]""").order = 100 + 100 * nvIdx
  selection.attrs(s"""nResAvail[$nvIdx]""").isVisible = getVar("super$filter$Flt_bShowResourceAvailability").asNNumber.nvl(0.nn).toBoolean
}

Примечание

Символ | в заголовке (caption) разделяет имя бенд-группы и имя атрибута. В примере: группа «Плановая», атрибут — динамическое название периода.

Реализация через DynMetaBuilder и DynRecBuilder#

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

Процесс состоит из двух этапов:

  • Описание метаданных (DynMetaBuilder): какие колонки добавляем (имя, тип, заголовок).

  • Заполнение данных (DynRecBuilder): какие значения писать в эти колонки для каждой строки.

Структура формирования:

Примечание

Recs(<объекты, представляющие строку (список rop или case class)>) .extend(.build()) .foreach((row, builder) => (row, .build))

  • Пример использования .extend() - метод расширяет исходный набор данных, добавляя к ним данные о динамических столбцах:

    val dynMetaBuilder = getObjAttrDynMetaBuilder()
    thisApi().refreshByParent(getIdMaster)
      .extend(dynMetaBuilder.build())
      .foreach{(rop, builder) =>
        val dynamicInfo = getDynamicObjAttr(rop, builder)
        (rop, dynamicInfo.build)
      }
    

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

    В данном методе столбец получает построчно значения.

  • Пример getObjAttrDynMetaBuilder - здесь формируются метаданные о добавляемых столбцах: название столбца, тип данных, отображаемый заголовок колонки.

    def getObjAttrDynMetaBuilder(): DynMetaBuilder = {
      val dynMetaBuilder = DynMetaBuilder()
      thisApi().jsonAttrsWithPrefix.foreach { case (rvAttr, sAttrPrefix) =>
        val svAttr = getJsonAttrName(rvAttr.id, sAttrPrefix)
        val attrClass = Btk_AttributeApi().getAttrValueClassById(rvAttr.id)
        // Добавляем метаданные об атрибуте
        dynMetaBuilder.add(svAttr, attrClass, rvAttr.sCaption)
        // для ссылочных атрибутов отображаем HL поля
        if (rvAttr.sType === AttrTypes.RefObject.toString.ns) {
          dynMetaBuilder.add(s"idValueHL[${rvAttr.id}]", classOf[String], rvAttr.sCaption)
        }
        // И добавляем скрытый столбец для редактора
        dynMetaBuilder.add(Btk_AttributeLib().getEditorAttrName(rvAttr.id, rvAttr.id.isNull), classOf[String], s"Редактор ${rvAttr.sCaption}")
      }
      dynMetaBuilder
    }
    
  • Пример getDynamicObjAttr - здесь формируются данные строк для каждого добавленного атрибута:

    def getDynamicObjAttr(rop: Rop[_, _], builder: DynRecBuilder): DynRecBuilder = {
      val svLetterInRuleCaption = thisApi().getLetterInProcessRule(rop).sCaption
      builder.set(A.sLetterInProcessRule.name + "HL", svLetterInRuleCaption.get)
      builder
    }
    

    В данном примере для атрибута sLetterInProcessRule будет выведен дополнительный столбец с заголовком.

В некоторых случаях требуется использовать методы DynMetaBuilder и DynRecBuilder вместе с классом AdditionalInfo. В таком случае вместо extend используется метод extend2.

  • Пример использования .extend2() - метод расширяет исходный набор данных, добавляя к ним данные о динамических столбцах и данные из класса AdditionalInfo:

    thisApi().refreshByParent(getIdMaster)
          .extend2(classOf[AdditionalInfo], dynMetaBuilder.build())
          .foreach{(rop, builder) =>
            val dynamicInfo = getDynamicObjAttr(rop, builder)
            (rop, getAdditionalInfo(rop.asInstanceOf[SomeApi#ApiRop]), dynamicInfo.build)
          }
    

    ,где SomeApi - какая-либо Api класса.

Упрощённая реализация через хуки карточки#

Использование специальных хуков внутри класса карточки доступно только для карточек объектных характеристик (наследники Btk_ObjectAttrLibCard_ObjectAttr) и позволяет упростить работу с коллекциями, избавляя от необходимости вручную писать длинные цепочки методов, таких как .extend() и .foreach().

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

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

  • onRefreshExtendMeta(dynMetaBuilder: DynMetaBuilder)
    Вызывается один раз при построении структуры.
    Служит для объявления новых колонок:

    • имя;

    • тип;

    • заголовок;

    • группировка.

  • onRefreshExtendValues(dynRecBuilder: DynRecBuilder)
    Вызывается для каждой записи выборки.
    Служит для заполнения значений в колонках, объявленных в onRefreshExtendMeta.

Пример задачи: отобразить год выпуска (dManufYear), преобразовав полную дату в строку года, и поместить колонку в группу «Основные данные»:

override def onRefreshExtendMeta(dynMetaBuilder: DynMetaBuilder): Unit = {
  super.onRefreshExtendMeta(dynMetaBuilder)
  // Получаем группу характеристик "Основные данные"
  val basicDataGroup = Btk_ObjectAttrGroupByObjectTypeApi()
    .findByMnemoCode(Btk_ObjectTypeApi().load(getCurIdObjectType), "Attr_groupBasicData")
  val sGroupAttr = if (basicDataGroup.isNotNull) {
    Btk_ObjectAttrGroupByObjectTypeApi().load(basicDataGroup).get(_.sCaption)
  } else "Основные данные"

  // Добавляем новую колонку "Год выпуска" в указанную группу
  dynMetaBuilder.add("dManufYearDisplay", classOf[String], s"Год выпуска|$sGroupAttr", 3.nn)
}

override def onRefreshExtendValues(dynRecBuilder: DynRecBuilder): Unit = {
  super.onRefreshExtendValues(dynRecBuilder)
  // Преобразуем дату dManufYear в год (например, 2025-03-15 → "2025")
  val convertedManufYear = if (thisRop().get(_.dManufYear).isNotNull) {
    thisRop().get(_.dManufYear).toNLocalDateTime.get.getYear.toString
  } else ""
  dynRecBuilder.set("dManufYearDisplay", convertedManufYear)
}

Как обращаться к динамической колонке в настройках

При конфигурировании через selection.attrs(...) или XML-конфигурацию AVM используется базовое имя, переданное первым параметром в dynMetaBuilder.add().

Параметр в коде (add)

Имя для настройки (selection.attrs)

Пояснение

"dManufYearDisplay"

selection.attrs("dManufYearDisplay")

Простое имя, используется как есть.

"nWreQty[$nvIdx]"

selection.attrs("nWreQty")

Настройка применяется ко всем колонкам с этим базовым именем.

"idFlyOverWay[13]"

selection.attrs("idFlyOverWay")

Индекс в квадратных скобках в настройках не указывается.

Практические примеры настройки

Вариант А: Программная настройка (в коде)

Выполняйте настройку динамических колонок непосредственно внутри метода onRefreshExtendMeta:

override def onRefreshExtendMeta(dynMetaBuilder: DynMetaBuilder): Unit = {
  super.onRefreshExtendMeta(dynMetaBuilder)
  
  // 1. Объявляем колонку (базовое имя: "dManufYearDisplay")
  dynMetaBuilder.add("dManufYearDisplay", classOf[String], "Год выпуска|Основные данные", 3.nn)
  
  // 2. Сразу настраиваем её отображение через selection.attrs
  selection.attrs("dManufYearDisplay").order = 150
  selection.attrs("dManufYearDisplay").columnWidth = 4
  selection.attrs("dManufYearDisplay").isColumnWidthFixed = true
  selection.attrs("dManufYearDisplay").isVisible = getVar("bShowYear").asBoolean
}

Вариант Б: Настройка через AVM / XML

Если имя динамического атрибута известно заранее, прописывайте в конфигурации интерфейса:

<attr name="dManufYearDisplay">
  <style attr="sAvailStyle"/>
  <grid columnWidth="5" isColumnWidthFixed="true"/>
  <caption value="Год выпуска"/>
</attr>