При разработке правил обмена в конфигурации «Конвертация данных» (как версии 2.1, так и версии 3) разработчики часто сталкиваются со специфическим поведением платформы 1С:Предприятие при работе с типами данных в обработчиках событий. Наиболее распространенные проблемы возникают в обработчике «Перед выгрузкой» при попытке отфильтровать документы по типу или сравнить их даты с параметрами обмена (например, с датой запрета изменения данных).
В рамках этой статьи мы подробно разберем, почему стандартные методы проверки типов могут не работать во время выгрузки, по какой причине возникает ошибка приведения к числу при сравнении дат, и как написать отказоустойчивый код, соответствующий стандартам разработки фирмы «1С».
Разобьем решение этих задач на последовательные шаги и проанализируем каждую проблему изнутри.
Рассмотрим ситуацию: в обработчике «Перед выгрузкой» нам необходимо определить, является ли выгружаемый объект документом. Попытка использовать привычный метод проверки типов для коллекции документов часто приводит к тому, что условие попросту игнорируется системой. Посмотрим на пример кода, который часто пишут новички:
Если Документы.ТипВсеСсылки().СодержитТип(ТипЗнч(Объект)) Тогда
// Код проверки...
КонецЕсли;
Выясним причину, почему это условие всегда возвращает Ложь. Дело в том, что в обработчиках выгрузки КД в переменной Объект нам доступен непосредственно выгружаемый прикладной объект — например, типа ДокументОбъект.ПеремещениеТоваров.
В то же время метод Документы.ТипВсеСсылки() возвращает описание типов, содержащее исключительно типы ссылок (то есть ДокументСсылка.ПеремещениеТоваров и аналогичные ссылки для всех остальных документов базы данных). С точки зрения платформы 1С:Предприятие, типы ДокументОбъект и ДокументСсылка — это абсолютно разные, несовпадающие типы данных. Именно поэтому проверка на вхождение типа объекта в массив типов ссылок всегда возвращает отрицательный результат.
Разберем три правильных и надежных способа обхода этой архитектурной особенности.
Способ 1. Использование свойства Ссылка (простой вариант)
Самый быстрый и очевидный способ — проверять тип не самого объекта, а его ссылки. У любого прикладного объекта в 1С есть свойство Ссылка, которое имеет правильный ссылочный тип:
Если Документы.ТипВсеСсылки().СодержитТип(ТипЗнч(Объект.Ссылка)) Тогда
// Теперь это условие сработает корректно для любого документа
КонецЕсли;
Однако этот способ таит в себе скрытую угрозу. Если в переменную Объект в силу каких-то причин (например, при выгрузке кастомных параметров или переопределении алгоритмов) будет передано значение примитивного типа (число, строка, булево) или таблица значений, обращение к свойству Объект.Ссылка вызовет критическую ошибку выполнения: «Поле объекта не обнаружено (Ссылка)». Чтобы сделать код безопасным, лучше использовать метаданные.
Способ 2. Проверка через метаданные (универсальный стандарт 1С)
Для того чтобы написать максимально надежный код, который никогда не упадет с ошибкой «Поле объекта не обнаружено», обратимся к глобальной коллекции метаданных с помощью функции Метаданные.НайтиПоТипу(). Проанализируем следующий шаблон:
ОбъектМетаданных = Метаданные.НайтиПоТипу(ТипЗнч(Объект));
Если ОбъектМетаданных <> Неопределено И Метаданные.Документы.Содержит(ОбъектМетаданных) Тогда
// Этот блок выполнится только в том случае, если Объект действительно является документом
КонецЕсли;
Этот способ идеален, так как функция НайтиПоТипу() корректно обработает абсолютно любой тип данных, переданный в качестве аргумента, и вернет Неопределено в случае работы с примитивными типами или структурами, предотвращая падение кода.
Способ 3. Использование функций БСП
Если ваша конфигурация разработана на базе Библиотеки Стандартных Подсистем (БСП), то в вашем распоряжении есть готовые функции общего назначения. Код проверки сокращается до одной элегантной строки:
Если ОбщегоНазначения.ЭтоДокумент(Объект.Метаданные()) Тогда
// Быстрая и безопасная проверка средствами БСП
КонецЕсли;
Иногда разработчики пытаются подстраховаться и перед обращением к дате программно проверить, есть ли у объекта такой реквизит. Посмотрим на ошибочный вариант такой проверки:
// ОШИБОЧНЫЙ КОД: Поиск даты в коллекции обычных реквизитов
Если Объект.Метаданные().Реквизиты.Найти("Дата") <> Неопределено Тогда
// Сюда мы никогда не попадем!
КонецЕсли;
Выясним причину этой ошибки. Платформа 1С делит структуру метаданных любого объекта на две разные категории:
Ссылка, Дата, Номер, Проведен, ПометкаУдаления.Организация, Контрагент, СуммаДокумента).Поскольку Дата — это стандартный реквизит, искать ее в коллекции Объект.Метаданные().Реквизиты бесполезно. Она там отсутствует. Если вам действительно требуется проверить наличие стандартного реквизита, то обращаться нужно к другой коллекции:
Если Объект.Метаданные().СтандартныеРеквизиты.Найти("Дата") <> Неопределено Тогда
// Проверка выполнена успешно
КонецЕсли;
Однако помните, что абсолютно каждый документ в системе 1С:Предприятие по определению обладает стандартным реквизитом Дата. Проверять его наличие программно не имеет практического смысла — достаточно просто убедиться, что обрабатываемый объект является документом (методами из Части 1 нашей статьи).
Перейдем ко второй проблеме. Допустим, мы определили, что объект является документом, и хотим сравнить его дату с параметром обмена: Параметры.ГраницаЗапретаИзменений. Мы пишем код:
Если Объект.Дата < Параметры.ГраницаЗапретаИзменений Тогда
Отказ = Истина;
КонецЕсли;
В процессе выгрузки выполнение прерывается критической ошибкой: «Преобразование значения к типу Число не может быть выполнено». При этом отладочные сообщения утверждают, что оба значения имеют тип Дата.
Разберем детально физику этого процесса. Платформа 1С на низком уровне сравнивает даты, переводя их в количество секунд (числовой формат). Ошибка преобразования к числу возникает тогда, когда одна из сторон сравнения принимает значение, отличное от валидной заполненной даты. Это происходит в следующих случаях:
Параметры.ГраницаЗапретаИзменений не был корректно инициализирован при старте выгрузки и имеет значение Неопределено.'00010101000000'.В некоторых случаях разработчикам помогает обертывание дат в функцию НачалоДня(), так как она может неявно преобразовывать пограничные значения. Однако полагаться только на нее опасно: если аргументом функции окажется значение Неопределено, это все равно приведет к падению системы на некоторых версиях платформы.
Рассмотрим, как написать максимально безопасный и стабильный код для сравнения дат, исключающий любые ошибки преобразования типов:
// Шаг 1: Проверяем тип объекта
ОбъектМетаданных = Метаданные.НайтиПоТипу(ТипЗнч(Объект));
Если ОбъектМетаданных <> Неопределено И Метаданные.Документы.Содержит(ОбъектМетаданных) Тогда
// Шаг 2: Безопасно получаем значение границы запрета изменений из параметров
ГраницаЗапрета = Неопределено;
Если Параметры.Свойство("ГраницаЗапретаИзменений") Тогда
ГраницаЗапрета = Параметры.ГраницаЗапретаИзменений;
КонецЕсли;
// Шаг 3: Проверяем заполненность обеих дат перед сравнением
Если ЗначениеЗаполнено(Объект.Дата) И ЗначениеЗаполнено(ГраницаЗапрета) Тогда
// Сравниваем даты, приводя их к началу дня для исключения влияния времени
Если НачалоДня(Объект.Дата) < НачалоДня(ГраницаЗапрета) Тогда
Отказ = Истина;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Проанализируем преимущества этой конструкции:
Метаданные.НайтиПоТипу() защищает код от непредвиденных типов выгружаемых объектов.Параметры.Свойство() гарантирует, что обращение к параметру не вызовет падения, даже если сам параметр забыли объявить в правилах обмена.ЗначениеЗаполнено() отсекает пустые даты, значения типа Неопределено и Null, защищая оператор сравнения от попытки некорректного приведения к числу.НачалоДня() нивелирует разницу во времени документов, позволяя сравнивать именно календарные дни, что критически важно для корректной работы границ запрета изменений.Используя этот подход в обработчиках конвертации данных, вы обеспечите стабильную и безотказную работу ваших интеграционных механизмов в любых нештатных ситуациях.