diamond АШ Tlg

1С ERP: паттерн доработки форм типовых объектов конфигурации

Описанный ниже способ позволяет выстроить единую систему доработок конфигурации ERP с минимальным вмешательством в типовой код (не в расширении)

Содержание

Общие сведения

В данной статье речь идёт о доработке типовой конфигурации 1С: Управление предприятием 2.5 (ERP) способом №1, в основной конфигурации, снятой с поддержки. Доработка основной конфигурации является наиболее распространённым способом на больших проектах. Данный паттерн придуман не мной, я его здесь лишь слегка усовершенствовал и задокументировал. Его схема по верхам выглядит так:

Схема

Основная идея заключается в том, что доработка типового объекта реализуется в трёх общих модулях разного типа: серверном, клиентском и модуле вызова сервера. Имена этих модулей формируются по единому шаблону, установленному архитектором.

В типовой конфигурации 1С ERP почти во всех формах объектов обработчики событий уже содержат вызовы методов из общих модулей СобытияФорм и СобытияФормКлиент. Нам остаётся лишь добавить в них вызовы обработчиков из собственного аналогичного модуля. В этом модуле реализуется алгоритм определения имени модуля с доработками (спойлер: на основе полного имени формы) — и вызываются соответствующие методы, если они были реализованы.

Стоить заметить, что эта методика подходит и для других типовых конфигураций где нет вышеуказанных общих модулей, вместо них придётся вставлять напрямую вызовы своих модулей по все типовые формы, которые требуется доработать. Точно также и в ERP можно делать вызовы из своих модулей напрямую, если вы посчитаете что СобытияФорм и СобытияФормКлиент - это лишняя прослойка.

Достоинства:

  • Минимальное вмешательство в исходный код типовой конфигурации, что упрощает её поддержку и обновление
  • Все изменения в коде типовой конфигурации шаблонизированы и стандартизированы, что повышает их предсказуемость и упрощает сопровождение
  • Единая архитектурная модель — любой разработчик из команды быстро разберётся в реализации доработок, выполненных коллегами
  • Сниженная трудоёмкость обновления типовой конфигурации поставщика, благодаря изолированности доработок
  • В системе коллективной разработки (GIT) отсутствует поток изменений по типовым объектам, что минимизирует риски нарушения стабильности типового функционала

Недостатки:

  • Повышенная трудоёмкость реализации отдельных доработок из-за сложности и громоздкости используемого паттерна
  • При значительном объёме доработок наблюдается резкий рост количества метаданных, что может существенно замедлить работу сред разработки, сборку конфигурации, отладку и выгрузку в базу. Для стабильной работы EDT может потребоваться выделение до 64 ГБ оперативной памяти и более
  • При обновлении типовой конфигурации поставщика через диалог сравнения-объединения будет практически невозможно предсказать что сломается в доработках - ошибки придётся отлавливать при опытной эксплуатации или настраивать автотесты.

Общие модули паттерна

Исходный код общих модулей АШ_СобытияФорм и АШ_СобытияФормКлиент приведён ниже. Пока он не полный, для начала реализованы только по паре обработчиков событий формы. Полный код с примерами будет в конце статьи.

Общий модуль 'АШ_СобытияФорм' (сервер)
#Область ПрограммныйИнтерфейс

// Все обработчики в этом интерфейсе должны вызываться из одноименных
// процедур общего модуля 'СобытияФорм' или 'СобытияФормЛокализация'

#Область СобытияФормы

Процедура ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначения.МетодСуществует(ОМ, "ПриСозданииНаСервере") Тогда
		ОМ.ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры);
	КонецЕсли;	
КонецПроцедуры

Процедура ПриЧтенииНаСервере(Форма, ТекущийОбъект, ДополнительныеПараметры) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначения.МетодСуществует(ОМ, "ПриЧтенииНаСервере") Тогда
		ОМ.ПриЧтенииНаСервере(Форма, ТекущийОбъект, ДополнительныеПараметры);
	КонецЕсли;	
КонецПроцедуры

// добавить здесь другие обработчики серверных событий формы при необходимости

#КонецОбласти

#Область СобытияЭлементовФормыНаСервере

// здесь будут обработчики событий элементов формы, вызванные из клиентских обработчиков

#КонецОбласти

#Область КомандыНаСервере

// здесь будут обработчики команд формы, вызванные из клиентских обработчиков

#КонецОбласти

#КонецОбласти

#Область Служебная

