Здравствуйте.
Перед Вами возникла задача загрузки, выгрузки метаданных (документы, справочники, регистры...).
Возникнуть такая задача может по разным причинам. Как правило — это бэкап и перенос из базы в базу. Сейчас нас не интересует причина, просто примем как данность: потребовалась выгрузка, а затем последующая загрузка. Ее мы и будем сегодня выполнять.
Прежде всего, сразу хочу Вас остановить бежать открывать конфигуратор. Есть прекрасный инструмент, созданный самой фирмой 1С, который называется ВыгрузкаЗагрузкаДанныхXML83.epf (есть и ВыгрузкаЗагрузкаДанныхXML82.epf). Этим инструментом можно воспользоваться, если требуется перенести вообще все записи в метаданных, например, все документы товаров и услуг или все строки в справочнике номенклатура. Однако тонкой настройки выгрузки и загрузки Вы не при этом не получите. Выгрузятся все документы, а не конкретный или за период, а при загрузке, если документ уже такой есть, вы можете получить или задвоение, или сообщение об ошибке (которую, впрочем можно игнорировать). В принципе, все решаемо, но потребует более осмысленных действий, чем просто нажатие на кнопку.
Для более тонкой настройки выгрузки/загрузки можно использовать конфигурацию «Конвертация». Выгружать /загружать тут можно что угодно и как угодно, но изучение данного продукта потребует не одной недели практики.
А у Вас ситуация: нужно срочно что-то выгрузить / загрузить и некогда разбираться c существующими инструментами, а конфигуратор открывать Вы умеете, или нужно доработать уже существующий механизм, или разработать свой для пользователя, который не хочет разбираться в Конвертации, а хочет иметь «волшебную» кнопку, которая сделает все за него.
Для начала выполнения кода по загрузке или выгрузке нам нужно 2 кнопки: по нажатии на одну будет происходить выгрузка, а по нажатии на другую будет происходить загрузка. Где они будут расположены не важно, для примера, создадим обработку, в которую добавим форму, а на форму закинем эти 2 кнопки.
Формат выгрузки выберем xml, это стандарт и будем ему следовать, да и обрабатывать такой файл легче, чем, допустим, текстовой.
Для примера, добавим возможность выгрузки за период. Так как в выгрузке у нас выгружается только то что нам нужно, то загружать будем все подряд (хотя дело Ваше, можете добавить условия и в загрузку).
В результате у Вас должна получиться форма похожая на:
Переходим в модуль формы и вставляем следующий текст:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
НачалоПериода = НачалоГода(ТекущаяДата());
КонецПериода = КонецГода(ТекущаяДата());
КонецПроцедуры
&НаКлиенте
Процедура Выгрузить(Команда)
АдресФайлаВоВременномХранилище = "";
ВыгрузитьНаСервере(АдресФайлаВоВременномХранилище);
ПолучитьФайл(АдресФайлаВоВременномХранилище, "Выгрузка.xml");
КонецПроцедуры
&НаСервере
Функция ВыгрузитьНаСервере(АдресФайлаВоВременномХранилище)
Расширение = ".xml";
ИмяВременногоФайла = ПолучитьИмяВременногоФайла(Расширение);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ФП_Месячный.Номер КАК Номер,
| ФП_Месячный.Дата КАК Дата,
| ФП_Месячный.Период КАК Период,
| ФП_Месячный.Статьи.(
| НомерСтроки КАК НомерСтроки,
| Вид.Код КАК ВидКод,
| ПланГодовой КАК ПланГодовой,
| ПланМесячный КАК ПланМесячный
| ) КАК Статьи
|ИЗ
| Документ.ФП_Месячный КАК ФП_Месячный
|ГДЕ
| ФП_Месячный.ПометкаУдаления = ЛОЖЬ
| И ФП_Месячный.Дата >= &Дата1
| И ФП_Месячный.Дата <= &Дата2";
Запрос.УстановитьПараметр("Дата1", НачалоПериода);
Запрос.УстановитьПараметр("Дата2", КонецПериода);
Выборка = Запрос.Выполнить().Выгрузить();
//заполняем файло
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.ОткрытьФайл(ИмяВременногоФайла,"UTF-8");
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.ЗаписатьНачалоЭлемента("Выгрузка");
Для Каждого СТР Из Выборка Цикл
ЗаписьXML.ЗаписатьНачалоЭлемента("НовыйДокумент");
ЗаписьXML.ЗаписатьНачалоЭлемента("Номер");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(СТР.Номер));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("Дата");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(СТР.Дата));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("Период");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(СТР.Период));
ЗаписьXML.ЗаписатьКонецЭлемента();
Для Каждого ТЧ Из СТР.Статьи Цикл
ЗаписьXML.ЗаписатьНачалоЭлемента("НоваяСтатья");
ЗаписьXML.ЗаписатьНачалоЭлемента("ВидКод");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(ТЧ.ВидКод));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("ПланГодовой");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(ТЧ.ПланГодовой));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("ПланМесячный");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(ТЧ.ПланМесячный));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьКонецЭлемента();
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента();
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.Закрыть();
//заполняем файло
Файл = Новый Файл(ИмяВременногоФайла);
Если Файл.Существует() Тогда
ДвоичныеДанные = Новый ДвоичныеДанные(ИмяВременногоФайла);
АдресФайлаВоВременномХранилище = ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);
УдалитьФайлы(ИмяВременногоФайла);
КонецЕсли;
КонецФункции
&НаКлиенте
Процедура Загрузить(Команда)
ПриЗавершении = Новый ОписаниеОповещения("ПоместитьФайлКомандаЗавершение", ЭтотОбъект);
ПередПомещением = Новый ОписаниеОповещения("ПоместитьФайлКомандаПередПомещением", ЭтотОбъект);
ДиалогВыбораФайлов = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
ДиалогВыбораФайлов.Заголовок = "Выбор файла для помещения";
ДиалогВыбораФайлов.МножественныйВыбор = Ложь;
ДиалогВыбораФайлов.Фильтр = "Файлы выгрузки|*.xml";
НачатьПомещениеФайла(ПриЗавершении, , ДиалогВыбораФайлов, Истина, , ПередПомещением);
КонецПроцедуры
&НаКлиенте
Процедура ПоместитьФайлКомандаЗавершение(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
Если Результат Тогда
ОбработатьНаСервере(Адрес);
ОповеститьОбИзменении(Тип("ДокументСсылка.ФП_Месячный"));
Сообщить("Загрузка завершена");
Иначе
Сообщить("Всетаки нужно было что-то выбрать");
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ПоместитьФайлКомандаПередПомещением(ПомещаемыйФайл, ОтказОтПомещенияФайла, ДополнительныеПараметры) Экспорт
Если ПомещаемыйФайл.Размер() > 4*1024*1024 Тогда
ПоказатьПредупреждение(, "Превышен максимальный размер файла");
ОтказОтПомещенияФайла = Истина;
КонецЕсли;
Файл = ПомещаемыйФайл.Файл.ПолноеИмя;
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ОбработатьНаСервере(Адрес)
Данные = ПолучитьИзВременногоХранилища(Адрес);
Парсер = Новый ЧтениеXML;
Парсер.УстановитьСтроку(ПолучитьСтрокуИзДвоичныхДанных(Данные,КодировкаТекста.UTF8));
Пока Парсер.Прочитать() Цикл
Если (Парсер.Имя = "НовыйДокумент") И (Парсер.ТипУзла = ТипУзлаXML.НачалоЭлемента) Тогда
НовыйДокумент = Документы.ФП_Месячный.СоздатьДокумент();
КонецЕсли;
Если (Парсер.Имя = "НовыйДокумент") И (Парсер.ТипУзла = ТипУзлаXML.КонецЭлемента) Тогда
Попытка
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
Исключение
КонецПопытки;
КонецЕсли;
Если (Парсер.Имя = "Номер") И (Парсер.ТипУзла = ТипУзлаXML.НачалоЭлемента) Тогда
Если Парсер.Прочитать() Тогда
НовыйДокумент.Номер = Парсер.Значение;
КонецЕсли;
КонецЕсли;
Если (Парсер.Имя = "Дата") И (Парсер.ТипУзла = ТипУзлаXML.НачалоЭлемента) Тогда
Если Парсер.Прочитать() Тогда
НовыйДокумент.Дата = XmlЗначение(Тип("Дата"), Парсер.Значение);
КонецЕсли;
КонецЕсли;
Если (Парсер.Имя = "Период") И (Парсер.ТипУзла = ТипУзлаXML.НачалоЭлемента) Тогда
Если Парсер.Прочитать() Тогда
НовыйДокумент.Период = ПредопределенноеЗначение("Перечисление.ФП_Месяцы." + Парсер.Значение);
КонецЕсли;
КонецЕсли;
Если (Парсер.Имя = "НоваяСтатья") И (Парсер.ТипУзла = ТипУзлаXML.НачалоЭлемента) Тогда
НоваяСтрокаДокумента = НовыйДокумент.Статьи.Добавить();
КонецЕсли;
Если (Парсер.Имя = "ВидКод") И (Парсер.ТипУзла = ТипУзлаXML.НачалоЭлемента) Тогда
Если Парсер.Прочитать() Тогда
НоваяСтрокаДокумента.Вид = Справочники.ФП_СтатьиДвижения.НайтиПоКоду(Парсер.Значение);
КонецЕсли;
КонецЕсли;
Если (Парсер.Имя = "ПланГодовой") И (Парсер.ТипУзла = ТипУзлаXML.НачалоЭлемента) Тогда
Если Парсер.Прочитать() Тогда
НоваяСтрокаДокумента.ПланГодовой = Парсер.Значение;
КонецЕсли;
КонецЕсли;
Если (Парсер.Имя = "ПланМесячный") И (Парсер.ТипУзла = ТипУзлаXML.НачалоЭлемента) Тогда
Если Парсер.Прочитать() Тогда
НоваяСтрокаДокумента.ПланМесячный = Парсер.Значение;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Парсер.Закрыть();
КонецПроцедуры
Возвращаемся в форму. Переходим во вкладку команд формы. И указываем для команды выгрузить действие выгрузить, для команды загрузить действие загрузить:
В принципе, для моей конфигурации обработка уже рабочая. Но под Ваши задачи ее нужно переделать. А для этого разберем код.
Процедура ПриОткрытии устанавливает по умолчанию даты периода. Чтобы процедура срабатывала при открытии формы, ее следует указать в соответствующем событии формы.
Делать это не обязательно. Просто для удобства пользования.
Небольшое отступление.
Прежде всего, нам нужно понять, что наш персональный компьютер и сервер в большинстве случаев — это разные компьютеры. По крайней мере так рекомендовано. 1С и я вместе с ними будем исходить из того, что вы работаете за клиентским ПК, а исполняемый код и база данных находятся на другом — сервере.
Продолжаем исходить из того, что сервер — это отдельный компьютер (даже если это не так, но для универсальности кода придется так считать - Ваш ПК будет сервером для самого себя).
Так вот 1С решила, что добавлять/читать документы или записи в справочнике можно только на сервере.
Соответственно, код по добавлению, изменению, чтению, будет выполняться на ДРУГОМ ПК. Не на Вашем!
А так как файлы сервера — это не Ваши файлы, а Ваши файлы — это не файлы сервера. То нам нужно какое-то общее и для Вас и для сервера хранилище файлов. Вот в нем и будут происходить все операции. Такое хранилище называется временным.
Разбираем код выгрузки.
Команду выгрузки я подглядел в обработке ВыгрузкаЗагрузкаДанныхXML83.epf, о которой я говорил выше. Так что выгрузка у нас будет вполне себе типовая (правда я выкинул кучу строк ненужного кода, чем облегчил Вам труд)
Первым делом на клиенте создаем общее хранилище:
АдресФайлаВоВременномХранилище, так как мы не знаем ничего про него, то присваиваем ему пустой адрес.
Затем строчкой ВыгрузитьНаСервере(АдресФайлаВоВременномХранилище); мы заполняем наше временное хранилище данными которые хотим сохранить в виде файла.
Строкой ПолучитьФайл(АдресФайлаВоВременномХранилище, "Выгрузка.xml"); мы забираем из временного хранилища на свой компьютер данные, которые можно или посмотреть или сохранить в виде файла с названием Выгрузка.xml. Это стандартная процедура. Работает везде одинаково и разбору не подлежит.
А вот процедуру ВыгрузитьНаСервере(АдресФайлаВоВременномХранилище) разберем подробнее. Именно в ней происходит создание и заполнение временного файла обмена.
На сервере еще нет нужного нам файла, а потому первыми двумя строками мы определяем временный файл на сервере. Код выполняется на сервере, а потому временный файл тоже будет на сервере. Его мы заполним и поместим во временное хранилище из которого уже заберем себе на клиента.
Далее запросом получаем данные, которые необходимо выгружать в файл. Для примера, взят абстрактый документ, который называется ФП_Месячный, у него есть реквизиты: Дата, Номер, Период и табличная часть: Статьи.
Далее идет блок заполнения временного файла данными из запроса.
Первым делом мы объявляем новую ЗаписьXML. С помощью нее будет производиться запись во временный файл: ЗаписьXML.ОткрытьФайл(ИмяВременногоФайла,"UTF-8");
UTF-8 — это кодировка текста. Ее можно принять как стандарт для большинства случаев.
ЗаписьXML.ЗаписатьОбъявлениеXML() - записываем во временный файл служебную информацию, например, информацию о кодировке. Тут думать не надо. Метод стандартный, все что нужно сделает сам.
Далее запись информации идет по принципу скобок в тексте. Есть открывающая, есть закрывающая и что-то в середине. Можно вкладывать одни элементы в другие:
Открываем элемент: ЗаписьXML.ЗаписатьНачалоЭлемента("НазваниеЭлемента");
//что-то делаем, конкретно в нашем случае, мы делаем запись о реквизите документа либо о самом документе
Закрываем элемент: ЗаписьXML.ЗаписатьКонецЭлемента()
Во время записи активно пользуемся функцией XMLСтрока(), которая преобразует метаданные в строковой тип, понятный для записи/чтения XML.
Так как сохранять данные мы можем только простые (число, дата, строка, булево…). То ссылочный тип данных Вид (это справочник с видами статей) мы сохранить не можем. Но мы можем сохранить Код Вида из справочника видов — это строка. Когда мы будем наоборот загружать данные из ХML, то мы будем получать Код. По коду искать ссылку в справочнике. И уже эту ссылку записывать в документ.
После того как документ xml сформирован (а именно это мы и делали). Мы ЗаписьXML помещаем в файл, хотя по сути, он и так временный файл, проверяем всели у нас получилось, и если да, то помещаем данные во временное хранилище. Почему это так сделано, я не знаю, механизм типовой.
Ну и последним делом, мы удаляем на сервере временный файл. Если это не сделать, то сервер быстро переполниться временными файлами.
Вообще ситуация с временными файлами мне не нравится. Если загрузка/выгрузка будет идти постоянно, то накопитель сервера быстро выйдет из строя. Но так сделано разработчиками 1С. Этот код я подсмотрел у них в стандартной обработке.
Если можете подсказать как сделать все в оперативной памяти буду благодарен.
Разбираем код загрузки.
Тут уж я не поленился не стал брать типовой механизм, обшарил все форумы и нашел как провести загрузку без использования временных файлов.
Вообще варианта 2:
1. Простой. Считать xml файл в переменную (массив) на клиенте. А после передав эту переменную на сервер, читая данные из массива записывать новые документы.
2. Интересный. Передать xml файл на сервер и читать строки из него в оперативной памяти, не используя файлы вообще.
Оба этих метода требуют значительного количества памяти на сервере, а потому рекомендуется делать проверку на количество передаваемой информации. И если ее много, то всеже использовать файловый вариант и читать из файла блоками.
Рассмотрим вариант 2.
Помещать файл выгрузки на сервер во временное хранилище мы будем стандартной процедурой НачатьПомещениеФайла(). Так как механизм типовой, я просто скопировал код из примера 1С.
Для всех он будет одинаков. Единственно что я поменял, это фильтр (сделал xml). Стоит еще обратить внимание на размер. В моем случае он равен 4Гб. В Вашем, можете сделать меньше (больше нельзя).
Процедуру ПоместитьФайлКомандаЗавершение() переписал полностью. В ней я смотрю был ли выбран файл. Если файл был выбран, если он удовлетворяет размеру и расширению, тогда будет происходить чтение из данных указного файла в процедуре ОбработатьНаСервере(Адрес), где Адрес — это адрес временного хранилища. Ну а после того, как мы добавим данные, следует попросить систему перечитать базу, так как данные изменились. Это мы делаем процедурой ОповеститьОбИзменении(Тип("ДокументСсылка.ФП_Месячный")). Делать это не обязательно, но тогда чтобы увидеть результат нам придется закрыть и снова открыть список документов ФП_Месячный.
Рассмотрим процедуру ОбработатьНаСервере(Адрес)
Первым делом на сервере из временного хранилища нужно получить то, что мы помещали на клиенте: Данные = ПолучитьИзВременногоХранилища(Адрес);
Объявляем новую переменную с типом ЧтениеXML. В ней будет храниться весь документ XML.
Хитрая строка Парсер.УстановитьСтроку(ПолучитьСтрокуИзДвоичныхДанных(Данные,КодировкаТекста.UTF8)) преобразует двоичные данные в строку xml, которую затем записывает в объявленную ранее переменную с типом ЧтениеXML.
А далее читаем каждую строчку из XML и, в зависимости от данных, заполняем документ.
Ссылочные данные документа заполняются через их поиск по коду из xml.
Отступление.
Да возможность передачи ссылки имеется. Но я не рекомендую ее использовать, дабы данные не задвоились. То есть Документы с одинаковыми Кодами по сути будут разные так как у них разные ссылки (GUID). А если вести запись по коду документа, то документов с одинаковыми кодами Вы не получите. Будет сформировано исключительное событие и перенос данных в базу будет приостановлен. Поэтому я использую оператор попытка при записи документа. Если документ с таким кодом и датой уже есть, то запись произведена не будет, а будет переход к следующей записи.
PS. Благодаря подсказке Сергея, код преобразования данных в XML можно серьезно сократить. Просто замените функцию и процедуру. Будет менее понятно, что такое XML, как он должен заполняться и читаться, но ведь и не всем это надо:
&НаСервере
Функция ВыгрузитьНаСервере(АдресФайлаВоВременномХранилище)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ФП_Месячный.Номер КАК Номер,
| ФП_Месячный.Дата КАК Дата,
| ФП_Месячный.Период КАК Период,
| ФП_Месячный.Статьи.(
| НомерСтроки КАК НомерСтроки,
| Вид.Код КАК ВидКод,
| ПланГодовой КАК ПланГодовой,
| ПланМесячный КАК ПланМесячный
| ) КАК Статьи
|ИЗ
| Документ.ФП_Месячный КАК ФП_Месячный
|ГДЕ
| ФП_Месячный.ПометкаУдаления = ЛОЖЬ
| И ФП_Месячный.Дата >= &Дата1
| И ФП_Месячный.Дата <= &Дата2";
Запрос.УстановитьПараметр("Дата1", НачалоПериода);
Запрос.УстановитьПараметр("Дата2", КонецПериода);
Выборка = Запрос.Выполнить().Выгрузить();
//заполняем файло
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, Выборка);
//заполняем файло
АдресФайлаВоВременномХранилище = ПоместитьВоВременноеХранилище(ЗаписьXML.Закрыть(), УникальныйИдентификатор);
КонецФункции
&НаСервереБезКонтекста
Процедура ОбработатьНаСервере(Адрес)
Данные = ПолучитьИзВременногоХранилища(Адрес);
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ПолучитьСтрокуИзДвоичныхДанных(Данные));
Объекты = СериализаторXDTO.ПрочитатьXML(ЧтениеXML);
Для Каждого Документ Из Объекты Цикл
Попытка
НовыйДокумент = Документы.ФП_Месячный.СоздатьДокумент();
НовыйДокумент.Номер = Документ.Номер;
НовыйДокумент.Дата = Документ.Дата;
НовыйДокумент.Период = Документ.Период;
Для Каждого Статья Из Документ.Статьи Цикл
НоваяСтрокаДокумента = НовыйДокумент.Статьи.Добавить();
НоваяСтрокаДокумента.Вид = Справочники.ФП_СтатьиДвижения.НайтиПоКоду(Статья.ВидКод);
НоваяСтрокаДокумента.ПланГодовой = Статья.ПланГодовой;
НоваяСтрокаДокумента.ПланМесячный = Статья.ПланМесячный;
КонецЦикла;
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
Исключение
КонецПопытки;
КонецЦикла;
КонецПроцедуры