Поиск и удаление дублей / Поиск дублей / Модуль формы

файл
///////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2024, ООО 1С-Софт
// Все права защищены. Эта программа и сопроводительные материалы предоставляются 
// в соответствии с условиями лицензии Attribution 4.0 International (CC BY 4.0)
// Текст лицензии доступен по ссылке:
// https://creativecommons.org/licenses/by/4.0/legalcode
///////////////////////////////////////////////////////////////////////////////////////////////////////

#Область ОписаниеПеременных

&НаКлиенте
Перем ОтчетыДляОтправки; // Массив из ОтчетОбОшибке

#КонецОбласти

#Область ОбработчикиСобытийФормы

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	УстановитьУсловноеОформление();
	
	КомпонентаНечеткогоПоиска = ОбщегоНазначения.ПодключитьКомпонентуИзМакета("FuzzyStringMatchExtension", "ОбщийМакет.КомпонентаПоискаСтрок");
	Если КомпонентаНечеткогоПоиска <> Неопределено Тогда 
		НечеткийПоиск = Истина;
	КонецЕсли;
	
	НастройкиФормыПоУмолчанию = НастройкиПоискаУдаленияДублей();
	НастройкиФормы = ОбщегоНазначения.ХранилищеОбщихНастроекЗагрузить(ИмяФормы, "", НастройкиФормыПоУмолчанию);
	ЗаполнитьЗначенияСвойств(НастройкиФормыПоУмолчанию, НастройкиФормы);
	НастройкиФормы = НастройкиФормыПоУмолчанию;
	ЗаполнитьЗначенияСвойств(НастройкиФормы, Параметры);
	
	ПриСозданииНаСервереИнициализацияДанных(НастройкиФормы);
	ИнициализироватьКомпоновщикОтбораИПравила(НастройкиФормы);
	
	// Схема должна быть переформирована всегда, настройки компоновщика -  в разрезе ОбластьПоискаДублей. 
	
	// Постоянный интерфейс
	ОтображениеСостояния = Элементы.ПоискНеВыполнялся.ОтображениеСостояния;
	ОтображениеСостояния.Видимость = Истина;
	ОтображениеСостояния.Текст = НСтр("ru = 'Поиск дублей не выполнялся. 
	                                  |Задайте условия отбора и сравнения и нажмите ""Найти дубли"".'");
	
	ОтображениеСостояния = Элементы.ВыполнениеПоиска.ОтображениеСостояния;
	ОтображениеСостояния.Видимость = Истина;
	ОтображениеСостояния.Картинка = Элементы.ДлительнаяОперация48.Картинка;
	
	ОтображениеСостояния = Элементы.ВыполнениеУдаления.ОтображениеСостояния;
	ОтображениеСостояния.Видимость = Истина;
	ОтображениеСостояния.Картинка = Элементы.ДлительнаяОперация48.Картинка;
	
	ОтображениеСостояния = Элементы.ДублейНеНайдено.ОтображениеСостояния;
	ОтображениеСостояния.Видимость = Истина;
	ОтображениеСостояния.Текст = НСтр("ru = 'Не обнаружено дублей по указанным параметрам.
	                                        |Измените условия отбора и сравнения, нажмите ""Найти дубли""'");
	
	// Автосохранение настроек
	СохраняемыеВНастройкахДанныеМодифицированы = Истина;
	
	// Инициализация шагов пошагового мастера.
	ИнициализироватьНастройкиПошаговогоМастера();
	
	// 1. Поиск не выполнялся.
	ШагПоиск = ДобавитьШагМастера(Элементы.ШагПоискНеВыполнялся);
	ШагПоиск.КнопкаНазад.Видимость = Ложь;
	ШагПоиск.КнопкаДалее.Заголовок = НСтр("ru = 'Найти дубли >'");
	ШагПоиск.КнопкаДалее.Подсказка = НСтр("ru = 'Найти дубли по указанным критериям'");
	ШагПоиск.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	ШагПоиск.КнопкаОтмена.Подсказка = НСтр("ru = 'Отказаться от поиска и замены дублей'");
	
	// 2. Длительный поиск.
	Шаг = ДобавитьШагМастера(Элементы.ШагВыполнениеПоиска);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Видимость = Ложь;
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Прервать'");
	Шаг.КнопкаОтмена.Подсказка = НСтр("ru = 'Прервать поиск дублей'");
	
	// 3. Обработка результатов поиска, выбор основных элементов.
	Шаг = ДобавитьШагМастера(Элементы.ШагВыборОсновногоЭлемента);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Заголовок = НСтр("ru = 'Удалить дубли >'");
	Шаг.КнопкаДалее.Подсказка = НСтр("ru = 'Удалить дубли'");
	Шаг.ПриНажатииКнопкиДалее = "ШагВыборОсновногоЭлементаПриНажатииКнопкиДалее";
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	Шаг.КнопкаОтмена.Подсказка = НСтр("ru = 'Отказаться от поиска и замены дублей'");
	
	// 4. Длительное удаление дублей.
	Шаг = ДобавитьШагМастера(Элементы.ШагВыполнениеУдаления);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Видимость = Ложь;
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Прервать'");
	Шаг.КнопкаОтмена.Подсказка = НСтр("ru = 'Прервать удаление дублей'");
	
	// 5. Успешное удаление.
	Шаг = ДобавитьШагМастера(Элементы.ШагУспешноеУдаление);
	Шаг.КнопкаНазад.Заголовок = НСтр("ru = 'Повторить поиск'");
	Шаг.КнопкаНазад.Подсказка = НСтр("ru = 'Начать новый поиск с другими параметрами'");
	Шаг.КнопкаДалее.Видимость = Ложь;
	Шаг.КнопкаОтмена.КнопкаПоУмолчанию = Истина;
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	
	// 6. Неполное удаление.
	Шаг = ДобавитьШагМастера(Элементы.ШагНеудачныеЗамены);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Заголовок = НСтр("ru = 'Повторить удаление >'");
	Шаг.КнопкаДалее.Подсказка = НСтр("ru = 'Удалить дубли'");
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	
	// 7. Дублей не найдено.
	Шаг = ДобавитьШагМастера(Элементы.ШагДублейНеНайдено);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Заголовок = НСтр("ru = 'Найти дубли >'");
	Шаг.КнопкаДалее.Подсказка = НСтр("ru = 'Найти дубли по указанным критериям'");
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	
	// 8. Ошибки выполнения.
	Шаг = ДобавитьШагМастера(Элементы.ШагВозниклаОшибка);
	Шаг.КнопкаНазад.Видимость = Ложь;
	Шаг.КнопкаДалее.Видимость = Ложь;
	Шаг.КнопкаОтмена.Заголовок = НСтр("ru = 'Закрыть'");
	
	// Обновление элементов формы.
	НастройкиМастера.ТекущийШаг = ШагПоиск;
	УстановитьВидимостьДоступность(ЭтотОбъект);
	
	Элементы.ГруппаПодробно.Видимость = Ложь;
	ОтображаемаяСтепеньРодства = 2;
	УстановитьОтборВозможныхДублей(ЭтотОбъект);
	
	Если ОбщегоНазначения.ЭтоМобильныйКлиент() Тогда
		Элементы.Шапка.ВыравниваниеЭлементовИЗаголовков = ВариантВыравниванияЭлементовИЗаголовков.ЭлементыПравоЗаголовкиЛево;
		Элементы.КоманднаяПанель.Видимость = Ложь;
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Процедура ПриОткрытии(Отказ)
	
	ПриАктивацииШагаМастера();
	ОтчетыДляОтправки = Новый Соответствие;
	
КонецПроцедуры

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

#КонецОбласти

#Область ОбработчикиСобытийЭлементовШапкиФормы

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

	ОткрытьФорму(ОбластьПоискаДублей + ".ФормаСписка");

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

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

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

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

&НаКлиенте
Процедура ОбластьПоискаДублейОчистка(Элемент, СтандартнаяОбработка)
	
	СтандартнаяОбработка = Ложь;
	
КонецПроцедуры

&НаКлиенте
Процедура ВсеМестаИспользованияНеобработанныхНажатие(Элемент)
	
	ПоказатьМестаИспользования(НеобработанныеДубли);
	
КонецПроцедуры

&НаКлиенте
Процедура ВсеМестаИспользованияНажатие(Элемент)
	
	ПоказатьМестаИспользования(НайденныеДубли);
	
КонецПроцедуры

&НаКлиенте
Процедура ПредставлениеПравилОтбораНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
	СтандартнаяОбработка = Ложь;
	
	ПодключитьОбработчикОжидания("ПриНачалеВыбораПравилОтбора", 0.1, Истина);
КонецПроцедуры

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

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

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

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

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

&НаКлиенте
Процедура ОписаниеСостоянияНайденныхДублейОбработкаНавигационнойСсылки(Элемент, Ссылка, СтандартнаяОбработка)
	
	СтандартнаяОбработка = Ложь;
	Если Ссылка = "СпособУдаления" Тогда
		СпособУдаления = ?(СпособУдаления = "Непосредственно", "Пометка", "Непосредственно");
		ОбновитьОписаниеСостоянияНайденныхДублей();
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Процедура СсылкаПодробнееНажатие(Элемент)
	СтандартныеПодсистемыКлиент.ПоказатьПодробнуюИнформацию(Неопределено, Элемент.Подсказка);
КонецПроцедуры

#КонецОбласти

#Область ОбработчикиСобытийЭлементовТаблицыФормыНайденныеДубли

&НаКлиенте
Процедура НайденныеДублиПриАктивизацииСтроки(Элемент)
	
	ПодключитьОбработчикОжидания("ОтложенныйОбработчикАктивизацииСтрокиДублей", 0.1, Истина);
	
КонецПроцедуры

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

&НаСервере
Процедура ОбновитьМестаИспользованияКандидата(Знач ИдентификаторСтроки)
	ТекущаяСтрока = НайденныеДубли.НайтиПоИдентификатору(ИдентификаторСтроки);
	
	Если ТекущаяСтрока.ПолучитьРодителя() = Неопределено Тогда
		// Описание группы
		МестаИспользованияКандидата.Очистить();
		
		НаименованиеОригинала = Неопределено;
		ПомеченыНаУдалениеИНеИспользуются = Истина;
		Для Каждого Кандидат Из ТекущаяСтрока.ПолучитьЭлементы() Цикл
			Если Кандидат.Основной Тогда
				НаименованиеОригинала = Кандидат.Наименование;
			ИначеЕсли Кандидат.НомерКартинки <> 4 Или Кандидат.Количество > 0 Тогда
				ПомеченыНаУдалениеИНеИспользуются = Ложь;
			КонецЕсли;
		КонецЦикла;
		
		Описание = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Для элемента ""%1"" найдено дублей: %2'"),
			НаименованиеОригинала, ТекущаяСтрока.Количество);
		Если ПомеченыНаУдалениеИНеИспользуются Тогда
			Описание = Описание + Символы.ПС
				+ НСтр("ru = 'Однако все они помечены на удаление и не используются, поэтому удаление дублей уже не требуется.'");
		КонецЕсли;
				
		Элементы.ГруппаПодробно.Заголовок = НСтр("ru = 'Сведения:'");
		Элементы.ОписаниеТекущейГруппыДублей.Заголовок = Описание;
		Элементы.СтраницыМестаИспользования.ТекущаяСтраница = Элементы.ОписаниеГруппы;
		Возврат;
	КонецЕсли;
	
	// Перечень мест использования.
	ТаблицаИспользования = ПолучитьИзВременногоХранилища(АдресМестИспользования);
	Фильтр = Новый Структура("Ссылка", ТекущаяСтрока.Ссылка);
	
	МестаИспользованияКандидата.Загрузить(ТаблицаИспользования.Скопировать(ТаблицаИспользования.НайтиСтроки(Фильтр)));
	
	Если ТекущаяСтрока.Количество = 0 Тогда
		Элементы.ОписаниеТекущейГруппыДублей.Заголовок = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Элемент ""%1"" не используется'"), 
			ТекущаяСтрока.Наименование);
		Элементы.ГруппаПодробно.Заголовок = НСтр("ru = 'Сведения:'");
		Элементы.СтраницыМестаИспользования.ТекущаяСтраница = Элементы.ОписаниеГруппы;
	Иначе
		Элементы.ГруппаПодробно.Заголовок = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Места использования ""%1"" (%2):'"), 
			ТекущаяСтрока.Наименование,
			ТекущаяСтрока.Количество);
		
		Элементы.СтраницыМестаИспользования.ТекущаяСтраница = Элементы.МестаИспользования;
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Процедура НайденныеДублиВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
	
	ОткрытьФормуДубля(Элемент.ТекущиеДанные);
	
