Showing posts with label SAP. Show all posts
Showing posts with label SAP. Show all posts

Wednesday, 14 September 2011

ABAP: Check if a customer is blocked for sales support

This is relatively simple, but it's best to keep it here for future use.

FORM check_if_customer_is_blocked USING a_cust_id TYPE kunnr.
 DATA :
   is_blocked TYPE cassd_x.

 SELECT SINGLE cassd
   INTO is_blocked
   FROM kna1
   WHERE kunnr = a_cust_id.

 IF NOT is_blocked IS INITIAL.
   MESSAGE e888(sabapdocu) WITH 'Customer is blocked!'.
 ENDIF.

 SELECT SINGLE cassd
  INTO is_blocked
  FROM knvv
  WHERE kunnr = a_cust_id.

 IF NOT is_blocked IS INITIAL.
   MESSAGE e888(sabapdocu) WITH 'Customer is blocked!'.
 ENDIF.
ENDFORM.

Tuesday, 1 December 2009

How to set up the SAPRFC extension for PHP on CentOS 5

SAPRFC

SAPRFC is an extension module for PHP 4 and 5, that makes it possible to call ABAP function modules running on a SAP R/3 system from PHP scripts.

I always wanted to test how SAP's remote function calls work together with PHP and since I am already evaluating CakePHP as our next development platform, I decided that the occasion was right to give it a try.

Next thing I did was to get my hands on The SAP Developer's Guide to PHP book by Craig S. Cmehil, which is an $85 cost and 98 page long (indexes included) tutorial on how to set up and use SAPRFC for PHP. Unfortunately the second chapter that discusses setting up your development system focuses mainly on Windows, so this post will contain the steps I took to set up SAPRFC for PHP on my x86_64 CentOS 5.4 server.

Package Requirements and Downloads

To get the latest PHP packages for Enterprise Linux I have used Remi's repository.

# wget http://download.fedora.redhat.com/pub/epel/5/x86_64/epel-release-5-3.noarch.rpm
# wget http://rpms.famillecollet.com/enterprise/remi-release-5.rpm
# rpm -Uvh remi-release-5*.rpm epel-release-5*.rpm

Remi's packages depend on the EPEL repository, so I am posting the installation procedure for EPEL as well. (if you haven't installed it yet, now will be a good time to do so.)

In addition to any other PHP packages that your application requires, in order for the SAPRFC to compile correctly, you will also require the php-devel package.

Next thing is the SAPRFC package itself. The method to install it will be to build the saprfc extension as a dynamic module without rebuilding PHP. (Remi has already done that for us.) The package itself can be downloaded from here.

Continue by installing SAP's Non Unicode RFC SDK version 6.20 or 6.40. This must be downloaded directly from the SAP Service Support Portal. You will need a customer SAP support ID

Be advised however, that SAP has implemented some form of a DNS transparent cluster for their WEB service, so each time you log in there, you end up accessing a server with a different DNS name (something like https://websmp104.sap-ag.de/support). That means that your browser will not be able to store your credentials because every time you attempt to connect to https://service.sap.com/support, the DNS name changes so it pops up a dialog asking for login data again and again... Perhaps this is SAP's way of implementing the ultimate security system but, as far as I can say it is very annoying.

Anyway, once you are there select "Download" from the top menu. Next click "Search on all categories" from the left menu and enter RFC SDK on the search box that appears. You will be given the chance to select SAP RFC SDK 6.40 from the results page. Be careful not to choose the UNICODE version. Select Linux on x86_64 64bit from the list of architectures and you will end up with an SAR file in your download basket. Now you can download it

There is one more problem though. The file you download is of type SAR. meaning SAP Archive. In order to unpack it you will need SAPCAR, SAP's unpacking program. You download this the same way you downloaded RFCSDK -- just type SAPCAR on the search box. Only thing is that the Linux x86_64 version does not run on CentOS. You will need to download a Windows version, unpack the archive on a Windows machine and then upload it again on you Linux system. At least that is what I had to do. (From what I was able to understand SAP's SAPCAR for Linux is compiled to run under SuSE, so if you have satch a machine, you can try unpacking the archive over there...)

Installation

So now let's assume that you have placed SAP's RFC SDK under /opt/SAP/rfcsdk and SAPRFC extention module for PHP under /usr/src/saprfc-1.4.1/. Type the following commands on your shell prompt or add them at the end of your startup file. (I put them in /etc/profile.)

# SAP RFC SDK
export SAPRFC_DIR=/opt/SAP/rfcsdk/
export PATH=$SAPRFC_DIR/bin:$PATH

