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