При разработке в 1С:Предприятии часто возникает задача создания универсальных функций или обработок, которые должны работать с различными типами документов. Одним из таких сценариев является формирование запроса, где источник данных (таблица документа) должен определяться динамически, исходя из переданной ссылки на документ. Например, нам может потребоваться выбрать номенклатуру из табличной части "Товары" документа, который может быть как "Поступлением товаров и услуг", так и "Перемещением товаров". В этом материале мы подробно разберем, как решить эту задачу, используя свойства метаданных и методы работы со строками.
Представим ситуацию: у нас есть ссылка на некий документ, и мы хотим сформировать запрос, который будет выбирать данные из этого документа. Классический запрос в 1С выглядит примерно так:
ВЫБРАТЬ
ДокументПоступление.Номенклатура КАК Номенклатура
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ДокументПоступление
ГДЕ
ДокументПоступление.Ссылка = &СсылкаДокумента
Проблема заключается в том, что имя таблицы Документ.ПоступлениеТоваровУслуг.Товары жестко прописано. Если нам нужно, чтобы запрос работал и для Документ.ПеремещениеТоваров.Товары, и для Документ.ПриобретениеТоваровУслуг.Товары, мы не можем просто так подставить переменную вместо этого имени. Язык запросов 1С не поддерживает прямую параметризацию имен таблиц или полей.
Автор исходного запроса на форуме предложил такую структуру:
"ВЫБРАТЬ
| ЗаполняемыйДокумент.Номенклатура КАК Номенклатура
|ИЗ
| &МойТипДокумента.Товары КАК ЗаполняемыйДокумент
|ГДЕ
| ЗаполняемыйДокумент.Ссылка = &Документ";
Изначально предполагалось, что можно будет передать параметр &МойТипДокумента, который будет автоматически раскрываться в имя метаданных. Однако такой механизм напрямую не работает для имен таблиц в секции ИЗ.
Метаданные.ПолноеИмяДля того чтобы динамически подставлять имя типа документа в запрос, нам необходимо программно получить полное имя объекта метаданных. Для этого объект "Ссылка" (или любой другой ссылочный объект) предоставляет очень полезное свойство: Метаданные.ПолноеИмя.
Давайте рассмотрим подробнее, как это работает:
ДокументПриобретения, которая может указывать на объект типа "Документ.ПриобретениеТоваровУслуг".ДокументПриобретения.Метаданные.ПолноеИмя, система вернет строку: "Документ.ПриобретениеТоваровУслуг".Пример использования в коде:
&НаСервере
Функция ПолучитьДанныеИзДокумента(СсылкаНаДокумент)
Если ТипЗнч(СсылкаНаДокумент) = Тип("Неопределено") Тогда
Возврат Неопределено;
КонецЕсли;
// 1. Получаем полное имя объекта метаданных по ссылке.
// Это вернет строку вида "Документ.ИмяДокумента"
ПолноеИмяТипаДокумента = СсылкаНаДокумент.Метаданные.ПолноеИмя;
// 2. Формируем текст запроса, используя полученную строку.
// Здесь мы используем конкатенацию строк для динамического формирования запроса.
// Обратите внимание, что если у документа есть табличная часть, например, "Товары",
// то её имя также добавляется к полному имени типа документа.
ТекстЗапроса = "ВЫБРАТЬ
| Док.Ссылка КАК Ссылка,
| Док.Номер КАК НомерДокумента,
| Док.Дата КАК ДатаДокумента,
| Док.Номенклатура КАК Номенклатура,
| Док.Количество КАК Количество,
| Док.Сумма КАК Сумма
|ИЗ
| " + ПолноеИмяТипаДокумента + ".Товары КАК Док
|ГДЕ
| Док.Ссылка = &Ссылка";
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
Попытка
Результат = Запрос.Выполнить();
Возврат Результат.Выгрузить(); // Возвращаем результат в виде ТаблицыЗначений
Исключение
Сообщить("Ошибка выполнения запроса: " + ОписаниеОшибки(), СтатусСообщения.Важное);
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Пример вызова функции:
// Допустим, есть переменная МояСсылкаНаДокумент, содержащая ссылку на документ
// ТаблицаДанныхДокумента = ПолучитьДанныеИзДокумента(МояСсылкаНаДокумент);
// Если ТаблицаДанныхДокумента <> Неопределено Тогда
// // Обработка полученных данных
// КонецЕсли;
Как мы видим, строка " + ПолноеИмяТипаДокумента + ".Товары КАК Док" позволяет нам динамически указать источник данных. Если СсылкаНаДокумент указывает на "ПоступлениеТоваровУслуг", то запрос будет работать с Документ.ПоступлениеТоваровУслуг.Товары. Если на "ПеремещениеТоваров", то с Документ.ПеремещениеТоваров.Товары.
Для подстановки динамических частей в текст запроса вы можете использовать два основных подхода:
Этот метод, который мы использовали в предыдущем примере, является наиболее прямым. Мы "склеиваем" части запроса, используя оператор +. Это удобно для простых подстановок.
// ... (получение ПолноеИмяТипаДокумента) ...
ТекстЗапроса = "ВЫБРАТЬ
| Док.Ссылка
|ИЗ
| " + ПолноеИмяТипаДокумента + " КАК Док
|ГДЕ
| Док.Ссылка = &Ссылка";
СтрЗаменитьЭтот подход может быть более удобным, когда у вас есть сложный шаблон запроса с несколькими динамическими частями. Вы можете определить в шаблоне специальные "плейсхолдеры" (маркеры) и затем заменить их на нужные значения.
&НаСервере
Функция ПолучитьДанныеИзДокументаЧерезСтрЗаменить(СсылкаНаДокумент)
Если ТипЗнч(СсылкаНаДокумент) = Тип("Неопределено") Тогда
Возврат Неопределено;
КонецЕсли;
ПолноеИмяТипаДокумента = СсылкаНаДокумент.Метаданные.ПолноеИмя;
// Определяем шаблон запроса с плейсхолдером для имени таблицы
ШаблонЗапроса = "ВЫБРАТЬ
| Док.Ссылка,
| Док.Номер,
| Док.Дата,
| Док.Номенклатура,
| Док.Количество,
| Док.Сумма
|ИЗ
| &ИМЯ_ТАБЛИЦЫ_ДОКУМЕНТА.Товары КАК Док
|ГДЕ
| Док.Ссылка = &Ссылка";
// Заменяем плейсхолдер на актуальное имя типа документа
ТекстЗапроса = СтрЗаменить(ШаблонЗапроса, "&ИМЯ_ТАБЛИЦЫ_ДОКУМЕНТА", ПолноеИмяТипаДокумента);
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
Попытка
Результат = Запрос.Выполнить();
Возврат Результат.Выгрузить();
Исключение
Сообщить("Ошибка выполнения запроса: " + ОписаниеОшибки(), СтатусСообщения.Важное);
Возврат Неопределено;
КонецПопытки;
КонецФункции
Оба метода эффективны. Выбор между ними зависит от личных предпочтений и сложности формируемого запроса. СтрЗаменить может улучшить читаемость кода, если шаблон запроса большой, и динамических частей много.
В универсальных функциях важно учитывать, что не все документы могут иметь одинаковую структуру. Например, не у каждого документа есть табличная часть "Товары", или в ней может отсутствовать реквизит "Номенклатура". Чтобы избежать ошибок выполнения запроса, мы можем программно проверить структуру документа перед его формированием.
Для этого мы используем доступ к метаданным системы:
МетаданныеДокумента = СсылкаНаДокумент.Метаданные();ЕстьТабличнаяЧастьТовары = МетаданныеДокумента.ТабличныеЧасти.Найти("Товары") <> Неопределено;Если ЕстьТабличнаяЧастьТовары Тогда МетаданныеТЧ = МетаданныеДокумента.ТабличныеЧасти.Товары; ЕстьРеквизитНоменклатура = МетаданныеТЧ.Реквизиты.Найти("Номенклатура") <> Неопределено; КонецЕсли;Пример использования этой логики:
&НаСервере
Функция ПолучитьДанныеИзДокументаСПроверкой(СсылкаНаДокумент)
Если ТипЗнч(СсылкаНаДокумент) = Тип("Неопределено") Тогда
Возврат Неопределено;
КонецЕсли;
ПолноеИмяТипаДокумента = СсылкаНаДокумент.Метаданные.ПолноеИмя;
ИмяОбъектаМетаданных = СсылкаНаДокумент.Метаданные().Имя; // Например, "ПоступлениеТоваровУслуг"
МетаданныеДокумента = Метаданные.Документы.Найти(ИмяОбъектаМетаданных);
Если МетаданныеДокумента = Неопределено Тогда
Сообщить("Не удалось найти метаданные для документа типа: " + ИмяОбъектаМетаданных, СтатусСообщения.Важное);
Возврат Неопределено;
КонецЕсли;
// Проверяем наличие табличной части "Товары"
МетаданныеТЧТовары = МетаданныеДокумента.ТабличныеЧасти.Найти("Товары");
Если МетаданныеТЧТовары = Неопределено Тогда
Сообщить("У документа " + ПолноеИмяТипаДокумента + " отсутствует табличная часть 'Товары'.", СтатусСообщения.Важное);
Возврат Неопределено;
КонецЕсли;
// Проверяем наличие реквизитов "Номенклатура", "Количество", "Сумма" в табличной части "Товары"
Если МетаданныеТЧТовары.Реквизиты.Найти("Номенклатура") = Неопределено
ИЛИ МетаданныеТЧТовары.Реквизиты.Найти("Количество") = Неопределено
ИЛИ МетаданныеТЧТовары.Реквизиты.Найти("Сумма") = Неопределено Тогда
Сообщить("В табличной части 'Товары' документа " + ПолноеИмяТипаДокумента + " отсутствуют необходимые реквизиты.", СтатусСообщения.Важное);
Возврат Неопределено;
КонецЕсли;
// Если все проверки пройдены, формируем запрос
ТекстЗапроса = "ВЫБРАТЬ
| Док.Ссылка КАК Ссылка,
| Док.Номер КАК НомерДокумента,
| Док.Дата КАК ДатаДокумента,
| Док.Номенклатура КАК Номенклатура,
| Док.Количество КАК Количество,
| Док.Сумма КАК Сумма
|ИЗ
| " + ПолноеИмяТипаДокумента + ".Товары КАК Док
|ГДЕ
| Док.Ссылка = &Ссылка";
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
Попытка
Результат = Запрос.Выполнить();
Возврат Результат.Выгрузить();
Исключение
Сообщить("Ошибка выполнения запроса: " + ОписаниеОшибки(), СтатусСообщения.Важное);
Возврат Неопределено;
КонецПопытки;
КонецФункции
Этот подход обеспечивает высокую надежность наших универсальных функций, предотвращая ошибки на этапе выполнения запроса, вызванные несоответствием структуры документа ожиданиям.
Важно четко понимать, что задача, которую мы разбираем — это динамическая подстановка имени таблицы в секцию ИЗ запроса. Это отличается от другой, также распространенной задачи: получения значений конкретных реквизитов объекта по его ссылке.
В типовых конфигурациях 1С, в частности в Библиотеке Стандартных Подсистем (БСП), существуют универсальные функции для получения значений реквизитов. Например, функции модуля ОбщегоНазначения:
ОбщегоНазначения.ПолучитьЗначениеРеквизитаОбъекта(Ссылка, ИмяРеквизита)ОбщегоНазначения.ПолучитьЗначенияРеквизитовОбъекта(Ссылка, СписокРеквизитов)ОбщегоНазначения.ПолучитьЗначениеРеквизитаОбъектов(МассивСсылок, ИмяРеквизита)ОбщегоНазначения.ПолучитьЗначенияРеквизитовОбъектов(МассивСсылок, СписокРеквизитов)Эти функции очень полезны, когда нам нужно получить значения одного или нескольких реквизитов из одного или нескольких объектов, и мы не хотим вручную формировать запрос для каждого случая. Они сами внутри себя используют механизмы, подобные тем, что мы рассматриваем (то есть динамическое построение запроса), но их API ориентировано на получение значений реквизитов в структуру или массив, а не на произвольное построение запроса с динамическим источником данных.
В нашем же случае, мы хотим не просто получить реквизиты, а именно построить гибкий запрос, который может включать соединения, условия, группировки и выбирать данные из разных табличных частей или даже из основной таблицы документа, имя которой определяется в рантайме. Поэтому прямое использование Ссылка.Метаданные.ПолноеИмя и строковой конкатенации является наиболее подходящим решением для поставленной задачи.
Неопределено, прежде чем пытаться получить ее метаданные.Попытка...Исключение...КонецПопытки для обработки возможных ошибок выполнения запроса.Используя описанные методы, вы сможете создавать гибкие и универсальные решения в 1С, которые легко адаптируются к различным типам документов и их структурам. Ссылка.Метаданные.ПолноеИмя — это мощный инструмент, который открывает широкие возможности для динамического программирования в системе 1С:Предприятие.