Как настроить периодичность по часам в отчете СКД и выводить остатки на каждый конкретный час?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Торговля и дистрибуция
← На главную

При создании торговых и складских отчетов в 1С часто возникает задача детального анализа движения товаров в течение дня. Например, руководителю или логисту необходимо видеть продажи и остатки товаров по часам: с 09:00 до 10:00, с 10:00 до 11:00 и так далее. Основная сложность заключается в том, что стандартная виртуальная таблица ОстаткиИОбороты при периодичности Час не выводит те часы, в которых не было никаких движений (оборотов). В результате остаток на этот час просто «выпадает» из отчета.

В этой статье мы подробно разберем, как решить проблему «выпадающих» часов в Системе компоновки данных (СКД), рассмотрим несколько практических способов реализации этой задачи — от «чистого» запроса до встроенных механизмов платформы 1С, а также ответим на вопрос, как правильно получать остатки на конец предыдущего дня.

Способ 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), 
        Час, 
        АналитикаУчетаНоменклатуры.Номенклатура = &Номенклатура
    ) КАК ВыручкаИСебестоимостьПродажОбороты
    ПО Периоды.Час = ВыручкаИСебестоимостьПродажОбороты.Период 
    И ВыручкаИСебестоимостьПродажОбороты.АналитикаУчетаНоменклатуры.Номенклатура = ТоварыНаСкладахОстаткиИОбороты.Номенклатура

Разберем логику работы этого запроса:

  1. Мы создаем виртуальную шкалу времени внутри временной таблицы Периоды. Функция ДОБАВИТЬКДАТЕ последовательно смещает базовое время &Период на нужное количество часов.
  2. Для соединения мы используем виртуальную таблицу ОстаткиИОбороты с периодичностью Час.
  3. При заполнении поля остатка мы применяем конструкцию ВЫБОР. Если текущая строка соответствует началу дня, мы берем физический начальный остаток. Для всех последующих часов мы берем конечный остаток предыдущего часа (который в таблице ОстаткиИОбороты для текущей строки является конечным остатком).

Способ 2. Автоматическое «Дополнение периодов» средствами СКД

Если вам не хочется вручную прописывать каждый час через оператор ОБЪЕДИНИТЬ в тексте запроса, можно использовать мощный штатный механизм системы компоновки данных — Дополнение периодов. Платформа самостоятельно сгенерирует недостающие строки в отчете и рассчитает для них правильные остатки.

Выполним следующие шаги для настройки:

  1. В схеме компоновки данных добавьте в выборку поле роли периода, например ПериодЧас (из виртуальной таблицы остатков и оборотов).
  2. Перейдите на вкладку «Настройки», выберите вашу группировку по полю ПериодЧас.
  3. Внизу в свойствах этой группировки найдите раздел «Тип дополнения» и установите значение «Час».
  4. Задайте параметры дополнения: укажите начальную и конечную дату (их можно связать с параметрами отчета &НачалоПериода и &КонецПериода).

В результате СКД сама добавит в отчет строки по всем часам, даже если по ним не было движений, и автоматически скопирует конечный остаток предыдущего заполненного часа в пустые интервалы.

Способ 3. Связь наборов данных (Динамический расчет остатка на каждый час)

Еще один профессиональный архитектурный подход в СКД — использование связей между наборами данных. Этот метод позволяет разделить получение «сетки времени» и получение самих остатков.

Разберем данный алгоритм по шагам:

  1. НаборДанных1 (Запрос-Календарь): Этот набор данных формирует только строки с часами за выбранный интервал.
  2. НаборДанных2 (Запрос-Остатки): Обычный запрос к виртуальной таблице Остатки с параметром &Период.
  3. Вкладка «Связи наборов данных»: Мы связываем два набора. В качестве источника связи выступает НаборДанных1 (поле Час), приемником является НаборДанных2. При этом мы указываем, что поле Час из источника должно передаваться в качестве **параметра** &Период для НабораДанных2.

Благодаря этому СКД при формировании отчета выполнит запрос к таблице остатков для каждого часа в отдельности. Это гарантирует 100% точность остатков на любую секунду времени, даже если обороты полностью отсутствовали.

Как правильно получить остаток на конец предыдущего дня (Ответ на Сообщение 10)

Часто возникает сопутствующий вопрос: если мы формируем отчет за конкретный день (например, 31.03.2019), как правильно вытащить остаток на конец предыдущего дня (30.03.2019)?

Выясним причину возможных ошибок: если просто передать дату 30.03.2019 23:59:59 в качестве параметра в виртуальную таблицу остатков, система может не учесть документы, проведенные ровно в эту секунду, так как остатки по умолчанию рассчитываются на «начало» секунды.

Для гарантированного получения корректных данных используем следующие приемы:

Вариант А. Начало текущего дня как альтернатива
Математически остаток на конец дня 30.03.2019 равен остатку на **начало дня** 31.03.2019 (00:00:00). Поэтому проще всего в параметрах виртуальной таблицы получать остаток на начало отчетного периода:

НАЧАЛОПЕРИОДА(&НачалоПериода, ДЕНЬ)

Вариант Б. Вычисление точной даты в выражении параметра СКД
Если вам принципиально нужно рассчитать именно границу предыдущего дня в СКД, перейдите на вкладку «Параметры» и для параметра, отвечающего за остатки, в колонке «Выражение» пропишите:

ДобавитьКДате(НачалоПериода(&НачалоПериода, "День"), "Секунда", -1)

Вариант В. Использование объекта «Граница» в кодовой базе
Если вы формируете отчет или выполняете запрос программно из модуля, для исключения погрешности последней секунды дня всегда используйте объект Граница с видом Включая:


КонецПредыдущегоДня = КонецДня(РабочаяДата - 86400); // Вычитаем 1 день
ПараметрГраница = Новый Граница(КонецПредыдущегоДня, ВидГраницы.Включая);
Запрос.УстановитьПараметр("МоментОстатков", ПараметрГраница);

Использование объекта Граница гарантирует, что все транзакции, записанные вплоть до последней миллисекунды предыдущего дня, будут корректно включены в расчет остатка.

← На главную