# Сквозные ДП

Документ описывает сквозные дополнительные параметры (ДП) — специальный тип ExtParam, автоматически протягивающий значение через цепочку лукапов. Для администраторов и разработчиков. Разделы: бизнес-сценарий, структура данных (таблицы БД), правила валидности цепочки, отображение значения в интерфейсе, основные процедуры (PullThroughExtParam, ExtParamTroughSettingsValidate и др.), настройка доступа, сценарии использования, типичные ошибки, диагностика.

**Сквозной ДП (дополнительный параметр)** — это специальный тип ExtParam, который автоматически «протягивает» значение из связанной задачи через цепочку лукапов (LookUpField). Вместо хранения собственного значения он отображает значение из задачи-источника.

## Бизнес-сценарий

Предположим, у вас есть связанные задачи:

- **Счёт на оплату** → связан с **Договором** → связан с **Контрагентом**
- У контрагента есть поле **ИНН**
- Вам нужно видеть ИНН контрагента прямо в карточке счёта

**Традиционный подход:** Создать ДП «ИНН контрагента» в счёте и вручную копировать значение.
**Проблема:** При изменении ИНН в контрагенте нужно обновлять все счета.

**Решение через сквозной ДП:** Создать сквозной ДП, который автоматически показывает актуальное значение ИНН из связанного контрагента.

## Пример цепочки

Цепочка сквозного ДП на примере счёта, договора и контрагента:

```
Задача "Счёт"          Задача "Договор"       Задача "Контрагент"
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│ [Договор]       │───►│ [Контрагент]    │───►│ [ИНН]           │
│ (LookUpField)   │    │ (LookUpField)   │    │ (Text)          │
│ ID=200          │    │ ID=300          │    │ ID=100          │
│ OrderID=1       │    │ OrderID=2       │    │ OrderID=3       │
│                 │    │                 │    │ = "7701234567"  │
│ [ИНН контр.]    │    └─────────────────┘    └─────────────────┘
│ (Through)       │                                      ▲
│ ID=500 ═════════╪══════════════════════════════════════╝
└─────────────────┘        значение автоматически копируется
```

В интерфейсе цепочка задаётся в настройках сквозного ДП, в блоке «Настройка пути по ДП»: последовательность ДП, по которым определяется значение (порядок важен; последний ДП — источник значения, остальные — ссылки на задачу).