// Функция - Получить общий модуль для модификации объекта
//
// Параметры:
//  ИмяФормы - Строка - Полное имя формы объекта конфигурации
// 
// Возвращаемое значение:
//  ОбщийМодуль - Объект общего модуля (на сервере) для модификации объекта, полученный
//                по имени формы и определённому шаблону
//
Функция ОбщийМодульМодификацииОбъекта(ИмяФормы)
	ОМ = Неопределено;
	// Здесь можно настроить собственный шаблон для имен общих модулей:
	Шаблон = "АШ_%1_%2";
	ЧастиИмени = СтрРазделить(ИмяФормы, ".");	
	Если ЧастиИмени.Количество() > 1 Тогда
		ИмяОбщегоМодуля = СтрШаблон(Шаблон, ЧастиИмени[0], ЧастиИмени[1]);
		Попытка
			ОМ = ОбщегоНазначения.ОбщийМодуль(ИмяОбщегоМодуля);
		Исключение
		КонецПопытки
	КонецЕсли;
	Возврат ОМ;
КонецФункции

#КонецОбласти
Общий модуль 'АШ_СобытияФормКлиент' (клиент)
#Область ПрограммныйИнтерфейс

// Все обработчики в этом интерфейсе должны вызываться из одноименных
// процедур ОМ 'СобытияФормКлиент'

#Область СобытияФормы

Процедура ПриОткрытии(Форма, Отказ) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ПриОткрытии") Тогда
		ОМ.ПриОткрытии(Форма, Отказ);
	КонецЕсли;	
КонецПроцедуры

Процедура ПередЗаписью(Форма, Отказ, ПараметрыЗаписи) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ПередЗаписью") Тогда
		ОМ.ПередЗаписью(Форма, Отказ, ПараметрыЗаписи);
	КонецЕсли;	
КонецПроцедуры

// добавить другие обработчики клиентских событий формы при необходимости

#КонецОбласти

#Область СобытияЭлементовФормы

// здесь будут обработчики событий элементов формы

#КонецОбласти

#Область Оповещения

// здесь будет обработчик оповещений

#КонецОбласти

#Область Команды

// здесь будет обработчик команд

#КонецОбласти

#КонецОбласти

#Область Служебная

// Функция - Получить общий модуль для модификации объекта
//
// Параметры:
//  ИмяФормы - Строка - Полное имя формы объекта конфигурации
// 
// Возвращаемое значение:
//  ОбщийМодуль - Объект общего модуля (на клиенте) для модификации объекта, полученный
//                по имени формы и определённому шаблону
//
Функция ОбщийМодульМодификацииОбъекта(ИмяФормы)
	ОМ = Неопределено;
	// Здесь можно настроить собственный шаблон для имен общих модулей:
	Шаблон = "АШ_%1_%2Клиент";
	ЧастиИмени = СтрРазделить(ИмяФормы, ".");	
	Если ЧастиИмени.Количество() > 1 Тогда
		ИмяОбщегоМодуля = СтрШаблон(Шаблон, ЧастиИмени[0], ЧастиИмени[1]);
		Попытка
			ОМ = ОбщегоНазначенияКлиент.ОбщийМодуль(ИмяОбщегоМодуля);
		Исключение
		КонецПопытки
	КонецЕсли;
	Возврат ОМ;
КонецФункции

#КонецОбласти

Кроме того, понадобятся ещё 2 общих модуля, они в ходе разработки изменяться не будут и окончательные варианты здесь: АШ_ОбщегоНазначения и АШ_ОбщегоНазначенияКлиент.

Пример 1: Добавление элементов на форму

Допустим, требуется добавить пару реквизитов и полей на форму документа 'Расходный кассовый ордер'. Для этого создадим общий серверный модуль с наименованием 'АШ_Документ_РасходныйКассовыйОрдер', согласно требованиям шаблона паттерна:

Дерево конфигурации

Реквизиты и элементы формы создаются программно в обработчике события формы 'ПриСозданииНаСервере':


#Область ПрограммныйИнтерфейс

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

#КонецОбласти

#Область Служебная

