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

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

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

В этой статье мы подробно разберем, почему физическая иерархия по подразделениям в типовых конфигурациях не всегда оправдана, рассмотрим типовые инструменты платформы без программирования, а также напишем надежный и быстрый программный алгоритм массового переноса сотрудников в целевые группы.

Архитектурные особенности и риски ручной группировки

Прежде чем приступать к программному переносу элементов, проанализируем, как устроен учет сотрудников в типовых конфигурациях на обычных формах (таких как 1С:ЗУП 2.5, 1С:УПП 1.3 или 1С:Комплексная автоматизация 1.1).

В этих решениях подразделение сотрудника не является жестким реквизитом самого справочника СотрудникиОрганизаций. Актуальное место работы хранится в периодическом регистре сведений РаботникиОрганизаций. Разделение справочника на физические папки отделов несет в себе следующие риски:

  1. Кадровые перемещения: При переводе сотрудника в другой отдел документом КадровоеПеремещение запись в регистре обновится автоматически, но в справочнике сотрудник останется лежать в старой папке. Возникнет расхождение данных, требующее повторного ручного или регламентного переноса.
  2. Внутреннее совместительство: Если один и тот же сотрудник работает одновременно в двух подразделениях, один физический элемент справочника технически невозможно поместить сразу в две разные папки.
  3. Уволенные сотрудники: Со временем в рабочих папках отделов накапливаются уволенные работники, что захламляет иерархию и усложняет визуальный поиск.
  4. Штатный режим отображения: В форме списка справочника на обычных формах предусмотрен режим отображения с деревом подразделений слева. В этом режиме список сотрудников справа фильтруется динамически на основании данных регистра без необходимости физически менять реквизит Родитель у элементов.

Если бизнес-задача все же требует жесткого распределения элементов по группам, разберем типовые и программные способы решения.

Решение без программирования: типовые обработки

Для разового перемещения сотен элементов не обязательно писать код с нуля. В составе типовых конфигураций и на диске ИТС присутствуют готовые универсальные инструменты:

  1. Групповая обработка справочников и документов: Доступна через меню Сервис -> Прочие обработки. Позволяет выбрать справочник СотрудникиОрганизаций, настроить отбор по актуальному подразделению (или списку сотрудников) и массово установить новое значение для реквизита Родитель.
  2. Универсальный подбор и обработка объектов: Позволяет составить произвольный запрос к базе с учетом виртуальной таблицы среза последних регистра РаботникиОрганизаций и применить групповое действие переноса в группу для отобранных ссылок.

Разбор ошибок в коде начинающих разработчиков

Посмотрим на пример типичного некорректного кода, часто встречающегося при первых попытках автоматизировать перенос:


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

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

Выясним основные причины неработоспособности такого подхода:

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

Правильный программный алгоритм переноса

Решим задачу эффективно. Построим алгоритм по следующим правилам:

  1. Сформируем один общий запрос, соединяющий элементы справочника и актуальные записи среза последних кадрового регистра.
  2. Исключим группы и помеченные на удаление элементы.
  3. В цикле найдем (или создадим) нужную группу-родитель с наименованием подразделения.
  4. Проверим, изменился ли родитель, чтобы не перезаписывать лишний раз неизмененные объекты.
  5. Запишем изменения в транзакции с перехватом возможных блокировок.

Рассмотрим пример готовой процедуры для внешней обработки:


Процедура ВыполнитьПереносСотрудниковПоОтделам() Экспорт

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

    Запрос.УстановитьПараметр("Период", ТекущаяДата());

    РезультатЗапроса = Запрос.Выполнить();
    Если РезультатЗапроса.Пустой() Тогда
        Сообщить("Данные для переноса не найдены.");
        Возврат;
    КонецЕсли;

    Выборка = РезультатЗапроса.Выбрать();
    
    // Соответствие для кэширования ссылок на группы, чтобы не искать их в базе на каждой итерации
    КэшГрупп = Новый Соответствие;
    ВсегоОбработано = 0;
    ВсегоПеренесено = 0;

    Пока Выборка.Следующий() Цикл
        
        ВсегоОбработано = ВсегоОбработано + 1;
        ИмяГруппы = СокрЛП(Выборка.ИмяПодразделения);
        
        Если ПустаяСтрока(ИмяГруппы) Тогда
            Продолжить;
        КонецЕсли;

        // Получаем ссылку на группу из кэша или ищем/создаем в справочнике
        ЦелеваяГруппа = КэшГрупп[ИмяГруппы];
        Если ЦелеваяГруппа = Неопределено Тогда
            ЦелеваяГруппа = Справочники.СотрудникиОрганизаций.НайтиПоНаименованию(ИмяГруппы, Истина);
            
            // Если группа подразделения еще не создана в справочнике сотрудников - создаем ее
            Если ЦелеваяГруппа.Пустая() Тогда
                НоваяГруппа = Справочники.СотрудникиОрганизаций.СоздатьГруппу();
                НоваяГруппа.Наименование = ИмяГруппы;
                НоваяГруппа.Записать();
                ЦелеваяГруппа = НоваяГруппа.Ссылка;
            КонецЕсли;
            
            КэшГрупп.Вставить(ИмяГруппы, ЦелеваяГруппа);
        КонецЕсли;

        // Если сотрудник уже находится в нужной группе, пропускаем запись
        Если Выборка.ТекущийРодитель = ЦелеваяГруппа Тогда
            Продолжить;
        КонецЕсли;

        // Переносим сотрудника в целевую группу
        Попытка
            СотрудникОбъект = Выборка.СотрудникСсылка.ПолучитьОбъект();
            СотрудникОбъект.Родитель = ЦелеваяГруппа;
            СотрудникОбъект.Записать();
            ВсегоПеренесено = ВсегоПеренесено + 1;
        Исключение
            Сообщить("Ошибка при переносе сотрудника " + Выборка.СотрудникСсылка + ": " + ОписаниеОшибки(), СтатусСообщения.Важное);
        КонецПопытки;

    КонецЦикла;

    Сообщить(СтрШаблон("Обработка завершена. Проверено сотрудников: %1, успешно перенесено: %2", ВсегоОбработано, ВсегоПеренесено));

КонецПроцедуры

Оптимизация для больших баз данных

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

  1. Пакетная фиксация транзакций: Оборачивать запись порциями по 100–200 элементов в конструкцию НачатьТранзакцию() и ЗафиксироватьТранзакцию().
  2. Отключение проверок при записи: Использовать метод Объект.ОбменДанными.Загрузка = Истина перед вызовом метода Записать(), если конфигурация содержит тяжелые подписки на события при изменении карточки сотрудника, не влияющие на смену папки.

Применение единого запроса с виртуальной таблицей СрезПоследних и кэшированием созданных групп обеспечивает максимальное быстродействие и гарантирует целостность кадровых данных в системе.

← На главную