Computerservice Wolfooo

 

SAP ITS Flow Logic Bestelleingang

o Schulung o Consulting o Programmierung o Links o Kontakt

Allgemeines

SAP bietet mit den Internet Transaction Server (ITS) die Möglichkeit, auf SAP R/3 Systeme über das World Wide Web zuzugreifen. Zur Realisierung stehen 3 Programmiermodelle zur Verfügung:

  • SAP GUI for HTML:
    Darstellung des SAP Frontends über einen Internetbrowser. Dazu ist eine Benutzerkennung im SAP System notwendig. Die komplexität von SAP wird nicht verborgen
  • Easy Web Transaction:
    Umsetzung von Dynpros in HTML Seiten durch den ITS. Ablauf- und Buissines Logik wird komplett in ABAB programmiert. Zur Benutzung ist keine SAP Benutzerkennung notwenig. Einfache Bedienung realisierbar. Hohe Last auf dem SAP System.
  • ITS Flow Logic:
    Ablauflogik wird auf dem ITS realisiert. Daten aus dem SAP System können per BAPI's und RFC's abgerufen und zurückgeschrieben werden.

Aufgabe

Firma A hat einen Produktkatalog im Internet, der mit PHP und MySQL realisiert ist. Dieser soll um die Möglichkeit der online Bestellung erweitert werden. Da Firma A SAP als ERP System einsetzt, und der Produktkatalog schon an SAP gekoppelt ist, sollen auch die Bestellungen ohne Medienbruch in SAP übernommen werden können. Dazu kann der SAP ITS verwendet werden.

Vorbereitung

  • Download und Installation des ITS:
    Öffentlich zugänglich ist die Seite: http://www.sapmarkets.com/its/, die hier angebotenen Downloads sind leider nicht besonders Aktuell. Wenn Sie zugriff auf sapservX haben, finden Sie ihn unter /general/its.
  • Installation des SAP@Web Studios:
    Für SAP R/3 < 4.6C kann der ITS nur über das SAP@Web Studio programmiert werden. Ab der 4.6C gibt es den Web Application Builder, der in die ABAP Workbench integriert ist. Das SAP@Web Studio findet sich unter der gleichen Adresse wie der ITS Server.

Realisierung

Übergabe der Daten aus dem Produktkatalog

Als Schnittstelle zwischen dem Produktkatalog und der ITS Flow Logic Anwendung dient ein HTML Formular, das per HTTP-POST an den ITS Server geschickt wird:

<form ACTION="http://its.domain.de/scripts/wgate/z_order_in/!?~language=de"
        METHOD="POST">
  <input type="hidden"
         name="ORDER_ITEMS_IN-REQ_QTY[1]"
         value="2000">
  <input type="hidden"
         name="ORDER_ITEMS_IN-MATERIAL[1]"
         value="539744">
  <input type="submit"
         name="bestellen"
         value="Bestellen">
</form>

Hier werden nur die, für die Bestellung notwendigen Daten wie Stückzahl (REQ_QTY) und Materialnummer (MATERIAL) übergeben. Dazu wird die Tabelle ORDER_ITEMS_IN verwendet. Jede Position muß mit einem Index versehen werden. Die Zählung beginnt bei 1.

Definition des Services im ITS

Als gegenseite für das Formular auf der Katalogseite wurde im ITS ein Service z_order_in definiert. Das Service File hat folgenden Inhalt:

~xGateway        sapxginet
~language  
~initialTemplate login
~theme           99

~xGateway muß auf sapxginet gesetzt werden, wenn man eine Flow Logic Anwendung realisieren möchte. Der Parameter ~language wird auf Leer gesetzt, damit dieser Parameter über die Aufruf URL übergeben werden kann, um dann über Sprachresourcen Dateien die Sprachunabhänigkeit zu gewährleisten. Als Startseite wird login aufgerufen, um den Kunden bevor er eine Bestellung tätigen kann zu authentifizieren. Diese Startseite liegt im Themenknoten 99.

Anmeldung am SAP R/3 als Internetbenutzer

