Как заполнить документ 1С из внешнего массива: решаем проблемы со ссылочными полями, табличными частями и датами

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

При интеграции 1С с внешними системами (например, базами данных MSSQL, веб-сервисами или сторонними API через формат JSON) разработчики часто сталкиваются с типовыми трудностями. Перенос простых текстовых или числовых данных обычно не вызывает проблем, но как только дело доходит до заполнения ссылочных реквизитов, работы с табличными частями или обработки дат, начинают возникать скрытые ошибки.

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

Проблема 1: Заполнение ссылочных реквизитов строковыми данными

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


ДокументОбъект.TEST = ЭлементМассива.TestName; // Ошибка! Попытка записать Строку в Ссылку

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

Чтобы решить эту задачу, нам необходимо предварительно найти элемент в справочнике по его наименованию. Если такой элемент уже существует в базе данных 1С, мы должны получить его ссылку. Если элемента нет — его нужно создать программно, записать и только после этого получить ссылку на созданный объект.

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


// Пример кода с логической ошибкой!
Тест = ЭлементМассива.TestName; 
ТестСсылка = Справочники.Тест.НайтиПоНаименованию(Тест);
Если НЕ ТестСсылка = Справочники.Тест.ПустаяСсылка Тогда
    ТестСсылка = Справочники.Тест.СоздатьЭлемент();
    ТестСсылка.Наименование = Тест;
    ТестСсылка.Записать();
КонецЕсли;
ДокументОбъект.TEST = ТестСсылка;

Разберем причину ошибки: в данном условии оператор НЕ меняет логику на противоположную. Конструкция буквально говорит системе: «Если ссылка НЕ пустая (то есть элемент НАЙДЕН), то зайди внутрь условия и создай новый дублирующий элемент». Если же элемент действительно не найден (ссылка пустая), код просто пропустит условие, ничего не создаст и запишет в документ пустую ссылку. Кроме того, при создании элемента переменной ТестСсылка ошибочно присваивается сам объект справочника, а не его ссылка.

Давайте исправим эту логику и напишем правильный, отказоустойчивый алгоритм:


ТестНаименование = СокрЛП(ЭлементМассива.TestName); 

// Ищем элемент в справочнике по наименованию
ТестСсылка = Справочники.Тест.НайтиПоНаименованию(ТестНаименование);

