Как переписать выгрузку и загрузку документов с 1С 7.7 на 1С 8.3 через метаданные и без использования GUID?

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

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

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

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

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

  1. Объект метаданных: Мы будем использовать коллекцию Метаданные для динамического обхода реквизитов шапки и табличных частей документа. Это делает обработку универсальной — она сможет работать с любым типом документа в конфигурации.
  2. Коллекции данных: Вместо старых списков значений мы применим Структура (для хранения одиночных значений шапки) и Массив (для упорядоченного хранения строк табличных частей).
  3. Формат JSON: Для записи данных в файл и последующего чтения мы будем использовать встроенные механизмы работы с JSON (ЗаписьJSON и ПрочитатьJSON). Это гарантирует быстродействие, компактность файлов и отличную совместимость.
  4. Поиск по бизнес-ключам: Поскольку нам запрещено использовать GUID, ссылки на справочники и перечисления мы будем преобразовывать в простые структуры с кодами и наименованиями (бизнес-ключами). При загрузке система выполнит поиск объектов в базе данных по этим ключам.

Реализация модуля выгрузки документа

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

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


Функция ПолучитьЗначениеДляВыгрузки(Значение) Экспорт
    ТипЗначения = ТипЗнч(Значение);
    
    // Проверяем, является ли значение ссылкой на справочник
    Если Справочники.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
        Если Значение.Пустая() Тогда
            Возврат Неопределено;
        КонецЕсли;
        
        // Создаем структуру бизнес-ключей без использования GUID
        Результат = Новый Структура;
        Результат.Вставить("ТипДанных", "Справочник");
        Результат.Вставить("ИмяМетаданных", Значение.Метаданные().Имя);
        Результат.Вставить("Код", Значение.Код);
        Результат.Вставить("Наименование", Значение.Наименование);
        Возврат Результат;
        
    // Проверяем, является ли значение перечислением
    ИначеЕсли Перечисления.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
        Результат = Новый Структура;
        Результат.Вставить("ТипДанных", "Перечисление");
        Результат.Вставить("ИмяМетаданных", Значение.Метаданные().Имя);
        
        // Получаем системное имя значения перечисления
        Индекс = Перечисления[Значение.Метаданные().Имя].Индекс(Значение);
        ИмяЗначения = Метаданные.Перечисления[Значение.Метаданные().Имя].ЗначенияПеречисления[Индекс].Имя;
        Результат.Вставить("Значение", ИмяЗначения);
        Возврат Результат;
        
    // Простые типы (числа, строки, даты, булево) возвращаем как есть
    Иначе
        Возврат Значение;
    КонецЕсли;
КонецФункции

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


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

Реализация модуля загрузки документа

Перейдем ко второй части задачи — чтению файла и созданию нового документа. Проанализируем ситуацию: при чтении файла мы получим простые типы данных и структуры, описывающие ссылки. Нам необходимо восстановить эти ссылки в текущей базе данных по бизнес-ключам.

Напишем вспомогательную функцию восстановления ссылок. Мы реализуем двухэтапный поиск для справочников: сначала попытаемся найти элемент по коду, а если поиск не дал результатов — по наименованию. Это гарантирует максимальную надежность при переносе данных.


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

Теперь спроектируем основную процедуру загрузки. Нам нужно динамически создать документ нужного типа, заполнить его реквизиты, очистить и перезаполнить табличные части, сгенерировать новый уникальный номер базы данных и записать объект.