If necessary, log out and back in again. Now move to the SAPRFC extension for PHP directory and issue the phpize command. This will create the configure script that needs to be run next. After configure completes, run make and make install (as root) to finish installation. When everything finishes the file saprfc.so will be placed in your /usr/lib64/php/modules folder. Open you php.ini file located in /etc and add a line line

extension=saprfc.so

in the Dynamic Extensions section, save it, restart http server and you are ready to go.

[root@barbara ~]# service httpd restart
Stopping httpd:                                            [  OK  ]
Starting httpd:                                            [  OK  ]

Verification and testing

The very first check will be as simple as looking at the saprfc section of your phpinfo() page. You should be seeing something like :

The next thing will be to write an actual program that does something more practical like connecting to an SAP R/3 system and fetching back some useful data. Since this already a rather lengthy post, I will prepare and provide a test program some time later.

One last comment: It took me a while to figure that out. All examples that come along with the SAPRFC module for PHP, as well as the examples on the "SAP Developer's Guide to PHP" book, use <? instead of <?php to introduce php code. This is going to give you a lot of trouble when attempting to use these files with php 5.3.1 so before trying anything else, go to the files in the installation directory -- especially saprfc.php that you will always include -- and perform the necessary changes.

Thursday, 26 November 2009

SAP: All the little text tables

Even after 5 years of tampering with ABAP, I still have problems getting used to the way SAP name their production tables. All those four letter German abbreviated names are explanatory enough to scare away any newcomer. Even the English descriptive texts that SAP provides along with each object are so Spartan, that I very seldom can make any good use out of them.

Luckily enough, I have just the right person available who seems to know all the four -- or five -- letter permutations that yield SAP table and field identifiers. (Thanks again Marilena), so today I am going to steal a few of her knowledge and give us code fragments that show how to get descriptive texts for various material and production related key's like material names, groups, blocking statuses, MRP controllers, etc.

We will start with the names of storage locations. The field and the data type for those are named lgode. The table that stores related info is t001l.

   DATA :
     storage_location TYPE lgort_d, 
     storage_location_name TYPE lgobe.

    " storage_location = '...'.
    SELECT SINGLE lgobe
      INTO storage_location_name
      FROM t001l
      WHERE lgort = storage_location.

The next one is easy. Even I know by heart how to get the material description given the material number, but I will put it down anyway. The thing to mention however, is that most SAP text tables are language dependent, so to get the text for the key, you also need to specify the language

    DATA :
      material TYPE matnr,
      material_descr TYPE maktx.

    " material = '...'.
    SELECT SINGLE maktx
      INTO material_descr
      FROM makt
      WHERE matnr = material
        AND spras = sy-langu.

Material types are stored in table t134t which is also language dependent.

    DATA :
      material_type TYPE mtart,
      material_type_descr TYPE mtbez.

    " material_type = '...'.
    SELECT SINGLE mtbez
      INTO material_type_descr
      FROM t134t
      WHERE mtart = material_type
        AND spras = sy-langu.

Our next table is t141t, that stores the various texts for the material blocking statuses.

    DATA :
      mat_blocking_status TYPE mstae,
      mat_blocking_status_descr TYPE mstb.

 " Material blocking status descriptions
    SELECT SINGLE mtstb
      INTO mat_blocking_status_descr
      FROM t141t
      WHERE mmsta = mat_blocking_status
        AND spras = sy-langu.

We wil now move to the inspiringly named t023t table, that provides access to the short and long names of material groups.

    DATA :
      material_group TYPE matkl,
      mat_group_short_descr type wgbez,
      mat_group_long_descr TYPE wgbez60.
    
    " material_group = "...".
    SELECT SINGLE wgbez wgbez60
      INTO (mat_group_short_descr, mat_group_long_descr)
      FROM t023t
      WHERE matkl = material_group
        AND spras = sy-langu.

Product hierarchy descriptions are found in in t179t.

    DATA :
      mat_hierarchy TYPE prodh_d,
      mat_hierarchy_descr TYPE vtext.

    " mat_hierarchy = '...'.
    SELECT SINGLE vtext
      INTO mat_hierarchy_descr
      FROM t179t
      WHERE prodh = mat_hierarchy
        AND spras = sy-langu.

Table t438t stores MRP type descriptions.

    DATA :
      mrp_type TYPE dismm,
      mrp_type_descr TYPE disbez.

    SELECT SINGLE dibez
      INTO mrp_type_descr
      FROM t438t
      WHERE dismm = mrp_type
        AND spras = sy-langu.

