При реализации сложных алгоритмов расчета премий, например, квартальных или полугодовых, стандартных механизмов 1С:ЗУП 3.1 не всегда бывает достаточно. Часто возникает необходимость внедрить собственный показатель, такой как НормаЧасовЗаПериод, и заполнить его значение программно в процессе расчета документа «Начисление зарплаты и взносов». В этой статье мы подробно разберем, как правильно модифицировать таблицу начислений в расширении, избегая типичных ошибок доступа к коллекциям данных.
Представим ситуацию: мы завели новый показатель «Норма часов за квартал» и хотим использовать его в формуле начисления. Чтобы этот показатель корректно участвовал в расчете, его значение должно быть помещено в таблицу начислений менеджера расчета. Основная сложность заключается в том, что данные в расчете представлены в виде сложных вложенных структур. Таблица Начисления содержит в себе колонку Показатели, которая, в свою очередь, является вложенной таблицей значений или массивом структур.
Рассмотрим по шагам, как добраться до нужного нам показателя и присвоить ему значение, не вызвав при этом критическую ошибку «Поле объекта не обнаружено».
Первая проблема, с которой сталкиваются разработчики — попытка обращения к показателям по индексу. Код вида СтрокаНачисления.Показатели[0] является крайне опасным, так как у некоторых начислений (например, у районного коэффициента или доплат) показатели могут отсутствовать вовсе, либо их порядок может меняться. Проанализируем правильный алгоритм обхода.
Для начала нам необходимо убедиться, что колонка Показатели существует и содержит данные. Вместо проверки количества строк, профессиональнее проверять тип данных или использовать цикл Для Каждого, который в языке 1С корректно обрабатывает пустые коллекции. Разберем пример кода:
// Проходим по всем строкам таблицы начислений сотрудника
Для Каждого СтрокаНачисления Из Начисления Цикл
// Проверяем наличие таблицы показателей
// Важно: в некоторых контекстах Показатели могут быть Неопределено
Если СтрокаНачисления.Показатели = Неопределено Тогда
Продолжить;
КонецЕсли;
// Перебираем вложенную коллекцию показателей конкретного начисления
Для Каждого СтрокаПоказатель Из СтрокаНачисления.Показатели Цикл
// Сравниваем наименование показателя
// Приводим к строке, чтобы избежать проблем с типами ссылок
Если Строка(СтрокаПоказатель.Показатель) = "Норма часов за квартал" Тогда
// Устанавливаем требуемое значение
СтрокаПоказатель.Значение = 100;
КонецЕсли;
КонецЦикла;
КонецЦикла;
В данном примере мы используем приведение к строке при проверке имени показателя. Это позволяет коду работать стабильно, даже если в метаданных показатель переименован или передан как объект ссылки. Однако, если вы разрабатываете тиражное решение, лучше использовать предопределенные элементы или поиск по внутреннему идентификатору показателя.
Часто расчет показателя за период требует анализа отработанного времени или видов времени, привязанных к начислению. Здесь разработчики часто натыкаются на ошибку доступа, пытаясь обратиться к СтрокаНачисления.ВидыВремени. Выясним причину: в структуре менеджера расчета колонка ВидыВремени часто привязана не к самой строке расчета, а к объекту метаданных начисления.
Проанализируем ситуацию: чтобы получить доступ к списку видов времени, которые учитываются данным начислением, нужно обращаться через свойство Начисление. Посмотрим на пример реализации поиска конкретного вида времени:
Для Каждого СтрокаНачисления Из Начисления Цикл
// Прямое обращение к коллекции видов времени через объект начисления
Для Каждого СтрокаВидВремени Из СтрокаНачисления.Начисление.ВидыВремени Цикл
Если СтрокаВидВремени.ВидВремени = Справочники.ВидыИспользованияРабочегоВремени.Явка Тогда
// Выполняем логику, если начисление учитывает явку
// Например, здесь можно рассчитать пропорцию нормы
КонецЕсли;
КонецЦикла;
КонецЦикла;
Важный нюанс: если вам нужно не просто посмотреть настройки начисления, а узнать, сколько часов фактически было отработано по конкретному виду времени в текущем периоде, следует обращаться к другой таблице — ДанныеВремени, которая также доступна в модуле менеджера расчета.
При работе с крупными базами (более 500 сотрудников) циклы внутри циклов могут значительно замедлить процесс расчета документа. Рассмотрим, как оптимизировать наш код. Вместо того чтобы на каждой итерации выполнять сравнение строк, мы можем заранее подготовить массив или соответствие нужных нам показателей.
Также стоит обратить внимание на то, в какой именно процедуре расширения делается вставка кода. Рекомендуется использовать перехват в методах ПередРассчетом или ЗаполнитьПоказателиРассчитанныхНачислений. Это гарантирует, что к моменту выполнения вашего кода основные данные о сотрудниках уже подтянуты в память, но финальный расчет еще не произведен.
Рассмотрим пример с использованием блока Попытка...Исключение для обеспечения отказоустойчивости расчета:
Для Каждого СтрокаНачисления Из Начисления Цикл
Попытка
// Безопасно обрабатываем только те строки, где есть показатели
Если СтрокаНачисления.Показатели.Количество() > 0 Тогда
Для Каждого Пок Из СтрокаНачисления.Показатели Цикл
Если Пок.Показатель.Наименование = "Норма часов за квартал" Тогда
Пок.Значение = ВычислитьНормуЗаКвартал(СтрокаНачисления.Сотрудник, ТекущийПериод);
КонецЕсли;
КонецЦикла;
КонецЕсли;
Исключение
// Записываем ошибку в журнал регистрации, чтобы не прерывать общий расчет
ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации("РасчетПоказателей", УровеньЖурналаРегистрации.Ошибка,,, ТекстОшибки);
КонецПопытки;
КонецЦикла;
Прежде чем внедрять программный код в расширение, проанализируем, нельзя ли решить задачу стандартными средствами ЗУП 3.1. Для квартальных премий эффективным инструментом являются Накапливаемые показатели.
ОтработаноВремениВЧасах, но с уточнением периода.Если же бизнес-логика требует именно внешнего управления показателем (например, норма берется из внешней системы или сложного графика), то описанный выше программный метод в расширении будет наиболее надежным решением. Главное — всегда проверять коллекции на Неопределено и избегать обращения к элементам по жестким индексам [0] или [1].