Как получить последнюю закупочную цену товара из регистра ЦеныНоменклатурыПоставщиков в 1С:УТ 11, игнорируя поставщика?

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

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

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

Причина возникновения нескольких цен при использовании СрезПоследних

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

Виртуальная таблица СрезПоследних, которую мы используем, возвращает последние записи на указанный момент времени для каждой уникальной комбинации всех измерений регистра. Это означает, что если у нас есть товар "НазваниеТовара" и он был закуплен у "Поставщик1" по цене 100 руб., а затем у "Поставщик2" по цене 120 руб., и обе эти цены являются последними по соответствующему поставщику, то СрезПоследних вернет обе записи:


НазваниеТовара | Поставщик1 | 100р.
НазваниеТовара | Поставщик2 | 120р.

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

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

Для решения этой задачи мы будем использовать подход с пакетным запросом и временными таблицами. Этот метод позволяет нам сначала найти максимально поздний период для каждой номенклатуры, а затем по этому периоду получить соответствующую цену.

Шаг 1: Определяем максимальный период для каждой номенклатуры

На первом этапе мы создадим временную таблицу, в которой для каждой уникальной номенклатуры будет указан самый последний зарегистрированный период. Для этого мы выбираем Номенклатура и используем агрегатную функцию МАКСИМУМ(Период), группируя результат по Номенклатура.

В этом запросе мы обращаемся к тому же регистру ЦеныНоменклатурыПоставщиков.СрезПоследних, но теперь наша задача - найти самый поздний момент времени, когда цена для данной номенклатуры вообще регистрировалась.


ВЫБРАТЬ
    МАКСИМУМ(ЦеныНоменклатурыПоставщиковСрезПоследних.Период) КАК Период,
    ЦеныНоменклатурыПоставщиковСрезПоследних.Номенклатура
ПОМЕСТИТЬ МАКСПЕРИОДЫ
ИЗ
    РегистрСведений.ЦеныНоменклатурыПоставщиков.СрезПоследних(&ДатаКонца, ) КАК ЦеныНоменклатурыПоставщиковСрезПоследних
СГРУППИРОВАТЬ ПО
    ЦеныНоменклатурыПоставщиковСрезПоследних.Номенклатура
;

Здесь:

  1. &ДатаКонца — это параметр, который мы можем установить, чтобы получить срез на конкретную дату. Если мы хотим получить текущие цены, параметр можно опустить или передать текущую дату.
  2. МАКСИМУМ(ЦеныНоменклатурыПоставщиковСрезПоследних.Период) КАК Период — эта часть выбирает самый поздний период для каждой группы.
  3. СГРУППИРОВАТЬ ПО ЦеныНоменклатурыПоставщиковСрезПоследних.Номенклатура — определяет, что агрегация (поиск максимума периода) будет выполняться для каждой уникальной номенклатуры.
  4. ПОМЕСТИТЬ МАКСПЕРИОДЫ — результат этого запроса сохраняется во временной таблице с именем МАКСПЕРИОДЫ. Эта таблица будет содержать всего две колонки: Номенклатура и Период (максимальный для этой номенклатуры).

Шаг 2: Присоединяем цену по найденному максимальному периоду

После того как мы получили временную таблицу МАКСПЕРИОДЫ, которая содержит каждую номенклатуру и соответствующий ей максимальный период, мы можем использовать ее для отбора нужных цен. Мы выполняем внутреннее соединение этой временной таблицы с исходным регистром сведений ЦеныНоменклатурыПоставщиков (опять же, можно использовать СрезПоследних для удобства, хотя можно и к основной таблице регистра) по двум ключевым полям: Номенклатура и Период.


////////////////////////////////////////////////////////////­////////////////////
ВЫБРАТЬ
    ЦеныНоменклатурыПоставщиковСрезПоследних.Цена,
    МАКСПЕРИОДЫ.Номенклатура,
    МАКСПЕРИОДЫ.Номенклатура.Код
ИЗ
    РегистрСведений.ЦеныНоменклатурыПоставщиков.СрезПоследних КАК ЦеныНоменклатурыПоставщиковСрезПоследних
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ МАКСПЕРИОДЫ КАК МАКСПЕРИОДЫ
        ПО ЦеныНоменклатурыПоставщиковСрезПоследних.Период = МАКСПЕРИОДЫ.Период
        И ЦеныНоменклатурыПоставщиковСрезПоследних.Номенклатура = МАКСПЕРИОДЫ.Номенклатура
УПОРЯДОЧИТЬ ПО
    МАКСПЕРИОДЫ.Номенклатура.Наименование
;