КонецПроцедуры

&НаКлиенте
Процедура НайденныеДублиПометкаПриИзменении(Элемент)
	
	ДанныеСтроки = Элементы.НайденныеДубли.ТекущиеДанные;
	ДанныеСтроки.Пометка = ДанныеСтроки.Пометка % 2;
	ИзменитьПометкиКандидатов(ДанныеСтроки);
	
	ОписаниеОшибкиПоискаДублей = "";
	ВсегоНайденоДублей = 0;
	Для Каждого Дубль Из НайденныеДубли.ПолучитьЭлементы() Цикл
		Для Каждого Потомок Из Дубль.ПолучитьЭлементы() Цикл
			Если Не Потомок.Основной И Потомок.Пометка Тогда
				ВсегоНайденоДублей = ВсегоНайденоДублей + 1;
			КонецЕсли;
		КонецЦикла;
	КонецЦикла;
	
	ОбновитьОписаниеСостоянияНайденныхДублей();
	
КонецПроцедуры

#КонецОбласти

#Область ОбработчикиСобытийЭлементовТаблицыФормыНеобработанныеДубли

&НаКлиенте
Процедура НеобработанныеДублиПриАктивизацииСтроки(Элемент)
	
	ПодключитьОбработчикОжидания("ОтложенныйОбработчикАктивизацииСтрокиНеобработанныхДублей", 0.1, Истина);
	
КонецПроцедуры

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

&НаСервере
Процедура ОбновитьМестаИспользованияНеобработанныхДубли(Знач СтрокаДанных)
	ДанныеСтроки = НеобработанныеДубли.НайтиПоИдентификатору(СтрокаДанных);
	
	Если ДанныеСтроки.ПолучитьРодителя() = Неопределено Или ДанныеСтроки.Основной Тогда
		МестаИспользованияНеобработанных.Очистить();
		Элементы.ОписаниеТекущейГруппыДублей1.Заголовок = НСтр("ru = 'Для просмотра причин выберите проблемный элемент-дубль.'");
		Элементы.СтраницыМестаИспользованияНеобработанных.ТекущаяСтраница = Элементы.ОписаниеГруппыНеобработанных;
		Возврат;
	КонецЕсли;
	
	Если ДанныеСтроки.Количество = 0 Тогда
		МестаИспользованияНеобработанных.Очистить();
		Элементы.ОписаниеТекущейГруппыДублей1.Заголовок = 
			НСтр("ru = 'Выбранный дубль не заменен из-за невозможности замены других дублей.'");
		Элементы.СтраницыМестаИспользованияНеобработанных.ТекущаяСтраница = Элементы.ОписаниеГруппыНеобработанных;
	Иначе
		ТаблицаОшибок = ПолучитьИзВременногоХранилища(АдресРезультатаЗамены); // см. ОбщегоНазначения.ЗаменитьСсылки
		Фильтр = Новый Структура("Ссылка", ДанныеСтроки.Ссылка);
		
		Данные = ТаблицаОшибок.Скопировать( ТаблицаОшибок.НайтиСтроки(Фильтр) );
		Данные.Колонки.Добавить("Пиктограмма");
		Данные.ЗаполнитьЗначения(Истина, "Пиктограмма");
		МестаИспользованияНеобработанных.Загрузить(Данные);
		
		Элементы.МестаИспользованияКандидата.Заголовок = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Не удалось заменить дубли в некоторых местах (%1)'"), 
			ДанныеСтроки.Количество);
		Элементы.СтраницыМестаИспользованияНеобработанных.ТекущаяСтраница = Элементы.ОписаниеМестИспользованияНеобработанных;
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Процедура НеобработанныеДублиВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
	
	ОткрытьФормуДубля(Элементы.НеобработанныеДубли.ТекущиеДанные);
	
КонецПроцедуры

#КонецОбласти

#Область ОбработчикиСобытийЭлементовТаблицыФормыМестаИспользованияНеобработанных

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

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

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

&НаКлиенте
Процедура ДекорацияОтправитьОтчетОбОшибкеНажатие(Элемент)

	ТекущиеДанные = Элементы.МестаИспользованияНеобработанных.ТекущиеДанные;
	Если ТекущиеДанные = Неопределено Тогда
		Возврат;
	КонецЕсли;

	ОтчетДляОтправки = ОтчетыДляОтправки[ТекущиеДанные.ИнформацияОбОшибке];
	Если ОтчетДляОтправки = Неопределено Тогда
		ОтчетДляОтправки = Новый ОтчетОбОшибке(ТекущиеДанные.ИнформацияОбОшибке);
		ОтчетыДляОтправки[ТекущиеДанные.ИнформацияОбОшибке] = ОтчетДляОтправки;
	КонецЕсли;
	СтандартныеПодсистемыКлиент.ПоказатьОтчетОбОшибке(ОтчетДляОтправки);
	
КонецПроцедуры

#КонецОбласти

#Область ОбработчикиСобытийЭлементовТаблицыФормыМестаИспользованияКандидата

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

#КонецОбласти

#Область ОбработчикиСобытийЭлементовТаблицыФормыВозможныеДубли

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

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

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

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

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

&НаКлиенте
Процедура ОбновитьСостояниеВозможныхДублей()

	ЗаголовокГруппы = НСтр("ru='В процессе работы могли возникнуть дубли в других списках (%1)'");
	КоличествоНеобработанных = ВозможныеДубли.НайтиСтроки(Новый Структура("Обработано", Ложь)).Количество();
	Элементы.ГруппаВозможныеДубли.Заголовок = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(ЗаголовокГруппы, КоличествоНеобработанных);
	Элементы.ГруппаВозможныеДубли.Видимость = КоличествоНеобработанных > 0;	

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

#КонецОбласти

#Область ОбработчикиКомандФормы

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

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

&НаКлиенте
Процедура ОткрытьКандидатаВДубли(Команда)
	
	ОткрытьФормуДубля(Элементы.НайденныеДубли.ТекущиеДанные);
	
КонецПроцедуры

&НаКлиенте
Процедура ОткрытьНеобработанныйДубль(Команда)
	
	ОткрытьФормуДубля(Элементы.НеобработанныеДубли.ТекущиеДанные);
	
КонецПроцедуры

&НаКлиенте
Процедура РазвернутьГруппыДублей(Команда)
	
	РазвернутьГруппуДублей();
	
КонецПроцедуры

&НаКлиенте
Процедура СвернутьГруппыДублей(Команда)
	
	СвернутьГруппуДублей();
	
КонецПроцедуры

&НаКлиенте
Процедура ПовторитьПоиск(Команда)
	
	ПерейтиНаШагМастера(Элементы.ШагВыполнениеПоиска);
	
КонецПроцедуры

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

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

&НаКлиенте
Процедура Подробно(Команда)
	Элементы.ГруппаПодробно.Видимость = Не Элементы.ГруппаПодробно.Видимость;
	Элементы.Подробнее.Заголовок = ?(Элементы.ГруппаПодробно.Видимость, НСтр("ru = '<< Скрыть'"), НСтр("ru = 'Подробнее >>'"));
КонецПроцедуры

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

#Область ПереключениеСтраницМастера

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

// Параметры:
//  Страница - ГруппаФормы
//
// Возвращаемое значение:
//  Структура:
//    * Индекс - Число
//    * ИмяСтраницы - Строка
//    * КнопкаНазад - см. КнопкаМастера
//    * КнопкаДалее - см. КнопкаМастера
//    * КнопкаОтмена - см. КнопкаМастера
// 
&НаСервере
Функция ДобавитьШагМастера(Знач Страница)
	ОписаниеШага = Новый Структура;
	ОписаниеШага.Вставить("Индекс", 0);
	ОписаниеШага.Вставить("ИмяСтраницы", "");
	ОписаниеШага.Вставить("КнопкаНазад", КнопкаМастера());
	ОписаниеШага.Вставить("КнопкаДалее", КнопкаМастера());
	ОписаниеШага.Вставить("ПриНажатииКнопкиДалее", "");
	ОписаниеШага.Вставить("КнопкаОтмена", КнопкаМастера());
	ОписаниеШага.ИмяСтраницы = Страница.Имя;
	
	ОписаниеШага.КнопкаНазад = КнопкаМастера();
	ОписаниеШага.КнопкаНазад.Заголовок = НСтр("ru = '< Назад'");
	
	ОписаниеШага.КнопкаДалее = КнопкаМастера();
	ОписаниеШага.КнопкаДалее.КнопкаПоУмолчанию = Истина;
	ОписаниеШага.КнопкаДалее.Заголовок = НСтр("ru = 'Далее >'");
	
	ОписаниеШага.КнопкаОтмена = КнопкаМастера();
	ОписаниеШага.КнопкаОтмена.Заголовок = НСтр("ru = 'Отмена'");
	
	НастройкиМастера.Шаги.Добавить(ОписаниеШага);
	
	ОписаниеШага.Индекс = НастройкиМастера.Шаги.ВГраница();
	Возврат ОписаниеШага;
