&НаКлиенте
Процедура ПриОткрытии(Отказ)
	ЗаполнитьСписокПлановОбмена();
	ЗагрузитьНастройкиПланОбмена();
	УстановитьЗначениеПланаОбмена();

	ЗагрузитьНастройкиУзелОбмена();
	// Сохранённый узел проверяем, а если он не годится (удалён, от другого плана, «Этот узел»)
	// или его не было — подставляем первый доступный узел плана.
	Если ПодобратьУзелОбмена() Тогда
		Обновить(Неопределено);   // реквизиты узла + статистика изменений сразу при открытии
	Иначе
		ПоказатьАтрибутыУзлаОбмена();
		ЗаполнитьСтатистикуИзмененийНаСервере();   // узла нет — просто чистим таблицу
	КонецЕсли;

	АктивироватьЭлементыПриВыбореУзлаОбмена();
	СохранитьНастройки();

	// Цепочка асинхронных вопросов: сначала про состав OData, затем (если узел так и не подобрался)
	// про создание узла. Второй вопрос вызывается из завершения первого — иначе он не покажется.
	ПредложитьВключитьОбъектыВODATA();
КонецПроцедуры


&НаСервере
Процедура ЗаполнитьСписокПлановОбмена()    
    СписокПланов = Новый СписокЗначений;
    Для Каждого ПланМета Из Метаданные.ПланыОбмена Цикл
        СписокПланов.Добавить(ПланМета.Имя, ПланМета.Представление());
    КонецЦикла;
    Элементы.ИмяПланаОбмена.СписокВыбора.ЗагрузитьЗначения(СписокПланов.ВыгрузитьЗначения());
КонецПроцедуры


&НаКлиенте    
Процедура УстановитьЗначениеПланаОбмена()
	Если ПроверитьЧтоПланОбменаСуществует(ИмяПланаОбмена)=Истина Тогда
		УзелОбмена = ПредопределенноеЗначение("ПланОбмена." + ИмяПланаОбмена + ".ПустаяСсылка");
		НастроитьПолеУзелОбмена();
	Иначе
		ИмяПланаОбмена = "";
		УзелОбмена = Неопределено;
		Элементы.УзелОбмена.Доступность = Ложь;
	КонецЕсли;                           
КонецПроцедуры

&НаСервере
Функция ПроверитьЧтоПланОбменаСуществует(ИмяПлана)
    Если ПустаяСтрока(ИмяПлана) Тогда
        Возврат Ложь;
    КонецЕсли;
	Возврат ПланыОбмена[ИмяПлана] <> Неопределено; 
КонецФункции

&НаКлиенте
Процедура ИмяПланаОбменаПриИзменении(Элемент)
	УстановитьЗначениеПланаОбмена();   // сбрасывает УзелОбмена в пустую ссылку нового плана

	Если ПодобратьУзелОбмена() Тогда   // подставляет первый узел этого плана
		Обновить(Неопределено);
	Иначе
		ПоказатьАтрибутыУзлаОбмена();
		ЗаполнитьСтатистикуИзмененийНаСервере();   // чистим статистику от прежнего плана
	КонецЕсли;

	АктивироватьЭлементыПриВыбореУзлаОбмена();
	СохранитьНастройки();

	ПредложитьВключитьОбъектыВODATA();
КонецПроцедуры

&НаКлиенте
Процедура НастроитьПолеУзелОбмена()
	
    Элементы.УзелОбмена.Доступность = Истина;
    МассивТипов = Новый Массив;
    МассивТипов.Добавить(Тип("ПланОбменаСсылка." + ИмяПланаОбмена));
    НовоеОписаниеТипов = Новый ОписаниеТипов(МассивТипов);
    Элементы.УзелОбмена.ОграничениеТипа = НовоеОписаниеТипов;
    УзелОбмена = НовоеОписаниеТипов.ПривестиЗначение(УзелОбмена);
    
КонецПроцедуры


&НаКлиенте
Процедура УзелОбменаНачалоВыбора(Элемент, ДанныеВыбора, ВыборДобавлением, СтандартнаяОбработка)

	Если ПустаяСтрока(ИмяПланаОбмена) Тогда
        Состояние("Сначала выберите план обмена!");
        СтандартнаяОбработка = Ложь;
        Возврат;
    КонецЕсли;

    СтандартнаяОбработка = Ложь;
    фиксНастройки = Новый НастройкиКомпоновкиДанных;
	
    Отбор = фиксНастройки.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    Отбор.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
    Отбор.ВидСравнения = ВидСравненияКомпоновкиДанных.НеРавно;
    Отбор.ПравоеЗначение = ПолучитьЭтотУзелНаСервере(ИмяПланаОбмена);
    Отбор.Использование = Истина;
    Отбор.РежимОтображения = РежимОтображенияЭлементаНастройкиКомпоновкиДанных.Недоступный;
    ПараметрыФормы = Новый Структура;
	
	ПараметрыФормы.Вставить("ФиксированныеНастройки", фиксНастройки);    
	
	ОткрытьФорму("ПланОбмена." + ИмяПланаОбмена + ".ФормаВыбора", ПараметрыФормы, Элемент);
КонецПроцедуры


// Без директивы — доступна и на клиенте, и на сервере. УзелОбмена может быть Неопределено
// (план не выбран/не существует), поэтому проверяем это перед вызовом метода Пустая().
Функция УзелПустой()
	Возврат УзелОбмена = Неопределено ИЛИ УзелОбмена.Пустая();