Процедура ФормаДокументаПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры)
	
	// Пример: программное создание реквизитов формы

	ДобавляемыеРеквизиты = Новый Массив;
	ДобавляемыеРеквизиты.Добавить(
		Новый РеквизитФормы(
			"АШ_ЧисловойРеквизит",
			Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(12, 2, ДопустимыйЗнак.Любой)),,
			"Числовой реквизит"
		)
	);
	ДобавляемыеРеквизиты.Добавить(
		Новый РеквизитФормы(
			"АШ_БулевыйРеквизит",
			Новый ОписаниеТипов("Булево"),,
			"Вкл. числовой реквизит"
		)
	);
	Форма.ИзменитьРеквизиты(ДобавляемыеРеквизиты);
	
	// Пример: программное создание элементов формы

	АШ_ПолеЧисло1 = Форма.Элементы.Добавить(
		"АШ_ПолеЧисло1", Тип("ПолеФормы"), Форма.Элементы.Найти("ГруппаЛево")
	);
	АШ_ПолеЧисло1.Вид = ВидПоляФормы.ПолеВвода;
	АШ_ПолеЧисло1.ПутьКДанным = "АШ_ЧисловойРеквизит";

	АШ_ПолеБулево1 = Форма.Элементы.Добавить(
		"АШ_ПолеБулево1", Тип("ПолеФормы"), Форма.Элементы.Найти("ГруппаЛево")
	);
	АШ_ПолеБулево1.Вид = ВидПоляФормы.ПолеФлажка;
	АШ_ПолеБулево1.ВидФлажка = ВидФлажка.Выключатель;
	АШ_ПолеБулево1.ПутьКДанным = "АШ_БулевыйРеквизит";

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

#КонецОбласти

Заглянем в модуль формы документа и убедимся, что вызов одноименного метода стандартного модуля присутствует:

Модуль формы

Таким образом, для нашей простой задачи типовой документ трогать вообще не требуется. Протестируем:

Тест документа

Пример 2: Добавление обработчиков событий элемента формы

Усложним задачу: требуется при изменении значения добавленного флажка переключать доступность числового поля. Для этого потребуется добавить обработчик события 'ПриИзменении':

АШ_ПолеБулево1.УстановитьДействие("ПриИзменении", "Подключаемый_ПриИзмененииЭлемента");

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

#Область АШ_Доработки

&НаКлиенте
Процедура Подключаемый_ПриИзмененииЭлемента(Элемент)
	// { АШ
	СобытияФормКлиент.ПриИзмененииЭлемента(ЭтотОбъект, Элемент);
	// }
КонецПроцедуры
	
#КонецОбласти

Далее идем в типовой модуль 'СобытияФормКлиент', и согласно концепции добавяем туда вызов нашего обработчика:


Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры = Неопределено) Экспорт
	
	СобытияФормКлиентЛокализация.ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
	
	// { АШ
	АШ_СобытияФормКлиент.ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
	// }
	
КонецПроцедуры

И уже в нашем модуле 'АШ_СобытияФормКлиент':

#Область СобытияЭлементовФормы

Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ПриИзмененииЭлемента") Тогда
		ОМ.ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
	КонецЕсли;		
КонецПроцедуры

// здесь будут другие обработчики событий элементов формы

#КонецОбласти

И наконец, создаем общий клиентский модуль 'АШ_Документ_РасходныйКассовыйОрдерКлиент':

#Область ПрограммныйИнтерфейс

Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
	
	Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
		ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
	Иначе
		// другие формы документа
	КонецЕсли;

КонецПроцедуры
	
#КонецОбласти

#Область Служебная

Процедура ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры)

	Если Элемент.Имя = "АШ_ПолеБулево1" Тогда
		Форма.Элементы.АШ_ПолеЧисло1.Доступность = Форма.АШ_БулевыйРеквизит;
	Иначе
		// другие элементы формы
	КонецЕсли;
	
КонецПроцедуры

#КонецОбласти

Проверяем, убеждаемся что переключатель работает и доступность меняется.

Что делать, если нам требуется также выполнить код на сервере? Для каких-то задач, не связанных с передачей мутабельных значений годится общий модуль с типом 'ВызовСервера', например выполнение запросов с простыми ссылочными параметрами. Для этого создаем общий модуль 'АШ_Документ_РасходныйКассовыйОрдерВызовСервера' и пишем серверный код там.

Если же в серверном обработчике требуются форма или объект, то схема вынужденно станет сложней. Поменяем код модуля формы типового объекта в области 'АШ_Доработки' на новый шаблон:

#Область АШ_Доработки

