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. |