КонецФункции

&НаКлиенте
Процедура АктивироватьЭлементыПриВыбореУзлаОбмена()
	Если УзелПустой() Тогда
		Элементы.Обновить.Доступность=Ложь;
		Элементы.ПолнаяВыгрузкаВсехОбъектов.Доступность=Ложь;
		Элементы.ПолнаяОчисткаВсехИзменений.Доступность=Ложь;
		Элементы.ПолнаяВыгрузкаОбъекта.Доступность=Ложь;
		Элементы.ОчиститьИзмененияОбъекта.Доступность=Ложь;		
	Иначе
		Элементы.Обновить.Доступность=Истина;
		Элементы.ПолнаяВыгрузкаВсехОбъектов.Доступность=Истина;
		Элементы.ПолнаяОчисткаВсехИзменений.Доступность=Истина;
		Элементы.ПолнаяВыгрузкаОбъекта.Доступность=Истина;
		Элементы.ОчиститьИзмененияОбъекта.Доступность=Истина;	
	КонецЕсли;
КонецПроцедуры


	
&НаСервереБезКонтекста
Функция ПолучитьЭтотУзелНаСервере(ИмяПлана)
    Возврат ПланыОбмена[ИмяПлана].ЭтотУзел();
КонецФункции


// --- Подбор и создание узла обмена ---------------------------------------------------

&НаКлиенте
Функция ПодобратьУзелОбмена()
	// Подставляет в УзелОбмена подходящий узел плана: сохранённый (если он ещё годится) либо
	// первый доступный. «Этот узел» (текущая база) кандидатом не является — как и при ручном выборе.
	// Возврат: Истина — узел подобран, Ложь — подходящих узлов в плане нет.
	Если НЕ ПроверитьЧтоПланОбменаСуществует(ИмяПланаОбмена) Тогда
		Возврат Ложь;
	КонецЕсли;

	УзелОбмена = ПодобратьУзелОбменаНаСервере(ИмяПланаОбмена, УзелОбмена);
	Возврат НЕ УзелПустой();
КонецФункции


&НаСервереБезКонтекста
Функция ПодобратьУзелОбменаНаСервере(ИмяПлана, ТекущийУзел)
	МенеджерПлана = ПланыОбмена[ИмяПлана];

	// Сохранённый узел может быть от другого плана обмена (настройки переживают смену плана) —
	// в этом случае он не кандидат и в запрос идёт пустая ссылка нужного типа.
	Если ТипЗнч(ТекущийУзел) <> Тип("ПланОбменаСсылка." + ИмяПлана) Тогда
		ТекущийУзел = МенеджерПлана.ПустаяСсылка();
	КонецЕсли;

	// Один запрос решает обе задачи: проверяет, что текущий узел ещё существует и годится
	// (ВЫБОР даёт ему приоритет 0), и заодно даёт первый доступный узел, если не годится.
	Запрос = Новый Запрос;
	Запрос.Текст =
		"ВЫБРАТЬ ПЕРВЫЕ 1
		|	Узлы.Ссылка КАК Ссылка,
		|	ВЫБОР
		|		КОГДА Узлы.Ссылка = &ТекущийУзел ТОГДА 0
		|		ИНАЧЕ 1
		|	КОНЕЦ КАК Приоритет
		|ИЗ
		|	ПланОбмена." + ИмяПлана + " КАК Узлы
		|ГДЕ
		|	Узлы.Ссылка <> &ЭтотУзел
		|	И НЕ Узлы.ПометкаУдаления
		|УПОРЯДОЧИТЬ ПО
		|	Приоритет,
		|	Узлы.Наименование";
	Запрос.УстановитьПараметр("ЭтотУзел", МенеджерПлана.ЭтотУзел());
	Запрос.УстановитьПараметр("ТекущийУзел", ТекущийУзел);

	Выборка = Запрос.Выполнить().Выбрать();
	Если Выборка.Следующий() Тогда
		Возврат Выборка.Ссылка;
	КонецЕсли;

	Возврат МенеджерПлана.ПустаяСсылка();
КонецФункции


&НаКлиенте
Процедура ПредложитьСоздатьУзелОбмена()
	// Спрашиваем только если узел действительно не подобрался — иначе молча работаем с найденным.
	Если ПустаяСтрока(ИмяПланаОбмена) ИЛИ НЕ УзелПустой() Тогда
		Возврат;
	КонецЕсли;

	Текст = "В плане обмена """ + ИмяПланаОбмена + """ нет ни одного узла-получателя."
		+ Символы.ПС + "Без него выгрузка невозможна."
		+ Символы.ПС + Символы.ПС + "Создать узел сейчас?";

	Оповещение = Новый ОписаниеОповещения("ПредложитьСоздатьУзелОбменаЗавершение", ЭтотОбъект);
	ПоказатьВопрос(Оповещение, Текст, РежимДиалогаВопрос.ДаНет);
КонецПроцедуры


&НаКлиенте
Процедура ПредложитьСоздатьУзелОбменаЗавершение(Ответ, ДополнительныеПараметры) Экспорт
	Если Ответ <> КодВозвратаДиалога.Да Тогда
		Возврат;   // узел остаётся пустым, команды выгрузки недоступны — как и раньше
	КонецЕсли;

	Оповещение = Новый ОписаниеОповещения("СоздатьУзелОбменаЗавершение", ЭтотОбъект);
	ПоказатьВводСтроки(Оповещение, "onecdc", "Наименование нового узла обмена", 50, Ложь);
КонецПроцедуры


&НаКлиенте
Процедура СоздатьУзелОбменаЗавершение(Наименование, ДополнительныеПараметры) Экспорт
	Если Наименование = Неопределено ИЛИ ПустаяСтрока(Наименование) Тогда
		Возврат;   // отказ от ввода
	КонецЕсли;

	УзелОбмена = СоздатьУзелОбменаНаСервере(ИмяПланаОбмена, Наименование);

	Обновить(Неопределено);   // у нового узла статистика пустая, но таблица объектов заполнится
	АктивироватьЭлементыПриВыбореУзлаОбмена();
	СохранитьНастройки();
КонецПроцедуры


&НаСервереБезКонтекста
Функция СоздатьУзелОбменаНаСервере(ИмяПлана, Наименование)
	// СоздатьУзел() всегда создаёт узел корреспондента, «Этим узлом» он стать не может.
	ПланМета = Метаданные.ПланыОбмена[ИмяПлана];

	НовыйУзел = ПланыОбмена[ИмяПлана].СоздатьУзел();
	НовыйУзел.Наименование = Лев(Наименование, ПланМета.ДлинаНаименования);
	Если ПланМета.ДлинаКода > 0 Тогда
		НовыйУзел.Код = УникальныйКодУзла(ИмяПлана, Наименование, ПланМета.ДлинаКода);
	КонецЕсли;
	НовыйУзел.Записать();

	Возврат НовыйУзел.Ссылка;
КонецФункции


&НаСервереБезКонтекста
Функция УникальныйКодУзла(ИмяПлана, Основа, ДлинаКода)
	// Код узла уникален в пределах плана: подбираем свободный, добавляя счётчик к наименованию.
	// Если в вашем плане обмена включена автонумерация кода, эта функция не нужна —
	// достаточно НовыйУзел.УстановитьНовыйКод().
	Префикс = Лев(ВРег(СокрЛП(Основа)), ДлинаКода);
	Кандидат = Префикс;
	Счётчик = 1;

	Пока НЕ ПланыОбмена[ИмяПлана].НайтиПоКоду(Кандидат).Пустая() Цикл
		Хвост = Строка(Счётчик);
		ДлинаПрефикса = ?(ДлинаКода > СтрДлина(Хвост), ДлинаКода - СтрДлина(Хвост), 0);
		Кандидат = Лев(Префикс, ДлинаПрефикса) + Хвост;
		Счётчик = Счётчик + 1;
	КонецЦикла;

	Возврат Кандидат;
КонецФункции






&НаСервере
Процедура ЗагрузитьНастройкиПланОбмена() 
	КлючОбъекта = "ВыгрузкаИзмененийПоODATA";
	КлючНастроек = "ОсновныеНастройки";
    Настройки = ХранилищеОбщихНастроек.Загрузить(КлючОбъекта, КлючНастроек);
    
    Если ТипЗнч(Настройки) = Тип("Структура") Тогда
        ИмяПланаОбмена = Настройки.ИмяПланаОбмена; 
	КонецЕсли;	
	
КонецПроцедуры

&НаСервере
Процедура ЗагрузитьНастройкиУзелОбмена() 
	КлючОбъекта = "ВыгрузкаИзмененийПоODATA";
	КлючНастроек = "ОсновныеНастройки";
    Настройки = ХранилищеОбщихНастроек.Загрузить(КлючОбъекта, КлючНастроек);
    
    Если ТипЗнч(Настройки) = Тип("Структура") Тогда
        УзелОбмена = Настройки.УзелОбмена;
    КонецЕсли;	
	
КонецПроцедуры


&НаСервере
Процедура СохранитьНастройки()
	КлючОбъекта = "ВыгрузкаИзмененийПоODATA";
    КлючНастроек = "ОсновныеНастройки";
    
    Настройки = Новый Структура;
    Настройки.Вставить("ИмяПланаОбмена", ИмяПланаОбмена);
    Настройки.Вставить("УзелОбмена", УзелОбмена);
    
    ХранилищеОбщихНастроек.Сохранить(КлючОбъекта, КлючНастроек, Настройки);

КонецПроцедуры

&НаКлиенте
Процедура УзелОбменаОбработкаВыбора(Элемент, ВыбранноеЗначение, ДополнительныеДанные, ВыборДобавлением, СтандартнаяОбработка)
		
	Если ВыбранноеЗначение = ПолучитьЭтотУзелНаСервере(ИмяПланаОбмена) Тогда
		СтандартнаяОбработка = Ложь;
		УзелОбмена = ПредопределенноеЗначение("ПланОбмена." + ИмяПланаОбмена + ".ПустаяСсылка");
		ПоказатьПредупреждение(, "Нельзя выбирать текущую базу (""Этот узел""). 
							|Пожалуйста, выберите узел базы-корреспондента.");
		
		
	КонецЕсли;
	ПоказатьАтрибутыУзлаОбмена();
	
КонецПроцедуры

&НаСервере
Процедура ПоказатьАтрибутыУзлаОбмена()
	Если НЕ УзелПустой() Тогда
		Запрос = Новый Запрос;
		Запрос.Текст = 
		    "ВЫБРАТЬ
		    |   Узлы.НомерОтправленного,Узлы.НомерПринятого
		    |ИЗ ПланОбмена." + ИмяПланаОбмена + " КАК Узлы
		    |ГДЕ Узлы.Ссылка = &Узел";
		Запрос.УстановитьПараметр("Узел", УзелОбмена);
		Результат = Запрос.Выполнить().Выбрать();
		Если Результат.Следующий() Тогда
		    НомерОтправленного = Результат.НомерОтправленного;
			НомерПринятого = Результат.НомерПринятого;
		КонецЕсли;
		
		GUIDУзла = УзелОбмена.УникальныйИдентификатор();
	Иначе
		НомерОтправленного = 0;
		НомерПринятого = 0;
		GUIDУзла = "";
	КонецЕсли;	
КонецПроцедуры

&НаКлиенте
Процедура УзелОбменаПриИзменении(Элемент)
	ПоказатьАтрибутыУзлаОбмена();
	АктивироватьЭлементыПриВыбореУзлаОбмена();
	СохранитьНастройки();
КонецПроцедуры

&НаКлиенте
Процедура Обновить(Команда)
	ПоказатьАтрибутыУзлаОбмена();
	ЗаполнитьСтатистикуИзмененийНаСервере();

КонецПроцедуры


&НаСервере
Процедура ЗаполнитьСтатистикуИзмененийНаСервере()
	
	ТаблицаСтатистики.Очистить();

	Если УзелПустой() Тогда
		Возврат;
	КонецЕсли;

	ИмяПлана = ИмяПланаОбмена;
	МетаданныеПлана = Метаданные.ПланыОбмена[ИмяПлана];

	Для Каждого ЭлементСостава Из МетаданныеПлана.Состав Цикл

		ПолноеИмяМетаданного = ЭлементСостава.Метаданные.ПолноеИмя();

		ОбъектМетаданных = ЭлементСостава.Метаданные;
		Если Метаданные.РегистрыСведений.Содержит(ОбъектМетаданных) Тогда
			Если ОбъектМетаданных.РежимЗаписи = Метаданные.СвойстваОбъектов.РежимЗаписиРегистра.Независимый Тогда
				Сообщить(""""+ПолноеИмяМетаданного+"""не поддерживается при обмене, т.к. является независимым (не имеет регистраторов).");
				Продолжить;
			КонецЕсли;
		КонецЕсли;

		Запрос = Новый Запрос;
		Запрос.Текст = 
			"ВЫБРАТЬ
			|	КОЛИЧЕСТВО(*) КАК Всего
			|ИЗ
			|	" + ПолноеИмяМетаданного + ".Изменения КАК ТаблицаИзменений
			|ГДЕ
			|	ТаблицаИзменений.Узел = &Узел";
		
		Запрос.УстановитьПараметр("Узел", УзелОбмена);

		НоваяСтрока = ТаблицаСтатистики.Добавить();
		НоваяСтрока.Объект = ПолноеИмяМетаданного;
		НоваяСтрока.ПолноеИмя = ПолноеИмяМетаданного;
		
		// Сбой подсчёта по одному объекту не должен обрывать всю статистику.
		Попытка
			РезультатЗапроса = Запрос.Выполнить();
			Если Не РезультатЗапроса.Пустой() Тогда
				Выборка = РезультатЗапроса.Выбрать();
				Выборка.Следующий();
				НоваяСтрока.КоличествоИзменений = Выборка.Всего;
			КонецЕсли;
		Исключение
			Сообщить("""" + ПолноеИмяМетаданного + """ — ошибка подсчёта изменений: "
				+ ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
		КонецПопытки;
		
	КонецЦикла;
	
КонецПроцедуры


&НаКлиенте
Процедура ТаблицаСтатистикиПриАктивизацииСтроки(Элемент)
	ТекущиеДанные = Элементы.ТаблицаСтатистики.ТекущиеДанные;
	Если  ТекущиеДанные <> Неопределено И 
		  ЗначениеЗаполнено(ТекущиеДанные.ПолноеИмя) И 
		  ТекущиеДанные.КоличествоИзменений>0 Тогда
		ЗаполнитьТаблицуИзменений(ТекущиеДанные.ПолноеИмя);	
	Иначе
		ОчиститьТаблицуИзменений();	
	КонецЕсли;
КонецПроцедуры

&НаСервере
Процедура ОчиститьТаблицуИзменений()
    ТаблицаИзменений.ТекстЗапроса = "ВЫБРАТЬ НЕОПРЕДЕЛЕНО КАК Ссылка, 0 КАК НомерСообщения ГДЕ ЛОЖЬ";
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьТаблицуИзменений(ПолноеИмяМетаданного)

	Узел = УзелОбмена;

	Метаданное = Метаданные.НайтиПоПолномуИмени(ПолноеИмяМетаданного);
	Если Метаданное = Неопределено Тогда
		ОчиститьТаблицуИзменений();
		Возврат;
	КонецЕсли;

	// Таблица изменений устроена по-разному в зависимости от вида объекта:
	//   ссылочные — есть поле Ссылка;
	//   регистры с регистратором (накопления, бухгалтерии, расчёта, подчинённые РС) — поле Регистратор;
	//   константы и независимые РС — ни того, ни другого (у независимых РС там измерения).
	// Раньше всё, что не РН и не РС, попадало в ветку со Ссылкой — и запрос падал
	// на регистрах бухгалтерии/расчёта и на константах.
	Если ЭтоРегистрСРегистратором(Метаданное) Тогда
		ТекстЗапроса =
		    "ВЫБРАТЬ
		    |	Т.Регистратор КАК Ссылка,
		    |	Т.НомерСообщения КАК НомерСообщения
		    |ИЗ
		    |	" + ПолноеИмяМетаданного + ".Изменения КАК Т
		    |ГДЕ
		    |	Т.Узел = &Узел";
	ИначеЕсли ЭтоСсылочныйОбъект(Метаданное) Тогда
		ТекстЗапроса =
			"ВЫБРАТЬ
			|	Т.Ссылка.Представление КАК Ссылка,
		    |	Т.НомерСообщения КАК НомерСообщения
			|ИЗ
			|	" + ПолноеИмяМетаданного + ".Изменения КАК Т
			|ГДЕ
			|	Т.Узел = &Узел";
	Иначе
		// Константа или независимый регистр сведений: показать нечего, кроме номера сообщения.
		ТекстЗапроса =
			"ВЫБРАТЬ
			|	НЕОПРЕДЕЛЕНО КАК Ссылка,
		    |	Т.НомерСообщения КАК НомерСообщения
			|ИЗ
			|	" + ПолноеИмяМетаданного + ".Изменения КАК Т
			|ГДЕ
			|	Т.Узел = &Узел";
	КонецЕсли;

	Попытка
		ТаблицаИзменений.ТекстЗапроса = ТекстЗапроса;
		ТаблицаИзменений.Параметры.УстановитьЗначениеПараметра("Узел", Узел);
	Исключение
		Сообщить("Не удалось построить список изменений для " + ПолноеИмяМетаданного + ": "
			+ ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
		ОчиститьТаблицуИзменений();
	КонецПопытки;

КонецПроцедуры


&НаСервере
Процедура ЗарегистрироватьИзмененияОбъектаНаСервере(ИмяОбъекта)
	Если УзелПустой() Тогда
		Возврат;
	КонецЕсли;
	ОбъектМетаданных = Метаданные.НайтиПоПолномуИмени(ИмяОбъекта);
	ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, ОбъектМетаданных);
КонецПроцедуры


&НаКлиенте
Процедура ПолнаяВыгрузкаОбъекта(Команда)
	ТекущиеДанные = Элементы.ТаблицаСтатистики.ТекущиеДанные;
	
	Если ТекущиеДанные = Неопределено Тогда
		Состояние("Выберите строку в таблице!");
		Возврат;
	КонецЕсли;
	Состояние("Пожалуйста, подождите, идет регистрация объектов...");
	ЗарегистрироватьИзмененияОбъектаНаСервере(ТекущиеДанные.ПолноеИмя);
	Состояние("Завершено");
	
	ЗаполнитьСтатистикуИзмененийНаСервере();
	ЗаполнитьТаблицуИзменений(ТекущиеДанные.ПолноеИмя);

КонецПроцедуры

&НаСервере
Процедура ОчиститьИзмененияОбъектаНаСервере(ИмяОбъекта)
	Если УзелПустой() Тогда
		Возврат;
	КонецЕсли;
	ОбъектМетаданных = Метаданные.НайтиПоПолномуИмени(ИмяОбъекта);
	ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, ОбъектМетаданных);
