Почему в 1С 8.3 не отображаются связанные документы и как решить эту проблему?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет IT и автоматизация бизнеса
← На главную

При работе в типовых конфигурациях «1С:Предприятие 8.3» (например, «1С:Управление торговлей», «1С:ERP Управление предприятием» или «1С:Комплексная автоматизация») пользователи часто сталкиваются с ситуацией, когда в отчете «Связанные документы» (структура подчиненности) перестают отображаться родительские документы. Например, открывая структуру подчиненности из «Заказа поставщику», мы не видим исходный «Заказ клиента», хотя реквизит ДокументОснование заполнен корректно, а при просмотре со стороны «Заказа клиента» связь отображается без проблем.

В этой статье мы подробно разберем, почему возникает эта плавающая ошибка, проанализируем поведение платформы при работе с расширениями конфигурации и найдем надежное техническое решение, которое поможет раз и навсегда устранить эту проблему.

Выясним причину: почему платформа 1С дает сбой?

Для того чтобы понять природу ошибки, обратимся к логике работы общей формы СтруктураПодчиненности. Построение дерева связей опирается на стандартный механизм платформы — Критерии отбора (в нашем случае это критерий отбора СвязанныеДокументы).

При открытии формы система пытается определить, какие реквизиты текущего документа могут выступать в качестве родительских связей. Для этого в процедуре ВывестиРодительскиеОбъекты выполняется обход метаданных текущего объекта и проверка, входят ли его реквизиты в состав критерия отбора:


Если Метаданные.КритерииОтбора.СвязанныеДокументы.Состав.Содержит(Реквизит) Тогда

В обычных условиях этот код работает безупречно. Однако ситуация кардинально меняется, если в базе данных используются расширения конфигурации. Если документ (например, ЗаказПоставщику) или его форма заимствованы в любое из активных расширений, платформа начинает вести себя следующим образом:

  1. Переменная Реквизит, полученная при обходе метаданных текущего объекта, начинает ссылаться на объект метаданных из контекста расширения.
  2. Коллекция состава критерия отбора Метаданные.КритерииОтбора.СвязанныеДокументы.Состав по-прежнему хранит ссылки на объекты метаданных из основной конфигурации.
  3. Метод Содержит() производит сравнение объектов метаданных «по ссылке» (как физических объектов в памяти компьютера). Поскольку контексты основной базы и расширения различаются, платформа считает их разными объектами.
  4. В результате метод Содержит() возвращает значение Ложь (False), и программа просто «пропускает» заполненный реквизит связи, из-за чего родительские документы исчезают из дерева подчиненности.

Интересно, что эта проблема является плавающей. Она может не воспроизводиться на копиях баз данных, если там отключены расширения, сброшен кэш метаданных или база запущена под другой версией платформы, где кэширование объектов сработало иначе. Но на рабочем сервере эта ошибка стабильно мешает пользователям вести оперативный учет.

Разберем по шагам: как диагностировать ошибку в отладчике

Давайте убедимся в наличии этой проблемы самостоятельно. Для этого выполним несколько простых шагов в режиме отладки:

  1. Установим точку останова в общей форме СтруктураПодчиненности (или в общем модуле, где реализован вывод родительских документов) внутри процедуры ВывестиРодительскиеОбъекты на строке проверки состава критерия отбора.
  2. Запустим сеанс под отладчиком и откроем структуру подчиненности проблемного документа.
  3. В табло отладчика вычислим выражение: Реквизит.ПолноеИмя() (получим, например, "Документ.ЗаказПоставщику.Реквизит.ДокументОснование").
  4. Затем проверим наличие этого реквизита в составе критерия отбора напрямую через цикл или поиск по имени. Мы увидим, что реквизит с таким именем физически присутствует в составе критерия отбора, но вызов Метаданные.КритерииОтбора.СвязанныеДокументы.Состав.Содержит(Реквизит) упорно возвращает Ложь.

Это прямое подтверждение ошибки сопоставления типов метаданных между основной конфигурацией и расширением.

Решение проблемы: исправляем код с помощью расширения

Поскольку мы не можем напрямую повлиять на внутренние алгоритмы платформенного метода Содержит(), нам необходимо обойти это ограничение программно. Самый надежный способ — перейти от ненадежного сравнения объектов метаданных по ссылке к сравнению их полных имен в виде строк.

Чтобы не замедлять работу системы постоянным строковым поиском в цикле, мы применим оптимизацию: предварительно выгрузим все полные имена состава критерия отбора в быстрое семейство коллекций — Соответствие.