![Настройки сквозного ДП: тип «Through» и блок «Настройка пути по ДП» с цепочкой](https://help.1forma.ru/help-images/ext-params/through_dp_settings.png)

## Структура данных

### Таблица `dbo.ExtParamTroughSettings`

Хранит настройки цепочки для каждого сквозного ДП.

| Поле | Описание |
|------|----------|
| `ExtParamID` | ID сквозного ДП (тип 'Through') |
| `TargetExtParamID` | ID ДП на очередном шаге цепочки |
| `OrderID` | Номер шага (1 = первый лукап от целевой задачи, max = источник данных) |

**Пример настройки** для сквозного ДП «ИНН контрагента» (ID=500):

| ExtParamID | TargetExtParamID | OrderID | Описание |
|------------|------------------|---------|----------|
| 500 | 200 (Договор) | 1 | Шаг 1: первый лукап от счёта |
| 500 | 300 (Контрагент) | 2 | Шаг 2: второй лукап |
| 500 | 100 (ИНН) | 3 | Шаг 3: источник данных |

**Таблица `dbo.ExtParams`:**

Справочник всех дополнительных параметров.

**Типы ExtParam:**

- `Through` — сквозной ДП (протягивает значение по цепочке)
- `LookUpField` — обычный лукап (один к одному)
- `MultiSlctSubcatTasks` — мультилукап (один ко многим)
- `Text`, `Checkbox`, `NumericValue`, `Money`, `Date`, `Datetime` — простые поля

**Таблицы значений:**

Значения ДП хранятся в нескольких таблицах:

- **ExtParamValues** — основные значения ДП (текст, числа, даты, bool)
- **ExtParamValueSelectedTasks** — выбранные задачи (для лукапов и мультилукапов)
- **ExtParamSelectUsersValues** — выбранные пользователи
- **ExtParamSelectUsersValuesGroups** — выбранные группы
- **ExtParamSelectUsersValuesOrgUnits** — выбранные оргединицы
- **FileStorageFileToExtParamLinks** — прикреплённые файлы к ДП

## Правила валидности цепочки

### Обязательные требования

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

Цепочка считается валидной при выполнении требований:

1. **Минимум 2 записи** в `ExtParamTroughSettings` для каждого сквозного ДП
2. **Максимум 3 записи** (поддерживаются только 2-х и 3-х уровневые цепочки)
3. `OrderID` должны быть **непрерывны** от 1 до count (без пропусков)
4. Все шаги **кроме последнего** (`OrderID < max`) должны быть типа `LookUpField`
5. Последний шаг (`OrderID = max`) — **источник данных** (любой тип, включая LookUpField)
6. Источник **не может быть** типа `Through` (сквозной от сквозного)
7. **Лукапы в цепочке НЕ должны быть мультилукапами** (только `LookUpField`, не `MultiSlctSubcatTasks`)
8. **Запрещено:** несколько сквозных в одной категории с одним `SourceExtParamID`, но разной длиной цепочки

**Примеры валидных конфигураций:**

```
Счёт [Договор] → Договор [Сумма]
OrderID: 1=Договор, 2=Сумма
```

```
Счёт [Договор] → Договор [Контрагент] → Контрагент [ИНН]
OrderID: 1=Договор, 2=Контрагент, 3=ИНН
```

**Примеры НЕвалидных конфигураций:**

❌ **Мультилукап в цепочке:**
```
Счёт [Договоры (multi)] → Договор [Контрагент]
Проблема: недетерминированность — какой из договоров выбрать?
```

❌ **Разные длины к одному источнику:**
```
В категории "Счета":
- Сквозной ДП #1: Договор → ИНН (2 уровня)
- Сквозной ДП #2: Договор → Контрагент → ИНН (3 уровня)
Проблема: конфликт путей к одному источнику
```

❌ **Пропуск в OrderID:**
```
OrderID: 1, 3 (нет 2)
Проблема: невозможно построить полную цепочку
```

## Отображение значения в интерфейсе

Собственного значения сквозной ДП не хранит: он показывает значение ДП-источника, поэтому тип значения совпадает с типом источника. В типизированных колонках `ExtParamValues` заполнена только «своя» колонка: у сквозного над ДП «Деньги» заполнен `MoneyValue`, а `DecimalValue` у такой строки пуст; у сквозного над ДП «Число» — наоборот. Значения нечисловых типов лежат в отдельных таблицах: лукап — в `ExtParamValueSelectedTasks`, файл — в `FileStorageFileToExtParamLinks`.

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

При разборе расхождения «в карточке задачи значение есть, а в колонке пусто» сверяйтесь с колонкой, соответствующей типу ДП-источника, а не с соседней типизированной колонкой строки.

### Сквозной ДП в итогах, группировке и фильтре табличного представления

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

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

Фильтр колонки сквозного ДП устроен так же: тип условия сервер берёт у параметра-источника в конце цепочки, а в хранимую процедуру передаёт тип `Through`, и целевой тип определяет уже процедура. Ссылка на удалённый или недоступный параметр фильтр не ломает — условие по этой колонке не применяется, остальные условия работают как обычно. Сквозной параметр, у которого источник не определяется, при доступной колонке прерывает фильтр сообщением о неверной настройке сквозного параметра. Что проверить администратору: цепочку в настройках сквозного параметра (минимум два шага, непрерывные `OrderID`, промежуточные параметры — только «Ссылка», источник — не сквозной) и флаг «Синхронизировать для всех задач», если цепочку правили после того, как параметр добавили в категорию.

## Основные процедуры

### 1. `dbo.PullThroughExtParam`

**Назначение:** Синхронизация значений сквозных ДП — копирование данных из задачи-источника в целевую задачу. В интерфейсе полный перерасчёт запускает кнопка «Синхронизировать значение для всех задач» в карточке сквозного ДП.

**Параметры:**

| Параметр | Описание |
|----------|----------|
| `@StartTaskID` | ID задачи-источника (откуда брать данные) |
| `@StartExtParamID` | ID ДП-источника |
| `@TargetTaskID` | ID целевой задачи (куда копировать) |
| `@TargetExtParamID` | ID сквозного ДП |
| `@DenormalizeF` | Вызывать ли денормализацию после синхронизации |
| `@ForceDeleteTargetF` | Режим пересчёта цели: `0` — merge (целевое значение очищается, если источник пропал), `1` — принудительное удаление целевого значения. При пересчёте во время удаления задачи используется `0` |
| `@ExecInTranF` | Выполнять в собственной транзакции: при вызове из процедуры удаления задачи — `0`, потому что процедура уже внутри её транзакции |
| `@DebugF` | Режим отладки |

**Режимы работы:**

| Входные параметры | Поведение |
|-------------------|-----------|
| `@StartTaskID` + `@StartExtParamID` указаны | От конкретной задачи-источника вниз по цепочке |
| `@TargetTaskID` + `@TargetExtParamID` указаны | Только конкретная целевая пара |
| Все NULL | Полная пересинхронизация всех сквозных ДП |

**Алгоритм работы:**

1. **Построить список связей** `(DstTaskID, DstExtParamID) → (SrcTaskID, SrcExtParamID)` с помощью функции `TaskThroughPath`
2. **Дедуплицировать** (если несколько путей ведут к одной цели, выбрать один детерминированно)
3. **Для каждой таблицы выполнить MERGE:**
   - `ExtParamSelectUsersValues` — выбранные пользователи
   - `ExtParamSelectUsersValuesGroups` — выбранные группы
   - `ExtParamSelectUsersValuesOrgUnits` — выбранные оргединицы
   - `ExtParamValueSelectedTasks` — выбранные задачи (лукапы/мультилукапы)
   - `FileStorageFileToExtParamLinks` — прикреплённые файлы
   - `ExtParamValues` — основные значения (текст, числа, даты)
4. **Вызвать денормализацию** изменённых задач (если `@DenormalizeF = 1`)

**Пример вызова:**

```sql
-- Пересинхронизировать все сквозные ДП для конкретной задачи
EXEC dbo.PullThroughExtParam 
    @TargetTaskID = 12345,
    @DenormalizeF = 1;

-- Обновить все зависимые задачи при изменении источника
EXEC dbo.PullThroughExtParam 
    @StartTaskID = 67890,
    @StartExtParamID = 100,
    @DenormalizeF = 1;
```

---

### 2. `dbo.ExtParamTroughSettingsValidate`

**Назначение:** Проверка корректности настроек сквозных ДП.

**Параметры:**

| Параметр | Описание |
|----------|----------|
| `@ExtParamID` | ID конкретного сквозного ДП (NULL = проверить все) |
| `@FixOrderIDGapsF` | Автоматически исправить пропуски в OrderID |

**Проверяемые ошибки:**

| Код | Уровень | Описание |
|-----|----------|----------|
| `NO_SETTINGS` | ERROR | Нет настроек цепочки |
| `CHAIN_TOO_SHORT` | ERROR | Меньше 2 элементов |
| `CHAIN_TOO_LONG` | ERROR | Больше 3 элементов |
| `ORDERID_NOT_FROM_1` | ERROR | Цепочка не начинается с 1 |
| `ORDERID_GAPS` | ERROR | Пропуски в OrderID |
| `DUPLICATE_ORDERID` | ERROR | Дублирование OrderID |
| `TARGET_EP_NOT_FOUND` | ERROR | TargetExtParamID не существует |
| `INTERMEDIATE_NOT_LOOKUP` | ERROR | Промежуточный элемент не LookUpField |
| `MULTILOOKUP_IN_CHAIN` | ERROR | Мультилукап в цепочке |
| `SUBCAT_MISMATCH` | ERROR | Несогласованность категорий |
| `SELF_REFERENCE` | ERROR | Ссылка на себя |
| `MIXED_CHAIN_LENGTHS` | ERROR | Разные длины к одному источнику |
| `TARGET_IS_THROUGH` | WARNING | Источник тоже сквозной |

**Примеры:**

```sql
-- Проверить все сквозные ДП
EXEC dbo.ExtParamTroughSettingsValidate;

-- Проверить конкретный ДП
EXEC dbo.ExtParamTroughSettingsValidate @ExtParamID = 500;

-- Автоматически исправить пропуски в OrderID
EXEC dbo.ExtParamTroughSettingsValidate @FixOrderIDGapsF = 1;
```

---

### 3. `dbo.ExtParamTroughSettingsSet`

**Назначение:** Создание/изменение/удаление настроек сквозного ДП.

**Параметры:**

| Параметр | Описание |
|----------|----------|
| `@TargetExtParamID` | ID сквозного ДП (обязательный) |
| `@PathExtParamIDs` | Таблица с цепочкой ExtParamID (тип `dbo.id_tbl_type`) |
| `@ValidateOnlyF` | Только проверить без сохранения |
| `@DebugF` | Режим отладки |

**Примеры:**

```sql
-- Создать 2-уровневую цепочку: Договор → ИНН
DECLARE @path dbo.id_tbl_type;
INSERT INTO @path (ID) VALUES (200), (100);
EXEC dbo.ExtParamTroughSettingsSet 
    @TargetExtParamID = 500, 
    @PathExtParamIDs = @path;

-- Создать 3-уровневую цепочку: Договор → Контрагент → ИНН
DECLARE @path dbo.id_tbl_type;
INSERT INTO @path (ID) VALUES (200), (300), (100);
EXEC dbo.ExtParamTroughSettingsSet 
    @TargetExtParamID = 500, 
    @PathExtParamIDs = @path;

-- Удалить настройки (передать пустой путь)
DECLARE @path dbo.id_tbl_type;
EXEC dbo.ExtParamTroughSettingsSet 
    @TargetExtParamID = 500, 
    @PathExtParamIDs = @path;

-- Только проверить без сохранения
EXEC dbo.ExtParamTroughSettingsSet 
    @TargetExtParamID = 500, 
    @PathExtParamIDs = @path, 
    @ValidateOnlyF = 1;
```

---

### 4. `dbo.TaskThroughPath` (функция)

**Назначение:** Разворачивание цепочки связей для построения путей от задачи-источника к целевым задачам.

**Возвращает:** Таблицу с парами `(DstTaskID, DstExtParamID) → (SrcTaskID, SrcExtParamID)`

**Алгоритм:**

1. Найти все сквозные ДП (ExtParamType = 'Through')
2. Для каждого проверить валидность цепочки в ExtParamTroughSettings
3. Рекурсивно пройти по цепочке:
   - **Начало:** задача с целевым сквозным ДП + первый лукап (OrderID=1)
   - **Рекурсия:**
     - Взять `TargetExtParamID` для текущего `OrderID`
     - Найти в `ExtParamValueSelectedTasks` задачу, на которую ссылается этот лукап
     - Перейти к `OrderID + 1`
   - **Конец:** `OrderID = max`, найден источник данных

**Используется:** Внутри `PullThroughExtParam` для построения списка связей.

## Запрет удаления лукапа, входящего в путь сквозного ДП

Пока ДП типа «Ссылка» (Lookup) обслуживает путь хотя бы одного сквозного ДП, удалить его нельзя — ни снять с категории, ни удалить глобально. Отказ приходит с текстом, в котором перечислены сквозные ДП и категории, где лукап стоит в пути; данные при отказе не меняются.

**Где действует запрет:**

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

Тот же отказ наследуют каскадные сценарии: удаление категории, в которой лежит такой ДП, и синхронизация с 1С. Отказ при удалении категории называет саму удаляемую категорию.

**Что делать администратору:** сначала убрать лукап из пути сквозного в настройках цепочки, затем снимать ДП с категорий и удалять глобально. Если лукап ни в одном пути не участвует, он удаляется как обычно.

> Удаление задачи, выбранной в лукапе, — другой сценарий: там удаляется не ДП, а задача-значение, и сквозное значение пересчитывается.

## Настройка доступа

Доступ пользователя к сквозному ДП определяется как доступ к обычному полю: чтение, чтение+запись или нет доступа.

Расчёт учитывает три уровня доступа пользователя к **целевому** параметру в задаче-источнике: нет чтения целевого параметра; есть чтение, нет записи; есть и чтение, и запись.

### Параметр «Учитывать настройки доступа по целевому ДП»

![Карточка сквозного ДП: настройка пути, кнопка «Синхронизировать значение для всех задач» и флаг «Учитывать настройки доступа по целевому ДП»](https://help.1forma.ru/help-images/ext-params/through_access_setting.png)

Когда **включен** — доступ рассчитывается как логическое И настроек сквозного и целевого ДП (обе должны разрешать):

| Доступ к сквозному | Доступ к целевому | Результат |
|---|---|---|
| нет доступа | чтение+запись | не видит |
| чтение+запись | чтение+запись | чтение+запись |
| чтение+запись | нет доступа | не видит |

Когда **выключен** — чтение определяется только по сквозному ДП, но при отсутствии прав на целевое поле оно становится доступным только для чтения:

| Доступ к сквозному | Доступ к целевому | Результат |
|---|---|---|
| нет доступа | чтение+запись | не видит |
| чтение+запись | чтение+запись | чтение+запись |
| чтение+запись | нет доступа | только чтение |

> Подробнее: [Настройка ДП](https://help.1forma.ru/domains/ext-params/admin.md)

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

**Ожидаемое поведение:**

| Доступ к сквозному ДП | Результат в интерфейсе |
|---|---|
| Только чтение | Кнопки «Добавить строку», «Удалить», «Редактировать» скрыты |
| Чтение + запись | Кнопки отображаются |

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

Права на целевой параметр влияют не только на то, что видно на форме, но и на состав ответа чтения.

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

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

## Сценарии использования

### Создание новой цепочки

Пример создания цепочки целиком:

```sql
-- 1. Убедитесь что все ДП созданы:
--    - Счёт имеет ДП "Договор" (ID=200, тип LookUpField)
--    - Договор имеет ДП "Контрагент" (ID=300, тип LookUpField)
--    - Контрагент имеет ДП "ИНН" (ID=100, тип Text)
--    - Счёт имеет ДП "ИНН контрагента" (ID=500, тип Through)

-- 2. Создайте настройки цепочки
DECLARE @path dbo.id_tbl_type;
INSERT INTO @path (ID) VALUES 
    (200),  -- OrderID=1: Договор
    (300),  -- OrderID=2: Контрагент
    (100);  -- OrderID=3: ИНН (источник)

EXEC dbo.ExtParamTroughSettingsSet 
    @TargetExtParamID = 500, 
    @PathExtParamIDs = @path;

-- 3. Проверьте настройки
EXEC dbo.ExtParamTroughSettingsValidate @ExtParamID = 500;

-- 4. Выполните первоначальную синхронизацию
EXEC dbo.PullThroughExtParam @DenormalizeF = 1;
```

### Обработка изменений

**Когда вызывать `PullThroughExtParam`:**

1. **При изменении лукапа в цепочке:**
   ```sql
   -- Пользователь изменил договор в счёте
   EXEC dbo.PullThroughExtParam 
       @TargetTaskID = @TaskID_Счёт,
       @TargetExtParamID = 500,  -- ИНН контрагента
       @DenormalizeF = 1;
   ```

2. **При изменении значения в источнике:**
   ```sql
   -- Изменили ИНН в контрагенте → обновить все зависимые счета
   EXEC dbo.PullThroughExtParam 
       @StartTaskID = @TaskID_Контрагент,
       @StartExtParamID = 100,  -- ИНН
       @DenormalizeF = 1;
   ```

3. **При создании новой задачи с лукапами:**
   ```sql
   -- Создали новый счёт и выбрали договор
   EXEC dbo.PullThroughExtParam 
       @TargetTaskID = @NewTaskID,
       @DenormalizeF = 1;
   ```

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

Если удаляемая задача выбрана в Lookup или MultiLookup, участвующем в цепочке сквозного ДП, пересчёт затронутых сквозных значений идёт внутри удаления задачи — в `tc_DeleteTasks`, до финального удаления строк. Граф сквозных обходится по связям `ExtParamValueSelectedTasks`, а они очищаются при удалении, поэтому пересчёт для удаляемых задач, выбранных в цепочке, выполняется **до** очистки лукапов, а для оставшихся целей — после неё.

Пересчёт идёт в режиме merge (`@ForceDeleteTargetF = 0`): сквозное значение, для которого после удаления не осталось источника, снимается, а значения от других оставшихся элементов MultiLookup сохраняются. Отдельной транзакции у вызова нет (`@ExecInTranF = 0` — он уже внутри транзакции удаления), денормализацию выполняет `tc_TasksDenormalize`. Все затронутые пары «ссылающаяся задача × сквозной ДП» передаются в пересчёт одним набором (`@Pairs`), а не отдельным вызовом на пару: число обращений к процедуре не зависит от того, в скольких задачах выбрана удаляемая, поэтому ссылающиеся справочники — контрагент, проект, договор, сотрудник — удаление больше не растягивают. Порядок прежний: пары собираются до очистки лукапов у удаляемых задач и после неё у оставшихся целей.

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

**Диагностика.** Если после удаления связанной задачи значение сквозного ДП расходится с ожидаемым, проверяют: что удаляемая задача действительно была выбрана в Lookup или MultiLookup цепочки; какие сквозные ДП и задачи-цели затрагиваются через `ExtParamTroughSettings`; что после merge значения соответствуют оставшимся источникам (при единственном источнике значение должно отсутствовать); что `ExtParamValues` согласованы с денормализованным представлением целевой категории.

## Типичные ошибки и решения

Ошибки конфигурации цепочек сквозных ДП и рекомендации по устранению.

**Ошибка: MULTILOOKUP_IN_CHAIN** — в цепочке используется мультилукап вместо обычного.

**Пример проблемы:**
```
Счёт [Договоры (multi)] → Договор [Контрагент] → Контрагент [ИНН]
```

**Почему это плохо:**

- Если счёт связан с 3 договорами, непонятно какой из них выбрать
- `row_number()` дедупликация даст случайный результат
- Значение сквозного ДП будет «прыгать» при каждом запуске

**Решение:** заменить мультилукап на обычный лукап (один к одному) или изменить бизнес-логику.

---

### Ошибка: MIXED_CHAIN_LENGTHS

**Причина:** Несколько сквозных ДП в одной категории ведут к одному источнику, но через цепочки разной длины.

**Пример проблемы:**
```
В категории "Счета":
- Сквозной ДП "ИНН-1": Договор → ИНН (2 уровня)
- Сквозной ДП "ИНН-2": Договор → Контрагент → ИНН (3 уровня)
```

**Почему это плохо:**

- Путь к одному источнику должен быть единственным
- Разные пути могут привести к конфликтам при синхронизации

**Решение:**
Унифицировать длину цепочек — все сквозные ДП к одному источнику должны идти через одинаковый путь.

---

**Производительность:** вызов `PullThroughExtParam` на каждое изменение может быть дорогим. Рекомендации: отключить `@DenormalizeF` для единичных изменений; накапливать изменения и вызывать денормализацию пакетом; использовать фоновое задание для периодической синхронизации.

---

## Диагностика

**Проверка всех сквозных ДП:** найти все сквозные ДП с ошибками через `EXEC dbo.ExtParamTroughSettingsValidate` — результат покажет ExtParamID, ExtParamName, ErrorCode, ErrorMessage, Severity.

```sql
-- Найти все сквозные ДП с ошибками
EXEC dbo.ExtParamTroughSettingsValidate;

-- Результат покажет:
-- - ExtParamID
-- - ExtParamName
-- - ErrorCode
-- - ErrorMessage
-- - Severity (ERROR/WARNING)
```

### Проверка конкретной цепочки

Посмотреть настройки конкретного сквозного ДП:

```sql
-- Посмотреть настройки для конкретного сквозного ДП
SELECT 
    epts.ExtParamID,
    ep_through.ExtParamName AS ThroughParamName,
    epts.OrderID,
    epts.TargetExtParamID,
    ep_target.ExtParamName AS TargetParamName,
    ep_target.ExtParamType AS TargetParamType
FROM dbo.ExtParamTroughSettings epts
JOIN dbo.ExtParams ep_through ON epts.ExtParamID = ep_through.ExtParamID
JOIN dbo.ExtParams ep_target ON epts.TargetExtParamID = ep_target.ExtParamID
WHERE epts.ExtParamID = 500
ORDER BY epts.OrderID;
```

**Отладка синхронизации:** запустить с `@DebugF = 1` для просмотра промежуточных результатов — список построенных связей, количество обновлённых записей в каждой таблице, список задач для денормализации.

```sql
-- Запустить с @DebugF = 1 для просмотра промежуточных результатов
EXEC dbo.PullThroughExtParam 
    @TargetTaskID = 12345,
    @DebugF = 1;

-- Увидите:
-- - Список построенных связей (DstTaskID → SrcTaskID)
-- - Количество обновлённых записей в каждой таблице
-- - Список задач для денормализации
```

**Совместимость:** требования к окружению — MS SQL Server 2016+, используются CTE, MERGE, table variables, window functions.
