Почему не получается записать измененный объект в 1С и как избежать конфликта имен в управляемой форме

Программист 1С v8.3 (Управляемые формы) 1С:Управление нашей фирмой IT и автоматизация бизнеса
← На главную

При разработке в среде «1С:Предприятие 8» программисты часто сталкиваются со скрытыми особенностями контекста выполнения кода. Одной из типичных ошибок, которая ставит в тупик начинающих разработчиков, является невозможность перезаписи или изменения реквизитов справочника непосредственно из модуля управляемой формы. В этой статье мы подробно разберем, почему простая операция модификации данных выдает ошибку, как правильно работать с контекстом формы, почему нельзя использовать имя переменной Объект и какие опасности таит в себе необдуманное изменение ключевых реквизитов номенклатуры в конфигурации «1С:Управление нашей фирмой» (УНФ).

Разбор исходной ситуации: в чем суть проблемы

Рассмотрим алгоритм, который вызвал затруднения у автора темы. Разработчик пытается выбрать всю номенклатуру с помощью запроса и в цикле изменить ее тип и единицу измерения. Посмотрим на исходную процедуру:


&НаСервере
Процедура Единицы()
    Запрос = Новый Запрос();
    Запрос.Текст = "ВЫБРАТЬ
    |    Номен.Наименование,
    |    Номен.ТипНоменклатуры,
    |    Номен.Ссылка
    |    ИЗ Справочник.Номенклатура КАК Номен";
    Выборка = Запрос.Выполнить().Выбрать();
    ОбъектЕдиниц = Справочники.КлассификаторЕдиницИзмерения.НайтиПоКоду("2009");
    Пока Выборка.Следующий() Цикл
        Объект = Проверка(Выборка.Ссылка);
        Объект.ТипНоменклатуры = Перечисления.ТипыНоменклатуры.Услуга;
        Объект.ЕдиницаИзмерения = ОбъектЕдиниц;
        Объект.Записать();
    КонецЦикла;
КонецПроцедуры

В приведенном коде скрыты сразу несколько серьезных архитектурных и синтаксических ошибок. Из-за них платформа выбрасывает исключение и прекращает выполнение операции. Разберем каждую ошибку по шагам.

Проблема №1: Конфликт имен и область видимости переменной «Объект»

Самая распространенная ошибка при работе в модулях управляемых форм — это использование зарезервированного имени Объект для обозначения локальной переменной. Выясним причину, по которой это приводит к сбою.

В модуле любой управляемой формы (как на клиенте, так и на сервере) существует локальный контекст. Главным реквизитом большинства форм элементов справочников или документов является реквизит с предопределенным именем Объект. Этот реквизит связан со структурой данных формы (тип ДанныеФормыСтруктура) и представляет собой отображение редактируемого объекта базы данных на экране.

Свойство управляемой формы Объект доступно для записи только в рамках изменения его собственных дочерних свойств (например, Объект.Наименование = "Провод"). Однако перезаписать само свойство целиком, присвоив ему новый объект базы данных, платформа категорически запрещает. Когда внутри цикла разработчик пишет:


Объект = Проверка(Выборка.Ссылка);
// Или:
Объект = Выборка.Ссылка.ПолучитьОбъект();

система воспринимает это как попытку полностью переопределить системный реквизит формы новым значением. В этот момент генерируется ошибка: «Поле объекта недоступно для записи».

Решение проблемы: Для локальных переменных никогда нельзя использовать имена, совпадающие с реквизитами формы или свойствами глобального контекста. Переименуем нашу переменную, например, в НоменклатураОбъект или ТекущийОбъект. Это сразу устранит конфликт видимости.

Проблема №2: Различие между Ссылкой и Объектом в 1С

Проанализируем вторую важную ошибку в исходном коде. Автор пытается изменить свойства напрямую у результата выборки или функции, не переводя ссылку в состояние объекта. В платформе 1С существует жесткое разграничение типов данных:

  1. Ссылка (СправочникСсылка) — это неизменяемый уникальный идентификатор записи в базе данных. Ссылку можно получить из запроса, передать в качестве параметра или вывести на форму. Однако изменить значения реквизитов внутри ссылки физически невозможно. Код вида Ссылка.Наименование = "Новое" вызовет системную ошибку.
  2. Объект (СправочникОбъект) — это копия записи базы данных, загруженная в оперативную память для редактирования. Только объект обладает возможностью изменения своих реквизитов и имеет встроенный метод Записать().

Чтобы внести изменения в базу данных, нам необходимо получить объект из имеющейся ссылки с помощью метода ПолучитьОбъект(). Посмотрим, как это выглядит на практике:


// Ссылка получена из выборки запроса
СсылкаНаНоменклатуру = Выборка.Ссылка;

// Получаем изменяемый объект базы данных
НоменклатураОбъект = СсылкаНаНоменклатуру.ПолучитьОбъект();

// Теперь мы можем свободно модифицировать реквизиты объекта
НоменклатураОбъект.ЕдиницаИзмерения = ОбъектЕдиниц;
НоменклатураОбъект.Записать();

Проблема №3: Опасность изменения типа номенклатуры в «1С:УНФ»

