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

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

В процессе разработки часто возникает задача массового изменения данных в связанных объектах. Типичный пример: в основном документе изменился Контрагент, и необходимо автоматически обновить это значение во всех связанных документах, которые указаны в табличной части (цепочке документов). При этом мы не всегда заранее знаем, в каких именно реквизитах (Покупатель, Поставщик, Грузоотправитель) хранится ссылка на этого контрагента.

В этой статье мы подробно разберем, как реализовать универсальный механизм поиска и замены ссылок, используя возможности метаданных платформы 1С:Предприятие 8.3.

Постановка задачи и анализ ситуации

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

Проанализируем основные этапы решения задачи:

  1. Получение ссылки на "старого" контрагента (до момента его изменения).
  2. Перебор связанных документов.
  3. Универсальный обход всех реквизитов каждого документа.
  4. Обработка табличных частей.
  5. Запись измененных объектов с соблюдением целостности данных.

Решение через перебор метаданных

Самый гибкий способ — это использование коллекции Метаданные. Рассмотрим программный код, который позволяет перебрать все реквизиты объекта, проверить их тип и значение.

Сначала подготовим данные на сервере. Нам нужно получить старое значение контрагента из базы данных, так как в объекте на форме оно уже изменено пользователем.


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

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

КонецПроцедуры

Разбор ключевых механизмов

Рассмотрим подробнее, какие инструменты 1С мы использовали в данном коде:

Важные предостережения и оптимизация

Проанализируем ситуацию с точки зрения производительности и целостности данных. Метод "ковровой бомбардировки" (замены во всех полях) имеет свои риски.

1. Связанные реквизиты (Договоры)

Замена контрагента — это только половина дела. В большинстве документов есть реквизит Договор, который подчинен контрагенту. Если вы замените контрагента "А" на "Б", но оставите договор от контрагента "А", документ станет логически неверным и может не провестись. Рекомендуем после замены контрагента очищать поле договора или программно подбирать подходящий договор для нового контрагента.

2. Использование метода НайтиПоСсылке

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

3. Производительность

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

4. Транзакции

Рассмотрим использование транзакций. Если при записи десятого документа в цепочке произойдет ошибка (например, из-за закрытого периода), первые девять уже будут изменены. Чтобы избежать "рассинхрона", лучше оборачивать весь процесс в конструкцию НачатьТранзакцию() — ЗафиксироватьТранзакцию().

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

← На главную