[转]BDC’s and CTR’s
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.
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:
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:
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.
**********************************************************************
*
* 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:
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 ??
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:
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:
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.
* 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:
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:
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:
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:
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:
* 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:
*
* 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:
**********************************************************************
*
* 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:
***********************************************************************
*
* 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:
**********************************************************************
*
* 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:
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):
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:
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:
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:
* * 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.
The constants c_batch_processed, c_batch_in_error (the Queue status) have the following values:
* * 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:
*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.