Synthesize export files for FM_SETS_FIPEX1 using LSMW
![Jimbo's picture Jimbo's picture](http://saplsmw.com/sites/default/files/styles/thumbnail/public/pictures/picture-1-1307661031.jpg?itok=XveYRgel)
Groups are a cumbersome object to load as there is no program to be called from LSMW for direct input or for creating BDCs. The program itself relies on objects that fall outside the normal access of BDCs and the hierarchical format of the data makes loading them with a batch all but impossible even if there were an easy way.
The FM_SETS_FIPEX1 comes with export and import functions and the same functions work for FM_SETS_FUND1, FM_SETS_FICTR1, FM_SETS_FUNDPRG1 and FM_SETS_FUNCTION1. The export files contain the information that denotes what type of group has been extracted and that can be adjusted as well.
These import and export functions would work perfectly if the source data were coming from a similar SAP system. More likely, the source data comes from a decades-old legacy system passed through a query into a format that is in no way related to the data SAP expects to import.
Like any software it is possible to reverse-engineer an extract file and to write new software that will synthesize an extract file based on the source data. The easiest way to start is by creating some dummy data in the system and then exporting it to create a sample file.
The exported data
R702 user: JKAUFFMANN date: 20121207 time: 170514 client: 100 host: unsapd1e sys-id: D1E op-sys: Linux db-sys: ORACLE dc-sys: HSTSO 03111000LEVELAA1 STSHD 03111000LEVELAA1 BFMIT RFIPEX X JKAUFFMANN E T800Y Elevel aa first instance E HSTSO 03111000LEVELAA2 STSHD 03111000LEVELAA2 BFMIT RFIPEX X JKAUFFMANN E STSBS 17007610 17007610 000000000100000 STSBS 17022010 17022010 000000000200000 T800Y Elevel aa second instance E HSTSO 03111000LEVELA1 STSHD 03111000LEVELA1 SFMIT RFIPEX X JKAUFFMANN E STSSG 03111000LEVELAA1 0000000001 STSSG 03111000LEVELAA2 0000000002 T800Y ELevel A first instance E HSTSO 03111000LEVALA2 STSHD 03111000LEVALA2 BFMIT RFIPEX X JXAUFFMANN E STSBS 69121010S 69121010S 000000000100000 STSBS 69131020S 69131020S 000000000200000 STSBS 69131030S 69131030S 000000000300000 T800Y Eleval a second instance E HSTSO 03111000TESTJIM STSHD 03111000TESTJIM SFMIT RFIPEX X JKAUFFMANN JKAUFFMANN E STSBS 11001000 11001000 000000000200000 STSSG 03111000LEVELA1 0000000003 STSSG 03111000LEVALA2 0000000004 T800Y ETest of data on all levels. E X |
The exported data consists of a flat representation of hierarchical data. Each line is 255 characters long and there are no delimiters, so it's safe to assume that the format is offset-length. This means that SAP knows, based on the type of record contained in that line, where each field begins and how long the field is.
In addition to understanding how the file is formatted it is important to understand how SAP stores the data in tables. Comparing the exported data to the hierarchical dummy data gives some clues as to how the records are organized, but not what all of the fields in the data mean. Comparing the exported sample data to the data stored in SAP tables provides the remaining clues necessary to reverse engineer the data.
The tables used to store the data are the same as those used by SAP to store sets. They are SETHEADER, SETNODE and SETLEAF. By looking for the sample data in those tables it is possible to see how the data is stored in SAP. With a little effort the connection between the exported data and the SAP tables can be intuited.
The subgroups are stored in SAP the same way that groups are. Each has a name and a description that is stored in the SETHEADER table. Additionally the type of group, the table and the field are stored in this table and these are shown in the export file just once at the top of the file. Each group is stored in the export file in the HSTSO
lines. The contents of the group are stored between the HSTSO
lines and the E
line that follows.
The type of group is also stored in the SETHEADER table. From the sample export file it can be intuited that the Basic groups have no subgroups and the Single-dimension groups have subgroups.
The SETNODE table holds the relationships between groups and subgroups. These hierarchical relationships are stored similarly in the export file in the STSSG
lines and therein the relationship between the table and the export file can be seen.
The SETLEAF table holds the items that are in a group. These values are stored in the STSBS
lines of the export file in a 1 to 1 relationship. The export file--just like the table--has value from and value to ranges with a single value. The FM_SETS_FIPEX1 transaction does not support ranges, but the items are stored in the form of ranges with a single value.
Handling the source data
The ABAP code to handle this mostly recycles the existing tables to create the internal tables used to store the data until it can be exported. After the parameters and their descriptions are declared the variables for storing the source data between the time it is read and then transformed into its hierarchical structure. These are simple internal tables based on the tables within SAP that are used to store Sets and Groups.
data: l_SCLASS like SETHEADER-SETCLASS, l_TNAME like SETHEADER-TABNAME, l_FNAME like SETHEADER-FIELDNAME. data: it_SETHEADER type standard table of SETHEADER initial size 0, lSETHEADER like SETHEADER, cFilename type string. data: isAdded(1) type c, nLineID type i, cIsExported(1) type c. data: wa_SETLEAF like SETLEAF, it_SETLEAF type standard table of SETLEAF initial size 0, wa_SETNODE like SETNODE, wa_SETNODE1 like SETNODE, wa_SETNODE2 like SETNODE, wa_SETNODE3 like SETNODE, wa_SETNODE4 like SETNODE, wa_SETNODE5 like SETNODE, it_SETNODE type standard table of SETNODE initial size 0, wa_SETHEADERT like SETHEADERT, it_SETHEADERT type standard table of SETHEADERT initial size 0. types: begin of exportfile, DataLine(498) type c, end of exportfile. data: wa_ExportFile type exportfile, it_ExportFile type standard table of exportfile initial size 0.
The source data is assumed to come in the form of a single, non-relational flat file with the headers shown below. In this format Groups and Subgroups point to their child Subgroups and the hierarchical form of the data is built that way. The last field is a flag that determines if the record is an item in the Group or a Subgroup.
Source Fields GROUPS Source SUBCLASS C(004) FM Area ITEM C(030) Item DESCRIPTION C(040) Description PARENT C(030) Parent ISGROUP C(001) Is a Group?
The Begin of Processing area holds the ABAP code that determines what type of Group is to be loaded. A set of radio buttons is used to make the determination when performing the conversion step.
if r_group1 eq 'X'. "Commitment group. write: / '*** Processing data for Commitment Group. ***' color 7. l_SCLASS = '0311'. l_TNAME = 'FMIT'. l_FNAME = 'RFIPEX'. elseif r_group2 eq 'X'. write: / '*** Processing data for Funded Program Group. ***' color 7. l_SCLASS = '0315'. l_TNAME = 'FMIT'. l_FNAME = 'RMEASURE'. elseif r_group3 eq 'X'. write: / '*** Processing data for Funds Center Group. ***' color 7. l_SCLASS = '0312'. l_TNAME = 'FMIT'. l_FNAME = 'RFISTL'. else. write: / 'No Group type has been selected for processing.' color 6. write: / 'Please select a Group type on previous screen. ' color 6. exit. endif.
The LSMW object creates a BDC with an entry for each synthetic export file created. Each Group, including all of its Subgroups is contained in a single synthetic export file.
The source data that was used as a template had the Subgroups listed first. Based on this the tool was developed to trigger the creation of the BDC entry on the first non-Subgroup entry (the first Item in the Group). This ensures that just one BDC entry is created for each Group. The export file is synthesized during the End of Processing step.
translate GROUPS-ITEM to UPPER CASE. translate GROUPS-PARENT to UPPER CASE. if GROUPS-ISGROUP eq 'X' and p_Ignore ne 'X'. perform CheckName changing GROUPS-ITEM. perform CheckName changing GROUPS-PARENT. endif. if GROUPS-ISGROUP eq 'X'. "HEADER! perform AddDescription using GROUPS-ITEM GROUPS-DESCRIPTION. if GROUPS-PARENT eq ''. perform AddHeader. concatenate p_folder '\' lSETHEADER-SETNAME '.txt' into SETS-FILENAME. replace '/' with '_' into SETS-FILENAME. else. "Subgroup... perform RemoveHeader. "This is a child group not a header. perform AddNode using GROUPS-PARENT GROUPS-ITEM. endif. else. "Item... perform AddLeaf using GROUPS-PARENT GROUPS-ITEM. endif. if cIsExported ne 'X' and GROUPS-ISGROUP ne 'X'. "This loop is performed after the headers are ratified. cIsExported = 'X'. loop at it_SETHEADER into lSETHEADER. concatenate p_folder '\' lSETHEADER-SETNAME '.txt' into SETS-FILENAME. replace '/' with '_' into SETS-FILENAME. transfer_record. transfer_transaction. endloop. endif. skip_transaction. "Skip every transaction process at end.
Knitting the source data into a hierarchical format and creating an export file is now a simple matter of aligning the data based on the supposed structure. A series of nested loops ensures that the structure can go as far as seven levels deep. More levels can be accommodated with minor adjustments to the source code in the End of Processing area.
if cIsExported ne 'X'. "Did not encouter an Item record... uline. write: 'The source data has no Item records.' color 6, 'No transactions were created. Check source file.' color 6. uline. endif. loop at it_SETHEADER into lSETHEADER. clear wa_ExportFile. wa_Exportfile-Dataline = 'R702 Import file creation tool written by Jim Kauffman'. append wa_Exportfile to it_Exportfile. loop at it_SETNODE into wa_SETNODE where SETNAME eq lSETHEADER-SETNAME. loop at it_SETNODE into wa_SETNODE1 where SETNAME eq wa_SETNODE-SUBSETNAME. loop at it_SETNODE into wa_SETNODE2 where SETNAME eq wa_SETNODE1-SUBSETNAME. loop at it_SETNODE into wa_SETNODE3 where SETNAME eq wa_SETNODE2-SUBSETNAME. loop at it_SETNODE into wa_SETNODE4 where SETNAME eq wa_SETNODE3-SUBSETNAME. loop at it_SETNODE into wa_SETNODE5 where SETNAME eq wa_SETNODE4-SUBSETNAME. perform ShowNode using wa_SETNODE5-subSETNAME. endloop. perform ShowNode using wa_SETNODE4-subSETNAME. endloop. perform ShowNode using wa_SETNODE3-subSETNAME. endloop. perform ShowNode using wa_SETNODE2-subSETNAME. endloop. perform ShowNode using wa_SETNODE1-subSETNAME. endloop. perform ShowNode using wa_SETNODE-subSETNAME. endloop. perform ShowNode using lSETHEADER-SETNAME. wa_Exportfile-Dataline = 'X'. append wa_Exportfile to it_Exportfile.
The final step is to export the it_Exportfile
internal table to a text file that matches the file name and path in the BDC entry. The ever-useful GUI_DOWNLOAD
function comes into play.
concatenate p_Folder '\' lSETHEADER-SETNAME '.txt' into cFileName. replace '/' with '_' into cFilename. CALL FUNCTION 'GUI_DOWNLOAD' EXPORTING FILENAME = cFileName FILETYPE = 'ASC' APPEND = '' WRITE_FIELD_SEPARATOR = 'X' HEADER = '00' TRUNC_TRAILING_BLANKS = 'X' TABLES DATA_TAB = it_ExportFile EXCEPTIONS FILE_WRITE_ERROR = 1 OTHERS = 2.
A handful of forms populate the internal tables with the source data from the legacy system. These are included in the LSMW object at the bottom of the page.
form AddHeader. data: lvIsAdded(1) type c. lvIsAdded = ''. loop at it_SETHEADER into lSETHEADER where SETNAME eq GROUPS-ITEM. lvIsAdded = 'X'. skip_transaction. "Don't want to import this file more than once. endloop. lSETHEADER-SETNAME = GROUPS-ITEM. lSETHEADER-SETCLASS = l_SCLASS. lSETHEADER-SUBCLASS = GROUPS-SUBCLASS. lSETHEADER-RVALUE = GROUPS-DESCRIPTION. lSETHEADER-SETTYPE = 'S'. lSETHEADER-TABNAME = l_TNAME. lSETHEADER-FIELDNAME = l_FNAME. if lvIsAdded ne 'X'. append lSETHEADER to it_SETHEADER. endif. endform. form RemoveHeader. "This function removes headers for subgroups. delete it_SETHEADER where SETNAME eq GROUPS-ITEM. endform. form AddDescription using lvSETNAME lvDESCRIPT. wa_SETHEADERT-SETNAME = lvSETNAME. wa_SETHEADERT-DESCRIPT = lvDESCRIPT. append wa_SETHEADERT to it_SETHEADERT. endform. form AddNode using lvSETNAME lvSUBSETNAME. data: lvIsAdded(1) type c. lvIsAdded = ''. loop at it_SETNODE into wa_SETNODE WHERE SUBSETNAME eq lvSUBSETNAME. lvIsAdded = 'X'. endloop. if lvIsAdded ne 'X'. wa_SETNODE-SETNAME = lvSETNAME. wa_SETNODE-SUBSETNAME = lvSUBSETNAME. append wa_SETNODE to it_SETNODE. endif. endform. Form AddLeaf using lvSETNAME lvVFrom lvVTo. data: lvLINEID type i. lvLINEID = 1. loop at it_SETLEAF into wa_SETLEAF where SETNAME eq lvSETNAME. add 1 to lvLINEID. endloop. wa_SETLEAF-SETNAME = lvSETNAME. wa_SETLEAF-VALFROM = lvVFROM. if lvVTO ne ''. wa_SETLEAF-VALTO = lvVTO. else. wa_SETLEAF-VALTO = lvVFROM. endif. wa_SETLEAF-LINEID = lvLINEID. append wa_SETLEAF to it_SETLEAF. endform. form ShowNode using lvSETNAME. data: lvLineID type i, lvcLineID(10) type c, lv_SETNODE like SETNODE, lv_SETLEAF like SETLEAF, lvSETTYPE like SETHEADER-SETTYPE, lvDATALINE(498) type c. lvSETTYPE = 'B'. "Basic set loop at it_SETNODE into lv_SETNODE where SETNAME eq lvSETNAME. lvSETTYPE = 'S'. "Single Dimensional (with sub-groups) endloop. * concatenate lSETHEADER-SETCLASS lSETHEADER-SUBCLASS lvSETNAME * into lvDATALINE. * shift lvDATALINE by 7 places right. lvDATALINE = ''. lvDATALINE+0(5) = 'HSTSO'. lvDATALINE+7(4) = lSETHEADER-SETCLASS. lvDATALINE+11(4) = lSETHEADER-SUBCLASS. lvDATALINE+15(20) = lvSETNAME. * write: / lvDATALINE. wa_Exportfile-Dataline = lvDATALINE. append wa_Exportfile to it_Exportfile. lvDATALINE = ''. lvDATALINE+0(6) = ' STSHD'. "Header lvDATALINE+0(6) = ' STSHD'. "Header lvDATALINE+7(4) = lSETHEADER-SETCLASS. lvDATALINE+11(4) = lSETHEADER-SUBCLASS. lvDATALINE+15(20) = lvSETNAME. lvDataLine+41(1) = lvSETTYPE. lvDATALINE+42(30) = lSETHEADER-TABNAME. lvDATALINE+72(30) = lSETHEADER-FIELDNAME. lvDATALINE+150(1) = 'X'. "Probably the "Unique Values" flag. * write: / lvDataline. wa_Exportfile-Dataline = lvDATALINE. append wa_Exportfile to it_Exportfile. lvLineID = 0. * if lvSETNAME eq lSETHEADER-SETNAME. "Starts at 2. Who knows why? * lvLineID = 1. * endif. loop at it_SETLEAF into lv_SETLEAF where SETNAME eq lvSETNAME. add 1 to lvLineID. lvcLineID = lvLineID. shift lvcLineID right deleting trailing space. overlay lvcLineID with '0000000000'. lvDATALINE = ''. lvDATALINE+0(6) = ' STSBS'. "Items lvDATALINE+7(20) = lv_SETLEAF-VALFROM. lvDATALINE+47(20) = lv_SETLEAF-VALTO. lvDATALINE+99(10) = lvcLineID. lvDATALINE+109(4) = '0000'. "Unknown variable. * write: / lvDATALINE. wa_Exportfile-Dataline = lvDATALINE. append wa_Exportfile to it_Exportfile. endloop. loop at it_SETNODE into lv_SETNODE where SETNAME eq lvSETNAME. add 1 to lvLineID. lvcLineID = lvLineID. shift lvcLineID right deleting trailing space. overlay lvcLineID with '0000000000'. lvDATALINE = ''. lvDATALINE+0(6) = ' STSSG'. "Subgroup lvDATALINE+7(4) = lSETHEADER-SETCLASS. lvDATALINE+11(4) = lSETHEADER-SUBCLASS. lvDATALINE+15(20) = lv_SETNODE-SUBSETNAME. lvDATALINE+52(10) = lvcLineID. * write: / lvDATALINE. wa_Exportfile-Dataline = lvDATALINE. append wa_Exportfile to it_Exportfile. endloop. lvDATALINE = ''. lvDATALINE+0(6) = ' T800Y'. lvDATALINE+7(1) = 'E'. "English loop at it_SETHEADERT into wa_SETHEADERT where SETNAME eq lvSETNAME. lvDATALINE+8(40) = wa_SETHEADERT-DESCRIPT. "lvDescription. endloop. wa_Exportfile-Dataline = lvDATALINE. append wa_Exportfile to it_Exportfile. lvDATALINE = 'E'. "English wa_Exportfile-Dataline = lvDATALINE. append wa_Exportfile to it_Exportfile. write: / lvSETNAME. endform. form CheckName changing lvName. data: lvLength type i. lvLength = strlen( lvName ). if lvLength gt 10. Write: / 'This group name must be reduced to 10 characters or fewer:', lvName color col_negative. cIsExported = 'X'. "Don't export anything! endif. endform.
Download this project
The LSMW object in its entirety, along with the recording, is available below. No additional LSMW objects or source code is required to make this work.
LSMW_GroupLoader.txt