Как реализовать универсальную выгрузку и загрузку документа в текстовый файл (TXT) средствами 1С

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

При разработке на платформе 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';
    КонецПопытки;
КонецФункции

Подведем итоги

Мы проанализировали алгоритм создания универсальной перегрузки данных. Основные моменты, на которые стоит обратить внимание:

  1. Использование метаданных: Это позволяет коду работать с любым типом документа без жесткой привязки к именам реквизитов.
  2. Обход ограничений: Поиск по коду/номеру заменяет UUID, а явное хранение имен типов в строке заменяет XML-сериализацию.
  3. Составные типы: Запись ПолноеИмяТипа() в файл позволяет однозначно определить, к какому менеджеру (Справочнику или Перечислению) обращаться при загрузке.
  4. Табличные части: Для полноценной копии документа необходимо использовать вложенные циклы по коллекциям ТабличныеЧасти и их реквизитам.

Данный подход является гибким и расширяемым. Вы можете добавить обработку дополнительных стандартных реквизитов, таких как Проведен или ПометкаУдаления, чтобы копия документа максимально соответствовала оригиналу.

← На главную