Как исправить ошибку «Поле объекта не обнаружено (ТЧ_П_ДокументОснование)» и оптимизировать запрос в цикле при печати в 1С

Программист 1С v8.3 (Обычные формы) Управленческий учет Промышленность, строительство и АПК
← На главную

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

Выясняем причину ошибки «Поле объекта не обнаружено»

Рассмотрим ситуацию, когда при проведении или печати документа «Отчет производства за смену» (или аналогичного выпускающего документа) система аварийно завершает работу с ошибкой:

{Документ.ОтчетПроизводстваЗаСмену.МодульОбъекта(176)}: Поле объекта не обнаружено (ТЧ_П_ДокументОснование)

Проанализируем структуру данных. Эта ошибка возникает в строке кода, где мы пытаемся передать параметр в запрос:


ЗапросДокОснование.УстановитьПараметр("КлючСтроки2", ВыборкаСтрок.ТЧ_П_ДокументОснование);

Выясним, почему платформа выдает этот сбой. Переменная ВыборкаСтрок представляет собой текущую строку обхода коллекции СтрокаПродукция (табличной части «Продукция» документа выпуска). Реквизит ТЧ_П_ДокументОснование (ссылка на «Сложный заказ» или расчет заказа) физически присутствует в табличной части «Товары» документа ЗаказПокупателя, но его нет в табличной части «Продукция» текущего документа «Отчет производства за смену».

Когда мы пытаемся напрямую обратиться к реквизиту ВыборкаСтрок.ТЧ_П_ДокументОснование, программа ищет его в строке выпуска и, не найдя, закономерно падает. Чтобы решить эту проблему, нам нужно получать этот реквизит из первоисточника — из табличной части заказа покупателя, а не из строки продукции.

Анализируем архитектурную ошибку: «Запрос в цикле» (Anti-pattern)

Обратите внимание на то, как устроен исходный алгоритм печати. Внутри цикла Для Каждого ВыборкаСтрок Из СтрокаПродукция Цикл на каждом шаге инициализируется новый объект Запрос, устанавливаются параметры и выполняется обращение к базе данных:


Для Каждого ВыборкаСтрок Из СтрокаПродукция Цикл
    ...
    ЗапросДокОснование = Новый Запрос;
    ЗапросДокОснование.Текст = "ВЫБРАТЬ ... ГДЕ ... = &КлючСтроки";
    ...
    Результат = ЗапросДокОснование.Выполнить();
    Выборка = Результат.Выбрать();
    Пока Выборка.Следующий() Цикл
        ...
    КонецЦикла;
    ...
КонецЦикла;

Выполнение запросов СУБД внутри цикла — это один из самых опасных антипаттернов в разработке на 1С. Если документ содержит 100 или 1000 строк продукции, система выполнит ровно столько же отдельных обращений к СУБД. Это приводит к колоссальным задержкам, блокировкам таблиц и зависанию программы при банальном нажатии кнопки «Печать».

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

Разбираем проблему сопоставления дублирующихся строк

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

Когда мы связываем данные только по условию:

ЗаказПокупателяТовары.Ссылка = &Ссылка И ЗаказПокупателяТовары.ХарактеристикаНоменклатуры = &КлючСтроки

Запрос возвращает нам обе строки. Конструкция обхода выборки в цикле:


Пока Выборка.Следующий() Цикл    
    ТЧ_П_ДокументОснование = Выборка.ТЧ_П_ДокументОснование;
    НомерПТО = выборка.НомерПТО;
КонецЦикла;

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

Рассмотрим три надежных варианта решения этой проблемы.

Вариант 1. Оптимальный (связывание через один запрос с Левым Соединением)

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

Посмотрим на пример правильного построения запроса перед циклом вывода печатной формы:


// 1. Создаем один общий запрос перед выводом областей макета
Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	ВыпускПродукции.Номенклатура КАК Номенклатура,
|	ВыпускПродукции.ХарактеристикаНоменклатуры КАК Характеристика,
|	ВыпускПродукции.ЗаказПокупателя КАК Заказ,
|	ВыпускПродукции.Количество КАК Количество,
|	ВыпускПродукции.КоличествоМест КАК КоличествоМест,
|	ВыпускПродукции.МассаБрутто КАК МассаБрутто,
|	ВыпускПродукции.Сумма КАК Сумма,
|	ВыпускПродукции.Номенклатура.Наименование КАК ТоварНаименование,
|	ЕСТЬNULL(ЗаказТовары.ТЧ_П_ДокументОснование, ЗНАЧЕНИЕ(Документ.П_СложныйЗаказ.ПустаяСсылка)) КАК ТЧ_П_ДокументОснование,
|	ЕСТЬNULL(ЗаказТовары.ТЧ_П_ДокументОснование.НомерПТО, """") КАК НомерПТО
|ИЗ
|	Документ.ОтчетПроизводстваЗаСмену.Продукция КАК ВыпускПродукции
|	ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя.Товары КАК ЗаказТовары
|		ПО ВыпускПродукции.ЗаказПокупателя = ЗаказТовары.Ссылка
|		И ВыпускПродукции.Номенклатура = ЗаказТовары.Номенклатура
|		И ВыпускПродукции.ХарактеристикаНоменклатуры = ЗаказТовары.ХарактеристикаНоменклатуры
|ГДЕ
|	ВыпускПродукции.Ссылка = &Ссылка";

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

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

Ном = 0;
КоличествоСтрок = ВыборкаСтрок.Количество();

// 2. Обходим готовую выборку без повторных обращений к СУБД
Пока ВыборкаСтрок.Следующий() Цикл
    
    // Проверка заполнения номенклатуры
    Если НЕ ЗначениеЗаполнено(ВыборкаСтрок.Номенклатура) Тогда
        ОбщегоНазначения.Сообщение("В одной из строк не заполнено значение номенклатуры - строка при печати пропущена.", СтатусСообщения.Важное);
        Продолжить;
    КонецЕсли;
    
    Ном = Ном + 1;
    
    // Код проверки вывода страниц и управления макетом...
    // ...
    
    // Заполняем параметры напрямую из выборки запроса
    ОбластьМакета.Параметры.Заполнить(ВыборкаСтрок);
    
    ТЧ_П_ДокументОснование = ВыборкаСтрок.ТЧ_П_ДокументОснование;
    НомерПТО = ВыборкаСтрок.НомерПТО;
    
    Если НЕ ЗначениеЗаполнено(ТЧ_П_ДокументОснование) Тогда
         Сообщить("Не найдена ссылка на Расчет заказа в Заказе покупателя!");
    КонецЕсли;
    
    // Формируем наименование с учетом Сложного заказа и номера ПТО
    Если НЕ ЗначениеЗаполнено(НомерПТО) Тогда
        ОбластьМакета.Параметры.ТоварНаименование = ?(ТипЗнч(ВыборкаСтрок.Заказ) = Тип("ДокументСсылка.ЗаказПокупателя"), "Заказ № " + ?(ЗначениеЗаполнено(ТЧ_П_ДокументОснование), ОбщегоНазначения.ПолучитьНомерНаПечать(ТЧ_П_ДокументОснование), "") + "  ", "") + СокрЛП(ВыборкаСтрок.ТоварНаименование);
    Иначе
        ОбластьМакета.Параметры.ТоварНаименование = ?(ТипЗнч(ВыборкаСтрок.Заказ) = Тип("ДокументСсылка.ЗаказПокупателя"), "Заказ № " + ?(ЗначениеЗаполнено(ТЧ_П_ДокументОснование), ОбщегоНазначения.ПолучитьНомерНаПечать(ТЧ_П_ДокументОснование), "") + " (" + Строка(НомерПТО) + ")  ", "") + СокрЛП(ВыборкаСтрок.ТоварНаименование);
    КонецЕсли;
    
    // Выводим строку в табличный документ
    ТабДокумент.Вывести(ОбластьМакета);
    
    // Расчет итогов...
КонецЦикла;

Такой подход полностью устраняет ошибку ненайденного поля объекта, так как мы не обращаемся к отсутствующим реквизитам внутри ВыборкаСтрок, а заранее получаем их через связь таблиц на уровне СУБД.

Вариант 2. Использование уникального ключа связи строк (КлючСвязи)

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

Проанализируем, как в этом случае меняется условие соединения таблиц в нашем общем запросе:


|	ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя.Товары КАК ЗаказТовары
|		ПО ВыпускПродукции.ЗаказПокупателя = ЗаказТовары.Ссылка
|		И ВыпускПродукции.КлючСвязи = ЗаказТовары.КлючСвязи

Использование уникального ключа связи гарантирует 100% точность сопоставления строк печатной формы с исходными строками заказа покупателя, даже если у них совпадают номенклатура, характеристики, серии и количество.

Вариант 3. Модификация метаданных (Добавление реквизита в выпуск)

Наиболее надежный способ с точки зрения бизнес-логики предприятия — это добавление реквизита ДокументОснование (или ссылки на сложный заказ) непосредственно в табличную часть «Продукция» документа «Отчет производства за смену».

Рассмотрим преимущества этого метода:

  1. При заполнении документа выпуска на основании Заказа покупателя, реквизиты сложных заказов автоматически копируются в строки продукции.
  2. В печатной форме нам вообще не потребуется соединять таблицы с Заказом покупателя. Все данные (включая сложные заказы) будут храниться прямо в документе «Отчет производства за смену».
  3. Это существенно упростит написание запросов для печатных форм, отчетов производства и распределения затрат.

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

← На главную