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

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

В процессе разработки в системе 1С:Предприятие 8 часто возникает задача: собрать данные из регистра сведений, которые были порождены конкретным документом, просуммировать их и вывести результат в поле самого документа. Типичный пример — учет трудозатрат в задании на работу, где записи в регистр могут добавляться многократно (при остановке и возобновлении работ), а в документе нужно видеть итоговую трудоемкость.

Разберем, как правильно реализовать этот механизм, избегая типичных ошибок начинающих разработчиков, связанных с производительностью и клиент-серверным взаимодействием.

Анализ проблемы и типичные ошибки

Проанализируем ситуацию: у нас есть документ ЗаданиеНаРаботу и регистр сведений ижтисиТрудозатраты. Нам нужно просуммировать ресурс Трудозатраты по всем записям, где измерение Задание равно ссылке на наш текущий документ.

Рассмотрим основные ошибки, которые часто допускаются в таких задачах:

  1. Отсутствие условия в запросе. Запрос без конструкции ГДЕ будет перебирать все записи регистра во всей базе данных, что приведет к критическому замедлению работы при росте объема данных.
  2. Перебор данных в цикле на стороне клиента. Вытягивание массива структур или таблицы значений с сервера на клиент для последующего суммирования — крайне неоптимальный путь. Всю математику и агрегацию (подсчет суммы) должен выполнять сервер базы данных.
  3. Работа с пустой ссылкой. Если документ новый и еще ни разу не записывался, его Объект.Ссылка будет пустой. В этом случае данных в регистре по нему быть еще не может, если только не использовался метод УстановитьСсылкуНового().

Решение 1: Оптимальный расчет через запрос на сервере

Наиболее правильный способ — создать на сервере функцию, которая принимает ссылку на документ, выполняет агрегирующий запрос и возвращает готовое число. Выясним, как это выглядит в коде. Нам понадобятся агрегатные функции языка запросов 1С, такие как СУММА.

Рассмотрим пример реализации функции в модуле формы или общем модуле:


&НаСервереБезКонтекста
Функция ПолучитьИтоговуюТрудоемкость(СсылкаНаДокумент)
    
    // Если ссылка пустая (новый документ), возвращаем 0
    Если СсылкаНаДокумент.Пустая() Тогда
        Возврат 0;
    КонецЕсли;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ЕСТЬNULL(СУММА(Трудозатраты.Трудозатраты), 0) КАК Итого
        |ИЗ
        |	РегистрСведений.ижтисиТрудозатраты КАК Трудозатраты
        |ГДЕ
        |	Трудозатраты.Задание = &Задание";
    
    Запрос.УстановитьПараметр("Задание", СсылкаНаДокумент);
    
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Итого;
    Иначе
        Возврат 0;
    КонецЕсли;
    
КонецФункции

В данном примере мы используем &НаСервереБезКонтекста для повышения производительности, так как функции не требуется передавать все данные формы (контекст). Мы передаем только СсылкаНаДокумент. Обратите внимание на функцию ЕСТЬNULL — она гарантирует, что если записей нет, запрос вернет число 0, а не Null, что исключит ошибки при дальнейших математических операциях.

Решение 2: Обновление данных в интерфейсе

Теперь разберем, как вызвать этот расчет на клиенте, например, при нажатии кнопки или при открытии формы. Допустим, нам нужно обновить поле Трудоемкость в текущей строке табличной части или в реквизите объекта.


&НаКлиенте
Процедура РассчитатьТрудоемкостьНаКлиенте()
    
    // Получаем ссылку из объекта формы
    ТекущаяСсылка = Объект.Ссылка;
    
    // Вызываем серверную функцию
    ИтоговоеЗначение = ПолучитьИтоговуюТрудоемкость(ТекущаяСсылка);
    
    // Записываем результат в реквизит документа
    Объект.Трудоемкость = ИтоговоеЗначение;
    
    // Если нужно обновить конкретную строку табличной части:
    // Элементы.ИмяТабличнойЧасти.ТекущиеДанные.Трудоемкость = ИтоговоеЗначение;
    
КонецПроцедуры

Проанализируем ситуацию, когда данные в регистр пишутся "фоном" (например, другой формой). В этом случае после закрытия вспомогательной формы ввода времени в основной форме документа желательно вызвать обработчик ОповеститьОбИзменении(Тип("РегистрСведенийКлючЗаписи.ижтисиТрудозатраты")), а в форме документа поймать это через ОбработкаОповещения.

Решение 3: Автоматический расчет при записи

Иногда требуется, чтобы трудоемкость пересчитывалась автоматически при каждом сохранении документа. В этом случае код лучше разместить в Модуле объекта в процедуре ПередЗаписью. Рассмотрим этот вариант подробнее:


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

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

Работа с ТЗ и загрузка данных

Если вам нужно не просто получить одно число, а заполнить целую таблицу (например, расшифровку трудозатрат), проанализируем совет из обсуждения по использованию метода Загрузить(). Это очень быстрый способ передачи данных из результата запроса в таблицу на форме.

Посмотрим на пример кода для заполнения табличной части:


&НаСервере
Процедура ЗаполнитьРасшифровкуТрудозатрат()
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Трудозатраты.Дата,
        |	Трудозатраты.Сотрудник,
        |	Трудозатраты.Трудозатраты
        |ИЗ
        |	РегистрСведений.ижтисиТрудозатраты КАК Трудозатраты
        |ГДЕ
        |	Трудозатраты.Задание = &Задание";
    
    Запрос.УстановитьПараметр("Задание", Объект.Ссылка);
    
    // Выгружаем результат запроса напрямую в табличную часть документа
    Объект.ТаблицаТрудозатрат.Загрузить(Запрос.Выполнить().Выгрузить());
    
КонецПроцедуры

Этот метод Загрузить() работает значительно быстрее, чем обход выборки в цикле и добавление строк через Добавить(), так как выполняется одной операцией на уровне платформы.

Рекомендации по оптимизации

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

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

← На главную