КонецФункции

&НаКлиентеНаСервереБезКонтекста
Процедура УстановитьВидимостьДоступность(Форма)
	
	Элементы = Форма.Элементы;
	НастройкиМастера = Форма.НастройкиМастера;
	ТекущийШаг = НастройкиМастера.ТекущийШаг;
	
	// Переключение страницы.
	Элементы[НастройкиМастера.ГруппаСтраниц].ТекущаяСтраница = Элементы[ТекущийШаг.ИмяСтраницы];
	
	// Обновление кнопок.
	ОбновитьСвойстваКнопкиМастера(Элементы[НастройкиМастера.КнопкаДалее],  ТекущийШаг.КнопкаДалее);
	ОбновитьСвойстваКнопкиМастера(Элементы[НастройкиМастера.КнопкаНазад],  ТекущийШаг.КнопкаНазад);
	ОбновитьСвойстваКнопкиМастера(Элементы[НастройкиМастера.КнопкаОтмена], ТекущийШаг.КнопкаОтмена);
	
КонецПроцедуры

&НаКлиенте
Процедура ПерейтиНаШагМастера(Знач ШагИлиИндексИлиГруппаФормы)
	
	// Поиск шага.
	Тип = ТипЗнч(ШагИлиИндексИлиГруппаФормы);
	Если Тип = Тип("Структура") Тогда
		ОписаниеШага = ШагИлиИндексИлиГруппаФормы;
	ИначеЕсли Тип = Тип("Число") Тогда
		ИндексШага = ШагИлиИндексИлиГруппаФормы;
		Если ИндексШага < 0 Тогда
			ВызватьИсключение НСтр("ru = 'Попытка выхода назад из первого шага мастера'");
		ИначеЕсли ИндексШага > НастройкиМастера.Шаги.ВГраница() Тогда
			ВызватьИсключение НСтр("ru = 'Попытка выхода за последний шаг мастера'");
		КонецЕсли;
		ОписаниеШага = НастройкиМастера.Шаги[ИндексШага];
	Иначе
		ШагНайден = Ложь;
		ИмяИскомойСтраницы = ШагИлиИндексИлиГруппаФормы.Имя;
		Для Каждого ОписаниеШага Из НастройкиМастера.Шаги Цикл
			Если ОписаниеШага.ИмяСтраницы = ИмяИскомойСтраницы Тогда
				ШагНайден = Истина;
				Прервать;
			КонецЕсли;
		КонецЦикла;
		Если Не ШагНайден Тогда
			ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
				НСтр("ru = 'Не найден шаг ""%1"".'"),
				ИмяИскомойСтраницы);
		КонецЕсли;
	КонецЕсли; 
	
	// Переключение шага.
	НастройкиМастера.ТекущийШаг = ОписаниеШага;
	
	// Обновление видимости.
	УстановитьВидимостьДоступность(ЭтотОбъект);
	ПриАктивацииШагаМастера();
	
КонецПроцедуры

#КонецОбласти

#Область СобытияМастера

&НаКлиенте
Процедура ПриАктивацииШагаМастера()
	
	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;
	
	Если ТекущаяСтраница = Элементы.ШагПоискНеВыполнялся Тогда
		
		Элементы.Шапка.Доступность = Истина;
		
		// Представление правил отбора.
		ПредставлениеПравилОтбора = Строка(КомпоновщикПредварительногоОтбора.Настройки.Отбор);
		Если ПустаяСтрока(ПредставлениеПравилОтбора) Тогда
			ПредставлениеПравилОтбора = НСтр("ru = 'Все элементы'");
		КонецЕсли;
		
		// Представление правил поиска.
		Союз = " " + НСтр("ru = 'И'") + " ";
		ТекстПравил = "";
		Для Каждого Правило Из ПравилаПоиска Цикл
			Если Правило.Правило = "Равно" Тогда
				Сравнение = Правило.ПредставлениеРеквизита + " " + НСтр("ru = 'совпадает'");
			ИначеЕсли Правило.Правило = "Подобно" Тогда
				Сравнение = Правило.ПредставлениеРеквизита + " " + НСтр("ru = 'совпадает по похожим словам'");
			Иначе
				Продолжить;
			КонецЕсли;
			ТекстПравил = ?(ТекстПравил = "", "", ТекстПравил + Союз) + Сравнение;
		КонецЦикла;
		Если УчитыватьПрикладныеПравила Тогда
			Для Позиция = 1 По СтрЧислоСтрок(ОписаниеПрикладныхПравил) Цикл
				СтрокаПравила = СокрЛП(СтрПолучитьСтроку(ОписаниеПрикладныхПравил, Позиция));
				Если Не ПустаяСтрока(СтрокаПравила) Тогда
					ТекстПравил = ?(ТекстПравил = "", "", ТекстПравил + Союз) + СтрокаПравила;
				КонецЕсли;
			КонецЦикла;
		КонецЕсли;
		Если ПустаяСтрока(ТекстПравил) Тогда
			ТекстПравил = НСтр("ru = 'Правила не заданы'");
		КонецЕсли;
		
		Если СкрыватьНезначимыеДубли Тогда
			СтрокаПравил = НСтр("ru = 'не показывать помеченные на удаление без мест использования'");
			ТекстПравил = ?(ТекстПравил = "", "", ТекстПравил + Союз) + СтрокаПравил;
		КонецЕсли;
		
		ПредставлениеПравилПоиска = ТекстПравил;
		
		// Доступность.
		Элементы.ПредставлениеПравилОтбора.Доступность = Не ПустаяСтрока(ОбластьПоискаДублей);
		Элементы.ПредставлениеПравилПоиска.Доступность = Не ПустаяСтрока(ОбластьПоискаДублей);
		
	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Тогда
		
		Если Не ЭтоАдресВременногоХранилища(АдресСхемыКомпоновки) Тогда
			Возврат; // Не инициализировано.
		КонецЕсли;
		Элементы.Шапка.Доступность = Ложь;
		НастройкиМастера.ПоказатьДиалогПередЗакрытием = Истина;
		НайтиИУдалитьДублиКлиент();
		
	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыборОсновногоЭлемента Тогда
		
		Элементы.Шапка.Доступность = Истина;
		Элементы.ПовторитьПоиск.Видимость = Истина;
		РазвернутьГруппуДублей();
		
	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда
		
		Элементы.Шапка.Доступность = Ложь;
		НастройкиМастера.ПоказатьДиалогПередЗакрытием = Истина;
		НайтиИУдалитьДублиКлиент();
		
	ИначеЕсли ТекущаяСтраница = Элементы.ШагУспешноеУдаление Тогда
		
		Элементы.Шапка.Доступность = Ложь;
		
		Если ВозможныйДубльВыбран Тогда
			Элементы.ШагМастераНазад.Заголовок = НСтр("ru='Новый поиск'");
			Элементы.ШагМастераНазад.КнопкаПоУмолчанию = Истина;
		Иначе
			Элементы.ШагМастераНазад.Заголовок = НСтр("ru='Повторить поиск'");
			Элементы.ШагМастераНазад.КнопкаПоУмолчанию = Ложь;
		КонецЕсли;
		
	ИначеЕсли ТекущаяСтраница = Элементы.ШагНеудачныеЗамены Тогда
		
		Элементы.Шапка.Доступность = Ложь;
		
	ИначеЕсли ТекущаяСтраница = Элементы.ШагДублейНеНайдено Тогда
		
		Элементы.Шапка.Доступность = Истина;
		Если ПустаяСтрока(ОписаниеОшибкиПоискаДублей) Тогда
			Сообщение = НСтр("ru = 'Не обнаружено дублей по указанным параметрам.'");
		Иначе	
			Сообщение = ОписаниеОшибкиПоискаДублей;
		КонецЕсли;	
		Элементы.ДублейНеНайдено.ОтображениеСостояния.Текст = Сообщение + Символы.ПС 
			+ НСтр("ru = 'Измените условия и нажмите ""Найти дубли""'");
		
	ИначеЕсли ТекущаяСтраница = Элементы.ШагВозниклаОшибка Тогда
		
		Элементы.Шапка.Доступность = Истина;
		Элементы.СсылкаПодробнее.Видимость = ЗначениеЗаполнено(Элементы.СсылкаПодробнее.Подсказка);
		
	КонецЕсли;
	
КонецПроцедуры

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

&НаКлиенте
Процедура ШагМастераДалееЗавершение(Результат, ДополнительныеПараметры) Экспорт

	Если Результат = Неопределено Тогда
		Возврат;
	КонецЕсли;
	ПерейтиНаШагМастера(Результат);

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

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

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

#КонецОбласти

#Область СобытияШага_ВыборОсновногоЭлемента

&НаКлиенте
Процедура ШагВыборОсновногоЭлементаПриНажатииКнопкиДалее(ОписаниеСледующегоШага, ДополнительныеПараметры) Экспорт

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

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

&НаКлиенте         
Процедура ШагВыборОсновногоЭлементаПриНажатииКнопкиДалееЗавершение(Результат, ДополнительныеПараметры) Экспорт

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

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

#КонецОбласти

#Область Служебные

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

КонецФункции

&НаКлиенте
Функция ПолноеИмяФормы(КраткоеИмяФормы)
	Имена = СтрРазделить(ИмяФормы, ".");
	Возврат Имена[0] + "." + Имена[1] + ".Форма." + КраткоеИмяФормы;
КонецФункции

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

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

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

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

