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

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

Автоматизация заполнения данных — одна из ключевых задач при разработке в среде 1С. Типичный сценарий: пользователь выбирает товар в табличной части документа, а система должна мгновенно подтянуть актуальную цену из регистра сведений. В этой статье мы подробно разберем, как реализовать этот механизм правильно, эффективно и с соблюдением стандартов разработки 1С.

Мы проанализируем различные подходы — от простых запросов до оптимизированных методов работы с большими объемами данных. Разберем особенности работы в управляемом приложении (клиент-серверное взаимодействие) и выясним, как избежать типичных ошибок новичков, таких как «запрос в цикле».

Постановка задачи и архитектура решения

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

Наша цель: при изменении значения в колонке Номенклатура автоматически найти цену в регистре и записать её в колонку Цена текущей строки.

Шаг 1. Организация клиент-серверного вызова

В управляемом приложении работа с базой данных (регистрами) возможна только на сервере. Поэтому нам необходимо создать обработчик события «ПриИзменении» для поля ввода номенклатуры в таблице. Рассмотрим, как правильно передать контекст на сервер.

На клиенте мы определяем текущую строку, чтобы знать, куда именно записывать результат. Важно использовать идентификатор строки, а не индекс, чтобы избежать проблем при сортировке таблицы пользователем.

Разберем пример кода на клиенте:


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

Шаг 2. Получение данных через менеджер регистра (для непериодических регистров)

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

Рассмотрим серверную функцию:


&НаСервереБезКонтекста
Функция ПолучитьЦенуНаСервере(НоменклатураСсылка)
    
    // Создаем структуру отбора по измерениям регистра
    Отбор = Новый Структура;
    Отбор.Вставить("Запчасть", НоменклатураСсылка);
    
    // Получаем значения ресурсов (вернется структура)
    ЗначенияРесурсов = РегистрыСведений.Цены.Получить(Отбор);
    
    // Возвращаем сумму из ресурса регистра
    Возврат ЗначенияРесурсов.Сумма;
    
КонецФункции

Важный нюанс: метод Получить() всегда возвращает структуру. Если запись в регистре не найдена, в структуре будут пустые значения типов, соответствующих ресурсам (например, 0 для чисел). Это удобно, так как код не вызовет ошибку.

Шаг 3. Использование запроса для периодических регистров

В реальных конфигурациях цены обычно хранятся в периодических регистрах. В этом случае нам нужно получить цену на конкретную дату (дату документа). Для этого лучше всего подходит запрос к виртуальной таблице СрезПоследних.

Проанализируем ситуацию, когда нужно учитывать дату документа:


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

Обратите внимание на использование параметров в виртуальной таблице СрезПоследних. Фильтрация по номенклатуре внутри параметров (Запчасть = &Запчасть) работает значительно быстрее, чем фильтрация через условие ГДЕ после выполнения запроса.

Шаг 4. Оптимизация производительности: Пакетное заполнение

Рассмотрим ситуацию, когда пользователь нажимает кнопку «Обновить цены» для всего документа. Самая большая ошибка здесь — запускать цикл по строкам табличной части и в каждой итерации делать запрос к базе данных. Это приведет к катастрофическому падению производительности при наличии 100+ строк.

Правильный подход — выполнить один запрос для всей таблицы. Разберем этот алгоритм по шагам:

  1. Выгружаем все уникальные ссылки на номенклатуру из табличной части в массив.
  2. Передаем этот массив в запрос.
  3. В запросе получаем срез последних цен для всех элементов массива сразу.
  4. Результат запроса выгружаем в таблицу значений или соответствие для быстрого поиска.
  5. Обходим строки документа и подставляем цены из подготовленных данных.

Пример реализации пакетного заполнения:


&НаСервере
Процедура ЗаполнитьЦеныВсейТаблицы()
    
    // 1. Собираем номенклатуру
    МассивЗапчастей = Объект.СписокЗЧ.ВыгрузитьКолонку("Номенклатура");
    
    // 2. Запрос ко всем ценам сразу
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ЦеныСрезПоследних.Запчасть КАК Запчасть,
        |	ЦеныСрезПоследних.Сумма КАК Сумма
        |ИЗ
        |	РегистрСведений.Цены.СрезПоследних(&Период, Запчасть В (&МассивЗапчастей)) КАК ЦеныСрезПоследних";
    
    Запрос.УстановитьПараметр("Период", Объект.Дата);
    Запрос.УстановитьПараметр("МассивЗапчастей", МассивЗапчастей);
    
    ТаблицаЦен = Запрос.Выполнить().Выгрузить();
    // Индексируем для быстрого поиска в памяти
    ТаблицаЦен.Индексы.Добавить("Запчасть");
    
    // 3. Распределяем значения по строкам
    Для Каждого СтрокаТЧ Из Объект.СписокЗЧ Цикл
        ПараметрыПоиска = Новый Структура("Запчасть", СтрокаТЧ.Номенклатура);
        НайденныеСтроки = ТаблицаЦен.НайтиСтроки(ПараметрыПоиска);
        
        Если НайденныеСтроки.Количество() > 0 Тогда
            СтрокаТЧ.Цена = НайденныеСтроки[0].Сумма;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Шаг 5. Тонкости работы с типами и NULL

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

Всегда используйте функцию ЕСТЬNULL в текстах запросов. Например: ЕСТЬNULL(ЦеныСрезПоследних.Сумма, 0). Это гарантирует, что на выходе вы всегда получите числовое значение.

Рекомендации по улучшению кода

Чтобы ваше решение было по-настоящему профессиональным, рекомендуем внедрить следующие практики:

Мы проанализировали основные способы взаимодействия табличной части документа с регистром сведений. Выбор конкретного метода зависит от типа регистра (периодический или нет) и объема данных, которые необходимо обработать. Главное — помнить о разделении контекста выполнения и стремиться к минимизации количества запросов к серверу базы данных.

← На главную