Процедура ЗагрузитьДокументИзФайла(ПутьКФайлу) Экспорт
    ФайлНаДиске = Новый Файл(ПутьКФайлу);
    Если Не ФайлНаДиске.Существует() Тогда
        ВызватьИсключение "Указанный файл не найден!";
    КонецЕсли;
    
    // 1. Чтение данных из JSON
    ЧтениеJSON = Новый ЧтениеJSON;
    ЧтениеJSON.ОткрытьФайл(ПутьКФайлу);
    ДанныеДокумента = ПрочитатьJSON(ЧтениеJSON);
    ЧтениеJSON.Закрыть();
    
    ИмяМетаданных = ДанныеДокумента.ИмяМетаданных;
    
    // 2. Динамическое создание нового документа нужного типа
    НовыйДокумент = Документы[ИмяМетаданных].СоздатьДокумент();
    
    // 3. Заполнение реквизитов шапки
    ДанныеШапки = ДанныеДокумента.Шапка;
    
    // Заполняем дату документа
    Если ДанныеШапки.Свойство("Дата") Тогда
        НовыйДокумент.Дата = ВосстановитьЗначениеИзФайла(ДанныеШапки.Дата);
    КонецЕсли;
    
    // Обходим метаданные реквизитов шапки нового документа
    Для каждого Реквизит Из НовыйДокумент.Метаданные().Реквизиты Цикл
        ИмяРеквизита = Реквизит.Имя;
        Если ДанныеШапки.Свойство(ИмяРеквизита) Тогда
            НовыйДокумент[ИмяРеквизита] = ВосстановитьЗначениеИзФайла(ДанныеШапки[ИмяРеквизита]);
        КонецЕсли;
    КонецЦикла;
    
    // 4. Заполнение табличных частей
    ДанныеТабличныхЧастей = ДанныеДокумента.ТабличныеЧасти;
    
    Для каждого ТЧ Из НовыйДокумент.Метаданные().ТабличныеЧасти Цикл
        ИмяТЧ = ТЧ.Имя;
        Если ДанныеТабличныхЧастей.Свойство(ИмяТЧ) Тогда
            НовыйДокумент[ИмяТЧ].Очистить(); // Очищаем на случай автозаполнения при создании
            
            МассивСтрокТЧ = ДанныеТабличныхЧастей[ИмяТЧ];
            Для каждого ДанныеСтроки Из МассивСтрокТЧ Цикл
                НоваяСтрока = НовыйДокумент[ИмяТЧ].Добавить();
                
                // Заполняем реквизиты строки табличной части
                Для каждого РеквизитТЧ Из ТЧ.Реквизиты Цикл
                    ИмяРеквизитаТЧ = РеквизитТЧ.Имя;
                    Если ДанныеСтроки.Свойство(ИмяРеквизитаТЧ) Тогда
                        НоваяСтрока[ИмяРеквизитаТЧ] = ВосстановитьЗначениеИзФайла(ДанныеСтроки[ИмяРеквизитаТЧ]);
                    КонецЕсли;
                КонецЦикла;
            КонецЦикла;
        КонецЕсли;
    КонецЦикла;
    
    // 5. Установка нового номера (номер не переносим из файла, обеспечивая уникальность)
    НовыйДокумент.УстановитьНовыйНомер();
    
    // Записываем новый документ в базу данных
    Попытка
        НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
        ПоказатьПредупреждение(, "Документ успешно загружен и сохранен! Создан объект: " + Строка(НовыйДокумент.Ссылка));
    Исключение
        ВызватьИсключение "Ошибка при записи созданного документа: " + ОписаниеОшибки();
    КонецПопытки;
КонецПроцедуры

Подключение обработчиков на управляемой форме

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

  1. ДокументСсылка (Тип: ЛюбаяСсылка) — для выбора исходного документа.
  2. ИмяФайла (Тип: Строка) — для хранения пути к файлу выгрузки.

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


&НаКлиенте
Процедура Выгрузить(Команда)
    Если Не ЗначениеЗаполнено(Объект.ДокументСсылка) Тогда
        ПоказатьПредупреждение(, "Выберите документ для выгрузки!");
        Возврат;
    КонецЕсли;
    
    Если ПустаяСтрока(Объект.ИмяФайла) Тогда
        ПоказатьПредупреждение(, "Укажите путь к файлу!");
        Возврат;
    КонецЕсли;
    
    ВыгрузитьНаСервере();
КонецПроцедуры

&НаСервере
Процедура ВыгрузитьНаСервере()
    ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
    ОбработкаОбъект.ВыгрузитьДокументВФайл(Объект.ДокументСсылка, Объект.ИмяФайла);
КонецПроцедуры

&НаКлиенте
Процедура Загрузить(Команда)
    Если ПустаяСтрока(Объект.ИмяФайла) Тогда
        ПоказатьПредупреждение(, "Укажите путь к файлу для загрузки!");
        Возврат;
    КонецЕсли;
    
    ЗагрузитьНаСервере();
КонецПроцедуры

&НаСервере
Процедура ЗагрузитьНаСервере()
    ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
    ОбработкаОбъект.ЗагрузитьДокументИзФайла(Объект.ИмяФайла);
КонецПроцедуры

Преимущества разработанного подхода

Мы успешно переписали и адаптировали алгоритм под современные стандарты платформы 1С 8.3. Проанализировав получившееся решение, можно выделить ключевые преимущества перед старым кодом из 1С 7.7:

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

Используйте этот шаблон в своих проектах для создания гибких и отказоустойчивых систем обмена данными на платформе 1С 8.3!

← На главную