Как программно обойти табличные части документов и динамически изменить их реквизиты в 1С?

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

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

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

  1. Перебирать документы разных типов.
  2. Идентифицировать табличные части этих документов.
  3. Проверять наличие конкретного реквизита в табличной части по его имени.
  4. Присваивать новое значение этому реквизиту в строках табличной части, если он существует.

Для успешного выполнения этой задачи нам необходимо четко понимать разницу между метаданными (описанием структуры) и данными (собственно информацией, хранящейся в базе). Работать с данными документов мы будем через их объекты.

Шаг 1: Получение объекта документа для работы с данными

Прежде чем мы сможем взаимодействовать с содержимым документа, включая его табличные части и строки, нам необходимо получить его объект. Метаданные документа предоставляют нам информацию о его структуре (какие у него есть табличные части, какие реквизиты, их типы и т.д.), но не дают прямого доступа к конкретным данным экземпляра документа. Чтобы получить доступ к данным, мы используем метод ПолучитьОбъект() от ссылки на документ.

Предположим, у нас есть ссылка на документ, который мы хотим обработать. Это может быть результат запроса или элемент выборки. Мы выполним следующее действие:


// Допустим, ДокументСсылка - это ссылка на один из документов
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();

После выполнения этой строки, переменная ДокументОбъект будет содержать объект документа, который позволит нам обращаться к его реквизитам и табличным частям как к свойствам.

Шаг 2: Динамический доступ к табличным частям документа

В отличие от метаданных, где мы перебираем объекты типа Метаданные.Документы.Документ.ТабличныеЧасти, для работы с данными конкретного документа мы обращаемся к его табличным частям как к свойствам объекта. Если имя табличной части заранее известно, мы можем обратиться к ней напрямую: ДокументОбъект.Товары. Однако, если имя табличной части хранится в переменной (например, мы получили его из метаданных), мы используем квадратные скобки.


ИмяТабличнойЧасти = "Товары"; // Пример, но это имя может быть получено динамически из метаданных
ТабличнаяЧастьДокумента = ДокументОбъект[ИмяТабличнойЧасти];

Этот подход позволяет нам обойти все табличные части документа, используя их имена, полученные из метаданных, и получить к ним доступ уже как к коллекциям данных.

Шаг 3: Итерация по строкам табличной части

Получив объект конкретной табличной части (например, ТабличнаяЧастьДокумента), мы можем перебрать все ее строки. Для этого мы используем стандартный цикл "Для Каждого ... Из ... Цикл". Каждая итерация этого цикла будет возвращать объект типа СтрокаТабличнойЧасти, который представляет собой одну строку в табличной части документа.


Для Каждого СтрокаТЧ Из ТабличнаяЧастьДокумента Цикл
    // Здесь СтрокаТЧ - это объект, представляющий одну строку табличной части.
    // Мы можем работать с ее реквизитами.
КонецЦикла;

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

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

Теперь, когда у нас есть объект СтрокаТЧ, представляющий текущую строку табличной части, мы можем обращаться к ее реквизитам. Как и в случае с табличными частями, если имя реквизита известно заранее, мы используем точечную нотацию (например, СтрокаТЧ.Договор). Но для динамического обращения, когда имя реквизита хранится в переменной, мы снова используем квадратные скобки.


ИмяРеквизита = "Договор"; // Имя реквизита, которое мы ищем и хотим изменить
// Сначала рекомендуется проверить существование реквизита, чтобы избежать ошибок
Если СтрокаТЧ.Свойства().Содержит(ИмяРеквизита) Тогда
    // Если реквизит существует, присваиваем ему значение
    // СсылкаНаОбъектДоговора - это заранее полученная ссылка на нужный объект справочника
    СтрокаТЧ[ИмяРеквизита] = СсылкаНаОбъектДоговора;
КонецЕсли;

Эта проверка с помощью СтрокаТЧ.Свойства().Содержит(ИмяРеквизита) является критически важной для обеспечения стабильности нашего кода, поскольку она предотвращает ошибку "Поле объекта не обнаружено", если в какой-либо табличной части или строке искомый реквизит отсутствует. Это особенно актуально, когда мы работаем с разнообразными документами и табличными частями.

Шаг 5: Сохранение изменений

После того как мы внесли все необходимые изменения в табличные части и их строки, эти изменения существуют только в оперативной памяти в объекте ДокументОбъект. Чтобы сохранить их в базе данных, нам обязательно нужно записать документ.

ДокументОбъект.Записать();

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

Комплексный пример: Решение задачи обхода и изменения реквизита "Договор"

Давайте теперь соберем все эти шаги в единый алгоритм для решения первоначальной задачи: обойти табличные части различных документов, найти реквизит "Договор" и присвоить ему определенную ссылку. Для этого нам понадобится целевая ссылка на объект справочника "Договоры", которую мы будем присваивать.


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

// Получаем выборку документов, которые мы хотим обработать.
// Например, это может быть запрос по всем документам или по определенным типам.
// Для демонстрации возьмем все документы, но в реальной задаче лучше ограничивать выборку.
ВыборкаДокументов = Документы.НайтиПоТипу(Тип("ДокументСсылка.РеализацияТоваровУслуг")); // Пример: только Реализации

НачатьТранзакцию(); // Рекомендуется выполнять такие массовые операции в транзакции

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

        Сообщить("Обрабатываем документ: " + ДокументСсылка);
        ДокументОбъект = ДокументСсылка.ПолучитьОбъект();

        // Перебираем метаданные табличных частей текущего документа
        Для Каждого МетаданныеТЧ Из ДокументОбъект.Метаданные().ТабличныеЧасти Цикл
            ИмяТабличнойЧасти = МетаданныеТЧ.Имя;

            // Проверяем, есть ли в этой табличной части реквизит с именем "Договор"
            МетаданныеРеквизитовТЧ = МетаданныеТЧ.Реквизиты;
            РеквизитДоговорНайден = Ложь;
            Для Каждого МетаданныеРеквизита Из МетаданныеРеквизитовТЧ Цикл
                Если МетаданныеРеквизита.Имя = "Договор" Тогда
                    // Дополнительно можно проверить тип реквизита, что это действительно СправочникСсылка.Договоры
                    Если МетаданныеРеквизита.Тип.СодержитТип(Тип("СправочникСсылка.Договоры")) Тогда
                        РеквизитДоговорНайден = Истина;
                        Прервать;
                    КонецЕсли;
                КонецЕсли;
            КонецЦикла;

            Если РеквизитДоговорНайден Тогда
                Сообщить("  В табличной части '" + ИмяТабличнойЧасти + "' найден реквизит 'Договор'.");

                // Получаем коллекцию строк табличной части
                ТабличнаяЧастьДокумента = ДокументОбъект[ИмяТабличнойЧасти];

                // Перебираем строки этой табличной части
                Для Каждого СтрокаТЧ Из ТабличнаяЧастьДокумента Цикл
                    // Присваиваем значение реквизиту "Договор"
                    // Проверка на существование реквизита в строке здесь избыточна, т.к. мы уже проверили по метаданным
                    СтрокаТЧ["Договор"] = СсылкаНаЦелевойДоговор;
                КонецЦикла;
                Сообщить("  Реквизит 'Договор' изменен во всех строках табличной части '" + ИмяТабличнойЧасти + "'.");
            КонецЕсли;
        КонецЦикла;

        // Записываем измененный документ
        ДокументОбъект.Записать();
        Сообщить("Документ " + ДокументСсылка + " успешно записан.");

    КонецЦикла;
    
    ЗафиксироватьТранзакцию();
    Сообщить("Операция завершена. Все изменения сохранены.");

Исключение
    Сообщить("Произошла ошибка при обработке документов: " + ОписаниеОшибки());
    ОтменитьТранзакцию();
КонецПопытки;

Обратите внимание, что в данном примере мы сначала получаем список документов (ВыборкаДокументов), затем для каждого документа получаем его объект. Внутри цикла по документам мы перебираем метаданные табличных частей, чтобы динамически определить, какие табличные части существуют и содержат нужный нам реквизит "Договор". После этого мы обращаемся к реальным данным табличной части и ее строкам, чтобы изменить значение реквизита. Все изменения фиксируются методом Записать(). Мы также добавили использование транзакции и обработку исключений для большей надежности.

Важные моменты и лучшие практики

  1. Различие метаданных и данных: Всегда помните, что Метаданные – это структура, а Объект документа – это конкретный экземпляр данных. Для изменения данных всегда нужен Объект.
  2. Производительность: При массовых изменениях большого количества документов или строк производительность может стать проблемой.
    • Использование запросов для предварительного отбора документов, которые *точно* содержат нужный реквизит или соответствуют другим критериям, может существенно снизить объем обрабатываемых данных.
    • Избегайте многократного вызова ПолучитьОбъект() и Записать() для одного и того же документа, если это не требуется логикой.
    • Для очень больших объемов данных рассмотрите возможность использования прямых запросов или временных таблиц, но это уже более продвинутый уровень работы.
  3. Транзакции: Массовые изменения рекомендуется выполнять в транзакции (НачатьТранзакцию(), ЗафиксироватьТранзакцию(), ОтменитьТранзакцию()). Это обеспечивает атомарность операции: либо все изменения сохраняются, либо ни одно из них. Это критически важно для сохранения целостности данных.
  4. Обработка ошибок: Используйте конструкцию Попытка...Исключение...КонецПопытки для перехвата возможных ошибок, особенно при работе с внешними данными или при выполнении операций, которые могут завершиться неудачно (например, из-за блокировок или нарушений контроля данных).
  5. Проверка существования реквизита: Как мы выяснили, проверка наличия реквизита перед обращением к нему (СтрокаТЧ.Свойства().Содержит(ИмяРеквизита) или проверка по метаданным) является хорошей практикой, чтобы избежать ошибок "Поле объекта не обнаружено".
  6. Тип реквизита: При работе с реквизитами ссылочного типа, как в нашем примере ("Договор"), убедитесь, что присваиваемое значение имеет подходящий тип. Платформа 1С строго следит за соответствием типов. Проверка МетаданныеРеквизита.Тип.СодержитТип(Тип("СправочникСсылка.Договоры")) поможет нам удостовериться, что мы работаем именно с тем типом реквизита, который ожидаем.
  7. Блокировки данных: При работе с документами, которые могут быть активно используются другими пользователями, учитывайте возможность блокировок. При получении объекта документа он может быть заблокирован. Если документ уже открыт или редактируется, метод ПолучитьОбъект() может вызвать ошибку. В некоторых сценариях может потребоваться программно управлять блокировками.

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

← На главную