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