В данной статье я хочу поделиться опытом разработки внешней обработки, которая автоматизирует этот процесс с помощью технологий искусственного интеллекта. Обработка принимает на вход файл (изображение, скан или PDF) с реквизитами организации, отправляет его в сервис распознавания ИИ Сканер документов, получает структурированные данные и на их основе создает или обновляет элемент справочника «Контрагенты» с банковским счётом.
Ключевая особенность решения — оно спроектировано как внешняя обработка с полной интеграцией в типовые конфигурации на базе «1С:Библиотеки стандартных подсистем» (БСП) версии 3.1 и выше. Это означает, что обработку можно подключить к «1С:Бухгалтерии», «1С:ERP», «1С:Управлению торговлей» или любой другой конфигурации на БСП без изменения самой конфигурации и снятия её с поддержки.
Постановка задачи
Исходные требования к обработке формировались исходя из реальных потребностей пользователей:
Входные данные: файл произвольного формата — изображение (JPEG, PNG), скан в PDF или документ с реквизитами организации. Это может быть карточка предприятия, выписка из ЕГРЮЛ, счет на оплату, договор.
Сервис распознавания: HTTP API, который принимает файл в формате multipart/form-data и возвращает JSON-ответ со структурированными полями:
- наименование организации,
- ИНН, КПП, ОГРН,
- юридический адрес,
- банковские реквизиты (наименование банка, БИК, корреспондентский счёт, расчётный счёт),
- контактные данные (телефон, email).
Результат работы: создание нового или обновление существующего элемента справочника «Контрагенты» с заполнением всех реквизитов и привязкой банковского счёта.
Среда выполнения: типовая конфигурация на БСП 3.1+, файловый или клиент-серверный вариант, тонкий клиент.
Ограничения: конфигурация находится на поддержке, поэтому вносить изменения в неё нельзя. Решение должно быть полностью автономным и подключаться через механизм дополнительных обработок.
Архитектура решения
Прежде чем переходить к коду, рассмотрим общую архитектуру. Она разделена на два логических слоя: клиентский и серверный.
Клиентский слой реализован в модуле формы обработки и отвечает за взаимодействие с сервисом распознавания. Это принципиальное решение: HTTP-запросы выполняются с клиента, а не с сервера «1С:Предприятия». Причин несколько:
В типовых конфигурациях сервер «1С:Предприятия» часто находится в закрытом сегменте сети без доступа в интернет, при этом пользователи работают через тонкий клиент, у которого такой доступ есть.
В веб-клиенте серверные HTTP-вызовы требуют расширений безопасного режима и разрешений, что усложняет регистрацию обработки.
В клиент-серверном варианте при работе через HTTP-сервис проксирование на сервере «1С» добавляет лишний слой, на котором могут возникать таймауты и блокировки.
Серверный слой реализован в модуле объекта обработки и выполняет все операции с базой данных: поиск существующего контрагента, создание или обновление элемента справочника, запись банковского счёта и контактной информации. Эти операции требуют серверного контекста из-за объектных блокировок и доступа к регистрам сведений.
Интеграция с типовой конфигурацией выполнена через подсистему БСП «Дополнительные отчеты и обработки» — стандартный механизм расширения функциональности без снятия конфигурации с поддержки.
Реализация: HTTP-клиент для запросов к сервису распознавания
Начнем с клиентской части — формирования и отправки multipart-запроса к сервису распознавания. Это наиболее технически насыщенный участок кода.
Платформа «1С:Предприятие» начиная с версии 8.3.5 предоставляет объект HTTPСоединение для работы с HTTP-сервисами. Однако для передачи файлов в формате multipart/form-data необходимо вручную сформировать тело запроса с правильными разделителями — готового метода для этого нет.
Формирование тела запроса
Спецификация multipart/form-data (RFC 7578) определяет структуру тела запроса как последовательность частей, разделенных boundary-меткой. Каждая часть содержит заголовки и данные. Для файла это:
--<boundary>
Content-Disposition: form-data; name="<имя поля>"; filename="<имя файла>"
Content-Type: <MIME-тип>
<двоичные данные файла>
--<boundary>--
Реализация этой логики в модуле формы обработки:
&НаКлиенте
Функция СобратьТелоMultipart(ДвоичныеДанныеФайла, ИмяФайла, Разделитель)
ПереводСтроки = Символы.ВК + Символы.ПС;
Кодировка = КодировкаТекста.UTF8;
ИмяФайлаБезПути = ИмяФайла;
ПозицияРазделителя = СтрНайти(ИмяФайла, "\", НаправлениеПоиска.СКонца);
Если ПозицияРазделителя > 0 Тогда
ИмяФайлаБезПути = Сред(ИмяФайла, ПозицияРазделителя + 1);
КонецЕсли;
ПозицияРазделителя = СтрНайти(ИмяФайлаБезПути, "/", НаправлениеПоиска.СКонца);
Если ПозицияРазделителя > 0 Тогда
ИмяФайлаБезПути = Сред(ИмяФайлаБезПути, ПозицияРазделителя + 1);
КонецЕсли;
ЧастьЗаголовок = "--" + Разделитель + ПереводСтроки
+ "Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайлаБезПути + """"
+ ПереводСтроки
+ "Content-Type: application/octet-stream" + ПереводСтроки + ПереводСтроки;
ЧастьРазделитель = "--" + Разделитель + "--" + ПереводСтроки;
ЧастьЗаголовокДД = ПолучитьДвоичныеДанныеИзСтроки(ЧастьЗаголовок, Кодировка);
ПереводСтрокиДД = ПолучитьДвоичныеДанныеИзСтроки(ПереводСтроки, Кодировка);
ЧастьРазделительДД = ПолучитьДвоичныеДанныеИзСтроки(ЧастьРазделитель, Кодировка);
Части = Новый Массив;
Части.Добавить(ЧастьЗаголовокДД);
Части.Добавить(ДвоичныеДанныеФайла);
Части.Добавить(ПереводСтрокиДД);
Части.Добавить(ЧастьРазделительДД);
Возврат СоединитьДвоичныеДанные(Части);
КонецФункции
Обратите внимание на несколько технических деталей. Во-первых, имя файла в HTTP-заголовке не должно содержать путь — только имя с расширением. Поэтому код сначала выделяет имя файла из полного пути, обрабатывая и обратный слэш (для Windows), и прямой (для Linux и macOS). Во-вторых, разделитель (boundary) должен быть уникальным в рамках запроса. Я генерирую его как WmkBoundary плюс уникальный идентификатор — это гарантирует отсутствие коллизий при многократных вызовах.
Отправка запроса
Отправка запроса начинается с разбора адреса сервиса. Адрес может содержать протокол (HTTP или HTTPS), хост и порт. Эту информацию нужно корректно передать в конструктор HTTPСоединение:
&НаКлиенте
Функция ОтправитьЗапросРаспознавания(АдресСервиса, ИмяФайла, ДвоичныеДанныеФайла)
Результат = Новый Структура;
Результат.Вставить("Успех", Ложь);
Результат.Вставить("Данные", Новый Соответствие);
Результат.Вставить("Ошибка", "");
Результат.Вставить("ИмяФайла", "");
АдресСервисаБезСлеша = СокрЛП(АдресСервиса);
Если Прав(АдресСервисаБезСлеша, 1) = "/" Тогда
АдресСервисаБезСлеша = Лев(АдресСервисаБезСлеша, СтрДлина(АдресСервисаБезСлеша) - 1);
КонецЕсли;
ЗащищенноеСоединение = Неопределено;
ХостСПортом = АдресСервисаБезСлеша;
Если НРег(Лев(ХостСПортом, 8)) = "https://" Тогда
ХостСПортом = Сред(ХостСПортом, 9);
ЗащищенноеСоединение = Новый ЗащищенноеСоединениеOpenSSL(Неопределено, Неопределено);
ИначеЕсли НРег(Лев(ХостСПортом, 7)) = "http://" Тогда
ХостСПортом = Сред(ХостСПортом, 8);
КонецЕсли;
Хост = ХостСПортом;
Порт = 0;
ПозицияДвоеточия = СтрНайти(ХостСПортом, ":");
Если ПозицияДвоеточия > 0 Тогда
Хост = Лев(ХостСПортом, ПозицияДвоеточия - 1);
ПортСтрокой = Сред(ХостСПортом, ПозицияДвоеточия + 1);
Порт = Число(СокрЛП(ПортСтрокой));
КонецЕсли;
Разделитель = "WmkBoundary" + Строка(Новый УникальныйИдентификатор());
ТелоЗапроса = СобратьТелоMultipart(ДвоичныеДанныеФайла, ИмяФайла, Разделитель);
ШаблонОшибкиСоединения = НСтр("ru = 'Ошибка соединения с %1: %2'");
ШаблонОшибкиКода = НСтр("ru = 'Сервис вернул код состояния %1.'");
Попытка
Соединение = Новый HTTPСоединение(Хост, Порт, , , , 120, ЗащищенноеСоединение);
Запрос = Новый HTTPЗапрос("/api/extract_company");
Запрос.Заголовки.Вставить("Content-Type", "multipart/form-data; boundary=" + Разделитель);
Запрос.Заголовки.Вставить("Accept", "application/json");
Запрос.УстановитьТелоИзДвоичныхДанных(ТелоЗапроса);
Ответ = Соединение.ВызватьHTTPМетод("POST", Запрос);
Исключение
Результат.Ошибка = СтрШаблон(ШаблонОшибкиСоединения,
АдресСервиса, ИнформацияОбОшибке().Описание);
Возврат Результат;
КонецПопытки;
Если Ответ.КодСостояния >= 300 Тогда
Результат.Ошибка = СтрШаблон(ШаблонОшибкиКода, Ответ.КодСостояния);
Возврат Результат;
КонецЕсли;
ТелоОтветаСтрокой = Ответ.ПолучитьТелоКакСтроку();
Попытка
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(ТелоОтветаСтрокой);
ОтветJSON = ПрочитатьJSON(Чтение, Истина);
Чтение.Закрыть();
Исключение
Результат.Ошибка = СтрШаблон(НСтр("ru = 'Не удалось разобрать ответ сервиса: %1'"),
ИнформацияОбОшибке().Описание);
Возврат Результат;
КонецПопытки;
Если ТипЗнч(ОтветJSON) <> Тип("Соответствие") Тогда
Результат.Ошибка = НСтр("ru = 'Некорректный формат ответа сервиса.'");
Возврат Результат;
КонецЕсли;
Успех = ОтветJSON.Получить("success");
Если Успех <> Истина Тогда
ТекстОшибки = ОтветJSON.Получить("error");
Если НЕ ЗначениеЗаполнено(ТекстОшибки) Тогда
ТекстОшибки = НСтр("ru = 'Сервис не смог распознать реквизиты.'");
КонецЕсли;
Результат.Ошибка = ТекстОшибки;
Возврат Результат;
КонецЕсли;
Данные = ОтветJSON.Получить("data");
Если ТипЗнч(Данные) = Тип("Соответствие") Тогда
Результат.Данные = Данные;
КонецЕсли;
ИмяФайлаОтвета = ОтветJSON.Получить("file_name");
Если ЗначениеЗаполнено(ИмяФайлаОтвета) Тогда
Результат.ИмяФайла = ИмяФайлаОтвета;
Иначе
Результат.ИмяФайла = ИмяФайла;
КонецЕсли;
Результат.Успех = Истина;
Возврат Результат;
КонецФункции
Несколько моментов, на которые стоит обратить внимание. Таймаут установлен в 120 секунд — распознавание изображения может занимать значительное время, особенно при обработке многостраничных PDF. Использование ПрочитатьJSON(Чтение, Истина) с параметром ИменаСвойствСоЗначениямиДата = Истина не требуется, так как сервис возвращает строковые данные, но я предпочитаю явное указание параметров для читаемости.
Обработка ошибок разнесена на три уровня: сетевые ошибки (исключение при вызове ВызватьHTTPМетод), HTTP-ошибки (код состояния 300 и выше) и ошибки разбора ответа. Это позволяет пользователю получить понятное сообщение в каждом случае.
Реализация: серверная логика обработки данных
Когда данные распознавания получены, начинается серверная работа. Здесь важно обеспечить несколько вещей: поиск существующего контрагента по ИНН и КПП, корректное заполнение реквизитов с учетом особенностей разных конфигураций, атомарность операций через транзакции и работу с банковскими реквизитами.
Поиск контрагента
Поиск по ИНН и КПП — стандартная задача, но есть нюанс: для индивидуальных предпринимателей и физических лиц КПП может быть пустым. Запрос должен это учитывать:
Функция НайтиКонтрагентаПоИННКПП(ИНН, КПП) Экспорт
ИНН = СокрЛП(ИНН);
КПП = СокрЛП(КПП);
Если НЕ ЗначениеЗаполнено(ИНН) Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Контрагенты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.ИНН = &ИНН
| И ВЫБОР
| КОГДА &КПП = """"
| ТОГДА ИСТИНА
| ИНАЧЕ Контрагенты.КПП = &КПП
| КОНЕЦ
| И НЕ Контрагенты.ПометкаУдаления";
Запрос.УстановитьПараметр("ИНН", ИНН);
Запрос.УстановитьПараметр("КПП", КПП);
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Возврат Неопределено;
КонецЕсли;
Выборка = РезультатЗапроса.Выполнить().Выбрать();
Выборка.Следующий();
Возврат Выборка.Ссылка;
КонецФункции
Создание контрагента
Создание нового элемента справочника — операция, которая затрагивает несколько связанных сущностей: сам контрагент, его банковский счёт, контактная информация. Все они должны быть записаны атомарно — если на любом этапе произойдет ошибка, все изменения откатываются:
Функция СоздатьКонтрагента(Данные) Экспорт
Результат = НовыйРезультатСозданияКонтрагента();
НачатьТранзакцию();
Попытка
НовыйОбъект = Справочники.Контрагенты.СоздатьЭлемент();
ЗаполнитьРеквизитыКонтрагентаИзДанных(НовыйОбъект, Данные);
НовыйОбъект.Записать();
Результат.Контрагент = НовыйОбъект.Ссылка;
РезультатСчета = СоздатьОбновитьБанковскийСчет(Результат.Контрагент, Данные);
Результат.СчетНовый = РезультатСчета.ЭтоНовый;
Результат.СчетОшибка = РезультатСчета.Ошибка;
Результат.БанкСоздан = РезультатСчета.БанкСоздан;
Результат.БанковскийСчет = РезультатСчета.БанковскийСчет;
Если РезультатСчета.БанковскийСчет <> Неопределено
И НЕ РезультатСчета.БанковскийСчет.Пустая() Тогда
Установлен = УстановитьРеквизитБезопасно(НовыйОбъект, "ОсновнойБанковскийСчет",
РезультатСчета.БанковскийСчет);
Если Установлен Тогда
НовыйОбъект.Записать();
КонецЕсли;
КонецЕсли;
ЗаписатьКонтактнуюИнформациюКонтрагента(Результат.Контрагент, Данные);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Результат.Ошибка = СтрШаблон(НСтр("ru = 'Не удалось создать контрагента: %1'"),
ИнформацияОбОшибке().Описание);
Возврат Результат;
КонецПопытки;
Возврат Результат;
КонецФункции
Адаптация к разным конфигурациям
Одна из главных сложностей при разработке для типовых конфигураций — различия в структуре справочника «Контрагенты» между «1С:Бухгалтерией», «1С:ERP» и «1С:Управлением торговлей». Например:
- В «1С:Бухгалтерии 3.0» полное наименование хранится в реквизите
ПолноеНаименование, а в «1С:ERP» и «1С:УТ» — вНаименованиеПолное. - Вид контрагента (юридическое лицо, физическое лицо, индивидуальный предприниматель) в разных конфигурациях называется по-разному:
ЮридическоеФизическоеЛицо,ВидКонтрагента,ЮрФизЛицо. - Банковский счёт может храниться в реквизите
КоррСчетилиКоррСчетБанкав зависимости от конфигурации.
Чтобы обработка работала корректно во всех типовых конфигурациях, я использую паттерн «безопасной установки реквизита». Суть его в том, что перед записью значения мы проверяем наличие реквизита в метаданных объекта:
Функция УстановитьРеквизитБезопасно(Объект, ИмяРеквизита, Значение, Диагностика = Неопределено)
Если НЕ ЗначениеЗаполнено(Значение) Тогда
Возврат Ложь;
КонецЕсли;
МетаданныеОбъекта = Объект.Метаданные();
Если НЕ ЕстьРеквизит(МетаданныеОбъекта, ИмяРеквизита) Тогда
Возврат Ложь;
КонецЕсли;
Если Объект[ИмяРеквизита] = Значение Тогда
Возврат Ложь;
КонецЕсли;
Объект[ИмяРеквизита] = Значение;
Если Диагностика <> Неопределено Тогда
Диагностика.Добавить(СтрШаблон(НСтр("ru = '%1: %2'"), ИмяРеквизита, Значение));
КонецЕсли;
Возврат Истина;
КонецФункции
Функция УстановитьРеквизитБезопасноЛюбоеИз(Объект, Значение, Диагностика, Имя1, Имя2 = "", Имя3 = "")
Если НЕ ЗначениеЗаполнено(Значение) Тогда
Возврат Ложь;
КонецЕсли;
МетаданныеОбъекта = Объект.Метаданные();
ИменаРеквизитов = Новый Массив;
ИменаРеквизитов.Добавить(Имя1);
Если ЗначениеЗаполнено(Имя2) Тогда
ИменаРеквизитов.Добавить(Имя2);
КонецЕсли;
Если ЗначениеЗаполнено(Имя3) Тогда
ИменаРеквизитов.Добавить(Имя3);
КонецЕсли;
Для Каждого ИмяРеквизита Из ИменаРеквизитов Цикл
Если ЕстьРеквизит(МетаданныеОбъекта, ИмяРеквизита) Тогда
Возврат УстановитьРеквизитБезопасно(Объект, ИмяРеквизита, Значение, Диагностика);
КонецЕсли;
КонецЦикла;
Возврат Ложь;
КонецФункции
Вспомогательная функция ЕстьРеквизит проверяет наличие реквизита как среди обычных, так и среди стандартных реквизитов:
Функция ЕстьРеквизит(МетаданныеОбъекта, ИмяРеквизита)
Если МетаданныеОбъекта.Реквизиты.Найти(ИмяРеквизита) <> Неопределено Тогда
Возврат Истина;
КонецЕсли;
Для Каждого Описание Из МетаданныеОбъекта.СтандартныеРеквизиты Цикл
Если Описание.Имя = ИмяРеквизита Тогда
Возврат Истина;
КонецЕсли;
КонецЦикла;
Возврат Ложь;
КонецФункции
Этот паттерн позволяет одной обработке корректно работать в разных конфигурациях без необходимости поддерживать отдельные версии кода для каждой.
Работа с банковскими реквизитами
Банковские реквизиты — наиболее сложная часть обработки. Сервис распознавания возвращает БИК, наименование банка, корреспондентский счёт и расчётный счёт. Логика работы следующая:
- Найти банк в справочнике банков по БИК.
- Если банк не найден — создать новый элемент справочника банков.
- Найти существующий расчётный счёт у контрагента с указанным номером.
- Если счёт найден — обновить банк и корреспондентский счёт.
- Если счёт не найден — создать новый.
Для конфигураций на БСП 3.1+ критически важно правильно заполнить поле «Валюта денежных средств» у банковского счёта. Без него счёт может не отображаться в карточке контрагента:
Работа с контактной информацией
Контактная информация в типовых конфигурациях на БСП хранится в регистре сведений «Контактная информация» или в табличной части справочника (в зависимости от версии БСП и конфигурации). В обработке я реализовал три стратегии записи контактной информации с автоматическим выбором доступной:
-
Через API БСП (
УправлениеКонтактнойИнформацией.ЗаписатьКонтактнуюИнформацию) — предпочтительный вариант для современных конфигураций. Однако сигнатура метода менялась между версиями БСП, поэтому я перебираю несколько вариантов вызова в блокеПопытка/Исключение. -
Через регистр сведений — для конфигураций, где регистр «КонтактнаяИнформация» существует, но API БСП недоступно.
-
Через табличную часть справочника — для конфигураций на старых версиях БСП или без неё (например, «1С:Управление торговлей» ранних версий).
Эту логику можно выразить следующей схемой:
Если ЗаписатьКИЧерезБСПAPI(Контрагент, Данные, ОписанияКИ, МенеджерВидовКИ) Тогда
Возврат;
КонецЕсли;
// Вариант 1: Регистр сведений
Если Метаданные.РегистрыСведений.Найти("КонтактнаяИнформация") <> Неопределено
И МенеджерВидовКИ <> Неопределено Тогда
// ...
Возврат;
КонецЕсли;
// Вариант 2: Табличная часть справочника
Если МетаданныеКонтрагента.ТабличныеЧасти.Найти("КонтактнаяИнформация") = Неопределено
ИЛИ МенеджерВидовКИ = Неопределено Тогда
Возврат;
КонецЕсли;
Интеграция с типовой конфигурацией через БСППосле того как логика обработки данных готова, остаётся обеспечить её регистрацию в типовой конфигурации. Подсистема БСП «Дополнительные отчеты и обработки» предоставляет стандартный механизм для этого: каждая внешняя обработка должна содержать экспортную функцию СведенияОВнешнейОбработке(), которая возвращает структуру с описанием обработки, её команд и назначения.
Документация по формату этой функции доступна на ИТС: its.1c.ru/db/bsp321doc#content:994:hdoc. Для нашего сценария регистрация выглядит так:
Функция СведенияОВнешнейОбработке() Экспорт
ПараметрыРегистрации = Новый Структура;
ПараметрыРегистрации.Вставить("Вид", "ЗаполнениеОбъекта");
ПараметрыРегистрации.Вставить("Версия", "1.0.0");
ПараметрыРегистрации.Вставить("Наименование",
НСтр("ru = 'Распознавание реквизитов контрагента'"));
ПараметрыРегистрации.Вставить("Информация",
НСтр("ru = 'Распознавание реквизитов контрагента из файла (изображение, скан или PDF)"
" через сервис ИИ-распознавания. Открывается из карточки или формы списка"
" справочника ""Контрагенты"" через команду ""Заполнуть"".""));
ПараметрыРегистрации.Вставить("БезопасныйРежим", Ложь);
Назначение = Новый Массив;
Назначение.Добавить("Справочник.Контрагенты");
ПараметрыРегистрации.Вставить("Назначение", Назначение);
Команды = Новый ТаблицаЗначений;
Команды.Колонки.Добавить("Идентификатор", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("Представление", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("Использование", Новый ОписаниеТипов("Строка"));
Команды.Колонки.Добавить("ПоказыватьОповещение", Новый ОписаниеТипов("Булево"));
Команды.Колонки.Добавить("Модификатор", Новый ОписаниеТипов("Строка"));
ПараметрыРегистрации.Вставить("Команды", Команды);
Команда = Команды.Добавить();
Команда.Идентификатор = "РаспознатьРеквизиты";
Команда.Представление = НСтр("ru = 'Распознать реквизиты из файла'");
Команда.Использование = "ОткрытиеФормы";
Команда.ПоказыватьОповещение = Ложь;
Команда.Модификатор = "";
Возврат ПараметрыРегистрации;
КонецФункции
Рассмотрим ключевые параметры регистрации.
Вид = «ЗаполнениеОбъекта» означает, что обработка будет доступна в карточках и списках объектов через подменю «Заполнить». Это именно тот сценарий, который нам нужен: пользователь открывает карточку контрагента и вызывает команду из меню «Заполнить».
Назначение ограничивает список объектов, в формах которых будет доступна команда. Указание «Справочник.Контрагенты» означает, что команда появится только в формах этого справочника.
Использование = «ОткрытиеФормы» говорит БСП о том, что команда должна открыть основную форму обработки. Альтернативные варианты — «ВызовКлиентскогоМетода», «ВызовСерверногоМетода», «ЗаполнениеФормы» — предполагают другие сценарии взаимодействия.
БезопасныйРежим = Ложь — осознанное решение. В безопасном режиме обработка не может выполнять HTTP-запросы к внешним сервисам, что делает её бесполезной для нашей задачи. Если в вашей организации требуется безопасный режим, в код обработки нужно добавить функцию РаботаВБезопасномРежиме.РазрешениеНаИспользованиеИнтернетРесурса("http://адрес-сервиса") — тогда администратор сможет подтвердить разрешение при регистрации обработки.
Приём параметров от БСП в форме
Когда пользователь вызывает команду «Заполнить → Распознать реквизиты из файла» из карточки контрагента, БСП передает в форму обработки массив ссылок на выбранные объекты через параметр формы ОбъектыНазначения. Это позволяет нам сразу подставить контрагента в реквизит формы, чтобы при сохранении данных распознавания обновлять именно его, а не создавать нового:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если НЕ ЗначениеЗаполнено(АдресСервиса) Тогда
АдресСервиса = "http://localhost:5000";
КонецЕсли;
Если Параметры.Свойство("ОбъектыНазначения")
И ТипЗнч(Параметры.ОбъектыНазначения) = Тип("Массив")
И Параметры.ОбъектыНазначения.Количество() > 0 Тогда
Контрагент = Параметры.ОбъектыНазначения[0];
КонецЕсли;
КонецПроцедуры
Текущая версия поддерживает работу с программой ИИ Сканер документов, но ее можно адаптировать под любую другую систему. У программы есть возможность работы бесплатно 60 дней, и к тому же она умеет работать с локальными моделями через LM-Studio и Lama.cpp поэтому в качестве теста взяли именно ее.
Вопросы безопасности
При работе с внешними сервисами всегда возникают вопросы безопасности. Рассмотрим основные аспекты.
Передача данных. Файлы с реквизитами организаций содержат персональные данные и коммерческую тайну. При использовании облачного сервиса распознавания убедитесь, что он соответствует требованиям законодательства о персональных данных (152-ФЗ) и имеет серверы на территории РФ. Для крупных организаций может быть оправдано развёртывание сервиса распознавания на собственных мощностях.
Хранение адреса сервиса. Адрес сервиса распознавания хранится в реквизите формы. Это удобно для отладки, но в продакшене стоит вынести его в константу или функциональную опцию, чтобы администратор мог централизованно управлять адресом для всех пользователей.
Защита от ошибок сервиса. Если сервис распознавания недоступен, обработка показывает пользователю понятное сообщение об ошибке, но не выполняет никаких деструктивных действий с базой. Создание контрагента происходит только после успешного распознавания.
Привилегированный режим. В текущей версии обработка не использует привилегированный режим, что соответствует принципу минимальных привилегий. Если в будущем потребуется запись данных в защищённые регистры, привилегированный режим нужно включать точечно — только для конкретных операций, а не для всей обработки.
Безопасный режим. Как уже упоминалось, обработка работает вне безопасного режима из-за необходимости HTTP-обращений. Если ваша организация требует обязательного безопасного режима для внешних обработок, потребуется доработка кода с добавлением разрешений через API РаботаВБезопасномРежиме.
Тестирование и отладка
При разработке обработки я столкнулся с несколькими типичными проблемами, о которых полезно знать.
Проблема: HTTP-сервис на локальной машине разработчика. По умолчанию я устанавливаю адрес сервиса http://localhost:5000, но в клиент-серверном варианте «localhost» относится к серверу «1С:Предприятия», а не к машине пользователя. Для отладки приходится указывать полный адрес или организовывать проксирование.
Проблема: таймаут при распознавании. Большие файлы или многостраничные PDF могут обрабатываться сервисом более минуты. Стандартный таймаут HTTPСоединения (30 секунд) недостаточен. Увеличение таймаута до 120 секунд решает проблему, но в веб-клиенте длительные операции без обратной связи выглядят как зависание. Я добавил вызов Состояние() перед запросом, чтобы показать пользователю, что процесс идёт.
Разработка внешней обработки с интеграцией через БСП — это баланс между универсальностью и простотой. С одной стороны, обработка должна корректно работать в разных типовых конфигурациях с их особенностями структуры справочников и регистров. С другой стороны, код должен оставаться понятным и поддерживаемым.
Паттерн «безопасной установки реквизита» в сочетании с перебором вариантов имён через УстановитьРеквизитБезопасноЛюбоеИз — это, пожалуй, ключевое архитектурное решение, которое позволяет одной обработке работать в «1С:Бухгалтерии», «1С:ERP» и «1С:Управлении торговлей» без отдельных версий кода.
Разделение на клиентский (HTTP) и серверный (работа с базой) слои — второе важное решение. Оно продиктовано практическими ограничениями серверного контекста в типовых конфигурациях и упрощает регистрацию обработки.
В будущем обработку можно расширить в нескольких направлениях: пакетная обработка нескольких контрагентов, распознавание не только реквизитов, но и контактных лиц, интеграция с сервисами проверки контрагентов (ФНС, ФССП), автоматическое заполнение договоров на основе распознанных данных.
Полный исходный код обработки доступен в репозитории на GitHub. Буду рад обратной связи и предложениям по улучшению.
Проверено на следующих конфигурациях и релизах:
- Бухгалтерия предприятия, редакция 3.0, релизы 3.0.205.22
Вступайте в нашу телеграмм-группу Инфостарт