Расширение и основная конфигурация 1С: кто кого вызывает и как это сделать без ошибок

· 9 мин

Содержание

Процедуру из общего модуля расширения вызывают так же, как в обычной конфигурации: ИмяМодуля.ИмяПроцедуры(), если у метода стоит Экспорт, а у модуля включены нужные флаги контекста (Сервер, Вызов сервера, Клиент). Чтобы встроить свою процедуру в типовой код, её не вызывают «откуда-то из конфигурации», а вешают на метод основной конфигурации аннотацией &Перед, &После или &Вместо.

Частое заблуждение — что основная конфигурация «видит» расширение и может напрямую обратиться к его модулю. Не может: конфигурация ничего не знает о расширениях. Связь идёт в обратную сторону. Расширение видит конфигурацию и встраивается в неё. Всё, что ниже, — про платформу 8.3 и одинаково работает в БП 3.0, ЗУП 3.1, УТ 11 и любых других конфигурациях.

Как устроена связь расширения и основной конфигурации

Расширение подключается к базе поверх основной конфигурации. В момент запуска сеанса платформа «склеивает» их: модули расширения дополняют модули конфигурации, а новые объекты расширения становятся доступны так же, как свои.

Отсюда правило направления вызовов:

  • ✅ Расширение → конфигурация. Код расширения может вызывать экспортные методы общих модулей конфигурации напрямую: ОбщегоНазначения.СообщитьПользователю(...).
  • ✅ Расширение → расширение. Свои общие модули расширения вызываются по имени, как обычно.
  • ⚠️ Конфигурация → расширение. Прямой вызов «в лоб» — плохая идея: в Конфигураторе основной конфигурации такого модуля нет, проверка модулей ругается, а в базе без расширения код упадёт.

Ещё одно понятие — заимствование. Чтобы изменить поведение объекта конфигурации (модуль, форму, обработчик события), объект заимствуют в расширение: правой кнопкой по объекту в дереве конфигурации → Добавить в расширение. Заимствованный объект — это «ссылка» на оригинал, в которой можно перехватывать методы и добавлять свои.

У расширения есть свойство Префикс имён (по умолчанию Расширение1_). Начинайте с него имена всех новых объектов и методов. Тогда при обновлении типовой ваша процедура не столкнётся с одноимённой процедурой разработчика.

Вызов процедуры из общего модуля расширения

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

  1. В дереве расширения откройте ветку Общие → Общие модули и добавьте модуль, например Расш1_Интеграция.
  2. В свойствах модуля отметьте контекст: Сервер для серверного кода, Вызов сервера — если процедуру будут вызывать с клиента.
  3. Напишите метод с ключевым словом Экспорт.
  4. Вызывайте его из любого модуля расширения по полному имени.
// Общий модуль расширения Расш1_Интеграция (флаги: Сервер)
Процедура ЗаписатьВЖурнал(Текст) Экспорт

	ЗаписьЖурналаРегистрации("Расш1.Интеграция",
		УровеньЖурналаРегистрации.Информация, , , Текст);

КонецПроцедуры

Вызов из модуля объекта, модуля менеджера или серверной процедуры формы расширения:

Расш1_Интеграция.ЗаписатьВЖурнал("Документ отправлен во внешнюю систему");

Без Экспорт метод виден только внутри своего модуля. При вызове снаружи получите Метод объекта не обнаружен (ЗаписатьВЖурнал).

Перехват методов конфигурации: &Перед, &После, &Вместо

Это главный способ «вызвать свою процедуру» в нужный момент работы типового кода. Вы не правите исходный метод, а говорите платформе: «перед ним (после него, вместо него) выполни мой».

Порядок действий:

  1. Заимствуйте в расширение общий модуль или объект, в модуле которого лежит нужный метод.
  2. Откройте модуль заимствованного объекта в расширении.
  3. Напишите свою процедуру с аннотацией и именем исходного метода в кавычках.
// Модуль объекта заимствованного документа РеализацияТоваровУслуг
&Перед("ПередЗаписью")
Процедура Расш1_ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)

	Если РежимЗаписи = РежимЗаписиДокумента.Проведение
		И Не ЗначениеЗаполнено(Комментарий) Тогда
		ОбщегоНазначения.СообщитьПользователю(
			"Перед проведением заполните комментарий", ЭтотОбъект, "Комментарий", , Отказ);
	КонецЕсли;

