# Перенос конфигурации между площадками

**Утилита переноса конфигурации** позволяет администратору перенести настройки с одной площадки 1Формы на другую. Через неё переносят категории и разделы, дополнительные параметры (ДП), статусы и маршруты, порталы, отчёты, публикации, автоматизации (смарт-пакеты, смарт-выражения, смарт-расписания) и другие элементы конфигурации. Переносятся именно **настройки** — рабочие данные (задачи, комментарии, пользовательские файлы) утилита не выгружает (исключение — записи справочников 2.0, см. ниже). Администратор выбирает нужные объекты в дереве, выгружает их в архив на площадке-источнике и загружает этот архив на площадке-приёмнике. Инструмент рассчитан на администратора или настройщика площадки и работает через раздел администрирования.

## Кто может переносить конфигурацию

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

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

## Важные условия и ограничения

⚠️ **Версии площадок должны совпадать.** Экспорт и импорт конфигурации предназначены не для обновления платформы, а только для переноса настроек между экземплярами одной версии, поэтому версии площадки-источника и площадки-приёмника должны совпадать. Утилита поддерживает и кросс-версионный перенос: при расхождении версий она автоматически создаёт заглушки для недостающих зависимостей (категории, группы, справочники) и сопоставляет объекты по GUID. Если на приёмнике нет таблиц или колонок из более новой версии, сначала выполняют миграцию схемы базы (dbdeploy).

⚠️ **Площадка-приёмник должна быть «чистой».** На приёмнике не должно быть заранее созданных сущностей — кроме пользователей, оргструктуры и групп пользователей. Все настройки должны попадать в базу только через перенос. Иначе возможны конфликты идентификаторов и перезапись чужих данных.

⚠️ **Для лога в реальном времени нужен доступный endpoint `/migrationUtilityHub`.** Чтобы на странице переноса в реальном времени обновлялись лог и статусы, через reverse proxy должен быть доступен SignalR-endpoint `/migrationUtilityHub`. Без него экспорт всё равно выполнится (как фоновая операция), но интерфейс не будет показывать обновления статуса.

**Без остановки приложения.** Перенос не требует остановки приложения. Новые настройки применяются без перезагрузки пула приложения.

## Что переносится, а что настраивается вручную

При переносе объекты сопоставляются по GUID, а не по ID — это защищает от дублирования. Ниже собраны все правила и особенности переноса: что переносится автоматически, что тянется вместе со связанными объектами, а что нужно донастроить на приёмнике вручную.

**Привязка к модулю у отдельного объекта.** Если архив объекта не содержит сам модуль, новый объект после импорта остаётся без привязки к модулю: совпавший на приёмнике числовой идентификатор модуля не используется, и сопоставление модуля по GUID не выполняется — назначьте модуль вручную в форме администрирования. Если объект уже есть на приёмнике и найден по GUID, импорт сохраняет назначенную там привязку. Правило относится к разделам, категориям, дополнительным параметрам, статусам, подписям, пакетам действий, смарт-выражениям и скриптам. Когда архив содержит сам модуль, привязка переносится как обычно.

