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