# Перенос печатных форм между ландшафтами

Для защиты контролируемых ландшафтов, например QAS, STG и PRD, от переноса непроверенных печатных форм используется следующая схема работы:

1. **Перенос через код (`DataInstall`).** Системные и проектные печатные формы поставляются в составе прикладных модулей из репозитория Git.
2. **Блокировка ручных изменений.** На целевых ландшафтах можно запретить ручной импорт, изменение и удаление печатных форм, поставляемых через код. Попытки выполнения запрещённых действий блокируются и регистрируются в журнале переносов.

## Способы переноса печатных форм

Объект печатной формы `Rpt_Report` содержит обязательный атрибут **Способ переноса** (`idTransferType`). Атрибут ссылается на справочник способов переноса `Rpt_TransferType` и имеет следующие значения:

- **Поставка с модулем** (`ModuleDelivery`) — печатная форма автоматически регистрируется через `DataInstall` при установке или обновлении прикладного модуля. При включённом запрете ручного переноса ручные операции с такой печатной формой блокируются.
- **Ручной перенос** (`Manual`) — печатная форма переносится через импорт и экспорт XML- или JSON-файлов либо путём ручного создания версий.

Значение атрибута определяется настройками модуля RPT и способом регистрации печатной формы:

- **Способ переноса печатной формы по умолчанию** (`Rpt_DefaultTransferType`) определяет значение, которое устанавливается при интерактивном создании печатной формы.
- При выполнении `DataInstall` для печатных форм, регистрируемых из ресурсов модуля, принудительно устанавливается значение **Поставка с модулем** (`ModuleDelivery`), а для существующих печатных форм при миграции — **Ручной перенос** (`Manual`).

## Автоматическая регистрация из ресурсов модуля

Для установки печатных форм при обновлении релиза используется автоматическая регистрация из ресурсов прикладного модуля через `DataInstall`.

**Правила именования каталогов и отчёта**

- **Имя отчёта (системное имя):** должно точно совпадать с именем корневого каталога печатной формы в ресурсах модуля с учётом регистра. Например, ресурсы отчёта `MyReport` должны находиться в каталоге `reports/MyReport/`.
- **Каталог отчёта:** структура каталогов в ресурсах прикладного модуля должна соответствовать шаблону `src/main/resources/reports/<СистемноеИмяОтчёта>/<ИмяВерсии>/`.
- **Имя версии:** значение, переданное в метод `.version("<ИмяВерсии>")`, должно точно совпадать с именем подкаталога версии в ресурсах с учётом регистра.

Пример каталога версии `1.0` печатной формы `MyReport`:

```text
src/main/resources/reports/MyReport/1.0/
```

Для регистрации печатной формы в API-классе прикладного модуля используется билдер `Rpt_ReportApi.registerFromResource`:

```scala
Rpt_ReportApi().registerFromResource("MyReportSystemName")
  .module("rpt")
  .version("1.0")
    .templateType("Jasper")
    .formats(Seq(ReportFormat.pdf, ReportFormat.xlsx))
  .register()
```

**Методы билдера печатной формы**

- `module(value: NString)` — обязательный метод. Задаёт прикладной модуль, к которому относится печатная форма. Если метод не вызван, регистрация завершается ошибкой.
- `version(value: String)` — обязательный метод. Начинает настройку версии печатной формы. В цепочке методов должна быть настроена хотя бы одна версия.

**Методы билдера версии печатной формы**

- `templateType(value: NString)` — обязательный метод. Задаёт тип шаблона по его мнемокоду, например `Jasper`, `Doc`, `Xls` или `Xlsx`. Если тип шаблона не указан, регистрация завершается ошибкой.
- `date(value: NDate)` — необязательный метод. Задаёт начальную дату действия версии печатной формы. Если метод не вызван, используется текущее системное время `NDate.now()`.
- `formats(value: Seq[ReportFormat.Value])` — необязательный метод. Задаёт перечень доступных выходных форматов.
- `register()` — обязательный метод. Запускает обработку ресурсов, проверку параметров, синхронизацию форматов, упаковку шаблонов и сохранение изменений в базе данных. Возвращает идентификатор печатной формы типа `NLong`.

**Регистрация нескольких версий**

