Android Client App for consuming SAP SOAP Web services using ksoap2 library

Recently SAP lauched their SAP Mobile Platform 3.0 (more about that here ) and I decide to write about how one can develop an Android Client App that can consume SAP SOAP Web Services with the ksoap2 library. I am maybe a bit late but it still deserves some attention.

Even though recently I had the chance to try out for myself the SAP Mobile Platform 2.2 and develop some Demo Apps testing both MBO and OData concepts I still personally feel that there are many people out there that are still developing mobile apps for SAP the old-fashion-way: ask for their SAP ABAP developers to prepare a couple of web services and develop mobile clients for these.

One point is that many companies still haven’t updated their Netweaver Gateways, so they can not use the OData goodies that come along with the newer versions. Another reason is that a change of mindset is needed and acceptance of the new web technologies and REST as an architecture.  And lest but not least is that there is still not a stable concept about the offline data management. MBOs were built with keeping that in mind, but SAP has decided not to support it anymore as it was a propriatery “protocol”. The Delta Query support that they mention sounds promising here but I have to try it out in order to judge it 🙂

I have one very old post about consuming SAP web services (or it should be consuming .NET web services that call RFC-enabled Function Modules from an SAP-Backend) where the landscape I had for development was SAP Backend, Visual Studio and SAP .NET connector library. Well, this time I had SAP Backend with enabled Webservices Runtime and as any normal developer would do: I made the use of the web services that you can build with SAP from any Remote-enabled Function module and develop clients that would consume this services. The best library out there that one can make use of at the moment is ksoap2 and since it is free you can use it and adapt it to your needs. I actually came to the idea of this blog post after reading many forums where people complained that they didn’t manage to get it work or they had to change the source and rebuild it after changing the names of the tags or something in the XML bulding logic.

How am I using the ksoap2 library to consume SAP Web services? Very elegant and smooth actually.

1. I developed an wrapper class for the SAPSerializationEnvelope that inherits from the SoapSerializationEnvelope where I just make it create XML-Requests in the SAP manner.

import java.io.IOException;
import org.ksoap2.SoapFault;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.xmlpull.v1.XmlSerializer;

public class SAPSerializationEnvelope extends SoapSerializationEnvelope {

public String namespace;
 public SAPSerializationEnvelope(int version, String namespace) {
 super(version);
 this.namespace= namespace;
 }

 @Override
 public void write(XmlSerializer writer) throws IOException {

writer.setPrefix("urn", namespace);
 writer.setPrefix("soapenv", env);
 writer.startTag(env, "Envelope");
 writer.startTag(env, "Header");
 writeHeader(writer);
 writer.endTag(env, "Header");
 writer.startTag(env, "Body");
 writeBody(writer);
 writer.endTag(env, "Body");
 writer.endTag(env, "Envelope");

 }

@Override
 public Object getResponse() throws SoapFault {
 return super.getResponse();
 }

}

2. Made a helper class for making calls.

import java.util.ArrayList;
import java.util.List;
import org.ksoap2.HeaderProperty;
import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

public class SAPServiceCaller {

String NAMESPACE_SOAP = "http://schemas.xmlsoap.org/soap/envelope/";
 static String NAMESPACE = "urn:sap-com:document:sap:soap:functions:mc-style";
 static String SOAP_ACTION = "";

 public static SoapObject webServiceCall(SoapObject request, String URL, boolean imp_types){
 SoapObject response = null;

 SAPSerializationEnvelope envelope = new SAPSerializationEnvelope(
 SoapEnvelope.VER11, NAMESPACE);
 envelope.implicitTypes = imp_types;
 envelope.dotNet = false;
 envelope.setAddAdornments(false);
 envelope.env = SoapSerializationEnvelope.ENV;

 envelope.setOutputSoapObject(request);

 HttpTransportSE androidHttpTransport = new HttpTransportSE(URL, 30000);
 try {
 List<HeaderProperty> headerList = new ArrayList<HeaderProperty>();
 headerList.add(new HeaderProperty("Authorization", "Basic "
 + org.kobjects.base64.Base64.encode("USER:PASS"
 .getBytes())));
 headerList.add(new HeaderProperty("Connection", "Keep-Alive"));
 androidHttpTransport.debug = true;
 androidHttpTransport.call(SOAP_ACTION, envelope, headerList);
 String answer = androidHttpTransport.responseDump;
 response = (SoapObject) envelope.bodyIn;
 }catch(Exception e){
 e.printStackTrace();
 }

 return response;
 }

}

3. And used it later in calls. A simple call is as follows.

SoapObject request = new SoapObject(
 "urn:sap-com:document:sap:soap:functions:mc-style",
 "ZbapiGetRoutes");
 request.addProperty("RoutesList", "");
SoapObject response;
response = SAPServiceCaller.webServiceCall(request, URL, false);
 SoapObject routesList = (SoapObject) response.getProperty("RoutesList");

This is a simple code that calls a method to return a list of Routes that one has to visit and read the meters in homes and upload them later to the backend.
With this simple “trick” I managed to build a Demo app for an SAP Backend with ISU Component that was displaying the Routes and devices from which the Meter data were read and uploaded to the backend for later billing purposes.

Hope it helps someone out there.

In the following posts I will try to keep up with the trends and write something about what I learned from the course that SAP was offered at openSAP  about Mobile development with the SAP Mobile Platform and maybe other ways how to achieve this as well.

Happy coding!

Something about Dynamic ABAP

It has been a while that I haven’t written anything new but it was due to making some ‘life-changing’ decisions recently and their impact on my ‘free-time’ activities.
First of all I decided to move to the lovely city of Dresden and start working in a company here, then I decided to get married and since my partner moved with me we had to find a new place to stay and equip it (it really takes some time to do that). OK, ok.. I admit I have been also searching for a wedding dress, invitation design, good wedding photographer and so it really takes some time.. 😉 I will now get to the point of the post in order not to annoy the male readers.
As the title says this time I will explain couple of concepts that are offered in ABAP that offer Real Time Type Services with a real-world problem.

Following is taken from help.sap.com:

