Python / Моя интеграция с 1С

в 11:02, , рубрики: python, интеграция, Питон, метки: , ,

Привет!

В этой статье я хочу рассказать о том, как налажена интеграция с платформой 1С в моей организации. Побудило меня это сделать практически полное отсутствие технической информации на эту тему. Читая различные статьи и доклады на тему связки 1С с какой-либо информационной системой, раз за разом убеждаешься, что все они носят маркетинговый, демонстрационный характер, и никогда — технический, отражающий проблему и суть ее решения.

Предупреждаю, что способ ни в коем случае не претендует на универсальность. Поскольку самих конфигураций 1С существует немало, а информационных систем, языков и платформ — еще больше, то количество возможных комбинаций огромно. Моя цель — продемонстрировать одно из возможных решений.

В качестве языка, который будет интегрироваться с 1С, я выбрал Питон. Он очень хорошо подходит для автоматизации процессов. Этому способствуют минималистичность синтаксиса (код набирается очень быстро), богатая стандартная библиотека (меньшая потребность в сторонних модулях), кроссплатформенность — с большой вероятностью, код, написанный в ОС Linix, успешно заработает в Windows.

Для начала обрисую данные, с которыми будем работать. Организация — энергосбытовая компания в дальневосточном регионе — обслуживает приблизительно 400 тыс. абонентов, база 1С на самописной конфигурации. Для каждого абонента хранятся его платежи, начисления, потребляемые услуги и схемы расчета, приборы учета, показания и множество других данных.

Когда-то в организации стояла программа, написанная на Дельфи и использующая в качестве БД MSSQL/Firebird. В те славные времена можно было подключиться к базе с помощью любого языка и совершить множество действий — выбрать абонентов-должников, разнести поступившие оплаты, зафиксировать показания приборов. Неудивительно, что коллекция скриптов, автоматизирующих рутину, постоянно росла. Программисты могли выполнять любые действия, не открывая саму программу.

Увы, с переходом на 1С халява кончилась — не стало возможности соединяться с базой напрямую. Вообще, платформа 1С сама по себе неделима и плохо идет на интеграцию с другими системами. Она, как говорится, вещь в себе. Загружая данные в 1С, следует помнить, что извлечь их оттуда будет не так просто. Но в виду того, что организации требовалось внедрять платежные системы и личный кабинет, было необходимо найти какое-то решение.

Основные задачи, стоявшие передо мной — это возможность быстрого получения данных по конкретному лицевому счету — ФИО, адрес, приборы учета, показания приборов, платежи, начисления. Плюс формирование документов — акта сверки, платежной квитанции. Итак, возможность прямого соединения с БД отсутствует — каждый, кто просматривал базу 1С на SQL-сервере, видел, что в массе таблиц вида aaa1, aaa2 разобраться трудно. А строить запросы с такими названиями таблиц и полей просто нереально. К тому же, многие таблицы 1С (особенно самые важные, вроде среза последних, остатков и оборотов) являются виртуальными и разбросаны по разным физическим таблицам, собираясь множественными джоинами. Это способ не подходит.

Платфома 1С предоставляет возможность соединяться с ней через COM-соединение. Подобно многим windows-программам, во время установки 1С в системе регистрируются два COM-объекта — Automation Server и COM Connector. С обоими объектами можно работать, используя язык, в котором предусмотрена поддержка COM-технологии.

Объект Automation Server — это приложение 1С, почти ничем не отличающееся от обычного клиентского приложения. Разница в том, что дополнительно появляется возможность программного управления экземпляром приложения. При работе с объектом COM Connector запускается облегченный вариант 1С-приложения, в котором недоступны формы, а так же функции и методы, имеющие отношение к интерфейсу и визуальным эффектам. Само приложение запускается в режиме «Внешнее соединение». Инициализация глобальных переменных (например, определение текущего пользователя и его настроек) должна выполняться в модуле внешнего соединения 1С. Если в режиме внешнего соединения в коде будет вызвана функция, не доступная в этом режиме, то будет вызвано исключение (которое будет передано в наш питон-скрирт). Вызов небезопасных функций следует обрамлять конструкциями вида

#Если НЕ ВнешнееСоединение Тогда Предупреждение("Привет!"); #КонецЕсли 

Поскольку работа с COM-объектами — технология исключительно windows-only, то не удивительно, что в стандартной поставке Питона она отсутствует. Потребуется установить расширение Win32 — набор модулей, предоставляющих весь нужный функционал для программирования под Windows на Питоне. Его можно скачать в виде уже собранного exe-установщика. Само расширение предоставляет доступ к реестру, службам, ODBC, COM-объектам и т.д. В качестве альтернативы можно сразу поставить дистрибутив ActiveState Python, в котором расширение Win32 поставляется из коробки.