Last table for today's post will be t024d. this one contains MRP Controller descriptions and is not language but plant organized.

    DATA :
      plant TYPE werks_d,
      mrp_controller type idspo,
      mrp_controller_descr TYPE disnam.

    SELECT SINGLE dsnam
      FROM t024d
      INTO mrp_controller_descr
      WHERE dispo = mrp_controller
        AND werks = plant.

... and that is enough for one day.

Friday, 11 January 2008

ABAP Get the value of a date type characteristic

When an object is classified with characteristics of type date then, using BAPI BAPI_OBJCL_GETCLASSES these characteristics are returned as numeric. That means that entries for date characteristics are found in the valuesnum array which --as you remeber from a couple of postings back -- is defined to be a standard table of bapi1003_alloc_values_num.

When I first saw this I thought that the actual date value could be acquired by assigning the value_from part of the returned bapi1003_alloc_values_num structure to a date variable, but it was not at all like this. The actual value is stored like an integer representing the ABAP internal date fromat which is YYYYMMDD. So an integer value like 20080112 represents January 12th 2008.

In ABAP code this translates as follows

  DATA :
    temp_int TYPE i,
    temp_date_str(8) TYPE c, 
    my_date TYPE d.

  FIELD-SYMBOLS :
     TYPE bapi1003_alloc_values_num.

      READ TABLE valuesnum
        WITH KEY charact = 'MY_DATE_CHARACT'
        ASSIGNING .

      IF sy-subrc = 0.
        temp_int = -value_from.
        temp_date_str = temp_int.
        my_date = temp_date_str.
      ENDIF.

Thursday, 10 January 2008

ABAP Obtaining characteristics of a document from the classification system

In addition to a previous post regarding getting classification information for objects of type Material or Batch and thanks to additional info and feedback that I received from our MM specialist Ms Marilena Svolaki, I will be able to provide an example of how to do the same thing with documents.

Master information about documents is found in the DRAW table. (This is short for German Dokumentinformationssatz). The key fields for each document are displayed in the following image :

Creating the object key for BAPI_OBJCL_GETCLASSES is performed by concatenating the document type (field DRAW-DOKAR), the document number (field DRAW-DOKNR), the document version (field DRAW-DOKVR) and the document part (field DRAW-DOKTL). In code this can be expressed as follows

 DATA
    wa_draw TYPE draw, 
    cur_bapi_objectkey LIKE inob-objek,

    class_list TYPE STANDARD TABLE OF bapi1003_alloc_list,
    valueschar TYPE STANDARD TABLE OF bapi1003_alloc_values_char,
    valuescurr TYPE STANDARD TABLE OF bapi1003_alloc_values_curr,
    valuesnum  TYPE STANDARD TABLE OF bapi1003_alloc_values_num,
    return     TYPE STANDARD TABLE OF bapiret2.

*   Make wa_draw somehow correspond to a valid document entry ....     
    CONCATENATE wa_draw-dokar
                wa_draw-doknr
                wa_draw-dokvr
                wa_draw-doktl
                INTO cur_bapi_objectkey.

*   Call BAPI to get the list of characteristics
    CALL FUNCTION 'BAPI_OBJCL_GETCLASSES'
    EXPORTING
      objectkey_imp         = cur_bapi_objectkey
      objecttable_imp       = 'DRAW'
      classtype_imp         = '017'
      read_valuations       = 'X'
      keydate               = sy-datum
*     language              = 'E'
    TABLES
      alloclist             = class_list
      allocvalueschar       = valueschar
      allocvaluescurr       = valuescurr
      allocvaluesnum        = valuesnum
      return                = return.

    READ TABLE return INDEX 1 ASSIGNING <ret>.

*   Check that object allocations exist
    IF  sy-subrc = 0 AND
        <ret>-id = 'CL' AND
        <ret>-number = 741.

*      Read characteristic values
       ...
    ENDIF

Tuesday, 6 November 2007

BAPI / RFC with Delphi 2006 / 2007

Six months ago I wrote an article regarding how to install and use SAP's Active X components using Delphi 6. Here is the sequel regarding Delphi 2006 and 2007.

The basic idea is still the same you only need to install SAP Logon Control version 1.1, SAP BABPI Control Version 1.2 and finally import the SAP Remote Function Call Controll (Version 5.0) type library.

