При настройке синхронизации данных между различными конфигурациями (например, между 1С:Управление торговлей и 1С:Бухгалтерия предприятия) разработчики часто сталкиваются со следующей задачей: необходимо переопределить контрагента при загрузке (например, схлопнуть всех розничных покупателей-физлиц в одного системного контрагента «ФизЛицо»), но при этом требуется, чтобы в документе корректно заполнился основной договор этого контрагента.
В обычном интерактивном режиме работы пользователя за это отвечает событие формы документа КонтрагентПриИзменении. Попытка вызвать эту процедуру напрямую из кода обмена — это частая архитектурная ошибка. В этой статье мы подробно разберем, почему вызов клиентских процедур формы при обмене невозможен, и рассмотрим правильные серверные способы решения этой задачи.
Для того чтобы понять ограничения, давайте проанализируем, как устроен процесс обмена данными в платформе 1С:Предприятие 8.3:
EnterpriseData, планы обмена или правила конвертации КД 2.1) выполняются в фоновом режиме на стороне сервера. В этот момент интерактивный интерфейс отсутствует, формы документов физически не создаются и не открываются. Соответственно, обратиться к элементам формы или вызвать экспортные процедуры модуля формы технически невозможно.ОбменДанными.Загрузка = Истина. Этот режим предназначен для того, чтобы объект записался «как есть», без выполнения лишних проверок, пересчетов и подстановок, которые могут нарушить ссылочную целостность или замедлить процесс загрузки. Пытаться обойти этот режим и эмулировать поведение пользователя на форме — грубое нарушение стандартов разработки 1С.Таким образом, единственно верным решением является перенос логики заполнения сопутствующих реквизитов (в нашем случае — договора контрагента) в обработчики правил обмена, выполняющиеся на сервере в момент загрузки объекта.
Если для интеграции вы используете Конвертацию данных 2.1, то наиболее подходящим местом для реализации алгоритма является обработчик «После загрузки» в Правилах конвертации объектов (ПКО) конкретного документа. В этом обработчике нам уже доступен полностью сформированный и заполненный объект (переменная Объект), но он еще не записан в базу данных.
Если используется Конвертация данных 3.0 (на базе формата EnterpriseData), логику следует внедрять в алгоритмы обработки данных после десериализации (например, в модуле менеджера обмена через переопределяемые процедуры).
Рассмотрим подробнее, как программно получить правильный договор контрагента в современных конфигурациях.
Перед написанием кода важно учесть изменения в архитектуре типовых конфигураций. В старых редакциях (например, БП 2.0) у справочника Контрагенты существовал прямой реквизит ОсновнойДоговорКонтрагента. В современных версиях 1С:Бухгалтерия предприятия 3.0 этот реквизит упразднен.
Теперь информация об основных договорах хранится во вспомогательном регистре сведений ОсновныеДоговорыКонтрагента. Это позволяет назначать разные основные договоры для одного и того же контрагента в разрезе разных организаций и различных видов договоров (например, отдельно для расчетов с покупателями и расчетов с поставщиками).
Если структура метаданных вашего документа содержит явные реквизиты организации и контрагента, а также вы заранее знаете требуемый вид договора, можно воспользоваться быстрым методом чтения из регистра сведений без написания тяжелых запросов. Посмотрим на практический пример кода:
// Проверяем, что в загруженном документе заполнен контрагент
Если ЗначениеЗаполнено(Объект.Контрагент) Тогда
// Создаем структуру для передачи параметров отбора в регистр
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Организация", Объект.Организация);
ПараметрыОтбора.Вставить("Контрагент", Объект.Контрагент);
// Определяем вид договора в зависимости от метаданных загружаемого документа
ИмяДокумента = Объект.Метаданные().Имя;
Если ИмяДокумента = "РеализацияТоваровУслуг" Или ИмяДокумента = "АктВыполненныхРабот" Тогда
ПараметрыОтбора.Вставить("ВидДоговора", Перечисления.ВидыДоговоровКонтрагентов.СПокупателем);
ИначеЕсли ИмяДокумента = "ПоступлениеТоваровУслуг" Тогда
ПараметрыОтбора.Вставить("ВидДоговора", Перечисления.ВидыДоговоровКонтрагентов.СПоставщиком);
КонецЕсли;
// Получаем значение основного договора из регистра сведений
СтруктураРесурсов = РегистрыСведений.ОсновныеДоговорыКонтрагента.Получить(ПараметрыОтбора);
Если ЗначениеЗаполнено(СтруктураРесурсов.Договор) Тогда
Объект.ДоговорКонтрагента = СтруктураРесурсов.Договор;
КонецЕсли;
КонецЕсли;
Этот способ лаконичен и отлично подходит для простых сценариев обмена, когда вид договора жестко привязан к типу загружаемого документа.
В более сложных сценариях (например, когда вид договора может определяться динамически на основе других реквизитов документа, или когда основного договора в регистре нет и требуется подобрать первый подходящий договор контрагента по данной организации) эффективнее использовать запрос.
Проанализируем следующий универсальный алгоритм подбора договора:
Если ЗначениеЗаполнено(Объект.Контрагент) И ЗначениеЗаполнено(Объект.Организация) Тогда
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 1
| ОсновныеДоговоры.Договор КАК Договор
|ИЗ
| РегистрСведений.ОсновныеДоговорыКонтрагента КАК ОсновныеДоговоры
|ГДЕ
| ОсновныеДоговоры.Организация = &Организация
| И ОсновныеДоговоры.Контрагент = &Контрагент
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ ПЕРВЫЕ 1
| Договоры.Ссылка
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК Договоры
|ГДЕ
| Договоры.Владелец = &Контрагент
| И Договоры.Организация = &Организация
| И НЕ Договоры.ПометкаУдаления";
Запрос.УстановитьПараметр("Организация", Объект.Организация);
Запрос.УстановитьПараметр("Контрагент", Объект.Контрагент);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Объект.ДоговорКонтрагента = Выборка.Договор;
КонецЕсли;
КонецЕсли;
Этот запрос решает сразу две задачи: сначала он пытается найти явно назначенный основной договор в регистре сведений, а если его там нет — выбирает первый попавшийся неудаленный договор этого контрагента по указанной организации. Это гарантирует, что документ не останется с незаполненным реквизитом ДоговорКонтрагента после окончания сессии синхронизации.
Иногда при переносе нового контрагента в базу-приемник для него создается новый договор, но запись в регистр ОсновныеДоговорыКонтрагента автоматически не создается. Мы можем решить эту проблему прямо «на лету» в процессе выполнения обмена.
Для этого в обработчик «После загрузки» ПКО можно добавить логику автоматической регистрации созданного договора в качестве основного:
// Если в документе заполнены и контрагент, и договор, проверим, назначен ли этот договор основным
Если ЗначениеЗаполнено(Объект.Контрагент) И ЗначениеЗаполнено(Объект.ДоговорКонтрагента) Тогда
ОтборРегистра = Новый Структура;
ОтборРегистра.Вставить("Организация", Объект.Организация);
ОтборРегистра.Вставить("Контрагент", Объект.Контрагент);
ОтборРегистра.Вставить("ВидДоговора", Объект.ДоговорКонтрагента.ВидДоговора);
ОсновнойДоговор = РегистрыСведений.ОсновныеДоговорыКонтрагента.Получить(ОтборРегистра).Договор;
// Если основной договор еще не назначен — сделаем текущий договор основным
Если Не ЗначениеЗаполнено(ОсновнойДоговор) Тогда
МенеджерЗаписи = РегистрыСведений.ОсновныеДоговорыКонтрагента.СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(МенеджерЗаписи, ОтборРегистра);
МенеджерЗаписи.Договор = Объект.ДоговорКонтрагента;
Попытка
МенеджерЗаписи.Записать(Истина);
Исключение
// Запись логов или обработка исключений при параллельном доступе
ЗаписьЖурналаРегистрации("ОбменДанными.ЗаполнениеДоговоров",
УровеньЖурналаРегистрации.Предупреждение, , ,
"Не удалось записать основной договор: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЕсли;
КонецЕсли;
Попытка вызывать интерактивные методы экранных форм во время выполнения фонового обмена данными противоречит клиент-серверной архитектуре платформы 1С. Вместо этого всю логику заполнения зависимых реквизитов необходимо выносить на серверную сторону.
Использование обработчика «После загрузки» в правилах конвертации объектов совместно с прямыми запросами к базе данных и регистру сведений ОсновныеДоговорыКонтрагента позволяет решить задачу быстро, надежно и в полном соответствии со стандартами разработки фирмы «1С».