&НаКлиенте
Процедура УстановитьПометкиВДереве(Дерево, Пометка, ПроверятьРодителя)
	
	ЭлементыСтроки = Дерево.ПолучитьЭлементы();
	Для Каждого Элемент Из ЭлементыСтроки Цикл
		Элемент.Пометка = Пометка;
		УстановитьПометкиВДереве(Элемент, Пометка, Ложь);
	КонецЦикла;
	
	Родитель = Дерево.ПолучитьРодителя();
	Если ПроверятьРодителя И Родитель <> Неопределено Тогда 
		ПроверитьПометкуРодителя(Родитель);
	КонецЕсли;
	
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьПометкуРодителя(Родитель)
	
	ПометкаРодителя = Истина;
	ЭлементыСтроки = Родитель.ПолучитьЭлементы();
	Для Каждого Элемент Из ЭлементыСтроки Цикл
		Если Не Элемент.Пометка Тогда
			ПометкаРодителя = Ложь;
			Прервать;
		КонецЕсли;
	КонецЦикла;
	Родитель.Пометка = ПометкаРодителя;
	
КонецПроцедуры

&НаКлиенте
Процедура ИзменитьПометкиКандидатов(Знач ДанныеСтроки)
	ПроставитьПометкиВниз(ДанныеСтроки);
	ПроставитьПометкиВверх(ДанныеСтроки);
КонецПроцедуры

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

&НаКлиенте
Процедура ПроставитьПометкиВверх(Знач ДанныеСтроки)
	РодительСтроки = ДанныеСтроки.ПолучитьРодителя();
	
	Если РодительСтроки <> Неопределено Тогда
		ВсеИстина = Истина;
		НеВсеЛожь = Ложь;
		
		Для Каждого Потомок Из РодительСтроки.ПолучитьЭлементы() Цикл
			ВсеИстина = ВсеИстина И (Потомок.Пометка = 1);
			НеВсеЛожь = НеВсеЛожь Или (Потомок.Пометка > 0);
		КонецЦикла;
		
		Если ВсеИстина Тогда
			РодительСтроки.Пометка = 1;
		ИначеЕсли НеВсеЛожь Тогда
			РодительСтроки.Пометка = 2;
		Иначе
			РодительСтроки.Пометка = 0;
		КонецЕсли;
		
		ПроставитьПометкиВверх(РодительСтроки);
	КонецЕсли;
	
КонецПроцедуры

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

&НаКлиенте
Процедура ОбновитьОписаниеСостоянияНайденныхДублей()
	
	ЕстьПодсистемаУдаленияПомеченных = ОбщегоНазначенияКлиент.ПодсистемаСуществует(
		"СтандартныеПодсистемы.УдалениеПомеченныхОбъектов");
	
	Если ПустаяСтрока(ОписаниеОшибкиПоискаДублей) Тогда
		Описание = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Выбрано дублей: %1 из %2.'"),
			ВсегоНайденоДублей, ВсегоЭлементов);
	Иначе	
		Описание = ОписаниеОшибкиПоискаДублей;
	КонецЕсли;
	
	Если ЕстьПодсистемаУдаленияПомеченных Тогда
	
		Если СпособУдаления = "Пометка" Тогда
			ТекстПодсказки = НСтр("ru = 'Выбранные элементы будут <a href = ""[Действие]"">помечены на удаление</a> и заменены на оригиналы (отмечены стрелкой).'");
			ПараметрыСтроки = Новый Структура("Действие", "СпособУдаления");
			ТекстПодсказки = СтроковыеФункцииКлиентСервер.ВставитьПараметрыВСтроку(ТекстПодсказки, ПараметрыСтроки);
		Иначе
			ТекстПодсказки = НСтр("ru = 'Выбранные элементы будут <a href = ""[Действие]"">безвозвратно удалены</a> и заменены на оригиналы (отмечены стрелкой).'");
			ПараметрыСтроки = Новый Структура("Действие", "СпособУдаления");
			ТекстПодсказки = СтроковыеФункцииКлиентСервер.ВставитьПараметрыВСтроку(ТекстПодсказки, ПараметрыСтроки);
		КонецЕсли;
		
	Иначе
		
		ТекстПодсказки = НСтр("ru = 'Выбранные элементы будут помечены на удаление и заменены на оригиналы (отмечены стрелкой).'");
		
	КонецЕсли;
	ОписаниеСостоянияНайденныхДублей = СтроковыеФункцииКлиент.ФорматированнаяСтрока(Описание + Символы.ПС + ТекстПодсказки);
		
КонецПроцедуры

&НаСервере
Функция АдресНастроекКомпоновщикаОтбора()
	Возврат ПоместитьВоВременноеХранилище(КомпоновщикПредварительногоОтбора.Настройки, УникальныйИдентификатор);
КонецФункции

&НаСервере
Функция АдресНастроекПравилПоиска()
	Настройки = Новый Структура;
	Настройки.Вставить("УчитыватьПрикладныеПравила", УчитыватьПрикладныеПравила);
	Настройки.Вставить("ВсеВариантыСравнения", ВсеВариантыСравнения);
	Настройки.Вставить("ПравилаПоиска", РеквизитФормыВЗначение("ПравилаПоиска"));
	Настройки.Вставить("СкрыватьНезначимыеДубли", СкрыватьНезначимыеДубли);

	Возврат ПоместитьВоВременноеХранилище(Настройки);
КонецФункции

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

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

&НаСервере
Процедура ИнициализироватьКомпоновщикОтбораИПравила(Знач НастройкиФормы)
	// 1. Очистка и инициализация сведений об объекте метаданных.
	ПредставлениеПравилОтбора = "";
	ПредставлениеПравилПоиска = "";
	
	ТаблицаНастроек = ПолучитьИзВременногоХранилища(АдресНастроек);
	СтрокаТаблицыНастроек = ТаблицаНастроек.Найти(ОбластьПоискаДублей, "ПолноеИмя");
	Если СтрокаТаблицыНастроек = Неопределено Тогда
		ОбластьПоискаДублей = "";
		Возврат;
	КонецЕсли;
	
	ОбъектМетаданных = ОбщегоНазначения.ОбъектМетаданныхПоПолномуИмени(ОбластьПоискаДублей);
	
	// 2. Инициализация СКД, которая используется для отборов.
	СхемаКомпоновки = Новый СхемаКомпоновкиДанных;
	ИсточникДанных = СхемаКомпоновки.ИсточникиДанных.Добавить();
	ИсточникДанных.ТипИсточникаДанных = "Local";
	
	НаборДанных = СхемаКомпоновки.НаборыДанных.Добавить(Тип("НаборДанныхЗапросСхемыКомпоновкиДанных"));
	НаборДанных.Запрос = "ВЫБРАТЬ " + ДоступныеРеквизитыОтбора(ОбъектМетаданных) + " ИЗ " + ОбластьПоискаДублей; // @query-part-1 @query-part-2
	НаборДанных.АвтоЗаполнениеДоступныхПолей = Истина;
	
	АдресСхемыКомпоновки = ПоместитьВоВременноеХранилище(СхемаКомпоновки, УникальныйИдентификатор);
	
	КомпоновщикПредварительногоОтбора.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(СхемаКомпоновки));
	
	// 3. Заполнение таблицы ПравилаПоиска.
	ТаблицаПравил = РеквизитФормыВЗначение("ПравилаПоиска");
	ТаблицаПравил.Очистить();
	
	ИсключаемыеРеквизиты = Новый Структура("ПометкаУдаления, Ссылка, Предопределенный, ИмяПредопределенныхДанных, ЭтоГруппа");
	Для Каждого РеквизитМетаданные Из ОбъектМетаданных.Реквизиты Цикл
		Если Не ОбщегоНазначения.ОбъектМетаданныхДоступенПоФункциональнымОпциям(РеквизитМетаданные) Тогда
			ИсключаемыеРеквизиты.Вставить(РеквизитМетаданные.Имя);
		КонецЕсли;
	КонецЦикла;
	
	ДобавитьПравилаРеквизитов(ТаблицаПравил, ИсключаемыеРеквизиты, ВсеВариантыСравнения, ОбъектМетаданных.СтандартныеРеквизиты, НечеткийПоиск);
	ДобавитьПравилаРеквизитов(ТаблицаПравил, ИсключаемыеРеквизиты, ВсеВариантыСравнения, ОбъектМетаданных.Реквизиты, НечеткийПоиск);
	
	// 4. Загрузка сохраненных значений.
	ОтборыЗагружены = Ложь;
	НастройкиКД = ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(НастройкиФормы, "НастройкиКД");
	Если ТипЗнч(НастройкиКД) = Тип("НастройкиКомпоновкиДанных") Тогда
		
		Для Сч = -(НастройкиКД.Отбор.Элементы.Количество()-1) По 0 Цикл
			Отбор = НастройкиКД.Отбор.Элементы[-Сч];
			Если СтрНайти(Отбор.Представление, ПредставлениеВыбранногоМестаИспользования()) > 0 Тогда
				ОбщегоНазначенияКлиентСервер.УдалитьЭлементыГруппыОтбора(НастройкиКД.Отбор,, Отбор.Представление);
			КонецЕсли;	
		КонецЦикла;
		
		КомпоновщикПредварительногоОтбора.ЗагрузитьНастройки(НастройкиКД);
		ОтборыЗагружены = Истина;
	КонецЕсли;
	
	ПравилаЗагружены = Ложь;
	СохраненныеПравила = ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(НастройкиФормы, "ПравилаПоиска");
	Если ТипЗнч(СохраненныеПравила) = Тип("ТаблицаЗначений") Тогда
		ПравилаЗагружены = Истина;
		Для Каждого СохраненноеПравило Из СохраненныеПравила Цикл
			Правило = ТаблицаПравил.Найти(СохраненноеПравило.Реквизит, "Реквизит");
			Если Правило <> Неопределено
				И Правило.ВариантыСравнения.НайтиПоЗначению(СохраненноеПравило.Правило) <> Неопределено Тогда
				Правило.Правило = СохраненноеПравило.Правило;
			КонецЕсли;
		КонецЦикла;
	КонецЕсли;
	
	// 5. Установка умолчаний.
	// Отбор по пометке удаления.
	Если Не ОтборыЗагружены Тогда
		ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбора(	КомпоновщикПредварительногоОтбора.Настройки.Отбор,
			"ПометкаУдаления", Ложь, ВидСравненияКомпоновкиДанных.Равно,, Ложь);
	КонецЕсли;
		
	// Отбор по обработанным местам использования (переход по механизму "Возможные дубли")	
	Если ЭтоАдресВременногоХранилища(АдресВыбранныхМестИспользования) Тогда
		
		ВыбранныеМестаИспользования = ПолучитьИзВременногоХранилища(АдресВыбранныхМестИспользования);
		ГруппаОтбора = Неопределено;
		Если ВыбранныеМестаИспользования.ЗначениеОтбора.Количество() > 0 Тогда
		
			ГруппаОтбора = ОбщегоНазначенияКлиентСервер.СоздатьГруппуЭлементовОтбора(
				КомпоновщикПредварительногоОтбора.Настройки.Отбор.Элементы,	
				ВыбранныеМестаИспользования.ПредставлениеОтбора,
				ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИли);
		
		КонецЕсли;
			
		Для каждого РеквизитЗначениеОтбора Из ВыбранныеМестаИспользования.ЗначениеОтбора Цикл
			ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбора(ГруппаОтбора,
				РеквизитЗначениеОтбора.Ключ, РеквизитЗначениеОтбора.Значение,
				ВидСравненияКомпоновкиДанных.ВСписке, "", Истина);	
		КонецЦикла;
		
		УдалитьИзВременногоХранилища(АдресВыбранныхМестИспользования);
		АдресВыбранныхМестИспользования = "";
	
	КонецЕсли;
		
	// Сравнение по наименованию.
	Если Не ПравилаЗагружены Тогда
		Правило = ТаблицаПравил.Найти("Наименование", "Реквизит");
		Если Правило <> Неопределено Тогда
			ЗначениеДляСравнения = ?(НечеткийПоиск, "Подобно", "Равно");
			Если Правило.ВариантыСравнения.НайтиПоЗначению(ЗначениеДляСравнения) <> Неопределено Тогда
				Правило.Правило = ЗначениеДляСравнения;
			КонецЕсли;
		КонецЕсли;
	КонецЕсли;
	
	// 6. Механизмы расширения в части прикладных правил.
	ОписаниеПрикладныхПравил = Неопределено;
	Если СтрокаТаблицыНастроек.СобытиеПараметрыПоискаДублей Тогда
		ПрикладныеПараметры = ПоискИУдалениеДублей.ПараметрыПоискаДублей(ТаблицаПравил, КомпоновщикПредварительногоОтбора);
		МенеджерОбъектаМетаданных = ОбщегоНазначения.МенеджерОбъектаПоПолномуИмени(ОбъектМетаданных.ПолноеИмя());
		МенеджерОбъектаМетаданных.ПараметрыПоискаДублей(ПрикладныеПараметры);
		
		// Представление прикладных правил.
		ОписаниеПрикладныхПравил = "";
		Для Каждого Описание Из ПрикладныеПараметры.ОграниченияСравнения Цикл
			ОписаниеПрикладныхПравил = ОписаниеПрикладныхПравил + Символы.ПС + Описание.Представление;
		КонецЦикла;
		ОписаниеПрикладныхПравил = СокрЛП(ОписаниеПрикладныхПравил);
	КонецЕсли;
	
	КомпоновщикПредварительногоОтбора.Восстановить(СпособВосстановленияНастроекКомпоновкиДанных.Полное);
	
	ТаблицаПравил.Сортировать("ПредставлениеРеквизита");
	ЗначениеВРеквизитФормы(ТаблицаПравил, "ПравилаПоиска");
	
	ИдентификаторОбластиПоискаДублей = Справочники.ИдентификаторыОбъектовМетаданных
		.НайтиПоРеквизиту("ПолноеИмя", ОбластьПоискаДублей);
	ТипОбластиПоискаДублей = ТипЗнч(ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ИдентификаторОбластиПоискаДублей, "ЗначениеПустойСсылки"));
	ТипыОбработанныхОбъектов = Новый ОписаниеТипов(
		ОбщегоНазначенияКлиентСервер.ЗначениеВМассиве(ТипОбластиПоискаДублей));
	
	Если НастройкиФормы = Неопределено Тогда
		СохранитьПользовательскиеНастройки();
	КонецЕсли;