The RTTS are implemented through a hierarchy of type classes that contain the methods for RTTC (Run Time Type Creation) and RTTI (Run Time Type Identification). With the help of these system classes it is possible to determine type information of existing instances and type names of the ABAP type system at runtime and to define new data types at runtime.
The properties of the types are represented by the attributes of type objects. For each type there is exactly one type object. The attributes of the type object contain information on the properties of the type. For each kind of type (elementary type, table, class, and so on), there is a type class with special attributes for special type properties. The class hierarchy of the type classes corresponds to the hierarchy of the type kinds in the ABAP type system.
In addition, type classes for complex types, references, classes, and interfaces have special methods for specifying references to partial types. With these methods, you can navigate to all partial types via a composite type.
Type objects can only be created through the methods of type classes. To get a reference to a type object of a type, you can use the static methods of the class CL_ABAP_TYPEDESCR or call methods of the special type classes.
Most important classes that every ABAPer has to know are:
CL_ABAP_TYPEDESCR
|
|–CL_ABAP_DATADESCR
|        |
|        |–CL_ABAP_ELEMDESCR
|        |–CL_ABAP_REFDESCR
|        |–CL_ABAP_COMPLEXDESCR
|          |
|          |–CL_ABAP_STRUCTDESCR
|          |–CL_ABAP_TABLEDESCR
|
|–CL_ABAP_OBJECTDESCR
|
|–CL_ABAP_CLASSDESCR
|–CL_ABAP_INTFDESCR

One can not understand the meaning of Dynamic programming without an example so let’s see one possible scenario.
Let’s suppose you need to upload some data in a table, but you have to make the code flexible and useful for uploading data in any table, not just a pre-defined one. Usually we create an internal table, use GUI_UPLOAD and we are done. But the bad thing about this approach is that sometimes we need to reuse the code for another table but before we have to change the type of the internal table, save, activate, test the code, transfer it to productive system and these all steps take a lot of time. When you don’t really have it, it is better to find some smart fast solution 🙂
So let’s explain the code step by step:

1. Creating the variables that we will need:


REPORT  zsax_upload_zmig_tables.

TYPE-POOLS: abap, sydes.

FIELD-SYMBOLS:  TYPE table.
FIELD-SYMBOLS: .
FIELD-SYMBOLS:  TYPE ANY.

DATA: lv_tab TYPE c VALUE cl_abap_char_utilities=&gt;horizontal_tab.

DATA: lt_ftab TYPE lvc_t_fcat,
      wa_ftab TYPE LINE OF lvc_t_fcat,
      lt_ftab1 TYPE lvc_t_fcat WITH HEADER LINE,
      vals TYPE string,
      word TYPE string.

DATA: it_file TYPE REF TO data,
      is_file TYPE REF TO data,
      dref    TYPE REF TO data.

DATA: serv_dir TYPE string VALUE '/usr/sap/SAX/SYS/global',
      filepath TYPE authb-filename,
      data_tab TYPE TABLE OF string,
      wa_line TYPE string,
      wa_tab TYPE string,
      itab TYPE TABLE OF string,
      comp TYPE string.

DATA: gv_table_name   TYPE string,
      gr_type_desc    TYPE REF TO cl_abap_typedescr,
      gr_struct_desc  TYPE REF TO cl_abap_structdescr,
      gr_table_desc   TYPE REF TO cl_abap_tabledescr,
      gv_t            TYPE c,
      gv_comp         TYPE i,
      gr_table_ref    TYPE REF TO data,
      gr_struc_ref   TYPE REF TO data,
      descr_ref TYPE REF TO cl_abap_typedescr.

DATA: gt_itab   TYPE TABLE OF string,
      gt_split  TYPE TABLE OF string,
      gv_str    TYPE string,
      gv_stro    TYPE string.

DATA: td       TYPE sydes_desc.

FIELD-SYMBOLS: <table>    TYPE ANY TABLE,
               <struct>   TYPE ANY,
               <comp>     TYPE ANY.

PARAMETERS: l_file TYPE string DEFAULT 'D:\Zentraltabelle.txt'.
PARAMETERS: table LIKE dd02l-tabname DEFAULT 'ZENTRAL'.

2. Upload data in a text table


CALL FUNCTION 'C13Z_UPLOAD'
 EXPORTING
   codepage                      = '1100'
   filename                      = l_file
  TABLES
    data_tab                      = data_tab
          .

IF sy-subrc  0.
write: 'Error!'.
ENDIF.

3. Call to dynamically create the table


CALL FUNCTION 'LVC_FIELDCATALOG_MERGE'
  EXPORTING
    i_structure_name = table
  CHANGING
    ct_fieldcat      = lt_ftab
  EXCEPTIONS
    OTHERS           = 1.

IF sy-subrc NE 0.
  WRITE: / 'Fehler bei Field catalog '.
  STOP.
ENDIF.

CALL METHOD cl_alv_table_create=&gt;create_dynamic_table
  EXPORTING
    it_fieldcatalog = lt_ftab
  IMPORTING
    ep_table        = it_file.

CREATE DATA dref TYPE (table).
ASSIGN dref-&gt;* TO .

ASSIGN it_file-&gt;* TO .

cl_abap_tabledescr=&gt;describe_by_name(
      EXPORTING p_name = table
      RECEIVING p_descr_ref = gr_type_desc
      EXCEPTIONS type_not_found = 4 ).

gr_struct_desc ?= gr_type_desc.
gr_table_desc = cl_abap_tabledescr=&gt;create( gr_struct_desc ).
CREATE DATA gr_table_ref TYPE HANDLE gr_table_desc.
CREATE DATA gr_struc_ref TYPE HANDLE gr_struct_desc.
ASSIGN gr_table_ref-&gt;* TO <table>.
ASSIGN gr_struc_ref-&gt;* TO .
DESCRIBE FIELD  TYPE gv_t COMPONENTS gv_comp.

3. Perform some parameter checks (if you happen to be dealing with Dates and crazy decimals)


LOOP AT data_tab INTO gv_str.
  CLEAR: gt_split.

  SPLIT gv_str AT lv_tab INTO TABLE gt_split.

  DO gv_comp TIMES.
    READ TABLE gt_split INTO gv_str INDEX sy-index.
    ASSIGN COMPONENT sy-index OF STRUCTURE  TO .

    DESCRIBE FIELD   INTO  td.
    descr_ref = cl_abap_typedescr=&gt;describe_by_data(  ).

    CASE descr_ref-&gt;type_kind.
        " DATUM TYPE
      WHEN 'D'.

        CALL FUNCTION 'CONVERSION_EXIT_DATEX_INPUT'
          EXPORTING
            input  = gv_str
          IMPORTING
            output = gv_stro.
         = gv_stro.

      WHEN 'P'.
        " DECIMAL TYPE
        CALL FUNCTION 'HRCM_STRING_TO_AMOUNT_CONVERT'
              EXPORTING
                string                    = gv_str
               decimal_separator         = ','
               thousands_separator       = '.'
