Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest)

в 13:10, , рубрики: api, django, ERP-системы, python, sap, sap cloud platform, SAP ERP, SAP HANA

Добрый день!

Всегда была интересна тема интеграции больших систем вроде SAP с небольшими, но более гибкими, так-сказать взять лучшее из того и другого.

В частности, в моем примере будет описана интеграция SAP ERP с Django.

Задача

Из за введенного нашим любимым государством множества разных систем контроля: Егаис, Меркурий и многое другое, многие компании принялись адаптировать свои тяжелые, и мягко сказать неповоротливые системы (касается больших компаний) к новым условиям. Не буду говорить в каких в частности адаптировал и я, но в голове всегда крутилась мысль – Создать единую систему отслеживания всего на базе отдельной платформы.

Средства

Не особо долго выбирав какие инструменты взять, я выбрал: Язык программирования Python – ввиду обилия библиотек со всем и вся, платформу Django, вот не спрашивайте почему Django, а не Flask или Odoo. Odoo я уже брал за платформу и хотелось изучить одну из этих, взял первую, ну не знаю почему, наверное из за большей простоты. ERP систему для разработки SAP- ну тут у меня не особо был выбор, т.к. я работаю в компании интеграторе SAP, поэтому у меня есть и знания и доступ к песочницам этой системы, что бы имея все условия спокойно делать свое дело беспрепятственно.

Frontend Django

Первое с чего я начал, это сформулировал конкретную задачу и расписал ее на бумажке, вообще всем советую прежде чем что либо кодить, описать процесс, и ОЧЕНЬ важно, если вы на ходу его меняете, в описании меняйте тоже.

Вот первый ОЧЕНЬ грубый вариант описания программы.

Исходящий процесс

1) Создается исходящая поставка

2) При проходе паллеты сквозь ворота — Автоматическая работа и полуавтоматическая работа

a. Автоматическая работа/Когда паллет проходит сквозь ворота, программа запрашивает по RFC в WMS системе сведения о том, что это за поставка, ее номер и посылает обратно в WMS систему ответ о идентификации к поставке ( возможно подтверждает складские задачи(задания на комплектацию) по этой паллете и всем вложениям ). Также сверяет все акцизные марки со сведениями в WMS системе

b. Полуавтоматическая работа/ Оператор вводит в системе ворот номер поставки/машины и прогоняют паллеты сквозь ворота, система ворот по каждой паллете посылает запрос в WMS систему для сверки акцизных марок внутри.

3) Отправляется состав поставки в Учетную систему

Входящий процесс

1) Создается входящая поставка

2) Проход паллета сквозь ворота

3) Посылается запрос к учетной системе о составе подлежащей приёмки на текущий склад

4) Проверяется внутренний состав паллета акцизных марок на основании данной учетной системы

5) Посылается сигнал в WMS системе о разгрузке паллеты.

Необходимые таблицы:

Ворота:
Идентификатор;
Склад:
Описание.
Сообщение о прохождении:

Заголовок:
Время, система, номер склада, идентификатор ворот.
Позиция:
Акцизная марка, время регистрации, привязка к заголовку
Сообщение из ERP о составе (входящая поставка)

Заголовок:
Время, система, номер поставки,
Позиция:
Материал, акцизная марка, номер паллеты(если есть)

Агрегированное сообщение (основанное на данных из ERP):

Заголовок:

Время, система, номер склада, идентификатор ворот, номер поставки из Учетной системы, Признак направления (Входящая исходящая), Признак сценария проверки, номер машины, номер ворот склада,

Позиция: Акцизная марка, номер паллета(опц), материал(опц), номер поставки, номер машины, номер позиции в документе, партия(опц), упаковка(опц)

Далее я начал изучать Django и рисовать процесс и схему БД.
Как оказалось в Django создавать модели-таблицы очень легко и удобно, это примерно выглядит так:

class SapOptions(models.Model):
    name = models.CharField(verbose_name='Имя системы', max_length=50)
    baseurl = models.CharField(max_length=500, verbose_name='Url системы базовый', help_text = 'URL сервиса базовый, до класса, например :"https://moses1005:44300/sap/opu/odata/sap/ZLS_SUPPLYCHAIN_SRV/"')# Базовый URL 
    sapset = models.CharField(default='Enter Sapset', max_length=100, verbose_name='Имя (Сета)')
    mandt = models.CharField(max_length=3, verbose_name='Мандант')
    user = models.CharField(max_length=15, verbose_name='Имя юзера под которым будет выполнятся вход сервиса')
    passwd = models.CharField(max_length=15, verbose_name='Пароль')
    verify = models.BooleanField(default=False, help_text = 'Будет ли соединение безопасным')
    def __str__(self):
        return 'Имя: '+self.name + ', Мандант : '+self.mandt

