Как объединить и одновременно редактировать данные из 15 различных документов в одной управляемой форме 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разработчики 1С:Предприятия часто сталкиваются с нестандартными требованиями клиентов, которые порой кажутся невыполнимыми или, по крайней мере, крайне сложными для реализации. Одна из таких задач — необходимость объединить функциональность и данные нескольких разных документов в одной общей форме. Представьте ситуацию: клиент желает видеть на форме одного документа (назовем его Документ X) данные и элементы управления для целых 15 различных типов документов (Документ Y), причем эти документы Y могут сильно отличаться по составу реквизитов и процедур. И самое главное — необходимо иметь возможность редактировать их одновременно, не дублируя при этом логику каждого из 15 типов Документа Y.

Данная задача представляет собой серьезный вызов, поскольку в платформе 1С:Предприятие нет стандартного механизма для прямого "встраивания" одной управляемой формы в другую. Однако, используя программные возможности платформы и нестандартные подходы, можно приблизиться к желаемому результату. Рассмотрим подробно различные варианты решения этой сложной задачи.

1. Программное создание элементов и реквизитов формы

Один из наиболее гибких подходов для реализации динамического интерфейса — это программное создание реквизитов и элементов формы. Этот метод позволяет нам формировать интерфейс формы Документа X в зависимости от того, какой из 15 типов Документа Y должен быть отображен в данный момент. Это хорошо согласуется с требованием иметь 15 разных документов с различным составом реквизитов, поскольку мы можем динамически адаптировать интерфейс.

  1. Создание реквизитов формы: Мы можем использовать метод Новый РеквизитФормы() для программного добавления реквизитов на форму Документа X. Каждый такой реквизит будет представлять собой поле для хранения данных одного из Документов Y. Мы указываем имя, тип, путь к данным (если реквизит связан с существующим объектом) и заголовок реквизита. Например, для хранения ссылки на текущий Документ Y мы можем создать реквизит типа «ДокументСсылка.ТотИлиИнойДокументY».
  2. Добавление элементов формы: После создания реквизитов мы используем методы Элементы.Вставить() или Элементы.Добавить() для размещения визуальных элементов на форме. Это могут быть ПолеФормы для отображения значений реквизитов, ГруппаФормы для организации элементов во вкладки или панели, а также кнопки для вызова специфических действий. Мы привязываем эти элементы к созданным программно реквизитам.

Как это работает на практике:

При выборе типа Документа Y или загрузке Документа X, в зависимости от контекста, мы программно формируем необходимые реквизиты и элементы. Для каждого из 15 типов Документа Y у нас будет свой набор правил, какие реквизиты и элементы нужно создать. Например, если мы работаем с Документом Y1, мы создадим реквизиты и поля для его специфических данных. Если переключились на Документ Y2, старые элементы можем удалить (если они были созданы программно) и создать новые, соответствующие Документу Y2.

Пример концептуального кода для программного создания реквизита и элемента:


// В модуле формы Документа X на сервере
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Допустим, у нас есть параметр формы, который указывает тип Документа Y
    // или мы определяем его по данным Документа X
    ТипДокументаY = Параметры.ТипДокументаY; // Или получаем из ДокументОбъект.Ссылка

    Если ТипДокументаY = "Документ.ПоступлениеТоваровУслуг" Тогда
        // Создаем реквизит для ссылки на Документ Y
        НовыйРеквизит = Новый РеквизитФормы("ДокументYСсылка", Новый ОписаниеТипов("ДокументСсылка.ПоступлениеТоваровУслуг"), "Объект");
        ЭтаФорма.ДобавитьРеквизиты({НовыйРеквизит});

        // Создаем элемент формы (поле ввода) для этого реквизита
        НовыйЭлемент = Элементы.Добавить("ПолеДокументYСсылка", Тип("ПолеФормы"), Элементы.ГруппаОсновная);
        НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
        НовыйЭлемент.ПутьКДанным = "ДокументYСсылка";
        НовыйЭлемент.Заголовок = "Документ Поступления";
        НовыйЭлемент.УстановитьДействие("ПриИзменении", "ПолеДокументYСсылкаПриИзменении");

        // ... и так далее для других реквизитов документа Y
        // Например, для табличной части
        НовыйРеквизитТЧ = Новый РеквизитФормы("ДокументYТабличнаяЧасть", Новый ОписаниеТипов("ТаблицаЗначений"), "Объект");
        ЭтаФорма.ДобавитьРеквизиты({НовыйРеквизитТЧ});

        НовыйЭлементТЧ = Элементы.Добавить("ТаблицаДокументY", Тип("ТаблицаФормы"), Элементы.ГруппаОсновная);
        НовыйЭлементТЧ.ПутьКДанным = "ДокументYТабличнаяЧасть";
        НовыйЭлементТЧ.Заголовок = "Табличная часть Документа Y";
        // Здесь потребуется программно добавлять колонки в ТаблицуФормы
        // и заполнять данные
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура ПолеДокументYСсылкаПриИзменении(Элемент)
    // Обработка изменения ссылки на Документ Y
    // Возможно, нужно загрузить данные Документа Y в другие реквизиты формы X
    // или открыть его форму для детального редактирования.