// Если элемент НЕ найден (то есть ссылка пустая), создаем его
Если ТестСсылка.Пустая() Тогда 
    НовыйЭлементОбъект = Справочники.Тест.СоздатьЭлемент();
    НовыйЭлементОбъект.Наименование = ТестНаименование;
    
    Попытка
        НовыйЭлементОбъект.Записать();
        ТестСсылка = НовыйЭлементОбъект.Ссылка; // Получаем ссылку созданного элемента
    Исключение
        ЗаписьЖурналаРегистрации("ОбменДанными", УровеньЖурналаРегистрации.Ошибка,,, 
            "Не удалось записать элемент справочника: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
        ТестСсылка = Справочники.Тест.ПустаяСсылка();
    КонецПопытки;
КонецЕсли;

// Теперь безопасно заполняем реквизит документа
ДокументОбъект.TEST = ТестСсылка;

---

Проблема 2: Дублирование строк при заполнении табличной части

При заполнении табличных частей документов часто возникает следующая ошибка. Разработчик пытается заполнить разные колонки строки табличной части последовательными вызовами метода Добавить():


// Ошибка! Создается две разные строки вместо одной
ДокументОбъект.Курсы.Добавить().Английский = ЭлементМассива.EngName;
ДокументОбъект.Курсы.Добавить().Испанский = ЭлементМассива.SpainName;

Выясним причину проблемы: метод Добавить() для табличной части при каждом вызове создает **новую пустую строку** в памяти, записывает в указанную колонку значение и возвращает управление. Таким образом, в приведенном выше примере в табличную часть будут добавлены две строки: в первой строке будет заполнена только колонка "Английский" (а "Испанский" останется пустой), а во второй строке — только "Испанский" (при пустом значении "Английский").

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


// Создаем одну строку и сохраняем ее в переменную "НоваяСтрока"
НоваяСтрока = ДокументОбъект.Курсы.Добавить();

// Заполняем реквизиты созданной строки
НоваяСтрока.Английский = ЭлементМассива.EngName;
НоваяСтрока.Испанский  = ЭлементМассива.SpainName;

---

Проблема 3: Преобразование дат при десериализации JSON

Передавая данные из MSSQL через формат JSON, разработчики регулярно сталкиваются с тем, что даты превращаются в пустые значения (01.01.0001 0:00:00) или отображаются как некорректные текстовые строки.

Дело в том, что стандарт JSON не имеет встроенного типа данных для представления даты и времени. Любая дата при сериализации в JSON преобразуется в строку (обычно в формате ISO 8601, например: "2026-03-30T14:25:00"). Когда платформа 1С считывает JSON, свойство объекта автоматически определяется как обычная Строка. Попытка записать эту строку напрямую в поле с типом Дата приведет к тому, что 1С запишет пустую дату.

Чтобы решить эту проблему, необходимо выполнить преобразование строки в дату. В платформе 1С для этого существуют удобные встроенные инструменты.

Вариант 1. Использование функции ПрочитатьДатуJSON()

Если у вас на руках уже есть строковое представление даты из десериализованного JSON-файла, вы можете быстро преобразовать его с помощью глобального метода:


// Преобразуем строку формата ISO в дату 1С
НоваяСтрока.Дата = ПрочитатьДатуJSON(ЭлементМассива.DateAdd, ФорматДатыJSON.ISO);

Вариант 2. Автоматическое чтение дат при парсинге JSON

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


ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(СтрокаJSON);

// Указываем имя свойства (или массив имен свойств), которые нужно преобразовать в Дату
ИменаСвойствСДатами = "DateAdd"; // Может быть массивом: Новый Массив; Массив.Добавить("DateAdd");

ДанныеИзJSON = ПрочитатьJSON(ЧтениеJSON, Ложь, ИменаСвойствСДатами, ФорматДатыJSON.ISO);
ЧтениеJSON.Закрыть();

---

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

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

Каждый вызов метода НайтиПоНаименованию() генерирует отдельный запрос к СУБД. Выполнение сотен единичных запросов в цикле нагружает сервер и замедляет работу всей системы. Это классическая проблема производительности "1+N".

Давайте разберем по шагам, как оптимизировать этот алгоритм по правилам стандартов разработки 1С:

  1. Сбор уникальных значений. Перед началом обработки мы обойдем исходный массив данных и соберем все уникальные текстовые наименования в отдельный список.
  2. Пакетный поиск одним запросом. Мы передадим этот список в единственный запрос к базе данных, чтобы сразу получить ссылки для всех уже существующих элементов справочника.
  3. Использование кэша (Соответствия). Результаты запроса мы поместим в объект Соответствие, где ключом будет строковое наименование, а значением — ссылка на элемент справочника. Это позволит нам моментально получать ссылки в оперативной памяти без обращения к СУБД.
  4. Транзакционная запись. Все создаваемые документы мы запишем в рамках одной транзакции. Это значительно ускорит работу дисковой подсистемы сервера за счет объединения транзакций СУБД.

Рассмотрим финальный, высокопроизводительный вариант кода, объединяющий все наши решения и оптимизации:


// ШАГ 1: Собираем уникальные наименования для поиска
МассивНаименований = Новый Массив;
Для Каждого ЭлементМассива Из ИсточникДанных Цикл
    Если ЭлементМассива.Id1C = "0" Тогда
        ИмяТеста = СокрЛП(ЭлементМассива.TestName);
        Если НЕ ЗначениеЗаполнено(ИмяТеста) Тогда
            Продолжить;
        КонецЕсли;
        
        Если МассивНаименований.Найти(ИмяТеста) = Неопределено Тогда
            МассивНаименований.Добавить(ИмяТеста);
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

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

// ШАГ 3: Заполнение документов в транзакции для максимального ускорения
НачатьТранзакцию();
Попытка
    Для Каждого ЭлементМассива Из ИсточникДанных Цикл
        Если ЭлементМассива.Id1C = "0" Тогда
            
            ТестИмя = СокрЛП(ЭлементМассива.TestName);
            Если НЕ ЗначениеЗаполнено(ТестИмя) Тогда
                Продолжить;
            КонецЕсли;
            
            // Получаем ссылку из нашего кэша в оперативной памяти
            ТестСсылка = КэшСправочника.Получить(ТестИмя);
            
            // Если в кэше ссылки нет — значит элемент не найден, создаем его программно
            Если ТестСсылка = Неопределено Тогда
                НовыйЭлемент = Справочники.Тест.СоздатьЭлемент();
                НовыйЭлемент.Наименование = ТестИмя;
                НовыйЭлемент.Записать();
                ТестСсылка = НовыйЭлемент.Ссылка;
                
                // Сразу добавляем в кэш, чтобы избежать дублирования при повторном поиске
                КэшСправочника.Вставить(ТестИмя, ТестСсылка);
            КонецЕсли;
            
            // Создаем и заполняем документ
            ДокументОбъект = Документы.Док1.СоздатьДокумент();
            ДокументОбъект.TEST = ТестСсылка;
            ДокументОбъект.Дата = ТекущаяДата();
            
            // Заполняем табличную часть в одну строку
            НоваяСтрока = ДокументОбъект.Курсы.Добавить();
            НоваяСтрока.Английский = ЭлементМассива.EngName;
            НоваяСтрока.Испанский  = ЭлементМассива.SpainName;
            
            // Безопасно преобразуем дату из формата ISO JSON
            Если ЗначениеЗаполнено(ЭлементМассива.DateAdd) Тогда
                НоваяСтрока.Дата = ПрочитатьДатуJSON(ЭлементМассива.DateAdd, ФорматДатыJSON.ISO);
            КонецЕсли;
            
            // Записываем документ
            ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
            
        КонецЕсли;
    КонецЦикла;
    
    // Если все документы успешно созданы, фиксируем транзакцию
    ЗафиксироватьТранзакцию();
Исключение
    // В случае любой ошибки откатываем изменения, чтобы не засорять базу данных
    ОтменитьТранзакцию();
    
    ЗаписьЖурналаРегистрации("ОбменДанными", УровеньЖурналаРегистрации.Ошибка,,, 
        "Ошибка при пакетном создании документов: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
    ВызватьИсключение "Процесс загрузки прерван из-за ошибки. Подробности в Журнале регистрации.";
КонецПопытки;

Использование транзакций, пакетных запросов и кэширования в оперативной памяти позволяет обрабатывать массивы данных в десятки раз быстрее по сравнению с базовым кодом, исключая зависания системы и блокировки таблиц базы данных.

← На главную