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