КонецПроцедуры

Преимущества:

  1. Гибкость: Полностью динамическое формирование интерфейса, адаптированное под любой из 15 типов Документа Y.
  2. Отсутствие дублирования UI: Мы не дублируем элементы управления в конфигураторе для каждого из 15 Документов Y.

Ограничения и сложности:

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

2. Использование внешних обработок или расширений для интеграции логики

Для соблюдения требования "не дублировать процедуры и функции документов Y" и минимизации изменений в основной конфигурации, использование внешних обработок или расширений становится крайне привлекательным. Этот подход позволяет централизовать логику, относящуюся к Документам Y, и динамически подключать ее к форме Документа X.

Как это работает:

  1. Расширение конфигурации: Мы можем создать расширение, в котором переопределим форму Документа X (или просто добавим в расширение необходимые общие модули). В этом расширении мы можем программно добавлять реквизиты и элементы на форму Документа X, как описано выше. Главное преимущество расширения в том, что оно не затрагивает основную конфигурацию, что значительно облегчает обновления.
  2. Общие модули и функции: Логику, специфичную для Документов Y (например, расчеты, проверки, заполнение табличных частей), можно вынести в общие модули расширения или обработки. Эти модули будут принимать в качестве параметра ссылку на объект Документа Y или его данные и выполнять нужные действия.
  3. Перехват событий формы: Через расширение можно перехватывать события формы Документа X (например, ПриСозданииНаСервере, ПередЗаписью) и в них вызывать функции из общих модулей, которые будут работать с данными Документов Y. Это позволяет нам интегрировать логику Документов Y без физического дублирования кода в модуле формы Документа X.

Пример концепции:


// В модуле формы Документа X в расширении (или во внешней обработке)
&НаСервере
Процедура Расширение_ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Добавление реквизитов и элементов, как в примере выше
    // ...

    // Загрузка данных Документа Y
    // Допустим, в реквизит формы X уже загружена ссылка на Документ Y
    Если ЗначениеЗаполнено(ДокументYСсылка) Тогда
        ОбъектДокументаY = ДокументYСсылка.ПолучитьОбъект();
        // Вызов функции из общего модуля (например, в расширении)
        // для заполнения программно созданных элементов формы X
        // данными из ОбъектДокументаY
        ОбщийМодульРасширения.ЗаполнитьДанныеДокументаY(ЭтаФорма, ОбъектДокументаY);
    КонецЕсли;
КонецПроцедуры

// В ОбщемМодулеРасширения
&НаСервере
Процедура ЗаполнитьДанныеДокументаY(Форма, ДокументYОбъект)
    // В зависимости от типа ДокументYОбъект, заполняем реквизиты формы
    Если ТипЗнч(ДокументYОбъект) = Тип("ДокументОбъект.ПоступлениеТоваровУслуг") Тогда
        Форма.Реквизит1ДокументаY = ДокументYОбъект.Номер;
        Форма.Реквизит2ДокументаY = ДокументYОбъект.Дата;
        // Заполнение табличной части
        Форма.ДокументYТабличнаяЧасть.Очистить();
        Для Каждого СтрокаТЧ Из ДокументYОбъект.Товары Цикл
            НоваяСтрока = Форма.ДокументYТабличнаяЧасть.Добавить();
            НоваяСтрока.Номенклатура = СтрокаТЧ.Номенклатура;
            НоваяСтрока.Количество = СтрокаТЧ.Количество;
            // ...
        КонецЦикла;
    ИначеЕсли ТипЗнч(ДокументYОбъект) = Тип("ДокументОбъект.РеализацияТоваровУслуг") Тогда
        // ... своя логика заполнения для этого типа документа
    КонецЕсли;
КонецПроцедуры

Преимущества:

  1. Минимизация изменений в типовой конфигурации: Использование расширений позволяет добавлять реквизиты, элементы и логику без снятия основной конфигурации с поддержки, что значительно облегчает обновления.
  2. Централизация кода: Логика, общая для нескольких или всех 15 Документов Y, может быть вынесена в общие модули расширения, избегая дублирования.
  3. Повторное использование: Если в Документах Y уже есть готовые процедуры, их можно вызывать из расширения, передавая им необходимые параметры.

Ограничения:

  1. Сложность разработки: Создание и отладка сложных расширений требуют высокой квалификации.
  2. Производительность: Динамические операции всегда имеют накладные расходы.

3. Открытие нескольких форм объектов в отдельных окнах (компромиссный вариант)