To complete the task in Delphi 2006, use the following procedure:

  1. Start Delphi 2006 and create a new package using the menu File → New → Package for Delphi Win 32.
  2. Save the package and the project using a name like SAPControls
  3. From the Delphi main menu select Component → Import Component.
  4. Locate the SAP Logon Control as show in the following image and click next
  5. In the following Screen just type SAP at the pallete page name ... and press next
  6. On the next screen select Add unit to Package radio item. ... and click Finish
  7. Repeat the previous steps and install the SAP BAPI Control and the Remote function type library. Unlike Delphi 6 you should get no compiler error messages.
  8. When you are finished, right click on the SAPControls package on the project manager window and select Install from the pop up menu.
  9. That does it. Create a new project and use the components of the new SAP pallete Category as you please.

Don't worry if you misplaced your components in an other category. You can drug them into any category you wish afterwards.

Wednesday, 17 October 2007

ABAP: How to tell if user input is a number

ABAP casts between types by just sequentially copying (as many as the size of the target type) bytes from one memory area to an other and then checks if the new contents of the target memory area can be perceived as the appropriate type. As a general rule strings and character objects are always copied left while numbers get copied right justified)

This little code fragment will get a number out of a string and convert it to practically any numeric format.

        DATA :
           temp_str(10) type c value '1234,34'.
           my_number TYPE f.

*       If the value of temp_str is comming from user input of from any other
*       user or ABAP dictionary type then it is also wise to execute something like
        CONDENSE temp_str.

*       You may want to execute this if you --like me -- your 
*       decimal symbol is a comma
        REPLACE ',' WITH '.' INTO temp_str.

        CATCH SYSTEM-EXCEPTIONS conversion_errors = 4.
          my_number = temp_str.
        ENDCATCH.
        IF sy-subrc <> 0.
          MESSAGE e888(sabapdocu) WITH temp_str ' is not a number'.
        ENDIF.

Friday, 12 October 2007

ABAP Obtaining an object's characteristics from the classification system

The easiest way to get a list of all the characteristics of an object is to use the BAPI_OBJCL_GETCLASSES BAPI. The function requires that you supply an object key, the database table where the object is stored, the class type of the obect to look for -- e.g. '001' for material or '023' for batch -- and finally whether or not to return the entire list of characteristics.

The return value consists of a table containing all the classes the object is currently assigned and if requested three additional tables with values of the object characteristics, one for string valued, one for numeric and one for currency.

Information about objects, db tables and corresponding class types is stored in the inob table. So one way of getting all this would be to write code like :

  DATA : 
    wa_inob TYPE inob, 
    my_object LIKE inob-objek.

  CLEAR wa_inob.
  CONCATENATE my_matnr my_batch INTO my_object.
  SELECT SINGLE * 
    FROM inob 
    INTO wa_inob
    WHERE objek = my_object.

So calling the BAPI would be as straight forward as ....

  DATA :
    class_list TYPE STANDARD TABLE of bapi1003_alloc_list.
    valueschar TYPE STANDARD TABLE OF bapi1003_alloc_values_char, 
    valuescurr TYPE STANDARD TABLE OF bapi1003_alloc_values_curr, 
    valuesnum  TYPE STANDARD TABLE OF bapi1003_alloc_values_num,
    return     TYPE STANDARD TABLE OF bapiret2.

  CALL FUNCTION 'BAPI_OBJCL_GETCLASSES'
    EXPORTING
      objectkey_imp         = wa_inob-objek
      objecttable_imp       = wa_inob-obtab
      classtype_imp         = wa_inob-klart
      read_valuations       = 'X'
      keydate               = sy-datum
*     language              = 'E'
    TABLES
      alloclist             = class_list
      allocvalueschar       = valueschar
      allocvaluescurr       = valuescurr
      allocvaluesnum        = valuesnum
      return                = return.

Determining if an object has indeed been assigned classification information is performed by checking the return table value. Correct classification means that the return table contains one line with field "TYPE" set to 'S', "ID" set to 'CL' and NUMBER set to 741. (When no allocations are found number is 740). So before trying to read any characteristic values is safer to test like this.

     FIELD-SYMBOLS
        <ret> TYPE bapiret2.

     READ TABLE return INDEX 1 ASSIGNING <ret>.

*   Check that object allocations exist
    IF  sy-subrc = 0 AND
        <ret>-id = 'CL' AND
        <ret>-number = 741.

      ....

Now getting a specific value from let's say the string valued characteristics can be accomplished by code similar to following, which gets the value of a characteristic named QUALITY:

  FIELD-SYMBOLS :
    <fs> TYPE bapi1003_alloc_values_char.

  READ TABLE valueschar WITH KEY charact = 'QUALITY' ASSIGNING <fs>.

  if <fs> IS ASSIGNED.