| № | Правило | Пояснение |
|---|---------|-----------|
| 1 | Данные задач и пользовательские файлы **не переносятся** | Утилита выгружает только настройки и отдельные объекты (например, перечисления). Рабочие данные задач и вложения остаются на источнике |
| 2 | Кастомные объекты БД настраиваются **вручную** | Хранимые процедуры, SQL-функции, представления (view), шаблоны файлов (печатные формы) утилита не переносит — их создают на приёмнике вручную (см. предупреждение ниже) |
| 3 | При переносе ДП переносятся только его настройки | Связанные категории, разделы, сводные разделы и другие ДП нужно выбирать в дереве вручную. Исключение: привязка ДП к блокам и доступ к ДП на статусах включаются автоматически |
| 4 | При переносе категории подтягиваются связанные группы | Группы с правами на категорию, ответственная группа, группа уведомления о превышении плана, группы из настроек доступа к ДП и из уведомлений |
| 5 | **Состав группы (участники) не переносится** | При переносе группы создаётся только сама группа с тем же ID; список её участников не передаётся |
| 6 | Объекты, удалённые на источнике, **удаляются и на приёмнике** | Касается ДП, подписей на переходе, JS/CSS-вставок, кнопок, произвольных событий, блоков «Используется», блоков ДП, а также привязок ДП к блокам. Важно учитывать при повторном (инкрементальном) переносе |
| 7 | Для lookup/multilookup ДП категорию-источник выбирают вручную | При первом переносе такого ДП или после изменений в категории-источнике. Если ДП настроен на сводный раздел — выбирают сам раздел и входящие в него категории |
| 8 | При экспорте multilookup сохраняются настройки «Параметры в гриде» | Они восстанавливаются вместе с ДП при импорте. Это настройка колонок табличного представления выбранных задач внутри параметра; настройки табличного представления самого источника данных — отдельная сущность (правило 33) |
| 9 | Для сквозных ДП проверяют наличие целевого ДП и категории | В целевом приложении должны быть и ДП, на который ссылается сквозной параметр, и соответствующая категория |
| 10 | Параметры действий с группами и оргструктурой сопоставляются автоматически | Параметры смарт-действий с группами сопоставляются по соответствиям пакета, параметры с подразделениями — по идентификатору при наличии объекта на приёмнике. Если объекта нет, одиночный параметр действия удаляется (без сбоя импорта), а из списков ненайденные значения исключаются; запись об этом есть в журнале импорта. Идентификаторы групп внутри SQL смарт-выражений по-прежнему при необходимости выверяются вручную |
| 11 | Общие смарт-выражения (SMART) выбирают вручную | В дереве экспорта — в ветке «Смарт-выражения» |
| 12 | Почтовые ящики в автоматизациях меняют вручную | После переноса указывают почтовые ящики целевой площадки |
| 13 | Настройки мобильного приложения переносят вручную | Шаблоны задач, источники данных, контейнеры, шаблоны. Привязка шаблонов к категории переносится автоматически |
| 14 | **Порталы корректно переносятся только с сохранением ID** | Перенос порталов работает только при импорте с сохранением идентификаторов. В архив входят блоки корневой раскладки, адаптивных раскладок (размеры экрана xs…xl) и секций Board: блок, размещённый только в секции или только на одном размере экрана, сохраняется при выгрузке и загрузке, а раскладка приёмника при домерже не затирается. Вместе с порталами переносятся JS/CSS и смарты блоков; кастомные объекты БД блоков настраивают вручную Зависимости блоков, ссылающиеся на объекты конфигурации, при импорте перепривязываются по GUID к объектам приёмника: у блока «Профиль пользователя» так переносятся узел оргструктуры и смарт-выражения источника и подстрочника. Ссылку на конкретного пользователя перенос не поддерживает — таблица пользователей в конфигурацию переноса не входит, поэтому блок с источником «фиксированный пользователь» останавливает импорт с явной ошибкой, а не подставляет идентификатор площадки-источника. |
| 15 | Публикации переносятся со связанными объектами | Вместе с публикацией переносятся связанные объекты доступа, смарт-пакеты и представления |
| 16 | Локализованные объекты переносятся с локализациями | При конфликте идентификатора локализации утилита генерирует новые ID. Язык локализованного значения сопоставляется не по номеру, а по коду языка `LangAlias` (запасной путь — по культуре): справочник языков источника экспортируется в `_meta/languages.json`. Языка нет на приёмнике или его код неоднозначен — локализованное значение не импортируется и уходит в отчёт переноса, чужой язык не подставляется. Архив без файла языков разбирается по номеру, как раньше, с предупреждением в лог. |
| 17 | Смарт-выражения и LUA-скрипты переносятся со всеми версиями | |
| 18 | Смарт-выражение при переносе на боевую площадку автоматически повышает версию | Если версии выражения на площадках различаются |
| 19 | Отдельные типы объектов можно исключить из экспорта | Настройка приложения `MigrationExportSettings` позволяет не выгружать выбранные типы объектов (например, права доступа групп к категории) |
| 20 | Смарт-доступ по ДП переносится вместе с параметром; при конфликте ID генерируются новые | Правило смарт-доступа тянет связанный дополнительный параметр в пакет экспорта как зависимость, поэтому на приёмнике привязка правила к параметру приезжает рабочей. Если на приёмнике уже есть запись прав с тем же ID, но другим GUID, утилита создаёт новые уникальные идентификаторы |
| 21 | Настройки сквозного ДП обновляются под приёмник | При расхождении между площадками утилита заменяет пути в настройках сквозного параметра |
| 22 | Гибкие права на задачи переносятся вместе с ДП категории | |
| 23 | Системный журнал категорий переносится по умолчанию | |
| 24 | Раздел «Статусы» можно экспортировать отдельно | Статусы экспортируются независимо от категории. Это удобно при конфликтах маршрутов (дублирующиеся идентификаторы маршрутов) |
| 25 | Права групп на категорию синхронизируются при импорте категории целиком | Права групп в целевой категории приводятся в точное соответствие с пакетом: отсутствующие в экспорте — удаляются, присутствующие — добавляются или обновляются. Работает и на MS SQL, и на PostgreSQL |
| 26 | Admin-настройки табличного представления категории и блоков «Используется» (БИ) переносятся вместе с категорией | Переносятся обе разновидности админской настройки табличного вида — и для табличного представления категории, и для каждого блока «Используется»; владелец настройки сопоставляется с объектом на приёмнике по GUID, поэтому состав и порядок колонок, ширины и фильтры совпадают с источником, даже если идентификаторы на площадках разные. Переносятся только записи без привязки к пользователю; пользовательские настройки колонок и фильтров не переносятся и пересоздаются автоматически на приёмнике |
| 27 | Параметры фильтра отчёта переносятся с сохранением ID | В стандартном режиме идентификаторы параметров фильтра сохраняются так же, как у отчёта и самого фильтра. Вместе с параметрами в архив попадают связи между зависимыми параметрами, поэтому настроенные зависимости работают на приёмнике без ручной правки |
| 28 | Валидаторы ДП переносятся вместе с категорией | Валидатор уезжает в архив следом за своими привязками — к шагу маршрута и к кнопке карточки задачи, — поэтому на приёмнике проверки значений ДП работают без ручной донастройки |
| 29 | Настройки произвольных источников данных и общие выборки переносятся с категорией | В пакет попадают настройки источника и сохранённые выборки, помеченные как общие; личные выборки и личные настройки конкретных пользователей не экспортируются. При импорте номера задач внутри сохранённых выборок пакета заменяются на номера задач приёмника — и в списке задач фильтра, и в поле отдельной задачи, и в фильтрах сохранённого вида выборки: в фильтре «Выбор значений», в мультилукап-фильтре по задачам и в конструкторе поиска (модель продвинутого фильтра); границы страницы выборки не меняются. Если личная выборка всё же оказалась в пакете, номера задач в ней заменяются так же, как в общей |
| 30 | Опрос переносится вместе со своим шаблоном | По решению владельца шаблон уезжает вместе с категорией: задачи-шаблоны опросов этой категории, конфигурация каждого опроса и его вопросы, а также режим опроса самой категории. Отдельно выбирать опросы в дереве экспорта не нужно. Созданные опросы, назначения опросов задачам и результаты прохождения не переносятся — это рабочие данные площадки-источника. При импорте утилита перепривязывает ссылки на задачи и файлы внутри конфигурации опроса во всех режимах, включая стандартный, а задачу вне пакета ищет на приёмнике по GUID. Ссылка, которую сопоставить не удалось, остаётся прежней, а в лог переноса пишется предупреждение. **Состояние на сборке 2.268:** задачи-шаблоны в пакет не попадают — их отсекает экспортный фильтр выгрузки категории, поэтому шаблон опроса пока не переносится; исправление готовится |
| 31 | Теги категории переносятся вместе с ней | Вместе с категорией переезжают типы тегов и маппинги тегов. При повторном переносе на приёмник, где такие теги уже есть, совпавшие записи не дублируются: утилита сопоставляет их по паре «тип тега и имя» или «тип тега, алиас параметра и внешний параметр» и переводит запись в обновление. Если маппинг источника совпал с разными записями приёмника или две записи источника ведут на одну запись приёмника, импорт прерывается с перечислением конфликтующих записей. |
| 32 | Запись с несопоставленной обязательной ссылкой не переносится | Если обязательная ссылка — часть составного ключа или обычная обязательная ссылка — не нашлась на приёмнике ни по идентификатору, ни по GUID, утилита не подставляет вместо неё произвольную запись справочника: запись отбрасывается, а в лог переноса идёт предупреждение с типом сущности и значением ссылки. Исключение — ссылка на пользователя: вместо ненайденного подставляется системный робот. Для параметров действий пакетов правило своё: значение параметра-пользователя сопоставляется с пользователями приёмника по идентификатору, и если такого пользователя на приёмнике нет, одиночное значение заменяется системным роботом, а в списке пользователей ненайденные убираются — робот вместо элемента списка не подставляется. Каждая такая замена и каждое удаление попадают в журнал переноса. Если записей не хватает после импорта, ищите эти предупреждения в логе |
| 33 | Настройки экрана «Источник данных» лукапа и мультилукапа переносятся вместе с параметром | В пакет включаются админские настройки табличного представления источника данных: состав и порядок колонок, «Доступность», «Видимость по умолчанию», «Стиль», «Выравнивание», «Ширина», «Тип фильтра», а также общие настройки экрана — «Тип статуса», «Группировать по», «Группировать одной строкой», «Позиция закрепления». Переносятся только админские настройки; личные настройки пользователей остаются на своей площадке. Если у параметра на приёмнике уже была сохранена своя настройка источника, она заменяется настройкой площадки-источника — вторая запись не создаётся. Категорию-источник при этом по-прежнему выбирают вручную (правило 7), отдельно переносятся «Параметры в гриде» мультилукапа (правило 8) |
| 34 | Привязки ДП к блокам синхронизируются при повторном переносе | Привязка «ДП → блок» приводится в соответствие с пакетом: привязки перенесённого ДП, которых нет в пакете, на приёмнике снимаются. Это важно при повторном (инкрементальном) переносе на непустой приёмник: если на источнике ДП переместили из блока A в блок B, а категорию переносят второй раз, привязка к блоку A на приёмнике снимается, и параметр остаётся только в блоке B. На сборках до 2.268 «Скульптор» привязка к прежнему блоку сохранялась, и один ДП отображался в карточке дважды — в старом и новом блоках |

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



