При настройке типовой синхронизации данных между конфигурациями 1С:Управление торговлей 11.4 (УТ) и 1С:Бухгалтерия предприятия 3.0 (БП) через Универсальный формат (EnterpriseData) часто возникает неприятная проблема. Если у одного контрагента зарегистрировано несколько адресов электронной почты (или других однотипных контактных данных, например, телефонов), при обмене переносится только один — самый последний из списка. При этом ранее сохраненные адреса в базе-приемнике бесследно затираются.
В этой статье мы подробно разберем, почему возникает эта ошибка, как устроены внутренние механизмы Библиотеки стандартных подсистем (БСП) в части работы с контактной информацией и как решить эту проблему раз и навсегда с помощью механизмов расширений в обеих базах.
Прежде чем приступать к доработке программного кода, нам необходимо убедиться в том, что в обеих конфигурациях включена системная возможность хранения нескольких значений для одного вида контактной информации. Без этой настройки СУБД на уровне платформы заблокирует запись дублирующих видов КИ.
Выполним проверку по следующему алгоритму в каждой из баз (УТ и БП):
Если этот флаг не установлен хотя бы в одной из баз, стандартная функция БСП ЗапрещенВводНесколькихЗначений() будет принудительно отсекать все адреса, кроме первого, даже если мы исправим код обмена.
Проанализируем файл выгрузки XML, который формирует база-источник. Мы увидим, что в пакете данных передаются абсолютно все почтовые ящики, привязанные к контрагенту. Однако при загрузке в БП сохраняется только последний. Почему так происходит?
Обратимся к программному коду конфигурации БП 3.0. В модуле менеджера обмена МенеджерОбменаЧерезУниверсальныйФормат13 (или актуальной версии формата в вашей конфигурации) за импорт контактной информации отвечает процедура ЗаполнитьКонтактнуюИнформациюВПолученныхДанных. В самом конце этой процедуры вызывается стандартный метод БСП:
УправлениеКонтактнойИнформацией.ДобавитьКонтактнуюИнформацию(ПолученныеДанные, СокрЛП(СтрокаXDTO.ЗначенияПолей), ТекВидКИ);
Посмотрим на сигнатуру метода ДобавитьКонтактнуюИнформацию в общем модуле БСП:
Процедура ДобавитьКонтактнуюИнформацию(СсылкаИлиОбъект, ЗначениеИлиПредставление, ВидКонтактнойИнформации, Дата = Неопределено, Замещать = Истина) Экспорт
Обратим внимание на последний параметр: Замещать = Истина. По умолчанию метод работает в режиме жесткого замещения. Это означает, что при обработке каждой строки из входящего пакета XML система сначала полностью очищает всю контактную информацию данного вида у объекта, а затем записывает новую. В результате циклического обхода коллекции КИ каждая последующая строка просто затирает предыдущую, и в базе остается только самая последняя запись.
Для исправления этой логики в БП нам нужно явно передавать Ложь в параметр Замещать. Реализуем это через расширение конфигурации.
Создадим расширение для БП и добавим в него процедуру загрузки с директивой &Вместо:
&Вместо("ЗаполнитьКонтактнуюИнформациюВПолученныхДанных")
Процедура Расш_ЗаполнитьКонтактнуюИнформациюВПолученныхДанных(ПолученныеДанные, ДанныеXDTO)
// Копируем стандартный код подготовки данных из типовой процедуры...
// В месте вызова записи КИ меняем параметр "Замещать" на Ложь:
УправлениеКонтактнойИнформацией.ДобавитьКонтактнуюИнформацию(ПолученныеДанные, СокрЛП(СтрокаXDTO.ЗначенияПолей), ТекВидКИ, , Ложь);
КонецПроцедуры
Теперь при обмене новые адреса электронной почты будут добавляться в табличную часть контрагента, не уничтожая уже имеющиеся записи.
В конфигурации УТ 11.4 ситуация аналогична, но имеет свою специфику. Нам необходимо скорректировать общий модуль МенеджерОбменаЧерезУниверсальныйФормат, переопределив процедуру ЗагрузитьКонтактнуюИнформацию.
Создадим расширение для УТ 11.4 и добавим следующий код, заменяющий стандартный метод загрузки КИ:
&Вместо("ЗагрузитьКонтактнуюИнформацию")
Процедура Расш_ЗагрузитьКонтактнуюИнформацию(ДанныеXDTO, ПолученныеДанные, ГруппаВидовКИ)
Если Не (ДанныеXDTO.Свойство("КонтактнаяИнформация") И ЗначениеЗаполнено(ДанныеXDTO.КонтактнаяИнформация)) Тогда
Возврат;
КонецЕсли;
Для Каждого СтрокаXDTO Из ДанныеXDTO.КонтактнаяИнформация Цикл
ВидКИСтрокой = СокрЛП(СтрокаXDTO.ВидКонтактнойИнформации.Значение);
НаименованиеКИ = СокрЛП(СтрокаXDTO.НаименованиеКонтактнойИнформации);
ТекВидКИ = ВидКонтактнойИнформацииИзСтроки(ВидКИСтрокой, НаименованиеКИ, ГруппаВидовКИ);
Если Не ЗначениеЗаполнено(ТекВидКИ) Тогда
Продолжить;
КонецЕсли;
// Вызываем метод БСП с отключенным замещением (параметр Замещать = Ложь)
УправлениеКонтактнойИнформацией.ДобавитьКонтактнуюИнформацию(ПолученныеДанные, СокрЛП(СтрокаXDTO.ЗначенияПолей), ТекВидКИ, , Ложь);
КонецЦикла;
КонецПроцедуры
Этот код успешно отработает и запишет все адреса в базу данных. Мы можем легко проверить это с помощью консоли запросов — все записи физически появятся в табличной части КонтактнаяИнформация справочника Контрагенты. Однако здесь нас поджидает еще одна архитектурная особенность УТ, из-за которой в интерфейсе карточки контрагента по-прежнему будет отображаться только один email.
Выясним причину, почему при наличии записей в базе данных интерфейс УТ упорно скрывает вторую и последующие почты. В отличие от БП, где сущность контрагента является единственной, в УТ реализовано разделение на:
Контактная информация хранится параллельно как у Партнера, так и у Контрагента. При изменении или загрузке контрагента запускается отложенный обработчик ОтложеннаяОбработка_Контрагенты, который синхронизирует КИ контрагента с КИ связанного с ним партнера.
Проанализируем типовой код этой процедуры. Разработчики "1С" допустили логическую ошибку, объединив очистку старых данных партнера и запись новых в один цикл:
// ТИПОВОЙ ОШИБОЧНЫЙ КОД:
Для Каждого СтрокаКИКонтрагента Из Объект.КонтактнаяИнформация Цикл
ВидКИПартнера = ВидКИПартнераПоВидуКИКонтрагента(СтрокаКИКонтрагента.Вид, ИспользоватьПартнеровКакКонтрагентов);
Если ВидКИПартнера = Неопределено Тогда
Продолжить;
КонецЕсли;
// На каждой итерации ищется и удаляется КИ партнера данного вида!
СтрокиКИПартнера = ПартнерОбъект.ContactИнформация.НайтиСтроки(Новый Структура("Вид", ВидКИПартнера));
Для Каждого СтрокаКИПартнера Из СтрокиКИПартнера Цикл
ПартнерОбъект.КонтактнаяИнформация.Удалить(СтрокаКИПартнера);
КонецЦикла;
СтрокаКИПартнера = ПартнерОбъект.КонтактнаяИнформация.Добавить;
ЗаполнитьЗначенияСвойств(СтрокаКИПартнера, СтрокаКИКонтрагента);
СтрокаКИПартнера.Вид = ВидКИПартнера;
КонецЦикла;
В чем заключается ошибка? Когда у контрагента есть два email-адреса, на первой итерации цикла система находит у партнера старый email, удаляет его и добавляет первый новый email. На второй итерации цикла система обрабатывает второй email контрагента, снова ищет у партнера КИ данного вида, находит только что добавленный на первом шаге первый email, благополучно удаляет его и записывает второй email. В итоге у связанного партнера всегда остается только одна (последняя) запись!
Чтобы исправить это поведение, нам нужно разделить алгоритм на два независимых этапа:
Добавим в наше расширение для УТ 11 доработанный вариант процедуры отложенной обработки:
&Вместо("ОтложеннаяОбработка_Контрагенты")
Процедура Расш_ОтложеннаяОбработка_Контрагенты(Объект, КомпонентыОбмена, ОбъектМодифицирован)
// ... [Здесь должен быть стандартный подготовительный код процедуры] ...
// ЭТАП 1: Предварительно очищаем всю существующую КИ у партнера по видам КИ контрагента
Для Каждого СтрокаКИКонтрагента Из Объект.КонтактнаяИнформация Цикл
ВидКИПартнера = ВидКИПартнераПоВидуКИКонтрагента(СтрокаКИКонтрагента.Вид, ИспользоватьПартнеровКакКонтрагентов);
Если ВидКИПартнера = Неопределено Тогда
Продолжить;
КонецЕсли;
СтрокиКИПартнера = ПартнерОбъект.КонтактнаяИнформация.НайтиСтроки(Новый Структура("Вид", ВидКИПартнера));
Для Каждого СтрокаКИПартнера Из СтрокиКИПартнера Цикл
ПартнерОбъект.КонтактнаяИнформация.Удалить(СтрокаКИПартнера);
КонецЦикла;
КонецЦикла;
// ЭТАП 2: Переносим все новые адреса КИ без риска взаимного удаления строк
Для Каждого СтрокаКИКонтрагента Из Объект.КонтактнаяИнформация Цикл
ВидКИПартнера = ВидКИПартнераПоВидуКИКонтрагента(СтрокаКИКонтрагента.Вид, ИспользоватьПартнеровКакКонтрагентов);
Если ВидКИПартнера = Неопределено Тогда
Продолжить;
КонецЕсли;
СтрокаКИПартнера = ПартнерОбъект.КонтактнаяИнформация.Добавить;
ЗаполнитьЗначенияСвойств(СтрокаКИПартнера, СтрокаКИКонтрагента);
СтрокаКИПартнера.Вид = ВидКИПартнера;
КонецЦикла;
// ... [Здесь должен быть стандартный завершающий код процедуры, включая запись ПартнерОбъект] ...
КонецПроцедуры
Благодаря такому разделению циклов, механизм отложенной синхронизации партнеров работает корректно, сохраняя всю структуру переданных адресов электронной почты.
Мы подробно разобрали, как решить проблему синхронизации множественных email-адресов на уровне исходного кода конфигураций 1С. Описанный метод через расширения является наиболее быстрым и эффективным для баз, находящихся на поддержке с возможностью изменения.
Однако, если вы не хотите вносить изменения в конфигурации программно, у вас есть альтернативные пути: