В процессе разработки в среде 1С:Предприятие 8.3 часто возникает задача автоматизации заполнения нормативно-справочной информации. Одной из классических подзадач является создание новых элементов справочника на основании данных, введенных пользователем в табличную часть документа (например, при поступлении новых товаров, которых еще нет в базе). В этой статье мы подробно разберем, как правильно реализовать этот механизм, проанализируем типичные ошибки и рассмотрим лучшие практики программирования.
Рассмотрим ситуацию: у нас есть документ ПриходТоваров, в табличной части которого (допустим, ТабличнаяЧасть1) имеется строковый реквизит НовыйТовар. Начинающие разработчики часто пытаются обратиться к этому реквизиту напрямую через имя табличной части, используя код следующего вида:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
СпрТовар = Справочники.Товары.СоздатьЭлемент();
СпрТовар.Наименование = ТабличнаяЧасть1.НовыйТовар; // Ошибка здесь!
СпрТовар.Записать();
КонецПроцедуры
При выполнении этого кода система выдаст ошибку: «Поле объекта не обнаружено (НовыйТовар)». Давайте выясним причину. Объект ТабличнаяЧасть1 является коллекцией (типа ДанныеФормыКоллекция или РезультатЗапроса, в зависимости от контекста), которая содержит множество строк. У самой коллекции нет реквизита НовыйТовар — этот реквизит есть только у каждой конкретной строки этой коллекции. Чтобы получить доступ к данным, нам необходимо перебрать все строки таблицы в цикле.
Для корректной записи данных нам нужно реализовать обход коллекции. Разберем по шагам правильный алгоритм:
Для Каждого... Из... Цикл для обращения к строкам табличной части.СоздатьЭлемент().Записать() для сохранения данных в базе.Посмотрим на пример исправленного кода:
// Перебираем все строки табличной части документа
Для Каждого СтрокаТЧ Из ТабличнаяЧасть1 Цикл
// Создаем новый объект справочника "Товары"
НовыйЭлемент = Справочники.Товары.СоздатьЭлемент();
// Заполняем наименование из строкового реквизита текущей строки
НовыйЭлемент.Наименование = СтрокаТЧ.НовыйТовар;
// Фиксируем изменения в базе данных
НовыйЭлемент.Записать();
КонецЦикла;
Важно проанализировать ситуацию, в какой момент должен срабатывать этот код. В исходном вопросе автор поместил код в обработчик ОбработкаПроведения. Рассмотрим подробнее, почему это плохая практика:
Рекомендуемые варианты размещения кода:
Чтобы не засорять базу данных повторяющимися элементами, перед созданием нового объекта необходимо проверить, не существует ли уже товар с таким наименованием. Для этого используем метод НайтиПоНаименованию() менеджера справочника.
Проанализируем улучшенный код с проверкой на существование:
Для Каждого СтрокаТЧ Из Объект.ТабличнаяЧасть1 Цикл
// Проверяем, не пустая ли строка
Если ПустаяСтрока(СтрокаТЧ.НовыйТовар) Тогда
Продолжить;
КонецЕсли;
// Ищем элемент в справочнике по точному совпадению наименования
НайденнаяСсылка = Справочники.Товары.НайтиПоНаименованию(СтрокаТЧ.НовыйТовар, Истина);
// Если ссылка пустая - значит товара еще нет, создаем его
Если НайденнаяСсылка.Пустая() Тогда
НовыйЭлемент = Справочники.Товары.СоздатьЭлемент();
НовыйЭлемент.Наименование = СтрокаТЧ.НовыйТовар;
Попытка
НовыйЭлемент.Записать();
// По желанию: записываем созданную ссылку обратно в документ
// СтрокаТЧ.ТоварСсылка = НовыйЭлемент.Ссылка;
Исключение
Сообщить("Ошибка при создании товара: " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЦикла;
Если в табличной части документа сотни или тысячи строк, запись каждого элемента по отдельности будет работать медленно, так как каждый вызов Записать() открывает и закрывает отдельную транзакцию в СУБД. В такой ситуации мы рекомендуем обернуть весь цикл в одну общую транзакцию. Это значительно ускорит процесс.
Рассмотрим пример использования механизмов транзакций:
НачатьТранзакцию();
Попытка
Для Каждого СтрокаТЧ Из Объект.ТабличнаяЧасть1 Цикл
// Логика поиска и создания элемента...
// ...
КонецЦикла;
ЗафиксироватьТранзакцию();
Сообщить("Справочник успешно обновлен.");
Исключение
ОтменитьТранзакцию();
Сообщить("Произошла ошибка, изменения не сохранены: " + ОписаниеОшибки());
КонецПопытки;
В идеальной архитектуре 1С реквизит табличной части должен иметь тип не Строка, а СправочникСсылка.Товары. Если ваша задача позволяет, лучше организовать работу так:
СправочникСсылка.АвтоПодбор или ОкончаниеВводаТекста система программно создает элемент справочника.Это обеспечит надежную связь между документами и справочниками. При простом хранении текста любая опечатка (лишний пробел или буква) приведет к тому, что отчеты не смогут правильно сгруппировать данные по товару. Использование объектов СправочникОбъект и СправочникСсылка — это фундамент правильной разработки на платформе 1С.
Подводя итог, отметим, что для успешного решения задачи необходимо всегда помнить о коллекционной природе табличных частей, обязательно проверять данные на уникальность и выбирать правильное событие в жизненном цикле объекта для выполнения программного кода.