В практике торгового учета часто возникает специфическая задача: найти товары, по которым итоговый оборот за период равен нулю, но при этом были активные продажи и последующие возвраты. Особенно критично это становится, когда нужно отфильтровать позиции, по которым количество таких возвратов превышает определенный порог (например, более 3-х документов за день). Такая ситуация может сигнализировать о проблемах с качеством партии товара или ошибках в работе торговой точки.
В данной статье мы разберем, как реализовать подобную логику с помощью механизма запросов 1С, используя временные таблицы, агрегатные функции и правильную обработку параметров.
Для начала определимся, что мы понимаем под «нулевым оборотом». В регистре накопления Продажи продажи фиксируются положительными числами в поле Количество, а возвраты — отрицательными. Следовательно, если СУММА(КоличествоОборот) = 0 за период, это означает, что весь проданный товар был возвращен обратно.
Основные этапы решения задачи:
Первым делом нам необходимо создать временную таблицу, которая отсечет лишние данные и оставит только те позиции, которые «закрылись в ноль». Мы будем использовать виртуальную таблицу Обороты регистра Продажи.
Обратите внимание, что на этом этапе мы группируем данные только по Номенклатура, чтобы вычислить общий итог.
ВЫБРАТЬ
ПродажиОбороты.Номенклатура КАК Номенклатура,
СУММА(ПродажиОбороты.КоличествоОборот) КАК КоличествоОборот
ПОМЕСТИТЬ ЗапросПоНоменклатуре
ИЗ
РегистрНакопления.Продажи.Обороты(&Дата_Начала, &Дата_Окончания, , ) КАК ПродажиОбороты
СГРУППИРОВАТЬ ПО
ПродажиОбороты.Номенклатура
ИМЕЮЩИЕ
СУММА(ПродажиОбороты.КоличествоОборот) = 0
;
Важное замечание: В отличие от исходного кода автора, в первом пакете лучше не использовать периодичность Регистратор, если это не требуется для фильтрации на данном этапе. Это существенно ускорит выполнение запроса на больших базах данных.
Теперь нам нужно понять, какие именно документы сформировали этот оборот. Нас интересуют только возвраты (отрицательные значения). Мы соединяем нашу временную таблицу с физической таблицей регистра или снова с виртуальной таблицей, но уже с детализацией до регистратора.
ВЫБРАТЬ
ПродажиОбороты.Регистратор КАК Регистратор,
ПродажиОбороты.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ЗапросПоВозвратам
ИЗ
ЗапросПоНоменклатуре КАК ВТ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&Дата_Начала, &Дата_Окончания, Регистратор, ) КАК ПродажиОбороты
ПО ВТ.Номенклатура = ПродажиОбороты.Номенклатура
ГДЕ
ПродажиОбороты.КоличествоОборот < 0
;
Рассмотрим ситуацию, когда нам нужно вывести только ту номенклатуру, по которой произошло более N возвратов. Также часто требуется дополнительная фильтрация по свойствам номенклатуры (например, категория товара или принадлежность к определенному бренду). В конфигурации «Управление торговлей 10.3» свойства хранятся в регистре сведений ЗначенияСвойствОбъектов.
Проанализируем условие ИМЕЮЩИЕ. Если пользователь не задал параметр (он равен 0), мы можем установить значение по умолчанию (например, 3) прямо в запросе.
ВЫБРАТЬ
ВозвратТоваровОтПокупателяТовары.Номенклатура КАК Номенклатура,
ЗначенияСвойствОбъектов.Значение КАК ЗначениеСвойства
ПОМЕСТИТЬ ТЗНоменклатуры
ИЗ
Документ.ВозвратТоваровОтПокупателя.Товары КАК ВозвратТоваровОтПокупателяТовары
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ПО ВозвратТоваровОтПокупателяТовары.Номенклатура = ЗначенияСвойствОбъектов.Объект
ГДЕ
ВозвратТоваровОтПокупателяТовары.Ссылка В (ВЫБРАТЬ Регистратор ИЗ ЗапросПоВозвратам)
И ЗначенияСвойствОбъектов.Свойство = &СвойствоНоменклатуры
СГРУППИРОВАТЬ ПО
ВозвратТоваровОтПокупателяТовары.Номенклатура,
ЗначенияСвойствОбъектов.Значение
ИМЕЮЩИЕ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВозвратТоваровОтПокупателяТовары.Ссылка) >= ВЫБОР
КОГДА &КоличествоВозвратов = 0 ТОГДА 3
ИНАЧЕ &КоличествоВозвратов
КОНЕЦ
;
На финальном этапе мы собираем все данные воедино. Нам нужно получить информацию о реализации (документе партии), складе и самом возврате. Мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ с нашей отфильтрованной таблицей ТЗНоменклатуры, чтобы в результат попали только те позиции, которые прошли все проверки.
ВЫБРАТЬ
ПоследниеВозвраты.Номенклатура КАК Номенклатура,
ТЗНоменклатуры.ЗначениеСвойства КАК Свойство,
ПоследниеВозвраты.ДокументПартии КАК Реализация,
ПоследниеВозвраты.Ссылка КАК Возврат,
ПоследниеВозвраты.Склад КАК Склад
ИЗ
ТЗНоменклатуры КАК ТЗНоменклатуры
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ВозвратТоваровОтПокупателя.Товары КАК ПоследниеВозвраты
ПО ТЗНоменклатуры.Номенклатура = ПоследниеВозвраты.Номенклатура
ГДЕ
ПоследниеВозвраты.Ссылка В (ВЫБРАТЬ Регистратор ИЗ ЗапросПоВозвратам)
УПОРЯДОЧИТЬ ПО
Реализация УБЫВ,
Номенклатура
ИТОГИ
КОЛИЧЕСТВО(Реализация),
КОЛИЧЕСТВО(Возврат)
ПО
Номенклатура
Разберем подробнее несколько критических моментов, которые помогут сделать ваш запрос надежнее:
ВЫБОР...КОНЕЦ внутри секции ИМЕЮЩИЕ, лучше подготовить параметр заранее в модуле на языке 1С. Это упростит жизнь планировщику запросов SQL.
Запрос.УстановитьПараметр("КоличествоВозвратов", ?(КоличествоВозвратов = 0, 3, КоличествоВозвратов));
ДокументПартии может быть не заполнено, если возврат оформляется без привязки к конкретной реализации. Если ваш бизнес-процесс подразумевает обязательную привязку, добавьте проверку НЕ ПоследниеВозвраты.ДокументПартии ЕСТЬ NULL.Номенклатура. Это делается добавлением строки ИНДЕКСИРОВАТЬ ПО Номенклатура после объявления временной таблицы.ИМЕЮЩИЕ всегда используйте КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Регистратор). В одной табличной части может быть несколько строк с одной и той же номенклатурой, и обычный подсчет строк даст неверный результат количества документов.Проанализировав данную ситуацию, мы выяснили, что ключ к решению лежит в последовательном отсеивании данных: сначала по суммарному обороту, затем по типам документов, и в конце — по количественному порогу. Такой подход гарантирует точность данных и позволяет гибко настраивать отчет под нужды бизнеса.