Рассмотрим детально, как изменить процедуру ВывестиРодительскиеОбъекты в расширении. Мы заменим исходную логику более безопасной.

Ниже представлен готовый код функции, оптимизированный для работы в расширении конфигурации:


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

    // Шаг 3. Анализируем табличные части объекта
    Для Каждого ТЧ Из МетаданныеОбъекта.ТабличныеЧасти Цикл
        СтрРеквизитов = "";
        СодержимоеТЧ = ТекущийОбъект[ТЧ.Имя].Выгрузить();
        
        Для Каждого Реквизит Из ТЧ.Реквизиты Цикл
            
            // Здесь также заменяем стандартную проверку на обращение к нашему кэшу строк
            Если КэшКритерияОтбора[Реквизит.ПолноеИмя()] = Истина Тогда
                Для Каждого ТекущийТип Из Реквизит.Тип.Типы() Цикл
                    МетаданныеРеквизита = Метаданные.НайтиПоТипу(ТекущийТип);
                    
                    Если МетаданныеРеквизита <> Неопределено 
                        И (Метаданные.Документы.Содержит(МетаданныеРеквизита) 
                        Или Метаданные.Справочники.Содержит(МетаданныеРеквизита) 
                        Или Метаданные.ПланыВидовХарактеристик.Содержит(МетаданныеРеквизита)) 
                        И ПравоДоступа("Чтение", МетаданныеРеквизита) Тогда
                        
                        СтрРеквизитов = СтрРеквизитов + ?(СтрРеквизитов = "", "", ", ") + Реквизит.Имя;
                        Прервать;
                    КонецЕсли;
                КонецЦикла;
            КонецЕсли;
            
        КонецЦикла;
        
        СодержимоеТЧ.Свернуть(СтрРеквизитов);
        
        Для Каждого КолонкаТЧ Из СодержимоеТЧ.Колонки Цикл
            Для Каждого СтрокаТЧ Из СодержимоеТЧ Цикл
                ЗначениеРеквизита = СтрокаТЧ[КолонкаТЧ.Имя];
                МетаданныеЗначения = Метаданные.НайтиПоТипу(ТипЗнч(ЗначениеРеквизита));
                
                Если МетаданныеЗначения <> Неопределено Тогда
                    Если ЗначениеЗаполнено(ЗначениеРеквизита) 
                        И (Метаданные.Документы.Содержит(МетаданныеЗначения) 
                        Или Метаданные.Справочники.Содержит(МетаданныеЗначения) 
                        Или Метаданные.ПланыВидовХарактеристик.Содержит(МетаданныеЗначения)) 
                        И ЗначениеРеквизита <> ТекущийОбъект 
                        И СписокРеквизитов.НайтиПоЗначению(ЗначениеРеквизита) = Неопределено 
                        И ПравоДоступа("Чтение", МетаданныеЗначения) Тогда
                        
                        ЯвляетсяДокументом = ОбщегоНазначения.ЭтоДокумент(МетаданныеЗначения);
                        Если ЯвляетсяДокументом Тогда
                            СписокРеквизитов.Добавить(ЗначениеРеквизита, Формат(ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ЗначениеРеквизита, "Дата"), "ДЛФ=DT"));
                        Иначе
                            СписокРеквизитов.Добавить(ЗначениеРеквизита, Дата(1,1,1));
                        КонецЕсли;
                    КонецЕсли;
                КонецЕсли;
            КонецЦикла;
        КонецЦикла;
    КонецЦикла;

    // Шаг 4. Вывод собранных родительских связей в дерево
    СписокРеквизитов.СортироватьПоПредставлению();
    Для каждого ЭлементСписка Из СписокРеквизитов Цикл
        Выборка = ПолучитьВыборкуПоРеквизитамОбъекта(ЭлементСписка.Значение);
        Если Выборка.Следующий() Тогда
            СтрокаДерева = ДобавитьСтрокуВДерево(СтрокиДерева, Выборка);
            Если НЕ ДобавляемыйОбъектИмеетсяСредиРодителей(ДеревоРодитель, ЭлементСписка.Значение) Тогда
                ВывестиРодительскиеОбъекты(ЭлементСписка.Значение, СтрокаДерева);
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Преимущества предложенного решения

Давайте проанализируем, почему этот способ исправления является наиболее предпочтительным:

Таким образом, мы успешно обошли ограничение платформы и вернули пользователям привычный функционал анализа связанных документов. Применяйте данный подход при обнаружении схожих проблем в любых механизмах конфигураций, использующих анализ метаданных совместно с расширениями.

← На главную