При доработке внешних печатных форм в конфигурациях 1С на обычных формах (таких как «Управление торговлей 10.3», «Управление производственным предприятием 1.3» или «Комплексная автоматизация 1.1») разработчики часто сталкиваются с двумя типичными проблемами. Первая — это возникновение ошибки «Поле объекта не обнаружено» при обращении к полям выборки запроса. Вторая — добавленная колонка физически не появляется в распечатанном документе, хотя в макете она присутствует.
В этой статье мы подробно разберем, почему возникают эти ошибки, как правильно структурировать запросы с вложенными табличными частями, и детально изучим механизм вывода вертикальных секций макета в табличный документ.
Рассмотрим структуру исходного запроса, который вызвал ошибку у автора темы. В конфигурациях эпохи 1С 8.0–8.2 часто использовались так называемые запросы со вложенными табличными частями. В таких запросах данные шапки документа и его табличных частей выбираются одновременно. Выборка табличной части оформляется в круглых скобках как отдельное поле.
Проанализируем фрагмент ошибочного запроса:
ВЫБРАТЬ
ПеремещениеТоваров.Номер,
ПеремещениеТоваров.Дата,
...
ПеремещениеТоваров.Товары.(
Номенклатура,
Номенклатура.Наименование КАК Товар,
Количество
),
МестаХраненияНоменклатуры.МестоХранения // Поле находится ВНЕ скобок табличной части!
ИЗ
Документ.ПеремещениеТоваров КАК ПеремещениеТоваров
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.МестаХраненияНоменклатуры КАК МестаХраненияНоменклатуры
ПО ...
При такой структуре поле МестоХранения попадает в выборку верхнего уровня (в выборку шапки документа). Когда мы начинаем обходить строки табличной части с помощью детального цикла Пока ВыборкаСтрокТовары.Следующий() Цикл, программа закономерно выдает ошибку «Поле объекта не обнаружено (МестоХранения)». Платформа ищет это поле внутри вложенной выборки ВыборкаСтрокТовары, но физически оно находится на уровень выше — в объекте Шапка.
Чтобы исправить эту ошибку, поле МестоХранения необходимо перенести внутрь скобок табличной части Товары.(...). Тогда оно станет частью вложенной таблицы и будет доступно при обходе строк товаров.
Правильный вариант структуры запроса с вложенной таблицей выглядит так:
ВЫБРАТЬ
ПеремещениеТоваров.Номер,
ПеремещениеТоваров.Дата,
ПеремещениеТоваров.Товары.(
Номенклатура,
Номенклатура.Наименование КАК Товар,
Количество,
// Помещаем поле внутрь скобок ТЧ Товары
МестаХраненияНоменклатуры.МестоХранения КАК МестоХранения
)
ИЗ
Документ.ПеремещениеТоваров КАК ПеремещениеТоваров
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.МестаХраненияНоменклатуры КАК МестаХраненияНоменклатуры
ПО ПеремещениеТоваров.Товары.Номенклатура = МестаХраненияНоменклатуры.Номенклатура
И ПеремещениеТоваров.СкладОтправитель = МестаХраненияНоменклатуры.Склад
Внимательно проанализируем связь с регистром сведений МестаХраненияНоменклатуры. Этот регистр в типовой конфигурации УТ 10.3 хранит информацию о расположении товаров на складах. На одном складе для одного товара может быть задано несколько мест хранения (например, основная ячейка, зона приемки, витрина). Каждое место имеет свой приоритет.
Если связать табличную часть документа напрямую с регистром сведений через ЛЕВОЕ СОЕДИНЕНИЕ, и для какого-то товара на складе окажется записано два или три места хранения, СУБД вернет несколько строк для одной строки документа! В результате в печатной форме этот товар сдублируется, а итоговые количества и суммы накладной увеличатся, что приведет к грубой ошибке в учете.
Чтобы избежать дублирования строк, необходимо соединяться только с одним (приоритетным) местом хранения. Для этого в запросе лучше использовать подзапрос с ограничением по максимальному или минимальному приоритету. Посмотрим на пример безопасного соединения:
ЛЕВОЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
Места.Номенклатура КАК Номенклатура,
Места.Склад КАК Склад,
МИНИМУМ(Места.МестоХранения) КАК МестоХранения // Или отбор по приоритету
ИЗ
РегистрСведений.МестаХраненияНоменклатуры КАК Места
СГРУППИРОВАТЬ ПО
Места.Номенклатура,
Места.Склад
) КАК МестаХраненияОптимальные
ПО ПеремещениеТоваровТовары.Номенклатура = МестаХраненияОптимальные.Номенклатура
И ПеремещениеТоваров.СкладОтправитель = МестаХраненияОптимальные.Склад
В коде автора печатной формы в цикле обработки возвратной тары присутствует следующая строка:
// Выборка возвратная тара
Пока ВыборкаСтрокТара.Следующий() Цикл
...
// ОШИБКА: обращение к выборке товаров вместо выборки тары!
ПараметрыПозиции.Вставить("МестоХранения", ВыборкаСтрокТовары.МестоХранения);
...
КонецЦикла;
При обходе выборки тары (ВыборкаСтрокТара) разработчик ошибочно обращается к объекту ВыборкаСтрокТовары. К моменту начала этого цикла выборка товаров уже полностью завершена, ее указатель находится в самом конце, из-за чего этот код либо вызовет очередную ошибку «Поле объекта не обнаружено», либо во все строки тары запишет место хранения последнего товара. В цикле тары нужно обращаться исключительно к полям ВыборкаСтрокТара.
Выясним причину, по которой колонка, добавленная в макет и заполняемая в коде, вообще не выводится на печать.
Дело в том, что печатные формы во многих типовых решениях строятся динамически с использованием **вертикальных областей**. Программа не просто выводит строки целиком, а собирает их «по кирпичикам» слева направо. Это необходимо для того, чтобы гибко скрывать или отображать определенные колонки (например, колонку артикула или колонку цены в зависимости от настроек пользователя).
Посмотрим на алгоритм вывода строки в исходном коде:
ТабДокумент.Вывести(ОбластьНомера).ТабДокумент.Присоединить(ОбластьКодов).ТабДокумент.Присоединить(ОбластьТовар).ТабДокумент.Присоединить(ОбластьМест).ТабДокумент.Присоединить(ОбластьДанных).В коде автора область места хранения инициализируется:
ОбластьМестоХранения = Макет.ПолучитьОбласть(ИмяСтроки + "|МестоХранения");
Но далее в коде полностью отсутствует команда на её вывод! Метод Присоединить() для этой области не вызывается, поэтому колонка просто игнорируется платформой при формировании итогового документа.
Рассмотрим подробнее процесс правильного изменения кода внешней печатной формы. Нам потребуется внести изменения в запрос, исправить логику обхода выборок и добавить вывод вертикальной области в трех секциях: в шапке таблицы, в строках товаров и в подвале таблицы.
Перепишем функцию получения данных, исправив структуру запроса. Для надежности избавимся от устаревших вложенных таблиц и перейдем к современному и более быстрому «плоскому» запросу по табличной части документа. Это гарантирует отсутствие дублей и значительно упростит чтение кода.
Функция ПолучитьДанныеДляПечатиПеремещенияТоваров()
ПараметрыПечати = Новый Структура;
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТекущийДокумент", СсылкаНаОбъект);
Запрос.Текст =
"ВЫБРАТЬ
| ПеремещениеТоваров.Номер КАК Номер,
| ПеремещениеТоваров.Дата КАК Дата,
| ПеремещениеТоваров.Организация КАК Организация,
| ПеремещениеТоваров.СкладОтправитель КАК СкладОтправитель,
| ПеремещениеТоваров.СкладОтправитель.Представление КАК Поставщик,
| ПеремещениеТоваров.СкладПолучатель КАК СкладПолучатель,
| ПеремещениеТоваров.СкладПолучатель.Представление КАК Покупатель,
| ПеремещениеТоваровТовары.Номенклатура КАК Номенклатура,
| ПеремещениеТоваровТовары.Номенклатура.Наименование КАК Товар,
| ПеремещениеТоваровТовары.Номенклатура.Код КАК Код,
| ПеремещениеТоваровТовары.Номенклатура.Артикул КАК Артикул,
| ПеремещениеТоваровТовары.КоличествоМест КАК КоличествоМест,
| ПеремещениеТоваровТовары.Количество КАК Количество,
| ПеремещениеТоваровТовары.ЕдиницаИзмерения.Представление КАК ЕдиницаИзмерения,
| ЕСТЬNULL(ПеремещениеТоваровТовары.ЕдиницаИзмерения.Вес, 0) * ПеремещениеТоваровТовары.Количество КАК Вес,
| ПеремещениеТоваровТовары.ЕдиницаИзмеренияМест.Представление КАК ЕдиницаИзмеренияМест,
| ПеремещениеТоваровТовары.Цена КАК Цена,
| ПеремещениеТоваровТовары.ХарактеристикаНоменклатуры КАК Характеристика,
| ПеремещениеТоваровТовары.СерияНоменклатуры КАК Серия,
| ЕСТЬNULL(ПеремещениеТоваровТовары.ЕдиницаИзмерения.Объем, 0) * ПеремещениеТоваровТовары.Количество КАК Объем,
| ЕСТЬNULL(МестаХранения.МестоХранения, """") КАК МестоХранения
|ИЗ
| Документ.ПеремещениеТоваров.Товары КАК ПеремещениеТоваровТовары
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров КАК ПеремещениеТоваров
| ПО ПеремещениеТоваровТовары.Ссылка = ПеремещениеТоваров.Ссылка
| ЛЕВОЕ СОЕДИНЕНИЕ (
| ВЫБРАТЬ
| РегМеста.Номенклатура КАК Номенклатура,
| РегМеста.Склад КАК Склад,
| МИНИМУМ(РегМеста.МестоХранения) КАК МестоХранения
| ИЗ
| РегистрСведений.МестаХраненияНоменклатуры КАК РегМеста
| СГРУППИРОВАТЬ ПО
| РегМеста.Номенклатура,
| РегМеста.Склад
| ) КАК МестаХранения
| ПО ПеремещениеТоваровТовары.Номенклатура = МестаХранения.Номенклатура
| И ПеремещениеТоваров.СкладОтправитель = МестаХранения.Склад
|ГДЕ
| ПеремещениеТоваров.Ссылка = &ТекущийДокумент
|УПОРЯДОЧИТЬ ПО
| ПеремещениеТоваровТовары.НомерСтроки";
// Для упрощения примера мы опустили запрос по возвратной таре.
// При необходимости он объединяется через ОБЪЕДИНИТЬ ВСЕ с аналогичными полями.
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
Возврат ТаблицаДанных;
КонецФункции
Теперь разберем по шагам, как правильно изменить основную экспортную функцию Печать. Нам нужно получить области из макета и последовательно вывести их в результирующий табличный документ с помощью метода Присоединить().
Ниже представлен детальный и исправленный вариант кода функции печати:
Функция Печать() Экспорт
ТаблицаДанных = ПолучитьДанныеДляПечатиПеремещенияТоваров();
ТабДокумент = Новый ТабличныйДокумент;
ТабДокумент.ИмяПараметровПечати = "ПАРАМЕТРЫ_ПЕЧАТИ_ПеремещениеТоваров_Накладная";
Макет = ПолучитьМакет("Накладная");
// 1. Выводим шапку накладной
ОбластьМакета = Макет.ПолучитьОбласть("Заголовок");
// Заполнение параметров заголовка...
ТабДокумент.Вывести(ОбластьМакета);
ОбластьМакета = Макет.ПолучитьОбласть("Поставщик");
// Заполнение параметров отправителя...
ТабДокумент.Вывести(ОбластьМакета);
ОбластьМакета = Макет.ПолучитьОбласть("Покупатель");
// Заполнение параметров получателя...
ТабДокумент.Вывести(ОбластьМакета);
// Определение имен секций макета
ВыводитьЦены = Истина; // Или флаг из настроек
Если ВыводитьЦены Тогда
ИмяШапки = "ШапкаТаблицыСЦенами";
ИмяСтроки = "СтрокаСЦенами";
Иначе
ИмяШапки = "ШапкаТаблицы";
ИмяСтроки = "Строка";
EndIf;
// 2. Получаем области ШАПКИ таблицы
ОбластьНомера = Макет.ПолучитьОбласть(ИмяШапки + "|НомерСтроки");
ОбластьКодов = Макет.ПолучитьОбласть(ИмяШапки + "|КолонкаКодов");
ОбластьТовар = Макет.ПолучитьОбласть(ИмяШапки + "|Товар");
// Добавляем инициализацию нашей новой области в шапке
ОбластьМестоХранения = Макет.ПолучитьОбласть(ИмяШапки + "|МестоХранения");
ОбластьМест = ...
ОбластьДанных = Макет.ПолучитьОбласть(ИмяШапки + "|Данные");
// Выводим шапку таблицы в документ
ТабДокумент.Вывести(ОбластьНомера);
ВыводитьКоды = Истина; // Определяется константой
Если ВыводитьКоды Тогда
ТабДокумент.Присоединить(ОбластьКодов);
КонецЕсли;
ТабДокумент.Присоединить(ОбластьТовар);
// ВАЖНО: Присоединяем шапку колонки места хранения сразу после товара!
ТабДокумент.Присоединить(ОбластьМестоХранения);
ФлагПечатиМест = Ложь;
Если ФлагПечатиМест Тогда
ТабДокумент.Присоединить(ОбластьМест);
КонецЕсли;
ТабДокумент.Присоединить(ОбластьДанных);
// 3. Получаем области СТРОКИ таблицы
ОбластьНомера = Макет.ПолучитьОбласть(ИмяСтроки + "|НомерСтроки");
ОбластьКодов = ...
ОбластьТовар = Макет.ПолучитьОбласть(ИмяСтроки + "|Товар");
// Инициализируем область строки для места хранения
ОбластьМестоХранения = Макет.ПолучитьОбласть(ИмяСтроки + "|МестоХранения");
ОбластьДанных = Макет.ПолучитьОбласть(ИмяСтроки + "|Данные");
// Обход строк товаров
Ном = 0;
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
Ном = Ном + 1;
// Заполняем параметры номеров и кодов
ОбластьНомера.Параметры.НомерСтроки = Ном;
ТабДокумент.Вывести(ОбластьНомера);
Если ВыводитьКоды Тогда
ОбластьКодов.Параметры.Заполнить(СтрокаДанных);
ТабДокумент.Присоединить(ОбластьКодов);
КонецЕсли;
ОбластьТовар.Параметры.Заполнить(СтрокаДанных);
ТабДокумент.Присоединить(ОбластьТовар);
// ВАЖНО: Заполняем значение места хранения и присоединяем строку ячейки!
ОбластьМестоХранения.Параметры.МестоХранения = СтрокаДанных.МестоХранения;
ТабДокумент.Присоединить(ОбластьМестоХранения);
ОбластьДанных.Параметры.Заполнить(СтрокаДанных);
ТабДокумент.Присоединить(ОбластьДанных);
КонецЦикла;
// 4. Получаем области ПОДВАЛА таблицы
ОбластьНомера = Макет.ПолучитьОбласть("Подвал|НомерСтроки");
ОбластьКодов = Макет.ПолучитьОбласть("Подвал|КолонкаКодов");
ОбластьТовар = Макет.ПолучитьОбласть("Подвал|Товар");
// Инициализируем область подвала для места хранения
// Внимание: берем область из секции "Подвал", а не "Строка"!
ОбластьМестоХранения = Макет.ПолучитьОбласть("Подвал|МестоХранения");
ОбластьДанных = Макет.ПолучитьОбласть("Подвал|Данные");
// Выводим подвал в документ
ТабДокумент.Вывести(ОбластьНомера);
Если ВыводитьКоды Тогда
ТабДокумент.Присоединить(ОбластьКодов);
КонецЕсли;
ТабДокумент.Присоединить(ОбластьТовар);
// ВАЖНО: Присоединяем пустую ячейку подвала под колонкой места хранения
ТабДокумент.Присоединить(ОбластьМестоХранения);
// Заполнение и вывод итогов
ОбластьДанных.Параметры.ИтогоВес = ...
ТабДокумент.Присоединить(ОбластьДанных);
// Вывод подписей...
Возврат ТабДокумент;
КонецФункции
Для успешной доработки сложных печатных форм в среде 1С необходимо четко контролировать соответствие структуры запроса логике обхода выборок в коде. Ошибка «Поле объекта не обнаружено» практически всегда сигнализирует о том, что нужное поле находится в другой выборке или на другом уровне иерархии результата запроса. При выводе новых колонок обязательно следите за тем, чтобы все созданные области макета своевременно присоединялись методом ТабДокумент.Присоединить().