Некоторое время я экспериментировал с COM-соединением в разработке веб-приложений, в частности, личного кабинета. Были выявлены следующие минусы:

— COM-соединение работает медленно. Низкая производительность — известный минус COM-технологии.
— Процесс установки соединения с 1С в зависимости от конфигурации может занять от 1 до 8 секунд (в моем случае — 6 секунд). Стоит ли говорить, что установка соединения на каждый запрос приведет к тому, то каждая страница будет загружаться 8 секунд.
— Поскольку веб-приложения на питоне работают как самостоятельные сервера, то предыдущий пункт можно компенсировать хранением соединения в некоторой глобальной переменной и в случае ошибки восстанавливать его. Как поддерживать соединение на PHP, я, честно говоря, еще не думал.
— Теряется кроссплатформенность веб-приложения.

Исходя из перечисленных выше пунктов, было решено изменить принцип взаимодейстия, разделив его на 2 части — первую платформозависимую (виндовую), выгружающую данные 1С в какой-либо удобный формат, и вторую, не зависимую от платформы, способную работать с данными, ничего не подозревая об 1С в принципе.

Стратегия действий состоит в следующем: питон-скрипт соединяется с 1С, выполняет нужные запросы и выгружает данные в SQLite базу. К этой базе можно подключиться из Питона, PHP, Джавы. Большинство наших проектов работает на питоне, и так как я не выношу писать сырые SQL-запросы руками, то вся работа с базой SQLite выполняется через ORM SQLAlchemy. Потребовалось лишь описать структуру данных базы декларативном стиле:

from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, Numeric, DateTime, Unicode, Boolean, LargeBinary, ForeignKey  Base = declarative_base() class Abonent(Base):     __tablename__ = "abonents"      id = Column(Integer, primary_key=True)     account = Column(Unicode(32), index=True)     code = Column(Unicode(32))     address = Column(Unicode(512))     fio = Column(Unicode(256))     source = Column(Unicode(16))     psu = Column(Unicode(256))     tso = Column(Unicode(256))     np = Column(Unicode(256))     street = Column(Unicode(256))     house = Column(Integer)     flat = Column(Integer)     mro = Column(Unicode(256))  class Payment(Base):     __tablename__ = "payments"      # и так далее... 

Теперь достаточно импортировать этот модуль в любой питон-проект, и можно работать с данными.

Предвижу ваш вопрос — «а почему SQLite»? Главная причина — база нужна только для чтения, поэтому проблемы с записью в SQLite нас волновать не должны. Во-вторых, формат этой этой СУБД удобен — ее удобнее просматривать (существуем множество бесплатных утилит, в том числе супер-расширение для FireFox). В-третьих, в некоторых случаях требовалось получить доступ к абонентам с тех машин, на которых нет соединения с MySQL-сервером. В таком случае достаточно скопировать файл SQLite-базы, и на этой машине будет доступ ко всей информации.

Выгрузка происходит раз в сутки ночью. Занесение данных в 1С можно автоматизировать таким же образом. Например, требуется фиксировать показания, оставленные абонентами на сайте личного кабинета. В этом случае опять соединяемся с 1С и программным методом создаем и проводим документ «Акт снятия показаний». Код я приведу чуть ниже.

Работа с COM-объектами в Питоне немного необычна. Во-первых, утрачивается «питоничность» кода — правила именования переменных и функций в 1С, мягко говоря, не соответствуют Дзену Питона. Во-вторых, всем известно, что объекты 1С зачастую именуются кириллическими символами, что вызовет проблемы при разработке на Питоне… но они решаемы. Предлагаю ознакомиться с кодом:

import pythoncom import win32com.client  V82_CONN_STRING = "Srvr=v8_server;Ref=v8_db;Usr=username;Pwd=megapass;"  pythoncom.CoInitialize() V82 = win32com.client.Dispatch("V82.COMConnector").Connect(V82_CONN_STRING) 

Как видно из кода, инициализируется клиент для работы с 1С. Определение COM-объекта происходит по имени «V82.COMConnector». Обратите внимание, что это название справедливо для платформы V8.2, если у вас версия 8.1, то имя будет «V81.COMConnector».

У инициализированного клиента мы вызываем метод Сonnect(), передавая ему строку подключения. Строка складывается из имени сервера, базы, пользователя и пароля. Полученный объект V82 хранит в себе соединение с приложением 1С. У него нет метода Disconnect() или чего-то в этом роде. Чтобы отключиться о базы, достаточно удалить объект из памяти функцией del() или присвоить переменной None.