&НаКлиенте
Процедура Подключаемый_ПриИзмененииЭлемента(Элемент)
	// { АШ
	ДополнительныеПараметры = Неопределено;
	СобытияФормКлиент.ПриИзмененииЭлемента(ЭтотОбъект, Элемент, ДополнительныеПараметры);
	Если ЗначениеЗаполнено(ДополнительныеПараметры) И ДополнительныеПараметры.Свойство("ТребуетсяВыполнитьНаСервере") Тогда
		Подключаемый_ПриИзмененииЭлементаНаСервере(Элемент.Имя, ДополнительныеПараметры);	
	КонецЕсли;
	Если ЗначениеЗаполнено(ДополнительныеПараметры) И ДополнительныеПараметры.Свойство("ТребуетсяЗавершитьНаКлиенте") Тогда
		СобытияФормКлиент.ПриИзмененииЭлемента(ЭтотОбъект, Элемент, ДополнительныеПараметры);	
	КонецЕсли;
	// }
КонецПроцедуры

&НаСервере
Процедура Подключаемый_ПриИзмененииЭлементаНаСервере(ИмяЭлемента, ДополнительныеПараметры)
	// { АШ
	СобытияФорм.ПриИзмененииЭлемента(ЭтотОбъект, Элементы[ИмяЭлемента], ДополнительныеПараметры);
	// }
КонецПроцедуры

#КонецОбласти

Добавляем в типовом модуле 'СобытияФорм' ещё один вызов своего обработчика:

Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры = Неопределено) Экспорт
	
	СобытияФормЛокализация.ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);

	// { АШ
	АШ_СобытияФорм.ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
	// }
	
КонецПроцедуры

... и обработчик в модуле 'АШ_СобытияФорм':

#Область СобытияЭлементовФормыНаСервере

Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначения.МетодСуществует(ОМ, "ПриИзмененииЭлемента") Тогда
		ОМ.ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
	КонецЕсли;	
КонецПроцедуры

// здесь будут обработчики событий других элементов формы, вызванные из клиентских обработчиков

#КонецОбласти

Теперь, для проверки концепции, добавим в обработчик события флажка формы какой-нибудь код на сервере. Как уж догадались, в клиентском общем модуле нам нужно для вызова серверного метода вставить новый параметр "ТребуетсяВыполнитьНаСервере" в структуру 'ДополнительныеПараметры'. Видоизменим код:

Процедура ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры)
	
	ДополнительныеПараметры = ? (ДополнительныеПараметры = Неопределено, Новый Структура, ДополнительныеПараметры);

	Если Элемент.Имя = "АШ_ПолеБулево1" Тогда
		
		Если Не ДополнительныеПараметры.Свойство("ТребуетсяЗавершитьНаКлиенте") Тогда 
			Форма.Элементы.АШ_ПолеЧисло1.Доступность = Форма.АШ_БулевыйРеквизит;
			ДополнительныеПараметры.Вставить("ТребуетсяВыполнитьНаСервере");
		Иначе
			ОбщегоНазначенияКлиент.СообщитьПользователю("Завершение на клиенте");
		КонецЕсли;

	Иначе
		// другие элементы формы
	КонецЕсли;
	
КонецПроцедуры

В модуле 'АШ_Документ_РасходныйКассовыйОрдер' добавляем код по аналогии с клиентским модулем:

Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
	
	Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
		ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
	Иначе
		// другие формы документа
	КонецЕсли;

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

Процедура ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры)
	
	Если Элемент.Имя = "АШ_ПолеБулево1" Тогда
		Если Форма.Объект.Ссылка.Пустая() Тогда
			ОбщегоНазначения.СообщитьПользователю("Документ не сохранен");
		Иначе
			ОбщегоНазначения.СообщитьПользователю("Документ сохранен");
		КонецЕсли;			
		ДополнительныеПараметры.Вставить("ТребуетсяЗавершитьНаКлиенте");
	Иначе
		// другие элементы формы
	КонецЕсли;
	
КонецПроцедуры

Запускаем, проверяем - сообщения выводятся.

Следующая проблема: как быть, если поле типовое, но обработчик нужного события не определён? Очень просто: в модуле 'АШ_Документ_РасходныйКассовыйОрдер' добавляем для него обработчик события программно (так тоже можно) и далее по аналогии сами допишите вывод сообщения:

Форма.Элементы.Выдать.УстановитьДействие("ПриИзменении", "Подключаемый_ПриИзмененииЭлемента");

И последняя возможная проблема: в типовом модуле формы обработчик события уже определен. В этом случае уже делаем вставку в типовом коде обработчика события, в каком именно месте надо определятся по содержанию алгоритма, в следующем примере я вставил в начале для поля 'Касса':

