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