*       WAERS                     = ' '
             IMPORTING
               betrg                     =
*     EXCEPTIONS
*       CONVERT_ERROR             = 1
*       OTHERS                    = 2
                      .

      WHEN 'F'.
        " DECIMAL TYPE
        CALL FUNCTION 'HRCM_STRING_TO_AMOUNT_CONVERT'
              EXPORTING
                string                    = gv_str
               decimal_separator         = ','
               thousands_separator       = '.'
*       WAERS                     = ' '
             IMPORTING
               betrg                     =
*     EXCEPTIONS
*       CONVERT_ERROR             = 1
*       OTHERS                    = 2
                      .

      WHEN 'T'.
        " TIME TYPE
        CALL FUNCTION 'CONVERSION_EXIT_TIMLO_INPUT'
          EXPORTING
            input  = gv_str
          IMPORTING
            output = gv_stro.
         = gv_stro.

      WHEN OTHERS.
        " CHAR, NUMC... TYPE
         = gv_str.
    ENDCASE.

    CLEAR gv_str.
  ENDDO.

  INSERT INTO (table) VALUES .
ENDLOOP.

4. Keep on ABAPing… 🙂
Next time I will write about dynamic creation of objects and dynamic calls of methods from objects.

Material master creation for all organizational levels with BAPI_MATERIAL_SAVEDATA

I have to admit that I am lazy when it comes to creating test data before user trainings. Too much clicking for nothing. I’d rather stay overnight and prepare LSMW tool, eCATT tool or write some ABAP code that would either generate data or simply upload some Excel data then click it myself. This is what I’d do even if I have to add manually 3 test cases. Another good thing is that in the process of creating the tool you learn a lot about the system.

This time the problem is different, there was a need to create material master data for all organizational levels… And there were lots of them! Imagine 9 company codes, each with 1-3 plants, each plant with 1-5,6,7-13, 14, 15 or more storage locations, multiply these and you’ll see why I say “lazy”… Then there was the WM part with the warehouses, storage types and storage bins and all the materials could be placed everywhere! And the PM team asked me to do this every other week so that they could test some different scenarios… So I ABAPed my way out of it 😉

The point is the following code is just to show you that sometimes ABAPing is better than all the other tools! And with BAPI_MATERIAL_SAVEDATA you can do it all at once and you have a better insight of what is happening than with LSMW (OK.. this is just my opinion).

So let’s see what this code is all about.

First check the FM through se37 -> BAPI_MATERIAL_SAVEDATA and see which tables/structures you need. If you don’t really understand which you should use consult Google or even better consult a senior and you will realize which of them you really need. HINT: Another starting point how you can discover which of the structures you need to use is to create manually one new material in the system and see which are obligatory fields while you create it and through the technical information find out the structure where they belong. So in an hour 0r two you will (like it or not) learn all the tables that make the material master data.

REPORT  ZMM_MMASTER_ALL.
DATA: HEADDATA LIKE  BAPIMATHEAD,
CLIENTDATA LIKE  BAPI_MARA,
CLIENTDATAX LIKE  BAPI_MARAX,
PLANTDATA LIKE  BAPI_MARC,
PLANTDATAX LIKE  BAPI_MARCX,
PLANNINGDATA LIKE  BAPI_MPGD,
PLANNINGDATAX LIKE  BAPI_MPGDX,
STORAGELOCATIONDATA LIKE  BAPI_MARD,
STORAGELOCATIONDATAX LIKE  BAPI_MARDX,
VALUATIONDATA LIKE  BAPI_MBEW,
VALUATIONDATAX LIKE  BAPI_MBEWX,
WAREHOUSENUMBERDATA LIKE  BAPI_MLGN,
WAREHOUSENUMBERDATAX LIKE  BAPI_MLGNX,
SALESDATA LIKE  BAPI_MVKE,
SALESDATAX LIKE  BAPI_MVKEX,
STORAGETYPEDATA LIKE  BAPI_MLGT,
STORAGETYPEDATAX LIKE  BAPI_MLGTX,
FLAG_ONLINE LIKE  BAPIE1GLOBAL_DATA-TESTRUN,
RETURN LIKE  BAPIRET2,
S_LOC TYPE T001L-LGORT,
ST_TYPE TYPE BAPI_MLGT-STGE_TYPE,
SLS_ORG TYPE BAPI_MVKE-SALES_ORG.
TABLES: BAPI_MAKT, BAPI_MLTX, BAPI_MLAN.
TABLES: T001L, T001W.
DATA: MATERIALDESCRIPTION TYPE TABLE OF BAPI_MAKT WITH HEADER LINE,
MATERIALLONGTEXT TYPE TABLE OF  BAPI_MLTX WITH HEADER LINE,
TAXCLASSIFICATIONS TYPE TABLE OF  BAPI_MLAN WITH HEADER LINE,
IT_T001L TYPE TABLE OF T001L WITH HEADER LINE,
IT_T001W TYPE TABLE OF T001W WITH HEADER LINE.

I am sure that by analyzing these lines of code you will learn many of the tables where the material master data is stored in SAP and the people there were nice enough to give them descriptive names in the BAPI so that you find your way out easily.

Next step internal table  that represents the migration structure and will be needed in the Excel file.

