Как реализовать отбор в форме списка справочника 1С v7.7 по реквизиту неопределенного вида?

Программист 1С v7.7
← На главную

При работе в 1С v7.7 мы часто сталкиваемся с необходимостью создания универсальных механизмов, например, для прикрепления вложений к различным документам или элементам справочников. Когда мы пытаемся реализовать это через единый справочник, где реквизит-основание имеет неопределенный вид, возникает проблема с корректным отбором в форме списка. Стандартные механизмы 1С v7.7 не всегда эффективно работают с такими реквизитами. Рассмотрим подробнее, почему возникают сложности и какие существуют решения.

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

Почему прямой отбор по реквизиту неопределенного вида не работает?

В 1С v7.7 стандартные механизмы отбора формы списка имеют ограничения при работе с реквизитами неопределенного типа. Даже если в метаданных справочника для такого реквизита установить флаги "сортировка" и "отбор", фактически эта функциональность будет ограничена. Система не может автоматически определить, по какому конкретному типу из множества возможных следует производить отбор, когда реквизит объявлен как "неопределенного вида".

Решение 1: Хранение ссылок в строковом виде для отбора

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

Пошаговая реализация:

  1. Создание дополнительного строкового реквизита: В справочнике для хранения вложений мы добавляем новый реквизит, например, ОснованиеСтрока, с типом "Строка". Важно правильно определить длину этой строки, чтобы она могла вместить идентификатор любого объекта.

  2. Конвертация объекта в строку: При записи нового вложения или изменении основания, мы будем конвертировать объект-основание (документ или элемент справочника) в его строковое представление. Для этого используем стандартные функции 1С v7.7:

    • ЗначениеВСтрокуВнутр(Объект)

      : Преобразует значение любого типа в его внутреннее строковое представление. Это наиболее универсальный метод.
    • ЗначениеВСамуюДлиннуюСтрокуБД(Объект)

      : Также преобразует значение в строковое представление, но может быть более компактным. Длина такого представления обычно составляет 13 символов (для ID объекта) плюс дополнительные символы для типа и вида.
  3. Запись строкового представления: Полученную строку сохраняем в реквизит ОснованиеСтрока.

  4. Установка отбора: В форме списка вложений мы устанавливаем отбор по реквизиту ОснованиеСтрока, передавая в качестве значения строковое представление текущего документа или элемента справочника, для которого мы хотим видеть вложения.

  5. Обратная конвертация (для получения объекта): При необходимости получить сам объект из его строкового представления используем функцию ЗначениеИзСтрокиВнутр(Строка).

Пример кода для преобразования и отбора (на основе сообщений форума):

Рассмотрим пример функции глПолучитьИдОбъектаДляЭК, которая преобразует объект в укороченное строковое представление для отбора. Эта функция позволяет нам получить ID объекта, состоящий из типа, вида и собственно ID, упакованных в строку длиной около 20 символов, что укладывается в ограничения отбора по строке (обычно до 22-23 символов).


