Как запустить функцию модуля объекта для элемента справочника по его ссылке в 1С?

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

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

Анализируем исходную проблему

Представим ситуацию, когда у нас есть ссылка на элемент справочника (например, ЭлементНоменклатурыСсылка), и мы хотим запустить некую функцию, которая должна обработать этот элемент. Автор изначально пытался сделать это так:


&НаКлиенте
Процедура ЗагрузитьДанныеВБазу(Команда)
    Артикул = "буквыцыфры";
    // Создаем элемент справочника Номенклатура на сервере
    ЭлементНоменклатурыСсылка = СоздатьЭлементСправочникаНоменклатура(Артикул);
    // Что написать вместо этой строчки чтобы работало???
    ЭлементНоменклатурыСсылка.ПолучитьОбъект.ВидНоменклатурыПриИзмененииНастройкаФормы;
КонецПроцедуры

В этой строчке содержится несколько принципиальных ошибок, которые мы сейчас подробно рассмотрим и исправим. Основная проблема в том, что:

  1. Код выполняется на клиенте (&НаКлиенте).
  2. Метод ПолучитьОбъект() вызывается на клиенте, что недопустимо.
  3. Автор пытается вызвать обработчик события формы (ВидНоменклатурыПриИзмененииНастройкаФормы), который предназначен для работы с элементами формы и не должен вызываться напрямую из модуля объекта.
  4. Некорректно возвращается ссылка из функции СоздатьЭлементСправочникаНоменклатура.

Ключевые концепции: Ссылка, Объект и Клиент-Серверное взаимодействие

1. Различие между Ссылкой и Объектом

В 1С важно понимать разницу между ссылкой и объектом справочника (или любого другого ссылочного типа).

Чтобы из ссылки получить объект, используется метод ПолучитьОбъект(). Он доступен только в серверном контексте. Если же вы попытаетесь вызвать ПолучитьОбъект() на клиенте, система выдаст ошибку, как и было отмечено в обсуждении.

2. Клиент-Серверное взаимодействие

Платформа 1С:Предприятие 8.3 работает по клиент-серверной архитектуре. Это означает, что код может выполняться либо на клиенте (в браузере, тонком или толстом клиенте), либо на сервере 1С.

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

3. Экспортные функции и процедуры

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


// Пример экспортной процедуры в модуле объекта справочника "Номенклатура"
Процедура ОбработатьИзменениеВидаНоменклатуры() Экспорт
    // Здесь размещается основная бизнес-логика,
    // которая не зависит от элементов формы
    // и работает с данными текущего объекта.
    // Например:
    // Если ЭтотОбъект.ВидНоменклатуры = Справочники.ВидыНоменклатуры.НайтиПоНаименованию("Услуга") Тогда
    //     ЭтотОбъект.ЕдиницаИзмерения = Справочники.ЕдиницыИзмерения.НайтиПоНаименованию("шт");
    // Иначе
    //     ЭтотОбъект.ЕдиницаИзмерения = Справочники.ЕдиницыИзмерения.НайтиПоНаименованию("компл");
    // КонецЕсли;
    Сообщить("Выполнена обработка изменения вида номенклатуры для элемента " + ЭтотОбъект.Наименование);
КонецПроцедуры

Без ключевого слова Экспорт процедура или функция будет "невидима" извне модуля объекта.

4. Отделяем бизнес-логику от логики форм

Автор изначально пытался вызвать функцию ВидНоменклатурыПриИзмененииНастройкаФормы. Название указывает на то, что это, скорее всего, обработчик события формы ПриИзменении для поля ВидНоменклатуры. Такие обработчики обычно содержат код, который напрямую взаимодействует с элементами формы (например, делает видимыми или невидимыми другие поля, меняет их доступность и т.д.).

Крайне важно: Не следует вызывать обработчики событий форм напрямую из модуля объекта или из серверного кода, который не связан с формой. Логика, которая должна выполняться при изменении реквизита независимо от того, через форму это произошло или программно, должна быть вынесена в отдельную экспортную функцию или процедуру модуля объекта (как в примере выше ОбработатьИзменениеВидаНоменклатуры) или в общий модуль. А уже из обработчика формы вызывать эту универсальную функцию, передавая ей объект.


// В модуле формы элемента справочника "Номенклатура"
&НаКлиенте
Процедура ВидНоменклатурыПриИзменении(Элемент)
    // Клиентский код, если необходимо (например, отобразить сообщение)
    // ...

    // Вызываем серверную процедуру для выполнения основной логики
    ОбработатьИзменениеВидаНоменклатурыНаСервере();
КонецПроцедуры

&НаСервере
Процедура ОбработатьИзменениеВидаНоменклатурыНаСервере()
    // Здесь получаем объект и вызываем нашу универсальную процедуру из модуля объекта
    Если Объект.Модифицирован() Тогда // Если объект уже изменен на форме, то он в текущем контексте актуален
        Объект.ОбработатьИзменениеВидаНоменклатуры(); // Вызываем экспортную процедуру модуля объекта
        // Если процедура изменила данные объекта, нужно его записать.
        // Но обычно в обработчиках формы запись происходит позже, при "Записать" формы.
        // Если это отдельный программный вызов, то запись нужна.
    КонецЕсли;