* --- INTERNAL TABLE
data:begin of itab occurs 0,
MATERIAL type BAPIMATHEAD-MATERIAL,
IND_SECTOR type c,
MATL_TYPE type bapimathead-MATL_TYPE,
OLD_MAT_NO type bapi_mara-old_mat_no,
EXTMATLGRP TYPE BAPI_MARA-EXTMATLGRP,
MATL_GROUP type bapi_mara-MATL_GROUP,
MATL_DESC TYPE BAPI_MAKT-MATL_DESC,
MATL_DESCMK TYPE BAPI_MAKT-MATL_DESC,
BASE_UOM TYPE BAPI_MARA-BASE_UOM,
TRANS_GRP TYPE BAPI_MARA-TRANS_GRP,
BATCH_MGMT TYPE BAPI_MARA-BATCH_MGMT,
LOADINGGRP TYPE BAPI_MARC-LOADINGGRP,
AVAILCHECK TYPE BAPI_MARC-AVAILCHECK,
SERNO_PROF TYPE BAPI_MARC-SERNO_PROF,
MRP_TYPE TYPE BAPI_MARC-MRP_TYPE,
LOTSIZEKEY TYPE BAPI_MARC-LOTSIZEKEY,
MRP_CTRLER TYPE BAPI_MARC-MRP_CTRLER,
SM_KEY TYPE BAPI_MARC-SM_KEY,
PLND_DELRY TYPE BAPI_MARC-PLND_DELRY,
ACCT_ASSGT TYPE BAPI_MVKE-ACCT_ASSGT,
ITEM_CAT TYPE BAPI_MVKE-ITEM_CAT,
PLACEMENT TYPE BAPI_MLGN-PLACEMENT,
WITHDRAWAL TYPE BAPI_MLGN-WITHDRAWAL,
*     STGE_TYPE TYPE BAPI_MLGT-STGE_TYPE,
STGE_BIN TYPE BAPI_MLGT-STGE_BIN,
PRICE_CTRL TYPE BAPI_MBEW-PRICE_CTRL,
VAL_CLASS TYPE BAPI_MBEW-VAL_CLASS,
STD_PRICE TYPE BAPI_MBEW-STD_PRICE,
end of itab,
IT_BAPI LIKE BAPI_MAKT OCCURS 0 WITH HEADER LINE.

This table should be prepared so that it suits your needs (or your client’s needs). And following is the code for uploading the file in the table.

AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_file.
PERFORM GET_FILE.
START-OF-SELECTION.
  PERFORM UPLOAD_FILE_ITAB.
PERFORM CALL_BAPI.
*&---------------------------------------------------------------------*
*& Form GET_FILE
*&---------------------------------------------------------------------*
FORM GET_FILE.
CALL FUNCTION 'F4_FILENAME'
EXPORTING
program_name  = syst-cprog
dynpro_number = syst-dynnr
IMPORTING
file_name     = p_file.
ENDFORM.   " GET_FILE
*&---------------------------------------------------------------------*
*& Form UPLOAD_FILE_ITAB
*&---------------------------------------------------------------------*
FORM UPLOAD_FILE_ITAB .
DATA:v_file TYPE string.
MOVE p_file TO v_file. CALL FUNCTION 'GUI_UPLOAD'
EXPORTING
filename = v_file
filetype = 'ASC'
has_field_separator = 'X'
TABLES
data_tab = itab .
ENDFORM. "UPLOAD_FILE_ITAB

OK let’s get to the most serious part of this migrating code. I hope you already analyzed the BAPI and realized that for every BAPI structure there is a BAPIX 🙂 These contain the same fields like the BAPI but they are all single chars and if they have the value ‘X’ it means update should be done with the value given in the BAPI structure, if it is space update shouldn’t be done. So let’s see the core of the code…

*&---------------------------------------------------------------------*
*& Form CALL_BAPI
*&---------------------------------------------------------------------*
form CALL_BAPI .
  LOOP AT ITAB.
*CODE FOR CREATION
    SELECT *
