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