Функция глПолучитьИдОбъектаДляЭК(Объект) Экспорт
    // Можно предварительно пробежаться по всем объектам и выяснить максимальную длину строки,
    // возвращаемую этой функцией, и добавить пару символов про запас.
    Результат = "";
    Если ТипЗначенияСтр(Объект) = "Справочник" Тогда
        Если Объект.Выбран() = 0 Тогда
            Возврат Результат;
        КонецЕсли;
        Идентификатор = ЗначениеВСтрокуВнутр(Объект.ТекущийЭлемент()); // Обязательно .ТекущийЭлемент()
    ИначеЕсли ТипЗначенияСтр(Объект) = "Документ" Тогда
        Если Объект.Выбран() = 0 Тогда
            Возврат Результат;
        КонецЕсли;
        Идентификатор = ЗначениеВСтрокуВнутр(Объект.ТекущийДокумент()); // Обязательно .ТекущийДокумент()
    Иначе
        Возврат Результат; // Другие нам неинтересны
    КонецЕсли;
          
    Идентификатор = Сред(Идентификатор, 2, СтрДлина(Идентификатор) - 2);
    Элементы = СоздатьОбъект("СписокЗначений");
    Элементы.ИзСтрокиСРазделителями(Идентификатор);
                                                                                    
    // Нам надо впихнуться в 23 символа, по которым можно делать отбор
    ID_ТипОбъекта = Элементы.ПолучитьЗначение(1); // 1 - символ "B"-справочник, "O"-документ
    ID_ВидОбъекта = Формат(Элементы.ПолучитьЗначение(4), "С6"); // Может быть 10 символов - у нас не встречается больше 5, возьмем 6
    ID_Объекта = Элементы.ПолучитьЗначение(7); // 13 символов
    
    Если СтрДлина(СокрЛП(Элементы.ПолучитьЗначение(4))) > 6 Тогда
        // Можно увеличить и до 8, но тогда надо и идентификатор увеличивать до 22 в справочнике
        // (отбор не работает больше 23?) и существующие перенумеровать на 8 символов
        Сообщить("Слишком длинный внутренний идентификатор у <" + СокрЛП(Объект) + ">. Обратитесь к программисту и сообщите код проблемы #7!", "!");
        а = 1 / 0;
    КонецЕсли;
    Возврат "" + ID_ТипОбъекта + ID_ВидОбъекта + ID_Объекта; // 20 символов
КонецФункции // глПолучитьИдОбъектаДляЭК

Работа с полным внутренним представлением ID объекта и недокументированные функции:

Внутреннее представление ID объекта данных в 1С v7.7 состоит из нескольких частей: Тип (1 символ) + Вид (до 10 символов) + Объект (до 10 символов) + КодИБ (3 символа). Общая длина может достигать 24 символов. Для оптимизации хранения и отбора, особенно в базе данных, части, состоящие из цифр (Вид и Объект), могут быть перекодированы в 36-ричное значение. Для этого существуют недокументированные функции _IdToStr() (для перекодировки в строку) и _StrToId() (для обратной перекодировки). Это позволяет уместить полный ID любого объекта данных в строку длиной до 16 символов, обеспечивая при этом простое получение ссылки на сам объект.

Рассмотрим комплексный пример функций, использующих эти подходы:


