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