&НаКлиенте
Процедура КассаПриИзменении(Элемент)

	// { АШ
	Подключаемый_ПриИзмененииЭлемента(Элемент);
	// }

	// типовой код вырезан

КонецПроцедуры
Процедура ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры)
	
	ДополнительныеПараметры = ? (ДополнительныеПараметры = Неопределено, Новый Структура, ДополнительныеПараметры);
	
	Если Элемент.Имя = "АШ_ПолеБулево1" Тогда
		
		Если Не ДополнительныеПараметры.Свойство("ТребуетсяЗавершитьНаКлиенте") Тогда 
			Форма.Элементы.АШ_ПолеЧисло1.Доступность = Форма.АШ_БулевыйРеквизит;
			ДополнительныеПараметры.Вставить("ТребуетсяВыполнитьНаСервере");
		Иначе
			ОбщегоНазначенияКлиент.СообщитьПользователю("Завершение на клиенте");
		КонецЕсли;

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

Финальная проверка что всё работает:

Тест документа 2

Точно таким же путём добавляются обработчики других событий элементов формы.

Пример 3: Добавление команд формы

Добавление команд формы делается по аналогии с добавлением элементов и их обработчиков событий. В качестве примера добавим команду формы в модуле 'АШ_Документ_РасходныйКассовыйОрдер' в процедуре 'ПриСозданииНаСервере':

АШ_Загрузить = Форма.Команды.Добавить("АШ_Загрузить");
АШ_Загрузить.Заголовок = "Загрузить из Excel";
АШ_Загрузить.Действие = "Подключаемый_ВыполнитьПереопределяемуюКоманду";

АШ_КнопкаЗагрузить = Форма.Элементы.Добавить("АШ_КнопкаЗагрузить", Тип("КнопкаФормы"), Форма.КоманднаяПанель);
АШ_КнопкаЗагрузить.ИмяКоманды = "АШ_Загрузить";

В модуле формы типового объекта уже есть эта процедура и ничего там делать не надо. Идём дальше, в общий модуль 'СобытияФормКлиент' и прописываем вызов одноименного метода из 'АШ_СобытияФормКлиент':

#Область Команды

Процедура ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ВыполнитьПереопределяемуюКоманду") Тогда
		ОМ.ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры);
	КонецЕсли;	
КонецПроцедуры

#КонецОбласти

В модуль '' добавляем:

Процедура ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры) Экспорт
	
	Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
		ФормаДокументаВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры);
	Иначе
		// другие формы документа
	КонецЕсли;
	
КонецПроцедуры

Процедура ФормаДокументаВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры)

	ДополнительныеПараметры = ? (ДополнительныеПараметры = Неопределено, Новый Структура, ДополнительныеПараметры);

	Если Команда.Имя = "АШ_Загрузить" Тогда
		ОбщегоНазначенияКлиент.СообщитьПользователю("Нажата кнопка 'Загрузить из файла'");	
		//ДополнительныеПараметры.Вставить("ТребуетсяВыполнитьНаСервере");
	Иначе
		// другие команды формы
	КонецЕсли;
		
КонецПроцедуры

Что делать, если требуется выполнять и серверный код? Ответ: делать по аналогии с событиями элементов формы. Обратите внимание, что в типовом коде при вызове СобытияФорм.ВыполнитьПереопределяемуюКоманду не передается третий аргумент 'ДополнительныеПараметры'.

типовой код

В этом случае лучше всего добавить в модуль формы собственный шаблон обработчика с префиксом, например 'Подключаемый_АШ_ВыполнитьПереопределяемуюКоманду' и прописывать в команде именно его.

Приложение: шаблоны модулей

АШ_ОбщегоНазначения
#Область ПрограммныйИнтерфейс

// Функция - Проверяет, существует ли экспортный метод у общего модуля
//
// Параметры:
//  Объект      - ОбщийМодуль - Объект общего модуля
//  ИмяМетода   - Строка      - Имя процедуры или функции
// 
// Возвращаемое значение:
//   Булево - В общем модуле реализован указанный метод
//
Функция МетодСуществует(Объект, ИмяМетода) Экспорт
	Результат = Ложь;
	Если Объект <> Неопределено Тогда		
		Выражение = СтрШаблон("Объект.%1(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,)", ИмяМетода);		
		Попытка
			//@skip-check server-execution-safe-mode
			Выполнить(Выражение);
		Исключение
			Описание = ИнформацияОбОшибке().Описание;
			Результат = Найти(НРег(Описание), СтрШаблон("(%1)", НРег(ИмяМетода))) = 0;
		КонецПопытки;
	КонецЕсли;
	Возврат Результат;
