# Дискретный доступ в отчётах и примеры скриптов

В документе описана интеграция дискретного доступа с печатными формами: как настроить ограничение в отчёте, какие макросы использовать для Jasper/Word/Excel. Также содержит готовые примеры скриптов фильтрации и проверки по кешу для типовых сценариев.  
Материал отвечает на вопросы: как ограничить данные в печатной форме по правам пользователя, как сформировать макрос для запроса, какие шаблоны скриптов использовать для примитивных и составных правил.

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

## Настройка в отчётах

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

```{important}
Если в запросе печатной формы не прописаны необходимые макросы, но есть обращение к администрируемой таблице, система не позволит распечатать документ и выведет ошибку с просьбой добавить макросы.
```

**Настройка дискретного доступа в отчётах**

1. В отчёте `Rpt_Report` на закладке «Настройка контроля доступа к данным печатной формы» выберите администрируемый объект.
2. Укажите объектную привилегию (по умолчанию проверяется `viewReport#`).
3. В поле «Список алиасов» укажите алиасы таблиц через `;` (по умолчанию — `t`).

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

## Макросы для Jasper-отчётов

Макрос передаётся как параметр с именем:
`DM_(<системное_имя_административного_объекта>)_(<алиас_таблицы>)`

В запросе используйте параметр со знаком `!`:

```xml
<parameter name="DM_(BTK_USER)_(T)" class="java.lang.String">
  <defaultValueExpression><![CDATA["1=2"]]></defaultValueExpression>
</parameter>
<queryString>
  <![CDATA[select sHeadLine_dz
            from btk_user t
            where $P!{DM_(BTK_USER)_(T)}]]>
</queryString>
```

```{tip}
Для тестирования можно указать значение по умолчанию `1=1`, но перед загрузкой в приложение удалите его, чтобы избежать случайного предоставления полного доступа.
```

## Макросы для отчётов «Текстовый», «Excel», «Word»

Макрос формируется по шаблону:
`&DM_(<системное_имя_административного_объекта>)_(<алиас_таблицы>)`

Пример для печатной формы типа «Word»:
```
[#&Order= select sHeadLine_dz, sMnemoCode_dz from BTK_USER t
where &DM_(Btk_User) #]
```

## Примеры скриптов

### Примитивное правило по атрибуту класса

Правило значимое, тип данных — строка. Фильтрация по `LIKE` атрибута `sCode`.

**Скрипт фильтрации объектных привилегий:**
```sql
select 1
  from RplTst_ClassTree tt
  join (
     select cast(json_array_elements_text(cast((&params) as json)) as varchar) as sCode
  ) as codes 
   on tt.sCode like codes.sCode
where tt.id = (&id)
```

**Скрипт проверки строк по объектному кешу:**
```scala
for (p: params) {
  if (row.sCode != null && p != null) {
    if (like(row.sCode, p)) {
      return true;
    }
  }  
}
```

### Примитивное правило по атрибуту коллекции

Правило ссылочное на `Btk_Group`. Фильтрация по прямому вхождению объекта в группу (`Btk_ObjectGroup`).

**Скрипт фильтрации объектных привилегий:**
```sql
select 1
  from Rpltst_TestGroup tt
  join Btk_ObjectGroup og
    on tt.gid = og.gidSrcObject
  join (
     select cast(json_array_elements_text(cast((&params) as json)) as int8) as id
  ) as params
   on og.idGroup = params.id
where tt.id = (&id)
```

**Скрипт проверки строк по объектному кешу:**
```scala
for (p: params) {
  var rops = toJRops(Btk_ObjectGroupApi.byParent(row.data())).asList();
  if (p != null) {
    for (r: rops) {
      if (r.idGroup == p) {
        return true;
      }
    }
  }  
}
```

### Примитивное правило по администрируемому объекту (произвольная выборка)

Правило значимое, тип данных — строка. Регистронезависимая фильтрация по `LIKE` поля `sClass`.

**Скрипт фильтрации объектных привилегий:**
```sql
select 1
   from json_array_elements_text(cast((&params) as json)) as p
  where upper((&sClass)) like upper(p)
```

**Скрипт проверки строк по объектному кешу:**
```scala
for (p: params) {
  if (row.sClass != null && p != null) {
    if (like(row.sClass.toUpperCase(), p.toUpperCase())) {
      return true;
    }
  }  
}
```

### Составное правило по трём атрибутам класса

Параметры:
1. `dDate` — значимый, дата;
2. `nNumber` — значимый, число;
3. `sCaption` — значимый, строка.

Фильтрация: дата меньше указанной, число равно указанному, наименование совпадает по `LIKE` (регистронезависимо).

**Скрипт фильтрации объектных привилегий:**
```sql
select 1
  from RplTst_AllDbTypes tt
 where tt.id = (&id) 
   and exists (
    select 1
      from (
        select v -> 'dDate' as dDate
              ,v -> 'nNumber' as nNumber
              ,v -> 'sCaption' as sCaption
          from json_array_elements(cast((&params) as json)) as v 
        ) p 
      where exists (
        select 1
          from json_array_elements_text(p.dDate) pp 
          where tt.dDate <= to_timestamp(pp, 'DD.MM.YYYY HH24:MI:SS')  
      )
        and exists (
        select 1
          from json_array_elements_text(p.nNumber) pp 
          where tt.nNumber = cast(pp as numeric) 
      )  
        and exists (
        select 1
          from json_array_elements_text(p.sCaption) pp 
          where upper(tt.sCaption) like upper(pp) 
      )  
   )
```