КонецПроцедуры

&НаКлиенте
Процедура ОчиститьИзмененияОбъекта(Команда)  
	ТекущиеДанные = Элементы.ТаблицаСтатистики.ТекущиеДанные;
	
	Если ТекущиеДанные = Неопределено Тогда
		Состояние("Выберите строку в таблице!");
		Возврат;
	КонецЕсли;

	// Снятие регистрации необратимо: эти изменения в приёмник уже не уйдут,
	// поэтому спрашиваем подтверждение.
	Текст = "Будет снята регистрация изменений объекта:"
		+ Символы.ПС + """" + ТекущиеДанные.ПолноеИмя + """"
		+ Символы.ПС + "Изменений в очереди: " + Строка(ТекущиеДанные.КоличествоИзменений) + "."
		+ Символы.ПС + Символы.ПС + "Эти изменения не будут выгружены в приёмник — данные там останутся"
		+ Символы.ПС + "устаревшими, пока объект не изменят снова или не сделают полную выгрузку."
		+ Символы.ПС + Символы.ПС + "Продолжить?";

	Оповещение = Новый ОписаниеОповещения("ОчиститьИзмененияОбъектаЗавершение", ЭтотОбъект, ТекущиеДанные.ПолноеИмя);
	ПоказатьВопрос(Оповещение, Текст, РежимДиалогаВопрос.ДаНет, , КодВозвратаДиалога.Нет);