// НАЗНАЧЕНИЕ:    Получение ID объекта данных (Перечисление,Счет,Справочник,Документ)
//     ВВЕДЕНИЕ:    Перечень ID объекта данных:    Тип(1)+Вид(10)+Объект(10)+КодИБ(3)
//                Полный ID объекта складывается из этих ID в указанном порядке, длина=24
//                В случае определенности типа справочника значение имеет только [Объект(10)+КодИБ(3)]
//    ПАРАМЕТРЫ:    * тОбъект    -    объект данных (ВНИМАНИЕ! для выборки - обязательно
//                                    "<ВыборкаОбъектов>.Текущий{Счет/Элемент/Документ}()"!)
//                * тИдСЗ        -    тип возвращаемого значения (СЗ=0/1/2/3/4, ПолныйИД,ТипИД,ВидИД,ОбъектИД,ИдИБ)
//    ВОЗВРАЩАЕМЫЕ ЗНАЧЕНИЯ:    * Явный возврат: Заданный во втором параметре ID
//                                * Дополнительно: Во втором параметре вызова возвращается СЗ со всеми ID (см.выше)
//------------------------------------------------------------------------------
Функция ПолучитьИДыОбъекта(Знач тОбъект,тИдСЗ=0) Экспорт
    Перем _Ок,тТипВозврата,тСЗ,тТхт;
    тТхт=ТипЗначенияСтр(тИдСЗ);
    Если тТхт="СписокЗначений" Тогда
        тИдСЗ.УдалитьВсе(); тТипВозврата="*";
    Иначе
        Если тТхт="Число" Тогда тТипВозврата=тИдСЗ Иначе тТипВозврата=Число(СокрЛП(тИдСЗ)) КонецЕсли;
        тИдСЗ=СоздатьОбъект("СписокЗначений"); тТипВозврата=Макс(0,Мин(4,Окр(тТипВозврата,0)));
        тТипВозврата=?(тТипВозврата=4,"ИдИБ",?(тТипВозврата=3,"ИдОбъекта",?(тТипВозврата=2,"ИдВида",?(тТипВозврата=1,"ИдТипа","*"))));
    КонецЕсли;
    тСЗ=СоздатьОбъект("СписокЗначений");
    тТхт=ТипЗначения(тОбъект);//ТипЗначенияСтр(тОбъект);
    Если тТхт=0 Тогда тТхт=ТипЗначенияСтр(тОбъект);
    ИначеЕсли тТхт=1 Тогда тТхт="Число";
    ИначеЕсли тТхт=2 Тогда тТхт="Строка";
    ИначеЕсли тТхт=3 Тогда тТхт="Дата";
    ИначеЕсли тТхт=10 Тогда тТхт="Перечисление";
    ИначеЕсли тТхт=11 Тогда тТхт="Справочник";
    ИначеЕсли тТхт=12 Тогда тТхт="Документ";
    ИначеЕсли тТхт=13 Тогда тТхт="Календарь";
    ИначеЕсли тТхт=14 Тогда тТхт="ВидРасчета";
    ИначеЕсли тТхт=15 Тогда тТхт="Счет";
    ИначеЕсли тТхт=16 Тогда тТхт="ВидСубконто";
    ИначеЕсли тТхт=17 Тогда тТхт="ПланСчетов";
    ИначеЕсли тТхт=100 Тогда тТхт=ТипЗначенияСтр(тОбъект) КонецЕсли;
    Если тТхт="СписокЗначений" Тогда    // в Объекте получен СЗ (выдернуть из него ИД-ы)
        тОбъект.Выгрузить(тСЗ);
    ИначеЕсли тТхт="Строка" Тогда        // в Объекте получено внутреннее представление (=ЗначениеВСТрокуВнутр(<данные>))
        тСЗ.ИзСтрокиСРазделителями(Сред(Лев(тОбъект,СтрДлина(тОбъект)-1),2));
    ИначеЕсли Найти("СчетПеречислениеСправочникДокумент",тТхт)>0 Тогда    // В объекте получен сам обыект данных:
        тОбъект=ЗначениеВСтрокуВнутр(тОбъект); тСЗ=СоздатьОбъект("СписокЗначений");
        тСЗ.ИзСтрокиСРазделителями(Сред(Лев(тОбъект,СтрДлина(тОбъект)-1),2));
    Иначе Возврат("");
    КонецЕсли;
    тТхт=СокрЛП(тСЗ.ПолучитьЗначение(1));        // T=счет/O=документ/B=справочник/E=перечисление/U=неопределеннный:
    Если ПустоеЗначение(тТхт)=1 Тогда тТхт="U" Иначе тТхт=?(Найти("TOBEU",Лев(тТхт,1))=0,"U",Лев(тТхт,1)) КонецЕсли;
    _Ок=тТхт;
    тТхт=тСЗ.ПолучитьЗначение(тСЗ.РазмерСписка());
    Если СтрДлина(тТхт)=13 Тогда тТхт=Формат(Число(тСЗ.ПолучитьЗначение(4)),"Ч10") Иначе тТхт=Лев(тТхт,10) КонецЕсли;
    _Ок=_Ок+тТхт;
    тТхт=тСЗ.ПолучитьЗначение(тСЗ.РазмерСписка());
    Если СтрДлина(тТхт)>13 Тогда тТхт=Прав(тТхт,13) КонецЕсли;
    _Ок=_Ок+Формат(Число(Лев(тТхт,СтрДлина(тТхт)-3)),"Ч10")+Прав(тТхт,3);
    тИдСЗ.Установить("ИдТипа",Лев(_Ок,1));
    тИдСЗ.Установить("ИдВида",Сред(_Ок,2,10));
    тИдСЗ.Установить("ИдОбъекта",Сред(_Ок,12,10));
    тИдСЗ.Установить("ИдИБ",Прав(_Ок,3));
    Если тТипВозврата<>"*" Тогда _Ок=тИдСЗ.Получить(тТипВозврата) КонецЕсли;
    Возврат(_Ок);
