К статье
Практический материал

Разработка и Конфигурация · 11 мин

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

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

Цены в 1С хранятся не в справочнике номенклатуры, а в периодическом регистре сведений — в типовых конфигурациях это «Цены номенклатуры» (в УТ 10.3, УПП) или «Цены номенклатуры поставщиков» и регистр с аналогичной ролью в УТ 11, ERP и КА. Периодичность регистра означает: одна и та же номенклатура имеет несколько записей с разными датами, а актуальная цена — это запись с максимальной датой, не превышающей нужную. Поэтому «получить цену на дату» — это всегда задача выборки среза последних на указанный момент.

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

Как устроено хранение цен и почему нужна дата

Регистр сведений «Цены номенклатуры» имеет измерения «Номенклатура» и «ТипЦен» (в УТ 11 — «ВидЦены»), ресурс «Цена» и стандартный реквизит «Период». Каждая установка цены документом «Установка цен номенклатуры» создаёт новую запись с датой документа. Старые записи не удаляются — они образуют историю изменения цены.

Из этого следует главное правило: цена «на дату» — это запись с максимальным периодом, меньшим или равным заданной дате. Если цена установлена 10 января и 20 января, то на 15 января действует цена от 10 января, а на 25 января — от 20 января. Записи с датой позже запрошенной игнорируются.

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

Получение цены запросом через СрезПоследних

Основной и самый надёжный способ — запрос к виртуальной таблице СрезПоследних. Параметр периода передаётся первым аргументом виртуальной таблицы, условия отбора — вторым, в параметре «Условие».

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Цены.Номенклатура КАК Номенклатура,

| Цены.Цена КАК Цена

|ИЗ

| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(

| &Период,

| ТипЦен = &ТипЦен

| И Номенклатура В (&СписокНоменклатуры)) КАК Цены";

Запрос.УстановитьПараметр("Период", КонецДня(ДатаОтчета));

Запрос.УстановитьПараметр("ТипЦен", ТипЦен);

Запрос.УстановитьПараметр("СписокНоменклатуры", СписокНоменклатуры);

Результат = Запрос.Выполнить().Выгрузить();

Обратите внимание на КонецДня(): если цена установлена документом в 10:00 15 марта, а вы передаёте дату 15 марта без времени (00:00:00), эта запись в срез не попадёт — её период больше запрошенного. Для отчётов «на дату» почти всегда нужно передавать конец дня или границу, включающую дату.

Отбор по типу цен и номенклатуре размещайте именно в параметрах виртуальной таблицы, а не в секции ГДЕ основного запроса. Условие внутри виртуальной таблицы выполняется до формирования среза и резко сокращает объём обрабатываемых данных, особенно на больших базах.

Соединение таблицы товаров со срезом цен

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

Запрос.Текст =

"ВЫБРАТЬ

| Товары.Номенклатура КАК Номенклатура

|ПОМЕСТИТЬ ВТ_Товары

|ИЗ

| &ТаблицаТоваров КАК Товары

|;

|ВЫБРАТЬ

| Товары.Номенклатура КАК Номенклатура,

| ЕСТЬNULL(Цены.Цена, 0) КАК Цена

|ИЗ

| ВТ_Товары КАК Товары

| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(

| &Период,

| ТипЦен = &ТипЦен

| И Номенклатура В (ВЫБРАТЬ Т.Номенклатура ИЗ ВТ_Товары КАК Т)) КАК Цены

| ПО Товары.Номенклатура = Цены.Номенклатура";

Левое соединение с ЕСТЬNULL(Цены.Цена, 0) обязательно: если для какой-то позиции цена никогда не устанавливалась, внутреннее соединение просто выкинет строку из результата, и отчёт «потеряет» товар молча. Левое соединение сохраняет строку и отдаёт ноль или NULL, что легко контролировать.

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

Получение цены для одной позиции без запроса

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

Отбор = Новый Структура;

Отбор.Вставить("Номенклатура", НоменклатураСсылка);

Отбор.Вставить("ТипЦен", ТипЦенСсылка);

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

Цена = ДанныеЦены.Цена; // 0, если записей нет

Если записей по отбору нет, метод вернёт структуру с пустыми значениями ресурсов — цена будет равна нулю, исключения не возникнет. Это удобно, но таит риск: нулевая цена неотличима от реальной нулевой, поэтому результат стоит проверять явно.

Сравнение способов

СрезПоследних в запросе

Подходит для списков и отчётов, работает на сервере СУБД, быстро на больших объёмах. Требует аккуратности с параметрами виртуальной таблицы.

ПолучитьПоследнее()

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

Типовые функции ценообразования

Учитывают валюту, единицу измерения, характеристики и соглашения. Зависят от конфигурации и версии; в УТ 11 логика вынесена в механизм соглашений с клиентами.

Особенности в УТ 11, ERP и КА