Die Loginseite bietet dem Kunden seine Kundennummer und das dazugehörige Passwort einzugeben. Das Passwort des Kunden kann in der SAP R/3 Transaktion Internetbenutzer pflegen (SU05) gesetzt werden. Über dieses Formular muss wiederum die Bestellte Menge und Materialnummer in versteckten Feldern übergeben werden.

<html>
<head>
   <title>`#order_in_login`</title>
</head>
<body>
<form method=post action=`wgateURL()`> 
<h2>`#Eingabe`</h2>
<table>
  <tr>
    <td>`#Kundennummer`</td>
  <td><input type=text name="CUSTOMERNO" value="`CUSTOMERNO`"></td>
</tr>
<tr>
  <td>`#Passwort`</td>
  <td><input type=password name="PASSWORD"></td>
</tr>
<tr>
  <td>
  <input type="hidden" name="SALES_ORGANIZATION" value="1000">
  <input type="hidden" name="DISTRIBUTION_CHANNEL" value="10">
  <input type="hidden" name="DIVISION" value="10">
  `repeat with i from 1 to ORDER_ITEMS_IN-MATERIAL.dim`
  <input type="hidden" name="ORDER_ITEMS_IN-MATERIAL[`i`]" 
                       value="`ORDER_ITEMS_IN-MATERIAL[i]`">
  <input type="hidden" name="ORDER_ITEMS_IN-REQ_QTY[`i`]" 
                       value="`ORDER_ITEMS_IN-REQ_QTY[i]`">
  `end`
  <input type="submit" name="~event=login" value="`#login`">
  </td>
</tr> 
</table>
</form>
<h2>`#Ausgabe`</h2>
`RETURN-MESSAGE`<br>
</body>
</html>

Beim absenden dieses Formulars wird das Event login ausgelöst. Die Ablauflogik stellt die folgende Flow Logic Source dar:

<flow>
  <event name="login" next_state="checkCustomerNumber">
  </event>
  <state name="checkCustomerNumber">
    <module name="BAPI_CUSTOMER_CHECKEXISTENCE" stateful="0" type="RFC">
      <result next_state="checkPassword">
        <!--Kundennummer ist Richtig-->
        <expr>
          RETURN-TYPE=="" 
        </expr>
      </result>
      <persistent name="CUSTOMER_NUMBER_OUT"/>
      <persistent name="SALES_ORGANIZATION"/>
      <persistent name="DISTRIBUTION_CHANNEL"/>
      <persistent name="DIVISION"/>
      <persistent name="lang"/>
      <persistent name="javascript"/>
      <persistent name="session"/>
    </module>
  </state>
  <state name="checkPassword">
    <module name="BAPI_CUSTOMER_CHECKPASSWORD" stateful="0" type="RFC">
      <result next_template="simulate">
        <!--Passwort stimmt-->
        <expr>
          RETURN-TYPE=="" 
        </expr>
      </result>
    </module>
  </state>
</flow>

Zunächst wird der BAPI BAPI_CUSTOMER_CHECKEXISTENCE gerufen, um zu überprüfen, ob die eingegebene Kundennummer existiert. Falls dies der fall ist, wird das Passwort mit BAPI_CUSTOMER_CHECKPASSWORD geprüft und in diesem Fall auf das Template simulate verzweigt. In allen anderen Fällen wird die Fehlermeldung des Systems über RETURN-MESSAGE ausgegeben.

Kundenauftrag Simulieren

Wenn das Template simulate aufgerufen wird, soll der Kundenauftrag simuliert werden, und die Rückgabe von ORDER_ITEMS_OUT als Tabelle mit den Kundenspezifischen Preisen ausgegeben werden. Hierzu das HTML Template:

<html>
<head>
   <title>`#simulate_oder`</title>
</head>
<body>
RETURN-TYPE: `RETURN-TYPE`<br>
RETURN-MESSAGE: `RETURN-MESSAGE`<br>
RETURN-CODE: `RETURN-CODE`<br>
<table border="1">
<tr>
  <td>`#material`</td>
  <td>`#quantity`</td>
  <td>`#price`</td>
