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