При работе в программной среде «1С:Предприятие 7.7» часто возникает задача оптимизировать интерфейс для ускорения ввода документов. Одной из таких задач является автоматическое открытие справочника контрагентов на конкретной группе (папке) в зависимости от контекста. Например, когда мы оформляем документ РасходнаяНакладная, логично сразу показывать пользователю группу «Клиенты» (или «Покупатели»), а при создании документа ПриходнаяНакладная — группу «Поставщики».
В этой статье мы подробно разберем, как решить эту задачу правильно, проанализируем ошибки из кода на форумах и предложим три различных архитектурных варианта реализации. Также мы затронем важный вопрос юзабилити и организации данных в справочнике.
Часто начинающие разработчики пытаются решить задачу «в лоб» и пишут код, который либо работает некорректно, либо вызывает критические ошибки выполнения. Проанализируем типичный пример неудачного кода, который часто встречается на форумах:
// Ошибочный код! Не используйте его в своих конфигурациях.
Процедура ПриНачалеВыбораЗначения(РеквИмя, ФСО)
Если РеквИмя = "Контрагент" Тогда
КонтекстВыбораКонтрагента = Контрагент.ТекущийЭлемент();
Если КонтекстВыбораКонтрагента.Выбран() <> 1 Тогда
НачГруппаКонтрагентов = СоздатьОбъект("Справочник.Contragents");
// ... поиск по наименованию ...
КонтекстВыбораКонтрагента.ИспользоватьРодителя(НачГруппаКонтрагентов);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Выясним причину, почему данный код не будет работать в реальной базе 1С 7.7:
КонтекстВыбораКонтрагента получает значение через Контрагент.ТекущийЭлемент(). Это возвращает ссылку на элемент (тип значения «Справочник»). Метод ИспользоватьРодителя() не существует у элементов-ссылок. Он может быть вызван только у объекта типа справочник, созданного через СоздатьОбъект(), либо внутри модуля самой формы списка.ФСО = 0 и вызвать функцию ОткрытьФорму(), система откроет форму списка справочника немодально. Программа продолжит выполнение кода, не дожидаясь выбора пользователя, и выбранное значение не вернется в документ автоматически.НайтиПоНаименованию() замедляют работу системы и делают код запутанным и сложным для поддержки.Теперь рассмотрим правильные, протестированные и надежные способы реализации этой задачи.
Этот способ является самым простым и не требует изменения формы списка самого справочника контрагентов. Выбор будет происходить в модальном режиме: открывается окно справочника, пользователь выбирает элемент, и окно закрывается, возвращая значение в реквизит документа.
Разберем по шагам, как написать этот код. Нам понадобится предопределенная процедура модуля формы документа ПриНачалеВыбораЗначения. Пропишем следующий код в модуле формы документа «Расходная накладная»:
Процедура ПриНачалеВыбораЗначения(РеквИмя, ФСО)
// Проверяем, что пользователь нажал кнопку выбора именно у реквизита "Контрагент"
Если РеквИмя = "Контрагент" Тогда
// Отменяем стандартное открытие справочника платформой
ФСО = 0;
// Создаем объект для работы со справочником контрагентов
СпрКонтр = СоздатьОбъект("Справочник.Контрагенты");
ГруппаКонтр = СоздатьОбъект("Справочник.Контрагенты");
// Пытаемся найти нужную группу по ее точному наименованию
// Второй параметр (0) означает поиск по всей иерархии справочника
// Третий параметр (1) указывает на точный поиск (полное совпадение)
Если ГруппаКонтр.НайтиПоНаименованию("Клиенты", 0, 1) = 1 Тогда
// Устанавливаем фильтр по родителю.
// Второй параметр (0) запрещает пользователю выходить за пределы этой папки (вверх по иерархии)
// Если вы хотите разрешить пользователю выходить из папки, укажите здесь 1
СпрКонтр.ИспользоватьРодителя(ГруппаContr.ТекущийЭлемент(), 0);
КонецЕсли;
// Открываем форму списка справочника в модальном режиме выбора
// Если пользователь выбрал контрагента и нажал Enter/Дважды кликнул
Если СпрКонтр.Выбрать("Выберите клиента из папки ""Клиенты""", "ФормаСписка") = 1 Тогда
// Записываем выбранный элемент в реквизит шапки документа
Контрагент = СпрКонтр.ТекущийЭлемент();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Для документа «Приходная накладная» код будет абсолютно идентичным, за исключением одной строки: вместо наименования группы "Клиенты" вам нужно будет указать "Поставщики".
Если в вашей системе нежелательно использовать модальные окна (например, пользователю во время выбора контрагента нужно иметь возможность переключаться на другие окна программы), мы можем использовать передачу контекста формы.
Посмотрим на пример реализации в модуле формы документа:
Процедура ПриНачалеВыбораЗначения(РеквИмя, ФСО)
Если РеквИмя = "Контрагент" Тогда
// Отменяем стандартный выбор
ФСО = 0;
// Объявляем переменную для хранения контекста открываемой формы
Перем КонтекстФормы;
// Открываем форму списка справочника Контрагенты и получаем её контекст
ОткрытьФорму("Справочник.Контрагенты.ФормаСписка", КонтекстФормы);
// Находим нужного родителя
ГруппаКонтр = СоздатьОбъект("Справочник.Контрагенты");
Если ГруппаКонтр.НайтиПоНаименованию("Клиенты", 0, 1) = 1 Тогда
// Напрямую через полученный контекст устанавливаем родителя в открытой форме
КонтекстФормы.ИспользоватьРодителя(ГруппаКонтр.ТекущийЭлемент(), 0);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
// Поскольку форма открыта немодально, выбор вернется в предопределенную процедуру документа
Процедура ОбработкаВыбораЗначения(ВыбрЗнач, ИдентРекв, ФСО)
Если ИдентРекв = "Контрагент" Тогда
// Заполняем реквизит выбранным значением
Контрагент = ВыбрЗнач;
КонецЕсли;
КонецПроцедуры
Этот подход считается классическим стандартом разработки на платформе 1С 7.7. Он часто применяется в типовых конфигурациях от компании «1С» (например, в «Торговля и Склад 7.7» или «Бухгалтерский учет 7.7»). Суть метода заключается в том, что мы открываем форму списка справочника стандартным образом, но передаем в качестве параметра открываемой формы нужную нам группу.
Шаг 1. Пропишем код вызова в модуле формы документа:
Процедура ПриНачалеВыбораЗначения(РеквИмя, ФСО)
Если РеквИмя = "Контрагент" Тогда
// Отменяем стандартный механизм
ФСО = 0;
ГруппаКонтр = СоздатьОбъект("Справочник.Contragents");
Если ГруппаКонтр.НайтиПоНаименованию("Клиенты", 0, 1) = 1 Тогда
// Передаем ссылку на группу контрагентов третьим параметром в функцию ОткрытьФорму()
ОткрытьФорму("Справочник.Контрагенты.ФормаСписка", ГруппаКонтр.ТекущийЭлемент());
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Шаг 2. Теперь нам необходимо обработать переданный параметр в модуле Формы Списка самого справочника Контрагенты. Откроем форму списка справочника в конфигураторе, перейдем в ее модуль и найдем предопределенную процедуру ПриОткрытии:
Процедура ПриОткрытии()
// Проверяем, был ли передан параметр при открытии формы
Если ПустоеЗначение(Форма.Параметр) = 0 Тогда
// Проверяем, является ли переданный параметр группой справочника
Если Форма.Параметр.ЭтоГруппа() = 1 Тогда
// Устанавливаем эту группу в качестве родителя в форме списка
// Второй параметр (0) запрещает выходить «вверх» к корню справочника
ИспользоватьРодителя(Форма.Параметр, 0);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Рассматривая этот технический вопрос, мы обязательно должны проанализировать архитектуру хранения данных в вашей информационной базе. На форуме справедливо прозвучал вопрос: «Что делать, когда клиент является одновременно покупателем машинок и продавцом колес?»
Если вы делите контрагентов по жестким физическим папкам («Клиенты» и «Поставщики»), вы неизбежно столкнетесь со следующими проблемами:
Гораздо более правильным архитектурным решением является отказ от деления контрагентов по папкам на основе их роли. Вместо этого в справочнике Контрагенты создаются два реквизита типа «Число» (длиной 1) или «Флаг»:
Покупатель (Да/Нет)Поставщик (Да/Нет)При таком подходе один и тот же контрагент может лежать в любой папке справочника (например, структурированной по регионам), но при этом у него могут стоять обе галочки. При выборе контрагента в документах мы будем использовать не ограничение по родителю, а отбор по значению реквизита.
Для реализации этого метода необходимо в конфигураторе у реквизитов Покупатель и Поставщик справочника контрагентов установить признак «Отбор по реквизиту». Код выбора в документе будет выглядеть следующим образом:
Процедура ПриНачалеВыбораЗначения(РеквИмя, ФСО)
Если РеквИмя = "Контрагент" Тогда
ФСО = 0;
Перем КонтекстФормы;
ОткрытьФорму("Справочник.Контрагенты.ФормаСписка", КонтекстФормы);
// В зависимости от вида текущего документа устанавливаем соответствующий отбор
Если Вид() = "РасходнаяНакладная" Тогда
// Показываем в списке только тех контрагентов, у которых стоит галочка "Покупатель"
КонтекстФормы.УстановитьОтбор("Покупатель", 1);
ИначеЕсли Вид() = "ПриходнаяНакладная" Тогда
// Показываем в списке только тех контрагентов, у которых стоит галочка "Поставщик"
КонтекстФормы.УстановитьОтбор("Поставщик", 1);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Используя этот метод, вы избавите базу данных от дубликатов контрагентов, существенно упростите ведение бухгалтерского и управленческого учета, а также сделаете работу пользователей максимально удобной и интуитивно понятной.