Расширение и основная конфигурация 1С: кто кого вызывает и как это сделать без ошибок
Содержание
Процедуру из общего модуля расширения вызывают так же, как в обычной конфигурации: ИмяМодуля.ИмяПроцедуры(), если у метода стоит Экспорт, а у модуля включены нужные флаги контекста (Сервер, Вызов сервера, Клиент). Чтобы встроить свою процедуру в типовой код, её не вызывают «откуда-то из конфигурации», а вешают на метод основной конфигурации аннотацией &Перед, &После или &Вместо.
Частое заблуждение — что основная конфигурация «видит» расширение и может напрямую обратиться к его модулю. Не может: конфигурация ничего не знает о расширениях. Связь идёт в обратную сторону. Расширение видит конфигурацию и встраивается в неё. Всё, что ниже, — про платформу 8.3 и одинаково работает в БП 3.0, ЗУП 3.1, УТ 11 и любых других конфигурациях.
Как устроена связь расширения и основной конфигурации
Расширение подключается к базе поверх основной конфигурации. В момент запуска сеанса платформа «склеивает» их: модули расширения дополняют модули конфигурации, а новые объекты расширения становятся доступны так же, как свои.
Отсюда правило направления вызовов:
- ✅ Расширение → конфигурация. Код расширения может вызывать экспортные методы общих модулей конфигурации напрямую:
ОбщегоНазначения.СообщитьПользователю(...). - ✅ Расширение → расширение. Свои общие модули расширения вызываются по имени, как обычно.
- ⚠️ Конфигурация → расширение. Прямой вызов «в лоб» — плохая идея: в Конфигураторе основной конфигурации такого модуля нет, проверка модулей ругается, а в базе без расширения код упадёт.
Ещё одно понятие — заимствование. Чтобы изменить поведение объекта конфигурации (модуль, форму, обработчик события), объект заимствуют в расширение: правой кнопкой по объекту в дереве конфигурации → Добавить в расширение. Заимствованный объект — это «ссылка» на оригинал, в которой можно перехватывать методы и добавлять свои.
У расширения есть свойство Префикс имён (по умолчанию Расширение1_). Начинайте с него имена всех новых объектов и методов. Тогда при обновлении типовой ваша процедура не столкнётся с одноимённой процедурой разработчика.
Вызов процедуры из общего модуля расширения
Самый простой случай: вам нужна своя процедура, которую будут вызывать разные места расширения.
- В дереве расширения откройте ветку
Общие → Общие модулии добавьте модуль, напримерРасш1_Интеграция. - В свойствах модуля отметьте контекст:
Сервердля серверного кода,Вызов сервера— если процедуру будут вызывать с клиента. - Напишите метод с ключевым словом
Экспорт. - Вызывайте его из любого модуля расширения по полному имени.
// Общий модуль расширения Расш1_Интеграция (флаги: Сервер)
Процедура ЗаписатьВЖурнал(Текст) Экспорт
ЗаписьЖурналаРегистрации("Расш1.Интеграция",
УровеньЖурналаРегистрации.Информация, , , Текст);
КонецПроцедуры
Вызов из модуля объекта, модуля менеджера или серверной процедуры формы расширения:
Расш1_Интеграция.ЗаписатьВЖурнал("Документ отправлен во внешнюю систему");
Без Экспорт метод виден только внутри своего модуля. При вызове снаружи получите Метод объекта не обнаружен (ЗаписатьВЖурнал).
Перехват методов конфигурации: &Перед, &После, &Вместо
Это главный способ «вызвать свою процедуру» в нужный момент работы типового кода. Вы не правите исходный метод, а говорите платформе: «перед ним (после него, вместо него) выполни мой».
Порядок действий:
- Заимствуйте в расширение общий модуль или объект, в модуле которого лежит нужный метод.
- Откройте модуль заимствованного объекта в расширении.
- Напишите свою процедуру с аннотацией и именем исходного метода в кавычках.
// Модуль объекта заимствованного документа РеализацияТоваровУслуг
&Перед("ПередЗаписью")
Процедура Расш1_ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если РежимЗаписи = РежимЗаписиДокумента.Проведение
И Не ЗначениеЗаполнено(Комментарий) Тогда
ОбщегоНазначения.СообщитьПользователю(
"Перед проведением заполните комментарий", ЭтотОбъект, "Комментарий", , Отказ);
КонецЕсли;
КонецПроцедуры
Параметры перехватчика повторяют параметры исходного метода — тот же порядок и количество.
| Аннотация | Когда срабатывает | Риск при обновлении типовой |
|---|---|---|
&Перед | до исходного метода | низкий |
&После | после исходного метода | низкий |
&Вместо | исходный метод не выполняется, пока вы не вызовете ПродолжитьВызов() | средний: можно «потерять» новую логику разработчика |
&ИзменениеИКонтроль | копия тела метода с вашими вставками | высокий, но платформа сама заметит, что оригинал изменился |
Для функций надёжнее всего &Вместо: вызываете ПродолжитьВызов(), берёте результат и меняете его.
&Вместо("ПредставлениеДокумента")
Функция Расш1_ПредставлениеДокумента(Документ)
Результат = ПродолжитьВызов(Документ);
Возврат Результат + " (проверен)";
КонецФункции
&ИзменениеИКонтроль появилась в платформе 8.3.11. Конфигуратор копирует тело метода, а ваши правки вы обрамляете #Вставка … #КонецВставки и #Удаление … #КонецУдаления. Если после обновления типовой текст оригинала изменится, расширение перестанет применяться — и вы узнаете об этом сразу, а не через месяц по жалобам пользователей.
&Вместо без ПродолжитьВызов() полностью отключает исходный метод. После очередного обновления разработчик может добавить туда важную логику, а у вас она молча не выполнится.
Свои обработчики событий форм и объектов
Событие — та же точка входа, только её вызывает платформа: открытие формы, запись, проведение, нажатие кнопки.
События модулей объектов
Работает схема из предыдущего раздела: аннотация с именем обработчика (ПередЗаписью, ОбработкаПроведения, ПриЗаписи). Внутри модуля объекта доступны его реквизиты (Комментарий, Дата, Ссылка) и ЭтотОбъект.
События форм
- Заимствуйте форму: правой кнопкой по форме объекта →
Добавить в расширение. - Откройте форму в расширении, выделите корневой элемент «Форма» или нужный элемент.
- В палитре свойств найдите событие (например,
ПриСозданииНаСервере) и создайте обработчик. - Для события выберите, когда вызывать процедуру расширения: перед, после или вместо обработчика конфигурации.
Конфигуратор сам добавит процедуру в модуль формы расширения с нужной аннотацией контекста (&НаСервере, &НаКлиенте). Из неё доступны реквизиты формы, Объект и все процедуры исходного модуля формы.
Подписки на события
Если реакция нужна для многих объектов сразу, создайте в расширении Общие → Подписки на события. В источнике отметьте нужные типы, обработчиком укажите экспортную процедуру серверного общего модуля расширения. Объекты, которые выбираете источником, должны быть заимствованы в расширение.
Клиент и сервер: откуда что вызывается
Большинство ошибок «процедура не вызывается» — про контекст, а не про расширение.
| Где код | Что можно вызвать напрямую |
|---|---|
&НаКлиенте в форме | клиентские общие модули; серверные — только с флагом Вызов сервера |
&НаСервере в форме | серверные общие модули, реквизиты формы и Объект |
&НаСервереБезКонтекста | серверные общие модули; данных формы нет |
| модуль объекта, менеджера | серверные общие модули |
Пример: кнопка на форме расширения проверяет остаток через серверный модуль.
// Модуль формы расширения
&НаКлиенте
Процедура Расш1_ПроверитьОстаток(Команда)
Остаток = Расш1_СкладВызовСервера.ОстатокТовара(Объект.Номенклатура);
ПоказатьПредупреждение(, "Свободный остаток: " + Формат(Остаток, "ЧДЦ=3; ЧН=0"));
КонецПроцедуры
У модуля Расш1_СкладВызовСервера должны стоять флаги Сервер и Вызов сервера. Если отметить только Сервер, клиент модуль не увидит: при выполнении будет Переменная не определена (Расш1_СкладВызовСервера).
💡 Каждый вызов сервера с клиента — это обмен по сети. Собирайте данные за один вызов. Передавайте простые значения (ссылки, строки, структуры), а не объекты целиком.
Как вызвать процедуру без Экспорт
Неэкспортную процедуру основной конфигурации снаружи её модуля вызвать нельзя. Зато её можно вызвать изнутри — из модуля заимствованного объекта. Для расширения заимствованный модуль и оригинал — один контекст.
Приём выглядит так:
- Заимствуйте общий модуль (или объект), где лежит нужная процедура.
- В модуле заимствованного объекта в расширении напишите экспортную «обёртку».
- Вызывайте обёртку из любого места расширения.
// Заимствованный общий модуль ПродажиСервер (условное имя), модуль в расширении
Функция Расш1_РассчитатьСкидку(Документ) Экспорт
// РассчитатьСкидкуЛокально — неэкспортная функция исходного модуля
Возврат РассчитатьСкидкуЛокально(Документ);
КонецФункции
// В любом другом модуле расширения
Скидка = ПродажиСервер.Расш1_РассчитатьСкидку(Ссылка);
С модулями форм то же самое. В модуле заимствованной формы можно вызывать локальные процедуры исходной формы без всякой обёртки.
Неэкспортные методы — внутренняя кухня разработчика. Их чаще всего переименовывают и меняют параметры. После каждого обновления типовой проверяйте, что расширение применилось.
Как основная конфигурация может вызвать код расширения
Иногда нужно наоборот: типовой код должен «дёрнуть» процедуру расширения. Есть три рабочих варианта.
Вариант 1. Перехват вместо прямого вызова
Лучший путь. Найдите в конфигурации метод, который выполняется в нужный момент, и повесьте на него &После. Конфигурацию трогать не нужно.
Вариант 2. Пустая «точка расширения»
Если подходящего метода нет, а конфигурация ваша (не на поддержке), добавьте в неё пустую экспортную процедуру и вызывайте её в нужном месте. Расширение перехватит её через &Вместо или &После. Так же устроены переопределяемые модули БСП: например, ОбщегоНазначенияПереопределяемый. Многие задачи решаются перехватом их методов.
Вариант 3. Вызов по имени через БСП
В конфигурациях на БСП есть функция ОбщегоНазначения.ОбщийМодуль(Имя). Она возвращает модуль по строке, и код компилируется даже там, где расширения нет.
Если Метаданные.ОбщиеМодули.Найти("Расш1_Интеграция") <> Неопределено Тогда
МодульИнтеграции = ОбщегоНазначения.ОбщийМодуль("Расш1_Интеграция");
МодульИнтеграции.ЗаписатьВЖурнал("Вызов из основной конфигурации");
КонецЕсли;
Проверка через Метаданные нужна, чтобы код не падал в сеансе, где расширение отключено. На клиенте используйте ОбщегоНазначенияКлиент.ОбщийМодуль(Имя).
Отладка: почему процедура из расширения не срабатывает
Сначала убедитесь, что расширение вообще работает в сеансе:
- 🔍 В Конфигураторе:
Конфигурация → Расширения конфигурации. Расширение должно быть в списке, у него отмечено «Активно». - 🔍 В пользовательском режиме конфигураций на БСП:
Администрирование → Печатные формы, отчеты и обработки → Расширения. - 🔍 Изменения в расширении получают только новые сеансы. Пользователь, который не перезапустил 1С, работает со старой версией кода.
Затем ставьте точку останова прямо в модуле расширения и запускайте отладку: Отладка → Начать отладку. Точки останова в расширениях работают так же, как в основной конфигурации. Если отладчик не останавливается, перехватчик не подключился или метод с этим именем не вызывается.
Обновление расширения в рабочей базе выполняйте вне рабочего времени. Пользователям придётся перезапустить 1С, иначе часть сеансов останется на старом коде.
Как писать вызовы так, чтобы расширение пережило обновления
Перехват — это всегда зависимость от чужого кода. Чем она тоньше, тем дольше расширение живёт без правок.
- 🎯 Держите перехватчики короткими. В самом перехватчике — одна-две строки и вызов процедуры вашего общего модуля. Логика живёт в модуле расширения, а не размазана по заимствованным объектам.
- 🎯 Выбирайте
&Переди&После, а не&Вместо. Они не мешают типовому коду меняться. - 🎯
&ИзменениеИКонтроль— только когда без вставки в середину метода не обойтись. Зато такие правки не ломаются молча. - 🎯 Перехватывайте точку как можно выше. Один перехват общего метода проведения надёжнее пяти перехватов мелких служебных процедур.
- 🎯 Кэшируйте неизменные данные. Для настроек и справочной информации заведите общий модуль с повторным использованием возвращаемых значений («На время сеанса»). Повторные вызовы с теми же параметрами не пойдут в базу.
- 🎯 Не гоняйте вызов сервера в цикле на клиенте. Сто строк табличной части — один вызов с массивом, а не сто вызовов по одной строке.
- 🎯 Не стройте вызовы по имени из пользовательских данных.
ВыполнитьиВычислитьсо строкой, которую ввёл пользователь, — прямой путь к выполнению чужого кода.