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