Как загрузить данные из регистра сведений в табличную часть объекта в 1С и настроить автоматический пересчет колонок?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке в среде «1С:Предприятие 8» программисты регулярно сталкиваются с задачей переноса данных из различных регистров (сведений, накопления) в табличные части документов, справочников или обработок. На первый взгляд задача кажется тривиальной: написать запрос, обойти выборку и заполнить строки. Однако здесь кроется множество подводных камней, начиная от банальных синтаксических ошибок и заканчивая неоптимальным кодом, который замедляет работу системы на реальных базах данных.

В рамках этой статьи мы подробно разберем, как правильно переносить данные из регистра сведений в табличную часть объекта, исправим типичные ошибки начинающих разработчиков, рассмотрим альтернативные (более быстрые) методы заполнения коллекций, а также выясним, как настроить автоматическое перемножение колонок (например, Количество * Цена = Сумма) прямо на форме без дублирования кода.

---

Часть 1. Разбор типичных ошибок при заполнении табличной части

Рассмотрим исходный код, который часто пытаются использовать начинающие разработчики для решения этой задачи (на примере процедуры ЗапросКонтрактов):


&НаСервере
Процедура ЗапросКонтрактов()
    Запрос = Новый Запрос; 
    Запрос.Текст = 
        "ВЫБРАТЬ 
        | Спецификация.СпецификацияКонтрактаНомерЗакупки, 
        | Спецификация.СпецификацияНаименование 
        |ИЗ 
        | РегистрСведений.Спецификация КАК Спецификация"; 
        
    РезультатЗапроса = Запрос.Выполнить(); 
    Выборка = РезультатЗапроса.Выбрать(); 
    
    Пока Выборка.Следующий() Цикл 
        Объект.НомерЗакупки = Выборка.СпецификацияНомерЗакупки; 
        НоваяСтрока = Объект.Поставка.Добавить(); 
        ЗаполнитьЗначенияСвойств(НоваяСтрока.Выборка); 
    КонецЦикла; 
КонецПроцедуры

При попытке выполнить такой код платформа выдает ошибку: «Недостаточно фактических параметров». Выясним причину этой ошибки и проанализируем слабые места данного алгоритма.

Причина ошибки в методе ЗаполнитьЗначенияСвойств

Функция ЗаполнитьЗначенияСвойств() предназначена для копирования значений свойств одного объекта в свойства другого, у которых совпадают имена. Метод принимает как минимум два обязательных параметра:

  1. Приемник — объект или строка, в свойства которой будут записываться данные.
  2. Источник — объект или строка, из свойств которой данные считываются.

В строке ЗаполнитьЗначенияСвойств(НоваяСтрока.Выборка); разработчик допустил синтаксическую ошибку: во-первых, вместо запятой между приемником и источником была поставлена точка; во-вторых, в метод был передан всего один параметр. Исходя из синтаксиса, платформа ожидает увидеть конструкцию:

ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);

Проблема несоответствия имен полей

Даже если мы исправим синтаксическую ошибку и напишем метод правильно, данные в табличную часть могут не загрузиться (строки добавятся, но останутся пустыми). Это происходит из-за того, что имена полей в источнике (выборке запроса) и в приемнике (реквизитах табличной части Объект.Поставка) не совпадают.

В запросе выбираются поля СпецификацияКонтрактаНомерЗакупки и СпецификацияНаименование. В то же время реквизиты вашей табличной части, скорее всего, называются лаконично, например, НомерЗакупки и Наименование. Метод ЗаполнитьЗначенияСвойств сравнивает имена один в один, и если совпадений не находит, то просто пропускает эти поля.

---

Часть 2. Правильные способы заполнения табличной части

Для решения поставленной задачи мы можем пойти двумя путями: использовать классическое явное присвоение значений, применить исправленный метод ЗаполнитьЗначенияСвойств, либо воспользоваться самым производительным способом — методом Загрузить. Разберем по шагам каждый из них.

Решение №1. Явное присвоение свойств в цикле

Этот способ максимально нагляден и прост для понимания. Мы вручную указываем, какое поле выборки в какой реквизит строки табличной части должно записаться. В этом случае нам не важно, совпадают ли имена полей в запросе и в табличной части.


&НаСервере
Процедура ЗаполнитьТЧ_ЯвнымПрисвоением()
    
    // Очищаем табличную часть перед заполнением, если это необходимо
    Объект.Поставка.Очистить();
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Спецификация.СпецификацияКонтрактаНомерЗакупки КАК НомерЗакупкиЗапроса,
        |   Спецификация.СпецификацияНаименование КАК НаименованиеЗапроса
        |ИЗ
        |   РегистрСведений.Спецификация КАК Спецификация";
        
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        // Добавляем новую строку в табличную часть "Поставка"
        НоваяСтрока = Объект.Поставка.Добавить();
        
        // Напрямую заполняем реквизиты строки ТЧ из полей выборки
        НоваяСтрока.НомерЗакупки = Выборка.НомерЗакупкиЗапроса;
        НоваяСтрока.Наименование = Выборка.НаименованиеЗапроса;
    КонецЦикла;
    
