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