*   The language independent value of the characteristic is 
    my_quality_id = <fs>-value_neutral.
*   The language dependent i.e. the one that will appear in the
*   result table of CL30N is 
    my_quality_descr = <fs>-value_char.
  ENDIF.

NOTE: The following approach is generic and is supposed to work fine on all systems. In our system however (Release 4.6C) the inob table does not have an index on inob-objek, so the SELECT SINGLE statement is executed by performing sequential reads. One way to overcome this would be to manually create the index. The other, as far as I am concerned, is to know beforehand the type of objects and classes that your program deals with, and therefore hard code the database table to 'MARA' or MCH1' and the class type to '001' and '023' for materials and batches respectively.

Tuesday, 21 August 2007

ABAP: How to get the Screen Table row that the cursor is in

Getting the row that the user cursor is in accomplished by using the GET CURSOR LINE statement. As always, we need to declare a global field named something like cur_tbl_line to hold the current table line. Then during PAI processing we need to issue statements like the following : (tc_mydata is the name of the screen table control and XXX is the screen number)

MODULE user_command_XXX INPUT.

* set up the current line variable
  GET CURSOR LINE cur_tbl_line.
  cur_tbl_line = tc_mydata-top_line + cur_tbl_line - 1.

* do the classic stuff
  ok_code = ok_code_XXX.
  CLEAR ok_code_XXX.

  ...
ENDMODULE.

Using this approach, you can get the selected line of the tbl_mydata table by writing code like this

FORM sync_mydata_with_selection
     CHANGING
        f_none_selected TYPE c.

  FIELD-SYMBOLS
    <fs> LIKE LINE OF tbl_mydata.

  CLEAR f_none_selected.

* give precedence to the table control selection
  READ TABLE tbl_mydata
  WITH KEY sel = 'X'
  ASSIGNING  <fs>.

  IF sy-subrc <> 0.
*   No selection was made so get the current row using the
*   cursor
    IF cur_tbl_line > 0.
      READ TABLE tbl_packages INDEX cur_tbl_line
        ASSIGNING .
    ELSE.
      f_none_selected = 'X'.     
    ENDIF.
  ENDIF.
ENDFORM.

After this is executed, the header line of tbl_mydata contains the selected row and if your user has not selected anything then f_none_selected will have a value of 'X'.

Friday, 3 August 2007

ABAP: Gettting the selected column

Every TABLEVIEW control defined in an ABAP program contains a cols field that contains information about selected columns. Whether or not column selection is allowed is set by the tableview object's properties in the screen painter. (see also the relevant image for the posting How to setup a Screen containing a Table a few days back.

One common use for column selection is sorting, so an example sorting function that sorts a global table named tbl_my_data based on the selected column might look like this :

FORM sort_table USING  sort_mode LIKE sy-ucomm.
  FIELD-SYMBOLS
     TYPE cxtab_column.

* find out if a column is selected.
  READ TABLE tc_mytab-cols
    WITH KEY selected = 'X'
    ASSIGNING <selected_col>.

  IF sy-subrc <> 0.
    MESSAGE s888(sabapdocu) WITH 'No column is selected'.
    EXIT.
  ENDIF.

* sort according to the selected column
  CASE sort_mode.
    WHEN 'SORT_ASC'.
      CASE <selected_col>-index.
        WHEN 1.
          SORT tbl_mydata ASCENDING BY field1.
        WHEN 2.
          SORT tbl_mydata ASCENDING BY field2.
        WHEN OTHERS.
          MESSAGE s888(sabapdocu) WITH 'Not yet implemented'.
      ENDCASE.
    WHEN 'SORT_DSC'.
      CASE <selected_col>-index.
        WHEN 1.
          SORT tbl_mydata DESCENDING BY field1.
        WHEN 2.
          SORT tbl_mydata DESCENDING BY field2.
        WHEN OTHERS.
          MESSAGE s888(sabapdocu) WITH 'Not yet implemented'.
      ENDCASE.
  ENDCASE.
ENDFORM.                    " sort_table

Tuesday, 31 July 2007

ABAP: Responding to changes on screen fields

The general case for checking user input during PAI can be summarized as follows :

PROCESS AFTER INPUT.

  FIELD fieldA MODULE check_fieldA ON REQUEST.
  ...
  FIELD fieldB MODULE check_fieldB ON REQUEST.

Any messages of type e or w executed during execution of the corresponding modules will cause SAPGui to stop processing, return focus to the appropriate field and execute the module again until no messages are thrown.

