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