FROM T001W
INTO IT_T001W
WHERE BEDPL = 'X'.
APPEND IT_T001W.
ENDSELECT.
*LOOP PLANTS / SORgs(Plant(2)00) / WHouses (Plant(3)) / StorageType(SLoc(1)0SLoc(4)
LOOP AT IT_T001W.
CLEAR IT_T001L[].
SELECT *
FROM T001L
INTO IT_T001L
WHERE T001L~WERKS = IT_T001W-WERKS
AND T001L~XBLGO = 'X'.
APPEND IT_T001L.
ENDSELECT.
LOOP AT IT_T001L.
*LOOP SLocs / Storage type
        CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
EXPORTING
INPUT         = ITAB-MATERIAL
IMPORTING
OUTPUT        = HEADDATA-MATERIAL
.
       " HEADDATA-MATERIAL  = ITAB-MATERIAL. " matnr
HEADDATA-IND_SECTOR = ITAB-IND_SECTOR. " indsector
HEADDATA-MATL_TYPE = ITAB-MATL_TYPE. " mattype
HEADDATA-BASIC_VIEW = 'X'.
HEADDATA-SALES_VIEW  = 'X'.
HEADDATA-PURCHASE_VIEW  = 'X'.
HEADDATA-MRP_VIEW  = 'X'.
HEADDATA-STORAGE_VIEW  = 'X'.
HEADDATA-WAREHOUSE_VIEW  = 'X'.
HEADDATA-ACCOUNT_VIEW  = 'X'.
*CLIENT DATA + UPDATE FIELD
        CLIENTDATA-MATL_GROUP = ITAB-MATL_GROUP. "matgr
CLIENTDATA-BASE_UOM = ITAB-BASE_UOM. "unitmeasure
CLIENTDATA-OLD_MAT_NO = ITAB-OLD_MAT_NO.
CLIENTDATA-EXTMATLGRP = ITAB-EXTMATLGRP.
CLIENTDATA-TRANS_GRP = ITAB-TRANS_GRP.
CLIENTDATA-BATCH_MGMT = ITAB-BATCH_MGMT.
        CLIENTDATAX-MATL_GROUP = 'X'. "ITAB-MATL_GROUP'. "matgr
CLIENTDATAX-BASE_UOM = 'X'. " ITAB-BASE_UOM. "unitmeasure
CLIENTDATAX-OLD_MAT_NO = 'X'. "ITAB-OLD_MAT_NO.
CLIENTDATAX-EXTMATLGRP = 'X'. "ITAB-EXTMATLGRP.
CLIENTDATAX-TRANS_GRP = 'X'. "ITAB-TRANS_GRP.
CLIENTDATAX-BATCH_MGMT = 'X'. "ITAB-BATCH_MGMT.
        PLANTDATA-PLANT = IT_T001W-WERKS.
PLANTDATA-PUR_GROUP = '999'.
PLANTDATA-AVAILCHECK = ITAB-AVAILCHECK.
PLANTDATA-MRP_TYPE = ITAB-MRP_TYPE.
PLANTDATA-LOTSIZEKEY = ITAB-LOTSIZEKEY.
PLANTDATA-MRP_CTRLER = ITAB-MRP_CTRLER.
PLANTDATA-SM_KEY = ITAB-SM_KEY.
PLANTDATA-PLND_DELRY = ITAB-PLND_DELRY.
PLANTDATA-LOADINGGRP = ITAB-LOADINGGRP.
PLANTDATA-SERNO_PROF = ITAB-SERNO_PROF.
        PLANTDATAX-PLANT = IT_T001W-WERKS.
PLANTDATAX-PUR_GROUP = 'X'.
PLANTDATAX-AVAILCHECK = 'X'.
PLANTDATAX-MRP_TYPE = 'X'.
PLANTDATAX-LOTSIZEKEY = 'X'.
PLANTDATAX-MRP_CTRLER = 'X'.
PLANTDATAX-SM_KEY = 'X'.
PLANTDATAX-PLND_DELRY = 'X'.
PLANTDATAX-LOADINGGRP = 'X'.
PLANTDATA-SERNO_PROF = 'X'.
*
*PLANNINGDATA-PLANT = '1100'.
*PLANNINGDATA-PLNG_MATL = ''.
*PLANNINGDATA-PLNG_PLANT  = ''.
*
*PLANNINGDATAX-PLANT = 'X'.
*PLANNINGDATAX-PLNG_MATL = 'X'.
*PLANNINGDATAX-PLNG_PLANT  = 'X'.
        STORAGELOCATIONDATA-PLANT = IT_T001W-WERKS.
STORAGELOCATIONDATA-STGE_LOC = IT_T001L-LGORT.
STORAGELOCATIONDATA-STGE_BIN = ITAB-STGE_BIN.
        STORAGELOCATIONDATAX-PLANT = IT_T001W-WERKS.
STORAGELOCATIONDATAX-STGE_LOC = IT_T001L-LGORT.
STORAGELOCATIONDATAX-STGE_BIN = 'X'.
        VALUATIONDATA-VAL_AREA = IT_T001W-WERKS.
VALUATIONDATA-PRICE_CTRL = ITAB-PRICE_CTRL.
VALUATIONDATA-VAL_CLASS = ITAB-VAL_CLASS.
VALUATIONDATA-STD_PRICE = ITAB-STD_PRICE.
        VALUATIONDATAX-VAL_AREA = IT_T001W-WERKS.
VALUATIONDATAX-PRICE_CTRL = 'X'.
VALUATIONDATAX-VAL_CLASS = 'X'.
VALUATIONDATAX-STD_PRICE = 'X'.
*WAREHOUSENUMBERDATA
        WAREHOUSENUMBERDATA-WHSE_NO = IT_T001W-WERKS(3).
WAREHOUSENUMBERDATA-PLACEMENT = ITAB-PLACEMENT.
WAREHOUSENUMBERDATA-WITHDRAWAL = ITAB-WITHDRAWAL.
        WAREHOUSENUMBERDATAX-WHSE_NO = IT_T001W-WERKS(3).
WAREHOUSENUMBERDATAX-PLACEMENT = 'X'.
WAREHOUSENUMBERDATAX-WITHDRAWAL = 'X'.
*STORAGETYPEDATA
        CONCATENATE IT_T001L-LGORT(2) IT_T001L-LGORT+3(1) INTO ST_TYPE.
STORAGETYPEDATA-WHSE_NO = IT_T001W-WERKS(3).
STORAGETYPEDATA-STGE_TYPE = ST_TYPE.
STORAGETYPEDATA-STGE_BIN = ITAB-STGE_BIN.
        STORAGETYPEDATAX-WHSE_NO = IT_T001W-WERKS(3).
STORAGETYPEDATAX-STGE_TYPE = ST_TYPE.
STORAGETYPEDATAX-STGE_BIN = 'X'.
*SALESDATA
        CONCATENATE IT_T001W-WERKS(2) '00' INTO SLS_ORG.
SALESDATA-SALES_ORG = SLS_ORG.
SALESDATA-DISTR_CHAN = '99'.
SALESDATA-ITEM_CAT = ITAB-ITEM_CAT.
SALESDATA-ACCT_ASSGT = ITAB-ACCT_ASSGT.
        SALESDATAX-SALES_ORG = SLS_ORG.
SALESDATAX-DISTR_CHAN = '99'.
SALESDATAX-ITEM_CAT = 'X'.
SALESDATAX-ACCT_ASSGT = 'X'.
* MATERIAL DESCRIPTION - MK & EN
MATERIALDESCRIPTION-LANGU = '봋'.
MATERIALDESCRIPTION-LANGU_ISO  = 'MK'.
MATERIALDESCRIPTION-MATL_DESC = ITAB-MATL_DESCMK.
APPEND MATERIALDESCRIPTION.
        MATERIALDESCRIPTION-LANGU = 'E'.
MATERIALDESCRIPTION-LANGU_ISO  = 'EN'.
MATERIALDESCRIPTION-MATL_DESC = ITAB-MATL_DESC.
APPEND MATERIALDESCRIPTION.
*TAXCLASSIFICATIONS
*MK Macedonia MWST  Output Tax  1 Taxable
TAXCLASSIFICATIONS-DEPCOUNTRY = 'MK'.
TAXCLASSIFICATIONS-DEPCOUNTRY_ISO  ='MK'.
TAXCLASSIFICATIONS-TAX_TYPE_1  = 'MWST'.
TAXCLASSIFICATIONS-TAXCLASS_1  = '1'.
APPEND TAXCLASSIFICATIONS.
        CALL FUNCTION 'BAPI_MATERIAL_SAVEDATA'
EXPORTING
HEADDATA                   = HEADDATA
CLIENTDATA                 = CLIENTDATA
CLIENTDATAX                = CLIENTDATAX
PLANTDATA                  = PLANTDATA
PLANTDATAX                 = PLANTDATAX
*   FORECASTPARAMETERS         =
*   FORECASTPARAMETERSX        =
*    PLANNINGDATA               = PLANNINGDATA
*    PLANNINGDATAX              = PLANNINGDATAX
STORAGELOCATIONDATA        = STORAGELOCATIONDATA
STORAGELOCATIONDATAX       = STORAGELOCATIONDATAX
VALUATIONDATA              = VALUATIONDATA
VALUATIONDATAX             = VALUATIONDATAX
WAREHOUSENUMBERDATA        = WAREHOUSENUMBERDATA
WAREHOUSENUMBERDATAX       = WAREHOUSENUMBERDATAX
SALESDATA                  = SALESDATA
SALESDATAX                 = SALESDATAX
STORAGETYPEDATA            = STORAGETYPEDATA
STORAGETYPEDATAX           = STORAGETYPEDATAX
*   FLAG_ONLINE                = ' '
*   FLAG_CAD_CALL              = ' '
*   NO_DEQUEUE                 = ' '
*   NO_ROLLBACK_WORK           = ' '
IMPORTING
RETURN                     = RETURN
TABLES
MATERIALDESCRIPTION        = MATERIALDESCRIPTION
*   UNITSOFMEASURE             =
*   UNITSOFMEASUREX            =
*   INTERNATIONALARTNOS        =
*    MATERIALLONGTEXT           = MATERIALLONGTEXT
TAXCLASSIFICATIONS         = TAXCLASSIFICATIONS
*   RETURNMESSAGES             =
*   PRTDATA                    =
*   PRTDATAX                   =
*   EXTENSIONIN                =
*   EXTENSIONINX               =
.
        CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' .
write: 'PLANT: ',IT_T001W-WERKS, ' STORAGE LOCATION: ', IT_T001L-LGORT, ' ',  RETURN-type , ' MESSAGE:', RETURN-MESSAGE, '/'.
WAIT UP TO 2 SECONDS.
ENDLOOP.
ENDLOOP.
ENDLOOP. " itab
ENDFORM.                    "call_bapi

Run the code, go make some coffee and check the results after it is done 😉

ABAP, XML, XSLT and something about acquisition of web services in SAP

Long time no written I know… The last couple of months were a bit busy so I guess I owe my blog some more attention… Just noticed that there are maybe three draft posts waiting for approval:  one for the Digra conference that I visited in September (more on: DIGRA2011  and I was a speaker there about the games that I have made for motivating physical activity: Me,  but the folks were even cooler than I could imagine so they really deserve a post), one kind of a review of my new smartphone HTC Desire S (the device: HTC Desire S) and one about ABAP & JSON project that will be the only one that will see the the light of the day as soon as I finish it 😀

I promise this 2012 I will write more often and I guess someone will find what they need in the posts.

As a first 2012 post I  chose to write something about SAP and web service acquisition. This is a really simple example with simple code but it can be further extended and improved in different cases.

I would suppose that the reader is familiar with XML and XSLT (if not please check the following basic tutorials: W3Schools ) and has experience in any programming language and thus will easily “read” and understand the ABAP lines of code. Maybe he/she has also tried some web service acquisition in some other language but is an ABAP beginner and wants to get some job done really quickly.

So.. we have some web-service  from the National Bank of Republic of Macedoniacurrency download – for getting the currency exchange rates on a given date. This is one of the main things that is implemented in any project since any company needs the exchange rates in many of the processes. Since I am not here to write about business processes I will stick to the technical details.

So what one has to do first is to see what are the input parameters and what is the result from the web service. Let’s take the method GetExchangeRates as an example the input parameters are the start and end date for which we need the rates and the result is a string. When the answer is encapsulated in a string it requires some testing to get the full information about the response. I use soapUI to test the methods and analyze the answers and for this method I got something like:

<GetExchangeRateResult><![CDATA[<dsKurs>
  <xs:schema id="dsKurs" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="dsKurs" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
      <xs:complexType>
        <xs:choice minOccurs="0" maxOccurs="unbounded">
          <xs:element name="KursZbir">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="RBr" type="xs:double" minOccurs="0" />
                <xs:element name="Datum" type="xs:dateTime" minOccurs="0" />
                <xs:element name="Valuta" type="xs:double" minOccurs="0" />
                <xs:element name="Oznaka" type="xs:string" minOccurs="0" />
                <xs:element name="Drzava" type="xs:string" minOccurs="0" />
                <xs:element name="Nomin" type="xs:double" minOccurs="0" />
                <xs:element name="Sreden" type="xs:double" minOccurs="0" />
                <xs:element name="DrzavaAng" type="xs:string" minOccurs="0" />
                <xs:element name="NazivMak" type="xs:string" minOccurs="0" />
                <xs:element name="NazivAng" type="xs:string" minOccurs="0" />
                <xs:element name="Datum_f" type="xs:dateTime" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>978</Valuta>
    <Oznaka>EUR</Oznaka>
    <Drzava>ЕМУ</Drzava>
    <Nomin>1</Nomin>
    <Sreden>61.5044</Sreden>
    <DrzavaAng>EMU</DrzavaAng>
    <NazivMak>евро</NazivMak>
    <NazivAng>Euro</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>840</Valuta>
    <Oznaka>USD</Oznaka>
    <Drzava>С А Д</Drzava>
    <Nomin>1</Nomin>
    <Sreden>48.2918</Sreden>
    <DrzavaAng>USA</DrzavaAng>
    <NazivMak>САД долар</NazivMak>
    <NazivAng>US dollar</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>826</Valuta>
    <Oznaka>GBP</Oznaka>
    <Drzava>В.Британија</Drzava>
    <Nomin>1</Nomin>
    <Sreden>74.0526</Sreden>
    <DrzavaAng>U.K.</DrzavaAng>
    <NazivMak>британска фунта</NazivMak>
    <NazivAng>British pound</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>756</Valuta>
    <Oznaka>CHF</Oznaka>
    <Drzava>Швајцарија</Drzava>
    <Nomin>1</Nomin>
    <Sreden>50.7797</Sreden>
    <DrzavaAng>Switzerland</DrzavaAng>
    <NazivMak>швајцарски франк</NazivMak>
    <NazivAng>Swiss franc</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>752</Valuta>
    <Oznaka>SEK</Oznaka>
    <Drzava>Шведска</Drzava>
    <Nomin>1</Nomin>
    <Sreden>6.9485</Sreden>
    <DrzavaAng>Sweden</DrzavaAng>
    <NazivMak>шведска круна</NazivMak>
    <NazivAng>Swedish krona</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>578</Valuta>
    <Oznaka>NOK</Oznaka>
    <Drzava>Норвешка</Drzava>
    <Nomin>1</Nomin>
    <Sreden>8.0115</Sreden>
    <DrzavaAng>Norway</DrzavaAng>
    <NazivMak>норвешка круна</NazivMak>
    <NazivAng>Norwegian krone</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>392</Valuta>
    <Oznaka>JPY</Oznaka>
    <Drzava>Јапонија</Drzava>
    <Nomin>100</Nomin>
    <Sreden>62.8109</Sreden>
    <DrzavaAng>Japan</DrzavaAng>
    <NazivMak>јапонски јен</NazivMak>
    <NazivAng>Japanese yen</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>208</Valuta>
    <Oznaka>DKK</Oznaka>
    <Drzava>Данска</Drzava>
    <Nomin>1</Nomin>
    <Sreden>8.2705</Sreden>
    <DrzavaAng>Denmark</DrzavaAng>
    <NazivMak>данска круна</NazivMak>
    <NazivAng>Danish krone</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>124</Valuta>
    <Oznaka>CAD</Oznaka>
    <Drzava>Канада</Drzava>
    <Nomin>1</Nomin>
    <Sreden>47.5084</Sreden>
    <DrzavaAng>Canada</DrzavaAng>
    <NazivMak>канадски долар</NazivMak>
    <NazivAng>Canadian dollar</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>
  <KursZbir>
    <RBr>7</RBr>
    <Datum>2012-01-13T00:00:00+01:00</Datum>
    <Valuta>36</Valuta>
    <Oznaka>AUD</Oznaka>
    <Drzava>Австралија</Drzava>
    <Nomin>1</Nomin>
    <Sreden>49.9914</Sreden>
    <DrzavaAng>Australia</DrzavaAng>
    <NazivMak>австралиски долар</NazivMak>
    <NazivAng>Australian dollar</NazivAng>
    <Datum_f>2012-01-12T00:00:00+01:00</Datum_f>
  </KursZbir>

Aha there is the definition: KursZbir is the element containing all the data about the exchange rate of the currency according the Macedonian denar. And that is the content we need to put in an ABAP table and do what is necessary with it.

So before you think about which parsing technique should be used and all that I will tell you that the solution is just couple of clicks away. SAP is really sometimes nice to developers and sometimes it is better to search for a day or two first for some technique or a module instead of writing a bunch of code.

Next please go through the following tutorial about how to set up a proxy and test it on the following link. It contains detailed explanation with screens how to create a Proxy class, set up URL and create valid SOAP message and all with couple of clicks no line of code. 🙂 This is not the only one available on the internet you can find many more…

You see here that the method is really simple and the result is just square root of the value sent. The problem is when there is a more complex structure like the one we have to deal with now. Now we have to make a transformation. It is nothing too complicated just a way to tell one really great SAP function how to transform the “weird” XML data string into table data. It is done through the Object Navigator (SE80) . Open the menu: Workbench -> Edit Object. From the dialog select transformation and then create.

For this example the following XSL transformation was used: (i put it here so that you can easily copy and paste )

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sap="http://www.sap.com/sapxsl" version="1.0">
  <xsl:strip-space elements="*"/>
  <xsl:template match="/">
    <asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
      <asx:values>
        <ALL_KURS>
          <xsl:apply-templates select="//KursZbir"/>
        </ALL_KURS>
      </asx:values>
    </asx:abap>
  </xsl:template>
  <xsl:template match="KursZbir">
  <ALLKURS>
    <RBR>
      <xsl:value-of select="RBr"/>
    </RBR>
    <DATUM>
      <xsl:value-of select="Datum"/>
    </DATUM>
    <VALUTA>
      <xsl:value-of select="Valuta"/>
    </VALUTA>
    <OZNAKA>
      <xsl:value-of select="Oznaka"/>
    </OZNAKA>
    <DRZAVA>
      <xsl:value-of select="Drzava"/>
    </DRZAVA>
    <NOMIN>
      <xsl:value-of select="Nomin"/>
    </NOMIN>
    <SREDEN>
      <xsl:value-of select="Sreden"/>
    </SREDEN>
    <DRZAVAANG>
      <xsl:value-of select="DrzavaAng"/>
    </DRZAVAANG>
    <NAZIVMAK>
      <xsl:value-of select="NazivMak"/>
    </NAZIVMAK>
    <NAZIVANG>
      <xsl:value-of select="NazivAng"/>
    </NAZIVANG>
    <DATUM_F>
      <xsl:value-of select="Datum_f"/>
    </DATUM_F>
 </ALLKURS>
  </xsl:template>
</xsl:transform>

Actually writing the transformation is the hard part. The ABAP code will actually be short and simple.

Here it goes:

*&---------------------------------------------------------------------*
*& Report  Z_KURS_DOWNLOAD
*&---------------------------------------------------------------------*
 REPORT  z_kurs_download.
 DATA: proxy TYPE REF TO zkurstestco_kurs_soap .
 DATA : st_data TYPE start_date ,
        en_data TYPE end_date.
 data: cr_date type string.
 concatenate sy-datum+6(2) '.' sy-datum+4(2) '.' sy-datum(4) into cr_date.

 DATA: zinput TYPE zkurstestget_exchange_rate_so1,
       zoutput TYPE zkurstestget_exchange_rate_soa.
 DATA: result TYPE string.

 TYPES : BEGIN OF kurszbir,
       rbr TYPE string,
       datum TYPE string,
       valuta TYPE string,
       oznaka TYPE string,
       drzava TYPE string,
       nomin TYPE string,
       sreden TYPE string,
       drzavaang TYPE string,
       nazivmak TYPE string,
       nazivang TYPE string,
       datum_f TYPE string ,
 END OF kurszbir.

 DATA itab TYPE kurszbir OCCURS 0.
 DATA lt_kurs TYPE TABLE OF kurszbir.
 DATA wa_kurs TYPE TABLE OF kurszbir WITH HEADER LINE.

 TRY.
     CREATE OBJECT proxy
       EXPORTING
         logical_port_name = 'ZKURSTESTCO_KURS_SOAP_PORT'.
   CATCH cx_ai_system_fault .
 ENDTRY.

 * ADD SYSTEM DATE IN FORMAT DD.MM.YYYY
 zinput-start_date = cr_date.
 zinput-end_date = cr_date. "cr_date .

 TRY.
     CALL METHOD proxy->get_exchange_rate
       EXPORTING
         input  = zinput
       IMPORTING
         output = zoutput.
   CATCH cx_ai_system_fault .
   CATCH cx_ai_application_fault .
 ENDTRY.

 result = zoutput-GET_EXCHANGE_RATE_RESULT.
 REPLACE ALL OCCURRENCES OF '##' IN result WITH ' ' .

 TRY.
     CALL TRANSFORMATION Z_KURS_XSLT
             SOURCE XML result
             RESULT     all_kurs = lt_kurs.
   CATCH cx_ai_system_fault.
 ENDTRY.

 MOVE lt_kurs TO wa_kurs[]. 

 LOOP AT wa_kurs .
   write: / wa_kurs-datum, ' ', wa_kurs-valuta, ' ',
  wa_kurs-nomin, ' ', wa_kurs-sreden, ' ', wa_kurs-drzavaang, ' ',    wa_kurs-nazivmak, ' '.
 ENDLOOP.

The output of the program is the following:

After that you can either schedule a job or set a transaction and the client can perform it on their own.

I guess someone will find this example useful and the good thing is that it is actually possible to test it because banks are generally reliable for this kind of data 🙂

Next time maybe something more about more complex XML structures.

Multi-language messages in SAP

The title of this post says it all.  It is known that usually international companies can afford to implement SAP and that part of the correspondence that they will have will be in several languages. So here is a simple procedure that can be used when a message to customers from different countries should be sent.

Step 1 -> Open the transaction s010 for Standard text creation

Step 2 ->  In the field add descriptive

TEXT NAME – (in this case – Z_CUST_MSG)

TEXD ID – ST

LANGUAGE – any.

Step 3 ->  Write the content of the message in the text editor and save it.

Step 4 ->  Repeat Steps 1-3 for all the languages that you would like to use.

Step 4 -> Using the text via ABAP code.

* t_lines to hold the text
  data: t_lines type table of tline with header line.
* CUSTOMER COMMUNICATION LANGUAGE
  select spras
    into cust_lang
    from kna1
    where kunnr = p_kunnr.
  endselect.
* GET THE TEXT FOR THE BODY OF THE MESSAGE
  CALL FUNCTION 'READ_TEXT'
    EXPORTING
      CLIENT   = SY-MANDT
      ID       = 'ST'
      LANGUAGE = cust_lang
      NAME     = 'Z_CUST_MSG'
      OBJECT   = 'TEXT'
    TABLES
      LINES    = t_lines.
  IF SY-SUBRC <> 0.
* MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
*         WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
* IF THERE IS NO TRANSLATION OF THE LANGUAGE ASK USER
  ENDIF.
  DATA: counter type i.
 counter = 0.
 loop at t_lines.
 if counter = 0.
* in this case the first line of the text is used as a subject text for the message
      REPLACE ALL OCCURRENCES OF '/' IN t_lines WITH ''.
      modify t_lines.
      msg_sub = t_lines.
      counter = counter + 1.
    else.
      REPLACE ALL OCCURRENCES OF '/' IN t_lines WITH ''.
      modify t_lines.
      concatenate msg_body t_lines CL_ABAP_CHAR_UTILITIES=>NEWLINE into msg_body.
      counter = counter + 1.
    endif.
  endloop.

Outlook messages from SAP system

This is a sample code that can be used to create draft messages in your Outlook through SAP. Of course it is important to understand what it does so I tried to comment as much I can.

The VB code that can be used to create draft message is given here:

Sub create_draftmsg()
  Dim oApp As Outlook.Application
    Dim oEmail As Outlook.MailItem
    Set oApp = New Outlook.Application
    Set oEmail = oApp.CreateItem(olMailItem)
With oEmail
        .To = "to@mail.com"
        .CC = ""
        .Subject = "SUBJECT"
        .Body = "BODY TEXT"
        .Attachments.Add "C:\Cat.bmp", olByValue
        .Save
    End With
    Set oEmail = Nothing
End Sub

The ABAP code is given below.

INCLUDE OLE2INCL.
* they are all OLE2_OBJECTS so it is important to know the real classes
DATA: OUTLOOK TYPE OLE2_OBJECT,
MAILITEM TYPE OLE2_OBJECT,
ATTACHMENTS TYPE OLE2_OBJECT,
APPT TYPE OLE2_OBJECT,
DESTI TYPE OLE2_OBJECT,
BODYMAIL(600) TYPE C.
CREATE OBJECT OUTLOOK 'OUTLOOK.APPLICATION'.
* Get the outlook namespace
CALL METHOD OF OUTLOOK 'GetNamespace' = NAMESPACE
EXPORTING
#1 = 'MAPI'.
* Get outlook folders and create a reference to sent mail folder
* (myfolder)
* If this is not used than a draft message is not created when Outlook is not running
CALL METHOD OF NAMESPACE 'GetDefaultFolder' = MYFOLDER  EXPORTING     #1 = 16.    "olFolderDraftMail (value from a short debugging in VB)
CALL METHOD OF OUTLOOK 'CreateItem' = OUTMAIL EXPORTING #1 = 0. " 0 - MailItem
SET PROPERTY OF OUTMAIL 'SUBJECT' = 'SUBJECT'.
SET PROPERTY OF OUTMAIL 'BODY' = BODYMAIL.
CALL METHOD OF OUTMAIL 'RECIPIENTS' = DESTI.
CALL METHOD OF DESTI 'ADD' EXPORTING #1 = 'recipient@mail.com'.
CALL METHOD OF OUTMAIL 'ATTACHMENTS' = ATTS.
CALL METHOD OF ATTS 'ADD' EXPORTING #1 = 'filepath:\file.type'.
CALL METHOD OF OUTMAIL 'SAVE'.
FREE OBJECT DESTI.
FREE OBJECT ATTS.
FREE OBJECT OUTMAIL.
FREE OBJECT OUTLOOK.

Today’s wisdom: Use Macro recorder to discover the classes needed in order to use Microsoft goodies in ABAP! 🙂

SAP Technical sites/blogs I often consult…

The best technical  ones

http://www.saptechnical.com

http://abap4.tripod.com

http://abaplovers.blogspot.com

http://sap.ittoolbox.com

http://www.erpdb.info

http://www.freesaptutorial.com/

http://www.erpgenie.com

I’ll update this list every time I find a site/blog that will help me.