> ⚠️ **Кастомных объектов БД в дереве экспорта нет.** Хранимые процедуры, SQL-функции и представления (view) утилита не выгружает — отдельной ветки под них в дереве объектов нет, искать их там не нужно. Их создают на площадке-приёмнике вручную (через SSMS/psql), и только после этого настраивают зависящие от них объекты (например, SQL-представление доступа к категории).

⚠️ **Повторный перенос на площадку, где настройка вида уже настроена.** Админские настройки табличного вида категории и блоков «Используется» сопоставляются с приёмником по GUID: если для того же блока или категории на приёмнике уже сохранена своя настройка, перенос добавит рядом вторую запись, а не заменит существующую. Замена сохранённой настройки гарантирована только для настроек экрана «Источник данных» лукапа и мультилукапа (правило 33). Перед переносом на непустой приёмник проверьте, не настроен ли нужный блок или категория вручную.

## Экспорт конфигурации

Администратор открывает дерево объектов, отмечает нужные элементы и выгружает их. Выгруженная конфигурация упаковывается в **ZIP-архив**. При импорте формат архива определяется автоматически, поэтому архивы, выгруженные разными версиями утилиты, загружаются без дополнительной подготовки.

### Поиск настроек на странице экспорта

Чтобы не искать нужные настройки перебором в дереве объектов, над деревом есть поле поиска. Поиск, как и остальные операции переноса, доступен только администратору (God).