КонецПроцедуры


&НаКлиенте
Процедура ОчиститьИзмененияОбъектаЗавершение(Ответ, ПолноеИмя) Экспорт
	Если Ответ <> КодВозвратаДиалога.Да Тогда
		Возврат;
	КонецЕсли;

	Состояние("Пожалуйста, подождите, идет удаление регистрация объектов...");
	ОчиститьИзмененияОбъектаНаСервере(ПолноеИмя);
	Состояние("Завершено");
	
	ЗаполнитьСтатистикуИзмененийНаСервере();
	ЗаполнитьТаблицуИзменений(ПолноеИмя);
КонецПроцедуры

&НаСервере
Процедура ПолнаяВыгрузкаВсехОбъектовНаСервере()
	ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, );
КонецПроцедуры

&НаКлиенте
Процедура ПолнаяВыгрузкаВсехОбъектов(Команда)
	Состояние("Пожалуйста, подождите, идет регистрация объектов...");
	ПолнаяВыгрузкаВсехОбъектовНаСервере();
	
	ЗаполнитьСтатистикуИзмененийНаСервере();
	
	ТекущиеДанные = Элементы.ТаблицаСтатистики.ТекущиеДанные;
	
	Если ТекущиеДанные <> Неопределено Тогда
		ЗаполнитьТаблицуИзменений(ТекущиеДанные.ПолноеИмя);
	КонецЕсли;
	Состояние("Завершено");
