Разработка мобильных приложений на платформе «1С:Предприятие» всегда сопряжена с организацией надежного и быстрого обмена данными между мобильным клиентом (или автономным мобильным приложением) и центральной базой данных. В процессе реализации двустороннего обмена разработчики часто сталкиваются с типовыми ошибками, которые на первый взгляд кажутся неочевидными. Одной из таких распространенных проблем является ошибка «Недопустимое значение параметра (параметр номер '2')» при вызове метода НачатьЗапись у объекта записи сообщения обмена.
В рамках этой статьи мы детально проанализируем причины возникновения этой ошибки, разберем по шагам эффективные методы ее устранения, рассмотрим перенос логики синхронизации в общий модуль для реализации автозапуска обмена при старте мобильного приложения, а также разберем методы борьбы с дублированием данных при двусторонней синхронизации.
Рассмотрим ситуацию, когда при выполнении выгрузки изменений из базы данных на мобильное устройство возникает критический сбой. Платформа выдает следующее сообщение об ошибке:
Ошибка при вызове метода контекста (НачатьЗапись): Недопустимое значение параметра (параметр номер '2')
Посмотрим на фрагмент кода, в котором происходит падение системы:
ЗаписьХМЛ = новый ЗаписьXML;
ЗаписьХМЛ.УстановитьСтроку();
ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
Узел = ПланыОбмена.МобильныйОбмен.НайтиПоКоду("ЦБ");
ЗаписьСообщения.НачатьЗапись(ЗаписьХМЛ, Узел);
Проанализируем работу метода НачатьЗапись объекта ЗаписьСообщенияПланаОбмена. Данный метод принимает два обязательных параметра:
ЗаписьXML, через который будет осуществляться непосредственная запись сообщения обмена.ПланОбменаСсылка), для которого подготавливается это сообщение.Когда платформа выбрасывает исключение о недопустимом значении второго параметра, это означает, что переданная переменная Узел не содержит корректную ссылку на существующий элемент плана обмена. Чаще всего в ней оказывается пустая ссылка (Неопределено или ПланыОбмена.МобильныйОбмен.ПустаяСсылка()). Это происходит потому, что метод НайтиПоКоду("ЦБ") возвращает пустое значение, так как элемент с кодом "ЦБ" просто не найден в базе данных.
Но почему узел не находится, если мы точно знаем, что создали его в режиме «1С:Предприятие»? Дело в механизме автонумерации и свойстве Длина кода плана обмена. В конфигураторе для каждого плана обмена задается фиксированная длина кода (например, 9 символов). Если длина кода равна 9, а мы ищем значение по строке "ЦБ" (длина которой всего 2 символа), платформа осуществляет точный поиск по строке.
Поскольку при создании новых узлов или при автонумерации платформа автоматически дополняет коды ведущими нулями или пробелами до заданной длины, реальный код узла в базе данных выглядит как "ЦБ0000001", "ЦБ0000002" или "0000000ЦБ". Поиск по точному значению "ЦБ" возвращает пустую ссылку, что и приводит к падению метода НачатьЗапись.
Рассмотрим подробнее три эффективных способа решения проблемы с поиском узла обмена.
Способ 1. Изменение длины кода в конфигураторе
Если в вашей системе обмена не планируется заводить тысячи узлов, и коды всегда будут короткими (например, только "ЦБ" для центральной базы и "МУ" для мобильного устройства), самым простым решением будет изменить свойства самого плана обмена:
МобильныйОбмен).После сохранения конфигурации автонумерация не будет дополнять код лишними символами, и метод НайтиПоКоду("ЦБ") начнет работать корректно.
Способ 2. Программное приведение кода к формату автонумерации
Если изменять структуру метаданных нежелательно, мы можем программно дополнить код нулями перед вызовом поиска. Разберем по шагам, как написать универсальную функцию для дополнения кода:
Функция ПолучитьФорматированныйКодУзла(Код, ПланОбменаМетаданные)
ДлинаКода = ПланОбменаМетаданные.ДлинаКода;
Результат = СокрЛП(Код);
Пока СтрДлина(Результат) < ДлинаКода Цикл
Результат = "0" + Результат;
КонецЦикла;
Возврат Результат;
КонецФункции
В этом случае перед поиском узла мы будем вызывать нашу функцию:
ИскомыйКод = ПолучитьФорматированныйКодУзла("ЦБ", Метаданные.ПланыОбмена.МобильныйОбмен);
Узел = ПланыОбмена.МобильныйОбмен.НайтиПоКоду(ИскомыйКод);
Способ 3. Использование Констант для хранения ссылок (Рекомендуемый метод)
Поиск узлов обмена по строковому коду в кодовой базе — это плохая практика (антипаттерн разработки). Если конечный пользователь случайно изменит код узла в режиме «1С:Предприятие», логика обмена мгновенно сломается. Чтобы защитить систему от подобных сбоев, мы рекомендуем использовать специальную константу.
УзелЦентральнойБазы с типом значения ПланОбменаСсылка.МобильныйОбмен.
Узел = Константы.УзелЦентральнойБазы.Получить();
Если Не ЗначениеЗаполнено(Узел) Тогда
ВызватьИсключение "В настройках обмена не указан узел центральной базы!";
КонецЕсли;
ЗаписьСообщения.НачатьЗапись(ЗаписьХМЛ, Узел);
Данный подход полностью исключает возникновение ошибок, связанных с изменениями кодов и автонумерацией.
Перейдем ко второму важному вопросу: как запускать синхронизацию автоматически при старте мобильного приложения, а не по нажатию интерактивной кнопки. Программно вызвать ОбщуюКоманду напрямую из кода невозможно, так как модули команд жестко привязаны к интерактивному контексту. Чтобы сделать наш код универсальным, нам потребуется провести рефакторинг и вынести логику обмена в Общий модуль.
Шаг 1. Создание Общего модуля
Создадим в конфигураторе новый общий модуль с именем ОбменДаннымиМобильныйКлиент. В свойствах модуля установим следующие флажки:
Перенесем всю логику подготовки данных и работы с веб-сервисом в этот общий модуль:
// Процедура запускает двусторонний обмен данными
Процедура ВыполнитьСинхронизациюДанных() Экспорт
#Если МобильныйКлиент Или ТонкийКлиент Тогда
// Выполняем серверный вызов для отправки и получения данных
РезультатОбмена = ВыполнитьСинхронизациюНаСервере();
Если РезультатОбмена Тогда
ЗаписатьЛогСобытий("Синхронизация успешно завершена.");
Иначе
ЗаписатьЛогСобытий("При синхронизации произошла ошибка.");
КонецЕсли;
#КонецЕсли
КонецПроцедуры
Функция ВыполнитьСинхронизациюНаСервере()
Попытка
// Подключение к веб-сервису центральной базы
ВСОпределение = новый WSОпределения("http://192.168.111.108/Web/ws/WebОбмен.1cws?wsdl");
ВСервис = ВСОпределение.Сервисы.Получить("Data", "WebОбмен");
ВТочкаВхода = ВСервис.ТочкиПодключения.Получить("WebОбменSoap");
ВОперация = ВТочкаВхода.Интерфейс.Операции.Получить("Синхронизация");
// Подготовка пакета данных для отправки
ДанныеВыгрузки = ЗарегистрироватьВыгрузку();
Данные = новый ХранилищеЗначения(ДанныеВыгрузки, новый СжатиеДанных(9));
ДанныеXDTO = ВСОпределение.ФабрикаXDTO.Создать(ВОперация.Параметры.Получить("Данные").Тип, Данные);
ВСПрокси = новый WSПрокси(ВСОпределение, "Data", "WebОбмен", "WebОбменSoap");
// Вызов веб-сервиса и получение входящего пакета
Ответ = ВСПрокси.Синхронизация(ДанныеXDTO);
ПринятьИзмененияПоПлану(Ответ.Получить());
Возврат Истина;
Исключение
ЗаписатьЛогСобытий("Ошибка веб-сервиса: " + ОписаниеОшибки());
Возврат Ложь;
КонецПопытки;
КонецФункции
Шаг 2. Вызов из Общей команды
Теперь модуль вашей общей команды, по которой пользователь может запустить обмен вручную, станет предельно простым и понятным:
&НаКлиенте
Процедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)
ОбменДаннымиМобильныйКлиент.ВыполнитьСинхронизациюДанных();
КонецПроцедуры
Шаг 3. Интеграция автозапуска при начале работы системы
Чтобы запустить процедуру синхронизации при открытии мобильного приложения, мы воспользуемся предопределенным событием модуля управляемого приложения.
ПриНачалеРаботыСистемы.Важно помнить, что сетевой обмен данными может занимать длительное время (в зависимости от качества интернет-соединения). Если запустить синхронизацию напрямую внутри ПриНачалеРаботыСистемы, мобильное приложение «зависнет» на этапе загрузки, показывая пользователю пустой экран. Чтобы избежать этого и дать интерфейсу приложения полностью отрисоваться, мы применим обработчик ожидания с однократным запуском.
Рассмотрим пример реализации:
Процедура ПриНачалеРаботыСистемы()
#Если МобильныйКлиент Тогда
// Подключаем однократный обработчик ожидания с задержкой в 1 секунду
ПодключитьОбработчикОжидания("ВыполнитьАвтоматическийОбменПриСтарте", 1, Истина);
#КонецЕсли
КонецПроцедуры
Теперь добавим саму процедуру-обработчик в этот же модуль управляемого приложения:
Процедура ВыполнитьАвтоматическийОбменПриСтарте() Экспорт
Попытка
ОбменДаннымиМобильныйКлиент.ВыполнитьСинхронизациюДанных();
Исключение
// Подавляем интерактивные сообщения при автоматическом фоновом обмене,
// чтобы не мешать пользователю работать при отсутствии связи с сервером
ЗаписатьЛогСобытий("Автоматический обмен не выполнен: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Проанализируем еще одну проблему, с которой часто сталкиваются разработчики при обмене с мобильными устройствами: создание дублирующих элементов справочников с одинаковыми кодами.
Причина кроется в механизме десериализации. Метод ПрочитатьXML по умолчанию сопоставляет объекты базы данных по их уникальному идентификатору GUID (UUID). Когда пользователь создает новый элемент (например, карточку клиента) на мобильном устройстве, ему присваивается новый случайный идентификатор GUID. При передаче этого объекта в центральную базу данных система пытается прочитать его. Так как объекта с таким идентификатором в центральной базе нет, платформа создает новый элемент с новым GUID, но со старым кодом (например, "ЦБ0001"), который уже может принадлежать другому элементу в центральной базе. Это приводит к замусориванию базы данных и дублированию.
Выделим три основных решения этой проблемы:
1. Использование уникальных префиксов информационных баз
Никогда не допускайте пересечения диапазонов автонумерации в разных базах данных. Для этого в свойствах каждой информационной базы (или в параметрах обмена) задайте уникальные префиксы кодов:
"ЦБ". Новые элементы будут иметь коды "ЦБ0000001", "ЦБ0000002"."МУ". Новые элементы получат коды "МУ0000001", "МУ0000002".В этом случае при объединении данных в центральной базе коллизии кодов полностью исключены.
2. Ручное сопоставление объектов по ключевым полям
Если вам необходимо синхронизировать объекты не по GUID, а по ключевым бизнес-полям (например, по ИНН/КПП для контрагентов или по Артикулу для номенклатуры), стандартный метод ПрочитатьXML использовать нельзя. Вам потребуется написать алгоритм предварительного поиска:
// Пример выборочного чтения объектов с поиском по коду
Если ТипДанных = Тип("СправочникОбъект.Номенклатура") Тогда
// Считываем данные во временный объект XDTO
ДанныеXDTO = ФабрикаXDTO.ПрочитатьXML(ЧтениеХМЛ);
// Ищем существующий элемент в центральной базе по коду
СсылкаНаНоменклатуру = Справочники.Номенклатура.НайтиПоКоду(ДанныеXDTO.Код);
Если ЗначениеЗаполнено(СсылкаНаНоменклатуру) Тогда
// Если элемент найден — обновляем его реквизиты
НоменклатураОбъект = СсылкаНаНоменклатуру.ПолучитьОбъект();
Иначе
// Если не найден — создаем новый элемент
НоменклатураОбъект = Справочники.Номенклатура.СоздатьЭлемент();
НоменклатураОбъект.Код = ДанныеXDTO.Код;
КонецЕсли;
НоменклатураОбъект.Наименование = ДанныеXDTO.Наименование;
НоменклатураОбъект.ОбменДанными.Загрузка = Истина;
НоменклатураОбъект.Записать();
КонецЕсли;
3. Своевременное удаление регистрации изменений
Убедитесь, что после успешной отправки пакета данных на сервер или получения подтверждения от веб-сервиса вы вызываете метод очистки регистрации изменений на узле плана обмена:
ПланыОбмена.УдалитьРегистрациюИзменений(Узел, НомерПринятогоСообщения);
Если этого не сделать, мобильный клиент при каждой следующей сессии обмена будет заново отправлять в центральную базу одни и те же данные, что приведет к зацикливанию обмена, конфликтам версий объектов и избыточной нагрузке на сеть.