КонецПроцедуры

Параметры перехватчика повторяют параметры исходного метода — тот же порядок и количество.

АннотацияКогда срабатываетРиск при обновлении типовой
&Переддо исходного методанизкий
&Послепосле исходного методанизкий
&Вместоисходный метод не выполняется, пока вы не вызовете ПродолжитьВызов()средний: можно «потерять» новую логику разработчика
&ИзменениеИКонтролькопия тела метода с вашими вставкамивысокий, но платформа сама заметит, что оригинал изменился

Для функций надёжнее всего &Вместо: вызываете ПродолжитьВызов(), берёте результат и меняете его.

&Вместо("ПредставлениеДокумента")
Функция Расш1_ПредставлениеДокумента(Документ)

	Результат = ПродолжитьВызов(Документ);
	Возврат Результат + " (проверен)";

КонецФункции

&ИзменениеИКонтроль появилась в платформе 8.3.11. Конфигуратор копирует тело метода, а ваши правки вы обрамляете #Вставка … #КонецВставки и #Удаление … #КонецУдаления. Если после обновления типовой текст оригинала изменится, расширение перестанет применяться — и вы узнаете об этом сразу, а не через месяц по жалобам пользователей.

&Вместо без ПродолжитьВызов() полностью отключает исходный метод. После очередного обновления разработчик может добавить туда важную логику, а у вас она молча не выполнится.

Свои обработчики событий форм и объектов

Событие — та же точка входа, только её вызывает платформа: открытие формы, запись, проведение, нажатие кнопки.

События модулей объектов

Работает схема из предыдущего раздела: аннотация с именем обработчика (ПередЗаписью, ОбработкаПроведения, ПриЗаписи). Внутри модуля объекта доступны его реквизиты (Комментарий, Дата, Ссылка) и ЭтотОбъект.

События форм

  1. Заимствуйте форму: правой кнопкой по форме объекта → Добавить в расширение.
  2. Откройте форму в расширении, выделите корневой элемент «Форма» или нужный элемент.
  3. В палитре свойств найдите событие (например, ПриСозданииНаСервере) и создайте обработчик.
  4. Для события выберите, когда вызывать процедуру расширения: перед, после или вместо обработчика конфигурации.

Конфигуратор сам добавит процедуру в модуль формы расширения с нужной аннотацией контекста (&НаСервере, &НаКлиенте). Из неё доступны реквизиты формы, Объект и все процедуры исходного модуля формы.

Подписки на события

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

Клиент и сервер: откуда что вызывается

Большинство ошибок «процедура не вызывается» — про контекст, а не про расширение.

Где кодЧто можно вызвать напрямую
&НаКлиенте в формеклиентские общие модули; серверные — только с флагом Вызов сервера
&НаСервере в формесерверные общие модули, реквизиты формы и Объект
&НаСервереБезКонтекстасерверные общие модули; данных формы нет
модуль объекта, менеджерасерверные общие модули

Пример: кнопка на форме расширения проверяет остаток через серверный модуль.

// Модуль формы расширения
&НаКлиенте
Процедура Расш1_ПроверитьОстаток(Команда)

	Остаток = Расш1_СкладВызовСервера.ОстатокТовара(Объект.Номенклатура);
	ПоказатьПредупреждение(, "Свободный остаток: " + Формат(Остаток, "ЧДЦ=3; ЧН=0"));

КонецПроцедуры

У модуля Расш1_СкладВызовСервера должны стоять флаги Сервер и Вызов сервера. Если отметить только Сервер, клиент модуль не увидит: при выполнении будет Переменная не определена (Расш1_СкладВызовСервера).

💡 Каждый вызов сервера с клиента — это обмен по сети. Собирайте данные за один вызов. Передавайте простые значения (ссылки, строки, структуры), а не объекты целиком.

Как вызвать процедуру без Экспорт

Неэкспортную процедуру основной конфигурации снаружи её модуля вызвать нельзя. Зато её можно вызвать изнутри — из модуля заимствованного объекта. Для расширения заимствованный модуль и оригинал — один контекст.

Приём выглядит так:

  1. Заимствуйте общий модуль (или объект), где лежит нужная процедура.
  2. В модуле заимствованного объекта в расширении напишите экспортную «обёртку».
  3. Вызывайте обёртку из любого места расширения.
// Заимствованный общий модуль ПродажиСервер (условное имя), модуль в расширении
Функция Расш1_РассчитатьСкидку(Документ) Экспорт

	// РассчитатьСкидкуЛокально — неэкспортная функция исходного модуля
	Возврат РассчитатьСкидкуЛокально(Документ);

КонецФункции
// В любом другом модуле расширения
Скидка = ПродажиСервер.Расш1_РассчитатьСкидку(Ссылка);

С модулями форм то же самое. В модуле заимствованной формы можно вызывать локальные процедуры исходной формы без всякой обёртки.

Неэкспортные методы — внутренняя кухня разработчика. Их чаще всего переименовывают и меняют параметры. После каждого обновления типовой проверяйте, что расширение применилось.

Как основная конфигурация может вызвать код расширения

Иногда нужно наоборот: типовой код должен «дёрнуть» процедуру расширения. Есть три рабочих варианта.

Вариант 1. Перехват вместо прямого вызова

Лучший путь. Найдите в конфигурации метод, который выполняется в нужный момент, и повесьте на него &После. Конфигурацию трогать не нужно.

Вариант 2. Пустая «точка расширения»

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

Вариант 3. Вызов по имени через БСП

В конфигурациях на БСП есть функция ОбщегоНазначения.ОбщийМодуль(Имя). Она возвращает модуль по строке, и код компилируется даже там, где расширения нет.

Если Метаданные.ОбщиеМодули.Найти("Расш1_Интеграция") <> Неопределено Тогда
	МодульИнтеграции = ОбщегоНазначения.ОбщийМодуль("Расш1_Интеграция");
	МодульИнтеграции.ЗаписатьВЖурнал("Вызов из основной конфигурации");
КонецЕсли;

Проверка через Метаданные нужна, чтобы код не падал в сеансе, где расширение отключено. На клиенте используйте ОбщегоНазначенияКлиент.ОбщийМодуль(Имя).

Отладка: почему процедура из расширения не срабатывает

Сначала убедитесь, что расширение вообще работает в сеансе:

  • 🔍 В Конфигураторе: Конфигурация → Расширения конфигурации. Расширение должно быть в списке, у него отмечено «Активно».
  • 🔍 В пользовательском режиме конфигураций на БСП: Администрирование → Печатные формы, отчеты и обработки → Расширения.
  • 🔍 Изменения в расширении получают только новые сеансы. Пользователь, который не перезапустил 1С, работает со старой версией кода.

Затем ставьте точку останова прямо в модуле расширения и запускайте отладку: Отладка → Начать отладку. Точки останова в расширениях работают так же, как в основной конфигурации. Если отладчик не останавливается, перехватчик не подключился или метод с этим именем не вызывается.

Диагностика: выберите симптом

Обновление расширения в рабочей базе выполняйте вне рабочего времени. Пользователям придётся перезапустить 1С, иначе часть сеансов останется на старом коде.

Как писать вызовы так, чтобы расширение пережило обновления

Перехват — это всегда зависимость от чужого кода. Чем она тоньше, тем дольше расширение живёт без правок.

  • 🎯 Держите перехватчики короткими. В самом перехватчике — одна-две строки и вызов процедуры вашего общего модуля. Логика живёт в модуле расширения, а не размазана по заимствованным объектам.
  • 🎯 Выбирайте &Перед и &После, а не &Вместо. Они не мешают типовому коду меняться.
  • 🎯 &ИзменениеИКонтроль — только когда без вставки в середину метода не обойтись. Зато такие правки не ломаются молча.
  • 🎯 Перехватывайте точку как можно выше. Один перехват общего метода проведения надёжнее пяти перехватов мелких служебных процедур.
  • 🎯 Кэшируйте неизменные данные. Для настроек и справочной информации заведите общий модуль с повторным использованием возвращаемых значений («На время сеанса»). Повторные вызовы с теми же параметрами не пойдут в базу.
  • 🎯 Не гоняйте вызов сервера в цикле на клиенте. Сто строк табличной части — один вызов с массивом, а не сто вызовов по одной строке.
  • 🎯 Не стройте вызовы по имени из пользовательских данных. Выполнить и Вычислить со строкой, которую ввёл пользователь, — прямой путь к выполнению чужого кода.
Перед переносом расширения в рабочую базу
Выполнено: 0 / 6