Для регистрации нескольких версий метод `version()` вызывается несколько раз в одной цепочке. При каждом вызове билдер переключается на настройку указанной версии:

```scala
Rpt_ReportApi().registerFromResource("MyReportSystemName")
  .module("rpt")
  .version("1.0")
    .templateType("Jasper")
    .formats(Seq(ReportFormat.pdf))
  .version("2.0")
    .templateType("Jasper")
    .formats(Seq(ReportFormat.pdf, ReportFormat.xlsx))
  .register()
```

**Алгоритм поиска и упаковки шаблонов**

1. **Поиск файлов.** Билдер сканирует каталог `reports/<Имя отчета>/<Имя версии>/` в `classpath`. В файловой системе обрабатывается содержимое каталога, а внутри JAR-файлов — записи `JarEntry` с помощью `JarFileHelper`. Исходные файлы шаблонов при этом хранятся в Git и доступны для проверки в GitLab.
2. **Упаковка в ZIP-архив.** Перед сохранением шаблона в базе данных файлы упаковываются в ZIP-архив.
   - Для шаблонов Jasper допускается несколько файлов, включая основной шаблон и шаблоны подотчётов. Файлы добавляются в архив с сохранением исходных имён.
   - Для остальных типов шаблонов, допускается один файл. В архиве файл сохраняется под именем `template.<расширение>`.
3. **Синхронизация доступных форматов.** Для коллекции доступных форматов экспорта версии печатной формы вычисляется разность (`Set.diff`) между форматами, указанными в методе `formats()`, и форматами, сохранёнными в базе данных. Форматы, отсутствующие в коде, удаляются из коллекции, а новые форматы добавляются. Вызовы `session.flush()` предотвращают коллизии ключей в кэше EclipseLink.
4. **Проверка форматов.** Для каждого формата автоматически проверяется совместимость с типом шаблона. Например, формат `xls` нельзя указать для шаблона типа `Doc`. При обнаружении несовместимого формата выполнение `DataInstall` завершается с ошибкой `AppException`.

## Блокировка и контроль ручного переноса

Если на ландшафте включена системная настройка **Запрет ручного переноса печатных форм** (`Rpt_BlockManualTransfer`), для печатных форм со способом переноса **Поставка с модулем** (`ModuleDelivery`) применяются ограничения на ручной импорт, изменение и удаление.

**На уровне бизнес-логики**

- Импорт из JSON через метод `registerFromJson` класса `Rpt_ReportApi` завершается с ошибкой `AppException`.
- Импорт через Менеджер конфигураций с помощью `Btk_ConfigManagerImporter` завершается с ошибкой `AppException`.
- Удаление отчётов через `Rpt_ReportApi` и их версий через `Rpt_ReportVersionApi` запрещено.

**В пользовательском интерфейсе**

- В карточке печатной формы блокируется операция удаления и скрывается кнопка **Загрузить ПФ из json**. Описательные текстовые поля остаются доступными для редактирования.
- Списки версий переводятся в режим только для чтения. Создание и удаление версий блокируются. Кнопки ручной загрузки файлов шаблонов в базу данных и импорта версий из JSON скрываются.
- Изменение состава доступных форматов версии блокируется.

## Журнал переносов печатных форм

Журнал переносов печатных форм доступен по пути: `Настройка системы > Аудит > Журнал переноса печатных форм`.

Также журнал можно открыть через поле **Выборка**, указав `Rpt_TransferLogAvi`.

В журнале регистрируются:

- автоматическая регистрация через `DataInstall`;
- импорт печатной формы из JSON-файла;
- импорт через Менеджер конфигураций;
- заблокированные попытки ручного импорта.

Для каждой операции сохраняются:

- дата и время операции;
- пользователь, выполнивший действие;
- системное имя печатной формы;
- способ переноса;
- способ доставки;
- признак активности запрета ручного переноса;
- результат выполнения в поле `b_success` и описание ошибки при неуспешном выполнении.

Сведения о переносах печатных форм сохраняются в таблице аудита `aud.rpt_transfer_log`. Таблица `aud.rpt_transfer_log` по умолчанию включена в типовое задание очистки устаревших данных, настроенное в Менеджере заданий. Срок хранения записей журнала составляет 60 дней. Записи старше установленного срока удаляются автоматически при выполнении задания.