В практике разработчика 1С иногда встречаются задачи, которые кажутся искусственно ограниченными. Одной из таких классических задач, часто предлагаемых на собеседованиях, является создание внешней обработки для "Перегрузки документов" с жестким запретом на использование современных форматов обмена (XML, JSON) и уникальных идентификаторов (GUID). В этой ситуации мы лишаемся привычных инструментов ЗначениеВСтрокуВнутр() или УникальныйИдентификатор(), что заставляет нас обратиться к основам объектной модели и работы с метаданными.
В данной статье мы разберем, как построить надежный алгоритм выгрузки данных в обычный текстовый файл (.txt), используя только возможности платформы по работе с метаданными и поиск объектов по "естественным" ключам.
Основная сложность заключается в том, что нам необходимо передать не только значения простых типов (строки, числа, даты), но и ссылки на справочники, документы и перечисления. Поскольку мы не можем сохранить GUID ссылки, нам нужно выгрузить достаточное количество информации, чтобы в базе-приемнике однозначно идентифицировать объект. Проанализируем, какие данные нам понадобятся:
Для того чтобы в файле выгрузки указать, какой объект мы сохраняем, нам необходимо получить его строковое представление в системе метаданных. Рассмотрим ситуацию, когда реквизит документа является ссылкой. Чтобы получить его полное имя, воспользуемся методом Метаданные().
Разберем пример кода для получения типа реквизита:
// Предположим, ТекущееЗначение — это ссылка на справочник или документ
Если Справочники.ТипЗнч(ТекущееЗначение) = Тип("СправочникСсылка.Номенклатура") Тогда
ПолноеИмяТипа = ТекущееЗначение.Метаданные().ПолноеИмя();
// Вернет "Справочник.Номенклатура"
ИначеЕсли Документы.ТипЗнч(ТекущееЗначение) = Тип("ДокументСсылка.СчетНаОплатуПокупателю") Тогда
ПолноеИмяТипа = ТекущееЗначение.Метаданные().ПолноеИмя();
// Вернет "Документ.СчетНаОплатуПокупателю"
КонецЕсли;
Для универсального решения мы можем использовать конструкцию ТекущееЗначение.Метаданные().ПолноеИмя() для любых ссылочных типов. Это позволит нам при загрузке точно знать, к какому менеджеру (Справочники, Документы, Перечисления) обращаться.
Рассмотрим структуру текстового файла. Чтобы разбор данных был удобным, лучше всего использовать строковый формат с разделителями или тегами. Проанализируем предложенный формат: [ИмяРеквизита] Тип=... Значение=.... Это позволит нам линейно читать файл и восстанавливать объект по полям.
При выгрузке ссылочных типов (Справочников) важно учитывать их уникальность. Если у справочника есть код, приоритет отдается ему. Если кода нет — используем наименование. Посмотрим на пример реализации выгрузки реквизита:
Процедура ЗаписатьРеквизитВФайл(ТекстовыйФайл, ИмяРеквизита, Значение)
МетаДанныеОбъекта = Значение.Метаданные();
ПолноеИмяТипа = МетаДанныеОбъекта.ПолноеИмя();
ТекстовыйФайл.ЗаписатьСтроку("Реквизит:" + ИмяРеквизита);
ТекстовыйФайл.ЗаписатьСтроку("Тип:" + ПолноеИмяТипа);
Если Справочники.ТипЗнч(Значение) Тогда
// Для справочников пишем Код или Наименование
ТекстовыйФайл.ЗаписатьСтроку("Код:" + Значение.Код);
ТекстовыйФайл.ЗаписатьСтроку("Наименование:" + Значение.Наименование);
ИначеЕсли Документы.ТипЗнч(Значение) Тогда
// Для документов важна связка Номер + Дата
ТекстовыйФайл.ЗаписатьСтроку("Номер:" + Значение.Номер);
ТекстовыйФайл.ЗаписатьСтроку("Дата:" + Формат(Значение.Дата, "ДФ=yyyyMMddHHmmss"));
ИначеЕсли Перечисления.ТипЗнч(Значение) Тогда
// Для перечислений лучше выгружать имя значения (не синоним!)
Индекс = Перечисления[МетаДанныеОбъекта.Имя].Индекс(Значение);
ИмяЗначения = МетаДанныеОбъекта.ЗначенияПеречисления[Индекс].Имя;
ТекстовыйФайл.ЗаписатьСтроку("Значение:" + ИмяЗначения);
Иначе
// Простые типы (Число, Строка, Дата, Булево)
ТекстовыйФайл.ЗаписатьСтроку("Значение:" + Строка(Значение));
КонецЕсли;
КонецПроцедуры
Как было замечено в обсуждении, выгрузка перечислений по синониму — это ненадежный путь. Правильнее всего выгружать имя значения перечисления, заданное в конфигураторе. Проанализируем ситуацию: синоним может измениться или локализоваться, в то время как программное имя остается стабильным. При загрузке мы восстановим значение через конструкцию: Перечисления[ИмяПеречисления][ИмяЗначения].
Теперь разберем, как восстановить документ в базе-приемнике. Поскольку мы не используем GUID, нам нужно найти существующие ссылки по выгруженным метаданным или создать новые. Выясним алгоритм поиска:
НайтиПоКоду(). Если результат ПустаяСсылка(), пробуем НайтиПоНаименованию().НайтиПоНомеру(Номер, Дата).Рассмотрим пример поиска справочника:
Функция НайтиСправочник(ПолноеИмяТипа, Код, Наименование)
// Отсекаем префикс "Справочник."
ИмяСправочника = Сред(ПолноеИмяТипа, 12);
Менеджер = Справочники[ИмяСправочника];
Ссылка = Менеджер.НайтиПоКоду(Код);
Если Ссылка.Пустая() Тогда
Ссылка = Менеджер.НайтиПоНаименованию(Наименование);
КонецЕсли;
Возврат Ссылка;
КонецФункции
Нужно понимать, что поиск по наименованию или номеру не гарантирует 100% точности. Рассмотрим основные риски, которые следует упомянуть при сдаче подобного задания:
Хотя по условию задачи запрещено использовать XML как структуру файла, мы можем применить хитрость для сериализации значений внутри текстовых строк. Функции XMLСтрока(Значение) и XMLЗначение(Тип, Строка) позволяют канонически преобразовывать примитивные типы (Число, Дата, Булево) в строку и обратно. Это гораздо надежнее, чем парсить дату вручную, пытаясь угадать формат разделителей. Проанализируем, как это помогает: дата в формате XML всегда выглядит как 2016-12-19T12:00:00, что исключает ошибки региональных настроек системы.
Решение задачи универсальной перегрузки без GUID требует от нас глубокого понимания того, как объекты 1С хранятся и идентифицируются. Основной упор мы делаем на метаданные и менеджеры объектов. Несмотря на "костыльность" метода (поиск по наименованиям), в рамках академической задачи или жестких ограничений обмена это единственный путь. Главное — обеспечить избыточность данных при выгрузке, чтобы процесс загрузки имел максимум информации для принятия решения о сопоставлении объектов.