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