**Скрипт проверки строк по объектному кешу:**
```scala
for (p: params) {
  var jObj = toJObject(p);
  var res = true;

  // проверка даты
  if (res) {
    res = false;
    var aValues = jObj.childJArray("dDate");
    var i = 0;
    while(i < aValues.size() && !res) {
      var value = aValues.getDate(i);
      if (row.dDate <= value) {
        res = true;
      }
      i = i + 1;
    }
  }

  // проверка числа
  if (res) {
    res = false;
    var aValues = jObj.childJArray("nNumber");
    var i = 0;
    while(i < aValues.size() && !res) {
      var value = aValues.getNumber(i);
      if (row.nNumber == value) {
        res = true;
      }
      i = i + 1;
    }
  }

  // проверка наименования
  if (res) {
    res = false;
    var aValues = jObj.childJArray("sCaption");
    var i = 0;
    while(i < aValues.size() && !res) {
      var value = aValues.getString(i);
      if (row.sCaption != null && value != null) {
        if (like(row.sCaption.toUpperCase(), value.toUpperCase())) {
          res = true;
        }
      }  
      i = i + 1;
    }
  }

  if (res) {
    return true;
  }
}
```

### Составное правило по атрибуту класса и коллекции

Параметры:
1. `sCaption` — значимый, строка;
2. `idGroup` — ссылочный на `Btk_Group`.

Фильтрация: наименование совпадает по `LIKE` (регистронезависимо) и есть прямое вхождение в группу (`Btk_ObjectGroup`).

**Скрипт фильтрации объектных привилегий:**
```sql
select 1
  from RplTst_TestGroup tt
 where tt.id = (&id) 
   and exists (
     select 1
     from (
       select v -> 'sCaption' as sCaption
             ,v -> 'idGroup' as idGroup
         from json_array_elements(cast((&params) as json)) as v 
       ) p
     where exists (
       select 1
         from json_array_elements_text(p.sCaption) pp 
         where upper(tt.sCaption) like upper(pp) 
     )
       and exists (
         select 1
           from Btk_ObjectGroup og
           join json_array_elements_text(p.idGroup) pp 
             on og.idGroup = cast(pp as int8)
           where og.gidSrcObject = tt.gid
       )
   )
```

**Скрипт проверки строк по объектному кешу:**
```scala
for (p: params) {
  var jObj = toJObject(p);
  var res = true;

  // проверка наименования
  if (res) {
    res = false;
    var aValues = jObj.childJArray("sCaption");
    var i = 0;
    while(i < aValues.size() && !res) {
      var value = aValues.getString(i);
      if (row.sCaption != null && value != null) {
        if (like(row.sCaption.toUpperCase(), value.toUpperCase())) {
          res = true;
        }
      }  
      i = i + 1;
    }
  }

  // проверка группы
  if (res) {
    res = false;
    var rops = toJRops(Btk_ObjectGroupApi.byParent(row.data())).asList();
    var aValues = jObj.childJArray("idGroup");
    var i = 0;
    while(i < aValues.size() && !res) {
      var value = aValues.getLong(i);
      for (r: rops) {
        if (r.idGroup == value) {
          res = true;
        }
      }
      i = i + 1;
    }
  }

  if (res) {
    return true;
  }
}
```

### Составное правило по двум атрибутам (произвольная выборка)

Параметры:
1. `sClass` — значимый, строка;
2. `idRefClass` — ссылочный на `Btk_Class`.

Фильтрация: `sClass` совпадает по `LIKE` (регистронезависимо), `idRefClass` совпадает точно.

**Скрипт фильтрации объектных привилегий:**
```sql
select 1
   from (
     select v -> 'sClass' as sClass
          ,v -> 'idRefClass' as idRefClass
       from json_array_elements(cast((&params) as json)) as v 
    ) p
  where exists (
      select 1
        from json_array_elements_text(p.sClass) pp 
       where upper((&sClass)) like upper(pp) 
    )   
    and exists (
      select 1
        from json_array_elements_text(p.idRefClass) pp 
       where (&idRefClass) = cast(pp as int8) 
    )   
```

**Скрипт проверки строк по объектному кешу:**
```scala
for (p: params) {
  var jObj = toJObject(p);
  var res = true;

  // проверка sClass
  if (res) {
    res = false;
    var aValues = jObj.childJArray("sClass");
    var i = 0;
    while(i < aValues.size() && !res) {
      var value = aValues.getString(i);
      if (row.sClass != null && value != null) {
        if (like(row.sClass.toUpperCase(), value.toUpperCase())) {
          res = true;
        }
      }  
      i = i + 1;
    }
  }

  // проверка idRefClass
  if (res) {
    res = false;
    var aValues = jObj.childJArray("idRefClass");
    var i = 0;
    while(i < aValues.size() && !res) {
      var value = aValues.getLong(i);
      if (row.idRefClass == value) {
        res = true;
      }
      i = i + 1;
    }
  }

  if (res) {
    return true;
  }
}
```
