# Объектные метаданные .epf (ExternalDataProcessor.json) — табличные части, типы, имя/синоним

Дополняет `elem_json_format.md` (тот про ФОРМЫ). Здесь — про **объект**: реквизиты
табличных частей, их ТИПЫ, и имя/синоним обработки. Формат — те же вложенные
массивы строк v8unpack. **Правь только программно** (`tools/obj_edit.py`), не текстом.

Инструмент: `tools/obj_edit.py` (attrs / typeid / retype / rename). Кейс, на котором
выверено, — `УстановкаЮрЛицаИДоговораГостиницы` (агентские услуги, счёт 76.06).

## Узел реквизита ('27')

```
["27", ["2",
          ["3", ["1","0","<BINDING-UUID>"], "\"Имя\"", ["1","\"ru\"","\"Синоним\""], "\"\"","0","0","00000000-…","0"],
          ["\"Pattern\"", <ТИП>]            # ← ТИП реквизита
       ],
       "0",["0"],["0"],"0","\"\"","0",["\"U\""],["\"U\""],"0","00000000-…","2","0",["5006","0"],
       ["3","0","0"],["0","0"],"0",["0"],["\"S\"","\"\""],"0","0","0"]   # ← «хвост»-флаги
```

**Кодировка ТИПА (`<ТИП>` в Pattern):**
| тип | Pattern |
|---|---|
| ссылка (Catalog/Document/ChartOfAccounts/Enum…) | `["\"#\"", "<TypeId>"]` |
| Строка | `["\"S\""]` |
| Число(15,2) | `["\"N\"", "15", "2", "0"]` |
| Булево | `["\"B\""]` |
| Дата | `["\"D\"", …]` |

`<TypeId>` — это **TypeId категории Ref** объекта метаданных (НЕ uuid самого объекта).
Берётся из XML-выгрузки: `GeneratedType … category="Ref"` → `<TypeId>`. Тул:
`obj_edit.py typeid НоваяКонфа Catalog.Организации`. Кросс-проверка кейса:
Контрагенты `9f6206b2…`, Организации `c92a1124…`, Хозрасчетный `6f58a5c7…`,
РеализацияТоваровУслуг `b66fce8c…`, СчетНаОплатуПокупателю2 `d002160c…`.

**Хвост-флаги ОДИНАКОВЫ у всех ссылочных типов** → ретайп ref→ref = подмена ОДНОГО
TypeId в Pattern. Для смены примитив↔ссылка `obj_edit.py retype` клонирует узел
нужного рода (образец ищет в этом же файле, иначе среди соседних обработок в
`ОбработкиЭкспорт/` — напр. Булево нашлось в `ОбработкаКомиссии_src`).

## Связь колонки формы ↔ реквизита ТЧ

Колонка формы (`'37'`, см. form_inspect `cols`) ссылается на реквизит по его
**BINDING-UUID** (= uuid в `'27'`-узле; `["0","<uuid>"]` в слоте data-path, либо
`["-2"]` для системного `НомерСтроки`). Поэтому:

> **Ретайп реквизита ПО МЕСТУ (сохраняя binding-uuid) → колонка формы автоматически
> показывает новый тип.** Заголовок колонки (если у `'37'` нет своего) наследуется
> из синонима реквизита. Это главный приём: НЕ добавлять колонки, а перетипизировать
> существующие.

Сколько в форме колонок-`'37'` — столько типизированных колонок и доступно. В кейсе
шаблон «КомиссияКлиентов» дал 5 атрибутных колонок + системную `НомерСтроки`;
ровно под Метку + 4 запрошенные колонки.

## Имя и синоним обработки — в `header`, поля name/name2 ПРОИЗВОДНЫЕ

⚠ **Грабли (стоили времени):** верхнеуровневые `name` и `name2` в JSON — это
производные поля, которые распаковщик ВЫВОДИТ, а пакер ИГНОРИТ. Меняешь их —
изменение НЕ переживает pack→unpack (синоним вернётся старый). Authoritative —
бинарный `header`, где имя+синоним лежат как
`"\"Имя\"", ["1","\"ru\"","\"Синоним\""]`. Имя встречается в файле НЕСКОЛЬКО раз
(поле + header + копия в структуре). `obj_edit.py rename` меняет ВЕЗДЕ (+header) и
выставляет name/name2 — и обязательно ПРОВЕРЬ `epf.sh roundtrip` + что имя/синоним
читаются из пересобранного .epf.

Тот же класс граблей у формы: распаковщик пере-выводит поле `name` реквизита формы
из raw (в кейсе id=2: «Новое Юр.Лицо»→«Контрагент»). Это **известный безобидный
шум** в round-trip diff (raw идентичен) — не пугаться.

## Команды и кнопки формы (`tools/form_cmd.py`)