КонецФункции 

#КонецОбласти
АШ_ОбщегоНазначенияКлиент

#Область ПрограммныйИнтерфейс

// Функция - Проверяет, существует ли экспортный метод у общего модуля
//
// Параметры:
//  Объект      - ОбщийМодуль - Объект общего модуля
//  ИмяМетода   - Строка      - Имя процедуры или функции
// 
// Возвращаемое значение:
//   Булево - В общем модуле реализован указанный метод
//
Функция МетодСуществует(Объект, ИмяМетода) Экспорт
	Результат = Ложь;
	Если Объект <> Неопределено Тогда		
		Выражение = СтрШаблон("Объект.%1(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,)", ИмяМетода);		
		Попытка
			Выполнить(Выражение);
		Исключение
			Описание = ИнформацияОбОшибке().Описание;
			Результат = Найти(НРег(Описание), СтрШаблон("(%1)", НРег(ИмяМетода))) = 0;
		КонецПопытки;
	КонецЕсли;
	Возврат Результат;
КонецФункции

#КонецОбласти
АШ_СобытияФорм
#Область ПрограммныйИнтерфейс

// Все обработчики в этом интерфейсе должны вызываться из одноименных
// процедур ОМ 'СобытияФорм' или 'СобытияФормЛокализация'

#Область СобытияФормы

Процедура ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначения.МетодСуществует(ОМ, "ПриСозданииНаСервере") Тогда
		ОМ.ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры);
	КонецЕсли;	
КонецПроцедуры

Процедура ПриЧтенииНаСервере(Форма, ТекущийОбъект, ДополнительныеПараметры) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначения.МетодСуществует(ОМ, "ПриЧтенииНаСервере") Тогда
		ОМ.ПриЧтенииНаСервере(Форма, ТекущийОбъект, ДополнительныеПараметры);
	КонецЕсли;	
КонецПроцедуры

// добавить другие обработчики серверных событий формы при необходимости

#КонецОбласти

#Область СобытияЭлементовФормыНаСервере

Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначения.МетодСуществует(ОМ, "ПриИзмененииЭлемента") Тогда
		ОМ.ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
	КонецЕсли;	
КонецПроцедуры

// здесь будут обработчики событий других элементов формы, вызванные из клиентских обработчиков

#КонецОбласти

#Область КомандыНаСервере

// здесь будут обработчики команд формы, вызванные из клиентских обработчиков

#КонецОбласти

#КонецОбласти

#Область Служебная

// Функция - Получить общий модуль для модификации объекта
//
// Параметры:
//  ИмяФормы - Строка - Полное имя формы объекта конфигурации
// 
// Возвращаемое значение:
//  ОбщийМодуль - Объект общего модуля (на сервере) для модификации объекта, полученный
//                по имени формы и определённому шаблону
//
Функция ОбщийМодульМодификацииОбъекта(ИмяФормы)
    ОМ = Неопределено;
	// Здесь можно настроить собственный шаблон для имен общих модулей:
	Шаблон = "АШ_%1_%2";
	ЧастиИмени = СтрРазделить(ИмяФормы, ".");	
	Если ЧастиИмени.Количество() > 1 Тогда
		ИмяОбщегоМодуля = СтрШаблон(Шаблон, ЧастиИмени[0], ЧастиИмени[1]);
		Попытка
			ОМ = ОбщегоНазначения.ОбщийМодуль(ИмяОбщегоМодуля);
		Исключение
		КонецПопытки
	КонецЕсли;
	Возврат ОМ;
КонецФункции

#КонецОбласти
АШ_СобытияФормКлиент
#Область ПрограммныйИнтерфейс

// Все обработчики в этом интерфейсе должны вызываться из одноименных
// процедур ОМ 'СобытияФормКлиент'

#Область СобытияФормы

Процедура ПриОткрытии(Форма, Отказ) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ПриОткрытии") Тогда
		ОМ.ПриОткрытии(Форма, Отказ);
	КонецЕсли;	
КонецПроцедуры

Процедура ПередЗаписью(Форма, Отказ, ПараметрыЗаписи) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ПередЗаписью") Тогда
		ОМ.ПередЗаписью(Форма, Отказ, ПараметрыЗаписи);
	КонецЕсли;	
КонецПроцедуры

// добавить другие обработчики клиентских событий формы при необходимости