Поиск идёт по названию настройки (вхождение подстроки, без учёта регистра) или по точному числовому id — например, по id категории или дополнительного параметра. Запускается он, когда в поле набрано не меньше символов, чем задано настройкой приложения `NumberCharactersForSearch`; числовой запрос ищет с первого символа.

Поиск охватывает разделы, категории, дополнительные параметры (ДП) и ДП в составе категории. Остальные настройки, как и раньше, ищут в дереве вручную.

Найденное показывается списком, сгруппированным по типу настройки; у каждой записи видны название и её id. Записи разных типов с одинаковым числовым id показываются отдельными строками и выбираются независимо друг от друга. Найденную категорию можно раскрыть прямо из результатов и отметить в ней нужные дочерние настройки — искать её отдельно в дереве не требуется.

Отмеченные из результатов поиска настройки остаются в выборе и после очистки строки запроса. Кнопка «По умолчанию» очищает и выбор, и поиск.

Если подходящих записей больше, чем помещается в выдачу, страница об этом сообщает и предлагает уточнить запрос.

При поиске поле «Поиск» остаётся над списком дерева. Если поиск выполняется, ничего не нашёл или выдача ограничена, сообщение состояния показывается под деревом и остаётся видимым без прокрутки страницы. Прокручивается список дерева: у области страницы экспорта своей вертикальной или горизонтальной полосы прокрутки нет. Длинные названия прокручиваются по горизонтали внутри списка.

Поиск ищет только то, что есть в дереве экспорта.


### Полная выгрузка

Кнопка «Полная выгрузка» запускает фоновую выгрузку всей конфигурации площадки. Готовый архив помещается в файловое хранилище, в папку «Файлы автоматизации / Export configurations»; доступ к нему получает группа администраторов (GodGroup). По завершении система присылает уведомление. Пока выгрузка идёт, кнопка неактивна.

### Частичный экспорт и родительские разделы

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

Частичный экспорт выполняется сразу и ждёт ответа сервера. На больших объёмах запрос может не уложиться в отведённое время — тогда система сообщает, что выгрузить файл не удалось, и рекомендует «Полную выгрузку», которая работает в фоне.

## Перенос справочников 2.0

Утилита переносит не только структуру категории-справочника (поля, маршруты, представления), но и сами **записи справочника** вместе с заполненными дополнительными параметрами. Это позволяет перенести готовый справочник целиком с одной площадки на другую. Перенос значений справочника включается отдельной опцией при экспорте.

**Что попадает в пакет:**
- Категория справочника (название, дополнительные параметры, представления, маршруты).
- Записи справочника — как задачи соответствующей категории.
- Значения дополнительных параметров этих записей: lookup-связи на другие справочники, выбранные задачи и папки, табличные ДП.

**Что пока не переносится:**
- Значения ДП типа «Выбор пользователей», «Выбор групп», «Выбор подразделений» — при экспорте по ним остаётся предупреждение в системном логе.

**Ссылка на задачу вне пакета переноса.** Если запись справочника через lookup ссылается на задачу, которая не вошла в пакет, утилита запоминает её GUID. При импорте она ищет задачу с этим GUID на площадке-приёмнике: если такая задача есть, ссылка автоматически привязывается к ней. Если задачи с этим GUID нет ни в пакете, ни на приёмнике, ссылка пропускается без ошибки — остальные записи импортируются штатно, а о пропущенных значениях остаётся запись в системном логе.

**Ссылка на запись справочника в смарт-выражениях и скриптах.** При переносе номер записи справочника подставляется автоматически, если запись перенесена в том же пакете и её номер стоит в SQL-сравнении с `taskid`, `SelectedTaskId`, `NaturalValue` или с колонкой `ExtParam{N}NativeValue` лукап-ДП - в смарт-выражениях, в SQL отчётов и в строковых литералах смарт-скриптов, которые распознаны как SQL. Номер, собранный конкатенацией, вычисленный или записанный вне такого сравнения, утилита не узнаёт и не меняет - там на запись справочника ссылаются по GUID.

