Как проверить, содержит ли табличная часть документа определенное значение в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки в системе 1С:Предприятие часто возникает задача анализа истории изменений какого-либо объекта. Типичный пример — табличная часть (ТЧ) документа, в которой фиксируются этапы его выполнения. Документ последовательно проходит через цепочку статусов, например: «Принято» — «Выполнено» — «Возвращено» — и снова «Выполнено».

Сложность заключается в том, что на текущий момент документ может находиться в финальном статусе «Выполнено», но нам крайне важно узнать, возвращался ли он когда-либо на доработку ранее (то есть присутствовал ли в истории табличной части статус «Возвращено»). Если такой статус был зафиксирован, система должна вернуть определенный номер, иначе — вернуть «0».

В этой статье мы подробно разберем, как решить эту задачу наиболее эффективными способами, исправим частые ошибки начинающих разработчиков и обсудим лучшие практики программирования в 1С.

Выясняем причину: почему простой поиск «в лоб» не работает?

В начале обсуждения автор темы пытался использовать следующее условие:


Если НЕ Результат.Пустой Тогда
    Если ЭтапВыполнения.Состояние = Справочники.СостоянияОбращения.НайтиПоНаименованию("Возвращено") Тогда
        Ответ = Номер;
    Иначе
        Ответ = 0;
    КонецЕсли;
КонецЕсли;

При таком подходе программа проверяет только одну конкретную строку (чаще всего текущую активную строку на форме или последнюю добавленную строку). Если текущее состояние равно «Выполнено», то условие возвращает «0», полностью игнорируя тот факт, что несколько строк назад в этой же табличной части был записан статус «Возвращено».

Давайте разберем по шагам, как заставить систему «прочитать» всю историю табличной части и найти нужное нам значение.

Способ №1. Классический перебор строк в цикле

Самый понятный и очевидный способ проверить всю историю — это последовательно обойти все строки табличной части в цикле Для Каждого. Если в процессе обхода мы натыкаемся на искомый статус, мы фиксируем этот факт и прерываем выполнение цикла для экономии времени.

Рассмотрим пример реализации:


// Инициализируем ответ значением по умолчанию
Ответ = 0; 

// Находим нужное нам состояние (пока используем справочник)
ИскомоеСостояние = Справочники.СостоянияОбращения.НайтиПоНаименованию("Возвращено");

// Перебираем строки табличной части документа
Для Каждого СтрокаЭтапа Из Объект.ЭтапыВыполнения Цикл
    Если СтрокаЭтапа.Состояние = ИскомоеСостояние Тогда
        // Если статус найден, присваиваем значение и досрочно выходим из цикла
        Ответ = Номер;
        Прервать; 
    КонецЕсли;
КонецЦикла;

Этот способ прост и надежен. Он отлично подходит для небольших табличных частей и интуитивно понятен даже начинающему разработчику.

Способ №2. Оптимизация поиска с использованием метода Найти()

В платформе 1С для коллекций строк (к которым относится и табличная часть) существуют встроенные методы поиска, которые работают быстрее ручного обхода в цикле. На форуме часто предлагают использовать метод НайтиСтроки(), однако мы настоятельно рекомендуем использовать метод Найти().

Давайте проанализируем разницу между ними:

  1. Метод НайтиСтроки(): принимает структуру отбора и всегда обходит всю табличную часть целиком, чтобы вернуть массив всех подходящих строк. Если таблица большая, это создает лишнюю нагрузку на память.
  2. Метод Найти(): ищет первое совпадение в указанной колонке. Как только совпадение найдено, метод мгновенно прекращает поиск и возвращает строку. Для нашей задачи (проверить факт наличия статуса хотя бы один раз) это идеальное решение.

Посмотрим на пример кода с использованием метода Найти():


ИскомоеСостояние = Справочники.СостоянияОбращения.НайтиПоНаименованию("Возвращено");

// Ищем первое вхождение значения в колонке "Состояние"
НайденнаяСтрока = Объект.ЭтапыВыполнения.Найти(ИскомоеСостояние, "Состояние");

Если НайденнаяСтрока <> Неопределено Тогда
    // Статус "Возвращено" присутствует в истории табличной части
    Ответ = Номер;
Иначе
    Ответ = 0;
КонецЕсли;

Способ №3. Избавление от НайтиПоНаименованию() — улучшаем архитектуру

Обратите внимание на конструкцию Справочники.СостоянияОбращения.НайтиПоНаименованию("Возвращено"). Использование жестко прописанных текстовых наименований в коде — это критическая архитектурная ошибка (так называемый «bad practice»).

