Создание платёжки из скана при помощи модели ИИ: как устроена обработка, которая читает счёт и создаёт платёжное поручение
Файлы
ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.
Подписка PRO — скачивайте любые файлы со скидкой до 85% из Базы знаний
Оформите подписку на компанию для решения рабочих задач
Оформить подписку и скачать решение со скидкойВы можете заказать платную доработку или адаптацию этой разработки под вашу конфигурацию на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.
1. Постановка задачи
Бухгалтер получает счёт на оплату в PDF/JPG/DOCX. Дальше — рутина: найти или завести контрагента, проверить ИНН/КПП, завести банк по БИК, банковский счёт, потом открыть «Платёжное поручение» и перенести туда десяток реквизитов.
Обработка закрывает весь путь:
- выбрать файл счёта;
- отправить его в сервис распознавания;
- найти контрагента по ИНН/КПП или создать нового (вместе с банком и счётом);
- открыть форму нового платёжного поручения с уже заполненными реквизитами.
Ключевое решение, которое стоит одобрить сразу: документ не записывается. Обработка возвращает ЗначенияЗаполнения, а форму открывает платформа:
ПараметрыФормы = Новый Структура("ЗначенияЗаполнения", Результат.ЗначенияЗаполнения);
ОткрытьФорму("Документ.ПлатежноеПоручение.ФормаОбъекта", ПараметрыФормы, ЭтотОбъект);
Пользователь видит заполненный документ, проверяет и сохраняет сам. Это правильная граница ответственности: распознавание ошибается, а «тихо созданный» платёжный документ в базе — это уже инцидент, а не удобство.
2. Регистрация в БСП: одна функция, три ловушки
Обработка подключается как внешняя через СведенияОВнешнейОбработке() с видом ЗаполнениеОбъекта и размещением в Документ.ПлатежноеПоручение — кнопка появляется прямо в форме документа, что для пользователя гораздо естественнее, чем «открыть файл → найти обработку».
ПараметрыРегистрации.Вставить("Вид", "ЗаполнениеОбъекта");
Размещение = Новый Массив;
Размещение.Добавить("Документ.ПлатежноеПоручение");
ПараметрыРегистрации.Вставить("Назначение", Размещение);
Ловушки, на которые легко наступить:
БезопасныйРежим = Ложь. Обработка пишет в справочники и ходит в сеть, поэтому безопасный режим отключён осознанно. Но это значит, что весь код обработки работает с полными правами — и внешний HTTP-ответ становится входными данными, влияющими на запись в базу. Об этом ниже.Использование = "ОткрытиеФормы". Команда не вызывает серверный метод, а открывает управляемую форму обработки. Если поставитьВызовСерверногоМетода, обработка с видомЗаполнениеОбъектане заработает так, как ожидается.- Справка.
Help/ru.html— пустая заготовка. Для обработки, которую отдают пользователям, справка нужна: как минимум куда вводить адрес сервиса и АПИ-ключ.
3. Транспорт: multipart/form-data руками
Самое «неприятное» место с точки зрения платформы — отправка файла методом POST. Готового «отправить файл в multipart» в языке нет, тело собирается вручную из двоичных данных:
ЧастьЗаголовок = "--" + Разделитель + ПереводСтроки
+ "Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайлаБезПути + """" + ПереводСтроки
+ "Content-Type: application/octet-stream" + ПереводСтроки + ПереводСтроки;
...
Возврат СоединитьДвоичныеДанные(Части);
Здесь всё сделано корректно: разделитель уникален ("WmkBoundary" + Новый УникальныйИдентификатор()), заголовки и двоичное тело кодируются отдельно, СоединитьДвоичныеДанные доступна с 8.3.10, HTTPS включается через ЗащищенноеСоединениеOpenSSL.
Что спорно:
- Разбор адреса вручную. Функция сама отрезает
http:///https://, выдёргивает порт, отделяет путь. Работает, но это 30 строк регулярной логики, которую проще вынести в отдельную функцию с тестами — сейчас она перемешана с отправкой запроса. - Запрос выполняется на клиенте. Для тонкого клиента это плюс: сервер не нагружается ожиданием чужого HTTP. Но на веб-клиенте прямое
HTTPСоединениеимеет ограничения, и вся схема потребует пересмотра. Если целевая аудитория — веб-клиент, транспорт надо переносить на сервер. - АПИ-ключ на клиенте. Ключ лежит в реквизите формы и сохраняется в
ХранилищеОбщихНастроекпод ключом"ПлатёжкаИИ.СервисРаспознавания". «Общее» хранилище — это настройка на всю базу, а не на пользователя. Для облачного сервиса с платным ключом это стоит как минимум обсудить: либо настройка на пользователя, либо константа/параметр сеанса на стороне сервера.
4. Два сервиса — один контракт данных
Обработка умеет работать с локальным сервисом (/api/extract_invoice) и облачным (/extract/invoice). Форматы ответа разные, поэтому между транспортом и бизнес-логикой вставлен слой нормализации — и это лучшее архитектурное решение во всей обработке.
Канонический контракт описан прямо в шапке модуля объекта:
organization_name, inn, kpp, ogrn, address, phone,
bank_name, bik, checking_account, corr_account,
invoice_number, invoice_date,
items[], total_amount, total_vat, total_with_vat, has_vat, vat_rate
Облачный ответ приводится к нему маппингом supplier_* → *, а НДС выводится расчётно:
СуммаНДС = ПолучитьЧислоИзСоответствия(Результат, "total_vat");
Результат.Вставить("has_vat", СуммаНДС > 0);
Вся остальная логика — создание контрагента, счёт, платёжка — работает уже только с каноническими ключами и про формат сервиса не знает. Добавление третьего сервиса сведётся к одной новой функции нормализации. Именно так и надо делать интеграции.
5. Запись в чужие справочники: оборона через метаданные
Обработка предназначена для типовой БП 3.0, но написана так, будто конфигурация может быть любой. Отсюда — слой «безопасной установки реквизитов»:
Функция УстановитьРеквизитБезопасно(Объект, ИмяРеквизита, Значение)
...
Если НЕ ЕстьРеквизит(МетаданныеОбъекта, ИмяРеквизита) Тогда
Возврат Ложь;
КонецЕсли;
Если Объект[ИмяРеквизита] = Значение Тогда
Возврат Ложь;
КонецЕсли;
Объект[ИмяРеквизита] = Значение;
Возврат Истина;
КонецФункции
Функция возвращает Истина, только если значение реально изменилось — за счёт этого запись выполняется лишь при наличии изменений, и вызывающий код не пишет «пустые» Записать().
Дальше это используется везде: НаименованиеПолное/ПолноеНаименование, ЮридическийАдрес/ЮрАдрес/Адрес, КоррСчет/КоррСчетБанка, перечисление СтавкиНДС или справочник СтавкиНДС, Банки или КлассификаторБанков. Приём рабочий, но у него есть цена: логика чтения реквизитов «по имени строкой» не проверяется парсером, ошибки в именах выясняются только в рантайме, а количество ветвлений растёт с каждой конфигурацией.
Компромисс, который я бы предложил: оставить «безопасные» хелперы только на границе нестабильных сущностей (банки, НДС, адреса), а для контрагента и банковского счёта писать прямым кодом под БП 3.0. Иначе обработка превращается в мини-БСП, который никто не поддерживает.
6. Транзакции и порядок операций
Создание контрагента обёрнуто в транзакцию: контрагент, банк, счёт, контактная информация — либо всё, либо ничего. Это правильно, но в коде есть нюанс:
РезультатСчета = СоздатьОбновитьБанковскийСчет(Результат.Контрагент, Данные);
...
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = СтрШаблон(НСтр("ru = 'Создан банковский счёт %1, банк: %2'"), НомерСчета, Банк);
Сообщение.Сообщить();
СообщениеПользователю.Сообщить() вызывается внутри транзакции, до ЗафиксироватьТранзакцию(). Если транзакция потом откатится, пользователь уже увидит сообщение о созданном счёте. Сообщения (как и любые побочные эффекты — запись в журнал, ОповеститьОбИзменении) нужно собирать в структуру результата и выдавать после фиксации. Сейчас частично так и сделано (Результат.СчетНовый, Результат.БанкСоздан), а эта строка — рудимент.
Ещё один момент: ЗаписатьКонтактнуюИнформациюКонтрагента пишет напрямую в регистр сведений КонтактнаяИнформация, минуя УправлениеКонтактнойИнформацией из БСП. Набор записей перечитывается, существующая запись обновляется — дублей не будет. Но штатный механизм БСП умеет больше (типы, представление, проверка вида), и использовать его было бы честнее, если он есть в базе.
7. НДС: самая сложная часть
Распознавание НДС — источник ошибок, и здесь накопилось несколько решений, которые стоит пересмотреть:
has_vatнормализуется из строки. Поддерживаются"true","истина","1","да". Важно, чтобы нормализация заканчивалась на слое разбора ответа.- Ставка берётся из первой позиции (
items[0].vat_rate) и считается за весь счёт. Для платёжного поручения, где ставка одна, это вполне приемлемо. ПолучитьСтавкуНДСПоДаннымимеет два документирующих комментария подряд — остаток рефакторинга. Один из них описывает несуществующий параметрДокументОбъект. Комментарий — часть контракта метода, и его расхождение с сигнатурой хуже, чем его отсутствие.- «Сумма прописью». Переменная называется
СуммаПрописью, а внутри —Формат(СуммаИтого, "ЧДЦ=2; ЧРГ=' '; ЧН=0"). Это не прописью, это число с разделителем разрядов. Либо переименовать, либо действительно использоватьЧислоПрописью. Для назначения платежа второе выглядит уместнее.
8. Чек-лист для похожих задач
Если вы делаете обработку «распознай документ → заполни объект», вот что стоит проверить у себя:
- Документ не пишется без человека. Возвращайте
ЗначенияЗаполненияи открывайте форму — не создавайте и не проводите документ автоматически. - Контракт данных фиксирован и описан в шапке модуля. Один слой нормализации на каждый внешний сервис, выше — только канонические ключи.
- Внешние ответы — это недоверенный ввод. Тип проверяется на каждом уровне (
ТипЗнч), числа парсятся черезПопытка, отсутствующие ключи не приводят к исключению. - Запись в справочники — в транзакции, сообщения — после фиксации.
Записать()только при реальных изменениях — хелпер, возвращающий признак изменения, экономит транзакции и регистрацию изменений для обмена.- Никаких реквизитов «через точку» на чужих ссылках. Здесь это соблюдено: все выборки — запросами (
ВЫБРАТЬ ПЕРВЫЕ 1 ... ГДЕ ИНН = &ИНН). - Секреты не в общем хранилище настроек. Ключ сервиса — это либо константа на уровне базы, либо настройка пользователя, но не то, что доступно всем.
- Справка заполнена. Внешняя обработка без инструкции — это обработка, которую будут запускать «как получится».
Главный вывод для тех, кто делает что-то похожее: сложность в такой задаче — не в вызове ИИ, а в записи результата в базу. 80% кода здесь — это не распознавание, а «а вдруг справочник называется иначе», «а вдруг ставка НДС — справочник, а не перечисление», «а вдруг банка по БИК ещё нет». Именно эту часть и надо проектировать в первую очередь.
Проверено на следующих конфигурациях и релизах:
- Бухгалтерия предприятия, редакция 3.0, релизы 3.0.64.54
Вступайте в нашу телеграмм-группу Инфостарт