В практике разработки на платформе 1С часто возникает задача, когда необходимо обработать данные «вложенных» документов. Например, при формировании реестра на выгрузку в банк мы можем столкнуться с ситуацией, когда основным документом является Платежное поручение, а в его табличной части перечислены несколько документов Ведомость на выплату зарплаты. Каждая такая ведомость, в свою очередь, содержит список сотрудников и сумм.
Разберем подробно, как правильно обратиться к этим данным, избежать типичных ошибок типизации и оптимизировать производительность вашего кода.
Рассмотрим ситуацию, когда разработчик пытается перебрать строки ведомостей внутри цикла по платежным поручениям. Типичная ошибка выглядит так:
Для каждого СтрокаВед из Платежка.Ведомости цикл
Для Каждого СтрокаВедомости Из СтрокаВед Цикл // Здесь возникает ошибка!
Работник = СтрокаВедомости.Sotrudnik;
// ... дальнейшая обработка
КонецЦикла;
КонецЦикла;
Проанализируем причину сбоя. Переменная СтрокаВед в данном контексте является строкой табличной части текущей платежки. Строка сама по себе не является коллекцией, по которой можно запустить цикл Для каждого. Чтобы получить доступ к данным сотрудников, нам нужно сначала обратиться к ссылке на документ, которая хранится в этой строке, а затем — к конкретной табличной части этого документа.
Выясним, как исправить структуру циклов. Сначала мы извлекаем ссылку на документ-ведомость из реквизита строки, а затем обращаемся к коллекции строк этой ведомости. Предположим, что табличная часть в документе ведомости называется Состав или Зарплата.
Для каждого СтрокаВед из Платежка.Ведомости Цикл
// Получаем ссылку на ведомость из реквизита строки
ВедомостьСсылка = СтрокаВед.Ведомость;
// Проверяем, заполнена ли ссылка, чтобы избежать ошибки "Обращение к методу объекта по пустой ссылке"
Если ЗначениеЗаполнено(ВедомостьСсылка) Тогда
// Обращаемся к конкретной табличной части ведомости (например, "Зарплата")
Для Каждого СтрокаВедомости Из ВедомостьСсылка.Зарплата Цикл
Работник = СтрокаВедомости.Сотрудник;
Сумма = СтрокаВедомости.КВыплате;
// Теперь код работает корректно
КонецЦикла;
КонецЕсли;
КонецЦикла;
Важный нюанс: при обращении через точку (ВедомостьСсылка.Зарплата) платформа 1С автоматически считывает весь объект из базы данных. Если документов много, это может замедлить работу системы.
Часто одна и та же обработка должна уметь работать с разными типами документов (например, напрямую с Ведомостью или через Платежное поручение). Для этого необходимо использовать проверку типов. Рассмотрим, как это сделать правильно, используя функцию ТипЗнч().
Многие начинающие разработчики совершают ошибку, сравнивая тип со строкой или используя ДокументОбъект вместо ДокументСсылка. Посмотрим на правильный пример проверки типа:
Если ТипЗнч(Платежка) = Тип("ДокументСсылка.ПлатежноеПоручение") Тогда
// Обработка логики для платежного поручения
ИначеЕсли ТипЗнч(Платежка) = Тип("ДокументСсылка.ВедомостьНаВыплатуЗарплаты") Тогда
// Обработка логики напрямую для ведомости
КонецЕсли;
Обратите внимание: если вы работаете с массивом ссылок (например, полученным из формы списка), следует проверять именно ДокументСсылка. Проверка на ДокументОбъект сработает только в том случае, если вы уже вызвали метод ПолучитьОбъект().
Использование циклов внутри циклов с обращением через точку — это классическая проблема производительности, известная как «запросы в цикле». Каждый раз, когда код встречает ВедомостьСсылка.Зарплата, платформа выполняет отдельное обращение к серверу БД.
Проанализируем профессиональный подход: использование одного запроса для получения всех данных. Это позволит получить «плоскую» таблицу, где в каждой строке будет и информация о платежке, и данные сотрудника из ведомости.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПлатежноеПоручениеВедомости.Ссылка КАК Платежка,
| ВедомостьЗарплата.Сотрудник,
| ВедомостьЗарплата.КВыплате,
| ВедомостьЗарплата.НомерЛицевогоСчета
|ИЗ
| Документ.ПлатежноеПоручение.Ведомости КАК ПлатежноеПоручениеВедомости
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ВедомостьНаВыплатуЗарплаты.Зарплата КАК ВедомостьЗарплата
| ПО ПлатежноеПоручениеВедомости.Ведомость = ВедомостьЗарплата.Ссылка
|ГДЕ
| ПлатежноеПоручениеВедомости.Ссылка В(&МассивПлатежек)";
Запрос.УстановитьПараметр("МассивПлатежек", МассивДокументов);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Прямой доступ к данным без лишних обращений к базе
Текст.ДобавитьСтроку(Выборка.НомерЛицевогоСчета + ";" + Выборка.КВыплате);
КонецЦикла;
Такой подход работает в десятки раз быстрее при больших объемах данных, так как платформа делает всего один запрос к СУБД.
Иногда названия табличных частей в разных конфигурациях (например, ЗУП 3.1 и Бухгалтерия предприятия) могут отличаться (Состав, Зарплата, Сотрудники). Чтобы написать универсальный код, который не сломается при обновлении, мы можем использовать коллекцию Метаданные.
Посмотрим на пример, как программно найти нужную табличную часть:
МетаданныеДокумента = ВедомостьСсылка.Метаданные();
Для Каждого ТЧ Из МетаданныеДокумента.ТабличныеЧасти Цикл
Если ТЧ.Имя = "Зарплата" Или ТЧ.Имя = "Состав" Тогда
ИмяНужнойТЧ = ТЧ.Имя;
Прервать;
КонецЕсли;
КонецЦикла;
Если ЗначениеЗаполнено(ИмяНужнойТЧ) Тогда
ТаблицаДанных = ВедомостьСсылка[ИмяНужнойТЧ];
// Работаем с коллекцией строк
КонецЕсли;
Для правильного получения данных из вложенных табличных частей необходимо придерживаться следующих правил:
ТипЗнч(Ссылка) = Тип("ДокументСсылка.ИмяДокумента") является наиболее надежным способом.ПолучитьОбъект() доступен только на сервере. Если вы формируете файл на стороне клиента, старайтесь подготовить все данные на сервере заранее и передать их в виде массива структур или таблицы значений.Следуя этим рекомендациям, вы сможете создавать стабильные и быстрые механизмы выгрузки данных, которые легко поддерживать даже при изменении структуры конфигурации.