Мы провели анализ и выяснили, что в базе УТ один и тот же файл прикрепляют к разным документам.
Пример на скрине, один файл прикрепили 139 раз, и это не предел:
Решили избавиться от данной проблемы.
Суть решения.
1. Прокешировать всё файлы алгоритмом МД5.
2. Выявить дубли.
3. Удалить лишние файлы, а в Справочнике присоединенных файлов сделать ссылки на уникальные файлы.
Вопрос, куда записывать информацию по файлам. Решил создать индексированный ресурс в регистре сведений Сведения о файлах.
К сожалению, в текущей версии платформы и конфигурации данный механизм нельзя полностью реализовать через расширение, т.к. нельзя создать доп. ресурс. А перечислять в расширении все имена справочников, в которых есть "...ПрисоединенныеФайлы" проблематично. Это реализация для старой УТ. Для Бухгалтерии всё это вынесено в расширение, которое прикрепляю к данной публикации.
И в процедуре "перед записью" в модуле объекта регистра сведений "СведенияОФайлах" дописывать кеш файла.
//++Семенихин 05.10.2022 ++ Полати //
//Дозаписываем Хеш по файлу
Процедура ПередЗаписью(Отказ, Замещение)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Для Каждого строка из ЭтотОбъект Цикл
//ПолучимИмяФалаФайл.Строка(Файл)
//ОбщегоНазначенияКлиентСервер.ПолучитьПолноеИмяФайла(ИмяВременногоКаталога, ИмяФайла)
Если строка.ТипХраненияФайла = Перечисления.ТипыХраненияФайлов.ВТомахНаДиске тогда
Попытка
ИмяФайла = ОбщегоНазначенияКлиентСервер.ПолучитьПолноеИмяФайла(строка.Файл.Том.ПолныйПутьWindows,строка.Файл.ПутьКФайлу);
строка.Пл_КонтрольнаяСумма = MD5ХешФайл(ИмяФайла);
Исключение
КонецПопытки;
ИначеЕсли строка.ТипХраненияФайла = Перечисления.ТипыХраненияФайлов.ВИнформационнойБазе тогда
//Двоичных данных ещё нет, они появятся позже..
//Надо обрабатывать уже ОбщийМодуль.РаботаСФайламиСлужебный.Модуль.ЗаписатьФайлВИнформационнуюБазу и обновлять
//сведения о файлах.
//Пока данный вариант реализации не рассматриваем, Его можно на отдельное рег задание возложить
//ДвоичныеДанные = РаботаСФайлами.ДвоичныеДанныеФайла(строка.Файл);
//строка.Пл_КонтрольнаяСумма = MD5ХешСтрока(ДвоичныеДанные);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
//++Семенихин 05.10.2022 ++ Полати //
Функция MD5ХешФайл(тСтрока)
Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.ДобавитьФайл(тСтрока);
Возврат Хеш.ХешСумма;
КонецФункции
//++Семенихин 05.10.2022 ++ Полати //
Функция MD5ХешСтрока(тСтрока)
Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.Добавить(тСтрока);
Возврат Хеш.ХешСумма;
КонецФункции
После добавления ресурса его можно заполнить обработкой:
Для этого перезаписываем регистр Сведения о файлах, а они уже сами допишутся, т.к. в модуле объекта уже есть соответствующая процедура
Процедура ДописатьКЕШвСведенияОФайлах()Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СведенияОФайлах.Файл КАК Файл,
| СведенияОФайлах.Пл_КонтрольнаяСумма КАК Пл_КонтрольнаяСумма
|ИЗ
| РегистрСведений.СведенияОФайлах КАК СведенияОФайлах
|ГДЕ
| СведенияОФайлах.ТипХраненияФайла = &ТипХраненияФайла
| И СведенияОФайлах.Пл_КонтрольнаяСумма = """"";
Запрос.УстановитьПараметр("ТипХраненияФайла", Перечисления.ТипыХраненияФайлов.ВТомахНаДиске);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// СвойстваФайлаВТоме = РаботаСФайламиВТомахСлужебный.СвойстваФайлаВТоме(ВыборкаДетальныеЗаписи.Файл);
//ИмяФайла = РаботаСФайламиВТомахСлужебный.ПолноеИмяФайлаВТоме(СвойстваФайлаВТоме);
// Пл_КонтрольнаяСумма = MD5ХешФайл(ИмяФайла);
ДописатьХешВСведенияОФайлах(ВыборкаДетальныеЗаписи.Файл);
КонецЦикла;
КонецПроцедуры
Процедура ДописатьХешВСведенияОФайлах(Файл)
Попытка
Запись = РегистрыСведений.СведенияОФайлах.СоздатьМенеджерЗаписи();
Запись.Файл = Файл;
Запись.Прочитать();
Если Запись.Выбран() Тогда
//Запись.Пл_КонтрольнаяСумма = Пл_КонтрольнаяСумма;
Запись.Записать();
КонецЕсли;
Исключение
Сообщить("Проблема в файле " + Файл);
КонецПопытки;
КонецПроцедуры
После этого обработкой удаляем ненужные файлы и меняем ссылки в справочнике на эксклюзивные файлы. Обработку тоже прилагаю
Процедура УдалитьОдинаковыеФайлы() Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ СведенияОФайлах.Файл) КАК Файл,
| СведенияОФайлах.Пл_КонтрольнаяСумма КАК Пл_КонтрольнаяСумма
|ПОМЕСТИТЬ ВТ_СведенияОФайлах
|ИЗ
| РегистрСведений.СведенияОФайлах КАК СведенияОФайлах
|ГДЕ
| СведенияОФайлах.Пл_КонтрольнаяСумма <> """"
|
|СГРУППИРОВАТЬ ПО
| СведенияОФайлах.Пл_КонтрольнаяСумма
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| СведенияОФайлах.Файл КАК Файл,
| СведенияОФайлах.Размер КАК Размер,
| ВТ_СведенияОФайлах.Файл КАК КоличетвоОдинаковыхФайлов,
| ВТ_СведенияОФайлах.Пл_КонтрольнаяСумма КАК Пл_КонтрольнаяСумма,
| СведенияОФайлах.ВладелецФайла КАК ВладелецФайла,
| СведенияОФайлах.Файл.ПутьКФайлу КАК ФайлПутьКФайлу,
| СведенияОФайлах.Файл.Том.ПолныйПутьWindows КАК ФайлТомПолныйПутьWindows,
| СведенияОФайлах.Файл.Том КАК ФайлТом
|ИЗ
| ВТ_СведенияОФайлах КАК ВТ_СведенияОФайлах
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СведенияОФайлах КАК СведенияОФайлах
| ПО ВТ_СведенияОФайлах.Пл_КонтрольнаяСумма = СведенияОФайлах.Пл_КонтрольнаяСумма
|ГДЕ
| ВТ_СведенияОФайлах.Файл > 1
|ИТОГИ ПО
| Пл_КонтрольнаяСумма";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаПл_КонтрольнаяСумма = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаПл_КонтрольнаяСумма.Следующий() Цикл
// Вставить обработку выборки ВыборкаПл_КонтрольнаяСумма
ВыборкаДетальныеЗаписи = ВыборкаПл_КонтрольнаяСумма.Выбрать();
ПервыйФайл = Истина;
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
Если ПервыйФайл Тогда
ГлавноеИмяФайла = ПолучитьИмяФайлаПоВыборке(ВыборкаДетальныеЗаписи);
ГлавныйТом = ВыборкаДетальныеЗаписи.ФайлТом;
ГлавныйПутьКФайлу = ВыборкаДетальныеЗаписи.ФайлПутьКФайлу;
Иначе
ИмяТекущегоФайла = ПолучитьИмяФайлаПоВыборке(ВыборкаДетальныеЗаписи);
Если ГлавноеИмяФайла <> ИмяТекущегоФайла Тогда
ВыбФайл = Новый Файл(ИмяТекущегоФайла);
Если ВыбФайл.Существует() Тогда
Если ВыбФайл.ПолучитьТолькоЧтение() Тогда
ВыбФайл.УстановитьТолькоЧтение(Ложь);
КонецЕсли;
УдалитьФайлы(ИмяТекущегоФайла);
КонецЕсли;
// УдалитьФайлы(ИмяТекущегоФайла);
ПереписатьСправочникПрисоединенныхФайлов(ВыборкаДетальныеЗаписи.Файл,ГлавныйТом,ГлавныйПутьКФайлу);
КонецЕсли;
КонецЕсли;
ПервыйФайл = Ложь;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Процедура ПереписатьСправочникПрисоединенныхФайлов(Файл,ГлавныйТом,ГлавныйПутьКФайлу)
ОбъектФайл = Файл.ПолучитьОбъект();
ОбъектФайл.Том = ГлавныйТом;
ОбъектФайл.ПутьКФайлу = ГлавныйПутьКФайлу;
ОбъектФайл.Записать();
КонецПроцедуры
Функция ПолучитьИмяФайлаПоВыборке(ВыборкаДетальныеЗаписи)
Возврат ОбщегоНазначенияКлиентСервер.ПолучитьПолноеИмяФайла(ВыборкаДетальныеЗаписи.ФайлТомПолныйПутьWindows,ВыборкаДетальныеЗаписи.ФайлПутьКФайлу);
КонецФункции
Тестировалось на Бухгалтерии 3.0.113.17 и Управление торговлей, редакция 11 (11.4.10.75).