class Gates(models.Model):
    from mainAPP.sap_connector import get_lgorts_fromsap
    ident = models.CharField(verbose_name='Ворота', max_length=10, help_text='Склад',unique=True)
    wh = models.CharField(verbose_name='Ссылка на склад', default='',max_length=10, help_text='Связь со складов WMS')
    help = models.CharField(verbose_name='Описание', default='',max_length= 500,help_text='Описание ворот, где, для чего, на каком складе')
    try:
        lgorts = get_lgorts_fromsap()
    except:
        lgorts = [('No Connect', 'No Connect'),]
    lgort = models.CharField(verbose_name='Склад',default='0000', max_length=20, choices=lgorts)

    def __str__(self):
        return self.ident +' : '+self.wh+' : '+self.help

После я уже понял, как из SAP дергать справочники, что бы интеграция казалась вовсе “бесшовной” ключевое слово “казалось”, но об этом позже.

Итак, после изучения Django (убил несколько вечером на это) я написал интерфейс для ввода информации и последующей отправки ее в SAP ERP.

Первый экран ввода информации о приемке выглядит так:

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 1

  • Склад – прямая интеграция с SAP ERP с кратким описанием,
  • Поставка ERP – Вводится поставка из SAP ERP, при вводе верифицируется (происходит запрос в SAP, есть ли такая поставка или нет,
  • ТТН – тут все понятно (Товарно-транспортная накладная),
  • Номер партнера – Это номер партнёра SAP ERP, поле не обязательное, оно сделано на будущее, чтобы находить поставку,
  • Идентификатор упаковки – Это одно из самых важных полей, это номер паллета или упаковки.

Также интерфейс адаптирован для мобильного терминала (ТСД)

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 2

Поля сделаны таким образом, что после каждого нажатия ENTER курсор перескакивал на следующее поле ввода, что бы можно было удобно сканировать всю информацию с ТТН, Ворот, Паллета.

Далее после сканирования последнего поля или нажатию “Сохранить” экран переходит на диалог ввода идентификаторов каждого товара:

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 3

Поля:

  1. Это экран списка идентификаторов, которые присланы самой системой ЕГАИС, где Красные, это еще не отсканированные идентификаторы, Желтые, это идентификаторы, которые были отсканированы, но в сообщении ЕГАИС их не было, и зеленые, это присланные ЕГАИС и были отсканированы
  2. Ввод идентификаторов, здесь так-же кнопка “+”, которая нужна, для появление еще одного поля и т.д.
  3. Вывод сообщений об ошибках, если они есть.

Реализация интеграции:

Для интеграции со стороны Django все понятно “rest” реализовать просто, а вот со стороны SAP ERP, пришлось немного почитать).

Итак, как это делается, как оказывается не очень сложно

1) Необходимо создать интеграционный класс для реализации, соответственно к нему пакет разработок. Делается это в транзакции SEGW

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 4

2) После создания класса необходимо определить Data Model, тут есть несколько вариантов, создавать свои поля, или залайкать из SAP уже по таблице. Это значит, что перед тем как создавать модель данных для интеграции, необходимо создать таблицу для данных, это делается в транзакции SE11 и как это сделать можно найти на просторах интернета. Итак, лайкаем структуру,

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 5

Я лайкаю уже созданную мной таблицу-структуру

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 6

3) Вот так выглядит сделанная нами работа:

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 7

Нажимаем “Сгенерировать” Класс сгенерировал необходимую для интеграции структуру, и с ней будем работать.

4) Далее у нас во вкладке Service implementation появляется наша структура со всеми методами доступные ей, в частности:

a. Create – Метод для создании записи в нашей таблице по присланным данным из вне
b. Delete- Метод удалении записи по идентификатору
c. GetEntity — Метод запроса одной записи
d. GetEntitySet – Метод получения множества записей по критериям
e. Update – Метод изменения записи
На самом деле все эти методы достаточно абстрактны, но есть конечно и отличия

5) После генерации класса у нас создается в ветке Runtime Artifacts список классов, выбираем тот, у которого на конце DPC_EXT

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 8
Два раза щелкаем, что бы попасть в сам класс