КонецПроцедуры

&НаСервере
Процедура ПриСозданииНаСервереИнициализацияДанных(НастройкиФормы)
	УчитыватьПрикладныеПравила = НастройкиФормы.УчитыватьПрикладныеПравила;
	ОбластьПоискаДублей = НастройкиФормы.ОбластьПоискаДублей;
	СпособУдаления = НастройкиФормы.СпособУдаления;
	СкрыватьНезначимыеДубли = НастройкиФормы.СкрыватьНезначимыеДубли;
	
	ТаблицаНастроек = ПоискИУдалениеДублей.НастройкиОбъектовМетаданных();
	АдресНастроек = ПоместитьВоВременноеХранилище(ТаблицаНастроек, УникальныйИдентификатор);
	
	СписокВыбора = Элементы.ОбластьПоискаДублей.СписокВыбора;
	КешКартинок = Новый Соответствие;
	Для Каждого СтрокаТаблицы Из ТаблицаНастроек Цикл
		КартинкаВида = КартинкаВидаМетаданных(КешКартинок, СтрокаТаблицы.Вид);
		СписокВыбора.Добавить(СтрокаТаблицы.ПолноеИмя, СтрокаТаблицы.ПредставлениеСписка, , КартинкаВида);
	КонецЦикла;
	
	ВсеВариантыСравнения.Добавить("Равно",   НСтр("ru = 'Совпадает'"));
	ВсеВариантыСравнения.Добавить("Подобно", НСтр("ru = 'Совпадает по похожим словам'"));
КонецПроцедуры

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

&НаСервере
Процедура УстановитьУсловноеОформление()
	ЦветПоясняющийТекст       = Метаданные.ЭлементыСтиля.ПоясняющийТекст.Значение;
	ЦветПоясняющийОшибкуТекст = Метаданные.ЭлементыСтиля.ПоясняющийОшибкуТекст.Значение;
	ЦветНедоступныеДанные     = Метаданные.ЭлементыСтиля.ТекстЗапрещеннойЯчейкиЦвет.Значение;
	
	ЭлементыУсловногоОформления = УсловноеОформление.Элементы;
	ЭлементыУсловногоОформления.Очистить();
	
	// Отсутствие мест использования у группы.
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();
	
	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Ссылка");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.НеЗаполнено;
	ОтборОформления.ПравоеЗначение = Истина;
	
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Текст", "");
	
	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("НайденныеДублиКоличество");
	
	// 1. Строка с текущим основным элементом группы:
		
	// Отсутствие пометки
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();
	
	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Основной");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
	ОтборОформления.ПравоеЗначение = Истина;
	
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Видимость", Ложь);
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Отображать", Ложь);
	
	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("НайденныеДублиПометка");
			
	// 2. Места использования
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();
	
	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Ссылка");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Заполнено;
	ОтборОформления.ПравоеЗначение = Истина;
	
	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Количество");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
	ОтборОформления.ПравоеЗначение = 0;
	
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("Текст", НСтр("ru = '-'"));
	
	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("НайденныеДублиКоличество");
	
	// 4. Неактивная строка
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();
	
	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("НайденныеДубли.Пометка");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
	ОтборОформления.ПравоеЗначение = 0;
	
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ЦветТекста", ЦветНедоступныеДанные);
	
	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("НайденныеДубли");
	
	// Возможные дубли
	ЭлементОформления = ЭлементыУсловногоОформления.Добавить();
	
	ОтборОформления = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ОтборОформления.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ВозможныеДубли.Обработано");
	ОтборОформления.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
	ОтборОформления.ПравоеЗначение = Истина;
	
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ЦветТекста", ЦветаСтиля.ТекстЗапрещеннойЯчейкиЦвет);
	
	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("ВозможныеДублиИмяОбъектаМетаданных");
	
	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("ВозможныеДублиИсточники");

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

&НаСервере
Функция ПараметрыУдаленияДублей()
	
	ПараметрыУдаления = Новый Соответствие;
	ИндексПорции = 0;
	ДеревоДублей = РеквизитФормыВЗначение("НайденныеДубли");
	ФильтрПоиска = Новый Структура("Основной", Истина);
	
	Для Каждого ГруппыДублей Из ДеревоДублей.Строки Цикл
		ПарыЗамен = Новый Соответствие;
		Оригинал = ГруппыДублей.Строки.НайтиСтроки(ФильтрПоиска)[0].Ссылка;
		Для Каждого Дубль Из ГруппыДублей.Строки Цикл
			Если Дубль.Пометка = 1 И Дубль.Ссылка <> Оригинал Тогда
				ПарыЗамен[Дубль.Ссылка] = Оригинал;
			КонецЕсли;
		КонецЦикла;
		КоличествоЗамен = ПарыЗамен.Количество();
		Если КоличествоЗамен = 0 Тогда
			Продолжить;
		КонецЕсли;
		
		ОбщееКоличествоВыбранных = ОбщееКоличествоВыбранных + КоличествоЗамен;
		
		ПараметрыПроцедуры = Новый Структура;
		ПараметрыПроцедуры.Вставить("УчитыватьПрикладныеПравила", УчитыватьПрикладныеПравила);
		ПараметрыПроцедуры.Вставить("СпособУдаления", СпособУдаления);
		ПараметрыПроцедуры.Вставить("ПарыЗамен", ПарыЗамен);
		ПараметрыВызоваСервера = Новый Массив;
		ПараметрыВызоваСервера.Добавить(ПараметрыПроцедуры);
		ПараметрыУдаления.Вставить(ИндексПорции, ПараметрыВызоваСервера);
		ИндексПорции = ИндексПорции + 1;
	КонецЦикла;
	
	Возврат ПараметрыУдаления;
	
КонецФункции

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

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

&НаСервереБезКонтекста
Функция ВариантыСравненияДляТипа(Знач ДоступныеТипы, Знач ВсеВариантыСравнения, Знач ДоступенНечеткийПоиск) 
	
	ЭтоХранилище = ДоступныеТипы.СодержитТип(Тип("ХранилищеЗначения"));
	Если ЭтоХранилище Тогда 
		Возврат Неопределено; // Нельзя сравнивать
	КонецЕсли;
	
	ЭтоСтрока = ДоступныеТипы.СодержитТип(Тип("Строка"));
	ЭтоФиксированнаяСтрока = ЭтоСтрока И ДоступныеТипы.КвалификаторыСтроки <> Неопределено 
		И ДоступныеТипы.КвалификаторыСтроки.Длина <> 0;
		
	Если ЭтоСтрока И Не ЭтоФиксированнаяСтрока Тогда
		Возврат Неопределено; // Нельзя сравнивать
	КонецЕсли;
	
	Результат = Новый СписокЗначений;
	ЗаполнитьЗначенияСвойств(Результат.Добавить(), ВсеВариантыСравнения[0]); // Совпадает
	
	Если ДоступенНечеткийПоиск И ЭтоСтрока Тогда
		ЗаполнитьЗначенияСвойств(Результат.Добавить(), ВсеВариантыСравнения[1]); // Похоже
	КонецЕсли;
		
	Возврат Результат;
