Почему поле ИтогоПростой не заполнено в запросе и как правильно рассчитать разность дат в 1С

Программист 1С v8.3 (Обычные формы) Управленческий учет Промышленность, строительство и АПК
← На главную

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

Анализ причины: почему данные видны на форме, но отсутствуют в базе

Рассмотрим ситуацию, с которой столкнулся автор темы. В документе ПростойОборудования есть реквизиты дат начала и окончания простоя, а также числовое поле ИтогоПростой. На форме документа значение заполняется, но запрос возвращает NULL или пустое значение. Выясним причину, изучив программный код в модуле формы:


Если ЗначениеЗаполнено(ВремяНачалаПростоя) И ЗначениеЗаполнено(ВремяУстраненияНеисправности) Тогда
    ЭлементыФормы.ПростойВМинутах.Значение = РассчитатьПростойВМинутах(ВремяНачалаПростоя, ВремяУстраненияНеисправности);
КонецЕсли;

Проанализируем этот фрагмент. Здесь разработчик совершил критическую ошибку: обратился напрямую к свойству Значение визуального элемента формы (ЭлементыФормы). Это приводит к тому, что результат расчета отображается пользователю «на лету», но не записывается в реквизит объекта. Следовательно, при записи документа в таблицу базы данных попадает пустое значение. Чтобы данные сохранялись, необходимо записывать результат в данные самого объекта.

Решение 1: Исправление логики записи в форму

Если нам принципиально нужно хранить вычисленное значение в реквизите документа, мы должны изменить подход к заполнению. Вместо обращения к элементам формы, следует работать напрямую с данными. Посмотрим на правильный вариант реализации в модуле формы (обычное приложение):


// В обработчике ПриИзменении для дат начала и окончания
Процедура ВремяНачалаПростояПриИзменении(Элемент)
    РассчитатьИЗаписатьПростой();
КонецПроцедуры

Процедура РассчитатьИЗаписатьПростой()
    Если ЗначениеЗаполнено(ВремяНачалаПростоя) И ЗначениеЗаполнено(ВремяУстраненияНеисправности) Тогда
        // Записываем напрямую в реквизит объекта, связанный с полем
        ИтогоПростой = РассчитатьПростойВМинутах(ВремяНачалаПростоя, ВремяУстраненияНеисправности);
    КонецЕсли;
КонецПроцедуры

Важный момент: если вы используете управляемые формы, код будет выглядеть иначе (через Объект.ИтогоПростой), но принцип остается тем же — работаем с данными, а не с представлением.

Решение 2: Динамический расчет в запросе (Функция РАЗНОСТЬДАТ)

Хранение вычисляемых данных в базе часто считается избыточным (денормализация). Если у нас есть время начала и время окончания, мы можем в любой момент вычислить разницу прямо в запросе. Это избавит нас от необходимости создавать лишние реквизиты и писать обработки для заполнения старых данных. Разберем, как изменить исходный запрос, используя функцию РАЗНОСТЬДАТ.


ВЫБРАТЬ
    ПростойОборудования.Ссылка,
    ПростойОборудования.ВремяНачалаПростоя,
    ПростойОборудования.ВремяУстраненияНеисправности,
    РАЗНОСТЬДАТ(ПростойОборудования.ВремяНачалаПростоя, ПростойОборудования.ВремяУстраненияНеисправности, МИНУТА) КАК ИтогоПростойМинуты
ИЗ
    Документ.ПростойОборудования КАК ПростойОборудования
ГДЕ
    ПростойОборудования.Дата МЕЖДУ &НачалоПериода И &КонецПериода

Функция РАЗНОСТЬДАТ принимает три параметра:

  1. Начальная дата: из которой вычитаем.
  2. Конечная дата: которую вычитаем.
  3. Тип разности: СЕКУНДА, МИНУТА, ЧАС, ДЕНЬ, МЕСЯЦ, КВАРТАЛ или ГОД.