**Ссылки на задачи и параметры в настройках смарт-действий.** Фиксированные значения параметров действий в пакетах тоже приводятся к приёмнику: номер задачи в параметрах действий «Отправить сообщение пользователю» (вставка текста и открытие задачи) и «Сгенерировать по шаблону», значение лукап-параметра и номер дополнительного параметра в действиях «Изменить значение ДП» и «Массово изменить значение ДП», а также SQL в действиях «Выполнить SQL» и «Создать или обновить смарт-фильтр» — он разбирается так же, как SQL смарт-выражений. Пользовательские действия и действия, неизвестные этой версии, пропускаются без изменений и не прерывают импорт; значение, которое не удалось разобрать или сопоставить, остаётся прежним, а в лог переноса пишется предупреждение.

**Пользователи в параметрах действий.** Параметры типа «Пользователь» и «Список пользователей» приводятся к приёмнику так же, как ссылки на задачи: значение сверяется с пользователями приёмника по идентификатору. Если пользователь на приёмнике есть, значение сохраняется; если его нет, одиночный параметр заменяется системным роботом приёмника, а из списка ненайденные пользователи убираются. Робот проверки прав не проходит насквозь, а снимает их, поэтому подмену важно заметить: по каждому параметру с явно заданными пользователями в журнал переноса идёт предупреждение с пакетом, действием, параметром, исходными идентификаторами и результатом.

**Группы и оргструктура в параметрах действий.** Параметры действий с группами и типами групп сопоставляются по соответствиям пакета, а параметры с подразделениями и типами подразделений — по идентификатору при условии, что объект есть на приёмнике. Если группа или подразделение на приёмнике не найдены, одиночный фиксированный параметр не перезаписывается пустым значением, а удаляется из действия (иначе таблица параметров не примет пустое значение без выражения), а из списочного параметра ненайденные идентификаторы исключаются. Перенос конфигурации при этом завершается успешно, а каждое убранное значение попадает в журнал импорта отдельной строкой с действием, параметром и идентификатором. Перенос внутри одной базы данных работает как прежде.

**Ссылки на объекты, которых нет в пакете.** Если пользователь или группа упоминается в переносимых данных, но сам объект в пакет не входит, ссылка ищется не по номеру, а по смысловому ключу: пользователь — по логину, группа — по своему идентификатору в справочнике. Раньше такая ссылка подбиралась по номеру, и если номер на приёмнике был занят другим объектом, ссылка молча указывала на чужого сотрудника или чужую группу, строки в журнале не появлялось. Если объекта на приёмнике нет, ссылка убирается по общему правилу, а случай попадает в журнал переноса.

**Группа уведомления шага, которой нет на приёмнике.** Если у шага маршрута задана группа уведомления, а на площадке-приёмнике такой группы нет, импорт снимает уведомление целиком: вместе со ссылкой на группу очищаются флаг уведомления и текст. После переноса такой шаг не отправляет ни уведомление, ни письма, а в журнал импорта попадает запись с объектом, полем, исходным идентификатором группы и итогом. Причина в том, что пустая группа для потребителя означает не «никому», а «всем»: получателем становится заданная группа, а если её нет — все пользователи с правом просмотра задач категории, и шаг, настроенный на узкий круг, после переноса рассылал бы уведомления всем. Если группа на приёмнике есть или шаг и на источнике уведомлял без группы, поведение не меняется.
**Группы и пользователи в настройках.** Ссылки на группы и пользователей в настройках при переносе сверяются с площадкой-приёмником по идентификатору, а не переезжают как есть: ненайденная ссылка убирается — одиночное значение остаётся пустым, из списка ненайденные значения убираются, исходный идентификатор не сохраняется и системный робот вместо отсутствующего объекта не подставляется. Сверяются, в частности, группы смарт-доступа, группа уведомления в маршруте категории, приглашённая группа категории, группа-источник данных блока портала, значения по умолчанию колонок табличных параметров с выбором пользователей, значения параметров действий по изменению параметров и созданию задачи, автор задачи и инициатор шага у переносимых задач и шаблонов. Если у правила смарт-доступа после переноса не осталось ни одной группы, правило вместе с привязкой к категориям на приёмник не переносится: правило без групп означает доступ для всех, и создавать его нельзя.

**Журнал переноса.** Каждое убранное значение попадает в журнал отдельной строкой: объект, поле и исходный идентификатор. О блоке портала отдельно: у него поле группы — не видимость, а источник данных рабочего места, поэтому отсутствие группы не расширяет доступ к блоку — кто видит блок, по-прежнему определяют связи блока с группами, и перенос их не трогает.

### Если на приёмнике нет объекта, на который ссылается перенос

Перенос не обрывается, если на площадке-приёмнике нет пользователя, задачи или адреса, на которые ссылаются переносимые данные. Значения дополнительных параметров записей справочников очищаются: ссылка на задачу, адрес и выбранный пользователь остаются пустыми. Строка настроек колонок иерархии задач с отсутствующим пользователем не переносится вовсе — в отличие от ссылок в записях справочников, системный робот вместо него не подставляется. Каждый такой случай пишется в журнал импорта отдельной строкой с сущностью, полем и исходным идентификатором, а сам импорт считается успешным. Проверить, что не переехало, можно по журналу переноса после завершения импорта.



