framework/skills/bsl-practices/data-exchange/SKILL.md
Use for реализации и диагностики обмена данными 1С (РИБ, КД 2.0/3.0, EnterpriseData, БСП). Helps выбрать модель обмена, обеспечить идемпотентность пакетов и явное разрешение конфликтов.
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.
Ключевой принцип: Обмен данными — это распределённая система. Каждый пакет должен быть идемпотентным (повторная загрузка не портит данные), каждый конфликт — явно разрешённым, каждая ошибка — залогированной с привязкой к узлу и номеру сообщения.
Перед реализацией необходимо определить модель обмена. Выбор влияет на весь дальнейший код.
| Модель | Когда использовать | Механизм |
|--------|-------------------|----------|
| РИБ (распределённая ИБ) | Полная копия конфигурации на узлах, переносятся все данные | ПланыОбмена, XML-сериализация, ОбменДаннымиXML |
| Избирательный обмен (БСП) | Обмен по правилам, фильтрация объектов, разные конфигурации | Подсистема «Обмен данными» БСП, формат EnterpriseData |
| КД 2.0 | Сложные правила конвертации между разными конфигурациями | Обработка «Конвертация данных», XML-правила |
| КД 3.0 / EDT | Современные проекты, правила на BSL, поддержка EnterpriseData | Конфигурация «Конвертация данных 3» |
// Главный узел — это текущая ИБ
// Подчинённые узлы — внешние базы данных
УзелГлавный = ПланыОбмена.МойОбмен.ГлавныйУзел();
Если УзелГлавный = Неопределено Тогда
// Это главный узел (центральная база)
КонецЕсли;
// Создание нового узла обмена
НовыйУзел = ПланыОбмена.МойОбмен.СоздатьУзел();
НовыйУзел.Код = "ФИЛИАЛ_001";
НовыйУзел.Наименование = "Филиал Москва";
НовыйУзел.Записать();
Регистрация изменений — основной механизм отслеживания того, что нужно передать узлу. Неправильная регистрация — главный источник ошибок: либо данные не уходят, либо уходят лишние.
// Зарегистрировать конкретный объект для конкретного узла
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, ОбъектСсылка);
// Зарегистрировать набор объектов (например, при первичной синхронизации)
МассивОбъектов = Новый Массив;
МассивОбъектов.Добавить(НоменклатураСсылка1);
МассивОбъектов.Добавить(НоменклатураСсылка2);
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, МассивОбъектов);
// В обработчике подписки ПриЗаписи — регистрировать изменения явно
Процедура НоменклатураПриЗаписи(Источник, Отказ, РежимЗаписи, РежимПроведения)
Если Источник.ОбменДанными.Загрузка Тогда
Возврат; // Не регистрировать изменения при загрузке из обмена
КонецЕсли;
Для Каждого УзелОбмена Из ПланыОбмена.МойОбмен.Выбрать() Цикл
Если УзелОбмена.Ссылка <> ПланыОбмена.МойОбмен.ЭтотУзел() Тогда
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена.Ссылка, Источник.Ссылка);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// ОБЯЗАТЕЛЬНО в начале каждого обработчика объекта
Если Источник.ОбменДанными.Загрузка Тогда
Возврат; // Пропустить все проверки и дополнительную логику при загрузке
КонецЕсли;
Без этой проверки: при загрузке данных из внешней базы сработают бизнес-правила текущей базы → данные испортятся или загрузка завершится с ошибкой.
Функция СформироватьСообщениеОбмена(УзелОбмена) Экспорт
СообщениеОбмена = ПланыОбмена.СоздатьСообщениеОбмена();
СообщениеОбмена.Отправитель = ПланыОбмена.МойОбмен.ЭтотУзел();
СообщениеОбмена.Получатель = УзелОбмена;
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
Попытка
ОбменДаннымиXML.НачатьЗаписьСообщения(ЗаписьXML, СообщениеОбмена);
// Выгрузка изменений
ВыборкаИзменений = ПланыОбмена.Выбрать(УзелОбмена, ТипПланаОбмена.ТолькоИзменения);
Пока ВыборкаИзменений.Следующий() Цикл
ОбменДаннымиXML.ЗаписатьИзменения(ЗаписьXML, ВыборкаИзменений.ПолучитьОбъект());
КонецЦикла;
ОбменДаннымиXML.ЗакончитьЗаписьСообщения(ЗаписьXML);
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Выгрузка'"),
УровеньЖурналаРегистрации.Ошибка,
Метаданные.ПланыОбмена.МойОбмен,
УзелОбмена,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
Возврат ЗаписьXML.Закрыть();
КонецФункции
// ВАЖНО: номер сообщения фиксируется только после успешной доставки
// Не вызывать УстановитьНомерОтправленного до подтверждения получения
// На стороне отправителя — после подтверждения доставки:
ПланыОбмена.УстановитьНомерОтправленного(УзелОбмена, НомерСообщения);
// На стороне получателя — после успешной загрузки:
ПланыОбмена.УстановитьНомерПринятого(УзелОбмена, НомерСообщения);
Идемпотентность пакета означает: повторная загрузка одного и того же сообщения не изменяет результат. Это критично при сетевых сбоях и ручных повторных запусках.
Процедура ЗагрузитьСообщениеОбмена(ТекстСообщения) Экспорт
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ТекстСообщения);
СообщениеОбмена = ПланыОбмена.СоздатьСообщениеОбмена();
НачатьТранзакцию();
Попытка
ОбменДаннымиXML.НачатьЧтениеСообщения(ЧтениеXML, СообщениеОбмена);
// Проверка: не загружать уже принятое сообщение (идемпотентность)
Если СообщениеОбмена.НомерСообщения <= ПланыОбмена.МойОбмен
.НайтиПоКоду(СообщениеОбмена.Отправитель.Код).НомерПринятого Тогда
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Загрузка'"),
УровеньЖурналаРегистрации.Предупреждение,,,
СтрШаблон(НСтр("ru = 'Сообщение №%1 от узла %2 уже было принято. Пропускаем.'"),
СообщениеОбмена.НомерСообщения, СообщениеОбмена.Отправитель));
ОтменитьТранзакцию();
Возврат;
КонецЕсли;
// Загрузка объектов
Пока ОбменДаннымиXML.ЧитатьИзменения(ЧтениеXML) Цикл
Данные = ОбменДаннымиXML.ПрочитатьИзменения(ЧтениеXML);
Данные.ОбменДанными.Загрузка = Истина;
Данные.Записать();
КонецЦикла;
ОбменДаннымиXML.ЗакончитьЧтениеСообщения(ЧтениеXML);
// Фиксируем номер принятого сообщения
ПланыОбмена.УстановитьНомерПринятого(
СообщениеОбмена.Отправитель, СообщениеОбмена.НомерСообщения);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Загрузка'"),
УровеньЖурналаРегистрации.Ошибка,
Метаданные.ПланыОбмена.МойОбмен,
СообщениеОбмена.Отправитель,
СтрШаблон(НСтр("ru = 'Ошибка загрузки сообщения №%1.
|%2'"), СообщениеОбмена.НомерСообщения,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке())));
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
// При загрузке справочников — искать по внешнему идентификатору, не создавать дубли
Функция НайтиИлиСоздатьНоменклатуру(ВнешнийКод, Наименование)
// Поиск по внешнему коду (синхронизационному ключу)
СуществующийЭлемент = Справочники.Номенклатура.НайтиПоКоду(ВнешнийКод);
Если СуществующийЭлемент <> Справочники.Номенклатура.ПустаяСсылка() Тогда
Возврат СуществующийЭлемент;
КонецЕсли;
// Создать новый только если не нашли
НовыйЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
НовыйЭлемент.Код = ВнешнийКод;
НовыйЭлемент.Наименование = Наименование;
НовыйЭлемент.ОбменДанными.Загрузка = Истина;
НовыйЭлемент.Записать();
Возврат НовыйЭлемент.Ссылка;
КонецФункции
Конфликт возникает когда один и тот же объект изменён в двух узлах одновременно. Стратегия разрешения должна быть задокументирована и реализована явно — «молчаливая» победа последнего изменения недопустима в большинстве бизнес-задач.
| Стратегия | Когда применять | Реализация |
|-----------|----------------|------------|
| Победа главного узла | Главная база — источник правды | Отклонять изменения из подчинённых при конфликте |
| Победа последнего изменения | Некритичные данные (настройки, описания) | МоментВремени() — кто новее, тот побеждает |
| Победа по бизнес-правилу | Статусная машина, приоритеты | Явная функция разрешения |
| Ручное разрешение | Критичные данные, не поддаются автоматике | Фиксировать в реестре конфликтов |
// В обработчике ОбменДанными.ОбработкаКонфликта (БСП)
// или в ручной логике загрузки:
Функция РазрешитьКонфликт(ДанныеПриемника, ДанныеИсточника, УзелОбмена) Экспорт
// Если это главный узел — наши данные побеждают всегда
Если ПланыОбмена.МойОбмен.ГлавныйУзел() = Неопределено Тогда
// Мы главный узел
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Конфликт'"),
УровеньЖурналаРегистрации.Предупреждение,
ДанныеПриемника.Метаданные(),
ДанныеПриемника.Ссылка,
СтрШаблон(НСтр("ru = 'Конфликт с узлом %1. Победа главного узла.'"), УзелОбмена));
Возврат Ложь; // Не загружать данные источника
КонецЕсли;
// Разрешение по дате последнего изменения
Если ДанныеИсточника.МоментВремени() > ДанныеПриемника.МоментВремени() Тогда
Возврат Истина; // Загрузить — источник новее
Иначе
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Конфликт'"),
УровеньЖурналаРегистрации.Предупреждение,
ДанныеПриемника.Метаданные(),
ДанныеПриемника.Ссылка,
НСтр("ru = 'Конфликт. Победа приёмника — локальная версия новее.'"));
Возврат Ложь; // Не загружать — наши данные новее
КонецЕсли;
КонецФункции
БСП предоставляет готовую инфраструктуру: настройки узлов, правила обмена, транспорт (файл, FTP, e-mail, WS), очередь сообщений, реестр конфликтов.
// Основной модуль подсистемы
// ОбменДаннымиСервер — серверный модуль
// ОбменДаннымиКлиент — клиентский модуль
// ОбменДанными — общий модуль (клиент-сервер)
// Регистрация объекта для обмена через БСП
ОбменДаннымиСервер.ЗарегистрироватьОбъектДляОбмена(УзелОбмена, ОбъектСсылка);
// Запуск синхронизации через БСП
НастройкиОбмена = ОбменДаннымиСервер.НастройкиОбменаДляУзла(УзелОбмена);
ОбменДаннымиСервер.СинхронизироватьДанные(НастройкиОбмена);
EnterpriseData — стандартизированный XML-формат для обмена между конфигурациями 1С. Используется в современных планах обмена БСП.
// Структура пакета EnterpriseData
// <Файл>
// <ТипФайла>ОбменДанными</ТипФайла>
// <ФорматВерсии>1.0</ФорматВерсии>
// <Отправитель>
// <Наименование>УТ 11.5</Наименование>
// <Идентификатор>GUID_узла</Идентификатор>
// </Отправитель>
// <Данные>
// <Объект ТипОбъекта="Справочник.Номенклатура">
// ...поля объекта...
// </Объект>
// </Данные>
// </Файл>
// Чтение пакета EnterpriseData
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.ОткрытьФайл(ПутьКФайлу);
Десериализатор = Новый СериализаторXDTO(ФабрикаXDTO);
Пока ЧтениеXML.Прочитать() Цикл
Если ЧтениеXML.ТипУзла = ТипУзлаXML.НачалоЭлемента
И ЧтениеXML.ЛокальноеИмя = "Объект" Тогда
ОбъектXDTO = Десериализатор.ПрочитатьXML(ЧтениеXML);
ОбработатьОбъектXDTO(ОбъектXDTO);
КонецЕсли;
КонецЦикла;
КД 2.0 используется для обмена между разными конфигурациями с помощью файла правил конвертации (XML).
// Загрузка данных с использованием обработки «Конвертация данных»
Процедура ЗагрузитьДанныеПоПравилам(ПутьКФайлу, ПутьКПравилам) Экспорт
ОбработкаЗагрузки = ВнешниеОбработки.Создать(ПутьКПравилам);
ОбработкаЗагрузки.ИмяФайлаОбмена = ПутьКФайлу;
ОбработкаЗагрузки.ПутьКПравиламОбмена = ПутьКПравилам;
ОбработкаЗагрузки.ЗагрузитьПравилаОбмена();
Попытка
ОбработкаЗагрузки.ВыполнитьЗагрузку();
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.КД2.Загрузка'"),
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
| Аспект | КД 2.0 | КД 3.0 | |--------|--------|--------| | Правила | XML-файл | BSL-модули в конфигурации КД 3 | | Поддержка EDT | Нет | Да | | Формат | Proprietary XML | EnterpriseData (опционально) | | Обработчики | В правилах (строки кода) | Полноценный BSL, отладка | | Рекомендация | Существующие проекты | Новые проекты |
При диагностике обмена искать события:
| Событие ЖР | Что означает |
|-----------|-------------|
| ОбменДанными | Общие события подсистемы БСП |
| ОбменДанными.Выгрузка | Формирование пакета |
| ОбменДанными.Загрузка | Приём и обработка пакета |
| ОбменДанными.Конфликт | Зафиксированные конфликты |
| ОбменДанными.Транспорт | Ошибки доставки |
// Диагностический запрос — состояние узлов и счётчиков сообщений
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Узлы.Ссылка КАК Узел,
| Узлы.Код,
| Узлы.НомерОтправленного,
| Узлы.НомерПринятого,
| КОЛИЧЕСТВО(Изменения.Узел) КАК КоличествоИзменений
|ИЗ
| ПланОбмена.МойОбмен КАК Узлы
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ИзменениеПоПланамОбмена КАК Изменения
| ПО Изменения.Узел = Узлы.Ссылка
|ГДЕ
| Узлы.Ссылка <> Узлы.ЭтотУзел
|СГРУППИРОВАТЬ ПО
| Узлы.Ссылка, Узлы.Код, Узлы.НомерОтправленного, Узлы.НомерПринятого";
РезультатЗапроса = Запрос.Выполнить();
// Сброс счётчика и перерегистрация всех данных для узла
// ВНИМАНИЕ: использовать только при полной ресинхронизации
Процедура ПеререгистрироватьВсеДанные(УзелОбмена) Экспорт
// Сбросить счётчики
УзелОбменаОбъект = УзелОбмена.ПолучитьОбъект();
УзелОбменаОбъект.НомерОтправленного = 0;
УзелОбменаОбъект.НомерПринятого = 0;
УзелОбменаОбъект.Записать();
// Удалить накопленные изменения
ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, 0);
// Зарегистрировать заново
// (зависит от состава плана обмена — перебрать все типы объектов)
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Перерегистрация'"),
УровеньЖурналаРегистрации.Предупреждение,
Метаданные.ПланыОбмена.МойОбмен,
УзелОбмена,
НСтр("ru = 'Выполнена полная перерегистрация данных для узла.'"));
КонецПроцедуры
| Ошибка | Последствие | Как избежать |
|--------|------------|--------------|
| Нет проверки ОбменДанными.Загрузка | Бизнес-логика срабатывает при загрузке, данные портятся или зацикливается обмен | В каждом обработчике ПриЗаписи в первых строках |
| Фиксация НомерОтправленного до подтверждения доставки | Изменения помечены как отправленные, но до узла не дошли; при следующем сеансе не выгрузятся | Фиксировать номер только после подтверждения получателя |
| Загрузка без проверки идемпотентности | Дублирование документов, двойные движения регистров | Проверять НомерСообщения <= НомерПринятого перед загрузкой |
| Конфликт без логирования | Данные тихо перезаписаны, потеряны правки пользователя | Всегда писать в ЖР при конфликте с уровнем Предупреждение |
| Регистрировать изменения внутри обработчика загрузки | Бесконечный обмен: А→Б→А→Б | Флаг Загрузка = Истина отключает регистрацию |
| Длинная транзакция при загрузке большого пакета | Блокировки, таймауты, откат всего пакета | Загружать пообъектно с отдельными транзакциями или батчами |
| Не удалять записи регистра изменений после выгрузки | Накопление миллионов записей, деградация производительности | ПланыОбмена.УдалитьРегистрациюИзменений после подтверждения |
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.