</tr>
`repeat with i from 1 to ORDER_ITEMS_OUT-MATERIAL.dim`
<tr>
  <td>`ORDER_ITEMS_OUT-MATERIAL[i]`</td>
  <td>`ORDER_ITEMS_OUT-REQ_QTY[i]`</td>
  <td>`ORDER_ITEMS_OUT-SUBTOTAL_2[i]/1000`</td>
</tr>  
`end`
</table>
<form action="`wgateURL()`" method="POST">
`repeat with i from 1 to ORDER_ITEMS_IN-MATERIAL.dim`
  <input type="hidden" name="ORDER_ITEMS_IN-MATERIAL[`i`]" 
                       value="`ORDER_ITEMS_IN-MATERIAL[i]`">
  <input type="hidden" name="ORDER_ITEMS_IN-REQ_QTY[`i`]" 
                       value="`ORDER_ITEMS_IN-REQ_QTY[i]`">
`end`
  <input type="submit"
		 name="~event=shipto"
		 value= "`#shipto`">
  <input type="submit"
		 name="~event=order"
		 value = "`#order`">
</form>
</body>
</html>

Damit der Kundenauftrag über BAPI_SALESORDER_SIMULATE simuliert werden kann, müssen viele zusätzliche Felder in der Struktur ORDER_HEADER_IN und den Tabellen ORDER_PARTNERS und ORDER_ITEMS_IN gefüllt werden. Einige Felder müssen mit dem aktuellen Datum gefüllt werden. Dazu wird der RFC Z_P6B_RFC_GET_DATETIME verwendet:

function z_p6b_rfc_get_datetime.                                        
*"----------------------------------------------------------------------
*"*"Lokale Schnittstelle:                                               
*"       IMPORTING                                                      
*"             VALUE(DAYSPAST) LIKE  MARA-MATNR DEFAULT 0               
*"             VALUE(TIMEPAST) LIKE  MARA-MATNR DEFAULT 0               
*"       EXPORTING                                                      
*"             VALUE(DATE) LIKE  SY-DATUM                               
*"             VALUE(TIME) LIKE  SY-UZEIT                               
*"----------------------------------------------------------------------
* Funktion: Übergibt an die rufende Anwendung das aktuelle Systemdatum  
*           und die aktuelle Systemzeit                                 
*           Zur Verwendung für Internet-Applikationen                   
*"----------------------------------------------------------------------
                                                                        
  date = sy-datum - dayspast.                                           
  time = sy-uzeit - timepast.                                           
                                                                        
endfunction.

Dies Füllen der Tabellen und Strukturen wird durch das HTMLBuissines Script init erreicht:

`
ORDER_HEADER_IN-DOC_TYPE = "ZTA";
ORDER_HEADER_IN-SALES_ORG = SALES_ORGANIZATION;
ORDER_HEADER_IN-DISTR_CHAN = DISTRIBUTION_CHANNEL;
ORDER_HEADER_IN-DIVISION = DIVISION;
ORDER_HEADER_IN-CD_VALUE1 = "0.00";
ORDER_HEADER_IN-CD_VALUE2 = "0.00";
ORDER_HEADER_IN-CD_VALUE3 = "0.00";
ORDER_HEADER_IN-CD_VALUE4 = "0.00";
ORDER_HEADER_IN-PRICE_DATE = DATE;
ORDER_HEADER_IN-QT_VALID_F = DATE; 
ORDER_HEADER_IN-QT_VALID_T = DATE;
ORDER_HEADER_IN-CT_VALID_F = DATE; 
ORDER_HEADER_IN-CT_VALID_T = DATE;
ORDER_PARTNERS-PARTN_ROLE[1] = "AG"; 
ORDER_PARTNERS-PARTN_NUMB[1] = CUSTOMER_NUMBER_OUT;
ORDER_PARTNERS-ITM_NUMBER[1] = "000000";
if (ORDER_PARTNERS-NAME[2] != "")
  ORDER_PARTNERS-PARTN_ROLE[2]="WE";
  ORDER_PARTNERS-PARTN_NUMB[2]=CUSTOMER_NUMBER_OUT;
  ORDER_PARTNERS-ITM_NUMBER[2]="000000";
end;
repeat with i from 1 to ORDER_ITEMS_IN-MATERIAL.dim
  ORDER_ITEMS_IN-COND_VALUE[i] = "0.00";
  ORDER_ITEMS_IN-COND_VAL1[i] = "0.00";
  ORDER_ITEMS_IN-CD_VALUE1[i] = "0.00";
  ORDER_ITEMS_IN-CD_VALUE2[i] = "0.00"; 
  ORDER_ITEMS_IN-CD_VALUE3[i] = "0.00";
  ORDER_ITEMS_IN-CD_VALUE4[i] = "0.00";
end;
`