#КонецОбласти

#Область СобытияЭлементовФормы

Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ПриИзмененииЭлемента") Тогда
		ОМ.ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
	КонецЕсли;		
КонецПроцедуры

// здесь будут другие обработчики событий элементов формы

#КонецОбласти

#Область Оповещения

// здесь будет обработчик оповещений

#КонецОбласти

#Область Команды

Процедура ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры) Экспорт
	ОМ = ОбщийМодульМодификацииОбъекта(Форма.ИмяФормы);	
	Если ОМ <> Неопределено И АШ_ОбщегоНазначенияКлиент.МетодСуществует(ОМ, "ВыполнитьПереопределяемуюКоманду") Тогда
		ОМ.ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры);
	КонецЕсли;	
КонецПроцедуры

#КонецОбласти

#КонецОбласти

#Область Служебная

// Функция - Получить общий модуль для модификации объекта
//
// Параметры:
//  ИмяФормы - Строка - Полное имя формы объекта конфигурации
// 
// Возвращаемое значение:
//  ОбщийМодуль - Объект общего модуля (на клиенте) для модификации объекта, полученный
//                по имени формы и определённому шаблону
//
Функция ОбщийМодульМодификацииОбъекта(ИмяФормы)
    ОМ = Неопределено;
	// Здесь можно настроить собственный шаблон для имен общих модулей:
	Шаблон = "АШ_%1_%2Клиент";
	ЧастиИмени = СтрРазделить(ИмяФормы, ".");	
	Если ЧастиИмени.Количество() > 1 Тогда
		ИмяОбщегоМодуля = СтрШаблон(Шаблон, ЧастиИмени[0], ЧастиИмени[1]);
		Попытка
			ОМ = ОбщегоНазначенияКлиент.ОбщийМодуль(ИмяОбщегоМодуля);
		Исключение
		КонецПопытки
	КонецЕсли;
	Возврат ОМ;
КонецФункции

#КонецОбласти
АШ_Документ_РасходныйКассовыйОрдер
#Область ПрограммныйИнтерфейс

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

Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
	
	Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
		ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
	Иначе
		// другие формы документа
	КонецЕсли;

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

#КонецОбласти

#Область Служебная

Процедура ФормаДокументаПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка, ДополнительныеПараметры)
	
	// Пример: программное создание реквизитов формы
	ДобавляемыеРеквизиты = Новый Массив;
	ДобавляемыеРеквизиты.Добавить(
		Новый РеквизитФормы(
			"АШ_ЧисловойРеквизит",
			Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(12, 2, ДопустимыйЗнак.Любой)),,
			"Числовой реквизит"
		)
	);
	ДобавляемыеРеквизиты.Добавить(
		Новый РеквизитФормы(
			"АШ_БулевыйРеквизит",
			Новый ОписаниеТипов("Булево"),,
			"Вкл. числовой реквизит"
		)
	);
	Форма.ИзменитьРеквизиты(ДобавляемыеРеквизиты);
	
	// Пример: программное создание элементов формы
	АШ_ПолеЧисло1 = Форма.Элементы.Добавить(
		"АШ_ПолеЧисло1", Тип("ПолеФормы"), Форма.Элементы.Найти("ГруппаЛево")
	);
	АШ_ПолеЧисло1.Вид = ВидПоляФормы.ПолеВвода;
	АШ_ПолеЧисло1.ПутьКДанным = "АШ_ЧисловойРеквизит";

	АШ_ПолеБулево1 = Форма.Элементы.Добавить(
		"АШ_ПолеБулево1", Тип("ПолеФормы"), Форма.Элементы.Найти("ГруппаЛево")
	);
	АШ_ПолеБулево1.Вид = ВидПоляФормы.ПолеФлажка;
	АШ_ПолеБулево1.ВидФлажка = ВидФлажка.Выключатель;
	АШ_ПолеБулево1.ПутьКДанным = "АШ_БулевыйРеквизит";
	АШ_ПолеБулево1.УстановитьДействие("ПриИзменении", "Подключаемый_ПриИзмененииЭлемента");
	
	// Пример: программное добавление отсутствующего обработчика события для типового поля:
	Форма.Элементы.Выдать.УстановитьДействие("ПриИзменении", "Подключаемый_ПриИзмененииЭлемента");
	
	// Команды
	АШ_Загрузить = Форма.Команды.Добавить("АШ_Загрузить");
	АШ_Загрузить.Заголовок = "Загрузить из Excel";
	АШ_Загрузить.Действие = "Подключаемый_ВыполнитьПереопределяемуюКоманду";
	// Кнопка команды
	АШ_КнопкаЗагрузить = Форма.Элементы.Добавить("АШ_КнопкаЗагрузить", Тип("КнопкаФормы"), Форма.КоманднаяПанель);
	АШ_КнопкаЗагрузить.ИмяКоманды = "АШ_Загрузить";

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

