В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача автоматизации ввода данных. Типичный пример: пользователь выбирает значение в одном поле (перечислении), и система должна автоматически подставить логически связанное значение в другое поле (тоже перечисление). Несмотря на кажущуюся простоту, начинающие разработчики часто сталкиваются с ошибками типизации и непониманием разницы между контекстом клиента и сервера.
В этой статье мы подробно разберем, как правильно реализовать такую связь, проанализируем типичные ошибки и рассмотрим продвинутые методы архитектурного решения этой задачи.
Представим ситуацию: на форме документа или справочника есть два реквизита: НаименованиеАктива и ВидАктива. Оба имеют тип ПеречислениеСсылка. Нам нужно, чтобы при выборе в поле "Наименование" значения "Нематериальные активы", поле "Вид" автоматически принимало значение "Внеоборотные".
Рассмотрим код, который часто пишут новички, и выясним, почему он не работает:
&НаКлиенте
Процедура НаименованиеПриИзменении(Элемент)
// ОШИБКА 1: Сравнение ссылки с текстовой строкой
Если ЭтаФорма.Элементы.Наименование = "НематериальныеАктивы" Тогда
// ОШИБКА 2: Попытка присвоить значение элементу формы, а не данным
// ОШИБКА 3: Использование коллекции "Перечисления" на клиенте
ЭтаФорма.Элементы.ВидАктива = Перечисления.ВидыАктивов.Внеоборотные;
КонецЕсли
КонецПроцедуры
Проанализируем причины неудачи. Во-первых, элементы перечисления в 1С — это не строки, а ссылочные типы. Сравнивать их с текстом в кавычках нельзя. Во-вторых, коллекция Перечисления доступна только на стороне сервера. В-третьих, значения нужно присваивать реквизиту данных (например, Объект.ВидАктива), а не элементу управления на форме.
Для решения задачи на клиенте (в процедуре ПриИзменении) необходимо использовать функцию ПредопределенноеЗначение. Эта функция позволяет получить ссылку на объект метаданных, не обращаясь к серверу в явном виде (точнее, платформа делает это максимально оптимизировано).
Разберем корректный код:
&НаКлиенте
Процедура НаименованиеПриИзменении(Элемент)
// Сравниваем значение реквизита с предопределенным значением перечисления
Если Объект.Наименование = ПредопределенноеЗначение("Перечисление.НаименованияАктивов.НематериальныеАктивы") Тогда
// Устанавливаем значение связанного реквизита
Объект.ВидАктива = ПредопределенноеЗначение("Перечисление.ВидыАктивов.Внеоборотные");
ИначеЕсли Объект.Наименование = ПредопределенноеЗначение("Перечисление.НаименованияАктивов.Запасы") Тогда
Объект.ВидАктива = ПредопределенноеЗначение("Перечисление.ВидыАктивов.Оборотные");
КонецЕсли;
КонецПроцедуры
Обратите внимание на синтаксис: в функции ПредопределенноеЗначение тип указывается в единственном числе (Перечисление), а не во множественном, как мы привыкли писать на сервере.
Если вариантов выбора много, конструкция Если...ИначеЕсли превратит ваш программный код в трудночитаемое полотно. В таких случаях мы рекомендуем использовать объект Соответствие. Это позволит сделать логику более гибкой и масштабируемой.
Посмотрим, как это реализовать. Сначала создадим функцию на сервере, которая подготовит нам карту зависимостей, а затем используем ее на клиенте:
&НаСервере
Функция ПолучитьКартуСвязей()
Связи = Новый Соответствие;
Связи.Вставить(Перечисления.НаименованияАктивов.НематериальныеАктивы, Перечисления.ВидыАктивов.Внеоборотные);
Связи.Вставить(Перечисления.НаименованияАктивов.ОсновныеСредства, Перечисления.ВидыАктивов.Внеоборотные);
Связи.Вставить(Перечисления.НаименованияАктивов.Запасы, Перечисления.ВидыАктивов.Оборотные);
Возврат Связи;
КонецФункции
&НаКлиенте
Процедура НаименованиеПриИзменении(Элемент)
КартаСвязей = ПолучитьКартуСвязей(); // В реальной задаче лучше кэшировать это значение
НужныйВид = КартаСвязей.Получить(Объект.Наименование);
Если НужныйВид <> Неопределено Тогда
Объект.ВидАктива = НужныйВид;
КонецЕсли;
КонецПроцедуры
Прописывать жесткие связи между перечислениями прямо в коде (так называемый "хардкодинг") — это не всегда удачное решение. Если завтра логика учета изменится и "Запасы" нужно будет относить к другой категории, вам придется вносить изменения в конфигурацию и обновлять базу у всех пользователей.
Проанализируем более профессиональный метод: использование Регистра сведений. Мы можем создать непериодический регистр сведений СвязиТиповАктивов с двумя измерениями:
Теперь в коде нам достаточно просто прочитать значение из регистра:
&НаСервереБезКонтекста
Функция ОпределитьВидПоРегистру(Знач Наименование)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СвязиТиповАктивов.Вид КАК Вид
|ИЗ
| РегистрСведений.СвязиТиповАктивов КАК СвязиТиповАктивов
|ГДЕ
| СвязиТиповАктивов.Наименование = &Наименование";
Запрос.УстановитьПараметр("Наименование", Наименование);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Вид;
КонецЕсли;
Возврат ПредопределенноеЗначение("Перечисление.ВидыАктивов.ПустаяСсылка");
КонецФункции
Такой подход позволяет администратору системы менять логику связей "на лету" без привлечения программиста, просто редактируя записи в регистре.
При реализации автоматического заполнения важно не забывать про следующие моменты:
Наименование. Логично будет также очистить и ВидАктива, используя ПустаяСсылка().Связи параметров выбора в свойствах элемента формы.Объект.ИмяРеквизита автоматически устанавливает признак "Модифицированность" (звездочка в заголовке формы), что важно для работы стандартных механизмов сохранения.Мы рассмотрели путь от простого исправления ошибок в коде до создания гибкой архитектуры на базе регистров сведений. Выбор конкретного метода зависит от масштаба вашей задачи и требований к гибкости системы.