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