Теперь обратим внимание на критическую логическую ошибку, связанную со спецификой конфигурации «1С:Управление нашей фирмой». Попытка массово изменить реквизит ТипНоменклатуры на значение Услуга для всей существующей номенклатуры в работающей базе данных — это чрезвычайно опасная операция.

Разберем, к каким последствиям это может привести:

Перед изменением подобных реквизитов необходимо обязательно выполнять проверку на наличие движений по регистрам накопления у данной ссылки.

Проблема №4: Производительность управляемых форм и транзакционность

Выполнение тяжелых транзакционных циклов непосредственно на сервере управляемой формы (директива &НаСервере) считается плохим тоном разработки. Причина кроется в передаче контекста.

При контекстном вызове сервера платформа упаковывает все данные формы (все ее реквизиты, элементы, таблицы) в пакет, отправляет его на сервер, выполняет процедуру и передает весь этот объем данных обратно на клиент. Если в базе содержатся тысячи позиций номенклатуры, цикл модификации данных заставит форму «зависнуть», а клиентское приложение может аварийно завершиться по таймауту.

Для оптимизации подобных задач всегда следует использовать директиву &НаСервереБезКонтекста. В такую процедуру передаются только те параметры, которые явно указаны разработчиком. Сама форма при этом не сериализуется, что экономит ресурсы сервера и ускоряет работу алгоритма в разы.

Проектируем правильное и безопасное решение

Объединим все наши выводы и напишем чистый, производительный и безопасный код для изменения данных номенклатуры. Мы учтем бесконтекстный вызов, переименуем конфликтующие переменные, добавим транзакционность для ускорения записи и внедрим базовую проверку на наличие движений.

Рассмотрим пример готовой реализации процедуры:


&НаКлиенте
Процедура Кнопка(Команда)
    // Запускаем процесс обработки данных на сервере без передачи контекста формы
    ВыполнитьОбновлениеЕдиницИзмерения();
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ВыполнитьОбновлениеЕдиницИзмерения()
    Запрос = Новый Запрос();
    Запрос.Текст = "ВЫБРАТЬ
    |    Номен.Ссылка КАК Ссылка
    |    ИЗ Справочник.Номенклатура КАК Номен";
    
    Выборка = Запрос.Выполнить().Выбрать();
    ОбъектЕдиниц = Справочники.КлассификаторЕдиницИзмерения.НайтиПоКоду("2009");
    
    // Открываем транзакцию, чтобы ускорить процесс записи в базу данных
    НачатьТранзакцию();
    Попытка
        Пока Выборка.Следующий() Цикл
            // Используем корректное имя переменной во избежание конфликта имен
            НоменклатураОбъект = Выборка.Ссылка.ПолучитьОбъект();
            
            // Защищаем базу от нарушения партионного учета
            Если НоменклатураИмеетДвижения(Выборка.Ссылка) Тогда
                // Если движения есть, менять тип номенклатуры на "Услуга" запрещено
                ЗаписьЖурналаРегистрации("ОбновлениеНоменклатуры",
                    УровеньЖурналаРегистрации.Предупреждение,, Выборка.Ссылка,
                    "Пропущено изменение типа для позиции: " + НоменклатураОбъект.Наименование);
            Иначе
                НоменклатураОбъект.ТипНоменклатуры = Перечисления.ТипыНоменклатуры.Услуга;
            КонецЕсли;
            
            // Устанавливаем единицу измерения и записываем объект
            НоменклатураОбъект.ЕдиницаИзмерения = ОбъектЕдиниц;
            НоменклатураОбъект.Записать();
        КонецЦикла;
        
        // Фиксируем все изменения в базе данных одним пакетом
        ЗафиксироватьТранзакцию();
        Сообщить("Обработка номенклатуры успешно завершена!");
    Исключение
        // В случае любой ошибки откатываем изменения, чтобы не повредить базу
        ОтменитьТранзакцию();
        ЗаписьЖурналаРегистрации("ОбновлениеНоменклатуры",
            УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        ВызватьИсключение "Не удалось обновить номенклатуру. Подробности записаны в журнал регистрации.";
    КонецПопытки;
КонецПроцедуры

&НаСервереБезКонтекста
Функция НоменклатураИмеетДвижения(СсылкаНаНоменклатуру)
    // Функция проверяет, были ли проведены операции с данной номенклатурой
    Запрос = Новый Запрос();
    Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1
    |    Запасы.Номенклатура КАК Номенклатура
    |    ИЗ РегистрНакопления.Запасы КАК Запасы
    |    ГДЕ Запасы.Номенклатура = &Номенклатура";
    Запрос.УстановитьПараметр("Номенклатура", СсылкаНаНоменклатуру);
    
    Возврат НЕ Запрос.Выполнить().Пустой();
КонецФункции

Использование транзакций с помощью операторов НачатьТранзакцию() и ЗафиксироватьТранзакцию() позволяет объединить физические операции записи на диске в один общий пакет. Это увеличивает скорость выполнения обработки в несколько десятков раз и гарантирует, что при возникновении ошибки база вернется в исходное состояние без частичной или некорректной записи данных.

Таким образом, мы детально изучили принципы работы с переменными в контексте управляемых форм, разобрали разницу между ссылочными и объектными типами данных, а также научились предотвращать повреждение бизнес-логики учета в конфигурациях линейки «1С:Управление нашей фирмой».

← На главную