КонецПроцедуры


&НаСервере
Процедура ПолнаяОчисткаВсехИзмененийНаСервере()
	ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, );
КонецПроцедуры


&НаКлиенте
Процедура ПолнаяОчисткаВсехИзменений(Команда)
	// Операция разом обнуляет очередь по всем объектам плана — подтверждение обязательно.
	Текст = "Будет снята регистрация ВСЕХ изменений по узлу:"
		+ Символы.ПС + """" + Строка(УзелОбмена) + """"
		+ Символы.ПС + "Изменений в очереди: " + Строка(ВсегоИзмененийВОчереди()) + "."
		+ Символы.ПС + Символы.ПС + "Ни одно из этих изменений не будет выгружено в приёмник."
		+ Символы.ПС + "Данные там останутся устаревшими до следующего изменения объектов"
		+ Символы.ПС + "или до полной выгрузки. Отменить операцию будет нельзя."
		+ Символы.ПС + Символы.ПС + "Продолжить?";

	Оповещение = Новый ОписаниеОповещения("ПолнаяОчисткаВсехИзмененийЗавершение", ЭтотОбъект);
	ПоказатьВопрос(Оповещение, Текст, РежимДиалогаВопрос.ДаНет, , КодВозвратаДиалога.Нет);
КонецПроцедуры


&НаКлиенте
Процедура ПолнаяОчисткаВсехИзмененийЗавершение(Ответ, ДополнительныеПараметры) Экспорт
	Если Ответ <> КодВозвратаДиалога.Да Тогда
		Возврат;
	КонецЕсли;

	Состояние("Пожалуйста, подождите, идет удаление регистрация объектов...");
	ПолнаяОчисткаВсехИзмененийНаСервере();
	
	ЗаполнитьСтатистикуИзмененийНаСервере();
	
	ТекущиеДанные = Элементы.ТаблицаСтатистики.ТекущиеДанные;
	
	Если ТекущиеДанные <> Неопределено Тогда
		ЗаполнитьТаблицуИзменений(ТекущиеДанные.ПолноеИмя);
	КонецЕсли;
	Состояние("Завершено");
	
КонецПроцедуры


&НаКлиенте
Функция ВсегоИзмененийВОчереди()
	// Суммируем уже посчитанную статистику, чтобы не делать лишний запрос к базе.
	Всего = 0;
	Для Каждого Строка Из ТаблицаСтатистики Цикл
		Всего = Всего + Строка.КоличествоИзменений;
	КонецЦикла;
	Возврат Всего;
КонецФункции