Nach der erfolgreichen Simulation des Kundenauftrags soll der Kunde die möglichkeit haben, entweder direkt zu bestellen, oder eine abweichende Liefeanschrift anzugeben. Hier die gesamte Flow Source:

<flow>
  <state name="simulate">
    <module name="BAPI_SALESORDER_SIMULATE" stateful="0" type="RFC">
    </module>
  </state>
  <state name="init">
    <module name="init" stateful="0" type="SCRIPT">
      <default next_state="simulate">
      </default>
    </module>
  </state>
  <event name="ontouch" next_state="datum_holen">
  </event>
  <state name="datum_holen">
    <module name="Z_P6B_RFC_GET_DATETIME" stateful="0" type="RFC">
      <default next_state="init">
      </default>
    </module>
  </state>
  <event name="order" next_template="bestellen">
  </event>
  <event name="shipto" next_template="lieferanschrift">
  </event>
</flow>

Abweichende Lieferanschrift angeben

Über das folgende Formular kann eine Abweichende Lieferanschrift angeben werden. Auch hier muß wieder die Materialnummer und die Bestellmenge in versteckten Feldern übergeben werden.

<html>
<head>
   <title>`#shipto`</title>
</head>
<body>
<form action="`wgateURL()`" method="POST">
`repeat with i from 1 to ORDER_ITEMS_IN-MATERIAL.dim`
  <input type="hidden" name="ORDER_ITEMS_IN-MATERIAL[`i`]" 
                       value="`ORDER_ITEMS_IN-MATERIAL[i]`">
  <input type="hidden" name="ORDER_ITEMS_IN-REQ_QTY[`i`]" 
                       value="`ORDER_ITEMS_IN-REQ_QTY[i]`">
`end`
<table>
<tr>
  <td>Name1:</td>
  <td><input type=text
		name="ORDER_PARTNERS-NAME[2]"
		maxlength=35></td>
</tr>
<tr>
  <td>Name2:</td>
  <td><input type=text
		name="ORDER_PARTNERS-NAME_2[2]"
		maxlength=35></td>
</tr>
<tr>
  <td>Name3:</td>
  <td><input type=text
		name="ORDER_PARTNERS-NAME_3[2]"
		maxlength=35></td>
</tr>
<tr>
  <td>Name4:</td>
  <td><input type=text
		name="ORDER_PARTNERS-NAME_4[2]"
		maxlength=35></td>
</tr>
<tr>
  <td>Straße:</td>
  <td><input type=text
		name="ORDER_PARTNERS-STREET[2]"
		maxlength=35></td>
</tr>
<tr>
  <td>PLZ:</td>
  <td><input type=text
		name="ORDER_PARTNERS-POSTL_CODE[2]"
		maxlength=10></td>
</tr>
<tr>
  <td>Ort:</td>
  <td><input type=text
		name="ORDER_PARTNERS-CITY[2]"
		maxlength=35></td>
</tr>
<tr>
  <td>Land:</td>
  <td><input type=text
		name="ORDER_PARTNERS-COUNTRY[2]"
		value="DE"
		maxlength=3></td>
</tr>
</table>
  <input type="submit" name="~event=order" value="`#order`">
