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

Разберем это на практической задаче: дать пользователю выбрать месяц отчета так, как в типовых документах, — поле «Месяц» с кнопками регулирования «влево-вправо».

Почему не своя форма и не правка общей формы

У подсистемы «Варианты отчетов» одна общая форма на все отчеты. В ней панель быстрых настроек, работа с вариантами, рассылки, формирование в фоне. Варианты решения задачи:

ПодходПлюсыМинусы
Собственная форма отчетаПолный контрольТеряется часть функциональности БСП, приходится поддерживать копию
Правка общей формы ФормаОтчета в конфигурацииБыстроКонфликты при каждом обновлении типовой
Программное добавление элемента + расширение формыОбновляется безболезненно, механизм переиспользуетсяНужно немного инфраструктурного кода

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

Шаг 1. Включаем событие ПриСозданииНаСервере в модуле отчета

В модуле объекта отчета, подключенного к подсистеме «Варианты отчетов», есть экспортная процедура ОпределитьНастройкиФормы. В ней можно подписаться на события общей формы.

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

// Настройки общей формы отчета подсистемы «Варианты отчетов».
Процедура ОпределитьНастройкиФормы(Форма, КлючВарианта, Настройки) Экспорт

	Настройки.События.ПриСозданииНаСервере = Истина;

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

// Вызывается из общей формы отчета при создании на сервере.
Процедура ПриСозданииНаСервере(Форма, Отказ, СтандартнаяОбработка) Экспорт

	ДополнительныеПоляОтчетов.ДобавитьПолеМесяца(Форма, "ПериодОтчета", НСтр("ru = 'Месяц'"));

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

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

Отчет сам решает, нужно ли ему поле. Другие отчеты общей формы не затрагиваются.

Шаг 2. Создаем реквизит и элемент программно

Логику создания выносим в серверный общий модуль ДополнительныеПоляОтчетов, чтобы любой отчет мог добавить поле месяца одной строкой.

1С
// Добавляет на общую форму отчета поле выбора месяца.
//
// Параметры:
//   Форма        - ФормаКлиентскогоПриложения - общая форма отчета.
//   ИмяСвойства  - Строка - имя, под которым значение будет доступно при компоновке.
//   Заголовок    - Строка - заголовок поля.
//
Процедура ДобавитьПолеМесяца(Форма, ИмяСвойства, Заголовок) Экспорт

	ИмяРеквизита = ДополнительныеПоляОтчетовКлиентСервер.ИмяРеквизитаМесяца(ИмяСвойства);

	НовыеРеквизиты = Новый Массив;
	НовыеРеквизиты.Добавить(Новый РеквизитФормы(ИмяРеквизита,
		Новый ОписаниеТипов("Дата", , , Новый КвалификаторыДаты(ЧастиДаты.Дата)), , Заголовок));
	Форма.ИзменитьРеквизиты(НовыеРеквизиты);

	Поле = Форма.Элементы.Добавить(ИмяРеквизита, Тип("ПолеФормы"), Форма.Элементы.ПанельБыстрыхНастроек);
	Поле.Вид = ВидПоляФормы.ПолеВвода;
	Поле.ПутьКДанным = ИмяРеквизита;
	Поле.Формат = "ДФ='MMMM yyyy'";
	Поле.ФорматРедактирования = "ДФ=MM.yyyy";
	Поле.КнопкаРегулирования = Истина;
	Поле.Ширина = 14;

	Поле.УстановитьДействие("ПриИзменении", "Подключаемый_ПолеМесяцаПриИзменении");
	Поле.УстановитьДействие("Регулирование", "Подключаемый_ПолеМесяцаРегулирование");

	// Значение по умолчанию и место, откуда его прочитает компоновка.
	ДополнительныеСвойства = Форма.Отчет.КомпоновщикНастроек.ФиксированныеНастройки.ДополнительныеСвойства;
	Значение = Неопределено;
	Если Не ДополнительныеСвойства.Свойство(ИмяСвойства, Значение) Или Не ЗначениеЗаполнено(Значение) Тогда
		Значение = НачалоМесяца(ТекущаяДатаСеанса());
		ДополнительныеСвойства.Вставить(ИмяСвойства, Значение);
	КонецЕсли;
	Форма[ИмяРеквизита] = Значение;

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

И маленький клиент-серверный модуль, чтобы правило именования было в одном месте:

1С
// Модуль ДополнительныеПоляОтчетовКлиентСервер.

Функция ИмяРеквизитаМесяца(ИмяСвойства) Экспорт
	Возврат "ДопМесяц_" + ИмяСвойства;
КонецФункции

Функция ИмяСвойстваПоРеквизиту(ИмяРеквизита) Экспорт
	Возврат СтрЗаменить(ИмяРеквизита, "ДопМесяц_", "");
КонецФункции

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

Добавить поле на форму отчета БСП: новый элемент встраивается в общую форму, не меняя ее
Добавить поле на форму отчета БСП: новый элемент встраивается в общую форму, не меняя ее

Шаг 3. Универсальные обработчики в расширении общей формы

