При создании торговых и складских отчетов в 1С часто возникает задача детального анализа движения товаров в течение дня. Например, руководителю или логисту необходимо видеть продажи и остатки товаров по часам: с 09:00 до 10:00, с 10:00 до 11:00 и так далее. Основная сложность заключается в том, что стандартная виртуальная таблица ОстаткиИОбороты при периодичности Час не выводит те часы, в которых не было никаких движений (оборотов). В результате остаток на этот час просто «выпадает» из отчета.
В этой статье мы подробно разберем, как решить проблему «выпадающих» часов в Системе компоновки данных (СКД), рассмотрим несколько практических способов реализации этой задачи — от «чистого» запроса до встроенных механизмов платформы 1С, а также ответим на вопрос, как правильно получать остатки на конец предыдущего дня.
Самый надежный способ гарантировать вывод всех часов в отчете независимо от наличия движений — это предварительное формирование временной таблицы, содержащей все интересующие нас интервалы времени (так называемая таблица-календарь), с последующим левым соединением с реальными данными.
Рассмотрим структуру такого запроса. Мы создаем сетку часов (например, для стандартного рабочего дня с 9:00 до 20:00) относительно базовой даты, передаваемой в параметр &Период (обычно это начало дня).
ВЫБРАТЬ
&Период КАК Час,
"Начало дня (до 09:00)" КАК ПредставлениеПериода
ПОМЕСТИТЬ Периоды
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 9), "с 09:00 до 10:00"
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 10), "с 10:00 до 11:00"
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 11), "с 11:00 до 12:00"
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 12), "с 12:00 до 13:00"
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 13), "с 13:00 до 14:00"
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 14), "с 14:00 до 15:00"
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 15), "с 15:00 до 16:00"
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 16), "с 16:00 до 17:00"
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 17), "с 17:00 до 18:00"
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 18), "с 18:00 до 19:00"
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ДОБАВИТЬКATЕ(&Период, ЧАС, 19), "с 19:00 до 20:00"
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Периоды.Час КАК Час,
Периоды.ПредставлениеПериода КАК ПериодПредставление,
ТоварыНаСкладахОстаткиИОбороты.Номенклатура КАК Номенклатура,
ВЫБОР
КОГДА Периоды.Час = &Период
ТОГДА ЕСТЬNULL(ТоварыНаСкладахОстаткиИОбороты.ВНаличииНачальныйОстаток, 0)
ИНАЧЕ ЕСТЬNULL(ТоварыНаСкладахОстаткиИОбороты.ВНаличииКонечныйОстаток, 0)
КОНЕЦ КАК ОстатокТовара,
ЕСТЬNULL(ВыручкаИСебестоимостьПродажОбороты.КоличествоОборот, 0) КАК КоличествоПродаж,
ЕСТЬNULL(ВыручкаИСебестоимостьПродажОбороты.СуммаВыручкиОборот, 0) КАК СуммаПродаж
ИЗ
Периоды КАК Периоды
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(
&Период,
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 23),
Час, ,
Номенклатура = &Номенклатура
) КАК ТоварыНаСкладахОстаткиИОбороты
ПО Периоды.Час = ТоварыНаСкладахОстаткиИОбороты.Период
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(
&Период,
ДОБАВИТЬКДАТЕ(&Период, ЧАС, 23),
Час,
АналитикаУчетаНоменклатуры.Номенклатура = &Номенклатура
) КАК ВыручкаИСебестоимостьПродажОбороты
ПО Периоды.Час = ВыручкаИСебестоимостьПродажОбороты.Период
И ВыручкаИСебестоимостьПродажОбороты.АналитикаУчетаНоменклатуры.Номенклатура = ТоварыНаСкладахОстаткиИОбороты.Номенклатура
Разберем логику работы этого запроса:
Периоды. Функция ДОБАВИТЬКДАТЕ последовательно смещает базовое время &Период на нужное количество часов.ОстаткиИОбороты с периодичностью Час.ВЫБОР. Если текущая строка соответствует началу дня, мы берем физический начальный остаток. Для всех последующих часов мы берем конечный остаток предыдущего часа (который в таблице ОстаткиИОбороты для текущей строки является конечным остатком).Если вам не хочется вручную прописывать каждый час через оператор ОБЪЕДИНИТЬ в тексте запроса, можно использовать мощный штатный механизм системы компоновки данных — Дополнение периодов. Платформа самостоятельно сгенерирует недостающие строки в отчете и рассчитает для них правильные остатки.
Выполним следующие шаги для настройки:
ПериодЧас (из виртуальной таблицы остатков и оборотов).ПериодЧас.&НачалоПериода и &КонецПериода).В результате СКД сама добавит в отчет строки по всем часам, даже если по ним не было движений, и автоматически скопирует конечный остаток предыдущего заполненного часа в пустые интервалы.
Еще один профессиональный архитектурный подход в СКД — использование связей между наборами данных. Этот метод позволяет разделить получение «сетки времени» и получение самих остатков.
Разберем данный алгоритм по шагам:
Остатки с параметром &Период.Час), приемником является НаборДанных2. При этом мы указываем, что поле Час из источника должно передаваться в качестве **параметра** &Период для НабораДанных2.Благодаря этому СКД при формировании отчета выполнит запрос к таблице остатков для каждого часа в отдельности. Это гарантирует 100% точность остатков на любую секунду времени, даже если обороты полностью отсутствовали.
Часто возникает сопутствующий вопрос: если мы формируем отчет за конкретный день (например, 31.03.2019), как правильно вытащить остаток на конец предыдущего дня (30.03.2019)?
Выясним причину возможных ошибок: если просто передать дату 30.03.2019 23:59:59 в качестве параметра в виртуальную таблицу остатков, система может не учесть документы, проведенные ровно в эту секунду, так как остатки по умолчанию рассчитываются на «начало» секунды.
Для гарантированного получения корректных данных используем следующие приемы:
Вариант А. Начало текущего дня как альтернатива
Математически остаток на конец дня 30.03.2019 равен остатку на **начало дня** 31.03.2019 (00:00:00). Поэтому проще всего в параметрах виртуальной таблицы получать остаток на начало отчетного периода:
НАЧАЛОПЕРИОДА(&НачалоПериода, ДЕНЬ)
Вариант Б. Вычисление точной даты в выражении параметра СКД
Если вам принципиально нужно рассчитать именно границу предыдущего дня в СКД, перейдите на вкладку «Параметры» и для параметра, отвечающего за остатки, в колонке «Выражение» пропишите:
ДобавитьКДате(НачалоПериода(&НачалоПериода, "День"), "Секунда", -1)
Вариант В. Использование объекта «Граница» в кодовой базе
Если вы формируете отчет или выполняете запрос программно из модуля, для исключения погрешности последней секунды дня всегда используйте объект Граница с видом Включая:
КонецПредыдущегоДня = КонецДня(РабочаяДата - 86400); // Вычитаем 1 день
ПараметрГраница = Новый Граница(КонецПредыдущегоДня, ВидГраницы.Включая);
Запрос.УстановитьПараметр("МоментОстатков", ПараметрГраница);
Использование объекта Граница гарантирует, что все транзакции, записанные вплоть до последней миллисекунды предыдущего дня, будут корректно включены в расчет остатка.