6) После того, как попали в список методов класса, в конце вы видите список всех методов, обязательно переопределите его, иначе после очередного изменение модели данных, у вас все сотрется, я столкнулся с этим, было обидно…

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 9
Для примера покажу реализацию метода Create

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 10

Все очень просто, на вход подается IT_KEY_TAB и на основании этих данных, мы реализуем какие-то действия, в данном коде обычная запись в таблицу, или вывод ошибки, которая потом будет передаваться в Django. Выходные данные об успешном создании записываем в структуру ER_ENTITY.

7) Тестируем наш интерфейс в транзакции /IWFND/MAINT_SERVICE, длинная такая. Заходим в нее, находим созданные наш класс и нажимаем “Клиент шлюза SAP”

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 11

Нам открывается по сути ЭМУЛЯТОР GETPOSTPUTDELETE web Запросов, только от SAP,

P.S. Можно в чем угодно тестировать созданный сервис., я тестирую в программе postman

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 12

Так выглядит запрос get, “GetEntitySet”
/sap/opu/odata/sap/ZLS_SUPPLYCHAIN_SRV/ZLS_INBOUND_HEADSet?$format=json
Где:
/ZLS_SUPPLYCHAIN_SRV/ — Это наш созданный класс
/ZLS_INBOUND_HEADSet – это созданная нами модель данных,
format=json – это формат данных, который мы получаем, выбор xml или json, я выбираю json, потому что вот почему, мне так удобнее.

8) Аналогично пишем методы

Что мы имеем, создали фронт на Django, создали интерфейс на стороне SAP
Теперь нам необходимо это все оживить, и именно на стороне Django пишем методы:

1) Метод создания сессии, для того, чтобы залогинится, получить scrf-token и уже дальше
дергать нужную информацию из БД по нашему настроенному интерфейсу или создать новую
запись. Для этого создаем отдельный файл в Django, я назвал его Sap_connector.py и описал
в нем основные методы.

def sap_createSession(): # Создание сессии для запросов oDATA
    from scanner.models import SapOptions
    # Конектимся к SAP
    sap_opt = SapOptions.objects.all()[0] # Получаю из локально бд данные коннекта
    s = requests.Session()
    s.headers.update({'Connection': 'keep-alive', 'X-CSRF-TOKEN': 'Fetch'})
    auth = (sap_opt.user, sap_opt.passwd)
    try:
        r = s.get(sap_opt.baseurl, auth=auth,verify=sap_opt.verify)
    except:
        message = "Нет соединения с системой %s  %s"%(sap_opt.mandt, sap_opt.name)
        return ('NO TOKEN', 'NoSession', message)
    token = r.headers['x-csrf-token']
    session = s
    sess = (token, session, None)
    return sess

2) Метод верификации поставки в SAP ERP

def sap_delivery_verify(token, session, delivery): # проверить есть ли поставка в ERP
    from scanner.models import SapOptions, Gates
    sap_opt = SapOptions.objects.all()[0]
    s = session
    format = '?$format=json'
    set = 'likpSet'
    url = sap_opt.baseurl + set + "('"+delivery+"')"+format
    headers = {'Content-type': 'application/json;charset=utf-8', 'X-CSRF-TOKEN': token} # обновляем хидер и добавляем токен
    auth = (sap_opt.user, sap_opt.passwd) #для auth
    get = s.get(url, headers=headers, auth=auth,verify=sap_opt.verify)# Запрос данных


    if get.status_code ==200:
        delivery_out = json.loads(get.text).get('d').get('Vbeln')
        return (True, 'OK')
    else:
        error_text = json.loads(get.text).get('error').get('message').get('value')
        return (False, error_text)

3) Метод интеграции складов SAP с Django

def get_lgorts_fromsap():
    from scanner.models import SapOptions
    session = sap_createSession() # Создаю сессию
    token = session[0]
    session = session[1]
    sap_opt = SapOptions.objects.all()[0] # Получаю настройки
    s = session
    format = '?$format=json'
    set = 't001lSet'
    url = sap_opt.baseurl + set +format
    headers = {'Content-type': 'application/json;charset=utf-8', 'X-CSRF-TOKEN': token} # обновляем хидер и добавляем токен
    auth = (sap_opt.user, sap_opt.passwd) #для auth
    get = s.get(url, headers=headers, auth=auth,verify=sap_opt.verify)# Запрос данных
    jdata = json.loads(get.text)
    lgorts = []
    for l in jdata.get('d').get('results'):
        l.get('lgort')
        lgorts.append((l.get('Lgort'),l.get('Lgort')))
    return lgorts