УстановитьДействие лишь связывает событие с именем процедуры. Сама процедура должна существовать в модуле формы, иначе при создании формы будет ошибка. Поэтому создаем расширение, заимствуем в него общую форму ФормаОтчета и добавляем в модуль формы расширения универсальные обработчики. Они ничего не знают о конкретном отчете и только передают управление в клиентский общий модуль.

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

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

Клиентский модуль ДополнительныеПоляОтчетовКлиент:

1С
// Приводит введенную дату к началу месяца и сохраняет ее для компоновки.
Процедура ПолеМесяцаПриИзменении(Форма, Элемент) Экспорт

	Значение = НачалоМесяца(Форма[Элемент.Имя]);
	УстановитьЗначениеМесяца(Форма, Элемент.Имя, Значение);

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

// Листает месяцы кнопками регулирования.
Процедура ПолеМесяцаРегулирование(Форма, Элемент, Направление, СтандартнаяОбработка) Экспорт

	СтандартнаяОбработка = Ложь;
	ТекущееЗначение = Форма[Элемент.Имя];
	Если Не ЗначениеЗаполнено(ТекущееЗначение) Тогда
		ТекущееЗначение = ОбщегоНазначенияКлиент.ДатаСеанса();
	КонецЕсли;
	Значение = ДобавитьМесяц(НачалоМесяца(ТекущееЗначение), Направление);
	УстановитьЗначениеМесяца(Форма, Элемент.Имя, Значение);

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

Процедура УстановитьЗначениеМесяца(Форма, ИмяРеквизита, Значение)

	Форма[ИмяРеквизита] = Значение;
	ИмяСвойства = ДополнительныеПоляОтчетовКлиентСервер.ИмяСвойстваПоРеквизиту(ИмяРеквизита);
	Форма.Отчет.КомпоновщикНастроек.ФиксированныеНастройки.ДополнительныеСвойства.Вставить(ИмяСвойства, Значение);

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

Для нового вида поля (например, выбора организации с особым отбором) добавляется пара процедур в расширение и в клиентский модуль. Отчеты, которые этим полем пользуются, остаются чистыми.

А если хочется полной универсальности

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

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

Явные процедуры в общем модуле надежнее, а писать их приходится не так уж много.

Шаг 4. Читаем значение при формировании отчета

Динамически созданный реквизит формы в объект отчета при формировании не попадает. Поэтому значение мы и кладем в дополнительные свойства фиксированных настроек: они путешествуют вместе с компоновщиком. В модуле отчета:

1С
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)

	СтандартнаяОбработка = Ложь;

	МесяцОтчета = Неопределено;
	ДополнительныеСвойства = КомпоновщикНастроек.ФиксированныеНастройки.ДополнительныеСвойства;
	Если Не ДополнительныеСвойства.Свойство("ПериодОтчета", МесяцОтчета) Или Не ЗначениеЗаполнено(МесяцОтчета) Тогда
		МесяцОтчета = НачалоМесяца(ТекущаяДатаСеанса());
	КонецЕсли;

	Настройки = КомпоновщикНастроек.ПолучитьНастройки();
	Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("НачалоПериода", НачалоМесяца(МесяцОтчета));
	Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("КонецПериода", КонецМесяца(МесяцОтчета));

	КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
	МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);

	ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
	ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки, Истина);

	ДокументРезультат.Очистить();
	ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
	ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
	ПроцессорВывода.Вывести(ПроцессорКомпоновки);

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

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

Вариант для ЗУП и других конфигураций на БЗК

В конфигурациях на библиотеке зарплатно-кадровых решений есть готовый механизм ввода месяца, как в документах: строковое поле и процедуры модуля ЗарплатаКадрыКлиент с префиксом ВводМесяца для событий начала выбора, регулирования, автоподбора и окончания ввода текста. Если вы работаете в такой конфигурации, удобнее вызывать их из клиентского модуля вместо собственного кода: поле будет выглядеть и вести себя точно как в типовых документах. Сигнатуры процедур сверяйте с вашей версией библиотеки.

Типичные ошибки

  • Ошибка при открытии формы «процедура не найдена». Имя в УстановитьДействие не совпадает с процедурой в расширении формы или расширение не применено.
  • Значение не доходит до отчета. Читается реквизит формы, которого нет в объекте отчета. Используйте свойства настроек компоновщика.
  • Поле пропадает после смены варианта. Общая форма пересоздает часть элементов при переключении вариантов. Проверяйте поведение и при необходимости повторно вызывайте добавление поля; значение по умолчанию в ПриКомпоновкеРезультата должно браться и при отсутствии свойства.
  • Конфликт с обновлением. Изменения внесены прямо в общую форму конфигурации. Переносите их в расширение.
  • Регулирование листает дни, а не месяцы. Не выставлено СтандартнаяОбработка = Ложь в обработчике регулирования.

Доработку отчетов на БСП и перенос изменений типовой в расширения я делаю в рамках услуг отчеты на СКД и расширения 1С.

Итог

  • Добавить поле на форму отчета БСП можно без собственной формы: через ОпределитьНастройкиФормы и ПриСозданииНаСервере.
  • Реквизит и элемент создаются программно, обработчики событий живут в расширении общей формы ФормаОтчета.
  • Обработчики делаем универсальными и отправляем в клиентский общий модуль, без Выполнить.
  • Значение передается в компоновку через дополнительные свойства настроек и читается в ПриКомпоновкеРезультата.