Настройка скриптового модуля приложения "Интерактивная доска" 2.0 — 2.0.2

Встроенное приложение "Интерактивная доска" версии ниже 2.4.0 может быть несовместимо с версией системы.
Рекомендуется обновить встроенное приложение на последнюю версию.

Описание примера настройки

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

Скриптовый модуль может использоваться с настройками по умолчанию.

Примеры возможных изменений:

  • Изменить настройки и названия столбцов на интерактивной доске в разрезе статусов.
  • Изменить названия столбцов на интерактивной доске в разрезе времени дедлайна.
  • Изменить набор атрибутов на плашке запроса.

Изменить часть настроек скриптового модуля можно самостоятельно или по запросу в службу технической поддержки NAUMEN.

Место настройки в интерфейсе

Интерфейс администратора. Раздел "Настройка системы" → "Каталог скриптов и модулей" → "Каталог модулей".

Форма редактирования модуля, см. Редактирование модуля.

Выполнение настройки

Интерактивная доска запросы команды в разрезе статусов

Функция Agile формирует доску, на которой отображаются запросы команды с разделением запросов по статусам и предоставляется возможностью менять статусы непосредственно на доске, перетаскивая запросы между столбцами.

Возможные изменения:

  • изменить название столбца, соответствующей статусу;
  • задать цвет шапки столбца в формате HEX в соответствии со статусом столбца (цвет по умолчанию — серый);
  • использовать несколько статусов для отображения в одном столбце;
  • добавить новые столбцы или скрыть существующие.
Copy
def Agile(subjectUuid, userUuid = null) { 
  generateBoard([
    // формируем список в виде объектов системы
    tasks: utils.find('serviceCall', ['responsibleTeam':subjectUuid, 'removed':false, 'state':op.not('closed')]),
    // columns: определяем столбцы. В разрезе статусов можно использовать несколько статусов на колонку
    columns: [
      new KanboardColumn('Новая', ['registered'], '#FF0000'),
      new KanboardColumn('В работе', ['inprogress'], '#FFD800'),
      new KanboardColumn('Ожидание', ['waitClientAnswer'], '#676A7E'),
      new KanboardColumn('Выполнена', ['resolved'], '#00FF00')
    ]
  ] as KanboardConfiguration, smrmTaskPropertiesExtractor) //Если не указан атрибут, то при перетаскивании меняется статус
}

Интерактивная доска запросы сотрудника в разрезе статусов

Функция AgileForEmployee формирует доску, на которой отображаются запросы в ответственности сотрудника с разделением запросов по статусам и предоставляется возможностью менять статусы непосредственно на доске, перетаскивая запросы между столбцами.

Возможные изменения:

  • изменить название столбца, соответствующей статусу;
  • задать цвет шапки столбца в формате HEX в соответствии со статусом столбца (цвет по умолчанию — серый);
  • использовать несколько статусов для отображения в одном столбце;
  • добавить новые столбцы или скрыть существующие.
Copy
def AgileForEmployee(subjectUuid, userUuid = null) { 
  generateBoard([
    // формируем список в виде объектов системы, будут отображаться элементами на доске
    tasks: utils.find('serviceCall', ['responsibleEmployee':subjectUuid, 'removed':false, 'state':op.not('closed')]),
    // columns: определяем столбцы
    columns: [
      new KanboardColumn('Новая', ['registered'], '#FF0000'),
      new KanboardColumn('В работе', ['inprogress'], '#FFD800'),
      new KanboardColumn('Ожидание', ['waitClientAnswer'], '#676A7E'),
      new KanboardColumn('Выполнена', ['resolved'], '#00FF00')
    ]
  ] as KanboardConfiguration, smrmTaskPropertiesExtractor) //Если не указан атрибут, то при перетаскивании меняется статус
}

Интерактивная доска запросы команды в разрезе ответственных

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

На доске первой отображается столбец "Без ответственного", затем отдельные столбцы для каждого участника команды.

Возможные изменения:

  • изменить название столбца "Без ответственного".
