В интернете существует достаточно много статей по пакетам XDTO, но все они рассчитаны в основном на выгрузку документов. Но мною не было найдено практически никакой толковой, полной информации про загрузку справочников с иерархией. То есть, у нас есть справочник номенклатура, в котором есть несколько групп. И наша задача перенести из одной конфигурации в другую нашу номенклатуру, причем так, чтобы каждый элемент лежал в своей группе. Если во второй конфигурации эта группа существует, то мы просто добавляем в нее переносимый элемент. Если же группа отсутствует, то мы ее создаем.
В данной статье: первая конфигурация – из которой загружаем данные, вторая конфигурация – в которую будем загружать данные.
Обе конфигурации состоят из следующих объектов:
Иерархические справочники:
- «Номенклатура» (доп. реквизиты: Цена (тип – число), Поставщик (тип – СправочникСсылкаКонтрагенты))
- «Контрагенты» (доп. реквизиты типа строка: Адрес, Инн).
- Перечисление «ВидНоменклатуры».
Здесь очень важно обратить внимание, что основной переносимый справочник – это именно Номенклатура, именно его элементы и группы мы и будем создавать. Справочник Контрагенты в данном случае является вспомогательным – типообразующим для реквизита «Поставщик» в справочнике «Номенклатура». Тем ни менее, мы также будем создавать элементы справочника Контрагенты (в случае их отсутствия во второй конфигурации), но, если родитель контрагента во второй конфигурации будет отсутствовать, то контрагент будет создан в корне справочника «Контрагенты». Если же родитель будет найден, то созданный контрагент будет размещен в нем.
А теперь к реализации.
Добавим пакет XDTO. Имя: НоменклатураГруппыНомИПост. URI пространство имен: http://super-puper/nompost.
Два слова о пространстве имен. По ряду причин пространства именуются в виде URL-подобных строк. Но это не ссылка в интернете, а просто условный идентификатор. Простыми словами пространства имен нужны, чтобы 1С знал, объект из какого XDTO-пакета ему брать.
В пакет добавим 3 типа объектов:
- СправочникНоменклатура (основной справочник, который мы будем переносить)
- Список (для хранения массива
- СправочникКонтаргенты (необходим для переноса ссылочного реквизита «Поставщик»)
Если бы справочник Номенклатура не имел реквизитов типа СправочникСсылка, то мы могли бы обойтись первыми двумя типами объектов.
Заполним типы объектов свойствами как на рисунке ниже:
При этом у всех свойств СправочникаНоменклатура и СправочникаКонтрагенты, кроме ссылочных (в нашем примере это единственный реквизит СправочникаНоменклатура «Поставщик») ставим значение формы «Атрибут». Для ссылочного реквизита «Поставщик» значение формы указываем «Элемент».
У свойства Поставщик необходимо поставить минимальное количество 0. Иначе если оставить 1, то при попытке загрузить 1С будет ругаться, что у групп Номенклатуры не заполнено обязательное свойство «Поставщик».
Отдельно остановлюсь на типе объекта «Список». У его свойства «Строки» устанавливаем максимальное количество -1, что означает неограниченное количество элементов.
Создадим обработку «Выгрузка». Для удобства отладки ее можно сделать внешней. Создадим на ней строковый реквизит, куда запишем адрес и имя файла, в который будем выгружать данные, а также кнопку, по нажатию на которую будет происходить выгрузка.
Адрес файла будем указывать в таком формате:
Диск:\Папка\НазваниеФайла.xml
(на картинке с загрузкой далее по тексту будет пример)
Сначала напишем код выгрузки, потом разберем.
//Выгрузка иерархического справочника "Номенклатура" с ссылочным реквизитом Поставщик
&НаКлиенте
Процедура ВыгрузитьНомИПост(Команда)
ОбъектXDTO = ВыгрузитьНаСервереГруппыРодПоставшик();
ЗаписьXML = Новый ЗаписьXML();
ЗаписьXML.ОткрытьФайл(выбФайл);
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьБезОбработки(ОбъектXDTO);
ЗаписьXML.Закрыть()
КонецПроцедуры
&НаСервере
Функция ВыгрузитьНаСервереГруппыРодПоставшик()
ЗаписьXML = Новый ЗаписьXML();
ЗаписьXML.УстановитьСтроку();
// дополнительные реквизиты примитивного типа,
//которые могут оказаться равны NULL для групп
//заменяем функуцией ЕСТЬNULL, иначе мы не сможем их выгрузить
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Код КАК Код,
| ЕСТЬNULL(Номенклатура.Цена, 0) КАК Цена,
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры,
| Номенклатура.Поставщик КАК Поставщик,
| Номенклатура.ЭтоГруппа КАК ЭтоГруппа,
| Номенклатура.Родитель КАК Родитель
|ИЗ
| Справочник.Номенклатура КАК Номенклатура";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Список = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип("http://super-puper/nompost", "Список"));
Пока Выборка.Следующий() Цикл
сНоменклатураXDTO = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип("http://super-puper/nompost", "СправочникНоменклатура"));
сНоменклатураXDTO.Ссылка = СериализаторXDTO.XMLСтрока(Выборка.Ссылка);
сНоменклатураXDTO.Наименование = Выборка.Наименование;
сНоменклатураXDTO.ВидНоменклатуры = XMLСтрока(Выборка.ВидНоменклатуры); //ПЕРЕЧИСЛЕНИЕ
сНоменклатураXDTO.Цена = Выборка.Цена;
сНоменклатураXDTO.Код = Выборка.Код;
сНоменклатураXDTO.ЭтоГруппа = Выборка.ЭтоГруппа;
сНоменклатураXDTO.Родитель = СериализаторXDTO.XMLСтрока(Выборка.Родитель);
//Контрагент
Если НЕ Выборка.ЭтоГруппа Тогда
//эта проверка необходима если этот реквизит используется только для элементов (а для групп не используется)
// Первый параметр пространство имен, второй - имя типа в пространстве имен.
ТипОбъекта = ФабрикаXDTO.Тип("http://super-puper/nompost", "СправочникКонтрагенты");
// Создаем объект
ОбъектПоставщик = ФабрикаXDTO.Создать(ТипОбъекта);
ОбъектПоставщик.Ссылка = СериализаторXDTO.XMLСтрока(Выборка.Поставщик);
ОбъектПоставщик.Код = Выборка.Поставщик.Код;
ОбъектПоставщик.Наименование = Выборка.Поставщик.Наименование;
ОбъектПоставщик.ИНН = Выборка.Поставщик.ИНН;
ОбъектПоставщик.Адрес = Выборка.Поставщик.Адрес;
ОбъектПоставщик.Родитель = СериализаторXDTO.XMLСтрока(Выборка.Поставщик.Родитель);
сНоменклатураXDTO.Поставщик = ОбъектПоставщик;
//Иначе
//сНоменклатураXDTO.Поставщик = "";
//Если у свойства Поставщик в типе объекта XDTO Номенклатура Минимальное количество стоит 1,
//то это свойство необходимо заполнить
//для групп заполняем пустой строкой, поскольку иначе при загрузке при создании Фабрики 1С будет
//ругаться, что свойство "Поставщик" не заполнено.
//именно пустой строкой, а не ПустойСсылкуой, так как это для xml
//альтернатива - в пакете XDTO в типе Номенклатура свойствах свойства "Поставщик" установить
//минимальное количество - 0
//тогда ветка Иначе вообще не нужна
КонецЕсли;
Список.Строки.Добавить(сНоменклатураXDTO);
КонецЦикла;
ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, Список);
Возврат ЗаписьXML.Закрыть();
КонецФункции
В клиентской процедуре все достаточно стандартно и описано во множестве других статей. Остановимся на серверной процедуре.
Мы выгружаем запросом все нужные нам реквизиты. И обязательно – «ЭтоГруппа» (для того, чтобы отделить группы от элементов) и «Родитель» (чтобы знать, куда помещать каждый создаваемый элемент).
Далее мы создаем две фабрики – для самой номенклатуры и для контрагента.
А потом добавляем строки с номенклатурой в список. При этом мы проводим проверку на группу, поскольку реквизит поставщик используется только для элементов, и при попытке записать его для групп 1С выдаст ошибку).
В результате мы получим XML документ с переносимой информацией. Теперь осталось загрузить его во вторую конфигурацию.
Создадим обработку «Загрузка». Для удобства отладки ее можно сделать внешней. Создадим на ней строковый реквизит, где будем указывать адрес и имя файла, который необходимо загрузить, а также кнопку, по нажатию на которую будет происходить загрузка.
Код загрузки.
ВАЖНО!!!
1) Перед загрузкой в ТУ ЖЕ конфигурацию необходимо удалить всю номенклатуру из справочника Номенклатура и, по желанию, поставщиков из справочника «Контрагенты». При этом в справочнике «Контрагенты» целесообразно удалять только элементы (самих контрагентов), на которые есть ссылка в справочнике «Номенклатура», а группы-папки оставить. При загрузке в ДРУГУЮ конфигурацию убедитесь, что у нее такая же структура –есть иерархические справочники «Номенклатура», «Контрагенты» и перечисление «ВидыНоменклатуры».
2)Перед загрузкой целесообразно закрыть справочники «Номенклатура» и «Контрагенты». Если они останутся открыты, то, так как форма списка справочника рисуется при открытии, чтобы увидеть загруженную номенклатуру, нужно или обновить форму, или закрыть и открыть заново данные справочники.
Описание кода:
Функция «ДобавлениеСсылочныхРеквизитов» позволяет универсальным способом добавить в Номенклатуру столько реквизитов типа СправочникСсылка, сколько нам нужно. В данном примере он только один – реквизит «Поставщик» типа «СправочникСсылкаКонтрагенты». Но не забываем для каждого такого объекта создать в пакете свой тип объектов.
Если номенклатуры нет (оНоменклатура = Неопределено), мы ее создаем.
Но так как справочник иерархический, мы проверяем, группа это или элемент. И если это группа, то создаем группу, если элемент – то создаем элемент. При этом каждая группа или элемент окажутся в своем родители благодаря полю «родитель».
Два слова про создание элементов типообразующего справочника «Контрагенты».
Может так случиться, что в справочнике «Контрагенты» не окажется Группы, в которой должен находится наш поставщик. В таком случае, если этот момент не проверить, то у нас в справочнике Номенклатура создастся Номенклатура, и поле Поставщик будет заполнено, однако в самом справочнике «Контрагенты» поставщик не создастся. Тогда мы должны создать его хотя бы в корне. Если же родитель контрагента будет существовать, то поставщик нормально разместится в своей группе.
Таким образом,проверяем есть ли в справочнике Контрагенты такая группа (родитель). И если рОбъектXDTO.ПолучитьОбъект() = Неопределено будет ИСТИНА, то значит группы (родителя) у нашего поставщика в справочнике Контрагенты нет, и в поле родитель ничего указывать не надо, тогда поставщик он создастся в корне. Если же родитель есть, то помещаем поставщика в него.
&НаКлиенте
Процедура Загрузить(Команда)
ЧтениеТекста = Новый ЧтениеТекста(выбФайл);
ТекстФайла = ЧтениеТекста.Прочитать();
ЗагрузитьРеализации(ТекстФайла);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ЗагрузитьРеализации(ТекстФайла)
ЧтениеXML = Новый ЧтениеXML();
ЧтениеXML.УстановитьСтроку(ТекстФайла);
РеализацияXDTO = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML, ФабрикаXDTO.Тип("http://super-puper/nompost", "Список"));
Для Каждого сТоварыXDTO Из РеализацияXDTO.Строки Цикл
НоменклатураXDTO = сТоварыXDTO;
сНоменклатура = ПолучитьСсылкуПоУИ(НоменклатураXDTO.Ссылка, Справочники.Номенклатура);
рНоменклатура = ПолучитьСсылкуПоУИ(НоменклатураXDTO.Родитель, Справочники.Номенклатура);
оНоменклатура = сНоменклатура.ПолучитьОбъект();
Если оНоменклатура = Неопределено И НоменклатураXDTO.ЭтоГруппа = "true" Тогда
гНоменклатура = Справочники.Номенклатура.СоздатьГруппу();
гНоменклатура.УстановитьСсылкуНового(сНоменклатура);
гНоменклатура.Родитель = рНоменклатура;
гНоменклатура.Код = НоменклатураXDTO.Код;
гНоменклатура.Наименование = НоменклатураXDTO.Наименование;
гНоменклатура.Записать();
ИначеЕсли оНоменклатура = Неопределено И НоменклатураXDTO.ЭтоГруппа = "false" Тогда
оНоменклатура = Справочники.Номенклатура.СоздатьЭлемент();
оНоменклатура.УстановитьСсылкуНового(сНоменклатура);
оНоменклатура.Родитель = рНоменклатура;
оНоменклатура.Код = НоменклатураXDTO.Код;
оНоменклатура.Наименование = НоменклатураXDTO.Наименование;
оНоменклатура.Цена = НоменклатураXDTO.Цена;
//Если перечисление будет не заполнено, будет ругаться, поэтому надо осуществить проверку
//Проверяем не пустую ссылку, а пустую строку, так как в xml она будет значится как пустая строка
Если НЕ НоменклатураXDTO.ВидНоменклатуры = "" Тогда
оНоменклатура.ВидНоменклатуры = Перечисления.ВидНоменклатуры[НоменклатураXDTO.ВидНоменклатуры];
КонецЕсли;
//Поставщик будет добавлен в соотвующую группу если эта группа (родитель этого поставщика)существует.
//СОздаем стурктуру со списокм заполняемых у Поставщика реквизитов (КРОМЕ ссылки и родителя)
СтруктураРеквизитов = Новый Структура("Код,Наименование,ИНН,Адрес");
оНоменклатура.Поставщик = ДобавлениеСсылочныхРеквизитов(сТоварыXDTO.Поставщик,Справочники.Контрагенты,СтруктураРеквизитов);
оНоменклатура.Записать();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ДобавлениеСсылочныхРеквизитов(ПолученныйОбъект,СправочникПолучаемогоРеквизита,СтруктураРеквизитов)
ОбъектXDTO = ПолученныйОбъект;
сОбъектXDTO = ПолучитьСсылкуПоУИ(ОбъектXDTO.Ссылка, СправочникПолучаемогоРеквизита);
рОбъектXDTO = ПолучитьСсылкуПоУИ(ОбъектXDTO.Родитель, СправочникПолучаемогоРеквизита);
оОбъектXDTO = сОбъектXDTO.ПолучитьОбъект();
Если оОбъектXDTO = Неопределено Тогда
оОбъектXDTO = СправочникПолучаемогоРеквизита.СоздатьЭлемент();
оОбъектXDTO.УстановитьСсылкуНового(сОбъектXDTO);
Для каждого Элемент Из СтруктураРеквизитов Цикл
ДобавляемыйРеквизит = Элемент.Ключ;
оОбъектXDTO[ДобавляемыйРеквизит] = ОбъектXDTO[ДобавляемыйРеквизит];
КонецЦикла;
//Может так случиться, что в спраочнике Контрагенты не окажется Группы, в которой должен находится наш поставщк
//В таком случае, если этот момент не проверить, то у нас в справочнике Номенклатура создастся Номенклатура, и поле
//Поставщик будет заполнено, однако в справочнике Контрагенты поставщик не создастся.
//Тогда мы должны создать его хотя бы в корне.
//Если же группа (родитель) будет присутствовать, то поставщик нормально разместится в своей папке
//проверяем есть ли в справочнике Контрагенты такая группа (родитель)
//Исли рОбъектXDTO.ПолучитьОбъект() = Неопределено истина, то значит группы (родителя) у нашего поставщика в спарвочнике Контрагенты нет
//и в поле родитель ничего указывать не надо, тогда поставщик он создатся в корне
//если же родитель есть, то помещаем поставщика в него
Если НЕ рОбъектXDTO.ПолучитьОбъект() = Неопределено Тогда
оОбъектXDTO.Родитель = рОбъектXDTO;
КонецЕсли;
оОбъектXDTO.Записать();
КонецЕсли;
Возврат сОбъектXDTO;
КонецФункции
&НаСервереБезКонтекста
Функция ПолучитьСсылкуПоУИ(УникальныйИдентификатор, МенеджерОбъекта)
УИДСсылки = СериализаторXDTO.XMLЗначение(Тип("УникальныйИдентификатор"), УникальныйИдентификатор);
Возврат МенеджерОбъекта.ПолучитьСсылку(УИДСсылки);
КонецФункции
Примечание 1. В прикрепленных файлах 2 epf-файла обработок - выгрузка иерархического справочника и загрузка, а также dt-файл с базой подходящей данных.
Примечание 2. В статье рассматривается универсальный механизм переноса иерархического справочника на примере простейшей самописной конфигурации, полная структура которой описана в статье. Файл данной конфигурации также есть в архиве. Тестировалось на платформе 8.3(8.3.24.1761).