This website is not affiliated with, sponsored by, or approved by SAP AG.

0018 - BDCs and CTRs

Moderators: Snowy, thx4allthefish, Rich, ilya

0018 - BDCs and CTRs

Postby Rich » Thu Mar 26, 2009 12:01 pm

BDC's and CTR's.

SAP provides various methods by which the entry of data into an application can be automated so that data provided in an electronic form does not need to be printed out and then rekeyed by an operator.

These include IDOC'S, BAPI'S, BDC's and CTR's.

Strictly speaking, the last three items do not import data into SAP but are the end process for a program that uploads data from an operating system file.

This article deals with the last two, BDC's and CTR's.

So. What are they ?

They are basically the same idea in that a series of responses are provided for the use of a program being run. These responses are provided by a table populated by another program that subsequently stores these responses for use at a later time via the transaction SM35, program RSBDCSUB for BDC's (Batch Data Control) or in the case of a CTR (Call Transaction) in real time.

Image

The screens that are required and the values or responses that should be used to populate the fields are stored in an internal table which is populated in an identical manner for both types of process.

This table consists of the following structure:

Code: Select all
PROGRAM   BDC_PROG   CHAR   40   0   BDC module pool
DYNPRO   BDC_DYNR   NUMC   4   0   BDC Screen number
DYNBEGIN   BDC_START   CHAR   1   0   BDC screen start
FNAM   FNAM_____4   CHAR   132   0   Field name
FVAL   BDC_FVAL   CHAR   132   0   BDC field value


Once populated the program will contain two types of data. The first is the program name and screen number that the data in the following rows applies to, upto the next program name and screen number. After each program name and screen number there can be from one to many entries defining which fields are to be populated and the values that they should contain.

Buttons that a user may press during the execution of the transaction are translated into OK Codes which are entered into the command line field at the top of the SAP Window:

Image

All the values specified in this table must be in character (or external) format. Therefore any dates or numeric data must be converted to it's external representation, either by using the relevant Conversion Exit or more simply by using the WRITE statement to a character field with the appropriate formatting clause, generally the 'NO-GROUPING' clause which does not include the delimiters used to separate thousands, hundred thousands and so forth.

If a transaction is called using the CALL TRANSACTION command, an option is available to return any messages generated by the transaction in a table, more of which later.

Where do you start ?