Если конечный пользователь решит исправить опечатку в справочнике (например, переименует «Возвращено» в «Возвращено на доработку» или случайно добавит лишний пробел в конце), поиск перестанет работать, и система всегда будет возвращать «0».

Давайте рассмотрим, как сделать наш код отказоустойчивым:

Вариант А: Использование Перечислений (Enum)

Если список состояний фиксирован и не должен изменяться пользователями в режиме предприятия, колонка «Состояние» должна ссылаться на Перечисление, а не на Справочник. Перечисления создаются на этапе конфигурации и защищены от изменений пользователями. Код будет выглядеть максимально просто и безопасно:


// Обращение к перечислению происходит мгновенно и безопасно
ИскомоеСостояние = Перечисления.СостоянияОбращения.Возвращено;

Если Объект.ЭтапыВыполнения.Найти(ИскомоеСостояние, "Состояние") <> Неопределено Тогда
    Ответ = Номер;
Иначе
    Ответ = 0;
КонецЕсли;

Вариант Б: Использование Предопределенных элементов справочника

Если бизнес-логика требует, чтобы это был именно Справочник (например, пользователи могут добавлять свои уникальные статусы), сделайте статус «Возвращено» предопределенным элементом в конфигураторе. Тогда вы сможете безопасно обращаться к нему по имени предопределенного элемента:


ИскомоеСостояние = Справочники.СостоянияОбращения.Возвращено; // Безопасное обращение к предопределенному элементу

Способ №4. Учет хронологии и порядка строк

Иногда бизнес-логика требует не просто узнать, был ли когда-то «Возврат», а убедиться, что он был записан строго до текущего (последнего) статуса документа.

Поскольку строки в табличную часть добавляются последовательно, хронологию процесса определяет индекс строки. Мы можем проверить, что статус «Возвращено» содержится в истории, исключая из проверки самую последнюю (текущую) строку.

Давайте реализуем алгоритм с учетом индексов:


КоличествоСтрок = Объект.ЭтапыВыполнения.Количество();
Ответ = 0;

// Проверяем, есть ли вообще строки в табличной части
Если КоличествоСтрок > 1 Тогда
    СостояниеВозвращено = ПредопределенноеЗначение("Справочник.СостоянияОбращения.Возвращено");
    
    // Перебираем все строки, кроме последней (индексы от 0 до КоличествоСтрок - 2)
    Для Индекс = 0 По КоличествоСтрок - 2 Цикл
        Если Объект.ЭтапыВыполнения[Индекс].Состояние = СостояниеВозвращено Тогда
            Ответ = Номер;
            Прервать; // Возврат найден в истории, прекращаем поиск
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

Способ №5. Использование языка запросов для анализа ТЧ

Запросы — наиболее эффективный инструмент для работы с базой данных 1С. Однако у начинающих разработчиков часто возникает вопрос: как написать запрос к документу, который пользователь прямо сейчас заполняет на форме, ведь эти данные еще не записаны в базу данных?

Мы можем легко решить эту проблему! Для этого достаточно выгрузить текущую табличную часть документа во временную Таблицу Значений и передать ее в запрос в качестве параметра.

Проанализируем этот элегантный пример кода:


// Выгружаем ТЧ документа в память как Таблицу Значений
ТаблицаЭтапов = Объект.ЭтапыВыполнения.Выгрузить(); 

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |   1 КАК БылВозврат
    |ИЗ
    |   &ТаблицаЭтапов КАК ТаблицаЭтапов
    |ГДЕ
    |   ТаблицаЭтапов.Состояние = &СостояниеВозвращено";

// Передаем таблицу значений и искомое состояние в параметры запроса
Запрос.УстановитьПараметр("ТаблицаЭтапов", ТаблицаЭтапов);
Запрос.УстановитьПараметр("СостояниеВозвращено", ПредопределенноеЗначение("Справочник.СостоянияОбращения.Возвращено"));

РезультатЗапроса = Запрос.Выполнить();

Если НЕ РезультатЗапроса.Пустой() Тогда
    Ответ = Номер;
Иначе
    Ответ = 0;
КонецЕсли;

Использование запроса к выгруженной табличной части особенно актуально, если вам нужно применить сложные условия фильтрации, соединить эти данные с другими таблицами или выполнить группировку.

Подведем итоги

Мы рассмотрели пять различных подходов к решению задачи поиска исторического значения в табличной части документа. Для простых задач идеально подойдет Способ №2 с использованием оптимизированного метода Найти(). Если требуется сложный анализ истории с учетом дат и сортировок — лучшим выбором станет использование запросов (Способ №5).

Главное — избегайте поиска элементов по наименованию и всегда стремитесь использовать перечисления или предопределенные элементы справочников для стабильной и безотказной работы вашей конфигурации!

← На главную