Рассмотрим компоненты второго запроса:

  1. ИЗ РегистрСведений.ЦеныНоменклатурыПоставщиков.СрезПоследних КАК ЦеныНоменклатурыПоставщиковСрезПоследних — мы снова обращаемся к виртуальной таблице среза, чтобы получить доступ ко всем данным регистра, включая цену. Обратите внимание, что здесь мы не передаем параметр даты для СрезПоследних, так как мы уже получили конкретные периоды из временной таблицы.
  2. ВНУТРЕННЕЕ СОЕДИНЕНИЕ МАКСПЕРИОДЫ КАК МАКСПЕРИОДЫ — мы присоединяем нашу временную таблицу.
  3. ПО ЦеныНоменклатурыПоставщиковСрезПоследних.Период = МАКСПЕРИОДЫ.Период И ЦеныНоменклатурыПоставщиковСрезПоследних.Номенклатура = МАКСПЕРИОДЫ.Номенклатура — это условие соединения является ключевым. Оно гарантирует, что мы выбираем только те записи из регистра, которые соответствуют максимальному периоду, найденному для каждой номенклатуры.
  4. ВЫБРАТЬ ЦеныНоменклатурыПоставщиковСрезПоследних.Цена, МАКСПЕРИОДЫ.Номенклатура, МАКСПЕРИОДЫ.Номенклатура.Код — мы выбираем саму цену, номенклатуру (можно из любой из таблиц, так как они соединены по ней) и код номенклатуры для удобства.

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

Полный текст запроса

Объединим оба шага в один пакетный запрос:


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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ЦеныНоменклатурыПоставщиковСрезПоследних.Цена,
    МАКСПЕРИОДЫ.Номенклатура,
    МАКСПЕРИОДЫ.Номенклатура.Код
ИЗ
    РегистрСведений.ЦеныНоменклатурыПоставщиков.СрезПоследних КАК ЦеныНоменклатурыПоставщиковСрезПоследних
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ МАКСПЕРИОДЫ КАК МАКСПЕРИОДЫ
        ПО ЦеныНоменклатурыПоставщиковСрезПоследних.Период = МАКСПЕРИОДЫ.Период
        И ЦеныНоменклатурыПоставщиковСрезПоследних.Номенклатура = МАКСПЕРИОДЫ.Номенклатура
УПОРЯДОЧИТЬ ПО
    МАКСПЕРИОДЫ.Номенклатура.Наименование";

Этот запрос является надежным решением для получения последней цены по номенклатуре, игнорируя другие измерения регистра, такие как "Поставщик".

Что делать, если в одном периоде несколько цен для одной номенклатуры?

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

В таком случае, даже предложенный выше запрос может вернуть несколько строк для одной номенклатуры, поскольку условие соединения ЦеныНоменклатурыПоставщиковСрезПоследних.Период = МАКСПЕРИОДЫ.Период И ЦеныНоменклатурыПоставщиковСрезПоследних.Номенклатура = МАКСПЕРИОДЫ.Номенклатура будет истинным для каждой такой записи.

Чтобы разрешить эту коллизию, нам необходимо принять решение, какую именно цену из этих нескольких мы считаем "последней". Рассмотрим несколько подходов:

Вариант 1: Выбор максимальной/минимальной/средней цены

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


////////////////////////////////////////////////////////////­////////////////////
ВЫБРАТЬ
    МАКСПЕРИОДЫ.Номенклатура,
    МАКСПЕРИОДЫ.Номенклатура.Код,
    МАКСИМУМ(ЦеныНоменклатурыПоставщиковСрезПоследних.Цена) КАК Цена
ИЗ
    РегистрСведений.ЦеныНоменклатурыПоставщиков.СрезПоследних КАК ЦеныНоменклатурыПоставщиковСрезПоследних
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ МАКСПЕРИОДЫ КАК МАКСПЕРИОДЫ
        ПО ЦеныНоменклатурыПоставщиковСрезПоследних.Период = МАКСПЕРИОДЫ.Период
        И ЦеныНоменклатурыПоставщиковСрезПоследних.Номенклатура = МАКСПЕРИОДЫ.Номенклатура
СГРУППИРОВАТЬ ПО
    МАКСПЕРИОДЫ.Номенклатура,
    МАКСПЕРИОДЫ.Номенклатура.Код
УПОРЯДОЧИТЬ ПО
    МАКСПЕРИОДЫ.Номенклатура.Наименование
;

Здесь мы использовали МАКСИМУМ(ЦеныНоменклатурыПоставщиковСрезПоследних.Цена) КАК Цена. В зависимости от бизнес-логики, можно использовать МИНИМУМ() или СРЕДНЕЕ(). Это решение гарантирует, что для каждой номенклатуры будет получена ровно одна цена.

Вариант 2: Выбор "произвольной" цены (первой попавшейся)

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

Дополнительные аспекты получения цен в УТ 11

В конфигурации 1С:УТ 11 существуют и другие механизмы, связанные с закупочными ценами и себестоимостью, которые могут быть полезны в зависимости от задачи:

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

Заключение

Мы успешно проанализировали и решили проблему получения последней закупочной цены по номенклатуре из регистра ЦеныНоменклатурыПоставщиков в 1С:УТ 11. Универсальный подход с использованием пакетного запроса и временных таблиц, когда сначала ищется максимальный период для каждой номенклатуры, а затем по этому периоду выбирается сама цена, является эффективным и надежным. Мы также рассмотрели, как обрабатывать редкие случаи, когда для одной номенклатуры в один и тот же момент времени может быть несколько цен, предлагая варианты с агрегатными функциями.

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

← На главную