КонецПроцедуры

Пошаговое решение: Как запустить функцию модуля объекта по ссылке

Теперь, когда мы разобрались с основными принципами, давайте соберем все воедино и рассмотрим правильный алгоритм действий.

Шаг 1: Подготавливаем функцию в модуле объекта

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


// В модуле объекта справочника "Номенклатура" (например, в Объекты.МодульОбъекта)
Процедура ОбработатьНоменклатурнуюПозицию() Экспорт
    // Здесь размещаем всю необходимую логику обработки для *этого* объекта.
    // Например, расчет характеристик, заполнение дополнительных реквизитов,
    // или любая другая сложная логика, которую вы не хотите дублировать.
    Сообщить("Элемент '" + ЭтотОбъект.Наименование + "' с видом '" + ЭтотОбъект.ВидНоменклатуры + "' был обработан.", СтатусСообщения.Информация);
    // Если функция меняет данные объекта, необходимо их записать позже
    // или непосредственно здесь, если это уместно.
КонецПроцедуры

Шаг 2: Корректно создаем и возвращаем ссылку на новый элемент

В функции, которая создает новый элемент, важно правильно вернуть именно ссылку на созданный элемент, а не объект. В исходном коде автора была ошибка, когда возвращался результат НайтиПоРеквизиту, который сам по себе возвращает объект, если найден.


&НаСервере
Функция СоздатьЭлементСправочникаНоменклатура(Артикул)
    // Создаём новый элемент справочника номенклатура
    НоменклатураЭлемент = Справочники.Номенклатура.СоздатьЭлемент();

    // Заполняем реквизиты нового элемента
    НоменклатураЭлемент.Наименование = "Новая номенклатура " + Артикул;
    НоменклатураЭлемент.Артикул    = Артикул;
    // ... другие реквизиты ...

    // ВОТ ТУТ МЫ МОЖЕМ СРАЗУ ВЫЗВАТЬ НУЖНУЮ ПРОЦЕДУРУ МОДУЛЯ ОБЪЕКТА
    // для только что созданного, но еще не записанного элемента.
    // Это удобно, если логика обработки должна применяться сразу при создании.
    НоменклатураЭлемент.ОбработатьНоменклатурнуюПозицию();

    // записываем элемент в базу данных
    НоменклатураЭлемент.Записать();

    // возвращаем ССЫЛКУ на только что созданный элемент
    Возврат НоменклатураЭлемент.Ссылка;
КонецФункции

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

Шаг 3: Вызываем экспортную функцию модуля объекта по ссылке (на сервере)

Теперь, когда у нас есть ссылка на элемент (полученная, например, из функции СоздатьЭлементСправочникаНоменклатура или из другого источника), мы можем вызвать для него экспортную процедуру модуля объекта. Для этого всегда понадобится серверная процедура.


&НаСервере
Процедура ЗапуститьОбработкуНоменклатурыПоСсылкеНаСервере(СсылкаНаЭлементНоменклатуры)
    Если СсылкаНаЭлементНоменклатуры.Пустая() Тогда
        Сообщить("Передана пустая ссылка на элемент номенклатуры.", СтатусСообщения.Внимание);
        Возврат;
    КонецЕсли;

    Попытка
        // 1. Получаем объект из ссылки. Это возможно только на сервере.
        НоменклатураОбъект = СсылкаНаЭлементНоменклатуры.ПолучитьОбъект();

        Если НоменклатураОбъект <> Неопределено Тогда
            // 2. Вызываем экспортную процедуру или функцию модуля объекта.
            НоменклатураОбъект.ОбработатьНоменклатурнуюПозицию();

            // 3. Если процедура изменила данные объекта, его необходимо записать.
            // Если процедура просто что-то информирует или читает, запись не нужна.
            НоменклатураОбъект.Записать();
            Сообщить("Обработка элемента '" + НоменклатураОбъект.Наименование + "' успешно завершена.", СтатусСообщения.Информация);
        Иначе
            Сообщить("Не удалось получить объект по ссылке: " + СсылкаНаЭлементНоменклатуры, СтатусСообщения.Ошибка);
        КонецЕсли;
    Исключение
        Сообщить("Ошибка при вызове функции модуля объекта для номенклатуры: " + ОписаниеОшибки(), СтатусСообщения.Ошибка);
    КонецПопытки;
КонецПроцедуры

Шаг 4: Вызов серверной процедуры из клиентского кода

Возвращаясь к исходному коду автора, теперь мы можем правильно вызвать нашу серверную логику из клиентской процедуры.