Если требование "единой формы" не является абсолютно жестким и допустимо соседство окон, то самым простым и стандартным решением будет открытие форм Документов Y в отдельных окнах рядом с формой Документа X. Это не совсем то, что хочет клиент, но это самый легкий в реализации способ.

Как это работает:

Для открытия формы каждого Документа Y мы используем стандартный метод ОткрытьФорму(). Чтобы каждый документ открывался в отдельном окне (а не замещал предыдущий, если они одного типа), мы можем передавать уникальный идентификатор в параметре КлючУникальности.


// В модуле формы Документа X
&НаКлиенте
Процедура ОткрытьФормуДокументаY(Команда)
    // Допустим, СсылкаНаДокументY - это реквизит формы X, содержащий нужную ссылку
    Если ЗначениеЗаполнено(Объект.СсылкаНаДокументY) Тогда
        ПараметрыОткрытия = Новый Структура("КлючУникальности", Новый УникальныйИдентификатор());
        ОткрытьФорму("Документ." + Объект.СсылкаНаДокументY.Метаданные().Имя + ".ФормаОбъекта", ПараметрыОткрытия);
    Иначе
        ПоказатьПредупреждение(,"Выберите документ Y для открытия.");
    КонецЕсли;
КонецоПроцедура

Преимущества:

  1. Простота реализации: Используется стандартный функционал платформы, нет необходимости в сложных программных манипуляциях с элементами формы.
  2. Полная функциональность: Каждая форма Документа Y открывается со всей своей стандартной логикой, кнопками и обработчиками, без всяких "костылей".

Ограничения:

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

4. "Вложенные формы" через программный "разбор" и воссоздание (XML-слияние)

Этот подход является одним из наиболее "безумных", но потенциально позволяющих максимально приблизиться к визуализации нескольких форм в одной. Он упоминается в обсуждении как "решение Гения" и отсылает к сложным программным манипуляциям, например, через XML-представление форм. Суть идеи заключается не в прямом встраивании формы как объекта, а в программной имитации ее элементов и функциональности на родительской форме.

Как это работает (концепция):

  1. Выгрузка форм в XML: Мы можем получить XML-представление формы каждого Документа Y. Это XML содержит описание всех элементов, реквизитов, команд и даже часть кода модуля формы.
  2. Парсинг XML: Программно анализируем этот XML, чтобы получить структуру формы: какие реквизиты, какие элементы (поля, таблицы, группы), какие обработчики событий есть в дочерней форме.
  3. Программное воссоздание на родительской форме: На форме Документа X мы динамически создаем элементы и реквизиты, которые имитируют структуру формы Документа Y. Это означает, что для каждого поля Документа Y мы создаем соответствующее ПолеФормы на Документе X, для каждой табличной части — ТаблицуФормы и так далее.
  4. Синхронизация данных и событий: Это наиболее сложная часть. При изменении данных в "имитированных" полях Документа X необходимо программно обновлять данные в реальном объекте Документа Y. Аналогично, вызовы процедур Документа Y должны транслироваться через программный код на форме Документа X к соответствующим методам объекта Документа Y или его модулей.
  5. Переименование элементов и методов: Если мы "сливаем" несколько форм, у нас могут возникнуть конфликты имен реквизитов, элементов, команд и процедур. Придется программно переименовывать их при создании, чтобы избежать коллизий.

Пример концепции:


// Это очень упрощенный и концептуальный пример.
// Реализация будет значительно сложнее.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Допустим, мы читаем XML-описание формы Документа Y
    XMLФормыY = ОбщийМодуль.ПолучитьXMLФормы("Документ.ПоступлениеТоваровУслуг.ФормаОбъекта");
    
    // Парсим XML и создаем реквизиты и элементы на текущей форме
    Для Каждого ОписаниеРеквизита Из ОбщийМодуль.РазобратьРеквизитыИзXML(XMLФормыY) Цикл
        НовыйРеквизит = Новый РеквизитФормы(ОписаниеРеквизита.Имя, ОписаниеРеквизита.Тип, "ОбъектДокументаY." + ОписаниеРеквизита.ПутьКДанным);
        ЭтаФорма.ДобавитьРеквизиты({НовыйРеквизит});
    КонецЦикла;

    Для Каждого ОписаниеЭлемента Из ОбщийМодуль.РазобратьЭлементыИзXML(XMLФормыY) Цикл
        НовыйЭлемент = Элементы.Добавить(ОписаниеЭлемента.Имя, ОписаниеЭлемента.Тип, Элементы.ОсновнаяГруппа);
        НовыйЭлемент.ПутьКДанным = ОписаниеЭлемента.ПутьКДанным;
        // ... настройка других свойств элемента и привязка обработчиков
    КонецЦикла;
КонецПроцедуры

