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

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

При доработке типовых и нетиповых документов часто возникает задача автоматического заполнения цен и сумм при добавлении номенклатуры. Начинающие разработчики нередко сталкиваются с ошибкой: пытаются получить данные через запрос к табличной части документа в базе данных (например, Документ.ПеремещениеТоваров.Товары). Однако для нового или еще не записанного документа этот запрос вернет пустой результат, так как данные присутствуют только в оперативной памяти формы.

Разберем, как правильно организовать получение цен из периодического регистра сведений ЦеныНоменклатуры и подставить их в реквизиты табличной части несохраненного документа.

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

Проанализируем типичную ошибку в исходном запросе. Если в секции ИЗ обращаться к табличной части самого документа, СУБД читает данные из физических таблиц базы данных. Если пользователь только что создал документ или добавил новую строку кнопкой, этих данных в базе еще нет — они находятся в объекте формы Объект.Товары.

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

Способ 1. Получение цен по массиву номенклатуры через срез последних

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

Разберем реализацию по шагам:

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

Посмотрим на пример серверной процедуры:


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

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

Способ 2. Передача таблицы значений в качестве параметра запроса

Если требуется передать в запрос не просто массив номенклатуры, а готовую таблицу с несколькими колонками (например, Номенклатура и Характеристика), мы можем выгрузить табличную часть формы в таблицу значений и передать ее во временную таблицу запроса.

Посмотрим на пример реализации:


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

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

Способ 3. Точечное получение цены при изменении строки (рекомендуемый для UI)

В управляемых формах пересчитывать всю таблицу при добавлении одной позиции не всегда оптимально. Гораздо удобнее получать цену точечно при выборе номенклатуры в событии ТоварыНоменклатураПриИзменении.

Разберем клиент-серверное взаимодействие:


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

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

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

КонецФункции

Важные рекомендации и частые ошибки

  1. Сравнение по ссылке вместо наименования. Не используйте конструкцию вида ВидЦены.Наименование = Склад.Наименование. Сравнение строк не гарантирует однозначности и замедляет выполнение запроса в СУБД. В справочник складов следует добавить реквизит с типом ссылки на справочник ВидыЦен.
  2. Обработка дублирующихся строк. Метод НайтиСтроки возвращает массив. Не обращайтесь только к первому элементу стрНайд[0], если в документе может присутствовать одинаковая номенклатура на разных строках — обходите весь массив циклом.
  3. Использование параметров виртуальной таблицы. Всегда передавайте условия отбора в параметры виртуальной таблицы СрезПоследних(&Дата, Отбор), а не в секцию ГДЕ. Это позволяет платформе сразу выбрать минимальный объем срезанных данных, что значительно ускоряет работу базы.
← На главную