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