КонецФункции

&НаСервереБезКонтекста
Функция КартинкаВидаМетаданных(КешКартинок, Вид)

	Картинка = КешКартинок[Вид];
	Если Картинка = Неопределено Тогда
		Картинка = БиблиотекаКартинок[Вид];
		КешКартинок.Вставить(Вид, Картинка);
	КонецЕсли;

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

#КонецОбласти

#Область РаботаСДлительнымиОперациями

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

&НаСервере
Функция НайтиИУдалитьДубли()
	
	ПараметрыПроцедуры = Новый Структура;
	ПараметрыПроцедуры.Вставить("УчитыватьПрикладныеПравила", УчитыватьПрикладныеПравила);
	
	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;
	Если ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Тогда
		
		Элементы.ВыполнениеПоиска.ОтображениеСостояния.Текст = НСтр("ru = 'Поиск дублей...'");

		ПараметрыПроцедуры.Вставить("ОбластьПоискаДублей",     ОбластьПоискаДублей);
		ПараметрыПроцедуры.Вставить("МаксимальноеЧислоДублей", 1500);
		МассивПравилПоиска = Новый Массив;
		Для Каждого Правило Из ПравилаПоиска Цикл
			МассивПравилПоиска.Добавить(Новый Структура("Реквизит, Правило", Правило.Реквизит, Правило.Правило));
		КонецЦикла;
		ПараметрыПроцедуры.Вставить("ПравилаПоиска", МассивПравилПоиска);
		ПараметрыПроцедуры.Вставить("СхемаКомпоновки", ПолучитьИзВременногоХранилища(АдресСхемыКомпоновки));
		ПараметрыПроцедуры.Вставить("НастройкиКомпоновщикаПредварительногоОтбора", КомпоновщикПредварительногоОтбора.Настройки);
		ПараметрыПроцедуры.Вставить("СкрыватьНезначимыеДубли", СкрыватьНезначимыеДубли);
		
		НастройкиЗапуска = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
		НастройкиЗапуска.НаименованиеФоновогоЗадания = НСтр("ru = 'Поиск и удаление дублей: Поиск дублей'");
		
		Возврат ДлительныеОперации.ВыполнитьВФоне(
			"Обработка.ПоискИУдалениеДублей.МодульОбъекта.ФоновыйПоискДублей",
			ПараметрыПроцедуры,
			НастройкиЗапуска);
		
	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда
		
		ПараметрыУдаления = ПараметрыУдаленияДублей();
		Если ПараметрыУдаления.Количество() = 0 Тогда
			ОбщегоНазначения.СообщитьПользователю(НСтр("ru = 'Выберите хотя бы одну группу дублей.'"),, "НайденныеДубли");
			Возврат Неопределено;
		КонецЕсли;
		
		Элементы.ВыполнениеУдаления.ОтображениеСостояния.Текст = НСтр("ru = 'Обработка дублей...'");
		
		НастройкиЗапуска = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
		НастройкиЗапуска.НаименованиеФоновогоЗадания = НСтр("ru = 'Поиск и удаление дублей: Удаление дублей'");
		
		КоличествоПорций = ПараметрыУдаления.Количество();
		Возврат ДлительныеОперации.ВыполнитьФункциюВНесколькоПотоков(
			"Обработка.ПоискИУдалениеДублей.МодульОбъекта.ФоновоеУдалениеДублей",
			НастройкиЗапуска,
			ПараметрыУдаления);
		
	КонецЕсли;
	
КонецФункции

// Параметры:
//  Результат - см. ДлительныеОперацииКлиент.НовоеСостояниеДлительнойОперации
//  ДополнительныеПараметры - Неопределено
//
&НаКлиенте
Процедура НайтиИУдалитьДублиПрогресс(Результат, ДополнительныеПараметры) Экспорт
	
	Если Результат.Статус <> "Выполняется"
	 Или Результат.Прогресс = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;
	Если ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Тогда
		
		Сообщение = НСтр("ru = 'Поиск дублей...'");
		Если Результат.Прогресс.Текст = "РассчитыватьМестаИспользования" Тогда 
			Сообщение = НСтр("ru = 'Выполняется расчет мест использования дублей...'");
		ИначеЕсли Результат.Прогресс.Процент > 0 Тогда
			Сообщение = Сообщение + " " 
				+ СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
					НСтр("ru = '(найдено %1)'"), Результат.Прогресс.Процент);
		КонецЕсли;
		Элементы.ВыполнениеПоиска.ОтображениеСостояния.Текст = Сообщение;
		
	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда
		
		ПараметрыПрогресса = Неопределено;
		Если Результат.Прогресс.Свойство("ДополнительныеПараметры", ПараметрыПрогресса)
			И ПараметрыПрогресса = "ПрогрессМногопоточногоПроцесса" Тогда
			Возврат;
		КонецЕсли;
		
		Если НЕ ПустаяСтрока(Результат.Прогресс.Текст) Тогда
			Если ПараметрыПрогресса = Неопределено Тогда
				Сообщение = Результат.Прогресс.Текст;
			Иначе
				Сообщение = ТекстПрогресса(ПараметрыПрогресса, Результат.Прогресс.Текст);
			КонецЕсли;
		Иначе	
			Сообщение = НСтр("ru = 'Обработка дублей...'");
		КонецЕсли;
		
		Элементы.ВыполнениеУдаления.ОтображениеСостояния.Текст = Сообщение;
		
	КонецЕсли;
	
КонецПроцедуры

&НаСервере
Функция ТекстПрогресса(Знач ПараметрыПрогресса, Знач ИсходныйТекстПрогресса)
	
	ЭтоЗамена = СтрНачинаетсяС(ИсходныйТекстПрогресса, НСтр("ru = 'Замена дублей'"));
	ИмяРеквизитаПрогресса = ?(ЭтоЗамена, "КоличествоОбработанных", "КоличествоУдаленных");
	Если ЭтоЗамена Тогда
		ОбщееКоличествоОбработанных = ОбщееКоличествоОбработанных + ПараметрыПрогресса.КоличествоОбработанных;
	Иначе
		ОбщееКоличествоУдаленных = ОбщееКоличествоУдаленных + ПараметрыПрогресса.КоличествоОбработанных;
	КонецЕсли;
	
	ТекстПрогресса = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
		НСтр("ru = 'Замена дублей... обработано (%1 из %2)'"),
		ОбщееКоличествоОбработанных,
		ОбщееКоличествоВыбранных);
	Если СпособУдаления = "Непосредственно" Тогда
		ТекстПрогресса = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = '%1
			|Удаление дублей... обработано (%2 из %3)'"),
			ТекстПрогресса,
			ОбщееКоличествоУдаленных,
			ОбщееКоличествоВыбранных);
	КонецЕсли;
	
	Возврат ТекстПрогресса;
	
КонецФункции

// Параметры:
//  Результат - см. ДлительныеОперацииКлиент.НовыйРезультатДлительнойОперации
//  ДополнительныеПараметры - Неопределено
//
&НаКлиенте
Процедура НайтиИУдалитьДублиЗавершение(Результат, ДополнительныеПараметры) Экспорт
	НастройкиМастера.ПоказатьДиалогПередЗакрытием = Ложь;
	Активизировать();
	ТекущаяСтраница = Элементы.ШагиМастера.ТекущаяСтраница;
	
	Если Результат = Неопределено Тогда 
		Возврат;
	КонецЕсли;
	
	Если Результат.Статус <> "Выполнено" Тогда
		// Фоновое задание завершено с ошибкой.
		Если ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Тогда
			Кратко = НСтр("ru = 'Не удалость найти дубли по причине:'");
		ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда
			Кратко = НСтр("ru = 'Удаление дублей не выполнено по причине:'");
		КонецЕсли;
		Кратко = Кратко + Символы.ПС + Результат.КраткоеПредставлениеОшибки;
		Подробно = Кратко + Символы.ПС + Символы.ПС + Результат.ПодробноеПредставлениеОшибки;
		Элементы.НадписьТекстОшибки.Заголовок = Кратко;
		Элементы.СсылкаПодробнее.Подсказка    = Подробно;
		ПерейтиНаШагМастера(Элементы.ШагВозниклаОшибка);
		Возврат;
	КонецЕсли;
	
	Шаг = НастройкиМастера.ТекущийШаг;// см. ДобавитьШагМастера
	Если ТекущаяСтраница = Элементы.ШагВыполнениеПоиска Тогда
		ВсегоНайденоДублей = ЗаполнитьРезультатыПоискаДублей(Результат.АдресРезультата);
		ВсегоЭлементов = ВсегоНайденоДублей;
		Если ВсегоНайденоДублей > 0 Тогда
			ОбновитьОписаниеСостоянияНайденныхДублей();
			ПерейтиНаШагМастера(Шаг.Индекс + 1);
		Иначе
			ПерейтиНаШагМастера(Элементы.ШагДублейНеНайдено);
		КонецЕсли;
	ИначеЕсли ТекущаяСтраница = Элементы.ШагВыполнениеУдаления Тогда
		Успех = ЗаполнитьРезультатыУдаленияДублей(Результат.АдресРезультата);
		ОбщегоНазначенияКлиент.ОповеститьОбИзмененииОбъектов(ТипыОбработанныхОбъектов);
		Если Успех = Истина Тогда
			// Заменены все группы дублей.
			ОбновитьСостояниеВозможныхДублей();
			ПерейтиНаШагМастера(Шаг.Индекс + 1);
		Иначе
			// Не все места использования удалось заменить.
			ПерейтиНаШагМастера(Элементы.ШагНеудачныеЗамены);
		КонецЕсли;
	КонецЕсли;
	
КонецПроцедуры

