Как обратиться из модуля объекта к процедуре формы документа и почему так делать нельзя

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

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

Архитектурное разделение: почему модуль объекта не должен знать о форме

Платформа 1С:Предприятие построена на строгом разделении уровней визуализации (интерфейса) и бизнес-логики данных. Проанализируем ключевые причины, делающие вызов формы из объекта невозможным:

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

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

Часто попытка вызвать форму возникает из-за того, что разработчик перенес код запроса из формы в модуль объекта, но в модуле объекта запрос перестал возвращать данные (не заходит в цикл обработки). Выясним основные причины, из-за которых одинаковый запрос может вести себя по-разному на форме и в модуле объекта:

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

Как исправить неоптимальный код выборки и обработки данных

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


// Неэффективный подход: ручное создание колонок, цикл и свертка внутри обхода
КвалификаторыЧисла = Новый КвалификаторыЧисла(10, 2, ДопустимыйЗнак.Любой);     
ТЗВирт = Новый ТаблицаЗначений;
ТЗВирт.Колонки.Добавить("ОрганизацияОтправитель", Новый ОписаниеТипов("СправочникСсылка.Организации"));
ТЗВирт.Колонки.Добавить("Период", Новый ОписаниеТипов("Дата"));
ТЗВирт.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", КвалификаторыЧисла));

// Выполнение запроса и ручная перегрузка
Результат = Запрос.Выполнить().Выгрузить();
Для Каждого Стр Из Результат Цикл
    ЗаполнитьЗначенияСвойств(ТЗВирт.Добавить(), Стр);
    ТЗВирт.Свернуть("ОрганизацияОтправитель", "Количество"); // Ошибка: нельзя сворачивать таблицу в цикле
КонецЦикла;

Разберем допущенные здесь ошибки:

  1. Ручное создание колонок таблицы значений совершенно избыточно, так как метод Выгрузить() объекта РезультатЗапроса автоматически создает таблицу значений с правильными типами и именами колонок.
  2. Вызов метода Свернуть() на каждой итерации цикла многократно замедляет работу системы. Свертка — это групповая операция, которая выполняется один раз для всей таблицы целиком.
  3. Лишние поля в выборке: если поле Период нужно только для отбора в условии ГДЕ, его не нужно помещать в секцию ВЫБРАТЬ, иначе оно мешает корректной группировке данных.

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

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

Рассмотрим реализацию проверки в модуле объекта с получением уже сгруппированных данных напрямую из СУБД:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Формируем запрос с агрегацией на уровне базы данных
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Услуги.ОрганизацияОтправитель КАК ОрганизацияОтправитель,
        |   СУММА(Услуги.Количество) КАК Количество
        |ИЗ
        |   РегистрНакопления.УслугиПодразделениям КАК Услуги
        |ГДЕ
        |   Услуги.Период МЕЖДУ &НачалоПериода И &КонецПериода
        |   И Услуги.ОрганизацияОтправитель = &ОрганизацияОтправитель
        |СГРУППИРОВАТЬ ПО
        |   Услуги.ОрганизацияОтправитель
        |ИМЕЮЩИЕ
        |   СУММА(Услуги.Количество) > 1";
    
    Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ЭтотОбъект.Дата));
    Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ЭтотОбъект.Дата));
    Запрос.УстановитьПараметр("ОрганизацияОтправитель", ЭтотОбъект.Организация);
    
    Выборка = Запрос.Выполнить().Выбрать();
    
    Пока Выборка.Следующий() Цикл
        // Формируем платформенное сообщение пользователю
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = СтрШаблон(
            "Превышен лимит количества по организации %1. Текущее значение: %2", 
            Выборка.ОрганизацияОтправитель, 
            Выборка.Количество
        );
        Сообщение.Поле = "Организация";
        Сообщение.УстановитьДанные(ЭтотОбъект);
        Сообщение.Сообщить();
    КонецЦикла;

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

Как переиспользовать общий код между формой и объектом

Если один и тот же расчет или проверку необходимо выполнять и на форме документа (например, при нажатии кнопки проверки или изменении реквизита), и в модуле объекта при проведении, применим модуль менеджера документа:

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

Посмотрим на пример оформления такой процедуры в модуле менеджера:


// В модуле менеджера документа
Процедура ПроверитьПревышениеЛимитов(Организация, ДатаДокумента, Отказ = Ложь) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Услуги.ОрганизацияОтправитель КАК ОрганизацияОтправитель,
        |   СУММА(Услуги.Количество) КАК Количество
        |ИЗ
        |   РегистрНакопления.УслугиПодразделениям КАК Услуги
        |ГДЕ
        |   Услуги.Период МЕЖДУ &НачалоПериода И &КонецПериода
        |   И Услуги.ОрганизацияОтправитель = &ОрганизацияОтправитель
        |СГРУППИРОВАТЬ ПО
        |   Услуги.ОрганизацияОтправитель
        |ИМЕЮЩИЕ
        |   СУММА(Услуги.Количество) > 1";
        
    Запрос.УстановитьПараметр("НачалоПериода", НачалоМесяца(ДатаДокумента));
    Запрос.УстановитьПараметр("КонецПериода", КонецМесяца(ДатаДокумента));
    Запрос.УстановитьПараметр("ОрганизацияОтправитель", Организация);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = "Обнаружено превышение допустимого объема услуг!";
        Сообщение.Сообщить();
        Отказ = Истина;
    КонецЦикла;

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

Теперь в модуле объекта достаточно написать краткий вызов:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    Документы.АктОбОказанииУслуг.ПроверитьПревышениеЛимитов(ЭтотОбъект.Организация, ЭтотОбъект.Дата, Отказ);
    
    Если Отказ Тогда
        Возврат;
    КонецЕсли;
    
КонецПроцедуры

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

← На главную