Переход с устаревшей платформы 1С 7.7 на современную версию 1С 8.3 часто вызывает трудности у начинающих разработчиков. Особенно это заметно при переносе алгоритмов, работающих с динамическим чтением структуры объектов. В семерке для этого использовались специализированные объекты вроде Шаблон или прямой обход метаданных через специализированные методы, а данные часто выгружались в текстовые файлы с разделителями. В восьмерке объектная модель претерпела значительные изменения, став более логичной, строгой и объектно-ориентированной.
В этой статье мы подробно разберем, как решить классическую задачу: создать внешнюю обработку «Перегрузка документа», которая выгружает выбранный документ в файл и загружает его обратно в базу в виде копии (с новым уникальным номером). При этом мы полностью исключим использование внутренних уникальных идентификаторов (GUID) и стандартной сериализации типа ЗначениеВСтрокуВнутр, так как на практике часто требуется переносить данные между разнородными базами, где GUID справочников не совпадают, либо действуют строгие корпоративные ограничения на выгрузку системных идентификаторов.
В платформе 1С 7.7 для подобных задач часто применялся объект Текст, строки в котором формировались вручную. В 1С 8.3 такой подход считается устаревшим и неэффективным. Вместо этого мы задействуем мощные современные механизмы платформы:
Метаданные для динамического обхода реквизитов шапки и табличных частей документа. Это делает обработку универсальной — она сможет работать с любым типом документа в конфигурации.Структура (для хранения одиночных значений шапки) и Массив (для упорядоченного хранения строк табличных частей).ЗаписьJSON и ПрочитатьJSON). Это гарантирует быстродействие, компактность файлов и отличную совместимость.Рассмотрим подробнее процесс экспорта данных. Наша задача — обойти все реквизиты выбранного документа, определить их типы, корректно обработать ссылки и записать всё в структурированном виде. Выясним, как правильно обойти метаданные.
Для начала напишем вспомогательную функцию, которая преобразует любое значение реквизита в простой формат, пригодный для сериализации в 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С 8.3. Проанализировав получившееся решение, можно выделить ключевые преимущества перед старым кодом из 1С 7.7:
ЗначениеВСтрокуВнутр или GUID объектов. Это позволяет переносить документы даже между совершенно разными базами данных, где справочники имеют одинаковые коды или наименования, но разные внутренние идентификаторы.УстановитьНовыйНомер() гарантирует, что создаваемый документ-копия получит корректный свободный номер в текущей информационной базе, исключая конфликты нумерации.Используйте этот шаблон в своих проектах для создания гибких и отказоустойчивых систем обмена данными на платформе 1С 8.3!