Как получить цену номенклатуры на дату документа в 1С?

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

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

Использование виртуальной таблицы СрезПоследних

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

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


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

В параметры виртуальной таблицы мы передаем дату документа (&Период) и ссылку на нужный товар (&Номенклатура). Однако основные сложности у начинающих разработчиков возникают не с самим запросом, а с правильной передачей параметров с управляемой формы на сервер.

Проблема контекста: как правильно передать дату

Проанализируем ситуацию, которая часто приводит к ошибке «Переменная не определена (Объект)». При написании серверных вызовов в управляемых формах мы стремимся использовать директиву &НаСервереБезКонтекста, так как она не тянет за собой передачу всех данных формы по сети и работает максимально быстро.

Но внутри бесконтекстной процедуры переменная Объект недоступна. Выясним причину: сервер ничего не знает о данных формы, пока мы явно не передадим их через параметры. Разберем по шагам правильную архитектуру клиент-серверного взаимодействия для нашей задачи:


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

    РезультатЗапроса = Запрос.Выполнить();
    Если Не РезультатЗапроса.Пустой() Тогда
        Выборка = РезультатЗапроса.Выбрать();
        Выборка.Следующий();
        Возврат Выборка.Цена;
    КонецЕсли;
    
    Возврат 0;
КонецФункции

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

Тонкости и нюансы работы с ценами

Рассмотрим дополнительные методы и правила, которые помогут избежать скрытых логических ошибок и сделать код более профессиональным:

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

Альтернативный вариант: Обращение к физической таблице

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


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

Конструкция ВЫБРАТЬ ПЕРВЫЕ 1 вместе с УПОРЯДОЧИТЬ ПО Период УБЫВ выбирает все записи до указанной даты включительно и возвращает самую верхнюю (самую позднюю) цену. Важное требование: чтобы этот метод не тормозил систему на больших базах, измерение Номенклатура в регистре сведений обязательно должно быть проиндексировано (в свойствах измерения должен стоять флаг «Индексировать»). Тем не менее, использование СрезПоследних в большинстве случаев остается предпочтительным.

Оптимизация: получение цен для списка товаров

В завершение разберем критически важный аспект производительности. Выше мы рассмотрели получение цены для одной строки. Но что делать, если пользователь заполняет документ программно обработкой или копирует его из старого документа? В таблице может быть 500 строк.

Если мы будем вызывать функцию сервера в цикле 500 раз — база «повиснет». Разберем правильный алгоритм для массовой обработки:

  1. Передаем всю табличную часть документа на сервер единым массивом или деревом.
  2. Помещаем этот список во временную таблицу внутри запроса.
  3. Делаем ЛЕВОЕ СОЕДИНЕНИЕ этой временной таблицы с виртуальной таблицей СрезПоследних.
  4. Получаем все цены для документа за одно обращение к базе данных.

Используйте этот подход при массовых пересчетах, и ваши документы всегда будут работать быстро и корректно!

← На главную