Команды — секция `elem['commands']` (`{name,id,raw}`); `raw[3]` = синоним (текст
кнопки), `raw[7]` = **картинка команды** (`['4',<флаг>,[<idx>,<uuid-картинки>],…]`;
флаг `'0'`+`['0']` без uuid = без картинки → кнопка рендерится ТЕКСТОМ заголовка;
флаг `'1'`+реальный uuid = с картинкой → кнопка рендерится ТОЛЬКО иконкой, без
подписи, даже если заголовок задан), `raw[8]` = имя процедуры-обработчика, uuid
`409b9a53-…` = маркер форменной команды. Кнопка (`'31'` в raw таблицы): `[7]` title
(`["1","0"]`=брать из синонима команды), `[9]` привязка `[cmd-id, cmd-uuid]`.
Встроенные кнопки (сортировка, uuid≠409b9a53) можно **перевязать** на новую
форменную команду (`form_cmd.py rebind`) — дешевле, чем вставлять кнопку
структурно. Новая команда — `form_cmd.py add-cmd` (не забудь одноимённую
`&НаКлиенте Процедура Имя(Команда)` в модуле); инструмент **сбрасывает картинку**
клонированного шаблона по умолчанию (текстовая кнопка) — если задача явно просит
кнопку с иконкой, `raw[7]` придётся выставить вручную с нужным uuid картинки.

## Добавление колонки в ТЧ — РЕШЕНО (`obj_edit.py add-attr`)

Реквизит ТЧ размазан по **3 местам**, но они отмаплены, и `add-attr` патчит все три:
1. **header**: контейнер реквизитов = список с головой `5d24a9d1-098e-11d6-b9b8-0050bae0a95d`,
   формат `['5d24a9d1…', '<count>', attr0, attr1, …]` (реквизиты — соседние элементы,
   count в `[1]`). Клон реквизита-донора в конец + count+1.
2. **copyinfo `'33'`**: `[uuid, uuid, '2', ['2bcef0d1…','"ИмяТЧ"'], ['5d24a9d1…','"ИмяРеквизита"']]`.
3. **copyinfo `'22'`**: `[['0', uuid], '<категория-ТЧ-uuid>', ['-1']]`; категория ТЧ —
   та, что в `'12'`-блоке мапит `категория→binding-uuid ТЧ`.

```bash
obj_edit.py add-attr <obj.json> --donor-uuid <uuid существующего реквизита той же ТЧ> \
   --name СуммаНДС --syn "Сумма НДС" [--new-uuid <свой>]   # тип = как у донора; иначе потом retype
```

Затем **колонка формы** (клон поля в `Form.elem.json`, см. `elem_json_format.md` §«Колонка ТЧ в форму»):
поле колонки = `data["Таблица/ТаблицаИмяРекв"]` с raw `'37'`; привязка к реквизиту —
`["3", ["<id Объекта>"], ["0","<ТЧ-binding-uuid>"], ["0","<реквизит-uuid>"]]`. Клонируй
поле-донор: ремап его element-id (тройка `[n,SVC]`-пар, SVC=`02023637-…`) на свободные
(колонки идут с шагом 3), смени имя/заголовок и привязку на новый uuid, добавь data-ключ
и узел `Field` в `tree` (порядок колонок = порядок в tree). Raw таблицы `'55'` колонки
НЕ перечисляет — трогать его не надо. И заполни колонку в модуле (с маркерами).
Выверено: кейс СуммаНДС в `аСверкаРеестровыхКомпаний` (add-attr воспроизвёл ручную правку 1:1).

Платформенный путь (Конфигуратор + onec-build-agent §6) остаётся альтернативой, если
ручной структурный клон не принимается 1С — проверять загрузкой в 1С обязательно.

## Чего всё же НЕ делать вслепую

Ретайп/удаление реквизитов, на которые ссылается код модуля или другие формы, без
`grep` по имени. И не доверять текстовому `diff` round-trip после Python-правки JSON —
сравнивай СЕМАНТИЧЕСКИ (`epf.sh roundtrip` уже так делает: v8unpack нормализует формат,
текстовый diff даёт ложную тревогу, содержание при этом совпадает).

## Рецепт «переделать обработку под новую задачу» (выверено)

1. `epf.sh unpack` ближайшей обработки-шаблона (тот же домен/форма).
2. `obj_edit.py attrs` + `form_inspect cols` → увидеть binding-uuid колонок.
3. `obj_edit.py typeid` → TypeId нужных типов из `НоваяКонфа`.
4. `obj_edit.py retype` каждую колонку под нужный тип/имя/синоним (binding-uuid не трогаем).
5. `form_cmd.py set-title/add-cmd/rebind` — кнопки под свои процедуры.
6. Переписать `Form/Форма/Form.obj.bsl` (маркеры `//+ Бурдуков`); проверить, что
   подчинённые формы и объектный модуль не ссылаются на переименованные реквизиты
   и на старое имя обработки (`grep`).
7. `obj_edit.py rename` (имя=синоним, если просят совпадение).
8. `epf.sh roundtrip` → diff должен быть пустым ИЛИ только известный шум (имя реквизита формы).
9. Где возможно — финально собрать/провалидировать платформой (SKILL.md §6).