Процедура ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры)
	
	Если Элемент.Имя = "АШ_ПолеБулево1" Тогда
		Если Форма.Объект.Ссылка.Пустая() Тогда
			ОбщегоНазначения.СообщитьПользователю("Документ не сохранен");
		Иначе
			ОбщегоНазначения.СообщитьПользователю("Документ сохранен");
		КонецЕсли;
		ДополнительныеПараметры.Вставить("ТребуетсяЗавершитьНаКлиенте");
	Иначе
		// другие элементы формы
	КонецЕсли;
	
КонецПроцедуры

#КонецОбласти
АШ_Документ_РасходныйКассовыйОрдерКлиент
#Область ПрограммныйИнтерфейс

Процедура ПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры) Экспорт
	
	Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
		ФормаДокументаПриИзмененииЭлемента(Форма, Элемент, ДополнительныеПараметры);
	Иначе
		// другие формы документа
	КонецЕсли;

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

Процедура ВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры) Экспорт
	
	Если Форма.ИмяФормы = "Документ.РасходныйКассовыйОрдер.Форма.ФормаДокумента" Тогда
		ФормаДокументаВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры);
	Иначе
		// другие формы документа
	КонецЕсли;
	
КонецПроцедуры	

#КонецОбласти

#Область Служебная

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

Процедура ФормаДокументаВыполнитьПереопределяемуюКоманду(Форма, Команда, ДополнительныеПараметры)

	ДополнительныеПараметры = ? (ДополнительныеПараметры = Неопределено, Новый Структура, ДополнительныеПараметры);

	Если Команда.Имя = "АШ_Загрузить" Тогда
		ОбщегоНазначенияКлиент.СообщитьПользователю("Нажата кнопка 'Загрузить из файла'");	
		//ДополнительныеПараметры.Вставить("ТребуетсяВыполнитьНаСервере");
	Иначе
		// другие команды формы
	КонецЕсли;
		
КонецПроцедуры
	
#КонецОбласти
Шаблон для вставки в модуль типовой формы
#Область АШ_Доработки

&НаКлиенте
Процедура Подключаемый_АШ_ВыполнитьПереопределяемуюКоманду(Команда)
	ДополнительныеПараметры = Неопределено;
	СобытияФормКлиент.ВыполнитьПереопределяемуюКоманду(ЭтотОбъект, Команда, ДополнительныеПараметры);
	Если ЗначениеЗаполнено(ДополнительныеПараметры) И ДополнительныеПараметры.Свойство("ТребуетсяВыполнитьНаСервере") Тогда
		Подключаемый_АШ_ВыполнитьПереопределяемуюКомандуНаСервере(Команда.Имя, ДополнительныеПараметры);	
	КонецЕсли;
	Если ЗначениеЗаполнено(ДополнительныеПараметры) И ДополнительныеПараметры.Свойство("ТребуетсяЗавершитьНаКлиенте") Тогда
		СобытияФормКлиент.ВыполнитьПереопределяемуюКоманду(ЭтотОбъект, Команда, ДополнительныеПараметры);	
	КонецЕсли;
КонецПроцедуры

&НаСервере
Процедура Подключаемый_АШ_ВыполнитьПереопределяемуюКомандуНаСервере(ИмяКоманды, ДополнительныеПараметры)
	ДополнительныеПараметры.Вставить("ИмяКоманды", ИмяКоманды);
	СобытияФорм.ВыполнитьПереопределяемуюКоманду(ЭтотОбъект, ДополнительныеПараметры);
онецПроцедуры

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

&НаСервере
Процедура Подключаемый_ПриИзмененииЭлементаНаСервере(ИмяЭлемента, ДополнительныеПараметры)
	СобытияФорм.ПриИзмененииЭлемента(ЭтотОбъект, Элементы[ИмяЭлемента], ДополнительныеПараметры);
КонецПроцедуры

#КонецОбласти

Связанные публикации

Программное создание реквизитов управляемой формы 1С

Программное создание элементов управляемой формы 1С

Программное создание команд в управляемой форме 1С