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