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.
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
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:
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 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:
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:
Once you have completed the transaction a screen similar to the one shown below will be displayed:
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 ??
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'.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 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.
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:
The program then processes the sessions and reports on the results:
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.
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.
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. ... ... ...
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.
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 '*',
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.