If you wish to perform the same check on both fields then the fields can be chained.

PROCESS AFTER INPUT.
  CHAIN.
    FIELD :
            fieldA,
            fieldB

    MODULE check_fieldA_and_B ON CHAIN-REQUEST.
  ENDCHAIN.

if the field to be checked belongs to an internal table accessed via a table control then the checking and processing phase should be carried out as follows:

PROCESS AFTER INPUT. 
  ...
  LOOP AT tbl_mydata.
    CHAIN.
      FIELD
              tbl_mydata-my_field.

      MODULE check_tbl_mydata_my_field ON CHAIN-REQUEST.
    ENDCHAIN.

    MODULE write_table_line.

  ENDLOOP.

Monday, 30 July 2007

ABAP: How to setup a Screen containing a Table

This little guide aims to serve as a general template regarding how to create a SAP screen in a dialog module or a report program that will allow the user to edit data stored in an internal table using a table control grid. This may not appear to be the most elegant of approaches to programming but that is the way ABAP works.

First of all lets keep in mind that: in order to pass data to and from screen fields, they must have the same name as a global variable. Having said that we must define a global array containing our data and a global table view control that will be used to program the data transfers.

Let us start with the data. Assuming that the table you wish to work with corrsponds to an ABAP dictionary structure named ZMY_STRUCT, then the table definition might look something like the following:

* Basic ITAB
DATA  BEGIN OF tbl_mydata OCCURS 0.
DATA:  sel TYPE c.
       INCLUDE STRUCTURE zmy_struct.
DATA:  END OF tbl_mydata.

One can reasonably ask why a table with a header line and again why an ABAP dictionary structure. The answer to both questions will be given shortly afterwards but for now let's just say that things work much easier this way, or otherwise they don't work at all :-). The table control definition should be something like

CONTROLS :
 tc_mydata TYPE TABLEVIEW USING SCREEN 0200.

Make sure that the screen number corresponds to the actual screen number of your program. Next move to the screen and press the layout button to invoke the screen painter. Inside the screen drop a table control item and name it TC_MYDATA. Size it so that it fits your screen according to your needs. Double click on the table control to bring up the properties dialog box.

The sel field of the tbl_mydata table will mark the user selected lines. Getting a field symbol to point at the selected line is as easy as writing something like :

 FIELD-SYMBOLS
   <fs> LIKE LINE OF tbl_mydata.

 READ TABLE tbl_mydata ASSIGNING <fs> WITH KEY sel = 'X' .
 IF NOT <fs> IS ASSIGNED.
   MESSAGE s888(sabapdocu) WITH text-e01. " No Selection
 ELSE.
*   Do what ever you want with -...

 ENDIF.

Now press F6 to invoke the screen painter Dict.Program Fields window. In the field named Table field Name enter a search patter like like TBL_MYDATA-* and press the button labeled Get from program to display the matching table entries. Select the ones you wish to add to you screen and press the green ok button at the bottom. Then click inside the table view control to create the appropriate columns. Had the tbl_mydata table been declared any other way -- i.e. using a TYPES section or without the header line, then the process or field selection through F6 would not work.

Note: At this point just save the screen and exit screen painter without performing any kind of syntax check or activation.

Moving back to the screen properties, the basic flow logic should at least contain the following


PROCESS BEFORE OUTPUT.

 MODULE status_0200.

 LOOP AT tbl_mydata WITH CONTROL tc_mydata
                        CURSOR tc_mydata-current_line.

   MODULE read_tbl_line.
 ENDLOOP.


PROCESS AFTER INPUT.

 MODULE exit_screen_0200 AT EXIT-COMMAND.

 LOOP AT tbl_mydata.

   MODULE write_tbl_line.
 ENDLOOP.

 MODULE user_command_0200.

The basic idea is that during PBO the contents of the entire table are copied from the table to the table control. Then during PAI the contemns of the table control will be copied from the control back to the table.

Before copying any data though, we must first set the size of the table control. The best place to do this is probably at the status module. Now, although my mother told me never to use global variables, the usual approach to setting the table size during PBO, starts by declaring a global field named somthing liketotal_entries or table_size being of type i. Having done that your status_XXX module should at least contain the following.

MODULE status_0200 OUTPUT.
 SET TITLEBAR 'TB_200'.
 SET PF-STATUS 'SCREEN-0200'.

 DESCRIBE TABLE tbl_istat LINES total_entries.
 tc_mydata-lines = total_entries.
