Как исправить ошибку «Поле объекта не обнаружено (Должность)» при переносе документов приема на работу из ЗУП 2.5 в ЗУП 3.1

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

При переходе с устаревшей конфигурации 1С:Зарплата и управление персоналом 2.5 (КОРП) на современную 1С:ЗУП 3.1 технические специалисты часто сталкиваются с необходимостью переноса накопленной кадровой истории. Если типовой перенос данных по каким-то причинам не подходит и вы настраиваете собственные правила конвертации в программе «Конвертация данных 2.0», велика вероятность столкнуться со специфическими ошибками в обработчиках событий.

Одной из самых частых и неочевидных проблем является ошибка «Поле объекта не обнаружено (Должность)», возникающая при загрузке документа ПриемНаРаботуСписком. В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем архитектурные различия между конфигурациями и напишем правильный, оптимизированный код для заполнения позиций штатного расписания.

Почему возникает ошибка: анализируем различия в структуре данных

Давайте разберем причину сбоя. Ошибка возникает в обработчике события ПослеЗагрузкиОбъекта для правила конвертации объектов (ПКО) ПриемНаРаботуСписком. Чтобы понять, почему система выдает сообщение об отсутствии реквизита, сравним метаданные документов в обеих версиях программ.

В конфигурации ЗУП 2.5 (КОРП) документ ПриемНаРаботуВОрганизацию является списочным. Сведения о принимаемых сотрудниках, их должностях, ставках и подразделениях хранятся в табличной части РаботникиОрганизации. Напрямую в шапке документа реквизитов должности и подразделения нет.

В конфигурации ЗУП 3.1 документ ПриемНаРаботуСписком устроен аналогичным образом. Основные кадровые сведения распределены по строкам табличной части, которая называется Сотрудники. В этой табличной части присутствуют следующие реквизиты:

Поскольку обработчик ПослеЗагрузкиОбъекта выполняется в контексте всего документа, переменная Объект ссылается непосредственно на весь документ (тип ДокументОбъект.ПриемНаРаботуСписком). Попытка напрямую вызвать свойство Объект.Должность или Объект.Подразделение неизбежно приводит к ошибке «Поле объекта не обнаружено», так как у самого документа в шапке таких реквизитов просто не существует.

Разбор исходного (некорректного) кода

Проанализируем код, который изначально пытался использовать разработчик в обработчике события:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ШтатноеРасписание.Ссылка КАК Ссылка,
    |	ШтатноеРасписание.Подразделение КАК Подразделение,
    |	ШтатноеРасписание.Должность КАК Должность
    |ИЗ
    |	Справочник.ШтатноеРасписание КАК ШтатноеРасписание
    |ГДЕ
    |	ШтатноеРасписание.Подразделение = &Подразделение
    |	И ШтатноеРасписание.Должность = &Должность";

Запрос.УстановитьПараметр("Должность", Объект.Должность); // Ошибка здесь!
Запрос.УстановитьПараметр("Подразделение", Объект.Подразделение); // Ошибка здесь!

тзРез = Запрос.Выполнить().Выгрузить();
Если тзРез.Количество() > 0 Тогда
    Объект.ДолжностьПоШтатномуРасписанию = тзРез[0].Ссылка; // Ошибка здесь!
КонецЕсли;

В этом фрагменте допущены сразу три критические ошибки:

  1. Параметры &Должность и &Подразделение заполняются из несуществующих реквизитов шапки документа (Объект.Должность, Объект.Подразделение).
  2. Результат поиска позиции штатного расписания записывается в несуществующий реквизит шапки Объект.ДолжностьПоШтатномуРасписанию.
  3. Код не учитывает, что в одном документе может быть принято сразу несколько сотрудников на абсолютно разные должности и в разные отделы. Соответственно, сопоставление штатных единиц должно выполняться индивидуально для каждого сотрудника в табличной части.

Правильное решение: обход табличной части в цикле

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

Рассмотрим рабочий вариант исправленного кода для обработчика ПослеЗагрузкиОбъекта:


// Создаем запрос один раз вне цикла для экономии ресурсов
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ШтатноеРасписание.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.ШтатноеРасписание КАК ШтатноеРасписание
    |ГДЕ
    |	ШтатноеРасписание.Подразделение = &Подразделение
    |	И ШтатноеРасписание.Должность = &Должность
    |	И НЕ ШтатноеРасписание.ПометкаУдаления"; // Исключаем удаленные позиции

// Обходим строки табличной части "Сотрудники" документа "ПриемНаРаботуСписком"
Для Каждого СтрокаТЧ Из Объект.Сотрудники Цикл
    
    // Проверяем заполненность кадровых данных в строке
    Если ЗначениеЗаполнено(СтрокаТЧ.Подразделение) И ЗначениеЗаполнено(СтрокаТЧ.Должность) Тогда
        
        // Передаем параметры из текущей строки табличной части
        Запрос.УстановитьПараметр("Должность", СтрокаТЧ.Должность);
        Запрос.УстановитьПараметр("Подразделение", СтрокаТЧ.Подразделение);
        
        Выборка = Запрос.Выполнить().Выбрать();
        
        // Если позиция в штатном расписании найдена, подставляем ее в строку
        Если Выборка.Следующий() Тогда
            СтрокаТЧ.ДолжностьПоШтатномуРасписанию = Выборка.Ссылка;
        КонецЕсли;
        
    КонецЕсли;
    