Проанализируем нюанс: если в документе одна из дат не заполнена, функция может вернуть непредсказуемый результат или ошибку в зависимости от СУБД. Поэтому всегда стоит использовать проверку на NULL или пустую дату:


ВЫБОР 
    КОГДА ПростойОборудования.ВремяНачалаПростоя = ДАТАВРЕМЯ(1, 1, 1) ИЛИ ПростойОборудования.ВремяУстраненияНеисправности = ДАТАВРЕМЯ(1, 1, 1)
        ТОГДА 0
    ИНАЧЕ РАЗНОСТЬДАТ(ПростойОборудования.ВремяНачалаПростоя, ПростойОборудования.ВремяУстраненияНеисправности, МИНУТА)
КОНЕЦ КАК ИтогоПростой

Решение 3: Использование вычисляемых полей в СКД

Если вы не хотите менять текст запроса, СКД предоставляет мощный механизм Вычисляемых полей. Рассмотрим пошагово, как это реализовать:

  1. Откроем схему компоновки данных.
  2. Перейдем на вкладку Вычисляемые поля.
  3. Добавим новое поле с заголовком «Простой (расчетный)».
  4. В колонке «Выражение» напишем: РазностьДат(ВремяНачалаПростоя, ВремяУстраненияНеисправности, "Минута").

Преимущество этого метода в том, что в самом отчете вы сможете легко менять единицы измерения (например, секунды на часы), просто поправив формулу в одном месте, без переписывания запроса и перепроведения документов.

Архитектурные рекомендации и подводные камни

Выясним, какой подход лучше выбрать для долгосрочной эксплуатации системы. Программисту следует помнить о следующих аспектах:

1. Актуальность данных. Если вы храните ИтогоПростой в реквизите, и пользователь изменит дату начала простоя программно или через групповую обработку (которая не вызывает события формы), значение реквизита «протухнет» — станет неверным. Чтобы этого избежать, расчет следует перенести в модуль объекта в обработчик ПередЗаписью.


// Модуль объекта документа
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // Выполняем расчет перед сохранением в базу
    Если ЗначениеЗаполнено(ВремяНачалаПростоя) И ЗначениеЗаполнено(ВремяУстраненияНеисправности) Тогда
        ЭтотОбъект.ИтогоПростой = (ВремяУстраненияНеисправности - ВремяНачалаПростоя) / 60; // Разница в секундах / 60
    КонецЕсли;
КонецПроцедуры

2. Учет графиков работы. В реальных производственных задачах простой оборудования часто не должен включать выходные дни или нерабочее время. Обычная РАЗНОСТЬДАТ считает астрономическое время. Если требуется учитывать график, придется использовать левое соединение с регистром сведений (например, ГрафикиРаботы) или использовать функции общих модулей в СКД, которые вычисляют время по календарю.

3. Групповое исправление исторических данных. Если вы уже создали новый реквизит и хотите заполнить его в старых документах, напишите простую обработку. Посмотрим на пример кода:


Запрос = Новый Запрос("ВЫБРАТЬ Ссылка ИЗ Документ.ПростойОборудования");
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    ДокОбъект = Выборка.Ссылка.ПолучитьОбъект();
    // Логика расчета
    ДокОбъект.ИтогоПростой = РассчитатьМинуты(ДокОбъект.ВремяНачала, ДокОбъект.ВремяКонца);
    // Чтобы не пересчитывать движения по регистрам, используем:
    ДокОбъект.ОбменДанными.Загрузка = Истина; 
    ДокОбъект.Записать();
КонецЦикла;

Подведем итог: отсутствие данных в запросе при их наличии на форме — верный признак того, что работа ведется с визуальными элементами вместо данных объекта. Самым надежным и гибким способом решения задачи является расчет разности дат непосредственно в запросе или в вычисляемых полях СКД, так как это гарантирует актуальность цифр при любом изменении исходных дат.

← На главную