ENDMODULE.                 " status_0200  OUTPUT

To create the read_tbl_line module, double click on the read_tbl_line inside the screen flow editor. A message will pop up asking if the a module named read_tbl_line should be created. Answer yes and depending on the type of program you are creating select the appropriate file. After you press ok, change the text in the editor so it looks like this.

MODULE read_tbl_line OUTPUT.
 MOVE-CORRESPONDING tbl_mydata TO tc_mydata.
ENDMODULE.                 " read_tbl_line  OUTPUT

Finally the write_tbl_line PAI module does the exact opposite. It moves the data from the table control back to the internal table.

MODULE write_tbl_line INPUT.
 MODIFY tbl_mydata INDEX tc_mydata-current_line.

 IF sy-subrc <> 0.
   APPEND tbl_mydata.
 ENDIF.

ENDMODULE.                 " write_tbl_line  INPUT

From now on any code executing during the user_command_XXX module will get a consistent copy of the data.

Screen programming in ABAP is a complex subject. This post provides only the basic template for minimal operations. More post will follow explaining how to respond to data changes, sort tables based on selected columns and dynamically changing your screen.

Tuesday, 17 April 2007

SAP via Delphi Re-executing the same SAP function

After running the examples from BAPI / RFC with Delphi, I run into the following problem: Each time the Button2Click function was called the grid would show the results already displayed plus any data returned from SAP after the current call. A little bit of debugging showed that the table returned from the SAPFunctions object in statement Table := Funct.tables.item('DATA'), was the one that would not get initialized prior to Funct.call(). Aa I have no clues regarding the available methods of the table or the SAPFunctions object that would allow me to clear the table data before each call, I ended up rewriting the example performing the following changes.

  1. Create one connection object and retain it throughout the entire program session during FormCreate.
  2. Create and destroy a SAPFunctions object at each invocation of the ExecuteButtonClick() function. The way the table returned always has new data returned from SAP. This has been the only way I could avoid the problem of not initializing the returned table. Since I use similar code to perform data transfers between SAP and an other Oracle database at user defined intervals , this optin was more or less a one way.
The actual code of the get cost centers example now looks like this :
unit MainUni;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, OleCtrls, SAPLogonCtrl_TLB, Grids,
 StdCtrls, ComCtrls, ExtCtrls, Buttons;

type
 TFormMain = class(TForm)
   PanelLogin: TPanel;
   StatusBar: TStatusBar;
   ButtonExec: TButton;
   Grid: TStringGrid;
   SAPLogonControl: TSAPLogonControl;
   BitBtnClose: TBitBtn;
   procedure ButtonExecClick(Sender: TObject);
   procedure FormCreate(Sender: TObject);
 private
   { Private declarations }
   Table, Funct, Connection : VARIANT ;
 public
   { Public declarations }
 end;

var
 FormMain: TFormMain;

implementation
{$R *.dfm}

Uses
  SAPFunctionsOCX_TLB;

procedure TFormMain.FormCreate(Sender: TObject);
  begin
     (* create a new connection object to be used
      * for all sessions
      *)
     Connection                  := SAPLogonControl.newConnection;
     Connection.System           := 'R3Q';
     Connection.Client           := '100';
     Connection.ApplicationServer:= 'sapqa_prd.shelman.int';
     Connection.SystemNumber     := '00';
     Connection.Language         := 'EN' ;
  end;

procedure TFormMain.ButtonExecClick(Sender: TObject);
  var
     txt : String;
     r : integer;
     SAPFunctions: TSAPFunctions;
  begin
     SAPFunctions := nil;

     // parameter "true" = SilentLogOn
     if Connection.LogOn(0, false) = true then
        try
           (* Create a new SAPFunctions obejct and
            * assign the existing connection to it
            *)
           SAPFunctions := TSAPFunctions.Create(Self);
           SAPFunctions.Connection := Connection;

           Funct := SAPFunctions.add('RFC_READ_TABLE');
           Funct.exports('QUERY_TABLE').value := 'CSKT';

           // attempt to call
           if not Funct.call then
              // called failed display the error in the statusbar
              StatusBar.SimpleText := Funct.Exception
           else begin
              // Call is successfull display returned data
              Table := Funct.tables.item('DATA');
              grid.rowCount := Table.rowcount + 1;
              grid.cells[0,0] := 'RecNo';
              grid.cells[1,0] := 'Client';
              grid.cells[2,0] := 'CostCent-No';
              grid.cells[3,0] := 'CostCent-Des.';
              for r := 1 to grid.rowCount -1 do begin
                 txt := Table.value(r,1);
                 grid.cells[0,r] := IntToStr(r);
                 grid.cells[1,r] := copy(txt,0,3);
                 grid.cells[2,r] := copy(txt,9,10);
                 grid.cells[3,r] := copy(txt,27,20);
              end;
           end;
        finally
           // close connection
           Connection.LogOff;
           // get rid of the SAPFunctions Object
           SAPFunctions.Free;
        end
     else
        StatusBar.SimpleText := 'Unable to login';
  end;