&НаСервере
Процедура ВключитьПланИЕгоОбъктыВODATAНаСервере()

	ОбъектМетаданныхПлана = Метаданные.ПланыОбмена.Найти(ИмяПланаОбмена);
	Если ОбъектМетаданныхПлана = Неопределено Тогда
		Возврат;
	КонецЕсли;

	СоставOData = ПолучитьСоставСтандартногоИнтерфейсаOData();
	ЕстьИзменения = Ложь;

	Если СоставOData.Найти(ОбъектМетаданныхПлана) = Неопределено Тогда
		СоставOData.Добавить(ОбъектМетаданныхПлана);
		ЕстьИзменения = Истина;
		Сообщить("План обмена """+ИмяПланаОбмена+""" включен в ODATA");
	Иначе
		Сообщить("План обмена """+ИмяПланаОбмена+""" уже содержится в ODATA");
	КонецЕсли;

	Для Каждого ЭлементСостава Из ОбъектМетаданныхПлана.Состав Цикл
		ОбъектМетаданных = ЭлементСостава.Метаданные;
		ПолноеИмяМетаданного = ОбъектМетаданных.ПолноеИмя();

		Если СоставOData.Найти(ОбъектМетаданных) = Неопределено Тогда
			СоставOData.Добавить(ОбъектМетаданных);
			ЕстьИзменения = Истина;
			Сообщить("Объект """+ПолноеИмяМетаданного+""" включен в ODATA");
		Иначе
			Сообщить("Объект """+ПолноеИмяМетаданного+""" уже содержится в ODATA");
		КонецЕсли;
	КонецЦикла;

	// Состав OData устанавливаем один раз после сбора всех изменений: операция дорогая,
	// она переопределяет весь стандартный интерфейс OData целиком.
	Если ЕстьИзменения Тогда
		УстановитьСоставСтандартногоИнтерфейсаOData(СоставOData);
	КонецЕсли;

КонецПроцедуры

&НаКлиенте
Процедура ВключитьПланИЕгоОбъктыВODATA(Команда)
	ВключитьПланИЕгоОбъктыВODATAНаСервере();
КонецПроцедуры


&НаСервере
Функция ПолучитьЧислоОбъектовВнеODATA()
	// Сколько объектов плана обмена (включая сам план) ещё НЕ включено в состав стандартного
	// интерфейса OData. 0 — всё включено. Используется для предупреждения пользователю.
	ОбъектМетаданныхПлана = Метаданные.ПланыОбмена.Найти(ИмяПланаОбмена);
	Если ОбъектМетаданныхПлана = Неопределено Тогда
		Возврат 0;
	КонецЕсли;

	СоставOData = ПолучитьСоставСтандартногоИнтерфейсаOData();
	Количество = 0;

	Если СоставOData.Найти(ОбъектМетаданныхПлана) = Неопределено Тогда
		Количество = Количество + 1;
	КонецЕсли;

	Для Каждого ЭлементСостава Из ОбъектМетаданныхПлана.Состав Цикл
		Если СоставOData.Найти(ЭлементСостава.Метаданные) = Неопределено Тогда
			Количество = Количество + 1;
		КонецЕсли;
	КонецЦикла;

	Возврат Количество;
КонецФункции


&НаКлиенте
Процедура ПредложитьВключитьОбъектыВODATA()
	// Если не все объекты плана включены в OData — предупреждаем и предлагаем включить (Да/Нет).
	Если ПустаяСтрока(ИмяПланаОбмена) Тогда
		Возврат;
	КонецЕсли;

	ЧислоВне = ПолучитьЧислоОбъектовВнеODATA();
	Если ЧислоВне = 0 Тогда
		ПредложитьСоздатьУзелОбмена();   // вопроса не будет — продолжаем цепочку сразу
		Возврат;
	КонецЕсли;

	Текст = "Не все объекты плана обмена """ + ИмяПланаОбмена + """ включены в стандартный интерфейс OData."
		+ Символы.ПС + "Не включено объектов: " + Строка(ЧислоВне) + "."
		+ Символы.ПС + "Без этого внешний модуль onecdc не сможет прочитать их по OData."
		+ Символы.ПС + Символы.ПС + "Включить сейчас?";

	Оповещение = Новый ОписаниеОповещения("ПредложитьВключитьОбъектыВODATAЗавершение", ЭтотОбъект);
	ПоказатьВопрос(Оповещение, Текст, РежимДиалогаВопрос.ДаНет);
КонецПроцедуры


&НаКлиенте
Процедура ПредложитьВключитьОбъектыВODATAЗавершение(Ответ, ДополнительныеПараметры) Экспорт
	Если Ответ = КодВозвратаДиалога.Да Тогда
		ВключитьПланИЕгоОбъктыВODATAНаСервере();
	КонецЕсли;

	ПредложитьСоздатьУзелОбмена();
КонецПроцедуры


&НаСервере
Процедура ИнициализироватьПробнуюВыгрузкуНаСервере()
	// Помечает к выгрузке по нескольку записей КАЖДОГО объекта плана обмена, чтобы все объекты хотя бы
	// раз попали в SelectChanges. Внешний модуль (onecdc) при первом появлении объекта в пакете ставит
	// ему признак полной выгрузки (если её ещё не было) и затем выгружает объект целиком через OData.
	// Здесь регистрируем лишь небольшую выборку — это дёшево и достаточно, чтобы «объявить» объект;
	// полную перекачку всех данных через обмен не делаем (её сделает onecdc постранично по OData).

	Если УзелПустой() Тогда
		Сообщить("Сначала выберите узел обмена!");
		Возврат;
	КонецЕсли;

	КоличествоЗаписей = 3;
	МетаданныеПлана = Метаданные.ПланыОбмена.Найти(ИмяПланаОбмена);
	Если МетаданныеПлана = Неопределено Тогда
		Сообщить("План обмена """ + ИмяПланаОбмена + """ не найден в метаданных.");
		Возврат;
	КонецЕсли;

	Для Каждого ЭлементСостава Из МетаданныеПлана.Состав Цикл

		// Анализ метаданных тоже под Попыткой: иначе непонятный сбой на одном элементе состава
		// (например, объекта нет в текущей конфигурации) обрывал всю инициализацию
		// «непредвиденной ошибкой» без указания объекта.
		ПолноеИмя = "<объект не определён>";
		Пропустить = Ложь;
		Попытка
			ОбъектМетаданных = ЭлементСостава.Метаданные;
			ПолноеИмя = ОбъектМетаданных.ПолноеИмя();

			// Независимый регистр сведений не имеет регистраторов и не поддерживается обменом (как и в статистике).
			Если Метаданные.РегистрыСведений.Содержит(ОбъектМетаданных)
			   И ОбъектМетаданных.РежимЗаписи = Метаданные.СвойстваОбъектов.РежимЗаписиРегистра.Независимый Тогда
				Сообщить("""" + ПолноеИмя + """ пропущен: независимый регистр сведений (нет регистраторов).");
				Пропустить = Истина;

			// Неподдерживаемые типы (последовательности и т.п.) отсеиваем до основной Попытки.
			ИначеЕсли НЕ (ЭтоРегистрСРегистратором(ОбъектМетаданных)
				Или Метаданные.Константы.Содержит(ОбъектМетаданных)
				Или ЭтоСсылочныйОбъект(ОбъектМетаданных)) Тогда
				Сообщить("""" + ПолноеИмя + """ пропущен: тип объекта не поддерживается инициализацией.");
				Пропустить = Истина;
			КонецЕсли;
		Исключение
			Сообщить("""" + ПолноеИмя + """ — ошибка анализа метаданных: "
				+ ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
			Пропустить = Истина;
		КонецПопытки;

		// Продолжить не делаем из блока обработки исключения — на старых платформах это запрещено.
		Если Пропустить Тогда
			Продолжить;
		КонецЕсли;

		// Сбой по одному объекту не должен прерывать инициализацию остальных.
		Попытка
			Если ЭтоРегистрСРегистратором(ОбъектМетаданных) Тогда
				Зарегистрировано = ЗарегистрироватьЗаписиРегистра(ОбъектМетаданных, КоличествоЗаписей);
			ИначеЕсли Метаданные.Константы.Содержит(ОбъектМетаданных) Тогда
				ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, ОбъектМетаданных);
				Зарегистрировано = 1;
			Иначе
				Зарегистрировано = ЗарегистрироватьСсылочныеЗаписи(ОбъектМетаданных, КоличествоЗаписей);
			КонецЕсли;
			Сообщить("""" + ПолноеИмя + """: зарегистрировано записей — " + Строка(Зарегистрировано));
		Исключение
			Сообщить("""" + ПолноеИмя + """ — ошибка регистрации: "
				+ ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
		КонецПопытки;
	КонецЦикла;

КонецПроцедуры


&НаСервере
Функция ЭтоРегистрСРегистратором(ОбъектМетаданных)
	Возврат Метаданные.РегистрыНакопления.Содержит(ОбъектМетаданных)
		Или Метаданные.РегистрыБухгалтерии.Содержит(ОбъектМетаданных)
		Или Метаданные.РегистрыРасчета.Содержит(ОбъектМетаданных)
		Или (Метаданные.РегистрыСведений.Содержит(ОбъектМетаданных)
			И ОбъектМетаданных.РежимЗаписи <> Метаданные.СвойстваОбъектов.РежимЗаписиРегистра.Независимый);
КонецФункции


&НаСервере
Функция ЭтоСсылочныйОбъект(ОбъектМетаданных)
	Возврат Метаданные.Справочники.Содержит(ОбъектМетаданных)
		Или Метаданные.Документы.Содержит(ОбъектМетаданных)
		Или Метаданные.ПланыСчетов.Содержит(ОбъектМетаданных)
		Или Метаданные.ПланыВидовХарактеристик.Содержит(ОбъектМетаданных)
		Или Метаданные.ПланыВидовРасчета.Содержит(ОбъектМетаданных)
		Или Метаданные.БизнесПроцессы.Содержит(ОбъектМетаданных)
		Или Метаданные.Задачи.Содержит(ОбъектМетаданных)
		Или Метаданные.ПланыОбмена.Содержит(ОбъектМетаданных);
КонецФункции


&НаСервере
Функция ПолучитьМенеджерРегистра(ОбъектМетаданных)
	ИмяРегистра = ОбъектМетаданных.Имя;
	Если Метаданные.РегистрыНакопления.Содержит(ОбъектМетаданных) Тогда
		Возврат РегистрыНакопления[ИмяРегистра];
	ИначеЕсли Метаданные.РегистрыСведений.Содержит(ОбъектМетаданных) Тогда
		Возврат РегистрыСведений[ИмяРегистра];
	ИначеЕсли Метаданные.РегистрыБухгалтерии.Содержит(ОбъектМетаданных) Тогда
		Возврат РегистрыБухгалтерии[ИмяРегистра];
	ИначеЕсли Метаданные.РегистрыРасчета.Содержит(ОбъектМетаданных) Тогда
		Возврат РегистрыРасчета[ИмяРегистра];
	КонецЕсли;
	Возврат Неопределено;
КонецФункции


&НаСервере
Функция ЗарегистрироватьЗаписиРегистра(ОбъектМетаданных, КоличествоЗаписей)
	// Изменения регистра регистрируются по регистратору: берём несколько разных регистраторов
	// и регистрируем набор записей каждого. Отбора по регистратору достаточно — читать набор не нужно.
	Менеджер = ПолучитьМенеджерРегистра(ОбъектМетаданных);
	Если Менеджер = Неопределено Тогда
		Возврат 0;
	КонецЕсли;

	Запрос = Новый Запрос;
	Запрос.Текст =
		"ВЫБРАТЬ РАЗЛИЧНЫЕ ПЕРВЫЕ " + Строка(КоличествоЗаписей) + "
		|	Т.Регистратор КАК Регистратор
		|ИЗ
		|	" + ОбъектМетаданных.ПолноеИмя() + " КАК Т";
	Выборка = Запрос.Выполнить().Выбрать();

	Счётчик = 0;
	Пока Выборка.Следующий() Цикл
		НаборЗаписей = Менеджер.СоздатьНаборЗаписей();
		НаборЗаписей.Отбор.Регистратор.Установить(Выборка.Регистратор);
		ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, НаборЗаписей);
		Счётчик = Счётчик + 1;
	КонецЦикла;
	Возврат Счётчик;
КонецФункции


&НаСервере
Функция ЗарегистрироватьСсылочныеЗаписи(ОбъектМетаданных, КоличествоЗаписей)
	Запрос = Новый Запрос;
	Запрос.Текст =
		"ВЫБРАТЬ ПЕРВЫЕ " + Строка(КоличествоЗаписей) + "
		|	Т.Ссылка КАК Ссылка
		|ИЗ
		|	" + ОбъектМетаданных.ПолноеИмя() + " КАК Т";
	Выборка = Запрос.Выполнить().Выбрать();

	Счётчик = 0;
	Пока Выборка.Следующий() Цикл
		ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, Выборка.Ссылка);
		Счётчик = Счётчик + 1;
	КонецЦикла;
	Возврат Счётчик;
КонецФункции


&НаКлиенте
Процедура ИнициализироватьПробнуюВыгрузку(Команда)
	Состояние("Пожалуйста, подождите, идёт регистрация по нескольку записей каждого объекта...");
	ИнициализироватьПробнуюВыгрузкуНаСервере();
	ЗаполнитьСтатистикуИзмененийНаСервере();
	Состояние("Завершено");
КонецПроцедуры