...by Daniel Szego
quote
"On a long enough timeline we will all become Satoshi Nakamoto.."
Daniel Szego

Thursday, July 10, 2014

Integrating K2 with SAP (without K2 connect) - Part 2

In the first part of this blog we set up the envrionment and started to implement the first class for a custom smart object serivce to make an integration with SAP. 
Let we just continue with the rest of the coding. Let we just overwirte DescribeSchema : it implements which properties and which methods are published from the given smart object service :​

public override string DescribeSchema()
{
    //set base info 
    this.Service.Name = "SPEGSAPService";
    this.Service.MetaData.DisplayName = "SPEGSAPService";
    this.Service.MetaData.Description = "SPEGSAPService";

    //Create the service object, one to many 
    ServiceObject so = new ServiceObject();
    so.Name = "SPEGSAPServiceObject";
    so.MetaData.DisplayName = "SPEGSAPServiceObject";
    so.MetaData.DisplayName = "SPEGSAPServiceObject";
    so.Active = true;

    //Create field definition for CostCenter ID
    Property propertyCostCenterID = new Property();
    propertyCostCenterID.Name = "CostCenterID";
    propertyCostCenterID.MetaData.DisplayName = "CostCenterID";
    propertyCostCenterID.MetaData.Description = "CostCenterID";
    propertyCostCenterID.Type = "System.String";
    propertyCostCenterID.SoType = SoType.Text;
    so.Properties.Add(propertyCostCenterID);

    //Create field definition Cost Center Name
    Property propertyCostCenterName = new Property();
    propertyCostCenterName.Name = "CostCenterName";
    propertyCostCenterName.MetaData.DisplayName = "CostCenterName";
    propertyCostCenterName.MetaData.Description = "CostCenterName";
    propertyCostCenterName.Type = "System.String";
    propertyCostCenterName.SoType = SoType.Text;
    so.Properties.Add(propertyCostCenterName);

    //Create method
    Method method = new Method();
    method.Name = "Load";
    method.MetaData.DisplayName = "Load";
    method.MetaData.Description = "Load custom service data";
    method.Type = MethodType.List;
    method.ReturnProperties.Add(propertyCostCenterID);
    method.ReturnProperties.Add(propertyCostCenterName);
    so.Methods.Add(method);
    this.Service.ServiceObjects.Add(so);

    return base.DescribeSchema();
}

In the previous example we achived that the smart object instances of this type will publish two properties, named CostCenterID and CostCenterName, and there is gonna be one method, a list type, that reads out of list of these properties.   Let we now override the main Execute method that is called when any of the methods of the smartobject is called :
public override void Execute()
{
        foreach (ServiceObject so in Service.ServiceObjects)
        {
            switch (so.Name)
            {
                case "SPEGSAPServiceObject":
                    foreach (Method method in so.Methods)
                    {
                        switch (method.Type)
                        {
                            case MethodType.List:
                                ReadSPEGSAPService(so, method);
                                break;
                        }
                    }      
                    break;
            }
        }
}

The previous method simply iterates over possible service objects and methods, and chooses practicaly the only one that has been implemented and calls ReadSPEGSAPServices to read out the data from SAP. This function is acually responsible for for the SAP connection, it initiates a R3 connection with a given connection string to an SAP system. After that it creates a function to the BAPI_COSTCENTER_GETLIST BAPI; input parameters are set; with function.Execute() is the BAPI carried out; at the end the COSTCENTER_LIST table is iterated and costcenter code and name are read out.  Putting the result back is realised with the help of the ServiceObject property and table functions. Please note that BAPI structure is the easiest to discover with the help of the se37 transaction, described at the first part of this blog.

private void ReadSPEGSAPService(ServiceObject so, Method method)
{
    string connstring = "ASHOST=... SYSNR=... USER=... PASSWD=... LANG=... CLIENT=...";
    var connection = new R3Connection(connstring);

    try
    {
        connection.Open();

        var function = connection.CreateFunction("BAPI_COSTCENTER_GETLIST");
        function.Exports["CONTROLLINGAREA"].ParamValue = "1000";
        function.Execute();
        var table = function.Tables["COSTCENTER_LIST"];

        if (table != null || table.Rows.Count > 0)
        {
            var c = 0;
            so.Properties.InitResultTable();
            foreach (RFCStructure row in table.Rows)
            {
                if (c++ > 100)
                    break;

                var costcenterid = (string)row["COSTCENTER"];
                var costcentername = (string)row["COCNTR_TXT"];

                so.Properties["CostCenterName"].Value = costcentername;
                so.Properties["CostCenterID"].Value = costcenterid;
                so.Properties.BindPropertiesToResultTable();
            }
        }
    }
    finally
    {
        if (connection != null && connection.Ping())
            connection.Close();
    }
}

Last but not least, overwrite the Extend method with an empty procedure, than compile and build the project. 
 override void Extend()
{
}
STEP 4: Deployment. To deploy the project firstly copy the created dll and the ERPConnect45.dll as well to the folder of the service type dlls.  C:\Program Files (x86)\K2 blackpearl\ServiceBroker.Open SmartObject Service Tester (located in C:\Program Files (x86)\K2 blackpearl\Bin) and register the new service type.



Create a new service instance from the service type; here you can set the ControlAreaNumber configuration property as it was shown on the screenshot in the first part of this blog. Having created the instance create some SmartObject from the type and test them with the helo of SmartObject Service Tester. If everything went fine, you should get a list of costcenter IDs and Names after pressing execute on the test window. Please note that if something went wrong then the easiest way to find a bug is with the help of the Visual Studio Debugger. You should Attach to the K2HostServer.exe and you can debug your code.



If everything works, the smartobject that you created is ready for being used either in K2 workflow or in Smartforms. As an example the following screenshot demonstrates the simpleest Smartform generated to show the result.



The end.