&НаСервере
Функция ЗаполнитьРезультатыПоискаДублей(Знач АдресРезультата)
	
	// Получаем результат функции ГруппыДублей модуля обработки.
	Данные = ПолучитьИзВременногоХранилища(АдресРезультата); // см. ОбработкаОбъект.ПоискИУдалениеДублей.ГруппыДублей
	ОписаниеОшибкиПоискаДублей = Данные.ОписаниеОшибки;
	
	ДеревоНайденныхДублей = РеквизитФормыВЗначение("НайденныеДубли");
	ЭлементыДерева = ДеревоНайденныхДублей.Строки;
	ЭлементыДерева.Очистить();
	
	МестаИспользования = Данные.МестаИспользования;
	ТаблицаДублей      = Данные.ТаблицаДублей;
	ВозвращеноМеньшеЧемНайдено = Данные.ВозвращеноМеньшеЧемНайдено;
	
	ФильтрСтрок = Новый Структура("Родитель");
	ФильтрМест  = Новый Структура("Ссылка");
	
	ВсегоНайденоДублей = 0;
	
	ГруппыДублей = ТаблицаДублей.НайтиСтроки(ФильтрСтрок);
	Для Каждого ГруппаДубля Из ГруппыДублей Цикл
		ФильтрСтрок.Родитель = ГруппаДубля.Ссылка;
		ЭлементыГруппы = ТаблицаДублей.НайтиСтроки(ФильтрСтрок);
		
		ГруппаДерева = ЭлементыДерева.Добавить();
		ГруппаДерева.Количество = ЭлементыГруппы.Количество();
		ГруппаДерева.Пометка = 1;
		ГруппаДерева.НомерКартинки = -1;
		
		ОригинальныйЭлемент = Неопределено; // Элемент с наибольшим количеством мест использования.
		МаксимальноМестИспользования = -1;
		
		Для Каждого Элемент Из ЭлементыГруппы Цикл
			СтрокаДерева = ГруппаДерева.Строки.Добавить();
			ЗаполнитьЗначенияСвойств(СтрокаДерева, Элемент, "Ссылка, Код, Наименование");
			
			ФильтрМест.Ссылка = Элемент.Ссылка;
			МестИспользования = МестаИспользования.НайтиСтроки(ФильтрМест).Количество();
			
			СтрокаДерева.Количество = МестИспользования;
			СтрокаДерева.Пометка = ?(Элемент.ПометкаУдаления И МестИспользования = 0, 0, 1);
			СтрокаДерева.НомерКартинки = ?(Элемент.ПометкаУдаления, 4, 3);
			
			Если МаксимальноМестИспользования < МестИспользования Тогда
				Если ОригинальныйЭлемент <> Неопределено Тогда
					ОригинальныйЭлемент.Основной = Ложь;
				КонецЕсли;
				ОригинальныйЭлемент = СтрокаДерева;
				МаксимальноМестИспользования   = МестИспользования;
				ОригинальныйЭлемент.Основной = Истина;
			КонецЕсли;
			
			ВсегоНайденоДублей = ВсегоНайденоДублей + 1;
		КонецЦикла;
		
		
		// Если все дубли уже помечены на удаление и не используются, то эту группу дублей не требуется заменять повторно.
		ПометкаГруппы = Ложь;
		Для Каждого СтрокаДерева Из ГруппаДерева.Строки Цикл
			Если Не СтрокаДерева.Основной И СтрокаДерева.Пометка Тогда
				ПометкаГруппы = Истина;
				Прервать;
			КонецЕсли;
		КонецЦикла;
		
		ГруппаДерева.Пометка = ПометкаГруппы;
		ГруппаДерева.Наименование = ОригинальныйЭлемент.Наименование + " (" + ГруппаДерева.Количество + ")";
		ГруппаДерева.Строки.Сортировать("Наименование");
	КонецЦикла;
	
	ЗначениеВРеквизитФормы(ДеревоНайденныхДублей, "НайденныеДубли");
	
	МестаИспользованияКандидата.Очистить();
	Элементы.ОписаниеТекущейГруппыДублей.Заголовок = НСтр("ru = 'Дублей не найдено'");
	
	Если ЭтоАдресВременногоХранилища(АдресМестИспользования) Тогда
		УдалитьИзВременногоХранилища(АдресМестИспользования);
	КонецЕсли;
	АдресМестИспользования = ПоместитьВоВременноеХранилище(МестаИспользования, УникальныйИдентификатор);
	Возврат ВсегоНайденоДублей;
	
КонецФункции

&НаСервере
Функция ЗаполнитьРезультатыУдаленияДублей(Знач АдресРезультата)
	// ТаблицаОшибок - результат функции ЗаменитьСсылки модуля.
	РезультатВыполненияВФоне = ПолучитьИзВременногоХранилища(АдресРезультата); // см. ОбщегоНазначения.ЗаменитьСсылки
	ТаблицаОшибок = ПолучитьИзВременногоХранилища(РезультатВыполненияВФоне[0].АдресРезультата);
	Для ИндексПорции = 1 По КоличествоПорций - 1 Цикл
		АдресРезультатаПорции = РезультатВыполненияВФоне[ИндексПорции].АдресРезультата;
		ТаблицаОшибокПорции = ПолучитьИзВременногоХранилища(АдресРезультатаПорции);
		
		ОбщегоНазначенияКлиентСервер.ДополнитьТаблицу(ТаблицаОшибокПорции, ТаблицаОшибок);
	КонецЦикла;
	ОбщееКоличествоОбработанных = 0;
	ОбщееКоличествоУдаленных = 0;
	ОбщееКоличествоВыбранных = 0;
	КоличествоПорций = 0;
	ПрогрессУдаления.Очистить();
	
	Если ЭтоАдресВременногоХранилища(АдресРезультатаЗамены) Тогда
		УдалитьИзВременногоХранилища(АдресРезультатаЗамены);
	КонецЕсли;
	
	ЗавершеноБезОшибок = ТаблицаОшибок.Количество() = 0;
	ПоследнийКандидат  = Неопределено;
	
	Если ЗавершеноБезОшибок Тогда
		ВсегоОбработано = 0; 
		ВсегоОсновных   = 0;
		Для Каждого ГруппаДублей Из НайденныеДубли.ПолучитьЭлементы() Цикл
			Если ГруппаДублей.Пометка Тогда
				Для Каждого Кандидат Из ГруппаДублей.ПолучитьЭлементы() Цикл
					Если Кандидат.Основной Тогда
						ПоследнийКандидат = Кандидат.Ссылка;
						ВсегоОбработано   = ВсегоОбработано + 1;
						ВсегоОсновных     = ВсегоОсновных + 1;
					ИначеЕсли Кандидат.Пометка Тогда 
						ВсегоОбработано = ВсегоОбработано + 1;
					КонецЕсли;
				КонецЦикла;
			КонецЕсли;
		КонецЦикла;
		
		Если ВсегоОсновных = 1 Тогда
			// Много дублей в один элемент.
			Если ПоследнийКандидат = Неопределено Тогда
				ОписаниеСостоянияНайденныхДублей = Новый ФорматированнаяСтрока(
					СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
						НСтр("ru = 'Все найденные дубли (%1) успешно объединены'"),
						ВсегоОбработано));
			Иначе
				ПоследнийКандидатСтрокой = ОбщегоНазначения.ПредметСтрокой(ПоследнийКандидат);
				ОписаниеСостоянияНайденныхДублей = Новый ФорматированнаяСтрока(
					СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
						НСтр("ru = 'Все найденные дубли (%1) успешно объединены
							|в ""%2""'"),
						ВсегоОбработано, ПоследнийКандидатСтрокой));
			КонецЕсли;
		Иначе
			// Много дублей во много групп.
			ОписаниеСостоянияНайденныхДублей = Новый ФорматированнаяСтрока(
				СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
					НСтр("ru = 'Все найденные дубли (%1) успешно объединены.
						|Оставлено элементов (%2).'"),
					ВсегоОбработано,
					ВсегоОсновных));
		КонецЕсли;
	КонецЕсли;
	
	НеобработанныеДубли.ПолучитьЭлементы().Очистить();
	МестаИспользованияНеобработанных.Очистить();
	МестаИспользованияКандидата.Очистить();
	
	Если ЗавершеноБезОшибок Тогда
		ЗаполнитьВозможныеДубли();
		НайденныеДубли.ПолучитьЭлементы().Очистить();
		ЕстьВажныеВозможныеДубли = ВозможныеДубли.НайтиСтроки(Новый Структура("СтепеньРодства",2)).Количество() > 0; 
		ОтображаемаяСтепеньРодства = ?(ЕстьВажныеВозможныеДубли, 2, 1);		
		УстановитьОтборВозможныхДублей(ЭтотОбъект);
		Возврат Истина;
	КонецЕсли;
	
	// Сохраняем для последующего доступа при анализе ссылок.
	АдресРезультатаЗамены = ПоместитьВоВременноеХранилище(ТаблицаОшибок, УникальныйИдентификатор);
	
	// Формируем дерево дублей по ошибкам.
	ЗначениеВРеквизитФормы(РеквизитФормыВЗначение("НайденныеДубли"), "НеобработанныеДубли");
	
	// Анализируем оставшихся
	Фильтр = Новый Структура("Ссылка");
	Родители = НеобработанныеДубли.ПолучитьЭлементы();
	ПозицияРодителя = Родители.Количество() - 1;
	Пока ПозицияРодителя >= 0 Цикл
		Родитель = Родители[ПозицияРодителя];
		
		Потомки = Родитель.ПолучитьЭлементы();
		ПозицияПотомка = Потомки.Количество() - 1;
		ОсновнойПотомок = Потомки[0];	// Там есть минимум один
		
		Пока ПозицияПотомка >= 0 Цикл
			Потомок = Потомки[ПозицияПотомка];
			
			Если Потомок.Основной Тогда
				ОсновнойПотомок = Потомок;
				Фильтр.Ссылка = Потомок.Ссылка;
				Потомок.Количество = ТаблицаОшибок.НайтиСтроки(Фильтр).Количество();
				
			ИначеЕсли ТаблицаОшибок.Найти(Потомок.Ссылка, "Ссылка") = Неопределено Тогда
				// Был успешно удален, нет ошибок.
				Потомки.Удалить(Потомок);
				
			Иначе
				Фильтр.Ссылка = Потомок.Ссылка;
				Потомок.Количество = ТаблицаОшибок.НайтиСтроки(Фильтр).Количество();
				
			КонецЕсли;
			
			ПозицияПотомка = ПозицияПотомка - 1;
		КонецЦикла;
		
		КоличествоПотомков = Потомки.Количество();
		Если КоличествоПотомков = 1 И Потомки[0].Основной Тогда
			Родители.Удалить(Родитель);
		Иначе
			Родитель.Количество = КоличествоПотомков - 1;
			Родитель.Наименование = ОсновнойПотомок.Наименование + " (" + КоличествоПотомков + ")";
		КонецЕсли;
		
		ПозицияРодителя = ПозицияРодителя - 1;
	КонецЦикла;
	
	Возврат Ложь;
