При разработке печатных форм в конфигурациях на платформе 1С:Предприятие начинающие программисты часто сталкиваются с техническими ошибками сопоставления данных и снижением производительности системы. В рамках данной статьи мы подробно разберем, почему возникает популярная ошибка Поле объекта не обнаружено при попытке получить документ-основание из табличной части, проанализируем критическую ошибку проектирования под названием «Запрос в цикле», и найдем оптимальные пути решения этой задачи.
Рассмотрим ситуацию, когда при проведении или печати документа «Отчет производства за смену» (или аналогичного выпускающего документа) система аварийно завершает работу с ошибкой:
{Документ.ОтчетПроизводстваЗаСмену.МодульОбъекта(176)}: Поле объекта не обнаружено (ТЧ_П_ДокументОснование)
Проанализируем структуру данных. Эта ошибка возникает в строке кода, где мы пытаемся передать параметр в запрос:
ЗапросДокОснование.УстановитьПараметр("КлючСтроки2", ВыборкаСтрок.ТЧ_П_ДокументОснование);
Выясним, почему платформа выдает этот сбой. Переменная ВыборкаСтрок представляет собой текущую строку обхода коллекции СтрокаПродукция (табличной части «Продукция» документа выпуска). Реквизит ТЧ_П_ДокументОснование (ссылка на «Сложный заказ» или расчет заказа) физически присутствует в табличной части «Товары» документа ЗаказПокупателя, но его нет в табличной части «Продукция» текущего документа «Отчет производства за смену».
Когда мы пытаемся напрямую обратиться к реквизиту ВыборкаСтрок.ТЧ_П_ДокументОснование, программа ищет его в строке выпуска и, не найдя, закономерно падает. Чтобы решить эту проблему, нам нужно получать этот реквизит из первоисточника — из табличной части заказа покупателя, а не из строки продукции.
Обратите внимание на то, как устроен исходный алгоритм печати. Внутри цикла Для Каждого ВыборкаСтрок Из СтрокаПродукция Цикл на каждом шаге инициализируется новый объект Запрос, устанавливаются параметры и выполняется обращение к базе данных:
Для Каждого ВыборкаСтрок Из СтрокаПродукция Цикл
...
ЗапросДокОснование = Новый Запрос;
ЗапросДокОснование.Текст = "ВЫБРАТЬ ... ГДЕ ... = &КлючСтроки";
...
Результат = ЗапросДокОснование.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
...
КонецЦикла;
...
КонецЦикла;
Выполнение запросов СУБД внутри цикла — это один из самых опасных антипаттернов в разработке на 1С. Если документ содержит 100 или 1000 строк продукции, система выполнит ровно столько же отдельных обращений к СУБД. Это приводит к колоссальным задержкам, блокировкам таблиц и зависанию программы при банальном нажатии кнопки «Печать».
Правильное решение: всегда извлекайте все необходимые данные одним общим запросом перед началом цикла. В запросе соединяйте данные выпускаемой продукции с данными заказов-покупателей, а в цикле просто обходите готовый и быстродействующий результат выборки.
Дополнительная проблема кроется в ситуации, когда в одном заказе покупателя присутствуют две разные строки с абсолютно одинаковой номенклатурой и одинаковой характеристикой, но разными документами-основаниями (например, разными сложными заказами).
Когда мы связываем данные только по условию:
ЗаказПокупателяТовары.Ссылка = &Ссылка И ЗаказПокупателяТовары.ХарактеристикаНоменклатуры = &КлючСтроки
Запрос возвращает нам обе строки. Конструкция обхода выборки в цикле:
Пока Выборка.Следующий() Цикл
ТЧ_П_ДокументОснование = Выборка.ТЧ_П_ДокументОснование;
НомерПТО = выборка.НомерПТО;
КонецЦикла;
приводит к тому, что переменным каждый раз присваивается значение из последней найденной строки. В результате для всех одинаковых позиций номенклатуры печатная форма выводит один и тот же (последний по списку) заказ, что является грубой ошибкой учета.
Рассмотрим три надежных варианта решения этой проблемы.
Проанализируем, как переписать алгоритм, полностью исключив запросы в цикле. Мы соберем всю информацию заранее. Для этого свяжем таблицу продукции выпускающего документа с таблицей товаров заказа покупателя.
Посмотрим на пример правильного построения запроса перед циклом вывода печатной формы:
// 1. Создаем один общий запрос перед выводом областей макета
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВыпускПродукции.Номенклатура КАК Номенклатура,
| ВыпускПродукции.ХарактеристикаНоменклатуры КАК Характеристика,
| ВыпускПродукции.ЗаказПокупателя КАК Заказ,
| ВыпускПродукции.Количество КАК Количество,
| ВыпускПродукции.КоличествоМест КАК КоличествоМест,
| ВыпускПродукции.МассаБрутто КАК МассаБрутто,
| ВыпускПродукции.Сумма КАК Сумма,
| ВыпускПродукции.Номенклатура.Наименование КАК ТоварНаименование,
| ЕСТЬNULL(ЗаказТовары.ТЧ_П_ДокументОснование, ЗНАЧЕНИЕ(Документ.П_СложныйЗаказ.ПустаяСсылка)) КАК ТЧ_П_ДокументОснование,
| ЕСТЬNULL(ЗаказТовары.ТЧ_П_ДокументОснование.НомерПТО, """") КАК НомерПТО
|ИЗ
| Документ.ОтчетПроизводстваЗаСмену.Продукция КАК ВыпускПродукции
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя.Товары КАК ЗаказТовары
| ПО ВыпускПродукции.ЗаказПокупателя = ЗаказТовары.Ссылка
| И ВыпускПродукции.Номенклатура = ЗаказТовары.Номенклатура
| И ВыпускПродукции.ХарактеристикаНоменклатуры = ЗаказТовары.ХарактеристикаНоменклатуры
|ГДЕ
| ВыпускПродукции.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект); // Ссылка на текущий документ Отчет производства за смену
РезультатЗапроса = Запрос.Выполнить();
ВыборкаСтрок = РезультатЗапроса.Выбрать();
Ном = 0;
КоличествоСтрок = ВыборкаСтрок.Количество();
// 2. Обходим готовую выборку без повторных обращений к СУБД
Пока ВыборкаСтрок.Следующий() Цикл
// Проверка заполнения номенклатуры
Если НЕ ЗначениеЗаполнено(ВыборкаСтрок.Номенклатура) Тогда
ОбщегоНазначения.Сообщение("В одной из строк не заполнено значение номенклатуры - строка при печати пропущена.", СтатусСообщения.Важное);
Продолжить;
КонецЕсли;
Ном = Ном + 1;
// Код проверки вывода страниц и управления макетом...
// ...
// Заполняем параметры напрямую из выборки запроса
ОбластьМакета.Параметры.Заполнить(ВыборкаСтрок);
ТЧ_П_ДокументОснование = ВыборкаСтрок.ТЧ_П_ДокументОснование;
НомерПТО = ВыборкаСтрок.НомерПТО;
Если НЕ ЗначениеЗаполнено(ТЧ_П_ДокументОснование) Тогда
Сообщить("Не найдена ссылка на Расчет заказа в Заказе покупателя!");
КонецЕсли;
// Формируем наименование с учетом Сложного заказа и номера ПТО
Если НЕ ЗначениеЗаполнено(НомерПТО) Тогда
ОбластьМакета.Параметры.ТоварНаименование = ?(ТипЗнч(ВыборкаСтрок.Заказ) = Тип("ДокументСсылка.ЗаказПокупателя"), "Заказ № " + ?(ЗначениеЗаполнено(ТЧ_П_ДокументОснование), ОбщегоНазначения.ПолучитьНомерНаПечать(ТЧ_П_ДокументОснование), "") + " ", "") + СокрЛП(ВыборкаСтрок.ТоварНаименование);
Иначе
ОбластьМакета.Параметры.ТоварНаименование = ?(ТипЗнч(ВыборкаСтрок.Заказ) = Тип("ДокументСсылка.ЗаказПокупателя"), "Заказ № " + ?(ЗначениеЗаполнено(ТЧ_П_ДокументОснование), ОбщегоНазначения.ПолучитьНомерНаПечать(ТЧ_П_ДокументОснование), "") + " (" + Строка(НомерПТО) + ") ", "") + СокрЛП(ВыборкаСтрок.ТоварНаименование);
КонецЕсли;
// Выводим строку в табличный документ
ТабДокумент.Вывести(ОбластьМакета);
// Расчет итогов...
КонецЦикла;
Такой подход полностью устраняет ошибку ненайденного поля объекта, так как мы не обращаемся к отсутствующим реквизитам внутри ВыборкаСтрок, а заранее получаем их через связь таблиц на уровне СУБД.
Если в вашей конфигурации (например, УПП 1.3 или ERP) предусмотрены дублирующиеся строки, для их однозначного сопоставления используется реквизит связи строк. Как правило, это числовое поле КлючСтроки или КлючСвязи, которое одинаково и в заказе покупателя, и в документе выпуска.
Проанализируем, как в этом случае меняется условие соединения таблиц в нашем общем запросе:
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя.Товары КАК ЗаказТовары
| ПО ВыпускПродукции.ЗаказПокупателя = ЗаказТовары.Ссылка
| И ВыпускПродукции.КлючСвязи = ЗаказТовары.КлючСвязи
Использование уникального ключа связи гарантирует 100% точность сопоставления строк печатной формы с исходными строками заказа покупателя, даже если у них совпадают номенклатура, характеристики, серии и количество.
Наиболее надежный способ с точки зрения бизнес-логики предприятия — это добавление реквизита ДокументОснование (или ссылки на сложный заказ) непосредственно в табличную часть «Продукция» документа «Отчет производства за смену».
Рассмотрим преимущества этого метода:
Для реализации этого метода добавьте реквизит ТЧ_П_ДокументОснование в табличную часть «Продукция» нужного документа через конфигуратор, настройте его заполнение при вводе на основании, после чего вы сможете обращаться к нему напрямую через ВыборкаСтрок.ТЧ_П_ДокументОснование без каких-либо дополнительных запросов.