</form>
</body>
</html>

Nach dem Absenden des Formulars wird das Template bestellen aufgerufen:

<flow>
  <event name="order" next_template="bestellen">
    <!--*FlowDesigner bounds (-100, 8, 100, 10)*-->
  </event>
</flow>

Bestellung

Nach der erfolgreichen Bestellung werden noch einmal alle Bestelldaten und auch die Auftragsnummer ausgegeben.

<html>
<head>
   <title>`#order`</title>
</head>
<body>
<pre>
SOLD_TO_PARTY-NAME:       `SOLD_TO_PARTY-NAME`
SOLD_TO_PARTY-STREET:     `SOLD_TO_PARTY-STREET`
SOLD_TO_PARTY-POSTL_CODE: `SOLD_TO_PARTY-POSTL_CODE`
SOLD_TO_PARTY-CITY:       `SOLD_TO_PARTY-CITY`

SHIP_TO_PARTY-NAME:       `SHIP_TO_PARTY-NAME`
SHIP_TO_PARTY-STREET:     `SHIP_TO_PARTY-STREET`
SHIP_TO_PARTY-POSTL_CODE: `SHIP_TO_PARTY-POSTL_CODE`
SHIP_TO_PARTY-CITY:       `SHIP_TO_PARTY-CITY`

<table border="1">
<tr>
  <td>`#role`</td>
  <td>`#Kundennummer`</td>
  <td>`#item_numb`</td>
  <td>`#name`</td>
  <td>`#street`</td>
  <td>`#zip`</td>
  <td>`#city`</td>
</tr>
`repeat with i from 1 to ORDER_PARTNERS-NAME.dim`
<tr>
  <td>`ORDER_PARTNERS-PARTN_ROLE[i]`</td>
  <td>`ORDER_PARTNERS-PARTN_NUMB[i]`</td>
  <td>`ORDER_PARTNERS-ITM_NUMBER[i]`</td>
  <td>`ORDER_PARTNERS-NAME[i]`</td>
  <td>`ORDER_PARTNERS-STREET[i]`</td>
  <td>`ORDER_PARTNERS-POSTL_CODE[i]`</td>
  <td>`ORDER_PARTNERS-CITY[i]`</td>
</tr>  
`end`
</table>

RETURN-TYPE:              `RETURN-TYPE`
RETURN-MESSAGE:           `RETURN-MESSAGE`
RETURN-CODE:              `RETURN-CODE`
`#salesdocument`:         `SALESDOCUMENT`
</pre>
</body>
</html>

Zur Bestellung wird der BAPI_SALESORDER_CREATEFROMDATA aufgerufen. Auch dieser muß wiederum mit den Daten, die auch BAPI_SALESORDER_SIMULATE erhält versorgt werden. Im Sctip init wird dabei auch geprüft, ob eine abweichende Lieferanschrift angegeben wurde, die entsprechend Versorgt werden muß.

<flow>
  <state name="simulate">
    <module name="BAPI_SALESORDER_CREATEFROMDATA" stateful="0" type="RFC">
    </module>
  </state>
  <state name="init">
    <module name="init" stateful="0" type="SCRIPT">
      <default next_state="simulate">
      </default>
    </module>
  </state>
  <event name="ontouch" next_state="datum_holen">
  </event>
  <state name="datum_holen">
    <module name="Z_P6B_RFC_GET_DATETIME" stateful="0" type="RFC">
     <default next_state="init">
      </default>
    </module>
  </state>
</flow>

Leider hat die Funkion BAPI_SALESORDER_CREATEFROMDATA im Release 4.0B den Fehler, dass wenn eine abweichende Lieferanschrift angegeben wird, alle Daten bis auf die Postleitzahl richtig übernommen werden, aber bei der Postleitzah, die Postleitzahl des Auftraggebers übernommen wird. Die Lösung ist im OSS Hinweis Nr. 0441210 beschrieben.


© 2002 Computerservice Wolf - all rights reserved. webmaster@computerservice-wolf.com