При разработке систем учета ремонтов или сервисного обслуживания часто возникает задача: имея документ с датой начала и датой окончания события, развернуть эти данные в детализированный отчет по дням. Например, если ремонт длился с 02.01.2019 08:00 по 05.01.2019 20:00, нам нужно получить не общую сумму в 84 часа, а конкретные показатели: 16 часов в первый день, по 24 часа во второй и третий, и 20 часов в последний. В этой статье мы подробно разберем, как реализовать такой расчет с помощью механизмов запросов 1С.
Самый распространенный и эффективный способ «размножить» одну строку документа на несколько строк по дням — это соединение с таблицей дат. В типовых конфигурациях для этого идеально подходит регистр сведений РегламентированныйПроизводственныйКалендарь. Если же у вас самописная конфигурация, можно использовать любой другой справочник или регистр, содержащий непрерывную последовательность дат.
Проанализируем логику работы такого запроса. Нам необходимо выбрать все даты из календаря, которые попадают в диапазон между началом и окончанием ремонта, а затем для каждой даты вычислить фактические часы. Рассмотрим пример кода:
ВЫБРАТЬ
Календарь.ДатаКалендаря КАК Дата,
РемонтныйЛист.Ссылка КАК Документ,
РемонтныйЛист.ДатаНачала КАК ДатаНачала,
РемонтныйЛист.ДатаОкончания КАК ДатаОкончания
ПОМЕСТИТЬ ВТ_ДатыПериода
ИЗ
РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК Календарь
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РемонтныйЛист КАК РемонтныйЛист
ПО Календарь.ДатаКалендаря МЕЖДУ НАЧАЛОПЕРИОДА(РемонтныйЛист.ДатаНачала, ДЕНЬ)
И НАЧАЛОПЕРИОДА(РемонтныйЛист.ДатаОкончания, ДЕНЬ)
ГДЕ
РемонтныйЛист.Ссылка = &Ссылка;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ.Дата,
ВЫБОР
КОГДА ВТ.Дата = НАЧАЛОПЕРИОДА(ВТ.ДатаНачала, ДЕНЬ)
И ВТ.Дата = НАЧАЛОПЕРИОДА(ВТ.ДатаОкончания, ДЕНЬ)
ТОГДА РАЗНОСТЬДАТ(ВТ.ДатаНачала, ВТ.ДатаОкончания, СЕКУНДА)
КОГДА ВТ.Дата = НАЧАЛОПЕРИОДА(ВТ.ДатаНачала, ДЕНЬ)
ТОГДА РАЗНОСТЬДАТ(ВТ.ДатаНачала, КОНЕЦПЕРИОДА(ВТ.Дата, ДЕНЬ), СЕКУНДА) + 1
КОГДА ВТ.Дата = НАЧАЛОПЕРИОДА(ВТ.ДатаОкончания, ДЕНЬ)
ТОГДА РАЗНОСТЬДАТ(ВТ.Дата, ВТ.ДатаОкончания, СЕКУНДА)
ИНАЧЕ 86400
КОНЕЦ / 3600 КАК ЧасовРаботы
ИЗ
ВТ_ДатыПериода КАК ВТ
Разберем, что происходит в этом коде. В первой части мы соединяем документ с календарем. Условие МЕЖДУ позволяет получить столько строк, сколько дней затронул ремонт. Во второй части мы используем конструкцию ВЫБОР (CASE), чтобы определить, как считать время для конкретного дня:
Обратите внимание, что в примере выше мы использовали расчет в секундах с последующим делением на 3600. Это критически важно. Если использовать встроенную функцию РАЗНОСТЬДАТ(..., ЧАС), платформа 1С вернет количество целых границ часа. Например, разница между 08:50 и 09:10 составит 1 час, что фактически неверно для точного учета трудозатрат.
Также проанализируем ситуацию с функцией КОНЕЦПЕРИОДА. В 1С КОНЕЦПЕРИОДА(..., ДЕНЬ) возвращает время 23:59:59. Если мы просто вычтем из него 08:00:00, мы потеряем одну секунду. Чтобы получить ровно 24 часа для полных суток, в коде часто добавляют + 1 к разности секунд или используют ДОБАВИТЬКДАТЕ(КОНЕЦПЕРИОДА(Дата, ДЕНЬ), СЕКУНДА, 1), что фактически является началом следующего дня.
Если в вашей базе не ведется производственный календарь или вы хотите сделать запрос независимым от настроек системы, можно применить метод генерации чисел «на лету». Мы можем создать временную таблицу из 10 цифр и перемножить её саму на себя, получив последовательность от 0 до 999. Прибавляя эти числа в днях к начальной дате, мы получим нужный диапазон.
Рассмотрим этот элегантный прием:
ВЫБРАТЬ 0 КАК Цифра ПОМЕСТИТЬ Цифры ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2
// ... и так далее до 9
;
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&ДатаНачала, ДЕНЬ, т1.Цифра + т2.Цифра * 10) КАК Дата
ИЗ
Цифры КАК т1,
Цифры КАК т2
ГДЕ
ДОБАВИТЬКДАТЕ(&ДатаНачала, ДЕНЬ, т1.Цифра + т2.Цифра * 10) <= &ДатаОкончания
Этот подход позволяет сформировать таблицу дат программно внутри одного запроса. Это особенно полезно для внешних отчетов, которые должны работать в любой информационной базе без предварительной подготовки данных.
Если ваша конечная цель — отчет, то строить сложные конструкции в запросе не всегда обязательно. Выясним, как это делает профессиональный разработчик через СКД. В системе компоновки существует механизм дополнения периодов.
Вы можете выбрать в запросе только два поля: ДатаНачала и КоличествоЧасов. На закладке «Настройки» в СКД для группировки по полю даты можно указать тип дополнения «День». В этом случае система сама создаст строки для всех недостающих дней в периоде. Однако, для корректного распределения часов по дням (чтобы в первый и последний день были разные значения) всё же придется использовать вычисляемые поля с выражениями, аналогичными логике ВЫБОР из первого метода.
В процессе обсуждения на форуме прозвучала важная мысль: «Запрос к документам — плохая примета». Проанализируем, почему это так. Документы в 1С не имеют виртуальных таблиц остатков или оборотов. При росте базы (тысячи и десятки тысяч ремонтных листов) выполнение сложных расчетов с разыменованием полей и соединениями будет замедляться в геометрической прогрессии.
Рекомендуемое решение:
Создайте регистр сведений (например, ПродолжительностьРемонтовПоДням). При проведении документа «Ремонтный лист» выполняйте расчет распределения часов в коде на языке 1С и записывайте готовые данные (строка на каждый день) в этот регистр. В этом случае ваш отчет или запрос будет просто выбирать готовые цифры из таблицы, что обеспечит молниеносную скорость работы системы даже на очень больших объемах данных.
Мы рассмотрели несколько способов решения задачи. Выбор конкретного метода зависит от ваших условий:
Помните о точности: всегда старайтесь проводить промежуточные вычисления в минимальных единицах измерения (секундах), чтобы избежать накопления погрешности при округлении часов.