Load Classifications For Any Object Using RFC From Excel
SAP has decided that customer-supplied Classification Types are "out of scope" for S4 in their Migration Cockpit tool. When attempting to load custom Classification Types against Objects in S4, the Migration Cockpit throws an "Enter a correct class type" error and further states that your Class Type "isn't supported". SAP provides a little more information in Knowledge Base Article 3128863.
| Migration Object: Object classification - General template Occurred During: Simulate Migration: Completed with Errors The provided class type is not supported by data migration object. For supported class type list, please see object document and tool tips. Technical Information Error Message Message Class: CNV_DMC_SN Message Number: 433 |
Related articles:
- Use LSMW as a wrapper for BAPIs
- Canned tools for handling Classifications with VBA Macros
- Read Table Data from SAP into Excel Using RFCs
Rather than allowing the project to fail due to a simple existential crisis, this programmer sat down to write a tool in Excel that uses RFCs to call BAPIs in SAP using a little VBA. The code is even more confusing than the acronyms, so feel free to skip directly to the download link below to use this tool.
SAP has certain functions called BAPIs (Business Application Programming Interface) that can be called from outside of SAP by external systems. In this case, a little Visual Basic code calls the BAPI remotely by use of an RFC (Remote Function Call).
As long as the connection to the SAP system exists with appropriate authorization, the call can be made regardless of the external system or computer language. Because the source data is already in Excel and Excel supports VBA, it made perfect sense to use Excel and VBA.
The tool allows the user to provide a list of servers from which data can be extracted and to which data can be loaded. A dropdown in Cell A1 of the Classifications tab tells Excel with which server to interact.
The Extract code teases out a report that includes all of the Classification Allocations and the Characteristics that have values along with those values. The extracted data in this report can be used later to load updated data back into the server or migrate that data to another server; it is already perfectly formatted to be loaded and can be used as a template when receiving data to be loaded.
Once the source data is available in Excel, the Extract code first creates a connection to SAP. This code has been recycled so many times that the variable name is still R3. The credentials are provided by the user when SAP's popup requests them.
Dim R3 As Object
Set R3 = CreateObject("SAP.Functions")
'*************************************
' Put in your own credentials here.
'*************************************
R3.Connection.System = Range("A1").Value
R3.Connection.SystemNumber = strSystemNumber
R3.Connection.client = strClient
R3.Connection.User = "" ' Put your User ID here (optional).
R3.Connection.Password = "" 'Put your password here (optional).
R3.Connection.Language = "EN" 'Put the desired language here (optional).
R3.Connection.ApplicationServer = strApplicationServer
If R3.Connection.logon(0, True) <> True Then
If R3.Connection.logon(0, False) <> True Then
GetR3 = False
Exit Function
End If
End If
Next, we loop through the records in the Spreadsheet with data to be extracted and pass that to the BAPIs to tease out the data that are associated with each Classification allocation. It first checks to see what Classes are associated with the Object; if no Classification Type (KLART) value is provided then it checks all Classification Types associated with the Object Table provided in the ServerList tab (see Instructions tab in the spreadsheet).
The VBA code for this tool is particularly complicated; a lot of data is juggled into and out of these BAPIs. For the sake of maintaining this programmer's sanity, the various bits are abstracted into small functions that can be called independently to trigger the RFCs.
Function CallGetClassesBAPI(ByRef R3 As Object, strObject As String, strObjectTable As String, strClassType As String, ByRef strClasses() As String) As Boolean
'* * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* *
'* This function only checks to see if the Object has *
'* an assignment in the particular Class Type. *
'* *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * *
Dim MyFunc, App As Object, Result As Boolean, objALLOCLISTRow As Object, objALLOCVALUERow As Object, hasChars As Boolean
Dim strClass As String, nCurrentLine As Long
'Let the user know what is happening.
Application.StatusBar = "Extracting for " & strObject & ", " & strClassType
'Define the parameters to be passed to the BAPI.
Dim OBJECTKEY_IMP As Object
Dim OBJECTTABLE_IMP As Object
Dim CLASSTYPE_IMP As Object
Dim READ_VALUATIONS As Object
'Define the variables to be populated by the BAPI.
Dim ALLOCLIST As Object
Dim BAPIRET2 As Object 'Will hold the messages from the BAPI.
'Tell the connection which BAPI to call.
Set MyFunc = R3.Add("BAPI_OBJCL_GETCLASSES")
'Prepare the parameters to be passed to the BAPI.
Set OBJECTKEY_IMP = MyFunc.Exports("OBJECTKEY_IMP")
Set OBJECTTABLE_IMP = MyFunc.Exports("OBJECTTABLE_IMP")
Set CLASSTYPE_IMP = MyFunc.Exports("CLASSTYPE_IMP")
Set READ_VALUATIONS = MyFunc.Exports("READ_VALUATIONS")
'Populate the values to be passed to the BAPI.
OBJECTKEY_IMP.Value = strObject
OBJECTTABLE_IMP.Value = strObjectTable
CLASSTYPE_IMP.Value = strClassType
READ_VALUATIONS.Value = ""
'Call the function.
Result = MyFunc.call
If Result Then
'Prepare the variables to be populated by the BAPI.
Set ALLOCLIST = MyFunc.Tables("ALLOCLIST")
Set BAPIRET2 = MyFunc.Tables("RETURN")
'We loop through the Char data to tease it out.
For Each objALLOCLISTRow In ALLOCLIST.Rows
strClasses(0, 0) = 1 + strClasses(0, 0)
strClasses(strClasses(0, 0), 0) = objALLOCLISTRow("OBJECT")
strClasses(strClasses(0, 0), 1) = objALLOCLISTRow("OBJTYP")
strClasses(strClasses(0, 0), 2) = objALLOCLISTRow("CLASSNUM")
strClasses(strClasses(0, 0), 3) = objALLOCLISTRow("CLASSTYPE")
CallGetClassesBAPI = True
Next
retval = DoEvents
ALLOCLIST.Rows.RemoveAll
Set ALLOCLIST = Nothing
BAPIRET2.Rows.RemoveAll
Set BAPIRET2 = Nothing
Else
'Meh, do nothing.
End If
'Clean up memory.
Set OBJECTKEY_IMP = Nothing
Set OBJECTTABLE_IMP = Nothing
Set CLASSTYPE_IMP = Nothing
Set READ_VALUATIONS = Nothing
Set MyFunc = Nothing
Set objALLOCLISTRow = Nothing
Set objALLOCVALUERow = Nothing
R3.Remove ("BAPI_OBJCL_GETCLASSES")
Application.StatusBar = False
End Function
Function CallClassGetDetailBAPI(ByRef R3 As Object, strObject As String, strObjectTable As String, strClass As String, strClassType As String, strOutput() As String) As Boolean
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* *
'* This function teases existing Characteristic data from *
'* from SAP using the BAPI_OBJCL_GETDETAIL function. *
'* *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Dim MyFunc, App As Object, Result As Boolean, objALLOCLISTRow As Object, objALLOCVALUERow As Object, hasChars As Boolean
Application.StatusBar = "Extracting " & strObject & ", " & strClass & " . . ."
'Define the variables to pass parameters to the BAPI.
Dim OBJECTKEY As Object
Dim OBJECTTABLE As Object
Dim CLASSNUM As Object 'The Classification
Dim CLASSTYPE As Object
'Define the variable receive returned values from BAPI
Dim ALLOCVALUESCHAR As Object
Dim ALLOCVALUESCURR As Object
Dim ALLOCVALUESNUM As Object
Dim BAPIRET2 As Object 'Will hold the messages from the BAPI.
'Tell the connection which BAPI to call.
Set MyFunc = R3.Add("BAPI_OBJCL_GETDETAIL")
'Prepare the parameters to be passed to the BAPI.
Set OBJECTKEY = MyFunc.Exports("OBJECTKEY")
Set OBJECTTABLE = MyFunc.Exports("OBJECTTABLE")
Set CLASSNUM = MyFunc.Exports("CLASSNUM")
Set CLASSTYPE = MyFunc.Exports("CLASSTYPE")
'Populate the values to be passed to the BAPI.
OBJECTKEY.Value = strObject
OBJECTTABLE = strObjectTable
CLASSNUM = strClass
CLASSTYPE = strClassType
'Call the function.
Result = MyFunc.call
If Result Then
'Prepare the variables to be populated by the BAPI.
Set ALLOCVALUESCHAR = MyFunc.Tables("ALLOCVALUESCHAR")
Set ALLOCVALUESCURR = MyFunc.Tables("ALLOCVALUESCURR")
Set ALLOCVALUESNUM = MyFunc.Tables("ALLOCVALUESNUM")
Set BAPIRET2 = MyFunc.Tables("RETURN")
'We loop through the Char data to tease it out.
'strClass = objALLOCLISTRow(4)
For Each objALLOCVALUERow In ALLOCVALUESCHAR.Rows
strOutput(0, 0) = 1 + strOutput(0, 0)
strOutput(strOutput(0, 0), 0) = strObject
strOutput(strOutput(0, 0), 1) = strObjectTable
strOutput(strOutput(0, 0), 2) = strClass
strOutput(strOutput(0, 0), 3) = strClassType
strOutput(strOutput(0, 0), 4) = objALLOCVALUERow("CHARACT")
strOutput(strOutput(0, 0), 5) = objALLOCVALUERow("VALUE_NEUTRAL") 'The key value and not description.
CallClassGetDetailBAPI = True
Next
For Each objALLOCVALUERow In ALLOCVALUESCURR.Rows
strOutput(0, 0) = 1 + strOutput(0, 0)
strOutput(strOutput(0, 0), 0) = strObject
strOutput(strOutput(0, 0), 1) = strObjectTable
strOutput(strOutput(0, 0), 2) = strClass
strOutput(strOutput(0, 0), 3) = strClassType
strOutput(strOutput(0, 0), 4) = objALLOCVALUERow("CHARACT")
strOutput(strOutput(0, 0), 5) = objALLOCVALUERow("VALUE_FROM")
strOutput(strOutput(0, 0), 6) = objALLOCVALUERow("CURRENCY_FROM")
CallClassGetDetailBAPI = True
Next
For Each objALLOCVALUERow In ALLOCVALUESNUM.Rows
strOutput(0, 0) = 1 + strOutput(0, 0)
strOutput(strOutput(0, 0), 0) = strObject
strOutput(strOutput(0, 0), 1) = strObjectTable
strOutput(strOutput(0, 0), 2) = strClass
strOutput(strOutput(0, 0), 3) = strClassType
strOutput(strOutput(0, 0), 4) = objALLOCVALUERow("CHARACT")
strOutput(strOutput(0, 0), 5) = objALLOCVALUERow("VALUE_FROM")
strOutput(strOutput(0, 0), 6) = objALLOCVALUERow("VALUE_TO")
CallClassGetDetailBAPI = True
Next
retval = DoEvents
End If
'Clean up memory.
Set OBJECTKEY = Nothing
Set OBJECTTABLE = Nothing
Set CLASSTYPE = Nothing
Set CLASSNUM = Nothing
ALLOCVALUESCHAR.Rows.RemoveAll
ALLOCVALUESCURR.Rows.RemoveAll
ALLOCVALUESNUM.Rows.RemoveAll
Set ALLOCVALUESCHAR = Nothing
Set ALLOCVALUESCURR = Nothing
Set ALLOCVALUESNUM = Nothing
BAPIRET2.Rows.RemoveAll
Set BAPIRET2 = Nothing
R3.Remove ("BAPI_OBJCL_GETDETAIL")
Application.StatusBar = False
End Function
The Load feature is more of the same. It calls the CHECK_OBJECT_IN_CLASS BAPI to see if the Classification is already allocated to the Object and, if so, teases out the existing Characteristic data into memory and then knits in the new data to be loaded; this preserves values when only a subset of Characteristic values is being updated.
When no allocation exists, the tool calls the BAPI_OBJCL_CREATE BAPI. When the object already has an allocation, the code calls BAPI_OBJCL_CHANGE BAPI.
The code snippets that call the functions above are well documented in the remarks. Naturally, this tool comes with no warranty--expressed or implied--so be sure test this extensively in a sandbox before using it on a production system.
Downloads

