Как правильно заполнить табличную часть документа новыми элементами справочника в 1С?

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

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

В чем причина ошибки «Объект не найден»?

Проанализируем механику работы со ссылочными типами данных в платформе 1С. Разработчики часто пытаются заранее сгенерировать ссылку с помощью методов ПолучитьСсылку() или ПолучитьСсылкуНового() и присвоить ее объекту через метод УстановитьСсылкуНового(), надеясь подставить эту ссылку в табличную часть документа еще до того, как справочник физически записан в базу данных.

Посмотрим, к чему это приводит на практике:

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

Правильный алгоритм: последовательное создание «снизу вверх»

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

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

Пример правильной реализации кода

Посмотрим на пример корректно выстроенного кода для загрузки сотрудников и формирования документа начисления:


ОтсутствующиеСотрудники = Новый ТекстовыйДокумент;

// Создаем документ до цикла, но не записываем его
НачислениеЗарплаты = Документы.НачислениеЗарплаты.СоздатьДокумент();
НачислениеЗарплаты.Дата = ТекущаяДата();

Для Каждого ТЭлемент Из iikoДанные Цикл
    
    ФИОМассив = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(ТЭлемент.Сотрудник, " ");
    
    // Добавляем строку в табличную часть документа
    тчНачисления = НачислениеЗарплаты.Начисления.Добавить();
    
    // Ищем существующего сотрудника по полному наименованию
    СсылкаСотрудник = Справочники.Сотрудники.НайтиПоНаименованию(ТЭлемент.Сотрудник);
    
    Если Не ЗначениеЗаполнено(СсылкаСотрудник) Тогда
        
        // 1. Создаем и записываем физическое лицо
        ФизЛицо = Справочники.ФизическиеЛица.СоздатьЭлемент();
        ФизЛицо.Наименование = ТЭлемент.Сотрудник;
        ФизЛицо.ФИО = ТЭлемент.Сотрудник;
        ФизЛицо.Фамилия = ФИОМассив[0];
        
        Если ФИОМассив.Количество() > 1 Тогда
            ФизЛицо.Имя = ФИОМассив[1];
        КонецЕсли;
        
        Если ФИОМассив.Количество() > 2 Тогда
            ФизЛицо.Отчество = ФИОМассив[2];
        КонецЕсли;
        
        ФизЛицо.Записать();
        СсылкаФизЛицо = ФизЛицо.Ссылка;
        
        // 2. Создаем запись в регистре сведений по полученной ссылке
        НаборЗаписей = РегистрыСведений.ФИОФизическихЛиц.СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.ФизическоеЛицо.Установить(СсылкаФизЛицо);
        
        НовЗапись = НаборЗаписей.Добавить();
        НовЗапись.Период = ТекущаяДата();
        НовЗапись.ФизическоеЛицо = СсылкаФизЛицо;
        НовЗапись.Фамилия = ФизЛицо.Фамилия;
        НовЗапись.Имя = ФизЛицо.Имя;
        НовЗапись.Отчество = ФизЛицо.Отчество;
        НаборЗаписей.Записать(Истина);
        
        // 3. Создаем и записываем сотрудника
        Сотрудник = Справочники.Сотрудники.СоздатьЭлемент();
        Сотрудник.Наименование = ТЭлемент.Сотрудник;
        Сотрудник.ФизическоеЛицо = СсылкаФизЛицо;
        // Записываем, чтобы получить ссылку
        Сотрудник.Записать();
        
        // Если требуется заполнить головного сотрудника ссылкой на самого себя
        Сотрудник.ГоловнойСотрудник = Сотрудник.Ссылка;
        Сотрудник.Записать();
        
        СсылкаСотрудник = Сотрудник.Ссылка;
        ОтсутствующиеСотрудники.ДобавитьСтроку(ТЭлемент.Сотрудник);
        
    КонецЕсли;
    
    // Заполняем реквизит строки табличной части готовой ссылкой
    тчНачисления.Сотрудник = СсылкаСотрудник;
    
КонецЦикла;

// Документ записывается однократно за пределами цикла
НачислениеЗарплаты.Записать(РежимЗаписиДокумента.Запись);
ОтсутствующиеСотрудники.Записать("D:\ОтсутствующиеСотрудники.txt");

Обеспечение целостности данных с помощью транзакции

Рассмотрим важный аспект надежности. При последовательном создании элементов может возникнуть ситуация, когда физическое лицо уже записано, а на шаге сохранения сотрудника или документа произошел сбой. Чтобы в базе не оставалось «мусорных» незавершенных данных, обернем обработку каждой строки или всю процедуру целиком в явную транзакцию:


НачатьТранзакцию();
Попытка
    // Блок последовательного создания физлица, сотрудника и заполнения документа
    // ...
    НачислениеЗарплаты.Записать(РежимЗаписиДокумента.Запись);
    
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение "Ошибка при загрузке данных: " + ОписаниеОшибки();
КонецПопытки;

Использование транзакций гарантирует, что либо все связанные объекты (физическое лицо, записи регистра сведений, сотрудник и документ) будут записаны в базу корректно, либо изменения полностью откатятся, сохранив целостность информационной базы.

← На главную