**Денормализация значений.** После импорта утилита сама обновляет денормализованное представление справочника и пересоздаёт денорм-представления табличных ДП, чтобы значения сразу были видны в табличных представлениях, фильтрах и связанных задачах — без перезагрузки пула приложения.

**Паритет PostgreSQL.** На PostgreSQL поведение одиночных lookup-связей приведено к поведению MS SQL Server, поэтому импорт справочников 2.0 даёт одинаковый результат на обеих СУБД.

**Несопоставленные ссылки: сводка в конце переноса.** Замена идентификаторов — не блокирующая проверка: если ссылку сопоставить не удалось, импорт не прерывается, а в конце переноса утилита печатает одну сводку уровня Warn — по каждому объекту его вид, название, GUID и несопоставленные номера. Сводка идёт в системный лог приёмника и параллельно транслируется в открытое окно переноса (SignalR-хаб `MigrationUtilityHub`, событие `notify`). После переноса сводку стоит просмотреть: до первого срабатывания настройки несопоставленная ссылка внешне себя не проявляет. Иначе ведут себя обязательные ссылки в записях справочника — такая запись не переносится вовсе (см. правило 32).

## Опции импорта

По умолчанию утилита переносит объекты **с сохранением их идентификаторов**: объекты сопоставляются по GUID, уже существующие на приёмнике обновляются, а новые добавляются с тем же ID, что и на источнике. Это стандартный сценарий переноса настроек между площадками одной версии.

После выбора файла миграции становятся доступны дополнительные опции:

| Опция | Что делает |
|-------|-----------|
| **Нужна денормализация** | Запускает денормализацию переносимых категорий. Гарантированно применяется при полном переносе категории. При частичном импорте (только новые ДП или блоки) автоматическая денормализация **может не обновить** структуру денорм-таблицы — тогда денормализацию выполняют вручную после импорта. Для сводного раздела опция обязательна |
| **Загружать все резолюции** | По умолчанию резолюции из статических подписей **не импортируются** вместе с категорией; опция включает их перенос |
| **Импортировать как новые сущности** | Все объекты создаются заново — с новыми GUID и идентификаторами, которые выдаёт площадка-приёмник. Получается независимая копия конфигурации, не связанная с источником по ID. Автором версий указывается служебная учётная запись «Робот 1Ф». Исключения: системные статусы «Новая», «Выполняется», «Завершена», «Отклонена» не дублируются; служебные группы `Administrators`, «Исключить из чатов», «Включить в чаты» проверяются по общим настройкам приложения |
| **Импортировать без сохранения идентификаторов** | Объекты сопоставляются по GUID: уже существующие обновляются с сохранением своих прежних ID, новые добавляются с идентификаторами приёмника. После импорта утилита сама заменяет ID в смарт-выражениях, ссылки в JSON-настройках порталов и параметры смарт-правил. Предназначен для обновления боевой площадки конфигурацией со стенда разработки, когда идентификаторы объектов разошлись |

**Какой режим переноса выбрать:**

- **Стандартный перенос (с сохранением ID)** — обычный перенос настроек между площадками одной версии. Никаких дополнительных опций включать не нужно: объекты сопоставляются по GUID, существующие обновляются, новые добавляются с прежними идентификаторами.
- **Импортировать как новые сущности** — когда нужна независимая копия конфигурации с новыми идентификаторами (например, размножить готовую настройку на новом стенде).
- **Импортировать без сохранения идентификаторов** — когда обновляют боевую площадку конфигурацией со стенда разработки, а ID объектов на площадках уже разошлись.

⚠️ **Режим «Импортировать без сохранения идентификаторов» доступен только в утилите командной строки и в WinForms-версии.** В веб-интерфейсе (раздел администрирования) этой опции нет.

### Предпросмотр (Preview)

Кнопка «Проверить» запускает анализ переносимых объектов. В таблице предпросмотра показываются:

| Колонка | Что показывает |
|---------|----------------|
| Название | Название переносимого объекта |
| Таблица | Таблица базы данных приёмника |
| GUID | GUID переносимого объекта |
| Статус | Один из четырёх статусов (см. ниже) |
| Изменённые поля | Только для статуса «Обновить» — список отличающихся полей до запуска импорта |

Статусы предпросмотра:

| Статус | Значение |
|--------|----------|
| Добавить | Объект отсутствует на приёмнике и будет добавлен |
| Обновить | Объект есть на приёмнике, но будет обновлён; доступен список изменённых полей |
| Конфликт | Объект с таким идентификатором уже есть, загрузка невозможна |
| Без изменений | Объект одинаков на источнике и приёмнике |

Опция «Показать только изменяемые» скрывает записи со статусом «Без изменений».

### После импорта

После завершения импорта рекомендуется:

1. Вручную проверить корректность перенесённых настроек — в том числе настройки табличного представления: состав и порядок колонок табличного вида перенесённых категорий и блоков «Используется» должны совпадать с источником.
2. Очистить кеш.
3. Выполнить денормализацию категорий, для которых переносились настройки.
4. Перезапустить пул приложения.
5. Просмотреть журнал переноса и проверить пакеты, у которых в параметрах действий были явно заданы пользователи: сохранённые значения, удалённые из списков и замены на системного робота перечислены там отдельными предупреждениями. Замена на робота означает, что после переноса действие работает без проверки прав.
6. Просмотреть журнал переноса на предмет убранных ссылок на группы и пользователей в настройках: каждая такая ссылка указана отдельной строкой с полем и идентификатором. Если у правила смарт-доступа не осталось групп, правило на приёмник не переносилось — его нужно настроить заново.

## Импорт на непустую базу

При стандартном импорте (с сохранением идентификаторов) утилита добавляет объекты с теми же ID, что и на источнике. Из-за этого даже на, казалось бы, пустой базе возможны конфликты первичных ключей, если в ней уже есть предзаполненные (seed) данные платформы.

Типовой пример — конфликт по дополнительным параметрам: один и тот же внутренний идентификатор может быть занят разными ДП в импортируемом пакете и в seed-данных базы. В этом случае импорт прерывается ошибкой нарушения первичного ключа (`Violation of PRIMARY KEY constraint`).


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

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

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

Ссылки привязок JS/CSS-вставок при стандартном импорте разрешаются по GUID вставки: подробнее — в режимах импорта, раздел «Режим 1: KeepIdentity».

## Модули и версионирование

### Импорт модуля и разрешение конфликтов имён

При импорте конфигурации, содержащей модули, действуют такие правила:

- **Конфликт названий.** Название модуля уникально без учёта регистра. Если модуль из архива претендует на название, занятое на площадке-приёмнике другим модулем, импорт прерывается до записи в базу — в любом режиме импорта. Администратор получает сообщение с названием модуля из архива и занятым именем вместо технической ошибки базы.
- **Автор и даты при импорте.** Числовой идентификатор пользователя исходной базы в карточку модуля не переносится: для нового модуля автором становится пользователь, запустивший импорт, а датами создания и изменения — момент импорта.
- **Обновление существующего модуля.** Если модуль сопоставлен по идентификатору с уже существующим, его первоначальный автор и дата создания сохраняются, а импорт фиксируется как изменение модуля: автор и дата последнего изменения обновляются на пользователя и время импорта.
- **Поле источника.** Заполняется сведениями об источнике из архива нового формата; при импорте архивов старого формата остаётся пустым, а существующее описание модуля не затирается.

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

Базовые операции с модулями:

- **Создание** — указать название и сохранить.
- **Редактирование** — изменить данные модуля в списке.
- **Удаление** — возможно только если в модуле нет связанных сущностей: система проверяет категории и разделы, дополнительные параметры, пакеты действий, смарт-выражения и скрипты, подписи, статусы, блоки и шаблоны портала, отчёты, роли БП и версии конфигураций и при наличии связей сообщает, что именно мешает и сколько таких записей. Создание и удаление модулей доступны администратору с полными правами.
- **Назначение** — в формах администрирования у объекта есть поле «Модуль». Если модуль не выбран, объект относится к общему (глобальному) модулю.

### Что меняется при правке модуля

Название модуля уникально без учёта регистра: попытка создать или переименовать модуль в уже занятое имя отклоняется с понятной ошибкой. Пробелы по краям названия сервер отбрасывает сам, а имя длиннее 200 символов не принимает. Вместе с названием задаётся описание модуля.

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

### Что видно в карточке модуля

Карточка модуля несёт не только название. У модуля есть описание, автор-создатель и автор последнего изменения с признаком «пользователь или группа», источник модуля (текст из архива при импорте), дата создания и дата последнего изменения, а также категории, привязанные к модулю.

**Версия в карточке — только своя.** Система сопоставляет версию по паре «модуль и версия». Если версия для модуля не создавалась или сохранённый указатель ведёт на версию другого модуля, поле версии остаётся пустым: чужая версия и нулевой идентификатор в карточке не показываются. Вместе с версией приходят её идентификатор, значение и дата.

Карточка отдаёт и идентификатор сохранённого указателя на версию — он нужен интерфейсу выбора версии, и ориентироваться на него как на «версию модуля» не стоит: версия модуля — это отдельное поле с идентификатором, значением и датой.

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

**Наследование.** При создании ДП и смартов внутри категории обычно наследуется модуль этой категории. Система ограничивает смешивание сущностей разных модулей в ряде сценариев настройки.

### Навигация по модулю

Административная панель умеет показать, какие разделы администрирования относятся к конкретному модулю и сколько объектов в них уже привязано к модулю. Для этого у модуля есть контракт навигации `GET /api/admin/modules/{moduleId}/navigation`: в ответ приходят папки админки с формами модуля, а по каждой форме — два числа.

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

