Как в запросе 1С исключить товары, по которым был оформлен возврат клиента?

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

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

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

Метод 1: Использование регистров накопления (Рекомендуемый)

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

Проанализируем преимущества работы с регистрами:

  1. Автоматический расчет: Нам не нужно вручную искать документы возврата. Мы просто суммируем ресурсы регистра по нужным измерениям (Номенклатура, Склад и т.д.).
  2. Учет корректировок: Помимо возвратов, регистры учитывают и документы типа КорректировкаРеализации, что делает итоговый расчет еще точнее.
  3. Производительность: Запросы к виртуальным таблицам оборотов регистров работают значительно быстрее, чем сложные соединения нескольких табличных частей документов.

Разберем пример реализации. Если в вашей конфигурации используется регистр ДвиженияТоваров (или аналогичный), запрос будет выглядеть следующим образом:


ВЫБРАТЬ
    ДвиженияТоваровОбороты.Номенклатура КАК Номенклатура,
    ДвиженияТоваровОбороты.КоличествоОборот КАК Количество,
    ДвиженияТоваровОбороты.Регистратор.Цена КАК Цена
ИЗ
    РегистрНакопления.ДвиженияТоваров.Обороты(&НачалоПериода, &КонецПериода, Регистратор, 
        Регистратор В (&СписокДокументов) ИЛИ ДокументОснование В (&СписокДокументов)) КАК ДвиженияТоваровОбороты

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

Метод 2: Левое соединение табличных частей (если нельзя использовать регистры)

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

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

Рассмотрим пример запроса с использованием соединения:


ВЫБРАТЬ
    РеализацияТовары.Номенклатура КАК Номенклатура,
    СУММА(РеализацияТовары.Количество - ЕСТЬNULL(ВозвратТовары.Количество, 0)) КАК КоличествоОстаток,
    РеализацияТовары.Цена КАК Цена
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
ЛЕВОЕ СОЕДИНЕНИЕ
    Документ.ВозвратТоваровОтКлиента.Товары КАК ВозвратТовары
    ПО РеализацияТовары.Ссылка = ВозвратТовары.ДокументОснование
    И РеализацияТовары.Номенклатура = ВозвратТовары.Номенклатура
ГДЕ
    РеализацияТовары.Ссылка В(&СписокДокументов)
СГРУППИРОВАТЬ ПО
    РеализацияТовары.Номенклатура,
    РеализацияТовары.Цена
ИМЕЮЩИЕ
    СУММА(РеализацияТовары.Количество - ЕСТЬNULL(ВозвратТовары.Количество, 0)) > 0

В данном примере функция ЕСТЬNULL заменяет пустое значение (если возврата не было) на 0, что позволяет корректно выполнить арифметическую операцию вычитания. Условие в блоке ИМЕЮЩИЕ гарантирует, что в итоговую печатную форму не попадут товары, которые были возвращены полностью (где остаток стал равен нулю).

Анализ исходного кода и работа над ошибками

Посмотрим на исходный код, предоставленный автором темы. В нем данные берутся напрямую из Документ.РеализацияТоваровУслуг.Товары. Основная проблема здесь в том, что этот запрос игнорирует любые другие документы системы. Чтобы исправить ситуацию, не создавая новый сложный запрос, мы можем внедрить использование временных таблиц.

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

  1. Подготовка списка документов: Мы оставляем логику выбора реализаций пользователем в списке.
  2. Поиск связанных возвратов: Перед выполнением основного запроса мы можем найти все документы ВозвратТоваровОтКлиента, у которых в поле ДокументОснование указаны реализации из нашего списка.
  3. Объединение данных: В запросе мы объединяем данные из реализаций (с плюсом) и данные из возвратов (с минусом).

Проанализируем, как это будет выглядеть в коде 1С:


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

Этот подход через ОБЪЕДИНИТЬ ВСЕ и временную таблицу ВтДанные является универсальным. Он позволяет «вычесть» возвращенные товары, даже если структура метаданных регистра в вашей базе сильно изменена «очумелыми ручками», как выразились участники форума.

Важные нюансы при работе с данными

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

1. Статус проведения документов. Помните, что запрос к табличным частям видит данные даже непроведенных документов. Обязательно добавляйте условие ГДЕ Ссылка.Проведен = ИСТИНА. Если вы используете регистры накопления, эта проблема отпадает сама собой, так как в регистры попадают данные только проведенных документов.

2. Характеристики и серии. Если в вашей базе ведется учет по характеристикам, их обязательно нужно включать в поля группировки запроса и в условия связи. Иначе система может вычесть «Ботинки 42 размера» из «Ботинок 45 размера», просто потому что у них совпала номенклатура.

3. Единицы измерения. В разных документах могут использоваться разные единицы (штуки и упаковки). Перед вычитанием количества убедитесь, что вы приводите его к базовой единице измерения или используете коэффициент, иначе математический результат будет неверным.

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

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

← На главную