КонецПроцедуры

Преимущества: Полный контроль над процессом. Код легко читать и отлаживать. Нет строгой привязки к названиям полей в базе данных.

Недостатки: Если реквизитов в табличной части много (например, 20-30 колонок), код превращается в огромный список однотипных строк присвоения, что усложняет поддержку разработки.

Решение №2. Использование ЗаполнитьЗначенияСвойств с псевдонимами в запросе

Чтобы лаконично заполнить множество колонок в цикле, мы можем использовать встроенную функцию ЗаполнитьЗначенияСвойств. Но чтобы она отработала корректно, нам нужно переименовать поля в запросе с помощью конструкции КАК так, чтобы они полностью соответствовали именам реквизитов нашей табличной части.

Допустим, табличная часть Поставка имеет реквизиты: НомерЗакупки и Наименование. Тогда код будет выглядеть следующим образом:


&НаСервере
Процедура ЗаполнитьТЧ_Свойствами()
    
    Объект.Поставка.Очистить();
    
    Запрос = Новый Запрос;
    // Назначаем псевдонимы полям выборки, совпадающие с реквизитами ТЧ
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Спецификация.СпецификацияКонтрактаНомерЗакупки КАК НомерЗакупки,
        |   Спецификация.СпецификацияНаименование КАК Наименование
        |ИЗ
        |   РегистрСведений.Спецификация КАК Спецификация";
        
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = Объект.Поставка.Добавить();
        // Метод автоматически сопоставит "НомерЗакупки" и "Наименование"
        ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
    КонецЦикла;
    
КонецПроцедуры

Преимущества: Компактный и универсальный код. При добавлении новых реквизитов в табличную часть достаточно просто добавить их в запрос с нужным псевдонимом — сам цикл внутри изменять не придется.

Решение №3. Быстрая загрузка без циклов через метод Загрузить (Best Practice)

Если перед заполнением табличной части ее требуется полностью очистить, то построчный обход выборки в цикле является неоптимальным решением. В платформе 1С реализован высокопроизводительный метод Загрузить(), который позволяет передать всю таблицу значений в табличную часть одной командой. Этот метод работает на уровне платформы гораздо быстрее, так как минимизирует количество обращений к серверу и транзакций в памяти.

Для его реализации имена колонок в запросе также должны строго совпадать с именами реквизитов табличной части:


&НаСервере
Процедура ЗаполнитьТЧ_БыстраяЗагрузка()
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Спецификация.СпецификацияКонтрактаНомерЗакупки КАК НомерЗакупки,
        |   Спецификация.СпецификацияНаименование КАК Наименование
        |ИЗ
        |   РегистрСведений.Спецификация КАК Спецификация";
        
    РезультатЗапроса = Запрос.Выполнить();
    
    // Выгружаем результат запроса во временную Таблицу Значений в памяти
    ТаблицаРезультата = РезультатЗапроса.Выгрузить();
    
    // Метод Загрузить автоматически очищает ТЧ и заполняет её данными из Таблицы Значений
    Объект.Поставка.Загрузить(ТаблицаРезультата);
    
КонецПроцедуры

Преимущества: Максимальная скорость работы алгоритма. Отсутствие циклов, лаконичность кода.

Важное ограничение: Метод Загрузить() полностью затирает существующие строки в табличной части. Если вам нужно дописать новые строки к уже существующим, этот метод не подойдет — в таком случае используйте Решение №2.

---

Часть 3. Автоматический расчет колонок в табличной части

Второй популярный вопрос, возникающий сразу после заполнения табличной части: «Можно ли настроить колонки так, чтобы они автоматически перемножались между собой (например, Количество * Цена = Сумма), не прописывая для этого сложные вычисления в коде?»

Ответ однозначен: в платформе 1С нет декларативного механизма (настройки формул в свойствах колонок или реквизитов на уровне конфигуратора), который производил бы такие вычисления автоматически. Любые пересчеты необходимо описывать кодом.

Однако мы можем организовать этот процесс грамотно, лаконично и красиво, избежав дублирования программного кода. Проанализируем, как это сделать правильно по стандартам разработки 1С.

Архитектурное решение задачи пересчета

Для реализации пересчета необходимо использовать интерактивные события элементов формы. У полей ввода на форме, отображающих колонки табличной части, есть событие ПриИзменении. Именно в этих обработчиках мы и будем производить расчет.

Чтобы не писать одну и ту же формулу умножения отдельно для колонки "Количество" и отдельно для "Цены", мы создадим одну вспомогательную процедуру на форме и будем вызывать её из соответствующих обработчиков событий.

Ниже представлен пошаговый пример реализации на управляемой форме:


