Разработка интеграционных решений на платформе 1С — задача, с которой рано или поздно сталкивается каждый программист. Использование Web-сервисов (SOAP или REST HTTP) является стандартным и очень удобным способом обмена данными между 1С и внешними системами. Однако при попытке программного создания, заполнения и проведения документов (например, таких как РасходныйОрдерНаТовары или РеализацияТоваровУслуг) непосредственно внутри кода Web-сервиса разработчики часто сталкиваются с целым букетом ошибок. Программа отказывается записывать документы, ругается на отсутствующие процедуры, выдает ошибки контекста выполнения или аварийно завершает работу при вызове подписок на события.
В этой статье мы подробно разберем, почему возникают эти проблемы, как правильно управлять контекстом выполнения с помощью директив препроцессора, как исправить ошибки в подписках на события и какие архитектурные паттерны проектирования интеграции считаются наиболее надежными в экосистеме 1С.
При переносе или адаптации кода, который изначально создавался для интерактивной работы пользователя в интерфейсе, разработчики часто пытаются «отсечь» клиентские вызовы (такие как предупреждения, диалоги, открытие форм) с помощью инструкций препроцессора. Распространенная ошибка заключается в неверном понимании контекстов выполнения. Проанализируем, почему директива #Если НЕ ВнешнееСоединение не спасает от ошибок компиляции в Web-сервисе, в то время как #Если Клиент решает проблему.
Давайте разберем контексты выполнения на системном уровне:
V83.COMConnector).Когда код модуля документа выполняется в рамках вызова Web-сервиса, платформа проверяет условие #Если НЕ ВнешнееСоединение. Поскольку текущий сеанс является серверным сеансом Web-сервиса (а не COM-соединением), это условие возвращает Истина. В результате препроцессор пытается скомпилировать весь код, находящийся внутри этой директивы. Если внутри этого блока содержатся вызовы клиентских процедур общего модуля или интерактивные методы (например, Предупреждение() или Вопрос()), серверная компиляция завершится ошибкой, так как эти методы физически отсутствуют на сервере.
Директива #Если Клиент отрабатывает корректно, потому что для сервера Web-сервиса контекст Клиент всегда равен Ложь. Весь код внутри этого блока просто отбрасывается при компиляции на сервере.
Для правильного разделения контекста в модулях, которые могут вызываться как интерактивно, так и через Web-сервисы, фоновые задания или COM-соединения, мы рекомендуем использовать следующую конструкцию:
#Если Клиент Тогда
// Этот код скомпилируется и выполнится только в интерактивном сеансе (тонкий, толстый или веб-клиент)
ПоказатьПредупреждение(, "Документ успешно оформлен!");
#КонецЕсли
Если вам необходимо отсечь выполнение кода и для COM-соединений, и для Web-сервисов, и для фоновых заданий, правильнее использовать проверку на серверный контекст или проверять программные флаги, о которых мы поговорим ниже.
Вторая частая проблема при записи документов через Web-сервис — падение системы с критической ошибкой: «При подписке на событие произошла ошибка. Метод обработчика события не найден». Выясним причину этого поведения.
Подписки на события выполняются в том же контексте, в котором происходит действие с объектом. Если документ записывается или проводится на сервере в коде Web-сервиса, то и обработчики всех подписок на события (например, ПередЗаписьюДокумента, ОбработкаПроведенияДокумента) должны быть выполнены на сервере.
Эта ошибка возникает в том случае, если общий модуль, в котором объявлена процедура-обработчик подписки, не имеет необходимых свойств компиляции. Разберем по шагам, как это исправить:
F4) и проверьте установленные флаги:
Экспорт.После включения флага «Сервер» для общего модуля платформа получит доступ к процедуре-обработчику подписки в контексте Web-сервиса, и ошибка исчезнет.
Если вы хотите избежать масштабного переписывания модулей документов и расстановки директив препроцессора по всей конфигурации, рассмотрим два эффективных программных механизма, позволяющих временно отключить проверки и «тяжелые» алгоритмы при записи объекта через Web-сервис.
Это самый простой и быстрый способ отключить встроенные проверки в модуле объекта и подписках на события. Перед вызовом метода Записать() у созданного документа установите свойство ОбменДанными.Загрузка в значение Истина:
НовыйДокумент = Документы.РасходныйОрдерНаТовары.СоздатьДокумент();
// Заполнение реквизитов документа данными из Web-сервиса...
НовыйДокумент.Дата = ТекущаяДата();
НовыйДокумент.Организация = Справочники.Организации.НайтиПоКоду("000000001");
// Включаем режим загрузки данных
НовыйДокумент.ОбменДанными.Загрузка = Истина;
// Записываем документ с проведением
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
Как это работает: на уровне платформы 1С при установке флага Загрузка = Истина отключается большинство системных проверок (например, проверка уникальности номеров, контроль остатков на уровне типовых механизмов). Более того, во всех типовых конфигурациях (включая «Управление торговлей», «ERP», «Комплексная автоматизация») в самом начале обработчиков ПередЗаписью, ПриЗаписи и ОбработкаПроведения всегда прописано стандартное условие:
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Это позволяет мгновенно записать и провести документ, минуя все тяжелые интерактивные и транзакционные алгоритмы, которые могли бы вызвать ошибку компиляции или контекста.
Если режим полной загрузки вам не подходит (например, вам все же необходимо, чтобы выполнились стандартные расчеты или движения по регистрам, но нужно пропустить только конкретные вызовы клиентского кода), используйте структуру ДополнительныеСвойства, которая есть у любого прикладного объекта 1С.
В коде Web-сервиса при создании документа передайте в эту структуру специальный флаг:
НовыйДокумент = Документы.РасходныйОрдерНаТовары.СоздатьДокумент();
// Заполнение реквизитов...
НовыйДокумент.ДополнительныеСвойства.Вставить("СозданЧерезВебСервис", Истина);
НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
Теперь в модуле документа или общем модуле, где происходят сбои или нежелательные проверки, вы можете точечно анализировать этот флаг:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Проверяем, запущен ли процесс из Web-сервиса
Если ДополнительныеСвойства.Свойство("СозданЧерезВебСервис") Тогда
// Пропускаем интерактивные проверки или выполняем облегченный алгоритм
Возврат;
КонецЕсли;
// Стандартные тяжелые проверки для обычных пользователей...
КонецПроцедуры
Этот подход избавляет от необходимости плодить директивы препроцессора и позволяет гибко управлять логикой проведения документа в зависимости от источника его создания.
Несмотря на наличие программных способов обхода проверок, создание и проведение сложных документов непосредственно в транзакции Web-сервиса в режиме реального времени (синхронно) считается плохой практикой (Anti-pattern). Посмотрим на причины этого:
Наиболее надежным решением (Best Practice) является организация асинхронной очереди интеграции с использованием вспомогательного регистра сведений и регламентного задания. Разберем по шагам, как спроектировать эту схему:
Создайте новый регистр сведений, например, ОчередьЗадачИнтеграции (независимый, непериодический).
Определите структуру регистра:
ИдентификаторЗадачи (Тип: Строка или УникальныйИдентификатор) — уникальный ID пакета данных.Статус (Тип: ПеречислениеСсылка.СтатусыЗадачИнтеграции: Новый, ВРаботе, Выполнено, Ошибка).ДатаСоздания (Тип: Дата).ТекстОшибки (Тип: Строка) — для фиксации ошибок проведения.ДанныеПакета (Тип: ХранилищеЗначения или Строка неограниченной длины) — здесь мы сохраняем сырые XML/JSON данные, пришедшие от внешнего источника.Код Web-сервиса должен выполнять только одну задачу — быстро принять JSON/XML, записать его в регистр со статусом «Новый» и мгновенно вернуть клиенту успешный ответ с идентификатором задачи.
Функция СоздатьДокументАсинхронно(ДанныеЗапроса) Экспорт
ИдентификаторЗадачи = Новый УникальныйИдентификатор();
// Записываем входящий пакет данных в очередь
МенеджерЗаписи = РегистрыСведений.ОчередьЗадачИнтеграции.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ИдентификаторЗадачи = ИдентификаторЗадачи;
МенеджерЗаписи.Статус = Перечисления.СтатусыЗадачИнтеграции.Новый;
МенеджерЗаписи.ДатаСоздания = ТекущаяДата();
МенеджерЗаписи.ДанныеПакета = Новый ХранилищеЗначения(ДанныеЗапроса);
Попытка
МенеджерЗаписи.Записать(Истина);
Возврат "{\"status\": \"accepted\", \"job_id\": \"" + Строка(ИдентификаторЗадачи) + "\"}";
Исключение
Возврат "{\"status\": \"error\", \"message\": \"" + ОписаниеОшибки() + "\"}";
КонецПопытки;
КонецФункции
Создайте регламентное задание, которое будет запускаться в фоновом режиме каждые 10–30 секунд и вызывать процедуру общего модуля для обработки новых задач.
Проанализируем пример кода обработчика очереди:
Процедура ОбработатьОчередьИнтеграции() Экспорт
// Выбираем новые задачи
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Очередь.ИдентификаторЗадачи КАК ИдентификаторЗадачи,
| Очередь.ДанныеПакета КАК ДанныеПакета
|ИЗ
| РегистрСведений.ОчередьЗадачИнтеграции КАК Очередь
|ГДЕ
| Очередь.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыЗадачИнтеграции.Новый)";
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Возврат;
КонецЕсли;
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Устанавливаем статус "В работе" для предотвращения повторной обработки
МенеджерЗаписи = РегистрыСведений.ОчередьЗадачИнтеграции.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ИдентификаторЗадачи = Выборка.ИдентификаторЗадачи;
МенеджерЗаписи.Прочитать();
Если НЕ МенеджерЗаписи.Выбран() Тогда
Продолжить;
КонецЕсли;
МенеджерЗаписи.Статус = Перечисления.СтатусыЗадачИнтеграции.ВРаботе;
МенеджерЗаписи.Записать(Истина);
// Начинаем попытку создания документа
Попытка
ДанныеСтрокой = Выборка.ДанныеПакета.Получить();
// Разбираем данные (JSON/XML) и создаем документ
СозданныйДокумент = РазобратьИСоздатьДокумент(ДанныеСтрокой);
// Записываем статус успешного выполнения
МенеджерЗаписи.Статус = Перечисления.СтатусыЗадачИнтеграции.Выполнено;
МенеджерЗаписи.ТекстОшибки = "";
МенеджерЗаписи.Записать(Истина);
Исключение
// Фиксируем ошибку, если что-то пошло не так
МенеджерЗаписи.Статус = Перечисления.СтатусыЗадачИнтеграции.Ошибка;
МенеджерЗаписи.ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
МенеджерЗаписи.Записать(Истина);
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Функция РазобратьИСоздатьДокумент(ДанныеСтрокой)
// Здесь должна быть ваша прикладная логика парсинга и создания документа
// Например:
// Док = Документы.РасходныйОрдерНаТовары.СоздатьДокумент();
// ... заполнение полей ...
// Док.Записать(РежимЗаписиДокумента.Проведение);
// Возврат Док.Ссылка;
КонецФункции
Преимущества асинхронного подхода:
Используя приведенные рекомендации, вы сможете раз и навсегда решить проблемы с контекстом компиляции при вызове процедур из внешних источников и построить надежную, отказоустойчивую систему интеграции 1С с любыми веб-сервисами.