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