&НаКлиенте
Процедура ПоставкаКоличествоПриИзменении(Элемент)
    // Пользователь изменил количество — вызываем общий расчет суммы
    РассчитатьСуммуТекущейСтроки();
КонецПроцедуры

&НаКлиенте
Процедура ПоставкаЦенаПриИзменении(Элемент)
    // Пользователь изменил цену — вызываем общий расчет суммы
    РассчитатьСуммуТекущейСтроки();
КонецПроцедуры

&НаКлиенте
Процедура РассчитатьСуммуТекущейСтроки()
    
    // Получаем строку табличной части, на которой сейчас сфокусирован пользователь
    ТекущаяСтрока = Элементы.Поставка.ТекущиеДанные;
    
    // Проверяем, что строка действительно выбрана и существует
    Если ТекущаяСтрока <> Неопределено Тогда
        
        // Производим расчет с обязательным округлением результата до 2 знаков
        // Это стандарт ведения денежных и числовых расчетов
        ТекущаяСтрока.Сумма = Окр(ТекущаяСтрока.Количество * ТекущаяСтрока.Цена, 2);
        
    КонецЕсли;
    
КонецПроцедуры

В данном примере свойство Элементы.Поставка.ТекущиеДанные возвращает объект строки, с которой сейчас взаимодействует пользователь. Изменяя свойства этого объекта на клиенте, мы мгновенно обновляем данные на экране без лишних серверных вызовов.

Организация обратного расчета (Цена из Суммы)

Довольно часто бизнес-логика требует возможности ручного ввода итоговой суммы пользователем. В таком случае система должна сама рассчитать цену делением суммы на количество. При написании таких алгоритмов критически важно обрабатывать проверку на деление на ноль, иначе при случайном вводе нулевого количества программа завершится с критической ошибкой.

Посмотрим на пример правильной обработки события изменения суммы:


&НаКлиенте
Процедура ПоставкаСуммаПриИзменении(Элемент)
    
    ТекущаяСтрока = Элементы.Поставка.ТекущиеДанные;
    
    Если ТекущаяСтрока <> Неопределено Тогда
        
        // Защита от деления на ноль
        Если ТекущаяСтрока.Количество <> 0 Тогда
            ТекущаяСтрока.Price = Окр(ТекущаяСтрока.Сумма / ТекущаяСтрока.Количество, 2);
        Иначе
            // Если количество равно нулю, обнуляем цену или оставляем без изменений
            ТекущаяСтрока.Цена = 0;
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

---

Часть 4. Вынос логики расчетов в общий модуль (Best Practice)

Если в вашей конфигурации присутствует множество документов (например, "Поступление товаров", "Реализация товаров", "Заказ покупателя"), в каждом из которых есть аналогичные колонки "Количество", "Цена" и "Сумма", копировать этот код в форму каждого документа — это грубейшая ошибка проектирования (дублирование кода).

Согласно стандартам разработки фирмы "1С", бизнес-логику расчетов следует выносить в неглобальные общие модули.

Шаг 1. Создание общего модуля

Создайте новый общий модуль в конфигураторе с именем, например, ОбработкаТабличныхЧастейКлиент. В свойствах модуля обязательно установите флаг Клиент (управляемое приложение) и снимите флаг Сервер.

Шаг 2. Размещение процедуры расчета

Вставьте в общий модуль процедуру с ключевым словом Экспорт, передавая в нее строку табличной части в качестве параметра:


// Модуль: ОбработкаТабличныхЧастейКлиент

Процедура РассчитатьСуммуСтроки(СтрокаТаблицы) Экспорт
    
    СтрокаТаблицы.Сумма = Окр(СтрокаТаблицы.Количество * СтрокаТаблицы.Цена, 2);
    
КонецПроцедуры

Шаг 3. Вызов метода из форм документов

Теперь в форме любого документа код обработчиков событий станет невероятно лаконичным, чистым и легко поддающимся изменениям в будущем:


&НаКлиенте
Процедура ПоставкаКоличествоПриИзменении(Элемент)
    СтрокаТЧ = Элементы.Поставка.ТекущиеДанные;
    // Вызываем расчет из общего модуля
    ОбработкаТабличныхЧастейКлиент.РассчитатьСуммуСтроки(СтрокаТЧ);
КонецПроцедуры

&НаКлиенте
Процедура ПоставкаЦенаПриИзменении(Элемент)
    СтрокаТЧ = Элементы.Поставка.ТекущиеДанные;
    ОбработкаТабличныхЧастейКлиент.РассчитатьСуммуСтроки(СтрокаТЧ);
КонецПроцедуры

Если со временем в вашей компании изменится логика расчета (например, появится необходимость учитывать скидки или НДС при расчете суммы), вам потребуется внести изменения всего в одну процедуру в общем модуле ОбработкаТабличныхЧастейКлиент, и расчет моментально обновится во всех документах системы.

← На главную