В процессе разработки на платформе 1С:Предприятие часто возникает необходимость взаимодействия с объектами базы данных, такими как элементы справочников, напрямую из кода. Одна из типичных задач – это запуск определенной функции или процедуры, которая реализована в модуле объекта справочника, имея лишь ссылку на этот элемент. Разберем подробно, как это сделать, учитывая особенности клиент-серверного взаимодействия и правила работы с объектами в 1С.
Представим ситуацию, когда у нас есть ссылка на элемент справочника (например, ЭлементНоменклатурыСсылка), и мы хотим запустить некую функцию, которая должна обработать этот элемент. Автор изначально пытался сделать это так:
&НаКлиенте
Процедура ЗагрузитьДанныеВБазу(Команда)
Артикул = "буквыцыфры";
// Создаем элемент справочника Номенклатура на сервере
ЭлементНоменклатурыСсылка = СоздатьЭлементСправочникаНоменклатура(Артикул);
// Что написать вместо этой строчки чтобы работало???
ЭлементНоменклатурыСсылка.ПолучитьОбъект.ВидНоменклатурыПриИзмененииНастройкаФормы;
КонецПроцедуры
В этой строчке содержится несколько принципиальных ошибок, которые мы сейчас подробно рассмотрим и исправим. Основная проблема в том, что:
&НаКлиенте).ПолучитьОбъект() вызывается на клиенте, что недопустимо.ВидНоменклатурыПриИзмененииНастройкаФормы), который предназначен для работы с элементами формы и не должен вызываться напрямую из модуля объекта.СоздатьЭлементСправочникаНоменклатура.В 1С важно понимать разницу между ссылкой и объектом справочника (или любого другого ссылочного типа).
СправочникСсылка.<ИмяСправочника>): Это легкий идентификатор элемента, который позволяет быстро получить доступ к нему в базе данных. Ссылка не содержит всех данных элемента и не позволяет напрямую изменять его реквизиты или вызывать методы модуля объекта. Она используется для навигации, хранения связей и получения самого объекта.СправочникОбъект.<ИмяСправочника>): Это непосредственное представление элемента в памяти программы. Объект содержит все данные элемента, и именно для него можно вызывать методы модуля объекта, а также изменять его реквизиты с последующей записью в базу данных.Чтобы из ссылки получить объект, используется метод ПолучитьОбъект(). Он доступен только в серверном контексте. Если же вы попытаетесь вызвать ПолучитьОбъект() на клиенте, система выдаст ошибку, как и было отмечено в обсуждении.
Платформа 1С:Предприятие 8.3 работает по клиент-серверной архитектуре. Это означает, что код может выполняться либо на клиенте (в браузере, тонком или толстом клиенте), либо на сервере 1С.
&НаКлиенте): Здесь выполняется код, который взаимодействует с пользовательским интерфейсом (формы, элементы управления), а также легкие операции, не требующие доступа к базе данных или ресурсам сервера.&НаСервере, &НаСервереБезКонтекста): Здесь выполняется код, который взаимодействует с базой данных, выполняет сложные вычисления, работает с объектами базы данных (такими как СправочникОбъект) и вызывает методы модулей объектов.Для вызова метода модуля объекта или получения объекта из ссылки всегда необходим серверный контекст. Если вы находитесь в клиентской процедуре, необходимо совершить переход на сервер, вызвав серверную функцию или процедуру.
Для того чтобы функция или процедура в модуле объекта была доступна для вызова извне (из других модулей, форм или серверных обработчиков), она должна быть объявлена как экспортная. Это делается с помощью ключевого слова Экспорт после объявления функции или процедуры.
// Пример экспортной процедуры в модуле объекта справочника "Номенклатура"
Процедура ОбработатьИзменениеВидаНоменклатуры() Экспорт
// Здесь размещается основная бизнес-логика,
// которая не зависит от элементов формы
// и работает с данными текущего объекта.
// Например:
// Если ЭтотОбъект.ВидНоменклатуры = Справочники.ВидыНоменклатуры.НайтиПоНаименованию("Услуга") Тогда
// ЭтотОбъект.ЕдиницаИзмерения = Справочники.ЕдиницыИзмерения.НайтиПоНаименованию("шт");
// Иначе
// ЭтотОбъект.ЕдиницаИзмерения = Справочники.ЕдиницыИзмерения.НайтиПоНаименованию("компл");
// КонецЕсли;
Сообщить("Выполнена обработка изменения вида номенклатуры для элемента " + ЭтотОбъект.Наименование);
КонецПроцедуры
Без ключевого слова Экспорт процедура или функция будет "невидима" извне модуля объекта.
Автор изначально пытался вызвать функцию ВидНоменклатурыПриИзмененииНастройкаФормы. Название указывает на то, что это, скорее всего, обработчик события формы ПриИзменении для поля ВидНоменклатуры. Такие обработчики обычно содержат код, который напрямую взаимодействует с элементами формы (например, делает видимыми или невидимыми другие поля, меняет их доступность и т.д.).
Крайне важно: Не следует вызывать обработчики событий форм напрямую из модуля объекта или из серверного кода, который не связан с формой. Логика, которая должна выполняться при изменении реквизита независимо от того, через форму это произошло или программно, должна быть вынесена в отдельную экспортную функцию или процедуру модуля объекта (как в примере выше ОбработатьИзменениеВидаНоменклатуры) или в общий модуль. А уже из обработчика формы вызывать эту универсальную функцию, передавая ей объект.
// В модуле формы элемента справочника "Номенклатура"
&НаКлиенте
Процедура ВидНоменклатурыПриИзменении(Элемент)
// Клиентский код, если необходимо (например, отобразить сообщение)
// ...
// Вызываем серверную процедуру для выполнения основной логики
ОбработатьИзменениеВидаНоменклатурыНаСервере();
КонецПроцедуры
&НаСервере
Процедура ОбработатьИзменениеВидаНоменклатурыНаСервере()
// Здесь получаем объект и вызываем нашу универсальную процедуру из модуля объекта
Если Объект.Модифицирован() Тогда // Если объект уже изменен на форме, то он в текущем контексте актуален
Объект.ОбработатьИзменениеВидаНоменклатуры(); // Вызываем экспортную процедуру модуля объекта
// Если процедура изменила данные объекта, нужно его записать.
// Но обычно в обработчиках формы запись происходит позже, при "Записать" формы.
// Если это отдельный программный вызов, то запись нужна.
КонецЕсли;
КонецПроцедуры
Теперь, когда мы разобрались с основными принципами, давайте соберем все воедино и рассмотрим правильный алгоритм действий.
Прежде всего, убедитесь, что функция или процедура, которую вы хотите вызвать из модуля объекта, является экспортной и содержит только ту бизнес-логику, которая не зависит от контекста формы.
// В модуле объекта справочника "Номенклатура" (например, в Объекты.МодульОбъекта)
Процедура ОбработатьНоменклатурнуюПозицию() Экспорт
// Здесь размещаем всю необходимую логику обработки для *этого* объекта.
// Например, расчет характеристик, заполнение дополнительных реквизитов,
// или любая другая сложная логика, которую вы не хотите дублировать.
Сообщить("Элемент '" + ЭтотОбъект.Наименование + "' с видом '" + ЭтотОбъект.ВидНоменклатуры + "' был обработан.", СтатусСообщения.Информация);
// Если функция меняет данные объекта, необходимо их записать позже
// или непосредственно здесь, если это уместно.
КонецПроцедуры
В функции, которая создает новый элемент, важно правильно вернуть именно ссылку на созданный элемент, а не объект. В исходном коде автора была ошибка, когда возвращался результат НайтиПоРеквизиту, который сам по себе возвращает объект, если найден.
&НаСервере
Функция СоздатьЭлементСправочникаНоменклатура(Артикул)
// Создаём новый элемент справочника номенклатура
НоменклатураЭлемент = Справочники.Номенклатура.СоздатьЭлемент();
// Заполняем реквизиты нового элемента
НоменклатураЭлемент.Наименование = "Новая номенклатура " + Артикул;
НоменклатураЭлемент.Артикул = Артикул;
// ... другие реквизиты ...
// ВОТ ТУТ МЫ МОЖЕМ СРАЗУ ВЫЗВАТЬ НУЖНУЮ ПРОЦЕДУРУ МОДУЛЯ ОБЪЕКТА
// для только что созданного, но еще не записанного элемента.
// Это удобно, если логика обработки должна применяться сразу при создании.
НоменклатураЭлемент.ОбработатьНоменклатурнуюПозицию();
// записываем элемент в базу данных
НоменклатураЭлемент.Записать();
// возвращаем ССЫЛКУ на только что созданный элемент
Возврат НоменклатураЭлемент.Ссылка;
КонецФункции
Обратите внимание, что мы вызвали НоменклатураЭлемент.ОбработатьНоменклатурнуюПозицию(); до записи. Это возможно и часто используется, если логика в модуле объекта не требует наличия элемента в базе данных (например, не использует ссылочные реквизиты, указывающие на сам элемент).
Теперь, когда у нас есть ссылка на элемент (полученная, например, из функции СоздатьЭлементСправочникаНоменклатура или из другого источника), мы можем вызвать для него экспортную процедуру модуля объекта. Для этого всегда понадобится серверная процедура.
&НаСервере
Процедура ЗапуститьОбработкуНоменклатурыПоСсылкеНаСервере(СсылкаНаЭлементНоменклатуры)
Если СсылкаНаЭлементНоменклатуры.Пустая() Тогда
Сообщить("Передана пустая ссылка на элемент номенклатуры.", СтатусСообщения.Внимание);
Возврат;
КонецЕсли;
Попытка
// 1. Получаем объект из ссылки. Это возможно только на сервере.
НоменклатураОбъект = СсылкаНаЭлементНоменклатуры.ПолучитьОбъект();
Если НоменклатураОбъект <> Неопределено Тогда
// 2. Вызываем экспортную процедуру или функцию модуля объекта.
НоменклатураОбъект.ОбработатьНоменклатурнуюПозицию();
// 3. Если процедура изменила данные объекта, его необходимо записать.
// Если процедура просто что-то информирует или читает, запись не нужна.
НоменклатураОбъект.Записать();
Сообщить("Обработка элемента '" + НоменклатураОбъект.Наименование + "' успешно завершена.", СтатусСообщения.Информация);
Иначе
Сообщить("Не удалось получить объект по ссылке: " + СсылкаНаЭлементНоменклатуры, СтатусСообщения.Ошибка);
КонецЕсли;
Исключение
Сообщить("Ошибка при вызове функции модуля объекта для номенклатуры: " + ОписаниеОшибки(), СтатусСообщения.Ошибка);
КонецПопытки;
КонецПроцедуры
Возвращаясь к исходному коду автора, теперь мы можем правильно вызвать нашу серверную логику из клиентской процедуры.
&НаКлиенте
Процедура ЗагрузитьДанныеВБазу(Команда)
Артикул = "буквыцыфры";
// Создаем элемент справочника Номенклатура на сервере и получаем его ссылку
ЭлементНоменклатурыСсылка = СоздатьЭлементСправочникаНоменклатура(Артикул);
// Теперь, используя полученную ссылку, запускаем нашу серверную процедуру
// для дополнительной обработки элемента, если это необходимо отдельно от создания.
ЗапуститьОбработкуНоменклатурыПоСсылкеНаСервере(ЭлементНоменклатурыСсылка);
КонецПроцедуры
В этом примере мы сначала создаем элемент, а затем вызываем для него отдельную обработку. Однако, как мы видели в Шаге 2, обработку можно включить и непосредственно в процесс создания элемента, если это логически оправдано.
Мы выяснили, что функцию типа ВидНоменклатурыПриИзмененииНастройкаФормы, которая является обработчиком события формы, нельзя напрямую вызывать из модуля объекта. Давайте рассмотрим, как правильно поступить с логикой, которая изначально была в таком обработчике.
ВидНоменклатурыПриИзменении выделите ту часть кода, которая не зависит от элементов формы и манипулирует только данными объекта.ОбработатьИзменениеВидаНоменклатуры, как мы предложили ранее).ВидНоменклатурыПриИзменении) вызовите серверную процедуру, которая, в свою очередь, получит текущий объект формы и вызовет экспортную процедуру модуля объекта.
// В модуле объекта справочника "Номенклатура"
Процедура ОбработатьИзменениеВидаНоменклатурыНаОбъекте() Экспорт
// Эта процедура работает с текущим объектом "ЭтотОбъект"
Если ЭтотОбъект.ВидНоменклатуры = Справочники.ВидыНоменклатуры.НайтиПоНаименованию("Услуга") Тогда
ЭтотОбъект.ЕдиницаИзмерения = Справочники.ЕдиницыИзмерения.НайтиПоНаименованию("Услуга"); // Пример
Иначе
ЭтотОбъект.ЕдиницаИзмерения = Справочники.ЕдиницыИзмерения.НайтиПоНаименованию("шт"); // Пример
КонецЕсли;
Сообщить("Бизнес-логика 'ПриИзмененииВидаНоменклатуры' выполнена для: " + ЭтотОбъект.Наименование, СтатусСообщения.Информация);
// Запись объекта здесь не обязательна, если он будет записан через форму позже.
// Если же это вызов из другого программного места, то после этой логики может потребоваться запись.
КонецПроцедуры
// В модуле формы элемента справочника "Номенклатура"
&НаКлиенте
Процедура ВидНоменклатурыПриИзменении(Элемент)
// Здесь может быть клиентская логика, например,
// изменение видимости других элементов формы
// ...
// Вызываем серверную процедуру, которая уже обратится к модулю объекта
ВызватьОбработкуИзмененияВидаНоменклатурыНаСервере();
КонецПроцедуры
&НаСервере
Процедура ВызватьОбработкуИзмененияВидаНоменклатурыНаСервере()
// Объект формы (Объект) уже является СправочникОбъект
// и представляет текущее состояние элемента на форме.
Объект.ОбработатьИзменениеВидаНоменклатурыНаОбъекте();
// Если требуется немедленная запись после выполнения этой логики,
// и это не предусмотрено общей логикой формы, можно вызвать:
// Объект.Записать();
КонецПроцедуры
Таким образом, мы добиваемся многократного использования кода и четкого разделения ответственности: форма отвечает за UI, а модуль объекта – за бизнес-логику объекта.
СправочникОбъект) для вызова методов модуля объекта и изменения реквизитов, а не со ссылками (СправочникСсылка).ПолучитьОбъект()) должен происходить только на сервере.Экспорт.&НаСервере для процедур/функций.ПриИзменении) напрямую из модуля объекта. Выносите основную бизнес-логику в отдельные экспортные функции модуля объекта, а из обработчиков форм уже вызывайте эти функции через серверный контекст.Записать() для этого объекта.Следуя этим рекомендациям, мы значительно повышаем надежность, поддерживаемость и масштабируемость вашего кода на платформе 1С:Предприятие.