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

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

При разработке учетных документов в 1С часто возникает типовая задача: при выборе номенклатуры в табличной части автоматически подставить ее актуальную цену на дату документа. Нередко начинающие разработчики сталкиваются с ошибкой Поле объекта не обнаружено (Цена) или с некорректной работой метода ПолучитьПоследнее. Разберем по шагам, почему возникает эта ошибка, как устроен механизм среза последних и как правильно организовать программный код в управляемых формах 1С 8.3.

Анализ ошибки: почему возникает исключение

Рассмотрим типовой ошибочный фрагмент кода, вызвавший сбой в работе формы:


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

В этом коде допущены сразу две принципиальные ошибки:

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

Исправление ошибки и базовый рабочий вариант

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


Функция ЦенаНаДень(ДатаНовогоДоговора, НоменклатураСсылка) Экспорт  
    
    Отбор = Новый Структура("Номенклатура", НоменклатураСсылка);
    ИнформацияИзРегистра = РегистрыСведений.ЦеныНоменклатуры.ПолучитьПоследнее(ДатаНовогоДоговора, Отбор); 
    
    Возврат ИнформацияИзРегистра.Цена;

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

На стороне формы документа в обработчике события ПриИзменении вызываем функцию и заполняем реквизит строки:


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

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

Учет всех измерений регистра: работа с характеристиками

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

Если для услуг характеристики не используются или цена задается на позицию в целом, в структуру отбора необходимо явно передавать пустую ссылку на справочник характеристик:


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

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

Клиент-серверная архитектура и оптимизация вызовов

Разберем важные нюансы взаимодействия клиента и сервера в управляемом приложении:

  1. Флаги общего модуля: прямой вызов метода ОбщиеФункции.ЦенаНаДень(...) из клиентской процедуры &НаКлиенте возможен только в том случае, если у общего модуля ОбщиеФункции установлены флаги Сервер и Вызов сервера. Если флаг вызова сервера снят, возникнет ошибка недоступности метода.
  2. Минимизация контекста: для снижения объема передаваемых данных между тонким клиентом и сервером рекомендуется выполнять обращение к серверу через процедуру формы &НаСервереБезКонтекста.
  3. Проверка заполненности: перед отправкой запроса на сервер обязательно проверяем, заполнена ли номенклатура. При очистке поля пользователем событие ПриИзменении также инициируется, и лишний серверный вызов не должен выполняться.

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


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

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

&НаСервереБезКонтекста
Функция ПолучитьЦенуНаСервере(ДатаРасчета, Номенклатура, Характеристика)
    
    Возврат ОбщиеФункции.ПолучитьЦенуНоменклатуры(ДатаРасчета, Номенклатура, Характеристика);

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

Альтернативный вариант: получение данных через запрос к СрезуПоследних

Использование метода ПолучитьПоследнее удобно для получения единичного значения, но имеет ограничения: метод выполняет фильтрацию исключительно по точному совпадению измерений и возвращает только ресурсы регистра.

Если в процессе разработки потребуется одновременно получить цену, ставку НДС, единицу измерения или обработать массив строк табличной части за один вызов, наиболее производительным решением является использование объекта Запрос к виртуальной таблице СрезПоследних:


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

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

Использование объекта Граница с параметром ВидГраницы.Включая гарантирует, что в выборку попадут записи регистра сведений, установленные в ту же секунду, что и дата документа.

← На главную