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

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

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

Разбор типичных ошибок в коде и запросах

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

  1. Отсутствие фильтра по номенклатуре в запросе. При обращении к виртуальной таблице СрезПоследних без указания конкретной номенклатуры, система может вернуть данные по всем товарам сразу или (при наличии группировок) некорректные значения.
  2. Отсутствие метода Следующий(). После выполнения запроса необходимо инициализировать выборку. Без вызова метода Выборка.Следующий() данные из первой строки результата запроса будут недоступны.
  3. Неправильный цикл заполнения. В исходном примере автор пытался присвоить всем строкам документа одно и то же значение из выборки, не сопоставляя товар из строки табличной части с товаром из результата запроса.

Способ 1. Массовое заполнение цен через запрос (по кнопке)

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

Рассмотрим пример реализации процедуры на сервере:


&НаСервере
Процедура ЗаполнитьЦеныПриобретенияНаСервере()
    
    // 1. Собираем уникальную номенклатуру из табличной части в массив
    МассивНоменклатуры = Объект.Товары.ВыгрузитьКолонку("Номенклатура");
    
    // Удаляем дубликаты для оптимизации запроса
    ТаблицаНоменклатуры = Новый ТаблицаЗначений;
    ТаблицаНоменклатуры.Колонки.Добавить("Номенклатура");
    Для Каждого ТекНом Из МассивНоменклатуры Цикл
        ТаблицаНоменклатуры.Добавить().Номенклатура = ТекНом;
    КонецЦикла;
    ТаблицаНоменклатуры.Свернуть("Номенклатура");
    МассивУникальнойНоменклатуры = ТаблицаНоменклатуры.ВыгрузитьКолонку("Номенклатура");

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

КонецПроцедуры

Важный нюанс: использование объекта Соответствие позволяет избежать вложенных циклов и значительно ускоряет работу алгоритма при большом количестве строк в документе. Мы один раз обходим результат запроса и один раз — строки документа.

Способ 2. Заполнение цены при изменении номенклатуры (интерактивно)

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

На стороне клиента в модуле формы вызываем серверную функцию:


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

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

Проблема новых документов и временных таблиц

Разберем ситуацию, упомянутую в сообщении №2 и №9 на форуме. Если мы пытаемся выполнить запрос, соединяя физическую таблицу Документ.КоммерческоеПредложение.Товары с регистром сведений, мы столкнемся с проблемой: если документ еще не записан в базу данных, запрос ничего не найдет, так как в базе данных ссылки на этот документ еще не существует.

Чтобы избежать этой ошибки при работе в консоли запросов или сложных обработках, следует придерживаться правила: сначала выгружаем данные из объекта в таблицу значений, а затем передаем эту таблицу в запрос как параметр.

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


МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

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

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

Рекомендации по оптимизации

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

Подводя итог, можно сказать, что для решения задачи автора форума наиболее подходящим является Способ 1 с использованием Соответствия. Это обеспечит высокую производительность и корректность данных даже в незаписанных документах с большим количеством строк.

← На главную