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