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