КонецЦикла;

Оптимизация производительности: пакетный запрос

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

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

Рассмотрим пример оптимизированного кода:


// Проверяем, есть ли вообще сотрудники в документе
Если Объект.Сотрудники.Количество() > 0 Тогда

    // Выгружаем ТЧ во временную таблицу значений для передачи в запрос
    ТаблицаСотрудников = Объект.Сотрудники.Выгрузить( , "Подразделение, Должность");
    
    // Удаляем дубликаты, чтобы минимизировать объем обрабатываемых данных
    ТаблицаСотрудников.Свернуть("Подразделение, Должность");
    
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
    
    // Передаем уникальные пары во временную таблицу запроса
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ВТ_Исходная.Подразделение КАК Подразделение,
        |	ВТ_Исходная.Должность КАК Должность
        |ПОМЕСТИТЬ ВТ_Кадры
        |ИЗ
        |	&ТаблицаСотрудников КАК ВТ_Исходная
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |	ВТ_Кадры.Подразделение КАК Подразделение,
        |	ВТ_Кадры.Должность КАК Должность,
        |	ШтатноеРасписание.Ссылка КАК СсылкаШР
        |ИЗ
        |	ВТ_Кадры КАК ВТ_Кадры
        |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ШтатноеРасписание КАК ШтатноеРасписание
        |		ПО ВТ_Кадры.Подразделение = ШтатноеРасписание.Подразделение
        |			И ВТ_Кадры.Должность = ШтатноеРасписание.Должность
        |ГДЕ
        |	НЕ ШтатноеРасписание.ПометкаУдаления";
        
    Запрос.УстановитьПараметр("ТаблицаСотрудников", ТаблицаСотрудников);
    
    // Выполняем запрос и выгружаем соответствия в индексированную таблицу
    ТаблицаСопоставления = Запрос.Выполнить().Выгрузить();
    ТаблицаСопоставления.Индексы.Добавить("Подразделение, Должность");
    
    // Быстро заполняем реквизиты строк документа на основе индексированной таблицы поиска
    Для Каждого СтрокаТЧ Из Объект.Сотрудники Цикл
        
        ПараметрыОтбора = Новый Структура("Подразделение, Должность", СтрокаТЧ.Подразделение, СтрокаТЧ.Должность);
        НайденныеСтроки = ТаблицаСопоставления.НайтиСтроки(ПараметрыОтбора);
        
        Если НайденныеСтроки.Количество() > 0 Тогда
            СтрокаТЧ.ДолжностьПоШтатномуРасписанию = НайденныеСтроки[0].СсылкаШР;
        КонецЕсли;
        
    КонецЦикла;

КонецЕсли;

Этот подход работает в разы быстрее, поскольку обращение к СУБД происходит ровно один раз, вне зависимости от количества сотрудников в документе приемки.

Влияние настроек ведения штатного расписания в ЗУП 3.1

При переносе данных важно учитывать функциональные опции целевой информационной базы 1С:ЗУП 3.1. Логика заполнения кадровых документов напрямую зависит от настроек кадрового учета.

Перейдите в раздел Настройка -> Кадровый учет -> Настройка штатного расписания и проверьте статус флага «Ведется штатное расписание»:

Методологические нюансы: Порядок переноса и дубликаты

Помимо исправления синтаксиса кода, обязательно обратите внимание на методологию конвертации данных между базами:

  1. Очередность переноса объектов (Приоритет): Наш код в обработчике кадрового документа ищет элементы в справочнике ШтатноеРасписание. Это означает, что на момент проведения или записи документа справочник штатного расписания в базе ЗУП 3.1 уже должен быть заполнен. Настройте правила конвертации так, чтобы позиции штатного расписания переносились и записывались раньше, чем документы приема на работу (увеличьте приоритет ПКО штатного расписания или расположите его выше в структуре файла правил).
  2. Проблема дублирования позиций: Поиск только по паре «Подразделение + Должность» может выдать некорректный результат, если в штатном расписании вашей компании созданы одноименные позиции с разными условиями труда (например, разные разряды, категории или графики работы). Если в ЗУП 2.5 вы активно вели учет по разрядам/категориям, обязательно добавьте проверку реквизита РазрядКатегория как в исходные данные, так и в условия связи поискового запроса.

Используя предложенные схемы сопоставления данных, вы сможете полностью исключить ошибки при переносе кадровой истории и обеспечите корректный запуск учета в новой системе 1С:ЗУП 3.1.

← На главную