end.

Wednesday, 11 April 2007

BAPI / RFC with Delphi 6

A sad story with a happy end.

I wanted to write a small application that would update some Oracle tables based on data read from SAP using Delphi 6. Very soon I realized that it all came down to installing the various ActiveX components required for Delphi to connect to SAP, be able to execute functions and read the returned results.

And this is where the sad part of the story begins. Looking around at various sources I could find some instructions explaining how to perform the required installation but none of them seemed to be accurate enough or straightforward enough in order to produce the desired results. In my opinion the problem occurs because our German colleagues who provided the "how-to's" tried to translate the menus from their German Delphi to English and that caused the misunderstanding. Additionally, while following the directions provided I also run into compilations errors increasing my frustration. The following article provides my how to install SAP ActiveX controls on Delphi 6 and I hope to make it as clear as possible.

I have to admit that I owe everything to the guy who wrote and maintained the BAPI/RFC Programming with Delphi Page and to my good friend Marilena who found the page for me. The page has enough examples required in order for the average Delphi programmer to understand how to program the necessary communication. An other page that gives information regarding the pre-installation steps is this one found at SDN.

So here is the list of the steps I followed in order to install SAP ActiveX controls on my version of Delphi 6 If you are using Delphi 2006 then it you might beter have a look here.

  1. Create an additional package page to host the new components.
    1. Right click on the Delphi palette → Properties → Click the Add button.
    2. Type a name for the new page SAP will do just fine
    3. Use the Move Up and Move Down buttons to position the page at the correct position. Caution : Since the page will initially be empty it will not appear on the Delphi palette.
  2. Create an additional directory to hold the units for the new controls. In my case I created : C:\Program Files\Borland\Delphi6\Imports\SAPControls\.
  3. Go to main menu Component → Import ActiveX Control
    1. Select the SAP Logon Control Version 1.1 (wdtlog.OCX). The displayed class name should be TSAPLogonControl
    2. Select SAP as the Palette Page
    3. Change the directory of the unit file name to the new path created in the previous step.
    4. Click install. The first time select to create a new package, name it SAPControls and put it the path we are already using. The controls we will install next will be added to the package as well.
    5. Perform the same steps this time installing the TSAPBapiControl class from SAP BABPI Control Version 1.2
  4. Go to main menu Project → Import Type Library.
    1. Select SAP Remote Function Call Controll (Version 5.0). This will provide you with the TSAPFunctions, TFunction, TParameter, TExports, TImports, TStructure clases
    2. Click install and choose the SAPCOntrols package created previously.
  5. Press compile to compile the project. At this point I got many errors, but I will show you how to resolve them.
Many errors appear in functions with code like the following :
procedure TStructure.Set_Value(index: OleVariant; Param2: OleVariant);
 begin
   DefaultInterface.Set_Value(index) := Param2;
 end;
This should be corrected like this :
procedure TStructure.Set_Value(index: OleVariant; Param2: OleVariant);
 begin
   // DefaultInterface.Set_Value(index) := Param2;
   DefaultInterface.Value[index] := Param2;
 end;
The general error pattern is that there are unknown identifiers for functions of the form Set_XXX(i). These should be changed to indexed properties like XXX[i] Additionally I have had many functions not returning a value. The worst part is that the Delphi complier did not issue any warnings for some of them, so I had to look at each one separately. The worst case was the NewConnection method of the TSAPLogonControl class. The generated code was
function  TSAPLogonControl.NewConnection: IDispatch;
 begin
   DefaultInterface.NewConnection;
 end;
and it had to be corrected to
function  TSAPLogonControl.NewConnection: IDispatch;
 begin
   // had to add Result :=
   Result := DefaultInterface.NewConnection;
 end;
Failure to correct this results to all new Connections being returned null afterwards, making connections to R3 practically impossible. After you correct all the functions with warnings, you should be finished. Just press "Install" on the package dialog and you 're done The examples in BAPI RFC with Delphi page are good and they may get you started immediately. If I ever get any examples of my own I will post them in this page for future reference.