При разработке и кастомизации конфигураций на платформе 1С:Предприятие 7.7 программисты часто сталкиваются с необходимостью организовать связь между документами по принципу «родитель — потомок». Типичный пример: создание «Чека ККМ» на основании «Расходной накладной». Когда возникает задача вывести список всех чеков, подчиненных текущей накладной, разработчики интуитивно пытаются использовать типизированный объект документа и вызывают метод выборки. Однако платформа отвечает жестким отказом и ошибкой: "Объект - документ определенного вида!".
В этой статье мы подробно разберем причины возникновения этой ошибки, рассмотрим правильный классический способ работы с подчиненными документами, а также изучим альтернативные и более производительные варианты решения этой задачи.
Давайте проанализируем архитектуру платформы 1С 7.7. Когда мы пишем следующий код:
Док = СоздатьОбъект("Документ.ЧекФР");
Док.ВыбратьПодчиненныеДокументы(,, ТекущийДокумент());
Платформа генерирует ошибку выполнения. Дело в том, что метод ВыбратьПодчиненныеДокументы() (как и методы ВыбратьПоЗначению(), ВыбратьПоНомеру()) относится к методам сквозного поиска по всей базе данных. Они спроектированы для работы исключительно со специальным, универсальным (нетипизированным) объектом типа «Документ».
Если мы инициализируем объект конкретного вида (в нашем примере — Документ.ЧекФР), система жестко ограничивает контекст работы этого объекта только структурой и реквизитами данного метаданного. Вызывать методы сквозной выборки по таблицам связей на таком объекте запрещено на уровне ядра платформы.
Для решения этой задачи нам нужно использовать универсальный объект, а фильтрацию по типу документа производить на этапе обхода выборки.
---
Рассмотрим правильный код штатного механизма 1С 7.7. Для этого мы создаем объект общего вида Документ без указания конкретного идентификатора через точку. Затем мы открываем выборку подчиненных документов для нашего ТекущийДокумент() и в цикле отсекаем документы других видов с помощью метода Вид().
Давайте разберем этот процесс по шагам:
// 1. Создаем универсальный нетипизированный объект
Док = СоздатьОбъект("Документ");
// 2. Открываем выборку подчиненных документов для текущего документа-основания
Если Док.ВыбратьПодчиненныеДокументы(,, ТекущийДокумент()) = 1 Тогда
// 3. Обходим выборку в цикле
Пока Док.ПолучитьДокумент() = 1 Цикл
// 4. Фильтруем документы по нужному нам виду
Если Док.Вид() = "ЧекФР" Тогда
// Документ найден! Выполняем полезные действия
Сообщить("Найден подчиненный чек: " + Док.ТекущийДокумент() + " от " + Док.ДатаДок);
// Здесь может быть заполнение Таблицы Значений, открытие формы и т.д.
КонецЕсли;
КонецЦикла;
КонецЕсли;
Важный нюанс производительности: Связи подчиненности в 1С 7.7 отслеживаются автоматически и записываются в системный индекс (таблица _1SCRDOC в SQL-версии или файл 1CRDOC.CDX в DBF-версии). Метод ВыбратьПодчиненныеДокументы() работает напрямую с этим индексом. На очень больших базах данных, если у одного документа-родителя имеется огромное количество подчиненных документов разных видов, этот цикл может работать медленно, так как платформа сначала прочитает все ссылки, а затем будет последовательно анализировать их вид на клиенте.
---
Если вы хотите избежать холостого перебора лишних подчиненных документов и переложить фильтрацию по виду документа на плечи СУБД, отличным решением станет использование стандартного механизма запросов 1С. Этот способ работает быстрее на объемных базах данных.
Для реализации этого метода у вашего подчиненного документа (в нашем примере — ЧекФР) в конфигураторе должен быть создан реквизит (например, ДокОснование) с типом «Документ».
Давайте посмотрим на пример реализации такого запроса:
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(ВыборкаПодчиненных)
|Период с '01.01.1980' по '31.12.2050'; // Задаем максимально широкий диапазон времени
|Обрабатывать Документы;
|Чек = Документ.ЧекФР.ТекущийДокумент;
|Основание = Документ.ЧекФР.ДокОснование; // Наш реквизит связи
|Группировка Чек;
|Условие(Основание = ТекущийДокумент);
|"//}}ЗАПРОС
;
// Выполняем запрос к базе данных
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
// Обходим результат запроса
Пока Запрос.Группировка(1) = 1 Цикл
НайденныйЧек = Запрос.Чек;
Сообщить("Найден чек через запрос: " + НайденныйЧек);
КонецЦикла;
КонецЕсли;
Этот подход гарантирует, что база данных вернет только строго подходящие документы ЧекФР, у которых в реквизите ДокОснование записана наша расходная накладная, минуя чтение всей таблицы связей.
---
Еще один штатный способ быстрого поиска — использование системного механизма Граф отбора. Метод ВыбратьПоЗначению() также выдавал ошибку «Объект - документ определенного вида!», потому что вызывался на типизированном объекте. Исправим это.
Для реализации этого подхода выполните следующие шаги:
Основание.ДокОснование документа ЧекФР.Теперь мы можем использовать этот индекс для быстрого поиска документов через универсальный объект:
Док = СоздатьОбъект("Документ");
// Используем созданную нами графу отбора "Основание"
Док.ВыбратьПоЗначению(, , "Основание", ТекущийДокумент());
Пока Док.ПолучитьДокумент() = 1 Цикл
// Дополнительно проверяем вид, если в эту же графу включены другие документы
Если Док.Вид() = "ЧекФР" Тогда
Сообщить("Найден документ через графу отбора: " + Док.ТекущийДокумент());
КонецЕсли;
КонецЦикла;
Использование графы отбора — это стандартный и очень быстрый способ получения связанных данных, так как платформа создает под этот отбор отдельный физический индекс в таблицах базы данных.
---
Если ваша информационная база работает под управлением Microsoft SQL Server, а в системе установлена популярная внешняя компонента 1С++ (1cpp.dll), вы можете получить список документов мгновенно, отправив прямой запрос к СУБД в обход штатных механизмов.
Проанализируем пример кода с использованием класса ODBCRecordset:
// Создаем объект для работы с прямыми SQL-запросами
ЗапросSQL = СоздатьОбъект("ODBCRecordset");
ТекстЗапроса = "
|SELECT
| Журнал.IDDOC as [Док $Документ.ЧекФР]
|FROM
| _1SCRDOC as Связи (NOLOCK)
|INNER JOIN
| _1SJOURN as Журнал (NOLOCK) ON Связи.ChildID = Журнал.IDDOC
|WHERE
| Связи.ParentVal = :ВыбДокумент
| AND Журнал.IDDOCDEF = :IDDefЧек
|";
// Передаем параметры в запрос
ЗапросSQL.УстановитьТекстовыйПараметр("ВыбДокумент", ТекущийДокумент());
// Получаем внутренний идентификатор вида документа "ЧекФР"
ЗапросSQL.УстановитьТекстовыйПараметр("IDDefЧек", "ЧекФР");
// Выполняем SQL-инструкцию и получаем результат в виде Таблицы Значений
ТаблицаЧеков = ЗапросSQL.ВыполнитьИнструкцию(ТекстЗапроса);
// Теперь у нас есть готовая таблица с подчиненными чеками
ТаблицаЧеков.ВыбратьСтроки();
Пока ТаблицаЧеков.ПолучитьСтроку() = 1 Цикл
Сообщить("Найден подчиненный документ через SQL: " + ТаблицаЧеков.Док);
КонецЦикла;
Этот метод считается наиболее профессиональным для оптимизации высоконагруженных систем 1С 7.7, так как он минимизирует транзакционную нагрузку на SQL Server и возвращает результат за миллисекунды.
Мы подробно разобрали проблему и нашли несколько путей её решения. Главный вывод: никогда не вызывайте методы сквозного поиска на типизированных объектах документов. Используйте универсальный объект СоздатьОбъект("Документ") для простых задач, стандартные запросы и графы отбора для оптимизации штатных механизмов, или прямые SQL-запросы для достижения максимальной производительности вашей системы.