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

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

При программном создании и заполнении документов в 1С часто возникает задача репликации логики, которая обычно срабатывает при интерактивном изменении реквизитов формы. Например, при выборе договора контрагента в форме документа автоматически заполняются валюта, процент аванса и другие связанные поля. Разработчики сталкиваются с вопросом: как вызвать эту логику из серверной обработки, где форма документа не существует?

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

Разделение клиентской и серверной логики в 1С

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

  1. Клиентский контекст: Это среда, в которой выполняется код, взаимодействующий непосредственно с пользовательским интерфейсом (формой), элементами управления, реакцией на действия пользователя (клики, изменение полей). Примеры клиентских процедур – ПриИзменении, ОбработкаВыбора, ПриОткрытии формы. Код в этом контексте не имеет прямого доступа к базе данных и не может выполнять длительные операции без обращения к серверу.

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

Критически важно: прямой вызов клиентской процедуры из серверной невозможен, так же как и наоборот. Они существуют в разных "мирах" и общаются между собой строго определенными механизмами (например, через директивы компиляции НаСервере, НаСервереБезКонтекста для серверных вызовов из клиента).

Почему не получается "эмулировать изменение" реквизита формы

Автор вопроса справедливо замечает, что для вызова процедуры УФ (управляемой формы) нужно получить саму УФ, что возможно только на клиенте. Когда мы программно создаем документ в обработке, которая выполняется на сервере, никакой формы не существует. Мы работаем непосредственно с объектом базы данных (например, с экземпляром ДокументОбъект.РеализацияТоваровУслуг), а не с его представлением на форме.

Поэтому попытка "эмулировать" изменение реквизита формы, такого как ДоговорКонтрагентаПриИзменении(), изначально ошибочна. Эта процедура привязана к элементу формы, а не к логике самого объекта данных. На сервере нет элементов формы, нет "изменений", которые можно было бы "эмулировать" в контексте формы.

Правильное решение: перенос бизнес-логики в модуль объекта или общие модули

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

Давайте рассмотрим преимущества такого подхода:

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

  2. Единообразие поведения: Поведение документа будет идентичным независимо от того, как он был изменен – вручную через форму или автоматически через код.

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

  4. Соответствие архитектуре: Это соответствует рекомендованным практикам разработки в 1С, разделяющим представление (форма) и данные (объект).

Для реализации этого подхода мы будем использовать:

Пошаговая реализация заполнения данных по договору

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

1. Идентифицируем и извлекаем существующую логику

Первым делом нам нужно найти, какая именно серверная логика заполняет данные по договору. Откройте модуль формы документа и найдите процедуру-обработчик события ДоговорКонтрагентаПриИзменении. Внутри этой процедуры вы, скорее всего, увидите вызов другой процедуры, которая уже содержит директиву НаСервере или НаСервереБезКонтекста, либо напрямую содержит код получения данных по договору. Ваша задача – найти именно эту серверную часть логики.

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


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

&НаСервере
Процедура ЗаполнитьДанныеПоДоговоруНаСервере()
    Объект.ЗаполнитьДанныеПоДоговоруВОбъекте(); // Вот эту процедуру мы и ищем!
    // ... возможно, здесь еще какие-то действия с реквизитами формы ...
КонецПроцедуры

Или напрямую:


&НаКлиенте
Процедура ДоговорКонтрагентаПриИзменении(Элемент)
    ЗаполнитьДанныеПоДоговору(Объект);
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьДанныеПоДоговору(ДокументОбъект)
    // Здесь должна быть логика получения данных по договору
    // ...
КонецПроцедуры

Наша цель – найти или создать такую серверную процедуру, которая принимает объект документа и выполняет все необходимые действия по заполнению его реквизитов.

2. Создаем или модифицируем серверную процедуру в модуле объекта

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

Мы будем работать с документом, например, Документ.РеализацияТоваровУслуг. Откроем его модуль объекта.


// В модуле объекта документа (например, Документ.РеализацияТоваровУслуг)

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

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

        КонецЕсли;
    КонецЕсли;

    // Важный момент: эта процедура НЕ должна содержать очистку табличной части,
    // если только это не является явным требованием вашей бизнес-логики,
    // которое должно срабатывать именно при изменении договора.
    // Если очистка ТЧ должна происходить только при выборе контрагента,
    // это должна быть отдельная логика.

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

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