Имея объект, можно обращаться к любым полям и методам глобального контекста 1С, оперировать универсальными объеками типа ТабличныйДокумент, ТаблицаЗначений и тд. Важно учесть, что при работе через COM-соединение 1С работает в режиме «Внешнее соединение». В нем недоступны любые функции для интерактивной работы, например, всплывающие диалоги, уведомления, и, что самое главное, формы. Уверен, что вы не раз проклянете разработчиков конфигурации, которые заключают самый важный функционал в процедуру Кнопка1Нажатие() в модуле формы документа.

Давайте поговорим о такой важдной вещи, как киррилические атрибуты. Не смотря на то, что 1С — двуязычная среда и для каждого русского метода есть англоязычный аналог, рано или поздно потребуется обратиться к киррилическому атрибуту. Если на языках PHP или VBSCript это не вызовет никаких проблем,

Set Con = CreateObject("v81.COMConnector") Set v8 =Con.Connect("строкаПодключения") Set СчетаМенеджер = v8.Документы.Счета .... Set СчетаЗапись= СчетаМенеджер.СоздатьЭлемент() СчетаЗапись.Контрагент = .... .... СчетаЗапись.Записать() 

то код на питоне просто не вылетит с ошибкой Syntax Error. Что же делать? Править конфигурацию? Нет, достаточно воспользоваться методами getattr и setattr. Передавая в эти функции COM-объект и кириллическое имя аттрибута, можно соответственно получать и устанавливать значения:

#coding=cp1251  catalog = getattr(V82.Catalogs, "ЛицевыеСчета") 

Важно следующее: имена реквизитов, а так же параметры функций и методов должны передаваться в кодировке cp1251. Поэтому, чтобы заранее избежать путиницы с кодировками, имеет смысл объявить ее в начале файла: #coding=cp1251. После этого можно передавать строки, не волнуясь об их кодировке. Но! Все строки, полученные из 1С (результаты вызова функций, запросов), будут в кодировке UTF-8.

Пример кода, который выполняет в среде 1С запрос, перебирает результат и сохраняет в SQLite базу:

#coding=cp1251      q = ''' ВЫБРАТЬ 	ЛицевыеСчета.Код КАК code, 	ЛицевыеСчета.Строение.НаселенныйПункт.Наименование + ", " + ЛицевыеСчета.КраткийАдрес КАК address, 	ЛицевыеСчета.Абонент.Наименование КАК fio, 	ЛицевыеСчета.Дивизион.Наименование КАК psu, 	ВЫРАЗИТЬ(ХарактеристикиЛицевыеСчетаСрезПоследних.Значение КАК Справочник.ТерриториальноСетевыеОрганизации).Наименование КАК tso, 	ЛицевыеСчета.Строение.НаселенныйПункт.Наименование КАК np, 	ЛицевыеСчета.Строение.Улица.Наименование КАК street, 	ЛицевыеСчета.Строение.Дом КАК house, 	ЛицевыеСчета.ОсновноеПомещение.НомерПомещения КАК flat,     ЛицевыеСчета.Дивизион.Родитель.Наименование КАК mro ИЗ 	Справочник.ЛицевыеСчета КАК ЛицевыеСчета 		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ХарактеристикиЛицевыеСчета.СрезПоследних(, ВидХарактеристики = ЗНАЧЕНИЕ(Справочник.ВидыХарактеристик.ТерриториальноСетеваяОрганизация)) КАК ХарактеристикиЛицевыеСчетаСрезПоследних 		ПО ЛицевыеСчета.Ссылка = ХарактеристикиЛицевыеСчетаСрезПоследних.Объект '''     query = V82.NewObject("Query", q)     selection = query.Execute().Choose()      CONN = db.connect()     CONN.query(models.Abonent).delete()      while selection.Next():         abonent = models.Abonent()          abonent.account = selection.code.strip()         abonent.code = selection.code         abonent.fio = selection.fio         abonent.address = selection.address         abonent.psu = selection.psu         abonent.tso = selection.tso         abonent.source = u"ASRN"         abonent.np = selection.np         abonent.street = selection.street         abonent.house = selection.house         abonent.flat = selection.flat         abonent.mro = selection.mro          CONN.add(abonent)     CONN.commit() 

Здесь CONN — это сессия соединения с SQLite-базой. Создается объект запроса query, заполняется его текст. Как было замечено выше, текст запроса должен быть в cp1251, для чего вначале объявлена кодировка. После выполнения запроса в базе удаляются все абоненты, чтобы не добавить дубли, затем добавляются в цикле и следует финальный комит.

При работе с запросам я выявил следующие правила.
— Выбирая поля, назначайте им названия латиницей, будет гораздо удобнее обращаться к ним через селектор (точку), вместо getattr().
— Выбирайте только примитиные типы данных: строки, числа, дату и булево. Никогда не выбирайте ссылки на объект (документ, справочник)! В данном контексте ссылки вам абсолютно не нужны и даже вредны, потому что любое обращение к реквизиту или методу ссылки приведет к запросу через COM-соединение. Если обращаться к атрибутам ссылки в цикле, то это будет крайне медленно.

