"Мы словно живём в комнате, где на стене висит плакат. Уставимся на него и думаем, что это и есть весь мир. Комната... И плакат. На плакате что-то симпатичное: пейзаж, знаменитость. Как в том фильме про тюрьму... Как же он назывался? Комната — тюремная камера. А картинку каждый из нас видит по-своему. Она может быть прекрасной или ужасающей, но все мы к ней прикованы. Но это всё неправда: лишь ширма, скрывающая истину. Они нам врут. Мы врём самим себе. Комната — не весь мир. Мир намного больше, намного удивительнее. Плакат на стене скрывает лаз, ведущий в реальный мир. Мы ощущаем себя в безопасности в той комнате. Но иногда... Иногда нечто выползает из-за плаката. И каждый, кто становится тому свидетелем, в страхе пытается забыть о том, что видел."
- Джесси Фейден, Control
Итак, одеваем свой фурсьюит и ставим задачу:
Есть база 1С, в которой есть документы, к которым привязаны штрихкоды. Для простоты возьмём EAN13, но вообще более перспективными выглядят QR.
Штрихкоды печатаются в печатных формах, а еще есть возможность печатать их на этикетках и лепить на готовые бумажные документы.
Обе эти задачи решаются тривиально, например, с помощью специального шрифта для печати EAN13 и дополнительного реквизита.
Но теперь, после того, как эти распечатанные документы подписали, мы хотим их засунуть обратно в 1С. Для хранения файлов в БСП есть готовые средства, соответственно, они присутствуют во всех типовых конфигурациях.
Прикрепление небольшого количества файлов не вызывает затруднений - мы можем сканировать и добавлять их вручную. Однако, не будем забывать о том, что уже изобрели пакетное сканирование.
Будем считать, что пользователь кладет в сканер сразу все листы, относящиеся к одному документу. Например, это может быть счет на трёх страницах, коммерческое предложение и прайс. Условимся, что весь этот пакет будет отсканирован в один PDF-документ (для удобства дальнейшего использования).
Теперь у нас получилась папка с pdf-файлами, в которых содержатся сканы документов со штрихкодами. Нужно распознать штрихкоды и привязать их к документам. Естественно, часть про привязать находится на стороне 1С, но что с распознаванием?
Варианты примерно следующие:
- использовать какую-то внешнюю компоненту (вероятнее всего, просто ActiveX)
- использовать CLI утилиту (такой вариант мне нравится всё больше)
- использовать REST API, или, по-нашему, HTTP-сервис - красиво, модно, молодежно!
Собственно, третьим вариантом и попробуем воспользоваться. А так как сейчас довольно популярен Python, и я про него практически ничего не знаю, то я попробовал решить эту задачу с его помощью.
Архитектура будет такая:
в одной локальной сети стоит три машины:
- 1C (сервер или клиент - в данном случае не важно)
- Веб-сервер на питоне
- Обычная сетевая папка, пусть будет Windows
В первой версии архитектуры у сервера 1С и у Flask есть доступ к этой папке. Почему так? Потому что так легче отлаживать - можно делать это через браузер, не загружая каждый раз файл. Потом можно легко переделать на передачу самих двоичных данных файла через HTTP, благо ни у 1С, ни у Питона с этим проблем нет.
Что понадобится для реализации?
- Python
- IDE
- Flask
- pdf2image
- pyzbar
- poppler
Питон вроде бы устанавливается вместе с IDE, но можно скачать c https://www.python.org/downloads/
Очевидные варианты IDE - Microsoft Visual Studio Code, она у меня как раз есть, и PyCharm. Мне нравится IDEA, решил попробовать PyCharm.
Следующие три компонента устанавливаются с помощью пакетного менеджера:
pip install flask
pip install pyzbar
pip install pdf2image
К сожалению, так как на самом Питоне написано ничего, то для работы нужно установить некоторые компоненты. В нашем случае пришлось установить http://blog.alivate.com.au/poppler-windows/, распаковать архив и прописать путь к папке bin в переменную окружения PATH (у меня Windows).
Собственно, все эти библиотеки я нашел за 20 минут гуглежа, думаю, есть варианты и получше, но важен сам принцип.
Во фласке методы REST API прописываются с помощью так называемых роутов, выглядит похоже на 1С:
@app.route("/decode_ean13/<string:strfilename>/<int:pagenum>", methods=["GET"])
Также, как в HTTP-сервисах 1С. можно в качестве части пути объявлять обязательные параметры, но тут их можно еще и типизировать. У меня эти параметры strfilename и pagenum - хочу передавать путь к файлу и номер страницы (на самом деле она по условиям всегда первая, но на будущее), метод будет, естественно, GET (мы хотим только получать данные от этого сервиса). Тут роут может поддерживать несколько методов, также, как и в 1С.
Теперь осталось сохранить указанную страницу файла, путь к которому получили, и распознать с неё штрихкод EAN13.
За сохранение страницы как раз отвечает pdf2image, и делает это с помощью единственного вызова:
pages = convert_from_path(filename, dpi=300, first_page=pagenum, last_page=pagenum)
Здесь pages - список полученных страниц. Так как мы получаем только одну страницу, то она у нас будет pages[0].
За распознавание штрихкода отвечает pyzbar:
decode(image, symbols=[ZBarSymbol.EAN13])
Здесь я сразу ограничиваю виды штрихкодов, которые нужно распознавать - думаю, так будет работать быстрее. Кроме того, я так снижаю вероятность распознавания чужих штрихкодов - ведь, например, на документе поставщика может быть штрихкод поставщика и наш. Конечно, они оба могут быть EAN13, но какую-то часть ошибок я отсеку.
Как выяснилось, decode в качестве параметра принимает объект типа Image, и convert_from_path возвращает объект типа Image, но это разные Image :(
В общем, пока для простоты я полученную страницу сохраняю в PNG-файл, и тут же загружаю его обратно и распознаю штрихкод. В результате распознавания возвращается список со всеми штрихкодами, которые удалось распознать, и это очень полезно - помним про штрихкоды поставщика? Эти штрикоды в виде JSON я и возвращаю в ответ.
В процессе выяснилась странная особенность то ли Flask, то ли не знаю чего: нельзя передать слэш (разделитель пути) даже в виде url-кода %2F. Поэтому я его заменил на |:
Вот пример изображения:
В принципе, для случая с сетевой папкой, которая доступна для обеих машин, всё уже работает - осталось научиться сохранять картинку во временный файл, а не в папку с исходным PDF (она по идее не должна быть доступна на запись).
Для случая, когда папка недоступна, нужны доработки: 1С будет отправлять двоичные данные, а сервер на Питоне будет распознавать штрихкод из них. Для этого в pdf2image уже есть convert_from_bytes, и еще нужно выяснить, как объект-изображение из pdf2image преобразовать в изображение pyzbar. Заглушки в коде я уже поставил ;)
Код из статьи доступен в репозитории https://github.com/AlexNecro/NBarcoder. На стороне 1С ничего не делалось - и так всё понятно.
PS. Спросите, причем тут девопс? Ну так нужно этот скрипт на питоне положить в Docker, вот и всё!
PPS. Прошу пинать, так как всё натыкано мышкой как попало, хотелось бы привести всё в нормальный вид, а опыта работы с Питоном, Гитом и Докером особо нет.