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

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

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

Анализ исходной проблемы

Рассмотрим ситуацию, когда в документе ЗаказПоставщику в табличной части Товары есть два реквизита: НомерИнвойса (строка) и СправочникНомерИнвойса (ссылка на справочник). Наша задача — пройтись по всем документам, найти в справочнике элемент, наименование которого совпадает с текстом в строке, и записать эту ссылку в новое поле.

Типичная ошибка начинающего разработчика заключается в попытке изменить данные напрямую в выборке запроса или неправильном обращении к реквизитам табличной части. Проанализируем, почему код может выдавать ошибку «Поле объекта недоступно для записи» или «Поле объекта не обнаружено».

  1. Выборка запроса только для чтения: Данные, полученные через Запрос.Выполнить().Выбрать(), предназначены только для чтения. Вы не можете присвоить значение полю Выборка.Наименование.
  2. Контекст объекта: Для изменения данных документа необходимо получить его объектную версию методом ПолучитьОбъект().
  3. Обращение к строкам: Табличная часть — это коллекция строк. Нельзя обратиться к полю СправочникНомерИнвойса напрямую через ДокументОбъект.Товары, необходимо перебирать строки в цикле.

Шаг 1. Получение списка документов через запрос

Первым делом нам необходимо выбрать все документы, которые требуют обработки. Использование запроса — самый эффективный способ фильтрации данных на стороне сервера базы данных. Проанализируем текст запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ЗаказПоставщику.Ссылка КАК Ссылка
    |ИЗ
    |	Документ.ЗаказПоставщику КАК ЗаказПоставщику
    |ГДЕ
    |	ЗаказПоставщику.Товары.НомерИнвойса <> """"";

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

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

Шаг 2. Обход документов и получение объекта для изменения

После того как мы получили ссылки на нужные документы, мы начинаем цикл обхода. На каждой итерации нам нужно превратить «ссылку» (которая является лишь уникальным идентификатором) в «объект» (который можно редактировать).


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

    // Записываем документ, только если были внесены изменения
    Если ЛогикаИзмененияНужна Тогда
        ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
    КонецЕсли;
КонецЦикла;

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

Шаг 3. Оптимизация производительности (Кэширование)

Рассмотрим ситуацию, когда в базе тысячи документов и тысячи строк. Метод НайтиПоНаименованию внутри вложенного цикла будет выполнять отдельный запрос к базе на каждой строке каждой ТЧ. Это классическая проблема производительности «N+1».

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


КэшИнвойсов = Новый Соответствие; // Ключ - текст, Значение - Ссылка

Пока Выборка.Следующий() Цикл
    ДокументОбъект = Выборка.Ссылка.ПолучитьОбъект();
    Изменено = Ложь;

    Для Каждого Стр Из ДокументОбъект.Товары Цикл
        Если ПустаяСтрока(Стр.НомерИнвойса) Тогда Продолжить; КонецЕсли;

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

        Если Стр.СправочникНомерИнвойса <> НайденаяСсылка Тогда
            Стр.СправочникНомерИнвойса = НайденаяСсылка;
            Изменено = Истина;
        КонецЕсли;
    КонецЦикла;

    Если Изменено Тогда
        // Используем ОбменДанными.Загрузка, чтобы не срабатывали лишние подписки и проверки
        ДокументОбъект.ОбменДанными.Загрузка = Истина;
        ДокументОбъект.Записать();
    КонецЕсли;
КонецЦикла;

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

Важные нюансы и рекомендации

При выполнении подобных групповых обработок стоит учитывать следующие технические аспекты:

  1. Режим записи: Если документ уже проведен, использование РежимЗаписиДокумента.Запись сохранит данные, но может сделать документ «неактуальным» в некоторых конфигурациях (появится иконка, что документ изменен, но не перепроведен). Однако РежимЗаписиДокумента.Проведение может изменить движения по регистрам, что крайне опасно в закрытых периодах. Поэтому чаще всего технические реквизиты заполняются в режиме простого сохранения с флагом Загрузка = Истина.
  2. Блокировки: Если в базе активно работают пользователи, ПолучитьОбъект() может вызвать исключение, если документ заблокирован другим пользователем. В промышленном коде рекомендуется оборачивать получение объекта в блок Попытка...Исключение.
  3. Создание новых элементов: Если логика подразумевает, что справочник может быть не заполнен, в код можно добавить создание нового элемента справочника через Справочники.СпрНомерИнвойса.СоздатьЭлемент() в случае, если НайтиПоНаименованию вернуло пустую ссылку.
  4. Параметры запроса: Если вы запускаете обработку на рабочей базе, всегда добавляйте фильтрацию по дате или конкретным организациям в запрос, чтобы контролировать объем обрабатываемых данных.

Используя предложенные методы, вы сможете безопасно и быстро актуализировать данные в табличных частях любых документов, избегая распространенных ошибок доступа к объектам 1С.

← На главную