При разработке на платформе 1С:Предприятие часто возникают задачи по обмену данными между базами или сохранению состояния объектов во внешние файлы. Одной из классических задач, часто встречающихся в тестовых заданиях, является перегрузка документа в формат .txt. Особенность задачи заключается в жестких ограничениях: запрещено использовать механизмы XML, JSON, а также функции ЗначениеВСтрокуВнутр или выгрузку уникальных идентификаторов (GUID). Это заставляет разработчика использовать механизмы метаданных и строковые преобразования.
В этой статье мы подробно разберем, как построить универсальный алгоритм, который сможет «прочитать» любой документ, сохранить его структуру в текстовый файл и восстановить его в другой базе (или в этой же под другим номером).
Для того чтобы выгрузка была «исчерпывающей», нам необходимо получить не только основные реквизиты документа, но и данные его табличных частей. Поскольку мы не можем использовать UUID, единственным способом идентифицировать ссылочные данные (справочники, другие документы, перечисления) является их поиск по Коду, Наименованию или Номеру/Дате.
Проанализируем формат хранения данных в TXT. Нам нужно записывать информацию построчно, где каждая строка будет содержать имя реквизита, его тип и строковое представление значения. Для составных типов данных крайне важно сохранять имя конкретного типа, чтобы при загрузке знать, в какой таблице искать объект.
Рассмотрим процедуру выгрузки. Основная идея заключается в переборе коллекции Метаданные.Реквизиты выбранного документа. Для получения значения мы будем обращаться к объекту напрямую через имя реквизита.
Разберем по шагам создание процедуры выгрузки:
&НаСервере
Процедура ВыгрузитьОбъектНаСервере()
// Получаем объект документа из ссылки
ДокОбъект = Объект.Документ.ПолучитьОбъект();
МетаданныеДокумента = ДокОбъект.Метаданные();
ТекстовыйДок = Новый ТекстовыйДокумент;
// 1. Записываем тип документа (его имя в метаданных)
ТекстовыйДок.ДобавитьСтроку("ТипДокумента:" + МетаданныеДокумента.Имя);
// 2. Записываем стандартные реквизиты (Дата)
// Номер не выгружаем по условию задачи (он должен быть уникальным)
ТекстовыйДок.ДобавитьСтроку("СтандартныйРеквизит:Дата:Дата:" + Формат(ДокОбъект.Дата, "ДФ=yyyyMMddHHmmss"));
// 3. Перебор основных реквизитов
Для Каждого Реквизит Из МетаданныеДокумента.Реквизиты Цикл
ИмяРеквизита = Реквизит.Имя;
Значение = ДокОбъект[ИмяРеквизита];
СтрокаДанных = ПодготовитьСтрокуДляЗаписи(ИмяРеквизита, Значение);
ТекстовыйДок.ДобавитьСтроку("Реквизит:" + СтрокаДанных);
КонецЦикла;
// 4. Перебор табличных частей
Для Каждого ТЧ Из МетаданныеДокумента.ТабличныеЧасти Цикл
ТекстовыйДок.ДобавитьСтроку("НачалоТЧ:" + ТЧ.Имя);
Для Каждого СтрокаТЧ Из ДокОбъект[ТЧ.Имя] Цикл
ТекстовыйДок.ДобавитьСтроку("НачалоСтроки");
Для Каждого РеквизитТЧ Из ТЧ.Реквизиты Цикл
ЗначениеТЧ = СтрокаТЧ[РеквизитТЧ.Имя];
СтрокаДанныхТЧ = ПодготовитьСтрокуДляЗаписи(РеквизитТЧ.Имя, ЗначениеТЧ);
ТекстовыйДок.ДобавитьСтроку("РеквизитТЧ:" + СтрокаДанныхТЧ);
КонецЦикла;
ТекстовыйДок.ДобавитьСтроку("КонецСтроки");
КонецЦикла;
ТекстовыйДок.ДобавитьСтроку("КонецТЧ:" + ТЧ.Имя);
КонецЦикла;
ТекстовыйДок.Записать(Объект.ПутьКФайлу, КодировкаТекста.UTF8);
КонецПроцедуры
Обратите внимание на вспомогательную функцию ПодготовитьСтрокуДляЗаписи. Она критически важна для обработки ссылочных типов без использования GUID.
&НаСервере
Функция ПодготовитьСтрокуДляЗаписи(Имя, Значение)
ТипЗначения = ТипЗнч(Значение);
МетаДанныеТипа = Метаданные.НайтиПоТипу(ТипЗначения);
// Если это примитивный тип (Строка, Число, Булево, Дата)
Если МетаДанныеТипа = Неопределено Тогда
Возврат Имя + "|" + Строка(ТипЗначения) + "|" + Строка(Значение);
КонецЕсли;
// Если это ссылочный тип
ПолноеИмяТипа = МетаДанныеТипа.ПолноеИмя();
ПоисковоеЗначение = "";
Если Справочники.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
// Для справочников используем код (он надежнее наименования)
ПоисковоеЗначение = Значение.Код;
ИначеЕсли Документы.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
// Для документов - Дата и Номер (через разделитель)
ПоисковоеЗначение = Формат(Значение.Дата, "ДФ=yyyyMMddHHmmss") + "#" + Значение.Номер;
ИначеЕсли Перечисления.ТипВсеСсылки().СодержитТип(ТипЗначения) Тогда
// Для перечислений - имя значения в метаданных
Индекс = Перечисления[МетаДанныеТипа.Имя].Индекс(Значение);
ПоисковоеЗначение = МетаДанныеТипа.ЗначенияПеречисления[Индекс].Имя;
КонецЕсли;
Возврат Имя + "|" + ПолноеИмяТипа + "|" + ПоисковоеЗначение;
КонецФункции
Проанализируем процесс загрузки. Нам нужно прочитать файл построчно, определить вид документа, создать новый экземпляр и начать заполнение его свойств. Основная сложность — преобразование строкового представления обратно в объект 1С.
Выясним причину возможной ошибки «Значение не является значением объектного типа (Имя)». Она часто возникает, когда код пытается обратиться к свойству .Имя объекта метаданных, полученного через НайтиПоТипу для примитивного типа (например, Число). Поэтому всегда необходимо проверять результат поиска на Неопределено.
&НаСервере
Процедура ЗагрузитьОбъектНаСервере()
ТекстовыйДок = Новый ТекстовыйДокумент;
ТекстовыйДок.Прочитать(Объект.ПутьКФайлу, КодировкаТекста.UTF8);
НовыйДокумент = Неопределено;
ТекущаяТЧ = Неопределено;
НоваяСтрокаТЧ = Неопределено;
Для НомерСтроки = 1 По ТекстовыйДок.КоличествоСтрок() Цикл
ТекСтрока = ТекстовыйДок.ПолучитьСтроку(НомерСтроки);
ЧастиСтроки = СтрРазделить(ТекСтрока, ":", Ложь);
Если ЧастиСтроки.Количество() < 2 Тогда Продолжить; КонецЕсли;
Ключ = ЧастиСтроки[0];
Если Ключ = "ТипДокумента" Тогда
НовыйДокумент = Документы[ЧастиСтроки[1]].СоздатьДокумент();
ИначеЕсли Ключ = "СтандартныйРеквизит" И ЧастиСтроки[1] = "Дата" Тогда
НовыйДокумент.Дата = ИзСтрокиВДату(ЧастиСтроки[3]);
ИначеЕсли Ключ = "Реквизит" Тогда
Данные = СтрРазделить(ЧастиСтроки[1], "|");
НовыйДокумент[Данные[0]] = ПолучитьСсылкуИзДанных(Данные[1], Данные[2]);
ИначеЕсли Ключ = "НачалоТЧ" Тогда
ТекущаяТЧ = ЧастиСтроки[1];
ИначеЕсли Ключ = "НачалоСтроки" Тогда
НоваяСтрокаТЧ = НовыйДокумент[ТекущаяТЧ].Добавить();
ИначеЕсли Ключ = "РеквизитТЧ" Тогда
Данные = СтрРазделить(ЧастиСтроки[1], "|");
НоваяСтрокаТЧ[Данные[0]] = ПолучитьСсылкуИзДанных(Данные[1], Данные[2]);
КонецЕсли;
КонецЦикла;
Если НовыйДокумент <> Неопределено Тогда
НовыйДокумент.Записать();
Сообщить("Документ успешно загружен!");
КонецЕсли;
КонецПроцедуры
Разберем, как получить ссылку на объект, зная только его тип и строковый идентификатор (код или наименование). Мы будем использовать обращение к менеджерам объектов через квадратные скобки Справочники[...] или Документы[...].
&НаСервере
Функция ПолучитьСсылкуИзДанных(ПолноеИмяТипа, ЗначениеСтрокой)
Если ЗначениеСтрокой = "" Тогда
Возврат Неопределено;
КонецЕсли;
// Обработка примитивных типов
Если ПолноеИмяТипа = "Строка" Тогда Возврат ЗначениеСтрокой; КонецЕсли;
Если ПолноеИмяТипа = "Число" Тогда Возврат Число(ЗначениеСтрокой); КонецЕсли;
Если ПолноеИмяТипа = "Булево" Тогда Возврат ?(ЗначениеСтрокой = "Истина", Истина, Ложь); КонецЕсли;
Если ПолноеИмяТипа = "Дата" Тогда Возврат ИзСтрокиВДату(ЗначениеСтрокой); КонецЕсли;
// Разбор имени типа (например, "Справочник.Контрагенты")
МассивИмени = СтрРазделить(ПолноеИмяТипа, ".");
Категория = МассивИмени[0]; // Справочник
ИмяОбъекта = МассивИмени[1]; // Контрагенты
Если Категория = "Справочник" Тогда
Возврат Справочники[ИмяОбъекта].НайтиПоКоду(ЗначениеСтрокой);
ИначеЕсли Категория = "Документ" Тогда
// Разделяем дату и номер
МассивДок = СтрРазделить(ЗначениеСтрокой, "#");
ДатаДок = ИзСтрокиВДату(МассивДок[0]);
НомерДок = МассивДок[1];
Возврат Документы[ИмяОбъекта].НайтиПоНомеру(НомерДок, ДатаДок);
ИначеЕсли Категория = "Перечисление" Тогда
Возврат Перечисления[ИмяОбъекта][ЗначениеСтрокой];
КонецЕсли;
Возврат Неопределено;
КонецФункции
&НаСервере
Функция ИзСтрокиВДату(СтрДата)
// Формат yyyyMMddHHmmss
Попытка
Возврат Дата(Лев(СтрДата, 4), Сред(СтрДата, 5, 2), Сред(СтрДата, 7, 2), Сред(СтрДата, 9, 2), Сред(СтрДата, 11, 2), Прав(СтрДата, 2));
Исключение
Возврат '00010101';
КонецПопытки;
КонецФункции
Мы проанализировали алгоритм создания универсальной перегрузки данных. Основные моменты, на которые стоит обратить внимание:
UUID, а явное хранение имен типов в строке заменяет XML-сериализацию.ПолноеИмяТипа() в файл позволяет однозначно определить, к какому менеджеру (Справочнику или Перечислению) обращаться при загрузке.ТабличныеЧасти и их реквизитам.Данный подход является гибким и расширяемым. Вы можете добавить обработку дополнительных стандартных реквизитов, таких как Проведен или ПометкаУдаления, чтобы копия документа максимально соответствовала оригиналу.