3. Вызов из клиентского обработчика формы

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


// В модуле формы документа

&НаКлиенте
Процедура ДоговорКонтрагентаПриИзменении(Элемент)
    // Перед вызовом серверной процедуры, сохраним текущее состояние объекта
    // Если требуется интерактивное обновление формы после серверных действий
    ЗаполнитьДанныеШапкиПоДоговоруНаСервере();
КонецПроцедуры

// Серверная процедура, вызываемая из клиента.
// Она не имеет прямого доступа к элементам формы, но имеет доступ к Объекту.
&НаСервере
Процедура ЗаполнитьДанныеШапкиПоДоговоруНаСервере()
    // Вызываем экспортную процедуру модуля объекта
    Объект.ЗаполнитьДанныеШапкиПоДоговору();
    // Здесь можем обновить какие-то данные формы, если они не обновляются автоматически
    // Например, если реквизит формы ПроцентАванса (не объекта) должен быть обновлен.
    // Но для реквизитов объекта это не требуется, они обновятся при чтении объекта.
КонецПроцедуры

Теперь при интерактивном изменении договора, клиентская процедура вызовет серверную, которая в свою очередь вызовет логику в модуле объекта. Это обеспечивает единое поведение.

4. Вызов из серверной обработки (где создается документ)

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


// В модуле серверной обработки или в общем модуле с директивой &НаСервере

// Функция или процедура, где создается и заполняется документ
Процедура СоздатьИЗаполнитьДокументПрограммно()

    НовыйДокумент = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
    НовыйДокумент.Дата = ТекущаяДата();
    // ... заполняем другие обязательные реквизиты шапки ...

    // Устанавливаем договор контрагента
    // Получим ссылку на нужный договор (например, по наименованию или ИД)
    ЗапросДоговор = Новый Запрос;
    ЗапросДоговор.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1 Ссылка ИЗ Справочник.ДоговорыКонтрагентов ГДЕ Наименование = &НаименованиеДоговора";
    ЗапросДоговор.УстановитьПараметр("НаименованиеДоговора", "Основной договор");
    РезультатЗапроса = ЗапросДоговор.Выполнить().Выбрать();

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

    // ***** Ключевой момент: вызываем нашу процедуру заполнения из модуля объекта *****
    // После установки ДоговорКонтрагента, вызываем логику заполнения
    НовыйДокумент.ЗаполнитьДанныеШапкиПоДоговору();

    // Теперь, когда шапка заполнена, можем заполнять табличную часть,
    // не опасаясь, что она будет очищена логикой договора.
    НоваяСтрокаТЧ = НовыйДокумент.Товары.Добавить();
    НоваяСтрокаТЧ.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию("Товар 1");
    НоваяСтрокаТЧ.Количество = 10;
    НоваяСтрокаТЧ.Цена = 100;
    // ... другие поля ТЧ ...

    // ... заполняем остальные табличные части и реквизиты ...

    // Записываем документ
    Попытка
        НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение); // Или РежимЗаписиДокумента.Запись
        Сообщить("Документ " + НовыйДокумент.Номер + " от " + НовыйДокумент.Дата + " успешно создан и проведен.");
    Исключение
        Сообщить("Ошибка при создании/записи документа: " + ОписаниеОшибки());
    КонецПопытки;

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

Как видите, мы получили объект документа (НовыйДокумент) и напрямую вызвали его экспортный метод ЗаполнитьДанныеШапкиПоДоговору(). Это полностью обходит необходимость в форме и "эмуляции".

Избегаем нежелательной очистки табличной части

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

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

Использование стандартных подсистем БСП

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

Контроль актуальности данных

При программном заполнении документа мы всегда должны быть уверены, что все зависимые данные (такие как валюта, процент аванса, курсы валют) актуальны на момент проведения документа и соответствуют выбранному договору, даже если пользователь не взаимодействовал с формой. В нашей процедуре ЗаполнитьДанныеШапкиПоДоговору() мы можем явно получить актуальные данные (например, текущий курс валюты на дату документа) при помощи запросов, а не полагаться на потенциально устаревшие данные из самого справочника "ДоговорыКонтрагентов".

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

← На главную