## ГРАБЛИ add-attr / новой колонки (кейс аЗаполнениеТуродниСписком, 03.07.2026)

1. **UUID — строго hex.** Свой `--new-uuid` (реквизита ИЛИ поля формы) обязан быть
   валидным GUID `[0-9a-f]`. UUID с буквами вне hex (напр. `…0a0procent01`) v8unpack
   спакует, round-trip будет ЧИСТЫМ, но платформа отвергнет файл с «**Ошибка формата
   потока**» (ошибка контейнера-стрима, ещё до разбора метаданных). `uuid.uuid4()` уже
   hex — ломается только при ручном осмысленном uuid. Симптом коварен: round-trip и
   BSL-синтаксис зелёные, ловится только загрузкой/дампом платформой. Бисекция:
   `DumpExternalDataProcessorOrReportToFiles <epf>` оригинала (грузится) vs правки.
2. **copyinfo бывает не `33`/`22`, а `84`/`69` под обёрткой `4`.** `obj_edit.py add-attr`
   ищет верхнеуровневые блоки `33`/`22`; в некоторых файлах их нет — вместо них
   `copyinfo=[['4', ['84',…], ['20',…], ['69',…], …]]`, где тег блока = счётчик записей
   (`'84'`→84 записи), запись `84`-стиля = `[uuid,uuid,"2",[mark,'"ТЧ"'],[mark,'"Реквизит"']]`,
   `69`-стиля = `[["0",uuid], catUuid, ["-1"]]`. При добавлении — append записи + инкремент
   тега-счётчика. Тогда add-attr падает `TypeError/'33' None` — добавлять кастомным скриптом.
3. **Реквизиты буферных ТЧ могут отсутствовать в copyinfo.** Если у объекта есть
   ТЧ-двойники (`X`, `XДоп`, `XИсходные` — гоняются `Выгрузить/Загрузить`), в copyinfo
   часто лежат только реквизиты ОСНОВНОЙ ТЧ; у буферов реквизиты лишь в `header`. Правило
   скрипта: **header-контейнер патчим всегда, copyinfo — только если донор там реально есть.**
   Новую колонку заводить во ВСЕХ ТЧ-двойниках, иначе значение теряется на восстановлении из буфера.
4. **Финальный артефакт — платформенный, для НОВЫХ колонок это НЕ рекомендация, а
   ОБЯЗАТЕЛЬНЫЙ шаг (подтверждено, DEVACC-755, 03.07.2026).** Новая колонка, добавленная
   чистым v8unpack (`add-attr` + клон поля `'37'`), может быть **побайтово идентична**
   рабочей колонке-донору (полный рекурсивный diff показывает разницу только в
   id/имени/uuid-привязки), round-trip — чистым, объект корректно декларирует тип
   (`xs:decimal`+`NumberQualifiers` виден в дампе) — а колонка **всё равно не
   отрисуется** в 1С (пусто между соседними колонками, будто её нет).

   **Точный механизм (найден эмпирически):** `DumpExternalDataProcessorOrReportToFiles`
   твоего v8unpack-.epf НЕ ВСЕГДА восстанавливает `<DataPath>Объект.<ТЧ>.<Реквизит>
   </DataPath>` у поля формы, даже если raw-привязка поля (`'37'`-блок, индекс `[11]`:
   `["3",["<id Объекта>"],["0","<ТЧ-uuid>"],["0","<реквизит-uuid>"]]`) байт-в-байт
   идентична рабочему полю. **Простой `Dump→Load` без правки XML НЕ чинит колонку** —
   на выходе тот же дефект (проверено: первый прогон без проверки XML ничего не
   исправил, юзер подтвердил «колонки всё равно не появились»). Лечение — **проверить
   вручную**: после Dump открой `Forms/<Форма>/Ext/Form.xml`, найди `<InputField
   name="...">` нового поля — если внутри НЕТ `<DataPath>` (сравни с соседним рабочим
   полем, у него `<DataPath>` есть первым дочерним элементом) — **дописать вручную**
   `<DataPath>Объект.<ТЧ>.<ИмяРеквизита></DataPath>`, затем
   `LoadExternalDataProcessorOrReportFromFiles` починенный XML. Признак успеха —
   повторный Dump результата **снова** показывает этот `<DataPath>` (значит платформа
   его реально сохранила в бинарнике, а не просто проглотила твою правку без обработки).

   Полный цикл: `Dump` v8unpack-.epf → XML → **проверить/дописать `<DataPath>` у
   каждого нового поля** → `Load` XML **в базу С КОНФИГУРАЦИЕЙ** (не в пустую — иначе
   ссылки `CatalogRef.*` не разрешатся) → **Dump результата ещё раз** для контроля →
   отдать финальный .epf. Альтернатива первому Dump/Load — `POST /build` через
   `onec-build-agent` (§6), тот же принцип, если batch DESIGNER недоступен напрямую.