Проблемы и ограничения:

  1. Основной реквизит: В 1С у формы может быть только один основной реквизит. Если и в Документе X, и в Документе Y есть свои основные реквизиты, это создаст большие трудности при слиянии.
  2. Одноименные параметры, элементы, реквизиты, команды: Придется реализовывать сложную логику переименования, чтобы избежать конфликтов.
  3. Обработчики событий: Перехват и трансляция всех обработчиков событий (ПриИзменении, ПриНачалеРедактирования и т.д.) будут чрезвычайно трудоемкими и потребуют написания большого объема кода-посредника.
  4. Производительность: Решение может быть очень медленным и ресурсоемким.
  5. Сложность разработки и поддержки: Это "костыль", требующий глубочайшего понимания платформы и крайне сложный в отладке и дальнейшей поддержке, особенно при обновлении конфигурации.

5. Передача данных и событий между формами для связывания логики

Независимо от выбранного подхода (программное создание элементов, XML-слияние или даже открытие отдельных окон), критически важно обеспечить эффективную передачу данных и вызов процедур между Документом X и Документами Y. Это позволяет нам не дублировать логику Документов Y, а использовать уже существующие в них обработчики и алгоритмы.

Как это работает:

  1. Параметры открываемой формы: Если мы открываем форму Документа Y (даже если она "вложена" или имитирована), мы можем передать ей структуру с данными или контекст родительской формы (Документа X) через параметры функции ОткрытьФорму(). Это позволяет дочерней форме знать, откуда она была вызвана и какие данные ей нужны.
  2. Метод Оповестить() и обработчик ОбработкаОповещения: Это стандартный механизм платформы 1С для обмена данными и вызова процедур между открытыми формами. Когда на одной форме происходит событие, которое должно повлиять на другую форму, мы вызываем метод Оповестить() на исходной форме, передавая ей имя события, параметры и источник. Принимающая форма, в свою очередь, реализует обработчик события ОбработкаОповещения(), который анализирует пришедшее оповещение и выполняет необходимые действия.
  3. Общие переменные/реквизиты формы: В некоторых случаях можно использовать общие переменные модуля формы или программно созданные реквизиты формы для временного хранения объектов Документов Y или их данных. Например, реквизит формы Документа X может хранить объект ДокументОбъект.ПоступлениеТоваровУслуг, и тогда мы напрямую работаем с его свойствами и вызываем его методы (например, Записать(), Провести()).

Применение:

Эти механизмы критически важны для реализации сценария, когда данные из Документа Y должны влиять на Документ X (например, при изменении суммы в Документе Y, обновить итог в Документе X) или наоборот, или когда необходимо вызвать специфические процедуры из Документов Y (например, провести документ Y, распечатать его форму), не дублируя их код в Документе X. Мы просто вызываем существующую логику объекта Документа Y, который находится в памяти.


// В модуле формы Документа X (например, после изменения данных Документа Y)
&НаКлиенте
Процедура ПослеИзмененияДанныхДокументаY()
    // Предположим, мы изменили какие-то данные в "виртуальном" Документе Y
    // и хотим, чтобы другие формы, связанные с этим Документом Y, об этом узнали.
    Оповестить("ДокументYИзменен", Объект.СсылкаНаДокументY, ЭтаФорма);
КонецПроцедуры

// В модуле формы Документа X (для обработки оповещений от других форм)
// Или в модуле формы Документа Y, если он сам генерирует оповещения
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
    Если ИмяСобытия = "ДокументYИзменен" Тогда
        СсылкаНаИзмененныйДокументY = Параметр;
        Если СсылкаНаИзмененныйДокументY = Объект.СсылкаНаДокументY Тогда
            // Обновить данные на текущей форме Документа X,
            // если изменение касается связанного Документа Y.
            ОбновитьОтображениеДанныхДокументаYНаФорме();
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Заключение

Реализация задачи по объединению 15 различных документов в одной управляемой форме 1С:Предприятия является крайне трудоемкой и не имеет простого "коробочного" решения. Наиболее перспективные подходы, с учетом требования "не дублировать процедуры и функции документов Y", включают в себя:

  1. Комбинацию программного создания реквизитов и элементов формы на родительской форме Документа X для динамического формирования интерфейса.
  2. Использование внешних обработок или расширений для централизации логики Документов Y и вызова их процедур без дублирования.
  3. Активное применение механизмов передачи данных и событий между формами (Оповестить(), параметры форм) для синхронизации и вызова специфических процедур Документов Y.

Подход с "XML-слиянием" форм, хотя и выглядит технически возможным, является "тяжелым костылем", требующим огромных затрат на разработку и поддержку, и его следует рассматривать как крайнюю меру. Открытие нескольких форм в отдельных окнах может быть компромиссом, если клиент готов пойти на уступки в части "единой формы".

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

← На главную