There are two things that you must realise before starting out on writing a BDC (and I'll use the term BDC here to mean both Batch Data Control and Call Transactions). The first is that you cannot read anything from the screen. Ie. You cannot screen scrape in the same way that you can with some terminal emulators. The only information you can get out of a BDC is the messages issued by SAP during the execution of the transaction. The second is that everything you send to the BDC must be in character format. Therefore if the data you are transferring is not text based, WRITE it to a character field first.

This can be shown by a short procedure I use in a lot of cases where the screen structure can be reflected in a dictionary structure. When using this routine, adding a new field to the BDC is easy - you just populate it in the structure being passed to the routine.

Code: Select all
*eject
**********************************************************************
*
*       Procedure:            ZBDC_FillScreen
*
*       Purpose:              Creates a screen group and then the
*                             relevant field entries from a structure
*
*       Entry:                Program name,
*       Entry:                Screen number/actual screen No
*
*                             SAP has the ability to provide sub-screens
*                             and so forth.  In some cases, the batch
*                             screen number actuully uses another
*                             screen definition.
*
*                             Therefore in the above,  the first screen
*                             number is the number to use in the BDC
*                             calls,  the second is the number where the
*                             actual fields are defined.
*
*       Entry:                Structure to use.
*
*       Exit:
*
*       Called By:            Perform ZBDC_FillScreen using 'SAPML04I'
*                                                           '0223'
*                                                           w_stru.
*
*       Calls:                ZBDC_Screen
*                             ZBDC_Field
*
*       Modification History:
*
*   Date    Reason                                       Version  Who
*
Form ZBDC_FillScreen using p_program type Program_Name
                           p_screen
                           p_struc.
*
     Constants: c_flg1edt  type x value '80',     " Field edit flags
                c_fmb1ges  type x value '20',
*
                c_char     type c value 'C',      " Field type character
                c_date     type c value 'D'.      " Field type Date

*
     Data: Begin Of w__dynpro_30,                 " Screen name FOR 30+
                 Name(8)  type c,
                 Scrn(4)  type n,
           End of w__dynpro_30,
           Begin Of w__dynpro_40,                 " Screen name FOR 40+
                 Name(40) type c,
                 Scrn(4)  type n,
           End of w__dynpro_40,
           w__d020s       like d020s,             " Screen header
           t__d021s       like d021s occurs 0     " Field List
                           with header line,
           t__d022s       like d022S occurs 0,    " Flow Logic
           t__d023s       like d023s occurs 0,    " Match code
           w__program(40) type c,
           w__screen(4)   type n,
*
           w__sfnam       like d021s-fnam,        " Structure name
           w__stble       like d021s-fnam,        " Table name (Dumped)
           w__type(1)     type c,                 " Field type
           w__dfield(10)  type c,                 " Fld for date trans
           w__nfield(20)  type c,                 " Fld for nc trans
           w__scrn(4)     type n,                 " Screen for BDC Data
           w__subscrn(4)  type n.                 " Actual screen def
*
     Field-Symbols <f__field>.
*
*    Get the screen information.
*
     If p_screen ca '/'.
        Split p_screen at '/' into w__scrn w__subscrn.
     Else.
        Move p_screen to w__scrn.
        Move p_screen to w__subscrn.
     Endif.
     If sy-saprl+0(1) = 3.
        Move p_program to w__dynpro_30-name.
        Move w__subscrn  to w__dynpro_30-scrn.
        Import Dynpro w__d020s t__d021s t__d022s t__d023s
                   id w__dynpro_30.
     Else.
         Move p_program to w__dynpro_40-name.
         Move w__subscrn  to w__dynpro_40-scrn.
         Import Dynpro w__d020s t__d021s t__d022s t__d023s
                    id w__dynpro_40.
     EndIf.
*
*    Set up the screen bdc record.
*
     Perform ZBDC_Screen using p_program w__scrn.
*
*    And loop round the field list.
*
     Loop at t__d021s.
*
*         Process this field ?
*
          if t__d021s-flg1 o c_flg1edt.
             if t__d021s-fmb1 z c_fmb1ges.
*
*               Get the actual field name....
*
                Split t__d021s-fnam  at '-' into w__stble w__sfnam.
                If not w__sfnam is Initial.
                   Assign component w__sfnam
                          of structure p_struc to <f__field>.
                   If sy-subrc = 0.
                      If not <f__field> is initial.
*
*                        If this is a numeric or a date field then it
*                        needs to be written to a character field first.
*
                         Describe field <f__field> type w__type.
                         Case w__type.
                              When c_char.
                                   Perform ZBDC_Field
                                           using t__d021s-fnam
                                                 <f__field>.
                                   Move '' to <f__field>.
                              When c_Date.
                                   Write <f__field> to w__dfield.
                                   Perform ZBDC_Field
                                           using t__d021s-fnam
                                                 w__dfield.
                                   Move '' to <f__field>.
                              When others.
                                   Write <f__field> to w__nfield.
                                   Condense w__nfield.
                                   Perform ZBDC_Field
                                           using t__d021s-fnam
                                                 w__nfield.
                                   Move 0 to <f__field>.
                         Endcase.
                      EndIf.
                   EndIf.
                EndIf.
             Endif.
          Endif.
     EndLoop.
EndForm.


The starting point for a BDC is the transaction that the user wishes to automate. For the purposes of this text, I'll assume that the user wishes to extend a series of materials across a number of plants, creating the relevant views that are contained within the material master data and any plant relevant views that are required.

The transaction for this is MM01.

The first thing that you have to do is to figure out the program names, fields and buttons that are used by the user to carry out the task that is required. So. Go grab yourself the user that requested this, bolt him to his seat and ask him to take you through what he requires. Once you have the information that you need to be able to replicate what he wants to do, go back to your terminal and load the transaction recorder, transaction code SHDB.

This will then display a screen like so:

Image

Click the 'New Recording' button and provide the recording with a name and the transaction code that you wish to record. Click the 'Start Recording' button:

Image

Once you have completed the transaction a screen similar to the one shown below will be displayed:

Image

This provides you with the screen sequences, commands (Ok Codes), field names and values that will be placed in the fields.

At this point, you could actually generate a program by saving the recording by clicking the save button, returning to the previous screen, and selecting one of the 'Session', 'Program' or 'Function Module' buttons. This will enable you to create a program that will read data from an external file and then perform the recording exactly as it was recorded, populating the fields on the screen or creating an SM35 session for later processing.

Easy isn't it ??

Nope.

At this point you have a bare bones BDC. It will select the view that was selected during the recording, it will post data and it will provide results of a sort. However, even a relatively simple BDC like this requires some form of intelligence to be reasonably useful so lets treat the program generated by the recorder as a starting point.

What the program needs to be able to do is to determine the views required by the material being extended, select the relevant views, guide the program through the relevant screens, populating the fields as it goes. These views and screen sequences can be different for each material type. What then needs to be decided is how errors are going to be handled. For example, they can be reported on, SM35 sessions can be created so that the operation can be run again with corrected information, the program can be stopped and the erroneous screen displayed and so on.

The first thing to do is to make it reasonably easy to understand the screens and the ok codes (or button presses) that are going to be used. I tend to use constants for this type of thing:

Code: Select all
     Define_Transaction MM01 SAPLMGMM.
     Define_Subscreen:  MM01 0060 SAPLMGMM,      " In initial screen
                        MM01 0070 SAPLMGMM,      " Views
                        MM01 0080 SAPLMGMM,      " Organisational Levels
                        MM01 4000 SAPLMGMM,      " Main Data
                        MM01 4200 SAPLMGMM.      " Tax Classification
*
     Constants: c_View_Enter        type syUcomm value '=ENTR',
                c_Goto_Basic_Data_1 type syUcomm value '=SP01',
                c_Goto_Basic_Data_2 type syUcomm value '=SP02',
                c_Goto_Purchasing   type syUcomm value '=SP09',
                c_Goto_Sales_Org_1  type syUcomm value '=SP04',
                c_Goto_Sales_Org_2  type syUcomm value '=SP05',
                c_Goto_Sales_Generl type syUcomm value '=SP06',
                c_Goto_Accounting_1 type syUcomm value '=SP24',
                c_Goto_Accounting_2 type syUcomm value '=SP25',
                c_Main_Data         type syUcomm value '=MAIN'.


The macros Define_Transaction and Define_Subscreen create constants. The source can be found in Docs.zip in the repository.

Having done that the program needs to know what views will be required. The views are defined by the material type Mtart. Make sure that the material code being dealt with is in the right format to be used as selection criteria in a SELECT statement by using the appropriate Conversion Exit:

Code: Select all
          Call Function 'CONVERSION_EXIT_MATN1_INPUT'
            Exporting
              Input              = w_Upload_Record-Matnr
            Importing
              Output             = w_Upload_Record-Matnr
            Exceptions
              Length_Error       = 1
              Others             = 2.
          Select single Matnr Mtart
            into (w_Matnr, w_Mtart)
            from Mara
           where Matnr = w_Upload_Record-Matnr.


and get the views that will be processed:

Code: Select all
*
*            Get the views to build.
*
             Select Single Pstat
               Into W_Pstat
               From T134
              Where Mtart = W_Mtart.
*
             Call Function 'SELECTION_VIEWS_FIND'
               Exporting
                 Bildsequenz           = '21'
                 Pflegestatus          = W_Pstat
               Tables
                 Bildtab               = T_Bildtab
               Exceptions
                 Call_Wrong            = 1
                 Empty_Selection       = 2
                 Others                = 3.


Now the program is ready to construct the BDC. All of the procedures you see mentioned here can be found in Docs.zip in the repository, specifically the include ZBDCINC which contains a series of routines that I use each and every time I write a BDC (See the topic on Modularisation when it arrives!). Note that the version there is a few years old. If you find them of interest contact me and I will send you an uptodate copy (unless I get round to uploading it first!), however, there is a standard SAP include called BDCRECXX which has a lot of the same functions in it... but not quite!

The first part is easy. All that is needed is to enter the material number and press the Enter key:

Code: Select all
             Perform NewBdc.
*
             Perform Zbdc_Screen using c_MM01_0060-Program
                                       c_MM01_0060-Screen.
             Perform Zbdc_Field  using 'RMMG1-MATNR'
                                       w_Upload_Record-Matnr.
             Perform Zbdc_Field  using c_OkCode c_Enter.


The next screen is the bane of a lot of abapers lives. How to select the relevant views. The program knows what views the material requires. It also knows in what order they will appear in the View Selection window due to the function module called above. Note that the views defined in the upload record can be in any order.

Code: Select all
             Perform Zbdc_Screen using c_MM01_0070-Program
                                       c_MM01_0070-Screen.
             Compute w_View_Len = Strlen( w_Upload_Record-pstat ).
             Move 0 to w_View_Pos.
             Move True to w_First_Page.
             Do w_View_Len times.
                  Loop at t_BildTab into w_Bildtab
                      Where Pstat = w_Upload_Record-Pstat+w_View_Pos(1).
                            Move sy-tabix to w_Subscript.
                            If w_Subscript > 18.
                               If w_First_Page = True.
                                  Perform Zbdc_Field
                                          using c_OkCode c_Page_Down.
                                  Perform Zbdc_Screen
                                    using c_MM01_0070-Program
                                          c_MM01_0070-Screen.
                                  Move False to w_First_Page.
                               EndIf.
                               Subtract 18 from w_Subscript.
                            Else.
                               If w_First_Page = False.
                                  Perform Zbdc_Field
                                    using c_OkCode c_Page_Up.
                                  Perform Zbdc_Screen
                                    using c_MM01_0070-Program
                                          c_MM01_0070-Screen.
                                  Move True to w_First_Page.
                               EndIf.
                            EndIf.
                            Perform Zbdc_Subscript
                                    Using 'MSICHTAUSW-KZSEL'
                                          w_Subscript
                                          True.
                 EndLoop.
                Add 1 to w_View_pos.
             EndDo.
             Perform Zbdc_Field using c_okCode c_View_Enter.


The program calculates where in the list the view is. In ABAP, table arrays are accessed by appending a subscript to a field name. For example, the first logical row of the table is 1, the second 2 and so forth. Therefore, we would use 'MSICHTAUSW-KZSEL(1)' to select Basic View 1, 'MSICHTAUSW-KZSEL(2)' for Basic View 2 and so on. (Procedure Zbdc_Subscript does this automatically).

In a standard screen, the maximum number of views that can be displayed is 18, so the program decides on what page the view resides on and pages up or down automatically as required by the list of views passed to the routine. If a page up or page down command is required, the program has to insert the screen details again.

Having selected the relevant views and pressed the Enter key, the program is faced with another decision. A screen may or may not appear requiring Organisational details, and when it does, it will generally require something different from what you are expecting. Again, to handle this type of situation the views required are examined:

Code: Select all
             Move 0 to w_View_Pos.
             Do w_View_Len times.
                Case w_Upload_Record-Pstat+w_View_Pos(1).
                     When c_Accounting_View.
                          Add_View 'RMMG1-WERKS' w_Upload_Record-Werks.
                     When c_Purchasing_View.
                          Add_View 'RMMG1-WERKS' w_Upload_Record-Werks.
                     When c_Basic_View.
                     When c_Sales_View.
                          Add_View 'RMMG1-WERKS' w_Upload_Record-Werks.
                          Add_View 'RMMG1-VKORG' w_Upload_Record-Vkorg.
                          Add_View 'RMMG1-VTWEG' w_Upload_Record-Vtweg.
                EndCase.
                Add 1 to w_View_Pos.
             EndDo.
             Perform Zbdc_Field using c_OkCode c_View_Enter.


Whoa!! There's an error there - an empty case statement you say.... I would say no it's not. It's there to say that I've considered it and don't wish to do anything in that situation. The macro Add_View checks to see if the program has already declared the Organisational details screen, does so if it's required and adds in the relevant fields.

So, you can see that from the original recording there are more than a few changes going on to make the program usable.

There's still a way to go. By now we're at the main screen with the different Tabs and views on. Again, the program needs to know what views are required so that it can visit each tab:

Code: Select all
             If c_Tran_Code = c_MM01-Transaction.
                Perform Zbdc_Screen using c_MM01_4000-Program
                                          c_MM01_4000-Screen.
             Else.
                Perform Zbdc_Screen using c_MM02_4004-Program
                                          c_MM02_4004-Screen.
             EndIf.
             Move 0 to w_View_pos.
             Do w_View_Len times.
                Case w_Upload_Record-Pstat+w_View_Pos(1).
                     When c_Accounting_View.
                          Perform Zbdc_Field  using c_OkCode
                                                    c_Goto_Accounting_1.
                          Perform Zbdc_Screen using c_MM01_4000-Program
                                                    c_MM01_4000-Screen.
                          Perform Zbdc_Field
                                  using 'MBEW-BKLAS'
                                        w_Upload_Record-Bklas.
                          Perform Zbdc_Field
                                  using 'MBEW-VPRSV'
                                        w_Upload_Record-Vprsv.
                          Perform Zbdc_Field
                                  using 'MBEW-VERPR'
                                        w_Upload_Record-Verpr.
*
*                         Accounting 2 not required
*
                     When c_Purchasing_View.
                          Perform Zbdc_Field using c_OkCode
                                                   c_Goto_Purchasing.
                          If c_Tran_Code = c_MM01-Transaction.
                             Perform Zbdc_Screen
                                           using c_MM01_4000-Program
                                                 c_MM01_4000-Screen.
                          Else.
                             Perform Zbdc_Screen
                                           using c_MM01_4000-Program
                                                 c_MM01_4000-Screen.
                          EndIf.
                     When c_Basic_View.
*
*                         Basic Data not selectable in MM01
*
                          If c_Tran_Code = c_MM02-Transaction.
                             Perform Zbdc_Field
                                     using 'MARA-MTPOS_MARA'
                                           w_Upload_Record-MtPos.
                          EndIf.
                     When c_Sales_View.
*
*                         Nothing on Sales 1.
*
                          Perform Zbdc_Field  using c_OkCode
                                                    c_Goto_Sales_Org_2.
                          Perform Zbdc_Screen using c_MM01_4000-Program
                                                    c_MM01_4000-Screen.
                          Perform Zbdc_Field
                                  using 'MVKE-VERSG'
                                        w_Upload_Record-Versg.
                          Perform Zbdc_Field
                                  using 'MVKE-KTGRM'
                                        w_Upload_Record-Ktgrm.
*
                          If w_Upload_Record-Ladgr = ''.
                             Move '0001' to w_Upload_Record-Ladgr.
                          EndIf.
                          Call Function 'CONVERSION_EXIT_ALPHA_INPUT'
                               Exporting
                                  Input  = w_Upload_Record-Ladgr
                               Importing
                                  Output = w_Upload_Record-Ladgr.


And this continues until all of the views have been covered.

Other screens appear sporadically generally it seems without rhyme or reason at first (a bit like coding blocks....) and have to be handled. An example of this is the Tax classification Screen:

Code: Select all
*
*               Pops out to a tax classification screen....
*               if theres a sales view - on the first loop
*
                If c_Tran_Code = c_MM01-Transaction.
                   If w_View_Pos = 0.
                      Search w_Upload_Record-Pstat for c_Sales_View.
                      If sy-subrc = 0.
                         Perform Zbdc_Field  using c_OkCode c_Enter.
                         Perform Zbdc_Screen using c_MM01_4200-Program
                                                   c_MM01_4200-Screen.
                         Perform Zbdc_Field  using c_OkCode c_Main_Data.
                         Perform Zbdc_Screen using c_MM01_4000-Program
                                                   c_MM01_4000-Screen.
                      EndIf.
                   EndIf.
                EndIf.
                Add 1 to w_View_Pos.


But it does nothing!!! That's right. It does nothing at all, but you still need to handle it in your BDC. However, if you just blindly include it in your code then your BDC will work sometimes but not all the time.

So far, the procedure for creating BDC's (ie SM35 sessions) and CTR's is identical, but at this point they diverge. The next few paragraphs describe running the update in real time, ie via a Call transaction.

Having completed all the screens the program can then call the transaction. I use a procedure for this as more than a few other things are handled by the procedure. For example a table is read that contains values for various parameters - whether the screen is displayed or not, whether the BDC table should be printed before the call or the messages that are returned by the call and many other things beside.

When the transaction completes a list of messages issued by the transaction is available in a table with a structure of BDCMSGCOLL - the BDC Message Collector. Messages are catagorised by severity including Warnings, Abends and E errors so the program needs to know if the transaction was successful or not.

If the transaction fails then the error message will be the first error message in the message table. If the transaction is successful then the document number or other success message is the last success message in the table, with (normally) the document number being the first message variable:

Code: Select all
*
**********************************************************************
*
*       Procedure:            Check_Messages
*
*       Purpose:              Checks the specified BDC message table
*                             and returns the first error message,  or
*                             the value in MSGV1 for the last Success
*                             Message
*
*       Entry:                BDC Message Table
*
*       Exit:                 Error text
*                             MSGV1 from last success message
*
*       Called By:            Perform Check_Messages Tables t_bdcmsg
*                                                  changing pc_msgv1
*                                                           pc_error
*
*       Calls:
*
*       Modification History:
*
*   Date    Reason                                       Version  Who
*
Form Check_Messages tables t_bdc_messages Type Message_Table
                  changing pc_msgv1
                           pc_error_text.
*
     Data w_bdcmsg type bdcmsgcoll.
*
*    Check message table in order E class, A class and S class.
*
     Clear pc_msgv1.
     Clear pc_error_text.
     Read Table t_bdc_messages with key msgtyp = c_error.
     If sy-subrc <> 0.
        Read Table t_bdc_messages
              with key msgtyp = c_abort.
        If sy-subrc <> 0.
*
*          Find the LAST success message
*
           Loop at t_bdc_messages
                Where msgtyp = c_success.
           EndLoop.
        EndIf.
     EndIf.
*
     If sy-subrc = 0.
        If t_bdc_messages-msgtyp <> c_success.
           Call Function 'MESSAGE_TEXT_BUILD'
                Exporting MSGID = t_bdc_messages-msgid
                          MSGNR = t_bdc_messages-msgnr
                          MSGV1 = t_bdc_messages-msgv1
                          MSGV2 = t_bdc_messages-msgv2
                          MSGV3 = t_bdc_messages-msgv3
                          MSGV4 = t_bdc_messages-msgv4
                Importing MESSAGE_TEXT_OUTPUT = pc_error_text.
        Else.
           Move t_bdc_messages-msgv1 to pc_msgv1.
        EndIf.
     EndIf.
EndForm.


One thing to note is that there are certain messages which have a Success status but which in fact are errors. A Data lock is one of them. In MM02's case if the material number does not exist is another. I tend to have a translation table which contains these message id's and numbers which changes the status to 'E' if they occur.

So, by calling this routine the result is either the full readable message if it's an error, or the document number if not.

The program can then decide what to do, either reporting the message or some other action.

I have mentioned above that the procedure I use to call the transaction reads a table containing various values that should be used as either default actions for a BDC or by specifying a transaction code (and possibly a user) it can take actions based upon that transaction code. These actions can be to create an SM35 session of the failed BDC or use an external routine to mail a specified user. Out of interest, here is the routine that I am talking about:

Code: Select all
*eject
**********************************************************************
*
*       Procedure:            ZDo_BDC
*
*       Purpose:              Runs a specified BDC Session
*
*       Entry:                Transaction Code
*       Entry:                Screen Update Mode
*                             A - Show all screens
*                             N - Show no screens
*                             E - Show erroneous screens only.
*
*       Entry:                Database Update Type
*                             A - Asynchronous
*                             S - Synchronous
*
*       Entry:                Table
*                             p_messages - contains batch msgs
*
*       Exit:                 MsgId,
*       Exit:                 Error
*       Exit:                 Message if failed.
*
*       Called By:            Perform ZDo_BDC Using 'MM02' 'N' 'S'
*                                          Changing w_msgid w_err w_errt
*
*       Calls:
*
*       Modification History:
*
*   Date    Reason                                       Version  Who
*
Form ZDo_BDC Tables p_messages structure bdcmsgcoll
              using p_trans  like tstc-tcode
                    p_mode   like bdc_struc-bdcmode
                    p_update like bdc_struc-bdcupmode
           changing p_msgid  like sy-msgid
                    p_msgno  like sy-msgno
                    p_text   type status_text.
*
     Data: w__msgtp like bdcmsgcoll-msgtyp,
           w__msgv1 like sy-msgv1,                   " Message variables
           w__msgv2 like sy-msgv2,
           w__msgv3 like sy-msgv3,
           w__msgv4 like sy-msgv4,
           w__subrc like sy-subrc,
           w__exmsg type status_text,
           w__group like apqi-groupid,               " BDC Group name
           w__istat type i.                          " BDC Insert status
*
     Zap p_messages.
     Clear p_msgid.
     Clear p_msgno.
     Clear p_text.
*
*    Dump the contents of the BDC table ?
*
     If w__dumpbdc = True.
        New-page print on immediately True
                 with-title
                 with-heading
                 Line-size 254
                 Line-count 64.
        Loop at zbdc_table.
             Write :/ zbdc_table-Program+0(30),
                      zbdc_table-dynpro,
                      zbdc_table-dynbegin,
                      zbdc_table-fnam+0(30),
                      zbdc_table-fval+0(30).
        EndLoop.
        New-page print off.
     EndIf.
     Call Transaction p_trans using ZBDC_Table
                               Mode p_mode
                             Update p_update
                      Messages into p_messages.
     Move sy-subrc to w__subrc.
*
*    Scan the messages in YDCRAISES to see if we need to
*    change the message class.
*
     Loop at p_messages.
          Select single msgtyp
            into w__msgtp
            from ydcraise
           where tcode  = p_messages-tcode  and
                 msgid  = p_messages-msgid  and
                 msgnr  = p_messages-msgnr.
          If sy-subrc = 0.
             Move w__msgtp to p_messages-msgtyp.
             Modify p_messages.
          EndIf.
     EndLoop.
*
*    Dump the message table ?
*
     If w__ydcset-dumpmsg = True.
        New-page print on immediately True
                 with-title
                 with-heading
                 Line-size 254
                 Line-count 64.
        Loop at p_messages.
             Call Function 'MESSAGE_TEXT_BUILD'
                  Exporting Msgid = p_messages-msgid
                            Msgnr = p_messages-msgnr
                            Msgv1 = p_messages-msgv1
                            Msgv2 = p_messages-msgv2
                            Msgv3 = p_messages-msgv3
                            Msgv4 = p_messages-msgv4
                  Importing Message_Text_Output = w__exmsg.
             Write :/ p_messages-tcode,  p_messages-dyname,
                      p_messages-dynumb, p_messages-msgtyp,
                      p_messages-msgid,  p_messages-msgnr, w__exmsg.
        EndLoop.
        New-Page Print Off.
     EndIf.
*
*    Did the BDC work ?
*
     If w__subrc <> 0.
        Move sy-msgid to p_msgid.
        Move sy-msgno to p_msgno.
        Move sy-msgv1 to w__msgv1.
        Move sy-msgv2 to w__msgv2.
        Move sy-msgv3 to w__msgv3.
        Move sy-msgv4 to w__msgv4.
     Else.
*
*       Scan the messages for A or E class messages ?
*
        Read Table p_messages with key msgtyp = c_error.
        If sy-subrc <> 0.
           Read Table p_messages with key msgtyp = c_abort.
        EndIf.
        If sy-subrc = 0.
           Move p_messages-msgid to p_msgid.
           Move p_messages-msgnr to p_msgno.
           Move p_messages-msgv1 to w__msgv1.
           Move p_messages-msgv2 to w__msgv2.
           Move p_messages-msgv3 to w__msgv3.
           Move p_messages-msgv4 to w__msgv4.
        EndIf.
     EndIf.
*
*    Do we need to build an error message ?
*
     If not p_msgno is initial.
*
*       Get the message text.
*
        Call Function 'MESSAGE_TEXT_BUILD'
             Exporting MSGID = p_msgid
                       MSGNR = p_msgno
                       MSGV1 = w__msgv1
                       MSGV2 = w__msgv2
                       MSGV3 = w__msgv3
                       MSGV4 = w__msgv4
             Importing MESSAGE_TEXT_OUTPUT = p_text.
*
*       Make a batch of this session ??
*
        If w__bdcbatch = True.
           Get Time.
           Concatenate p_trans '_' sy-uname sy-uzeit+0(5) into w__group.
           Perform ZBDC_Open_Group using w__group changing w__istat.
           If w__istat = 0.
              Perform ZBDC_Insert using p_trans changing w__istat.
              If w__istat = 0.
                 Perform ZBDC_Close_Group changing w__istat.
              EndIf.
           EndIf.
        EndIf.
*
*       Mail a user ?
*
        If not w__ydcset-genmail is initial.
           If not w__ydcset-mailprog is initial.
              Perform (w__ydcset-genmail)
                      in program (w__ydcset-mailprog)
                           tables ZBDC_Table
                            using p_trans p_text sy-uname if found.
           EndIf.
        EndIf.
     EndIf.
EndForm.


Table Controls and Step Loops.

Even though SAP provides a standard screen size for BDC programming, I have found that the settings of a users Video card affect the screen even though it is in the back ground.

The things that can affect a BDC session include the screen resolution, the font and font size that the user has selected for his SAP sessions and a few other bits and bobs.

The other thing about table controls and step loops is that the area shown on the screen is a logical window into the table. The lines do not reflect the actual position in that table but are numbers from one to the number of lines displayed in the table control.

The effect that this has on table controls is to vary the number of lines displayed and then the number of lines that are paged up or down when the user hits the page up/down buttons. The other problem is that you cannot record the action of the scroll bar.

All of this makes the programming of BDC's that use table controls a bit of a hit or miss affair.... unless....

By using the navigational commands that are built into the majority of these type of transactions you can write a BDC that is totally independant of the settings of the users screen. In some cases where the program does not offer these navigational controls you can very often find a single record input screen to use.

As an example, lets have a look at transaction CO02, Production Order Change. The process is creating batch splits in a raw material and allocating the relevant quantities to the batches. When a batch is split, the quantities are zero, and the splits are located underneath the parent materials. The original quantity needs to be reset to zero.

How do you find the batch splits ? Well, you sort the components, forcing the new batch split to the top of the component list. Then the only line number in the table control you need to worry about is line one:

Code: Select all
*Eject
***********************************************************************
*
*       Procedure:     CO02_Sort_Components
*
*       Purpose:       Sorts CO02 by component quantity,  forcing
*                      new batch splits to the top.
*
*       Entry:
*
*       Tables:
*
*       Exit:          BDC Table updated with BDC statements
*
*       Called By:
*
*       Calls:
*
Form CO02_Sort_Components using pu_sortkey like tcopt-profid.
*
     Perform ZBDC_Screen using c_co02_0120-program
                               c_co02_0120-screen.
     Perform Zbdc_Field  using c_okcode 'SORT'.
     Perform Zbdc_Screen using c_sort_0100-program
                               c_sort_0100-screen.
*
*    Named sort or a standard sort ?

     If pu_sortkey <> ''.
        Perform Zbdc_Field  using 'TCOPT-PROFID' pu_sortkey.
        Perform Zbdc_Field  using c_okcode '/5'.
        Perform Zbdc_Screen using c_sort_0100-program
                                  c_sort_0100-screen.
        Perform Zbdc_Field  using c_okcode '/5'.
     Else.
        Perform Zbdc_Field  using c_okcode '/6'.
     EndIf.
     Perform ZBDC_Screen using c_co02_0120-program
                               c_co02_0120-screen.
     Perform Zbdc_Field  using c_okcode c_report_top.
*
Endform.


Another example is the Purchase Order Processing suite. This contains a table control that displays the lines of the purchase order. Using the OKCode 'POPO' you can bring the desired line to the first logical line of the table control - getting rid of the problem of when to page down and by how many lines have you actually paged down by.

Inserting items into Table Controls.

The same problem arises when you are inserting items into table controls, however, by always inserting at line one of the table control you do not have to worry about how many lines are displayed and when to page up or down. If neccesary, invert the order of your source itab to insert the lines in reverse order.

Single Record Entry Screens.

If there does not appear to be any navigational controls to allow you to move the required item line to the first line of the table control, try and find a 'Single Data Entry Screen'. VL02N is a good example of this. There does not appear to be a way of inserting a record at line one of teh table control, so dig around and what do you find ?? There's a single data entry screen which allows you to enter one record at a time, avoiding resolution problems once again:

Code: Select all
*Eject
**********************************************************************
*
*       Procedure:            Create_Single_Boxes
*
*       Purpose:              Creates individual HU's by using the
*                             single record entry screen of VL02N
*
*       Entry:                Pack Screen program
*                             Pack Screen number to use.
*                             Number of boxes to create
*
*       Exit:
*
*       Called By:            Perform Create_Single_Boxes
*                                     'SAPLV51A' '0100' '0004'
*
*       Calls:
*
*       Modification History:
*
*   Date    Reason                                       Version  Who
*
Form Create_Single_Boxes using pu_Pack_Program type Program_Name
                               pu_Pack_Screen  type syDynnr
                               pu_Boxes        type CHAR004.
*
     Define_Subscreen: VL02N_Sngl 4000 SAPLV51A.  " Single Record Entry
*
     Constants: c_Single_Entry  type syucomm value '=VDIR'.

     Data: w_Box_Cnt            type i,           " # SUs to create
           w_Exidv              type Exidv.       " HU Number


     Perform Zbdc_Screen using pu_Pack_Program pu_Pack_Screen.
     Perform Zbdc_Field  using c_OkCode c_Single_Entry.
*
*
*    There does not appear to be any way of 'Inserting' a new shipping
*    unit at line 1,  (avoiding screen size probs),  Use Single record
*    entry screen.
*
     Move pu_Boxes to w_Box_Cnt.
     Do w_Box_Cnt Times.
        Perform Zbdc_Screen using c_VL02N_Sngl_4000-Program
                                  c_VL02N_Sngl_4000-Screen.
        Write sy-Index to w_Exidv.
        Condense w_Exidv no-gaps.
        Perform Zbdc_Field using 'VEKP-EXIDV' w_Exidv.
        Perform Zbdc_Field using 'VEKP-VHILM' 'GBPACK'.
        Perform Zbdc_Field using c_OkCode c_Enter.
     EndDo.
*
*    Back to the main screen.
*
     Perform Zbdc_Screen using c_VL02N_Sngl_4000-Program
                               c_VL02N_Sngl_4000-Screen.
     Perform Zbdc_Field  using c_OkCode c_Back.
     Perform Zbdc_Screen using pu_Pack_Program pu_Pack_Screen.
Endform.


SM35 Sessions.

The next few paragraphs show how to create an SM35 session, and take up from where the Call Transaction would normally occur.

Three function modules are needed to create an Sm35 session. These are:

  • BDC_OPEN_GROUP
  • BDC_INSERT
  • BDC_CLOSE_GROUP
.

BDC_OPEN_GROUP creates the session in SM35. It provides the user details and the name of the session. BDC_INSERT takes the BDC table generated above and places it in the session. BDC_CLOSE_GROUP closes the session and gives it a 'NEW' status in SM35 but does not run the session.

Phew!

And theres more!

Having created the BDC Session in SM35 it can be run automatically should it be required (although for the life of me I can't see the point of creating a session and then running it straight away - why not use CALL Transaction ??)

Using program RSBDCSUB, you can select sessions to be run without resorting to SM35.

RSBDCSUB allows you to select sessions that youn wish to process via a selection screen:

Image

The program then processes the sessions and reports on the results:

Image

This can all be done under program control.

The first thing to do is to wait for the batch to appear in the Queue Information table APQI. (This is assuming that you are running the batch directly after creating it):

Code: Select all
     Do.
        Select single qstate
          into w_status
          from apqi
         where mandant = sy-mandt and
               groupid = pu_groupid.
        If sy-subrc = 0.
           Exit.
        Else.
           If sy-index > c_batch_wait_time.
              Move True to w_overtime.
              Exit.
           Else.
              Wait up to 1 seconds.
           EndIf.
        EndIf.
     EndDo.


You need to know the Group Id or session name that was created. This code snippet will only wait a certain amount of time for the batch to appear before giving up. When the batch does appear the Batch status is returned. Not for any particular reason mind you!

When the batch appears you can then run RSBDCSUB using the same Group ID that was used above. The report needs to be exported to memory so that you can make use of the information that RSBDCSUB returns:

Code: Select all
        Do.
           Submit RSBDCsub
                  Exporting List To Memory
                  with mappe    = pu_groupid
                  with Von      = sy-datum
                  with Bis      = sy-datum
                  with z_verarb = True
                  with fehler   = false
                  with logall   = true
              and return.
           Commit Work.
*
*          If the report ran,  this does not neccessarily
*          mean the batch did.  Get the report back and look
*          for the job number.
*
           If sy-subrc = 0.
              Call Function 'LIST_FROM_MEMORY'
                Tables
                   Listobject       = t_list
                Exceptions
                   Not_Found        = 0
                   Others           = 0.
              Call Function 'LIST_TO_ASCI'
                Tables
                   Listasci                 = t_asci
                   Listobject               = t_list
                Exceptions
                   Empty_List               = 0
                   List_Index_Invalid       = 0
                   Others                   = 0.
*
*             Get the Queue ID
*
              Read Table t_asci index 4.
              Search t_asci-line for 'Queue ID'.
              Read Table t_asci index 6.
              Move t_asci-line+sy-fdpos(20) to w_qid.
              Condense w_qid.
              If w_qid co c_numbers.
                 Exit.
              Else.
*
*                Break out of here after 5 minutes if there is
*                no activity
*
                 If sy-index > c_batch_wait_time.
                    Move True to w_overtime.
                    Exit.
                 Else.
                    Wait up to 1 seconds.
                 EndIf.
              EndIf.
           EndIf.
        EndDo.


The function modules LIST_FROM_MEMORY and LIST_FROM_ASCI provide the report produced (and shown above) by RSBDCSUB. The program needs to know the Queue ID in order to be able to check on the completion status of the batch.

With the Queue ID, the program can then wait for the batch to start and then monitor it's progress:

Code: Select all
          Call Function 'BDC_PROTOCOL_SELECT'
              Exporting
                  Name               = pu_groupid
                  Session_User       = sy-uname
                Tables
                  Apqltab            = t_apql
                Exceptions
                  Invalid_Data       = 0
                  Others             = 0.
*
*          Get the relevant job record back.
*
           Read Table t_apql with key qid = w_qid.
           If sy-subrc = 0.
              Exit.
...
...
...


When an entry appears in the t_apql table with the relevant Queue ID, the batch has started. It's then just a matter of waiting for the batch to complete. This is achieved by watching the Queue status:

Code: Select all
*
*          Wait for the batch to complete.
*
           Do.
              Move '*' to w_status.
              Select single qstate
                into w_status
                from apqi
               where mandant = sy-mandt and
                     qid     = w_qid.
*
*             Whats the batch up to ?
*
              Case w_status.
                   When c_batch_processed. Exit.
                   When c_batch_in_error.
                        Move False    to g_results-result.
                        Move text-070 to g_results-msg.
                        Exit.
                   When c_batch_incorrect.
                        Move False    to g_results-result.
                        Move text-070 to g_results-msg.
                        Exit.
              EndCase.
              Wait up to 1 seconds.
           EndDo.


The constants c_batch_processed, c_batch_in_error (the Queue status) have the following values:

Code: Select all
*
*          Batch statuses
*
           c_batch_new          like apql-status  value ' ',
           c_batch_processed    like c_batch_new  value 'F',
           c_batch_batch        like c_batch_new  value 'R',
           c_batch_incorrect    like c_batch_new  value 'E',
           c_batch_in_error     like c_batch_new  value 'E',
           c_batch_created      like c_batch_new  value 'C',
           c_batch_background   like c_batch_new  value 'S',
           c_batch_closed       like c_batch_new  value '*',


Once the batch has been processed, or has failed, the program needs to know what happened. In order to find out this interesting piece of information the job logs need to be read and interpreted.

There is a function module that purports to provide the job log - BP_JOBLOG_READ, but this just returns the time the job started and ended. What is actually required is the log of the messages produced by the transaction in the same manner as a CALL TRANSACTION does when a message table is requested.

These messages are accessed by using function modules RSTS_OPEN_RLC, RSTS_READ and RSTS_CLOSE. The RSTS_READ function module provides a table of messages held confusingly enough in a different format than the BDC Message table.

SAP provides the Message ID and number as normal (id those found in SY-MSGID and SY-MSGNO) and then a parameter count followed by a string containing the 4 variable parts of the message, each part being preceded by a length word. (Very much like how Basic used to store it's strings in string memory). The procedure to get at each variable part is to read the length of the variable as the first two bytes of the string, then the string for the length specified by the bytes until the total number of variable parts have been read.

Then it's a simple process to turn the message id, number and the variable parts into a human readable text:

Code: Select all
*Eject
***********************************************************************
*
*       Procedure:     Get_Log
*
*       Purpose:       Gets the log for a selected job.
*
*                      NOTE: BP_JOBLOG_READ returns the wrong log -
*                            that just shows job start, running and end.
*
*       Entry:         TemSeId of job log.
*
*       Exit:          Table of expanded text messages
*
*       Called By:
*
*       Calls:
*
*       Modification History:
*
*   Date    Reason                                         Version  Who
*
Form Get_Log Tables t_joblog
             using pu_TemSeId like Apql-TemSeId.
*
     Data: Begin Of t_logtable Occurs 50,  " Plain Log Info In Temse
                 Enterdate Like Btctle-Enterdate,
                 Entertime Like Btctle-Entertime,
                 Logmessage(400) Type C,
           End Of t_logtable,
*
           Begin Of t_bdclm  Occurs 0.        " Log message Structure
                 Include Structure bdclm.
     Data:       Counter Type I,
                 Longtext Type Bdc_Mpar,
           End Of t_bdclm,
*
           w_joblog        type JobLogs,
           w_External_Date(10),               " Display date
           w_Internal_Date Type D,
           W_Msgv1         Like T100-Text,    " Message variables 1,2,3
           W_Msgv2         Like T100-Text,
           W_Msgv3         Like T100-Text,
           W_Msgv4         Like T100-Text,    " and 4
           W_Mlen          Type I,            " Current message v length
           W_Subscr(1)     Type N,            " Current message variable
           W_Varname(7)    Type C.            " Message variable name
*
     Field-Symbols: <F_Field>.                " Pointer to current msgv
*
*    Open the log,  read it and then close it.
*
     Call Function 'RSTS_OPEN_RLC'
       Exporting
         Name         = pu_temSeId
         Client       = sy-mandt
         Authority    = 'Batch'
         Prom         = 'I'
         Rectyp       = 'VNL----'
       Exceptions
         Fb_Call_Handle =  4
         Fb_Error       =  8
         Fb_Rsts_Noconv = 12
         Fb_Rsts_Other  = 16
         No_Object      = 20
         Others         = 24.
     If sy-subrc = 0.
        Call Function 'RSTS_READ'
          Tables
            Datatab  = t_logtable
          Exceptions
            Fb_Call_Handle =  4
            Fb_Error       =  8
            Fb_Rsts_Noconv = 12
            Fb_Rsts_Other  = 16
            Others         = 16.
        If sy-subrc = 0.
           Call Function 'RSTS_CLOSE'
             Exceptions
               Others = 0.
*
*          The log messages are held as t100 messages with the message
*          variables in a single string.
*
           Clear t_bdclm[].
           Loop At t_logtable.
*
*               Get a displayable version of the date.  If there's
*               a problem,  don't display this record.
*
                Call 'DATE_CONV_INT_TO_EXT'
                  Id 'DATINT' Field t_logtable-Enterdate
                  Id 'DATEXT' Field w_External_Date.
                Call 'DATE_CONV_EXT_TO_INT'
                  Id 'DATEXT' Field w_External_Date
                  Id 'DATINT' Field w_Internal_Date.
                If Sy-Subrc Ne 0.
                   Continue.
                Endif.
                Clear t_bdclm.
                t_bdclm-Indate  = t_logtable-Enterdate.
                t_bdclm-Intime  = t_logtable-Entertime.
                t_bdclm+14      = t_logtable-Logmessage.
                Append t_bdclm.
           Endloop.
*
*          Decode the messages in t_bdclm inserting the variable parts
*          of the message.
*
           Loop At t_bdclm.
                Clear: W_Msgv1,
                       W_Msgv2,
                       W_Msgv3,
                       W_Msgv4.
*
*               Any variable parts in this message ?
*
                If t_bdclm-Mparcnt > 0.              "#EC PORTABLE
*
*                  Message variables in MPar are held in a single
*                  string.  Two bytes precede each variable with the
*                  variable length.
*
                   Do.
*
*                      Get the current variable length and remove that
*                      from the start of the string.
*
                       Move t_bdclm-Mpar+0(2) To W_Mlen.
                       Move t_bdclm-Mpar+2    To t_bdclm-Mpar.
*
*                      Calculate the message variable this is for
*
                       Move Sy-Index          To W_Subscr.
                       Concatenate 'W_Msgv' W_Subscr Into W_Varname.
                       Assign (W_Varname) To <F_Field>.
*
*                      Move the variable to the correct message var and
*                      check if there are any more variable parts to
*                      process.
*
                       Move t_bdclm-Mpar+0(W_Mlen) To <F_Field>.
                       Move t_bdclm-Mpar+W_Mlen To t_bdclm-Mpar.
                       If Sy-Index >= t_bdclm-Mparcnt.     "#EC PORTABLE
                          Exit.
                       Endif.
                   Enddo.
                Endif.
*
*               Finally build the complete message and place it in the
*               output table.
*
                Clear w_joblog.
                Move-corresponding t_bdclm to w_joblog.
                Move w_external_date to w_joblog-ddate.
                Call Function 'MESSAGE_TEXT_BUILD'
                  Exporting
                    Msgid                     = t_bdclm-Mid
                    Msgnr                     = t_bdclm-Mnr
                    Msgv1                     = W_Msgv1
                    Msgv2                     = W_Msgv2
                    Msgv3                     = W_Msgv3
                    Msgv4                     = W_Msgv4
                 Importing
                    Message_Text_Output       = W_joblog-text.
                Append w_joblog to t_joblog.
           Endloop.
        EndIf.
     EndIf.
EndForm.


That's it really. BDC's and CTR's in a big nutshell.
Regards

Rich

Image
Abap KC:http://www.richard-harper.me.uk/Kb
SFMDR:http://www.se37.com
Rich
 
Posts: 7112
Joined: Thu Oct 31, 2002 4:47 pm
Location: Liverpool

Return to ABAPers

Who is online

Users browsing this forum: No registered users and 2 guests





loading...


This website is not affiliated with, sponsored by, or approved by SAP AG.