При работе с планами обмена данными в 1С разработчики часто сталкиваются с ситуацией, когда в таблицу изменений попадают документы, которые, на первый взгляд, не должны там быть. Классический пример — документ «Корректировка регистров», который может быть зарегистрирован для обмена, даже если в нем нет ни одной записи по нужному регистру накопления. Это вызывает два основных вопроса: почему так происходит, и как безопасно работать с таким документом-регистратором, если его структура (например, наличие реквизита «Партнер») заранее неизвестна?
Давайте подробно разберем оба аспекта этой задачи: сначала выясним причину такого поведения системы, а затем рассмотрим несколько надежных способов программной проверки наличия реквизитов у объектов с неопределенным типом.
Основная путаница возникает из-за неверного понимания того, что именно отслеживают планы обмена. Механизм регистрации изменений фиксирует не сами движения в регистрах, а факт изменения объекта-регистратора, который включен в состав плана обмена.
Проанализируем типичный сценарий:
РасчетыСКлиентами. В этот момент платформа зарегистрировала изменение документа для всех узлов обмена.РасчетыСКлиентами были удалены. Набор записей стал пустым.Таким образом, появление документа с пустым набором записей в таблице изменений — это не ошибка, а корректное поведение системы, обеспечивающее целостность данных между базами. Система сообщает: «Состояние этого документа изменилось, примените это изменение у себя». Для узла-получателя это означает, что нужно удалить ранее загруженные записи.
Если ваша задача — исключить из выгрузки документы, которые не несут «полезной нагрузки», то нельзя полагаться только на таблицу изменений плана обмена. Необходимо в алгоритме выгрузки добавить дополнительную проверку: для каждого регистратора из таблицы изменений проверять, есть ли у него на текущий момент реальные движения в нужных регистрах.
Когда мы получаем из таблицы изменений регистратор, его тип может быть разным: «РеализацияТоваровУслуг», «ПоступлениеТоваровУслуг», «КорректировкаРегистров» и т.д. Попытка прямого обращения к реквизиту, который есть не у всех, приведет к ошибке.
// Такой код вызовет ошибку, если у СсылкаНаРегистратор нет реквизита "Партнер"
СсылкаНаРегистратор = РезультатЗапроса.Регистратор;
Партнер = СсылкаНаРегистратор.Партнер; // Поле объекта не обнаружено (Партнер)
Рассмотрим четыре способа, как решить эту проблему, от наиболее предпочтительного к менее желательному.
В современных типовых конфигурациях (УТ, ERP, КА) уже есть готовые инструменты для таких проверок. Самый правильный и надежный способ — использовать функцию общего модуля.
Функция ЕстьРеквизитИлиСвойствоОбъекта из общего модуля ОбщегоНазначенияКлиентСервер создана специально для таких задач. Она универсальна и проверяет наличие не только реквизитов шапки, но и других свойств объекта.
Пример кода:
СсылкаНаРегистратор = РезультатЗапроса.Регистратор;
Если ОбщегоНазначенияКлиентСервер.ЕстьРеквизитИлиСвойствоОбъекта(СсылкаНаРегистратор, "Партнер") Тогда
// Мы уверены, что реквизит существует, можно смело к нему обращаться
Партнер = СсылкаНаРегистратор.Партнер;
Сообщить("Для документа " + СсылкаНаРегистратор + " найден партнер: " + Партнер);
Иначе
Сообщить("У документа " + СсылкаНаРегистратор + " реквизит 'Партнер' отсутствует.");
КонецЕсли;
Этот подход является наиболее предпочтительным, так как он использует штатный механизм конфигурации, читабелен и надежен.
Этот метод будет работать в любой конфигурации, даже если в ней нет подходящих общих модулей. Он заключается в прямом обращении к метаданным объекта, чтобы проверить наличие реквизита в его структуре.
Пример кода:
СсылкаНаРегистратор = РезультатЗапроса.Регистратор;
// Получаем объект метаданных для нашего документа
МетаданныеДокумента = СсылкаНаРегистратор.Метаданные();
// Ищем в коллекции реквизитов нужный нам по имени
Если МетаданныеДокумента.Реквизиты.Найти("Партнер") <> Неопределено Тогда
// Реквизит найден в описании объекта
Партнер = СсылкаНаРегистратор.Партнер;
Сообщить("Партнер (через метаданные): " + Партнер);
Иначе
Сообщить("Реквизит 'Партнер' в метаданных не найден.");
КонецЕсли;
Этот способ абсолютно надежен, так как он оперирует структурой объекта, определенной в конфигураторе. Он немного более "низкоуровневый", но дает полный контроль.
Это элегантный и нетривиальный способ, который позволяет получить значение свойства, не вызывая ошибки, если его не существует. Суть метода в том, что мы пытаемся скопировать свойства из объекта-источника в структуру-приемник. Если в источнике есть свойство с именем, совпадающим с ключом структуры, значение будет скопировано. Если нет — метод просто проигнорирует этот ключ.
Пример кода:
СсылкаНаРегистратор = РезультатЗапроса.Регистратор;
// Создаем структуру с ключом, имя которого совпадает с искомым реквизитом
СтруктураДляПроверки = Новый Структура("Партнер");
// Пытаемся заполнить структуру из объекта. Ошибки не будет.
ЗаполнитьЗначенияСвойств(СтруктураДляПроверки, СсылкаНаРегистратор);
// Теперь проверяем, заполнилось ли значение в нашей структуре
Если ЗначениеЗаполнено(СтруктураДляПроверки.Партнер) Тогда
Партнер = СтруктураДляПроверки.Партнер;
Сообщить("Партнер (через структуру): " + Партнер);
Иначе
// Эта ветка сработает, если реквизита "Партнер" нет ИЛИ он есть, но не заполнен (пустая ссылка)
Сообщить("Реквизит 'Партнер' не найден или пуст.");
КонецЕсли;
Важный нюанс: этот метод не позволяет отличить ситуацию, когда реквизита нет, от ситуации, когда реквизит есть, но его значение пустое (NULL, пустая ссылка, 0). Это может быть как преимуществом, так и недостатком, в зависимости от логики вашей задачи.
Использование блока Попытка...Исключение для проверки наличия реквизита — работающий, но с точки зрения архитектуры плохой подход. Обработка исключений — это медленный механизм, предназначенный для отлова непредвиденных ошибок (проблемы с файлами, внешними компонентами и т.д.), а не для управления нормальной логикой программы.
Пример кода:
СсылкаНаРегистратор = РезультатЗапроса.Регистратор;
Партнер = Неопределено;
Попытка
Партнер = СсылкаНаРегистратор.Партнер;
// Этот код выполнится, только если реквизит существует
Исключение
// Сюда мы попадем, если обращение к СсылкаНаРегистратор.Партнер вызвало ошибку
Сообщить("Обращение к реквизиту 'Партнер' вызвало исключение.");
КонецПопытки;
Если Партнер <> Неопределено Тогда
Сообщить("Партнер (через Попытку): " + Партнер);
КонецЕсли;
Использовать этот метод стоит только в крайнем случае, когда другие способы по каким-либо причинам недоступны. Он делает код менее читаемым и может скрыть другие, более серьезные ошибки выполнения.