КонецФункции //ПолучитьИДыОбъекта
//------------------------------------------------------------------------------
// Способы вызова:
//    - только первый параметр = внутреннее представление
//    - только первый параметр = Ключ(24) = ИдТипа(1)+ИдВида(10)+ИдОбъекта(10)+ИдИБ(3)
//    - в параметрах заданы все четре ИД-а
Функция ПолучитьОбъектПоИДам(Знач ИдТипа,Знач ИдВида="",Знач ИдОбъекта="",Знач ИдИБ="", Знач тБазаОЛЕ="") Экспорт
    Перем _Ок;
    Если ТипЗначенияСтр(ИдТипа)="СписокЗначений" Тогда
        ИдВида=ИдТипа.Получить("ИдВида");
        ИдОбъекта=ИдТипа.Получить("ИдОбъекта");
        ИдИБ=ИдТипа.Получить("ИдИБ");
        ИдТипа=ИдТипа.Получить("ИдТипа");
        Если ТипЗначенияСтр(тБазаОЛЕ)="OLE" Тогда
            _Ок=тБазаОЛЕ.ValueFromStringInternal("{"""+ИдТипа+""",""0"",""0"","""
            +ИдВида+""",""0"",""0"","""+Формат(Число(ИдОбъекта),"Ч10")+Формат(ИдИБ,"С3")+"""}");
        Иначе//Если ТипЗначенияСтр(тБазаОЛЕ)<>"OLE" Тогда
            _Ок=ЗначениеИзСтрокиВнутр("{"""+ИдТипа+""",""0"",""0"","""
            +ИдВида+""",""0"",""0"","""+Формат(Число(ИдОбъекта),"Ч10")+Формат(ИдИБ,"С3")+"""}");
        КонецЕсли;
    ИначеЕсли СтрДлина(ИдТипа)=24 Тогда
        Если Лев(ИдТипа,1)="T" Тогда    // Счет - составляется по-другому
            _Ок="{""T"",""0"",""0"",""0"",""0"",""0"","""+Сред(ИдТипа,2)+"""}";
        Иначе//Если Лев(ИдТипа,1)<>"T" Тогда
            _Ок="{"""+Лев(ИдТипа,1)+""",""0"",""0"","""+Сред(ИдТипа,2,10)+""",""0"",""0"","""+Сред(ИдТипа,12)+"""}";
        КонецЕсли;
        Если ТипЗначенияСтр(тБазаОЛЕ)="OLE" Тогда
            //_Ок=тБазаОЛЕ.ValueFromStringInternal(_Ок);
            _Ок=тБазаОЛЕ.EvalExpr("ЗначениеИзСтрокиВнутр("""+СтрЗаменить(_Ок,"""","""""")+""")");
        Иначе//Если ТипЗначенияСтр(тБазаОЛЕ)<>"OLE" Тогда
            _Ок=ЗначениеИзСтрокиВнутр(_Ок);
        КонецЕсли;
    ИначеЕсли Лев(ИдТипа,1)="{" Тогда
        Если ТипЗначенияСтр(тБазаОЛЕ)="OLE" Тогда
            //_Ок=тБазаОЛЕ.ValueFromStringInternal(ИдТипа);
            _Ок=тБазаОЛЕ.EvalExpr("ЗначениеИзСтрокиВнутр("""+СтрЗаменить(ИдТипа,"""","""""")+""")");
        Иначе//Если ТипЗначенияСтр(тБазаОЛЕ)<>"OLE" Тогда
            _Ок=ЗначениеИзСтрокиВнутр(ИдТипа);
        КонецЕсли;
    КонецЕсли;
    Возврат(_Ок);
КонецФункции //ПолучитьОбъектПоИДам
//------------------------------------------------------------------------------
Функция ИдОбъектаВПвБД(тИд) Экспорт
    Возврат(Лев(тИд,1)+_IdToStr(Число(Сред(тИд,2,10)))+_IdToStr(Число(Сред(тИд,12,10)))+Прав(тИд,3));
КонецФункции //ИдОбъектаВПвБД
//------------------------------------------------------------------------------
Функция ИдОбъектаБДвВП(тИд) Экспорт
    Возврат(Лев(тИд,1)+Формат(СокрЛП(_StrToId(Сред(тИд,2,6))),"Ч10.0")+Формат(СокрЛП(_StrToId(Сред(тИд,8,6))),"Ч10.0")+Прав(тИд,3));
КонецФункции //ИдОбъектаБДвВП
//------------------------------------------------------------------------------

В этих функциях мы видим работу с различными частями внутреннего представления ID, их форматирование и обратное преобразование. Использование _IdToStr и _StrToId позволяет эффективно кодировать и декодировать числовые части ID, что критично для поддержания компактности и возможности отбора.

Решение 2: Использование нескольких реквизитов с определенным видом

Вместо одного реквизита неопределенного вида, мы можем создать несколько реквизитов, каждый из которых будет иметь определенный, но всё ещё неопределенный тип (например, "Документ неопределенного вида" или "Справочник неопределенного вида").

Пошаговая реализация:

  1. Создание нескольких реквизитов: Вместо реквизита Основание с типом "неопределенный", мы создаем, например:

    • ОснованиеДокумент

      с типом "Документ неопределенного вида".
    • ОснованиеСправочник

      с типом "Справочник неопределенного вида".

    Для каждого из этих реквизитов мы можем установить в метаданных флаг "отбор".

  2. Условное заполнение и отбор: При создании вложения, мы заполняем только соответствующий реквизит. Если основанием является документ, заполняем ОснованиеДокумент, если элемент справочника — ОснованиеСправочник. Остальные реквизиты остаются пустыми. При открытии формы списка, мы анализируем тип переданного параметра и устанавливаем отбор по соответствующему реквизиту:

    
    Процедура ПриОткрытии()
        Параметр = Форма.Параметр;
        Если ТипЗначенияСтр(Параметр) = "Документ" Тогда
            УстановитьОтбор("ОснованиеДокумент", Параметр.ТекущийДокумент());
            Форма.Заголовок("Вложения к документу " + Параметр);
        ИначеЕсли ТипЗначенияСтр(Параметр) = "Справочник" Тогда
            УстановитьОтбор("ОснованиеСправочник", Параметр.ТекущийЭлемент());
            Форма.Заголовок("Вложения к справочнику " + Параметр);
        КонецЕсли;
    КонецПроцедуры
    

Этот подход проще в реализации, поскольку опирается на стандартные возможности отбора по реквизитам, имеющим хоть какой-то определенный базовый тип (Документ, Справочник).

Решение 3: Программное управление списком элементов

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

Пошаговая реализация:

  1. Отключение стандартного отбора: Мы не устанавливаем никаких отборов в форме списка через метод УстановитьОтбор().

  2. Формирование списка элементов: В процедуре ПриОткрытии() или в отдельной функции мы программно выбираем все необходимые элементы справочника вложений, а затем фильтруем их в зависимости от переданного параметра (документа или элемента справочника).

  3. Отображение списка: Для отображения сформированного списка мы используем методы ИспользоватьСписокЭлементов() или ИерархическийСписок(1,0), передавая им список значений или массив объектов, которые должны быть показаны пользователю. Это по сути перехватывает управление над тем, что будет выведено в форме списка.

Этот метод дает максимальную гибкость, но требует большего объема кода для управления списком и его обновлением.

Дополнительные рекомендации и советы

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

← На главную