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