В УТ 11, ERP 2 и КА 2 регистр называется «ЦеныНоменклатуры», но измерение — «ВидЦены», и добавлено измерение «Характеристика» (точнее, связь через характеристики номенклатуры в отдельном регистре «ЦеныНоменклатуры» с учётом пакетного ценообразования). Кроме того, фактическая цена для клиента может определяться не напрямую из регистра, а через соглашение с клиентом, скидки и наценки.

Если вам нужна «базовая» цена вида цен на дату — срез последних работает так же. Если нужна цена с учётом соглашения, сегментов и скидок, используйте типовые процедуры ценообразования конфигурации (в УТ 11 это функции общего модуля, связанного с механизмом «Цены и скидки»), потому что воспроизвести всю цепочку расчёта вручную сложно и чревато расхождениями с поведением документов.

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

Частые ошибки и как их проверить

Самая распространённая ошибка — передача даты без времени. Цена, установленная сегодня утром, «не видна» запросу на начало этого же дня, и пользователь получает вчерашнюю цену. Второе место занимает отсутствие отбора по типу цен: срез возвращает несколько строк по одной номенклатуре с разными типами цен, и при соединении строки задваиваются, искажая суммы отчёта.

Третья типичная проблема — использование ПолучитьПоследнее() в цикле по строкам документа. На десяти позициях это незаметно, на десяти тысячах отчёт начинает выполняться минутами. Правило простое: больше нескольких позиций — только запрос со срезом.

Типичные ошибки
01
Передача даты без КонецДня() — цены, установленные в течение дня, теряются
02
Отбор по типу цен в секции ГДЕ вместо параметров виртуальной таблицы — лишняя нагрузка и риск неверного среза
03
Внутреннее соединение со срезом — товары без цены молча пропадают из результата
04
Вызов ПолучитьПоследнее() в цикле по большой таблице — резкое падение производительности
05
Игнорирование валюты вида цен — смешение рублёвых и валютных цен в одном отчёте
Контрольные точки

Как проверить результат

Цена конкретного товара на дату
Норма

Совпадает с ценой в карточке «История цен» или в документе установки цен с ближайшей датой до запрошенной

Тревожный признак

Возвращается более старая цена — вероятно, дата передана без времени

Количество строк результата
Норма

Равно числу позиций в исходной таблице товаров

Тревожный признак

Строк больше (не отобран тип цен) или меньше (внутреннее соединение)

Товары без установленной цены
Норма

Присутствуют с ценой 0 или NULL

Тревожный признак

Отсутствуют в выборке

Пошаговый алгоритм

Порядок действий
  1. 1
    Определите, какой регистр хранит цены в вашей конфигурации и как называются измерения (ТипЦен или ВидЦены)
  2. 2
    Определите нужную дату и приведите её к концу дня или границе «включая»
  3. 3
    Для одной позиции используйте ПолучитьПоследнее(), для списка — запрос со СрезПоследних с отбором в параметрах виртуальной таблицы
  4. 4
    Используйте левое соединение и ЕСТЬNULL, чтобы не терять позиции без цены
  5. 5
    Проверьте результат на 2–3 товарах с известной историей цен, включая товар без установленной цены

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

FAQ

Как получить цену на дату в СКД, а не в коде?

В наборе данных запроса СКД используйте тот же шаблон со срезом последних, а вместо фиксированного параметра &Период объявите параметр на вкладке «Параметры» и передайте туда дату отчёта. Для связи с периодом отчёта удобно задать выражение параметра через стандартную дату окончания периода, добавив вычисление конца дня.

Почему запрос возвращает цену не на ту дату, которую я указал?

Чаще всего дата передана без времени (00:00:00), и записи, созданные в течение этого дня, не попадают в срез. Примените функцию КонецДня() к параметру. Вторая причина — цена реально не менялась: срез последних честно возвращает последнюю действовавшую цену, даже если она установлена год назад.

Как получить цены сразу по нескольким типам цен?

Уберите отбор по типу цен из виртуальной таблицы или замените его условием ТипЦен В (&СписокТиповЦен), а в выборку добавьте поле ТипЦен. Для отчёта в разрезе типов цен удобно свернуть результат через итоги или перекрестную таблицу в СКД.

Чем отличается СрезПоследних от СрезПервых?

СрезПоследних возвращает самую позднюю запись до указанной даты — то есть действующую на эту дату цену. СрезПервых возвращает самую раннюю запись после указанной даты — он отвечает на вопрос «когда цена изменится в следующий раз» и для получения текущей цены не подходит.

Можно ли получить цену на дату без программирования?

Да. В типовых конфигурациях есть отчёт «История цен номенклатуры» или прайс-лист с настройкой «на дату», которые показывают действующие цены без написания кода. Кроме того, в карточке товара по ссылке на цены открывается список записей регистра, где видна вся история. Программный способ нужен, когда цену надо подставить в собственный отчёт, обработку или обмен.

Итог

Что важно запомнить

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