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

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

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

Проблема кроется в механизме работы регистров накопления. В типовых конфигурациях, таких как 1С:Управление торговлей, и продажи, и возвраты от покупателей могут записываться в один и тот же регистр, например, РегистрНакопления.Продажи. При этом продажа формирует движение с видом Расход, а возврат — с видом Приход. Если в запросе просто просуммировать ресурсы Количество и Сумма, то данные по возвратам сложатся с данными по продажам, что приведет к завышенному количеству и сумме, и, как следствие, к неверной средней цене.

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

Решение 1: Использование виртуальной таблицы "Обороты" (Рекомендуемый способ)

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

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

Разберем по шагам, как составить запрос:

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

Посмотрим на пример кода:


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

// Устанавливаем параметры запроса
ЗАПРОС.УстановитьПараметр("НачалоПериода", НачалоДня(ТекущаяДата() - 30 * 24 * 3600));
ЗАПРОС.УстановитьПараметр("КонецПериода", КонецДня(ТекущаяДата()));

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

Как видим, запрос получается очень лаконичным и понятным. Мы полностью делегируем платформе 1С задачу корректного расчета итогов, что является предпочтительной практикой.

Решение 2: Ручной расчет с помощью конструкции "ВЫБОР КОГДА"

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

Основным инструментом для этого в языке запросов 1С является оператор ВЫБОР КОГДА. Он позволяет нам в зависимости от условия (в нашем случае — от вида движения) применять разную логику к значениям полей.

Проанализируем логику такого подхода:

Для регистра продаж, где продажа — это Расход, а возврат — Приход, нам нужно:

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

Посмотрим, как это выглядит в коде запроса:


ЗАПРОС = Новый Запрос;
ЗАПРОС.Текст =
    "ВЫБРАТЬ
    |    Продажи.Номенклатура,
    |    СУММА(ВЫБОР
    |            КОГДА Продажи.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
    |                ТОГДА -Продажи.Сумма
    |            ИНАЧЕ Продажи.Сумма
    |        КОНЕЦ) КАК ИтогоСумма,
    |    СУММА(ВЫБОР
    |            КОГДА Продажи.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
    |                ТОГДА -Продажи.Количество
    |            ИНАЧЕ Продажи.Количество
    |        КОНЕЦ) КАК ИтогоКоличество
    |ИЗ
    |    РегистрНакопления.Продажи КАК Продажи
    |ГДЕ
    |    Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода
    |
    |СГРУППИРОВАТЬ ПО
    |    Продажи.Номенклатура
    |
    |ИМЕЮЩИЕ
    |    СУММА(ВЫБОР
    |            КОГДА Продажи.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
    |                ТОГДА -Продажи.Количество
    |            ИНАЧЕ Продажи.Количество
    |        КОНЕЦ) <> 0";

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

Разберем этот запрос подробнее:

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

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

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

Заключение: какой способ выбрать?

Подводя итог, мы рассмотрели два мощных метода для корректного расчета итогов в 1С с учетом разнонаправленных движений.

Первый способ, с использованием виртуальной таблицы РегистрНакопления.ИмяРегистра.Обороты, является стандартным, рекомендуемым и наиболее производительным. Он прост в реализации и использует встроенные, оптимизированные механизмы платформы. В 95% случаев следует выбирать именно его.

Второй способ, с ручной обработкой через ВЫБОР КОГДА, является более гибким и мощным инструментом. Он незаменим, когда требуется нестандартная логика агрегации, анализ дополнительных реквизитов движений или работа с регистрами без поддержки итогов. Это "швейцарский нож" разработчика, который позволяет решить практически любую задачу по обработке данных, но требует более глубокого понимания структуры данных и может быть менее производительным на больших объемах.

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

← На главную