И его вторая часть в части models:

class Gates(models.Model):
    from mainAPP.sap_connector import get_lgorts_fromsap
    ident = models.CharField(verbose_name='Ворота', max_length=10, help_text='Склад',unique=True)
    wh = models.CharField(verbose_name='Ссылка на склад', default='',max_length=10, help_text='Связь со складов WMS')
    help = models.CharField(verbose_name='Описание', default='',max_length= 500,help_text='Описание ворот, где, для чего, на каком складе')
    try: #Получаем склады из SAP
        lgorts = get_lgorts_fromsap()
    except:
        lgorts = [('No Connect', 'No Connect'),]
    lgort = models.CharField(verbose_name='Склад',default='0000', max_length=20, choices=lgorts)

    def __str__(self):
        return self.ident +' : '+self.wh+' : '+self.help

Интеграция выглядит так: когда я в настройках Django создаю новый склад, то в поле склад система мне выводит непосредственно склады из SAP ERP, которые в данный момент там созданы.

Интеграция с SAP ERP, на примере с Django-python, по протоколу oData(rest) - 13

4) Метод создания новой записи в SAP ERP

def sap_connect(token, session, data):
    from scanner.models import SapOptions, Gates
    # Запись идентификаторов в базу данных SAP ERP
    sap_opt = SapOptions.objects.all()[0]
    s = session
    delivery = data.get('delivery')
    url = sap_opt.baseurl + sap_opt.sapset # Базовая URL + сет для подключения
    headers = {'Content-type': 'application/json;charset=utf-8', 'X-CSRF-TOKEN': token} # обновляем хидер и добавляем токен
    auth = (sap_opt.user, sap_opt.passwd) #для auth
    data =json.dumps({"d":{
        "Mandt": sap_opt.mandt,
        "Lgort": Gates.objects.get(id=data.get('gates')).wh,
        "Vbeln":data.get('delivery'),
        "Mark": data.get('mark'),
        "Matnr": "",
        "Posnr": "",
        "Mbl": "",
        "InbDicid":"AAAAAAAAAAAAAAAAAAAAAA==",
        "DocExt": data.get('ttn'),
        "Exidv": data.get('pack')
    }})
    try:
        r2 = s.post(url, data=data, headers=headers, auth=auth)
    except:
        return (False, 'Нет соединения с системой')

    if r2.status_code ==201:
        print('Поставка %s Записана в БД'%(delivery))
        return (True, 'для поставки %s : '%delivery)
    else:
        print('Поставка НЕ %s Записана в БД'%(delivery))
        text = r2.text
        SO = soup(text)
        s = SO.find_all("message")[0].text
        return (False, s)

По сути мы просто делаем запрос POST и записываем туда данные в формате json

Заключение

Мы создали программу, интегрированную с SAP ERP, с простым сценарием работы,
Приезжает машина, мы вводим информацию по каждой паллете в интерфейсе, программа нам проверяем верно ли введены все данные, и предоставляет информацию по тому что должно быть и что уже введено. После ввода данных выдает отчет по проделанному и передает данные в SAP ERP. Так-же данный интерфейс адаптирован с мобильным интерфейсов ввода данных, что важно для складов с терминалами сбора данных (ТСД). После передачи данных в ERP систему так-же сохраняет все данные, на какой склад, какой идентификатор пришел, какой тип идентификатора, кто принимал, и прочее.

В итоге имеем программу обработки входящих и исходящих идентификаторов продукции в компании, при этом 90% всей работы проводится именно во внешней системе, интегрированной с основной системой.

В дальнейшем ее нужно доработать на ведение входящих партий, серийных номеров и прочего, и еще теснее с интегрировать с SAP, например создавать проводку поступления из данного интерфейса, ну это уже мысли на развитие :)

P.S. Я не расписывал код ABAP или python-django данного рабочего решения, не расписывал настройки Django или html теймплейтов, а сконцентрировался на интеграции с SAP ERP, что бы показать, что создание модуля для подключения к такой крупной системе как SAP достаточно не сложно и у меня ушло на такую систему, если включить еще изучение Django около 4х вечеров.

Спасибо всем за внимание, за конструктивную критику буду благодарен!

Автор: zambas

Источник

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


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