При разработке и доработке конфигураций 1С с использованием расширений, мы часто сталкиваемся с необходимостью добавлять или изменять существующую функциональность. Это может быть вызов новых функций или процедур из уже имеющихся методов конфигурации. Однако, при работе с расширениями и взаимодействии клиентских и серверных контекстов, нередко возникают ошибки, которые на первый взгляд кажутся неочевидными. Рассмотрим подробнее одну из таких ситуаций: ошибку "Процедура или функция с указанным именем не определена" при создании собственной функции в расширении и попытке ее вызова.
Наш читатель столкнулся с проблемой при попытке добавить новую серверную функцию в расширение и вызвать ее из процедуры, перехваченной с помощью аннотации &Вместо. Давайте проанализируем исходный код и ситуацию, чтобы понять корневую причину и найти эффективное решение.
Представим, что у нас есть процедура в общем модуле типовой конфигурации, которую необходимо доработать. Для этого мы создаем расширение и перехватываем эту процедуру с помощью аннотации &Вместо. Внутри нашей расширяющей процедуры мы хотим вызвать новую вспомогательную функцию, которая должна выполняться на сервере.
Исходный код, который вызвал ошибку, выглядел следующим образом:
&Вместо("ПриИзмененииСумма")
Процедура Расш1_ПриИзмененииСумма(Форма, ИмяТаблицы, ЗначениеПустогоКоличества, ПрименяютсяСтавки4и2, СоответствиеРеквизитов)
Массив = ПолучитьМассивНаСервере();
// ... дальнейшая логика ...
КонецПроцедуры
Функция ПолучитьМассивНаСервере Экспорт
Массив = Новый Массив;
Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000001"));
Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000002293"));
Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000003489"));
Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000217"));
Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000002"));
Возврат Массив;
КонецФункции
При попытке обновить расширение система выдает ошибку:
Процедура или функция с указанным именем не определена (ПолучитьМассивНаСервере)
Автор справедливо предположил, что проблема может быть связана с директивами компиляции &НаКлиенте/&НаСервере. Однако, даже их добавление не привело к желаемому результату.
Для того чтобы разобраться в этой ошибке, давайте глубже погрузимся в принципы работы клиент-серверного взаимодействия в 1С:Предприятие 8.3 и особенности использования расширений и общих модулей.
&НаКлиенте: Процедура или функция выполняется строго на клиентской стороне. Из нее доступен весь контекст формы (реквизиты, элементы), но нет прямого доступа к базе данных или другим серверным объектам.&НаСервере: Процедура или функция выполняется на сервере. Ей доступен контекст формы, и она может обращаться к базе данных. Однако передача контекста формы между клиентом и сервером может быть ресурсоемкой.&НаСервереБезКонтекста: Эта директива также указывает на выполнение на сервере, но при этом контекст формы не передается. Это значительно оптимизирует взаимодействие, поскольку передаются только параметры функции, а возвращается только результат. Идеально подходит для вспомогательных функций, которым не нужен прямой доступ к элементам формы.&НаКлиентеНаСервереБезКонтекста: Используется для кода, который должен быть доступен и выполняться как на клиенте, так и на сервере, исключая дублирование кода.Важно: Прямой доступ к базе данных возможен только на серверной стороне. Если из клиентской процедуры требуется обратиться к данным или выполнить серверные операции, необходимо вызывать серверные процедуры или функции.
Общие модули в 1С имеют ряд флагов (свойств), которые определяют их контекст выполнения и доступность:
В нашем случае, процедура Расш1_ПриИзмененииСумма была добавлена в расширение, замещая процедуру из ОбщийМодуль.ОбработкаТабличныхЧастейКлиентСервер. Полезное сообщение 5 указывает, что исходный общий модуль имеет флаги Клиент, Сервер, ВнешнееСоединение. Это означает, что он может быть вызван как с клиента, так и с сервера, и его методы доступны в обоих контекстах.
Когда мы добавляем процедуру Расш1_ПриИзмененииСумма через &Вместо, она становится частью контекста этого общего модуля. Если сама процедура ПриИзмененииСумма в исходной конфигурации вызывается из клиентского контекста формы, то и наша расширяющая процедура Расш1_ПриИзмененииСумма будет выполняться в клиентском контексте. А функция ПолучитьМассивНаСервере, очевидно, должна выполняться на сервере, так как она обращается к справочникам.
Таким образом, возникает классическая проблема: из клиентского контекста пытаемся вызвать серверную функцию, которая не помечена как "Вызов сервера". Именно поэтому появляется ошибка "Процедура или функция с указанным именем не определена". Система просто не знает, как найти и вызвать эту серверную функцию из текущего клиентского окружения.
&ВместоАннотация &Вместо (или "вместо") является мощным инструментом расширений, позволяющим полностью заменить исходный метод конфигурации. Это означает, что при вызове оригинального метода будет выполнен только наш расширяющий метод.
Есть несколько важных нюансов при работе с &Вместо:
ПродолжитьВызов(), то оригинальный код никогда не будет выполнен.ПродолжитьВызов(): Эта функция позволяет вызвать исходный (расширяемый) метод конфигурации. После его выполнения управление возвращается в расширяющий метод. Это критически важно, если мы хотим не просто заменить, а дополнить или изменить поведение исходного метода.&Вместо, так как нам нужно перехватить возвращаемое значение.&Вместо без ПродолжитьВызов() сродни снятию метода с поддержки, поскольку любые изменения в исходном методе конфигурации не будут учтены вашим расширением.В нашем случае, само использование &Вместо не было причиной ошибки с определением функции, но важно понимать его механизм.
Давайте рассмотрим, какие варианты решения проблемы обсуждались и к какому результату это привело.
Сообщение 8 предлагает перенести код функции ПолучитьМассивНаСервере непосредственно в модуль формы, связанный с табличной частью, или в модуль документа. Это может быть рабочим решением, если процедура ПриИзмененииСумма вызывается из конкретной формы.
Расш1_ПриИзмененииСумма будет находиться в модуле формы и будет вызвана на клиенте, она может напрямую вызвать серверную функцию в том же модуле формы, используя директиву &НаСервере. Система автоматически обработает вызов сервера в рамках одного модуля формы.ПолучитьМассивНаСервере универсальна и должна использоваться в разных местах, перенос ее в модуль конкретной формы приведет к дублированию кода или ограничит ее повторное использование. Кроме того, ПриИзмененииСумма из общего модуля может вызываться из разных форм или даже в серверном контексте, что усложнит универсальное решение.Самым эффективным и чистым решением, к которому пришли в ходе обсуждения (сообщения 9, 10, 11), стало создание нового общего модуля в расширении специально для функции ПолучитьМассивНаСервере.
Создаем новый общий модуль в расширении:
В конфигураторе, в дереве объектов расширения, находим ветку "Общие" -> "Общие модули". Добавляем новый общий модуль, например, Расш1_ПомощникСервер. Имя модуля желательно снабдить префиксом расширения, чтобы избежать конфликтов и улучшить читаемость.
Настраиваем свойства нового общего модуля:
Это самый важный шаг. Для нашего нового модуля Расш1_ПомощникСервер необходимо установить следующие флаги (свойства):
ПолучитьМассивНаСервере должна выполняться на сервере.После установки этих флагов, наш модуль Расш1_ПомощникСервер будет доступен для вызова серверных функций из клиентского кода.
Переносим функцию в новый модуль:
Копируем нашу функцию ПолучитьМассивНаСервере в новый общий модуль Расш1_ПомощникСервер.
// В общем модуле расширения Расш1_ПомощникСервер
// Свойства: Сервер=Истина, ВызовСервера=Истина, Клиент=Ложь
Функция ПолучитьМассивНаСервере() Экспорт
Массив = Новый Массив;
Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000001"));
Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000002293"));
Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000003489"));
Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000217"));
Массив.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000000002"));
Возврат Массив;
КонецФункции
Изменяем вызов функции в расширяющей процедуре:
Теперь, когда функция находится в отдельном общем модуле, мы должны вызывать ее, указывая имя модуля.
&Вместо("ПриИзмененииСумма")
Процедура Расш1_ПриИзмененииСумма(Форма, ИмяТаблицы, ЗначениеПустогоКоличества, ПрименяютсяСтавки4и2, СоответствиеРеквизитов)
// Добавляем директиву, если процедура вызывается на клиенте,
// а ПолучитьМассивНаСервере находится в общем модуле с ВызовСервера
// Если Расш1_ПриИзмененииСумма сама по себе серверная, то &НаСервере не требуется
// Если процедура Расш1_ПриИзмененииСумма выполняется в клиентском контексте,
// а наш новый модуль Расш1_ПомощникСервер имеет флаг "Вызов сервера",
// то можно вызывать функцию напрямую, платформа сама организует вызов сервера.
Массив = Расш1_ПомощникСервер.ПолучитьМассивНаСервере();
// ... дальнейшая логика ...
КонецПроцедуры
После выполнения этих шагов, ошибка "Процедура или функция с указанным именем не определена" исчезнет, и расширение успешно обновится и будет работать.
Давайте еще раз проанализируем, почему именно такое решение оказалось правильным:
ПолучитьМассивНаСервере была помещена непосредственно рядом с процедурой Расш1_ПриИзмененииСумма, которая сама по себе находилась в контексте расширяемого общего модуля. Если этот расширяемый модуль (ОбщийМодуль.ОбработкаТабличныхЧастейКлиентСервер) вызывается из клиентского контекста, то и Расш1_ПриИзмененииСумма начинает выполняться на клиенте. Клиентский код не имеет прямого доступа к серверным функциям, если они не находятся в модуле с флагом "Вызов сервера".Расш1_ПомощникСервер в расширении и устанавливаем для него флаг "Вызов сервера", мы явно сообщаем платформе 1С, что экспортные функции этого модуля (такие как ПолучитьМассивНаСервере) могут быть безопасно вызваны из клиентского контекста. Платформа самостоятельно организует необходимые механизмы для удаленного вызова серверной функции, передаст параметры и получит результат.Для более эффективной и надежной работы с расширениями и модулями в 1С, рассмотрим несколько дополнительных рекомендаций:
Всегда используйте префикс из свойства "Префикс имен расширения" для всех создаваемых объектов, методов и переменных в расширении. Это помогает избежать конфликтов имен с объектами типовой конфигурации и другими расширениями, а также улучшает наглядность кода.
ПродолжитьВызов()При использовании &Вместо всегда задавайтесь вопросом: "Нужно ли мне выполнить исходный код конфигурации?" Если ответ "да", то обязательно используйте ПродолжитьВызов(). Это сохранит работоспособность типового функционала и позволит вашему расширению правильно взаимодействовать с обновлениями конфигурации.
&Вместо("ИсходнаяПроцедура")
Процедура Расш1_ИсходнаяПроцедура(...)
// Наша дополнительная логика до вызова исходной процедуры
Сообщить("Выполняется код расширения до исходной процедуры");
ПродолжитьВызов(); // Вызываем исходную процедуру конфигурации
// Наша дополнительная логика после вызова исходной процедуры
Сообщить("Выполняется код расширения после исходной процедуры");
КонецПроцедуры
Если мы расширяем функцию, то результат ПродолжитьВызов() необходимо принять:
&Вместо("ИсходнаяФункция")
Функция Расш1_ИсходнаяФункция(...)
// Наша дополнительная логика до вызова исходной функции
Сообщить("Выполняется код расширения до исходной функции");
РезультатИсходнойФункции = ПродолжитьВызов(); // Вызываем исходную функцию конфигурации и получаем её результат
// Модифицируем результат или используем его
Если РезультатИсходнойФункции = Неопределено Тогда
Возврат "НашНовыйРезультат";
Иначе
Возврат РезультатИсходнойФункции;
КонецЕсли;
КонецФункции
В общем случае, старайтесь разделять общие модули на те, что предназначены только для клиента, и те, что предназначены только для сервера. Это улучшает архитектуру приложения, упрощает отладку и контроль контекстов выполнения. Модули, доступные для вызова с клиента на сервер, часто имеют постфикс "ВызовСервера" (например, ОбщегоНазначенияВызовСервера) в типовых конфигурациях.
Использование директив &НаСервере или &НаСервереБезКонтекста внутри модуля формы является исключением, так как модуль формы специально предназначен для смешанной логики.
Помимо некорректно установленных флагов общих модулей или директив компиляции, эта ошибка может возникать по другим причинам:
Экспорт для функции или процедуры, которую мы хотим вызвать извне модуля.Внимательное отношение к контекстам выполнения и правильная настройка свойств общих модулей в расширениях позволяет избежать многих типичных ошибок и создавать надежные и производительные доработки для 1С:Предприятия.