framework_eng/skills/bsl-practices/data-exchange/SKILL.md
Use for implementing and diagnosing 1C data exchange (РИБ, КД 2.0/3.0, EnterpriseData, БСП). Helps choose the exchange model, ensure packet idempotency, and explicit conflict resolution.
npx skillsauth add steelmorgan/1c-agent-based-dev-framework data-exchangeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Key principle: Data exchange is a distributed system. Each packet must be idempotent (reloading it does not corrupt data), each conflict must be explicitly resolved, and each error must be logged with a node and message number attached.
Before implementation, you must determine the exchange model. The choice affects all subsequent code.
| Model | When to use | Mechanism |
|--------|-------------------|----------|
| РИБ (distributed information base) | Full copy of the configuration at the nodes; all data is transferred | ПланыОбмена, XML serialization, ОбменДаннымиXML |
| Selective exchange (БСП) | Rule-based exchange, object filtering, different configurations | БСП "Data exchange" subsystem, EnterpriseData format |
| КД 2.0 | Complex conversion rules between different configurations | "Data Conversion" processing, XML rules |
| КД 3.0 / EDT | Modern projects, rules in BSL, EnterpriseData support | Configuration "Data Conversion 3" |
// Главный узел — это текущая ИБ
// Подчинённые узлы — внешние базы данных
УзелГлавный = ПланыОбмена.МойОбмен.ГлавныйУзел();
Если УзелГлавный = Неопределено Тогда
// Это главный узел (центральная база)
КонецЕсли;
// Создание нового узла обмена
НовыйУзел = ПланыОбмена.МойОбмен.СоздатьУзел();
НовыйУзел.Код = "ФИЛИАЛ_001";
НовыйУзел.Наименование = "Филиал Москва";
НовыйУзел.Записать();
ЗарегистрироватьИзмененияChange registration is the main mechanism for tracking what needs to be sent to a node. Incorrect registration is the main source of errors: either data does not leave, or extra data does.
// Зарегистрировать конкретный объект для конкретного узла
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, ОбъектСсылка);
// Зарегистрировать набор объектов (например, при первичной синхронизации)
МассивОбъектов = Новый Массив;
МассивОбъектов.Добавить(НоменклатураСсылка1);
МассивОбъектов.Добавить(НоменклатураСсылка2);
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, МассивОбъектов);
// В обработчике подписки ПриЗаписи — регистрировать изменения явно
Процедура НоменклатураПриЗаписи(Источник, Отказ, РежимЗаписи, РежимПроведения)
Если Источник.ОбменДанными.Загрузка Тогда
Возврат; // Не регистрировать изменения при загрузке из обмена
КонецЕсли;
Для Каждого УзелОбмена Из ПланыОбмена.МойОбмен.Выбрать() Цикл
Если УзелОбмена.Ссылка <> ПланыОбмена.МойОбмен.ЭтотУзел() Тогда
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена.Ссылка, Источник.Ссылка);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Загрузка flag// ОБЯЗАТЕЛЬНО в начале каждого обработчика объекта
Если Источник.ОбменДанными.Загрузка Тогда
Возврат; // Пропустить все проверки и дополнительную логику при загрузке
КонецЕсли;
Without this check: when data is loaded from an external base, the current base's business rules will run -> the data will be corrupted or the load will end with an error.
Функция СформироватьСообщениеОбмена(УзелОбмена) Экспорт
СообщениеОбмена = ПланыОбмена.СоздатьСообщениеОбмена();
СообщениеОбмена.Отправитель = ПланыОбмена.МойОбмен.ЭтотУзел();
СообщениеОбмена.Получатель = УзелОбмена;
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
Попытка
ОбменДаннымиXML.НачатьЗаписьСообщения(ЗаписьXML, СообщениеОбмена);
// Выгрузка изменений
ВыборкаИзменений = ПланыОбмена.Выбрать(УзелОбмена, ТипПланаОбмена.ТолькоИзменения);
Пока ВыборкаИзменений.Следующий() Цикл
ОбменДаннымиXML.ЗаписатьИзменения(ЗаписьXML, ВыборкаИзменений.ПолучитьОбъект());
КонецЦикла;
ОбменДаннымиXML.ЗакончитьЗаписьСообщения(ЗаписьXML);
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Выгрузка'"),
УровеньЖурналаРегистрации.Ошибка,
Метаданные.ПланыОбмена.МойОбмен,
УзелОбмена,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
Возврат ЗаписьXML.Закрыть();
КонецФункции
// ВАЖНО: номер сообщения фиксируется только после успешной доставки
// Не вызывать УстановитьНомерОтправленного до подтверждения получения
// На стороне отправителя — после подтверждения доставки:
ПланыОбмена.УстановитьНомерОтправленного(УзелОбмена, НомерСообщения);
// На стороне получателя — после успешной загрузки:
ПланыОбмена.УстановитьНомерПринятого(УзелОбмена, НомерСообщения);
Idempotency of a packet means: reloading the same message does not change the result. This is critical during network failures and manual reruns.
Процедура ЗагрузитьСообщениеОбмена(ТекстСообщения) Экспорт
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ТекстСообщения);
СообщениеОбмена = ПланыОбмена.СоздатьСообщениеОбмена();
НачатьТранзакцию();
Попытка
ОбменДаннымиXML.НачатьЧтениеСообщения(ЧтениеXML, СообщениеОбмена);
// Проверка: не загружать уже принятое сообщение (идемпотентность)
Если СообщениеОбмена.НомерСообщения <= ПланыОбмена.МойОбмен
.НайтиПоКоду(СообщениеОбмена.Отправитель.Код).НомерПринятого Тогда
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Загрузка'"),
УровеньЖурналаРегистрации.Предупреждение,,,
СтрШаблон(НСтр("ru = 'Сообщение №%1 от узла %2 уже было принято. Пропускаем.'"),
СообщениеОбмена.НомерСообщения, СообщениеОбмена.Отправитель));
ОтменитьТранзакцию();
Возврат;
КонецЕсли;
// Загрузка объектов
Пока ОбменДаннымиXML.ЧитатьИзменения(ЧтениеXML) Цикл
Данные = ОбменДаннымиXML.ПрочитатьИзменения(ЧтениеXML);
Данные.ОбменДанными.Загрузка = Истина;
Данные.Записать();
КонецЦикла;
ОбменДаннымиXML.ЗакончитьЧтениеСообщения(ЧтениеXML);
// Фиксируем номер принятого сообщения
ПланыОбмена.УстановитьНомерПринятого(
СообщениеОбмена.Отправитель, СообщениеОбмена.НомерСообщения);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Загрузка'"),
УровеньЖурналаРегистрации.Ошибка,
Метаданные.ПланыОбмена.МойОбмен,
СообщениеОбмена.Отправитель,
СтрШаблон(НСтр("ru = 'Ошибка загрузки сообщения №%1.
|%2'"), СообщениеОбмена.НомерСообщения,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())));
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
// При загрузке справочников — искать по внешнему идентификатору, не создавать дубли
Функция НайтиИлиСоздатьНоменклатуру(ВнешнийКод, Наименование)
// Поиск по внешнему коду (синхронизационному ключу)
СуществующийЭлемент = Справочники.Номенклатура.НайтиПоКоду(ВнешнийКод);
Если СуществующийЭлемент <> Справочники.Номенклатура.ПустаяСсылка() Тогда
Возврат СуществующийЭлемент;
КонецЕсли;
// Создать новый только если не нашли
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.Код = ВнешнийКод;
НовыйЭлемент.Наименование = Наименование;
НовыйЭлемент.ОбменДанными.Загрузка = Истина;
НовыйЭлемент.Записать();
Возврат НовыйЭлемент.Ссылка;
КонецФункции
A conflict occurs when the same object is changed in two nodes at the same time. The resolution strategy must be documented and implemented explicitly - a "silent" last-write-wins outcome is unacceptable in most business scenarios.
| Strategy | When to apply | Implementation |
|-----------|----------------|------------|
| Main node wins | The main base is the source of truth | Reject changes from subordinate nodes when a conflict occurs |
| Last change wins | Non-critical data (settings, descriptions) | МоментВремени() - the newer one wins |
| Business-rule wins | State machine, priorities | Explicit resolution function |
| Manual resolution | Critical data, cannot be automated | Record in the conflict register |
// В обработчике ОбменДанными.ОбработкаКонфликта (БСП)
// или в ручной логике загрузки:
Функция РазрешитьКонфликт(ДанныеПриемника, ДанныеИсточника, УзелОбмена) Экспорт
// Если это главный узел — наши данные побеждают всегда
Если ПланыОбмена.МойОбмен.ГлавныйУзел() = Неопределено Тогда
// Мы главный узел
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Конфликт'"),
УровеньЖурналаРегистрации.Предупреждение,
ДанныеПриемника.Метаданные(),
ДанныеПриемника.Ссылка,
СтрШаблон(НСтр("ru = 'Конфликт с узлом %1. Победа главного узла.'"), УзелОбмена));
Возврат Ложь; // Не загружать данные источника
КонецЕсли;
// Разрешение по дате последнего изменения
Если ДанныеИсточника.МоментВремени() > ДанныеПриемника.МоментВремени() Тогда
Возврат Истина; // Загрузить — источник новее
Иначе
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Конфликт'"),
УровеньЖурналаРегистрации.Предупреждение,
ДанныеПриемника.Метаданные(),
ДанныеПриемника.Ссылка,
НСтр("ru = 'Конфликт. Победа приёмника — локальная версия новее.'"));
Возврат Ложь; // Не загружать — наши данные новее
КонецЕсли;
КонецФункции
БСП provides ready-made infrastructure: node settings, exchange rules, transport (file, FTP, e-mail, WS), message queue, and conflict register.
// Основной модуль подсистемы
// ОбменДаннымиСервер — серверный модуль
// ОбменДаннымиКлиент — клиентский модуль
// ОбменДанными — общий модуль (клиент-сервер)
// Регистрация объекта для обмена через БСП
ОбменДаннымиСервер.ЗарегистрироватьОбъектДляОбмена(УзелОбмена, ОбъектСсылка);
// Запуск синхронизации через БСП
НастройкиОбмена = ОбменДаннымиСервер.НастройкиОбменаДляУзла(УзелОбмена);
ОбменДаннымиСервер.СинхронизироватьДанные(НастройкиОбмена);
EnterpriseData is a standardized XML format for exchange between 1C configurations. It is used in modern БСП exchange plans.
// Структура пакета EnterpriseData
// <Файл>
// <ТипФайла>ОбменДанными</ТипФайла>
// <ФорматВерсии>1.0</ФорматВерсии>
// <Отправитель>
// <Наименование>УТ 11.5</Наименование>
// <Идентификатор>GUID_узла</Идентификатор>
// </Отправитель>
// <Данные>
// <Объект ТипОбъекта="Справочник.Номенклатура">
// ...поля объекта...
// </Объект>
// </Данные>
// </Файл>
// Чтение пакета EnterpriseData
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);
Десериализатор = Новый СериализаторXDTO(ФабрикаXDTO);
Пока ЧтениеXML.Прочитать() Цикл
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента
И ЧтениеXML.ЛокальноеИмя = "Объект" Тогда
ОбъектXDTO = Десериализатор.ПрочитатьXML(ЧтениеXML);
ОбработатьОбъектXDTO(ОбъектXDTO);
КонецЕсли;
КонецЦикла;
КД 2.0 is used for exchange between different configurations using a conversion rules file (XML).
// Загрузка данных с использованием обработки «Конвертация данных»
Процедура ЗагрузитьДанныеПоПравилам(ПутьКФайлу, ПутьКПравилам) Экспорт
ОбработкаЗагрузки = ВнешниеОбработки.Создать(ПутьКПравилам);
ОбработкаЗагрузки.ИмяФайлаОбмена = ПутьКФайлу;
ОбработкаЗагрузки.ПутьКПравиламОбмена = ПутьКПравилам;
ОбработкаЗагрузки.ЗагрузитьПравилаОбмена();
Попытка
ОбработкаЗагрузки.ВыполнитьЗагрузку();
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.КД2.Загрузка'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
| Aspect | КД 2.0 | КД 3.0 | |--------|--------|--------| | Rules | XML file | BSL modules in the КД 3 configuration | | EDT support | No | Yes | | Format | Proprietary XML | EnterpriseData (optional) | | Handlers | In the rules (code strings) | Full BSL, debugging | | Recommendation | Existing projects | New projects |
When diagnosing exchange, look for events:
| Registration log event | What it means |
|------------------------|---------------|
| ОбменДанными | General events of the БСП subsystem |
| ОбменДанными.Выгрузка | Packet formation |
| ОбменДанными.Загрузка | Packet reception and processing |
| ОбменДанными.Конфликт | Recorded conflicts |
| ОбменДанными.Транспорт | Delivery errors |
// Диагностический запрос — состояние узлов и счётчиков сообщений
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Узлы.Ссылка КАК Узел,
| Узлы.Код,
| Узлы.НомерОтправленного,
| Узлы.НомерПринятого,
| КОЛИЧЕСТВО(Изменения.Узел) КАК КоличествоИзменений
|ИЗ
| ПланОбмена.МойОбмен КАК Узлы
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ИзменениеПоПланамОбмена КАК Изменения
| ПО Изменения.Узел = Узлы.Ссылка
|ГДЕ
| Узлы.Ссылка <> Узлы.ЭтотУзел
|СГРУППИРОВАТЬ ПО
| Узлы.Ссылка, Узлы.Код, Узлы.НомерОтправленного, Узлы.НомерПринятого";
РезультатЗапроса = Запрос.Выполнить();
// Сброс счётчика и перерегистрация всех данных для узла
// ВНИМАНИЕ: использовать только при полной ресинхронизации
Процедура ПеререгистрироватьВсеДанные(УзелОбмена) Экспорт
// Сбросить счётчики
УзелОбменаОбъект = УзелОбмена.ПолучитьОбъект();
УзелОбменаОбъект.НомерОтправленного = 0;
УзелОбменаОбъект.НомерПринятого = 0;
УзелОбменаОбъект.Записать();
// Удалить накопленные изменения
ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, 0);
// Зарегистрировать заново
// (зависит от состава плана обмена — перебрать все типы объектов)
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Перерегистрация'"),
УровеньЖурналаРегистрации.Предупреждение,
Метаданные.ПланыОбмена.МойОбмен,
УзелОбмена,
НСтр("ru = 'Выполнена полная перерегистрация данных для узла.'"));
КонецПроцедуры
| Error | Consequence | How to avoid |
|-------|-------------|--------------|
| No ОбменДанными.Загрузка check | Business logic runs during load, data is corrupted or the exchange loops | In every ПриЗаписи handler, at the first lines |
| Fixing НомерОтправленного before delivery confirmation | Changes are marked as sent but never reach the node; they will not be exported in the next session | Fix the number only after recipient confirmation |
| Loading without idempotency check | Duplicate documents, double register movements | Check НомерСообщения <= НомерПринятого before loading |
| Conflict without logging | Data is silently overwritten, user changes are lost | Always write to the registration log on conflict with Warning level |
| Registering changes inside a load handler | Infinite exchange: A->B->A->B | The Загрузка = Истина flag disables registration |
| Long transaction when loading a large packet | Locks, timeouts, rollback of the entire packet | Load object by object with separate transactions or batches |
| Not deleting change register entries after export | Accumulation of millions of records, performance degradation | ПланыОбмена.УдалитьРегистрациюИзменений after confirmation |
depends_on:
testing
MUST use BEFORE making a judgment about the cause of a conflict, a test failure, or an artifact dispute. Defines the end-to-end verification method L1→L6 and the classification of the first broken link.
development
MUST use AFTER a work cycle with ≥2 iterations (wrote → error → fixed → success). Provides the retrospective procedure and the format for recording practice/anti-patterns in references/learned-patterns.md or {project}/.context/learned-patterns.md.
tools
MUST use WHEN you are writing reusable knowledge into RLM (pattern / architectural decision / stable domain fact) OR reading it before a non-trivial task/solution in the domain. Provides the breakdown of native-push vs RLM-pull, tools for writing and reading RLM, H-MEM levels, and hygiene.
testing
MUST use WHEN the task is classified as simple (< 20 lines, 1 file, no new metadata objects, no architectural decisions). Provides a short cycle of 3 steps with a guard on the self path and mandatory verify.