Число описывает именно объекты самой формы: у формы «Категории» это количество категорий, привязанных к модулю, а не количество задач в этих категориях, — и совпадает с тем, что открывается по клику. Ответ содержит только те формы, на которые у администратора есть права: скрытая форма не раскрывается ни названием, ни числом.

Контракт не меняет общее дерево администрирования: глобальное дерево форм по-прежнему отдаётся без контекста модуля и обслуживает главную страницу админки. Доступ к навигации модуля — только у администратора: пользователь без административных прав получает отказ, несуществующий модуль — ошибку «не найдено».

### Версионирование модулей

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

#### Создание версии: либо версия создана, либо ничего не изменилось

**Переключение версии при отсутствии активной.** После удаления активной версии модуль может остаться без указателя на текущую версию. Переключение такого модуля на любую из оставшихся его версий отвечает успехом и применяет выбранную версию — внутренняя ошибка на этом пути не возникает. Если переключение не удалось, состояние откатывается к прежнему — то есть к модулю без активной версии, а несуществующая версия не подставляется. Переключение на версию другого модуля и на несуществующую версию по-прежнему отвечает отказом «не найдено».

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

**Сравнение даты модуля на Microsoft SQL Server.** При создании версии система сверяет состояние модуля перед записью указателя и аудита. Значение даты должно сравниваться с той же точностью, с которой оно хранится в базе: у типа с округлением миллисекунд сравнение с параметром другого временного типа может не найти неизменённую строку. Это не означает, что модуль действительно изменился параллельно. Исправление сохраняет корректное создание первой и следующих версий, а при ошибке выгрузки возвращает указатель версии и данные последнего изменения к состоянию до попытки, если модуль за это время не менялся. Если модуль был изменён параллельно, откат не перезаписывает чужие изменения; операция повторяется по актуальному состоянию либо завершается отказом. На PostgreSQL, при переключении версии и при удалении версии поведение не меняется.

Что это даёт на практике: после неудачной выгрузки название остаётся свободным, и повторный запрос с тем же названием выполняется как обычное создание версии, а не отклоняется как дубль. Отказ «версия с таким названием уже есть» приходит только тогда, когда версия с таким названием действительно создана ранее.

Остальное не меняется: отказ по занятому названию по-прежнему приходит до каких-либо записей, переключение модуля на другую версию и удаление версии работают как прежде.

#### Общая ветка в Git: что происходит с чужими файлами

Выгрузку версии можно направлять в уже существующую ветку, в которую пишет не только 1Форма. Файлы, появившиеся в ветке из чужих коммитов, выгрузка не трогает: она записывает и обновляет свои файлы, а всё, что не входит в опись снимка, остаётся на месте. Исключение — файлы, удалённые из самой конфигурации 1Формы: они снимаются с ветки вместе с выгрузкой.

Свои и чужие файлы система различает по описи снимка — служебному файлу `.1forma-owned` внутри каталога модуля. Опись записывается вместе с коммитом версии и приезжает на любой клон вместе с вершиной ветки, поэтому повторная выгрузка после чужого коммита в ту же ветку ничего из чужого не удаляет: в коммит попадают только файлы текущего снимка.

**Если ветку выгружал только прежний код** (до появления описи), при первой выгрузке новой версии система ещё не может отличить своё от чужого: такая выгрузка ничего на вершине не удаляет и только записывает опись снимка. Удаления настроек из конфигурации доезжают до такой ветки не с первой выгрузкой, а со следующей — отдельно чинить ничего не нужно, со второй выгрузки ветка ведёт себя как обычная.

#### Переключение версии читает вершину удалённой ветки

При переключении на версию система читает её прямо от вершины удалённой ветки модуля, а не слиянием с тем, что уже лежит в локальном клоне: она обновляет сведения об удалённой ветке, создаёт локальную ветку версии от её вершины, а если такая ветка уже есть — приводит её к вершине и выравнивает рабочую копию клона. Поэтому если в общую ветку модуля после выгрузки попал чужой коммит, переключение на версию вернёт состояние с вершины ветки, а не устаревший локальный снимок.

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

Правка касается репозитория Git.


#### Повторная выгрузка версии: что попадает в ветку

Повторная выгрузка — обычная операция: версия заново выгружается в ту же ветку `modules/<имя версии>`, и в ветке появляется новый коммит поверх того, что там уже есть. История ветки не переписывается: коммиты, попавшие в ветку помимо 1Формы, остаются в её истории, принудительная отправка не используется и прав на неё в репозитории не требуется.

Что попадает в коммит:

- все файлы версии, которые сейчас есть в конфигурации, — включая вложенные файлы мультифайловой раскладки;
- настройка, удалённая из конфигурации, исчезает из отправленного снимка тем же коммитом: удаления считаются по всему пути версии, а не только по тем каталогам, которые остались на диске;
- файл, который правила клона (`.gitignore`, `info/exclude`, глобальные правила) объявляют игнорируемым, всё равно попадает в снимок: файл версии служебный, и клиентские правила игнора не должны выбрасывать настройку из выгрузки.

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

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