Если вы выбираете поле типа Дата, то оно буде возвращено как объект PyTime. Это специальный тип данных для передачи даты-времени в COM-соединении. С ним не так удобно работать, как с привычным datetime. Если передать этот объект в int(), то вернется timestamp, из которого потом можно получить datetime методом fromtimestamp().

Теперь рассмотрим, как формируются печатные документы. Дело в том, что потребителю нужно предоставлять возможность скачивать заранее подготовленные документы, например, платежную квитанцию или акт сверки. Эти документы формируются в 1С в соответствии установленным требованиям, их реализация на Питоне займет много времени. Поэтому лучше сгенерировать документы в 1С и сохранять их в формат Excel.

Так, документ акта сверки генерируется специальной внешней обработкой. Для тех, кто не знаком с терминологией 1С: обработка — это автономная программа, имеющая свой модуль, формы, шаблоны, предназначенная для запуска в среде 1С. Необходимо инициализировать обработку, заполнить ее реквизиты и вызвать функцию, которая вернет нам табличный документ, предназначенный для просмотра в 1С. Этот документ нужно сохранить в формат Excel и скопировать на сервер или записать в базу.

link = getattr(V82.Catalogs, "ОтчетыСистемы").FindByDescription("Акт Сверки Элэн") nav_url =  V82 .GetURL(link, "Отчет") name =  V82 .ExternalReports.Connect(nav_url) ExternalReport =  V82 .ExternalReports.Create(name)  setattr(ExternalReport, "ЛицевойСчет", reference) table_doc = ExternalReport.GetDoc() path =  V82 .GetTempFileName("xls") table_doc.Write(path,  V82 .SpreadsheetDocumentFileType.XLS)  report = models.Report() report.account = reference.Code.strip() report.type = u"act" report.document = open(path, "rb").read()  CONN.add(report) 

В приведенном фрагменте выполняется следующее. Подключается обработка, формирующая документ. Обработка может быть встроена в конфигурацию, храниться на диске или в базе данных 1С (в каком-то справочнике). Поскольку обработки часто меняются, то, чтобы каждый раз не обновлять конфигурацию, самые часто меняющиеся обработки хранятся в справочнике «ОтчетыСистемы», в реквизите типа «хранилище значения» с именем Отчет. Обработку можно инициализировать, выгрузив ее из базы на диск и подгрузив, либо методом GetURL(), в который нужно передать ссылку на элемент справочника и имя реквизита. Полученному объекту обработки мы назначаем значения реквизитов, вызываем экспортируемую функцию GetDoc(), получаем табличный документ, который сохраняется во временный Excel-файл. Содержимое этого файла записывается в SQlite-базу.

Последнее, что остается рассмотреть — это программное занесение данных в 1С. Предположим, что требуется занести показания от абонентов. Для этого достаточно создать и провести документ «Акт снятия показаний»:

#coding=cp1251  acts = getattr(V82.Documents, "АктСнятияПоказаний") act = acts.CreateDocument()  setattr(act, "Показание", 1024.23) setattr(act, "Абонент", "Иванов")  # Заполнение прочих реквизитов...  act.Write()  

Теперь занесение данных автоматизированно.

Итак, я изложил способ, который основан на программной выгрузке и загрузке даных с использованием COM-соединния. Этот метод успешно функционирует в моей организации почти год. База, формируемая из 1С, обсулживает 3 платежные системы, интернет-эквайринг (оплата картам через интернет), а так же личный кабинет. Помимо этого, к базе подключаются различне скрипты для автоматизации рутины.

Несмотря на недостатки метода (медленная скорость COM-соединения), в целом он функционирует стабильно. У нас есть данные в платформонезависимом виде (SQLite), с которыми можно работать из любого языка. И основная часть кода написана на Питоне, а значит, доступны множество средств и приемов, о которых даже нельзя мечтать в 1С.

Это один из возможных способов взаимодейстия с 1С. Я уверен, что он не нов и наверняка уже был кем-то опробован, оптимизирован. Однако, я постарался изложить максимум делатей процесса, чтобы уберечь вас от подводных камней, на которые сам наступал. Желаю всем удачи, и помните, что не так страшен 1С, как его малюют!

Автор: igrishaev


  1. maxim:

    посмотрите comtypes – у меня всё работает без монстровой pywin32, проект с семёркой здесь venster-continue.googlecode.com, сейчас восьмёрочный доделываю

  2. maxim:

    с восьмёркой будет чистый Python

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js