При разработке внешних обработок для групповой корректировки данных часто возникает задача массовой замены одной номенклатурной позиции на другую в существующих документах. Начинающие разработчики нередко сталкиваются с ошибкой времени выполнения: «Поле объекта недоступно для записи (Номенклатура)». Разберем причины возникновения этой ошибки, выясним, как правильно работать с объектной моделью 1С, и рассмотрим комплексный алгоритм безопасной замены номенклатуры.
Проанализируем исходный фрагмент кода, вызвавший исключение:
&НаСервере
Процедура ЗаменитьНаСервере(ДокументСсылка, НоменклатураДляЗамены, НоваяНоменклатура)
Тов = ДокументСсылка.Товары;
Для Каждого Стр Из Тов Цикл
Если Стр.Номенклатура = НоменклатураДляЗамены Тогда
Стр.Номенклатура = НоваяНоменклатура; // Ошибка: Поле объекта недоступно для записи
КонецЕсли;
КонецЦикла;
ДокументСсылка.Записать(РежимЗаписиДокумента.Проведение);
КонецПроцедуры
Выясним причину ошибки: переменная ДокументСсылка содержит значение ссылочного типа (например, ДокументСсылка.РеализацияТоваровУслуг). Ссылочные типы данных в платформе 1С:Предприятие предназначены исключительно для чтения и навигации по данным информационной базы. Через ссылку можно прочитать реквизиты документа или строки его табличной части, но любые попытки изменить значения полей или вызвать метод записи приведут к ошибке.
Для изменения данных документа необходимо получить его объектную форму с помощью метода ПолучитьОбъект(). Только тип ДокументОбъект предоставляет доступ к модификации реквизитов, табличных частей и позволяет сохранять изменения в базе данных.
Рассмотрим базовый исправленный вариант процедуры, в котором выполняется получение объекта, обход табличной части и последующая запись:
&НаСервере
Процедура ЗаменитьНаСервере(ДокументСсылка, НоменклатураДляЗамены, НоваяНоменклатура)
// Получаем объект для редактирования
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
Если ДокументОбъект = Неопределено Тогда
Возврат; // Документ мог быть удален другим пользователем
КонецЕсли;
БылИзменен = Ложь;
Для Каждого Стр Из ДокументОбъект.Товары Цикл
Если Стр.Номенклатура = НоменклатураДляЗамены Тогда
Стр.Номенклатура = НоваяНоменклатура;
БылИзменен = Истина;
КонецЕсли;
КонецЦикла;
// Записываем документ только в том случае, если были внесены изменения
Если БылИзменен Тогда
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
КонецЕсли;
КонецПроцедуры
Простая замена ссылки на номенклатуру в строке табличной части решает только синтаксическую задачу. В реальных конфигурациях (1С:ERP, 1С:Комплексная автоматизация, 1С:Управление торговлей, 1С:Бухгалтерия) номенклатура связана с множеством зависимых реквизитов. Разберем важные моменты, на которые следует обратить внимание:
ЕдиницаИзмерения (или Коэффициент), итоговое количественное выражение в складских отчетах станет некорректным.Характеристика и Серия необходимо очищать или сопоставлять заново.ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение) система выполняет стандартные проверки, включая контроль остатков на складе. Если новой номенклатуры недостаточно на остатке на дату документа, проведение завершится ошибкой.Посмотрим на расширенный пример пакетной обработки, который учитывает дату запрета изменения данных, ошибки блокировок и позволяет изолировать сбой в одном документе от всего процесса:
&НаСервере
Процедура ЗаменитьНоменклатуруВМассивеДокументов(МассивСсылок, НоменклатураДляЗамены, НоваяНоменклатура)
Для Каждого Ссылка Из МассивСсылок Цикл
Попытка
ДокументОбъект = Ссылка.ПолучитьОбъект();
Если ДокументОбъект = Неопределено Тогда
Продолжить;
КонецЕсли;
Модифицирован = Ложь;
Для Каждого СтрокаТЧ Из ДокументОбъект.Товары Цикл
Если СтрокаТЧ.Номенклатура = НоменклатураДляЗамены Тогда
СтрокаТЧ.Номенклатура = НоваяНоменклатура;
// При необходимости обновляем единицу измерения из новой номенклатуры
Если ДокументОбъект.Товары.Колонки.Найти("ЕдиницаИзмерения") <> Неопределено Тогда
СтрокаТЧ.ЕдиницаИзмерения = НоваяНоменклатура.ЕдиницаИзмерения;
КонецЕсли;
Модифицирован = Истина;
КонецЕсли;
КонецЦикла;
Если Модифицирован Тогда
// Если документ уже был проведен, проводим заново, иначе просто сохраняем
Если ДокументОбъект.Проведен Тогда
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Иначе
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
КонецЕсли;
Исключение
// Фиксируем ошибку и продолжаем обработку остальных документов
ТекстОшибки = СтрШаблон("Ошибка при обработке документа %1: %2",
Строка(Ссылка), ОписаниеОшибки());
ЗаписьЖурналаРегистрации("ЗаменаНоменклатуры", УровеньЖурналаРегистрации.Ошибка,,, ТекстОшибки);
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Если нет необходимости писать собственный алгоритм с уникальной логикой заполнения реквизитов, рекомендуется использовать стандартные средства «Библиотеки стандартных подсистем» (БСП):