Как программно заполнить договор контрагента при обмене данными в 1С без вызова процедур формы

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

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

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

Почему нельзя вызывать код формы при обмене данными?

Для того чтобы понять ограничения, давайте проанализируем, как устроен процесс обмена данными в платформе 1С:Предприятие 8.3:

  1. Отсутствие контекста отображения (экранных форм): Все процессы обмена данными (будь то через универсальный формат EnterpriseData, планы обмена или правила конвертации КД 2.1) выполняются в фоновом режиме на стороне сервера. В этот момент интерактивный интерфейс отсутствует, формы документов физически не создаются и не открываются. Соответственно, обратиться к элементам формы или вызвать экспортные процедуры модуля формы технически невозможно.
  2. Режим загрузки данных: При записи объектов, созданных или измененных в процессе обмена, свойству объекта всегда присваивается признак ОбменДанными.Загрузка = Истина. Этот режим предназначен для того, чтобы объект записался «как есть», без выполнения лишних проверок, пересчетов и подстановок, которые могут нарушить ссылочную целостность или замедлить процесс загрузки. Пытаться обойти этот режим и эмулировать поведение пользователя на форме — грубое нарушение стандартов разработки 1С.

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

Где именно прописать код автозаполнения договора?

Если для интеграции вы используете Конвертацию данных 2.1, то наиболее подходящим местом для реализации алгоритма является обработчик «После загрузки» в Правилах конвертации объектов (ПКО) конкретного документа. В этом обработчике нам уже доступен полностью сформированный и заполненный объект (переменная Объект), но он еще не записан в базу данных.

Если используется Конвертация данных 3.0 (на базе формата EnterpriseData), логику следует внедрять в алгоритмы обработки данных после десериализации (например, в модуле менеджера обмена через переопределяемые процедуры).

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

Особенности хранения основных договоров в 1С:Бухгалтерии 3.0

Перед написанием кода важно учесть изменения в архитектуре типовых конфигураций. В старых редакциях (например, БП 2.0) у справочника Контрагенты существовал прямой реквизит ОсновнойДоговорКонтрагента. В современных версиях 1С:Бухгалтерия предприятия 3.0 этот реквизит упразднен.

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

Вариант 1. Получение договора через менеджер регистра сведений (простой способ)

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


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

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

Вариант 2. Универсальное получение договора с помощью запроса

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

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


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

Этот запрос решает сразу две задачи: сначала он пытается найти явно назначенный основной договор в регистре сведений, а если его там нет — выбирает первый попавшийся неудаленный договор этого контрагента по указанной организации. Это гарантирует, что документ не останется с незаполненным реквизитом ДоговорКонтрагента после окончания сессии синхронизации.

Вариант 3. Динамическая запись основного договора в регистр при его отсутствии

Иногда при переносе нового контрагента в базу-приемник для него создается новый договор, но запись в регистр ОсновныеДоговорыКонтрагента автоматически не создается. Мы можем решить эту проблему прямо «на лету» в процессе выполнения обмена.

Для этого в обработчик «После загрузки» ПКО можно добавить логику автоматической регистрации созданного договора в качестве основного:


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

Подведем итоги

Попытка вызывать интерактивные методы экранных форм во время выполнения фонового обмена данными противоречит клиент-серверной архитектуре платформы 1С. Вместо этого всю логику заполнения зависимых реквизитов необходимо выносить на серверную сторону.

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

← На главную