Как получить цену номенклатуры из регистра сведений и подставить ее в текущую строку документа?

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

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

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

Анализ типичных ошибок в коде

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


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

Запрос.УстановитьПараметр("ТипЦен", Объект.ТипЦен);
Запрос.УстановитьПараметр("Дата", Объект.Дата);
Запрос.УстановитьПараметр("Склад", Объект.Склад);

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

Выясним, какие ошибки и неоптимальные решения присутствуют в этом фрагменте:

  1. Избыточная выборка данных: Запрос получает срез последних цен по всем существующим номенклатурным позициям для заданного типа цен и склада, вместо того чтобы сразу ограничить выборку конкретной позицией номенклатуры из текущей строки.
  2. Некорректная работа с коллекцией колонок: Конструкция Объект.Товары.Выгрузить().Колонки.Номенклатура возвращает объект описания колонки таблицы значений (КолонкаТаблицыЗначений), а не конкретное значение номенклатуры. Поиск методом Найти() по объекту описания колонки не вернет результат.
  3. Добавление новой строки вместо обновления текущей: Вызов Объект.Товары.Добавить() создает в документе новую строку в самом конце таблицы, вместо того чтобы записать цену в ту строку, которую пользователь сейчас редактирует.
  4. Попытка обращения к свойству таблицы как к строке: При попытке исправить код выражением ТекСтрока = Объект.Товары.Выгрузить() переменная ТекСтрока становится таблицей значений, из-за чего обращение ТекСтрока.Номенклатура вызывает исключение «Поле объекта не обнаружено (Номенклатура)».

Вариант 1. Использование метода ПолучитьПоследнее (рекомендуемый способ)

Когда требуется быстро получить единственное значение ресурса регистра сведений по известным измерениям на определенную дату, метод менеджера регистра ПолучитьПоследнее() работает эффективнее и выглядит гораздо компактнее, чем создание отдельного объекта запроса.

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

На стороне клиента в обработчике события (например, при изменении номенклатуры) получаем текущие данные строки через элемент формы:


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

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


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

Важный момент: Если запись в регистре сведений не найдена, метод ПолучитьПоследнее() возвратит структуру, в которой числовой ресурс Цена будет равен 0, что предотвращает ошибки чтения несуществующих свойств.

Вариант 2. Точечный запрос к виртуальной таблице

Если по архитектурным соображениям или при сложной структуре условий требуется использовать запрос, передадим отбор по номенклатуре непосредственно в параметры виртуальной таблицы СрезПоследних.


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

Методические рекомендации

Обратим внимание на ключевые правила при проектировании подобной функциональности:

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