Copy
def ZagruzhennostSotrudnikov1(subjectUuid, userUuid = null) {
  generateBoard([
    // это список задач в виде объектов системы (не только список UUID-ов!)
    tasks: utils.find('serviceCall', ['responsibleTeam':subjectUuid, 'removed':false, 'state':op.not('closed')]),
    // columns: определяем столбцы, в данном случае это участники команды. Т.к. доска теперь в разрезе атрибута, задаваемого руками, каждая колонка должна иметь ровно один статус, чтобы можно было перенести в нее объект
    columns: [new KanboardColumn('Без ответственного', [null] as String[])] // первая колонка будет содержать запросы без ответственного сотрудника
      + utils.find('employee$employee', [teams:op.isNotNull()]) // находим всех сотрудников из текущей команды и для каждого создаем колонку
        .grep { subjectUuid in it.teams*.UUID }
        .collect { new KanboardColumn(it.title, it.UUID) }
  ] as KanboardConfiguration, smrmTaskPropertiesExtractorForEmployees, 'responsibleEmployee') //При перетаскивании меняем ответственного
  // дополнительный аргумент указывает на код атрибута, который будет редактироваться при переносе задач из колонки в колонку

Интерактивная доска запросы команды в разрезе срока дедлайна

Функция TeamZadachiSRazbivkoiPoSrokam формирует доску, на которой отображаются запросы команды с разделением запросов по оставшемуся времени на решение.

Интерактивная доска разделена на столбцы: "Дедлайн не задан" (запросы, у которых не задано время решения), "Дедлайн наступил" (запросы, у которых уже наступил дедлайн), пять столбцов с ближайшими датами (отображаются запросы, у которых дедлайн наступит в ближайшие пять дней. Каждая дата в отдельном столбце), "Более 5 дней" (отображаются запросы, у которых дата дедлайна дальше 5 дней)

Возможные изменения:

  • изменить название столбцов "Дедлайн не задан", "Дедлайн наступил" и "Более 5 дней".
Copy
def TeamZadachiSRazbivkoiPoSrokam(subjectUuid, userUuid = null) {
  def now = new Date()
  def e = smrmTaskPropertiesExtractorForDeadlines
  def tasks = utils.find('serviceCall', ['responsibleTeam':subjectUuid, 'removed':false, 'state':op.not('closed')])

  gson.toJson([
    columns: [
      [
        title: 'Дедлайн не задан',
        id: UUID.randomUUID(),
        statuses: [null],
        tasks: tasks.grep { it.deadLineTime == null }
          .collect {task -> [
            id: e.id(task),
            title: e.title(task),
            description: e.description(task),
            deadline: e.deadline(task),
            status: e.status(task),
            assignee: [
              title: e.assignee.title(task),
              avatarUrl: e.assignee.avatarUrl(task)
            ] as KanboardAssignee,
            category: [
              title: e.category.title(task),
              color: e.category.color(task),
              iconUrl: e.category.iconUrl(task)
            ] as KanboardCategory
            priority: [
              color: e.priority.color(task),
              title: e.priority.title(task)
            ] as KanboardPriority
          ] as KanboardTask}
      ],
      [
        title: 'Дедлайн наступил!',
        id: UUID.randomUUID(),
        statuses: [],
        tasks: tasks.grep { it.deadLineTime != null && compareDatesWithoutTimePart(it.deadLineTime, now) < 0 }
          .collect {task -> [
            id: e.id(task),
            title: e.title(task),
            description: e.description(task),
            deadline: e.deadline(task),
            status: e.status(task),
            assignee: [
              title: e.assignee.title(task),
              avatarUrl: e.assignee.avatarUrl(task)
            ] as KanboardAssignee,
            category: [
              title: e.category.title(task),
              color: e.category.color(task),
              iconUrl: e.category.iconUrl(task)
            ] as KanboardCategory
            priority: [
              color: e.priority.color(task),
              title: e.priority.title(task)
            ] as KanboardPriority
          ] as KanboardTask}
      ]
    ] +
    (0..4).collect { now + it }
      .collect {
        [
          title: new java.text.SimpleDateFormat("dd.MM.yyyy").format(it),
          id: UUID.randomUUID(),
          statuses: [new java.text.SimpleDateFormat("yyyy.MM.dd").format(it)],
          tasks: tasks.grep { task -> task.deadLineTime != null && compareDatesWithoutTimePart(task.deadLineTime, it) == 0 }
            .collect {task -> [
              id: e.id(task),
              title: e.title(task),
              description: e.description(task),
              deadline: e.deadline(task),
              status: e.status(task),
              assignee: [
                title: e.assignee.title(task),
                avatarUrl: e.assignee.avatarUrl(task)
              ] as KanboardAssignee,
              category: [
                title: e.category.title(task),
                color: e.category.color(task),
                iconUrl: e.category.iconUrl(task)
              ] as KanboardCategory
            priority: [
              color: e.priority.color(task),
              title: e.priority.title(task)
            ] as KanboardPriority
            ] as KanboardTask}
        ]
      } +
    [
      [
        title: 'Более 5 дней',
        id: UUID.randomUUID(),
        statuses: [],
        tasks: tasks.grep { it.deadLineTime != null && compareDatesWithoutTimePart(it.deadLineTime, now + 5) >= 0 }
          .collect { task -> [
            id: e.id(task),
            title: e.title(task),
            description: e.description(task),
            deadline: e.deadline(task),
            status: e.status(task),
            assignee: [
              title: e.assignee.title(task),
              avatarUrl: e.assignee.avatarUrl(task)
            ] as KanboardAssignee,
            category: [
              title: e.category.title(task),
              color: e.category.color(task),
              iconUrl: e.category.iconUrl(task)
            ] as KanboardCategory
            priority: [
              color: e.priority.color(task),
              title: e.priority.title(task)
            ] as KanboardPriority
          ] as KanboardTask}
      ]
    ],
    attributeCodeForEditing: 'deadLineTime'
  ] as Kanboard)
}

Интерактивная доска запросы сотрудника в разрезе срока дедлайна

Функция MoiZadachiSRazbivkoiPoSrokam формирует доску, на которой отображаются запросы команды с разделением запросов по оставшемуся времени на решение.

Интерактивная доска разделена на столбцы: "Дедлайн не задан" (запросы, у которых не задано время решения), "Дедлайн наступил" (запросы, у которых уже наступил дедлайн), пять столбцов с ближайшими датами (отображаются запросы, у которых дедлайн наступит в ближайшие пять дней. Каждая дата в отдельном столбце), "Более 5 дней" (отображаются запросы, у которых дата дедлайна дальше 5 дней).

Возможные изменения:

  • изменить название столбцов "Дедлайн не задан", "Дедлайн наступил" и "Более 5 дней".
Copy
def MoiZadachiSRazbivkoiPoSrokam(subjectUuid, userUuid = null) {
  def now = new Date()
  def e = smrmTaskPropertiesExtractorForDeadlines
  def tasks = utils.find('serviceCall', ['responsibleEmployee':subjectUuid, 'removed':false, 'state':op.not('closed')])

  gson.toJson([
    columns: [
      [
        title: 'Дедлайн не задан',
        id: UUID.randomUUID(),
        statuses: [null],
        tasks: tasks.grep { it.deadLineTime == null }
          .collect {task -> [
            id: e.id(task),
            title: e.title(task),
            description: e.description(task),
            deadline: e.deadline(task),
            status: e.status(task),
            assignee: [
              title: e.assignee.title(task),
              avatarUrl: e.assignee.avatarUrl(task)
            ] as KanboardAssignee,
            category: [
              title: e.category.title(task),
              color: e.category.color(task),
              iconUrl: e.category.iconUrl(task)
            ] as KanboardCategory
            priority: [
              color: e.priority.color(task),
              title: e.priority.title(task)
            ] as KanboardPriority
          ] as KanboardTask}
      ],
      [
        title: 'Дедлайн наступил!',
        id: UUID.randomUUID(),
        statuses: [],
        tasks: tasks.grep { it.deadLineTime != null && compareDatesWithoutTimePart(it.deadLineTime, now) < 0 }
          .collect {task -> [
            id: e.id(task),
            title: e.title(task),
            description: e.description(task),
            deadline: e.deadline(task),
            status: e.status(task),
            assignee: [
              title: e.assignee.title(task),
              avatarUrl: e.assignee.avatarUrl(task)
            ] as KanboardAssignee,
            category: [
              title: e.category.title(task),
              color: e.category.color(task),
              iconUrl: e.category.iconUrl(task)
            ] as KanboardCategory
            priority: [
              color: e.priority.color(task),
              title: e.priority.title(task)
            ] as KanboardPriority
          ] as KanboardTask}
      ]
    ] +
    (0..4).collect { now + it }
      .collect {
        [
          title: new java.text.SimpleDateFormat("dd.MM.yyyy").format(it),
          id: UUID.randomUUID(),
          statuses: [new java.text.SimpleDateFormat("yyyy.MM.dd").format(it)],
          tasks: tasks.grep { task -> task.deadLineTime != null && compareDatesWithoutTimePart(task.deadLineTime, it) == 0 }
            .collect {task -> [
              id: e.id(task),
              title: e.title(task),
              description: e.description(task),
              deadline: e.deadline(task),
              status: e.status(task),
              assignee: [
                title: e.assignee.title(task),
                avatarUrl: e.assignee.avatarUrl(task)
              ] as KanboardAssignee,
              category: [
                title: e.category.title(task),
                color: e.category.color(task),
                iconUrl: e.category.iconUrl(task)
              ] as KanboardCategory
              priority: [
                color: e.priority.color(task),
                title: e.priority.title(task)
              ] as KanboardPriority              
            ] as KanboardTask}
        ]
      } +
    [
      [
        title: 'Более 5 дней',
        id: UUID.randomUUID(),
        statuses: [],
        tasks: tasks.grep { it.deadLineTime != null && compareDatesWithoutTimePart(it.deadLineTime, now + 5) >= 0 }
          .collect { task -> [
            id: e.id(task),
            title: e.title(task),
            description: e.description(task),
            deadline: e.deadline(task),
            status: e.status(task),
            assignee: [
              title: e.assignee.title(task),
              avatarUrl: e.assignee.avatarUrl(task)
            ] as KanboardAssignee,
            category: [
              title: e.category.title(task),
              color: e.category.color(task),
              iconUrl: e.category.iconUrl(task)
            ] as KanboardCategory
            priority: [
              color: e.priority.color(task),
              title: e.priority.title(task)
            ] as KanboardPriority
          ] as KanboardTask}
      ]
    ],
    attributeCodeForEditing: 'deadLineTime'
  ] as Kanboard)
}

Параметры плашки запроса

За набор параметров объекта, отображаемого на плашке, отвечает функция generateBoard.

Copy
def generateBoard(KanboardConfiguration configuration, KanboardTaskPropertiesExtractor e, String attributeCodeForEditing = null) {
  gson.toJson([
    columns: configuration.columns.collect { column ->
      def columnCopy = column.clone()
      configuration.tasks
        .grep { task -> e.status(task) in columnCopy.statuses }
        .each { task ->
          columnCopy << ([
            id: e.id(task),
            title: e.title(task),
            description: e.description(task),
            deadline: e.deadline(task),
            status: e.status(task),
            assignee: [
              title: e.assignee.title(task),
              avatarUrl: e.assignee.avatarUrl(task),
              urlToCard: e.assignee.urlToCard(task)
            ] as KanboardAssignee,
            category: [
              title: e.category.title(task),
              color: e.category.color(task),
              iconUrl: e.category.iconUrl(task)
            ] as KanboardCategory
            priority   : [
              color: e.priority.color(task),
              title: e.priority.title(task)
            ] as KanboardPriority
          ] as KanboardTask)
        }
      return columnCopy
    },
    attributeCodeForEditing: attributeCodeForEditing
  ] as Kanboard)
}

В KanboardTaskPropertiesExtractor задается соответствие между параметрами приложения и атрибутами объектов SMP. Число и тип атрибутов статичны и должны соответствовать классу KanboardTask, другие параметры добавить нельзя.

Определение параметров происходит следующим образом: id({t->t.UUID}), где

  • id — код в приложении;
  • t — объект из списка tasks;
  • UUID — код атрибута объекта.

Возможные изменения:

  • переопределить соответствие между параметрами приложения и атрибутами объектов SMP.
Copy
// Для уменьшения объема кода можно выносить общие части extractor в переменную. Далее просто переопределять нужные свойства
@Field def defaultSmrmTaskPropertiesExtractorBuilder = KanboardTaskPropertiesExtractor.builder()
  .id({t->t?.UUID}) // Идентификатор объекта, тип String
  .title({t->t?.shortDescr}) // Тема объекта, тип String
  .description({t->api.string?.htmlToText(t?.descriptionRTF)}) // Описание объекта - тип String
  .deadline({t->t?.deadLineTime}) // Регламентное время решения - тип Date
  .status({t->t?.state}) // Статус объекта - в разрезе статусов это код статуса, в разрезе любых других атрибутов это строковое представление, которое будет передано в REST-запросе на редактирование при переносе объекта в колонку
  .assigneeTitle({t->t?.responsible?.title}) // Имя ответственного - тип String
  .assigneeAvatarUrl({t ->
    t.responsible?.getMain()?.hasProperty('image') && !t?.responsible?.image?.isEmpty()
      ? generateFileDownloadUrl(t?.responsible?.image[0].UUID)
      : '' // Аватарка ответственного - ссылка на файл изображения
  })
  .categoryTitle({t->t?.system_icon?.title}) // Название иконки - тип String
  .categoryColor({t->t?.priority?.color?.string ?: ''}) // Цвет бокоовой плашки элемента - тип String
  .categoryIconUrl({t->generateFileDownloadUrl(t?.system_icon ? t?.system_icon?.icon[0]?.UUID : null)}) // Иконка - ссылка на файл изображения
  .priorityColor { it.priority?.color } // Цвет приоритета - тип String
  .priorityTitle { it.priority?.title } // Наименование приоритета - тип String
@Field KanboardTaskPropertiesExtractor smrmTaskPropertiesExtractor = defaultSmrmTaskPropertiesExtractorBuilder.build()