КонецФункции

&НаСервере
Процедура ЗаполнитьВозможныеДубли()
	
	ИдентификаторОбрабатываемогоОбъекта = ИдентификаторОбластиПоискаДублей;
	ОбрабатываемыйОбъектМетаданных = ОбщегоНазначения.ОбъектМетаданныхПоИдентификатору(ИдентификаторОбрабатываемогоОбъекта);
	
	ОбработанныйВозможныйДубль = ВозможныеДубли.НайтиСтроки(
		Новый Структура("ТипОбъектаМетаданных", ОбрабатываемыйОбъектМетаданных.ПолноеИмя()));
	Если ОбработанныйВозможныйДубль.Количество() > 0 Тогда
		ОбработанныйВозможныйДубль[0].Обработано = Истина;	
	КонецЕсли;
		
	МестаИспользования = ПолучитьИзВременногоХранилища(АдресМестИспользования);
	ПредполагаемыеВозможныеДубли = СформироватьВозможныеДубли(МестаИспользования);
	
	ОтборПоискаВозможныхДублей = СформироватьОтборВозможныхДублей(РеквизитФормыВЗначение("НайденныеДубли"));
	ТипОбрабатываемогоОбъекта = ТипЗнч(ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ИдентификаторОбрабатываемогоОбъекта, 
		"ЗначениеПустойСсылки"));
	
	Для каждого МестоИспользования Из ПредполагаемыеВозможныеДубли Цикл
		
		Если ОбрабатываемыйОбъектМетаданных = МестоИспользования 
			ИЛИ МестоИспользования = Неопределено Тогда
			
			Продолжить;
		КонецЕсли;
		
		КлючиСвязи = КлючиСвязиМетаданных(ТипОбрабатываемогоОбъекта, МестоИспользования);
		Если КлючиСвязи.Количество() > 0 Тогда
			ДобавитьВозможныйДубль(ОбрабатываемыйОбъектМетаданных, МестоИспользования, КлючиСвязи, ОтборПоискаВозможныхДублей); 	
		КонецЕсли;
		
	КонецЦикла;
	
	ВыбранныеОбработанныеСтроки = ВозможныеДубли.НайтиСтроки(Новый Структура("Обработано, Выбран", Истина, Истина));
	Для каждого СтрокаВозможныеДубли Из ВыбранныеОбработанныеСтроки Цикл
		СтрокаВозможныеДубли.Выбран = Ложь;
	КонецЦикла;

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

&НаСервереБезКонтекста
Функция СформироватьОтборВозможныхДублей(ДеревоДублей)

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

КонецФункции


&НаСервереБезКонтекста
Функция СформироватьВозможныеДубли(МестаИспользования)

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

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

КонецФункции

&НаСервере
Процедура ДобавитьВозможныйДубль(ОбрабатываемыйОбъектМетаданных, МестоИспользования, КлючиСвязи, Отбор = Неопределено)
	
	ПредставлениеМестаИспользования = МестоИспользования.Представление();
	ПолноеИмяМестаИспользования = МестоИспользования.ПолноеИмя();
	
	ТекущийПорядковыйНомер = ?(ВозможныеДубли.Количество(),ВозможныеДубли[ВозможныеДубли.Количество() - 1].Порядок + 1, 0);
	ОтборДублей = Новый Структура("ТипОбъектаМетаданных", ПолноеИмяМестаИспользования);
	ДобавленныеДубли = ВозможныеДубли.НайтиСтроки(ОтборДублей);
	
	Если ДобавленныеДубли.Количество() > 0 Тогда
		
		ВозможныйДубль = ДобавленныеДубли[0];
		Если ВозможныйДубль.Источники.НайтиПоЗначению(ПолноеИмяМестаИспользования) <> Неопределено Тогда
			ВозможныйДубль.Источники.Добавить(ОбрабатываемыйОбъектМетаданных.Представление());	
		КонецЕсли;
		
		ИнформацияОбОтбореВозможныхДублей = ПолучитьИзВременногоХранилища(ВозможныйДубль.АдресОтбора);
				
	Иначе	
		
		ВозможныйДубль = ВозможныеДубли.Добавить();	
		ВозможныйДубль.ИмяОбъектаМетаданных = ПредставлениеМестаИспользования;
		ВозможныйДубль.ТипОбъектаМетаданных = ПолноеИмяМестаИспользования;
		ВозможныйДубль.Порядок = ТекущийПорядковыйНомер;
		ВозможныйДубль.Обработано = Ложь;
		
		Источники = Новый СписокЗначений;
		Источники.Добавить(ОбрабатываемыйОбъектМетаданных.Представление());
		ВозможныйДубль.Источники = Источники;
		
		ИнформацияОбОтбореВозможныхДублей = Новый Соответствие;
		
	КонецЕсли;	
	
	ВозможныйДубль.СтепеньРодства = ?(КлючиСвязи.Найти(2, "СтепеньРодства") <> Неопределено, 2, 1);
	ВозможныйДубль.ИсточникиПредставление = СтрСоединить(ВозможныйДубль.Источники.ВыгрузитьЗначения(), ", ");
	
	// Будем сохранять информацию о выполненных заменах для отбора возможных дублей
	// Соответствие: Ключ - левое значение отбора, Значение - правое значение		
	Для каждого КлючиСвязи Из КлючиСвязи Цикл
	
		ОтборВозможныхДублей = ИнформацияОбОтбореВозможныхДублей[КлючиСвязи.Имя];
		Если ОтборВозможныхДублей = Неопределено Тогда
			ИнформацияОбОтбореВозможныхДублей.Вставить(КлючиСвязи.Имя, Отбор);
		Иначе
			ОбщегоНазначенияКлиентСервер.ДополнитьМассив(ОтборВозможныхДублей, Отбор);
			ИнформацияОбОтбореВозможныхДублей.Вставить(КлючиСвязи.Имя, ОтборВозможныхДублей);
		КонецЕсли;
	
	КонецЦикла;	
	
	ВозможныйДубль.АдресОтбора = ПоместитьВоВременноеХранилище(ИнформацияОбОтбореВозможныхДублей, УникальныйИдентификатор);

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

// Возвращает степень родства метаданных
// 
// Возвращаемое значение:
//   - Число - как связаны объекты метаданных между собой
//    0 - не связаны
//    1 - связь по реквизиту
//    2 - связь по Родителю/Владельцу
//
&НаСервереБезКонтекста
Функция КлючиСвязиМетаданных(Знач ТипОбрабатываемогоОбъекта, Знач МестоИспользования)
	
	Результат = Новый ТаблицаЗначений;
	Результат.Колонки.Добавить("Имя");
	Результат.Колонки.Добавить("СтепеньРодства");
			
	Для каждого РеквизитМестаИспользования Из МестоИспользования.Реквизиты Цикл
		
		Если РеквизитМестаИспользования.Тип.Типы().Найти(ТипОбрабатываемогоОбъекта) <> Неопределено Тогда
			СвязьМетаданных = Результат.Добавить();
			СвязьМетаданных.Имя = РеквизитМестаИспользования.Имя;
			СвязьМетаданных.СтепеньРодства = 1;
		КонецЕсли;
		
	КонецЦикла;
	
	Для каждого РеквизитМестаИспользования Из МестоИспользования.СтандартныеРеквизиты Цикл
		
		Если РеквизитМестаИспользования.Тип.Типы().Найти(ТипОбрабатываемогоОбъекта) <> Неопределено Тогда
			СвязьМетаданных = Результат.Добавить();
			СвязьМетаданных.Имя = РеквизитМестаИспользования.Имя;
			СвязьМетаданных.СтепеньРодства = 2;
		КонецЕсли;
		
	КонецЦикла;
		
	Возврат Результат;

КонецФункции

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

#КонецОбласти

#Область СлужебныеПроцедурыИФункцииМастера

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

// Параметры:
//  КнопкаМастера - см. КнопкаМастера
//  Описание - Строка
//
&НаКлиентеНаСервереБезКонтекста
Процедура ОбновитьСвойстваКнопкиМастера(КнопкаМастера, Описание)
	
	ЗаполнитьЗначенияСвойств(КнопкаМастера, Описание);
	КнопкаМастера.РасширеннаяПодсказка.Заголовок = Описание.Подсказка;
	
КонецПроцедуры

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

	Перем АдресОтбора;
	ВыбранныеМестаИспользования = Новый Структура("ПредставлениеОтбора, ЗначениеОтбора","", Новый Структура);
	ВыбранныйДубль = ВозможныеДубли.НайтиСтроки(Новый Структура("ТипОбъектаМетаданных", ВыбранныйТип));
		
	ВыбранныеМестаИспользования.ПредставлениеОтбора = ПредставлениеВыбранногоМестаИспользования();
	
	Если ВыбранныйДубль.Количество() > 0 Тогда
	
		ВыбранныйДубль = ВыбранныйДубль[0];
		АдресОтбора = ВыбранныйДубль.АдресОтбора;
		
	КонецЕсли;
	
	Если ЭтоАдресВременногоХранилища(АдресОтбора) Тогда
	
		Отбор = ПолучитьИзВременногоХранилища(АдресОтбора);	
		Для каждого РеквизитЗначениеОтбора Из Отбор Цикл
			
			ЗначениеОтбора = Новый СписокЗначений;
			ЗначениеОтбора.ЗагрузитьЗначения(РеквизитЗначениеОтбора.Значение);
			ВыбранныеМестаИспользования.ЗначениеОтбора.Вставить(РеквизитЗначениеОтбора.Ключ, ЗначениеОтбора);	
		
		КонецЦикла;
	
	КонецЕсли;
	
	АдресВыбранныхМестИспользования = ПоместитьВоВременноеХранилище(ВыбранныеМестаИспользования, УникальныйИдентификатор);
	
КонецПроцедуры

// Используется в качестве идентификатора отбора.
&НаСервере
Функция ПредставлениеВыбранногоМестаИспользования()
	Возврат НСтр("ru='Элементы, среди которых возможны дубли'");
КонецФункции

#КонецОбласти

#КонецОбласти

Связанные материалы