Добрый день, Хабр!

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

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



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

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



Поля:

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

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


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

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

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



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



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



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



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

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

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

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


Два раза щелкаем, что бы попасть в сам класс

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


Для примера покажу реализацию метода Create



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

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



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

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



Так выглядит запрос 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, которые в данный момент там созданы.



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х вечеров.

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

Комментарии (21)


  1. KonkovVladimir
    16.10.2018 17:52

    Прекрасно, прекрасно.

    А такого рода запросы

    SapOptions.objects.all()[0]

    сейчас модно писать так
    SapOptions.objects.first()


    1. zambas Автор
      16.10.2018 17:54

      Спасибо, учту, я далёк от уровня pro в Django и python )


      1. KonkovVladimir
        16.10.2018 18:00

        А чем вам не понравилось Odoo? Мне оно тоже не нравится, но хочется услышать мнение человека поработавшего с несколькими ERP.


        1. zambas Автор
          16.10.2018 22:38

          Я бы не назвал ODOO полноценной erp в России, в частности для производственного предприятия или крупной логистической системы ввиду ее ограничений в модуле бухгалтерии, но скажу, что в умелых руках она может стать очень не плохой системой в компании


        1. zambas Автор
          16.10.2018 22:46

          Ещё добавлю, что на моем опыте сама система не сильно определяет конечный успех. Успешную систему делают профессиональные внедрёнцы


  1. sukhe
    16.10.2018 20:54

    Это реальный сценарий? RFID-ы на каждой палетте? Отличные у вас поставщики. Мы не всех своих можем заставить хотя-бы штрих-коды клеить.

    Я только не понял, зачем вообще нужна эта система, если вся информация уже есть в ERP. Что мешает сделать ввод данных о приходе/уходе товара прямо там? Есть какие-то сложности?


    1. zambas Автор
      16.10.2018 22:44

      Самое очевидное это, когда в компании не одна Система, а целый набор,
      Так-же ее не нужно рассматривать как трекинг системой за егаис, это скорее аналог системы track and trace, единая система следящая за идентификаторами, ЕГАИС, Меркурий, лекарства, партии и проч. Я думаю скоро на все введут UiN номера.


      1. VGusev2007
        17.10.2018 11:17

        Я думаю скоро на все введут UiN номера.

        Что это такое?

        Подскажите, на каком SAP ERP всё проделали, какие EHP? Бегло потыкал в наш SAP, у нас нету: /IWFND/MAINT_SERVICE

        Спасибо за пост!


        1. zambas Автор
          17.10.2018 12:21

          Должен быть установлен компонент
          SAP_GWFND 752 0001 SAPK-75201INSAPGWFND SAP Gateway Foundation


          1. VGusev2007
            17.10.2018 14:02

            Благодарю! Про UiN, подскажите пож-та, о чём это?


            1. zambas Автор
              17.10.2018 14:06

              Uncial identification number
              Номер уникальный к каждой единицы продукции.
              На сколько известно моей фирме, эта тенденция идёт с важной продукции, лекарства, алкоголь, мясо. А скоро будет и молочка, одежда и пока хватит безумия государства


              1. VGusev2007
                17.10.2018 15:26

                Понял… Во истину безумие. В общем, чтобы отслеживать конкретную пачку макарон по этому делу…


                1. zambas Автор
                  17.10.2018 15:38

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


                  1. VGusev2007
                    17.10.2018 16:09

                    Слышали что ответили производители молока на Меркурий? Слышали как он там эпично провалился? Они, собственно, заявили, что смешивают молоко с разных ферм в один чан, и им отменили Меркурий.


                    1. zambas Автор
                      17.10.2018 16:29

                      Я вижу тенденцию. Проработают Меркурий так, что бы все нюансы учитывались, или заставят производителей не смешивать. Главное не то, что происходит сейчас, а куда все идет.


                      1. gennayo
                        17.10.2018 22:30

                        С Меркурием на самом деле всё намного хуже, чем вы представляете. процентов 50 предприятий гонит туда полное фуфло…


                      1. VGusev2007
                        18.10.2018 09:50

                        Увы, согласен. Больше всего расстраивает то, что за все эти (в общем то, бесполезные) новшества, платить будем мы.

                        Спасибо за ответы!


                        1. zambas Автор
                          18.10.2018 11:19

                          Вам спасибо)


    1. zambas Автор
      16.10.2018 22:48

      Про rfid, буквально не так давно только рабочая Система считывания множества rfid на паллете была успешно протестирована.


  1. saipr
    17.10.2018 11:49

    А вот интеграция с SAP российской криптографии, включая электронную подпись.


    1. zambas Автор
      17.10.2018 13:22

      Не понял вопроса, сделать интеграцию?*)
      Ну наверное по необзодимости я бы сделал, не думаю что там какой то космос, я вообще заметил, что любая задача решается, вопрос «овчинки» и трудолюбия.