&НаКлиенте
Процедура ЗагрузитьДанныеВБазу(Команда)
    Артикул = "буквыцыфры";

    // Создаем элемент справочника Номенклатура на сервере и получаем его ссылку
    ЭлементНоменклатурыСсылка = СоздатьЭлементСправочникаНоменклатура(Артикул);

    // Теперь, используя полученную ссылку, запускаем нашу серверную процедуру
    // для дополнительной обработки элемента, если это необходимо отдельно от создания.
    ЗапуститьОбработкуНоменклатурыПоСсылкеНаСервере(ЭлементНоменклатурыСсылка);
КонецПроцедуры

В этом примере мы сначала создаем элемент, а затем вызываем для него отдельную обработку. Однако, как мы видели в Шаге 2, обработку можно включить и непосредственно в процесс создания элемента, если это логически оправдано.

Особенности использования обработчиков событий формы

Мы выяснили, что функцию типа ВидНоменклатурыПриИзмененииНастройкаФормы, которая является обработчиком события формы, нельзя напрямую вызывать из модуля объекта. Давайте рассмотрим, как правильно поступить с логикой, которая изначально была в таком обработчике.

  1. Выделите чистую бизнес-логику: Из обработчика формы ВидНоменклатурыПриИзменении выделите ту часть кода, которая не зависит от элементов формы и манипулирует только данными объекта.
  2. Создайте экспортную процедуру в модуле объекта: Поместите эту бизнес-логику в новую экспортную процедуру модуля объекта (например, ОбработатьИзменениеВидаНоменклатуры, как мы предложили ранее).
  3. Из обработчика формы вызовите серверный метод: В обработчике формы (ВидНоменклатурыПриИзменении) вызовите серверную процедуру, которая, в свою очередь, получит текущий объект формы и вызовет экспортную процедуру модуля объекта.

// В модуле объекта справочника "Номенклатура"
Процедура ОбработатьИзменениеВидаНоменклатурыНаОбъекте() Экспорт
    // Эта процедура работает с текущим объектом "ЭтотОбъект"
    Если ЭтотОбъект.ВидНоменклатуры = Справочники.ВидыНоменклатуры.НайтиПоНаименованию("Услуга") Тогда
        ЭтотОбъект.ЕдиницаИзмерения = Справочники.ЕдиницыИзмерения.НайтиПоНаименованию("Услуга"); // Пример
    Иначе
        ЭтотОбъект.ЕдиницаИзмерения = Справочники.ЕдиницыИзмерения.НайтиПоНаименованию("шт"); // Пример
    КонецЕсли;
    Сообщить("Бизнес-логика 'ПриИзмененииВидаНоменклатуры' выполнена для: " + ЭтотОбъект.Наименование, СтатусСообщения.Информация);
    // Запись объекта здесь не обязательна, если он будет записан через форму позже.
    // Если же это вызов из другого программного места, то после этой логики может потребоваться запись.
КонецПроцедуры

// В модуле формы элемента справочника "Номенклатура"
&НаКлиенте
Процедура ВидНоменклатурыПриИзменении(Элемент)
    // Здесь может быть клиентская логика, например,
    // изменение видимости других элементов формы
    // ...

    // Вызываем серверную процедуру, которая уже обратится к модулю объекта
    ВызватьОбработкуИзмененияВидаНоменклатурыНаСервере();
КонецПроцедуры

&НаСервере
Процедура ВызватьОбработкуИзмененияВидаНоменклатурыНаСервере()
    // Объект формы (Объект) уже является СправочникОбъект
    // и представляет текущее состояние элемента на форме.
    Объект.ОбработатьИзменениеВидаНоменклатурыНаОбъекте();
    // Если требуется немедленная запись после выполнения этой логики,
    // и это не предусмотрено общей логикой формы, можно вызвать:
    // Объект.Записать();
КонецПроцедуры

Таким образом, мы добиваемся многократного использования кода и четкого разделения ответственности: форма отвечает за UI, а модуль объекта – за бизнес-логику объекта.

В итоге: основные правила и рекомендации

  1. Всегда работайте с объектами (СправочникОбъект) для вызова методов модуля объекта и изменения реквизитов, а не со ссылками (СправочникСсылка).
  2. Переход от ссылки к объекту (ПолучитьОбъект()) должен происходить только на сервере.
  3. Функции и процедуры в модуле объекта, которые вы хотите вызывать извне, должны быть объявлены с ключевым словом Экспорт.
  4. Если вы находитесь на клиенте и вам нужно вызвать серверный код, используйте директиву &НаСервере для процедур/функций.
  5. Не вызывайте обработчики событий форм (такие как ПриИзменении) напрямую из модуля объекта. Выносите основную бизнес-логику в отдельные экспортные функции модуля объекта, а из обработчиков форм уже вызывайте эти функции через серверный контекст.
  6. После любых изменений объекта через его модуль, если эти изменения должны быть сохранены в базе данных, не забудьте вызвать метод Записать() для этого объекта.

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

← На главную