Pull to refresh

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

Reading time11 min
Views180K
Привет Хабравчанам!

В этой статье я хочу рассказать о том, как налажена интеграция с платформой 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С, как его малюют!
Tags:
Hubs:
+27
Comments65

Articles