framework/skills/bsl-practices/data-exchange/SKILL.md
Для обменов 1С: РИБ, КД, EnterpriseData, БСП
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.
Ключевой принцип: Обмен данными — это распределённая система. Каждый пакет должен быть идемпотентным (повторная загрузка не портит данные), каждый конфликт — явно разрешённым, каждая ошибка — залогированной с привязкой к узлу и номеру сообщения.
Перед реализацией необходимо определить модель обмена. Выбор влияет на весь дальнейший код.
| Модель | Когда использовать | Механизм |
|--------|-------------------|----------|
| РИБ (распределённая ИБ) | Полная копия конфигурации на узлах, переносятся все данные | ПланыОбмена, ЗаписьСообщенияОбмена/ЧтениеСообщенияОбмена, СериализаторXDTO |
| Избирательный обмен (БСП) | Обмен по правилам, фильтрация объектов, разные конфигурации | Подсистема «Обмен данными» БСП, формат EnterpriseData |
| КД 2.0 | Сложные правила конвертации между разными конфигурациями | Обработка «Конвертация данных», XML-правила |
| КД 3.0 / EDT | Современные проекты, правила на BSL, поддержка EnterpriseData | Конфигурация «Конвертация данных 3» |
// Главный узел — это текущая ИБ
// Подчинённые узлы — внешние базы данных
УзелГлавный = ПланыОбмена.МойОбмен.ГлавныйУзел();
Если УзелГлавный = Неопределено Тогда
// Это главный узел (центральная база)
КонецЕсли;
// Создание нового узла обмена
НовыйУзел = ПланыОбмена.МойОбмен.СоздатьУзел();
НовыйУзел.Код = "ФИЛИАЛ_001";
НовыйУзел.Наименование = "Филиал Москва";
НовыйУзел.Записать();
Регистрация изменений — основной механизм отслеживания того, что нужно передать узлу. Неправильная регистрация — главный источник ошибок: либо данные не уходят, либо уходят лишние.
// Зарегистрировать конкретный объект для конкретного узла
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, ОбъектСсылка);
// Зарегистрировать набор объектов (например, при первичной синхронизации)
МассивОбъектов = Новый Массив;
МассивОбъектов.Добавить(НоменклатураСсылка1);
МассивОбъектов.Добавить(НоменклатураСсылка2);
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, МассивОбъектов);
// В обработчике подписки ПриЗаписи — регистрировать изменения явно
Процедура НоменклатураПриЗаписи(Источник, Отказ, РежимЗаписи, РежимПроведения)
Если Источник.ОбменДанными.Загрузка Тогда
Возврат; // Не регистрировать изменения при загрузке из обмена
КонецЕсли;
Для Каждого УзелОбмена Из ПланыОбмена.МойОбмен.Выбрать() Цикл
Если УзелОбмена.Ссылка <> ПланыОбмена.МойОбмен.ЭтотУзел() Тогда
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена.Ссылка, Источник.Ссылка);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// ОБЯЗАТЕЛЬНО в начале каждого обработчика объекта
Если Источник.ОбменДанными.Загрузка Тогда
Возврат; // Пропустить все проверки и дополнительную логику при загрузке
КонецЕсли;
Без этой проверки: при загрузке данных из внешней базы сработают бизнес-правила текущей базы → данные испортятся или загрузка завершится с ошибкой.
Функция СформироватьСообщениеОбмена(УзелОбмена) Экспорт
ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
Попытка
// Получатель задаётся в НачатьЗапись(); номер отправленного назначается автоматически
ЗаписьСообщения.НачатьЗапись(ЗаписьXML, УзелОбмена);
// Выгрузка изменений, зарегистрированных для этого получателя
ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(
ЗаписьСообщения.Получатель, ЗаписьСообщения.НомерСообщения);
Пока ВыборкаИзменений.Следующий() Цикл
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, ВыборкаИзменений.Получить());
КонецЦикла;
ЗаписьСообщения.ЗакончитьЗапись();
Исключение
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Выгрузка'"),
УровеньЖурналаРегистрации.Ошибка,
Метаданные.ПланыОбмена.МойОбмен,
УзелОбмена,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
Возврат ЗаписьXML.Закрыть();
КонецФункции
// ВАЖНО: у ПланыОбмена НЕТ методов УстановитьНомерОтправленного/УстановитьНомерПринятого.
// Номер отправленного увеличивается автоматически при ЗаписьСообщения.НачатьЗапись().
// Номера принятого/отправленного — это реквизиты узла, правятся через объект узла.
// На стороне отправителя — после подтверждения доставки снимаем регистрацию изменений
// вплоть до подтверждённого номера:
ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, НомерСообщения);
// На стороне получателя — фиксируем номер принятого через объект узла:
ОбъектУзла = УзелОбмена.ПолучитьОбъект();
ОбъектУзла.НомерПринятого = НомерСообщения;
ОбъектУзла.Записать();
Идемпотентность пакета означает: повторная загрузка одного и того же сообщения не изменяет результат. Это критично при сетевых сбоях и ручных повторных запусках.
Транзакционный skeleton — канон error-handling (Правило 2); специфика здесь — идемпотентная проверка номера сообщения ДО отмены/фиксации.
Процедура ЗагрузитьСообщениеОбмена(ТекстСообщения) Экспорт
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ТекстСообщения);
ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();
НачатьТранзакцию();
Попытка
ЧтениеСообщения.НачатьЧтение(ЧтениеXML);
// Проверка: не загружать уже принятое сообщение (идемпотентность)
Если ЧтениеСообщения.НомерСообщения <= ЧтениеСообщения.Отправитель.НомерПринятого Тогда
ЗаписьЖурналаРегистрации(
НСтр("ru = 'ОбменДанными.Загрузка'"),
УровеньЖурналаРегистрации.Предупреждение,,,
СтрШаблон(НСтр("ru = 'Сообщение №%1 от узла %2 уже было принято. Пропускаем.'"),
ЧтениеСообщения.НомерСообщения, ЧтениеСообщения.Отправитель));
ОтменитьТранзакцию();
Возврат;
КонецЕсли;
// Загрузка объектов
Пока СериализаторXDTO.ВозможностьЧтенияXML(ЧтениеXML) Цикл
Данные = СериализаторXDTO.Прочитать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:
development
1C server maintenance webhooks: container restart and external component cache cleanup
development
Interactive DAP debugging of a single BSL procedure
tools
Rules for using RLM tools for project search and navigation in 1C/BSL
development
Creates web applications and routes on Winow (a web server on OneScript and Autumn). Use when working with a web server on OneScript, routing, or Winow controllers.