Sample Groovy Scripts
Here is a list of sample Groovy Scripts for reference.
- Post Quote Creation Script to Automatically Set Custom Expiry Date
- Check Credit Limit by Calling ERP BAPI
- Check Product Attributes in Quote and Set Line Item Attributes Accordingly
- Filtering Account List during Quote Creation
- Create a Custom BRA Whose Values Come from a Lookup Table
- Scripts to Execute on Configuration Exits
Post Quote Creation Script to Automatically Set Custom Expiry Date
This is a simple first example meant to get you started. It also shows the suggested way to work with date manipulation in groovy.
- Upload the script to the Post Quote Creation extension point. This example script sets the date of expiry to be 6 months after the creation date for all new Quotes, ignoring the value set in the UI.
import com.imc.vocabulary.Schema;
import com.imc.iss.groovy.salesitem.SalesItemsTree;
// Set the expiry date to be 6 months from now
use (groovy.time.TimeCategory)
{
Date expiryDate = new Date() + 6.months;
String attribute = Schema.salesDocumentDateOfExpiry.getUriAsString();
salesItemsTree.setQuoteDataAttribute(attribute, expiryDate.getTime());
} - Create a new Quote. The date of expiry of this newly created Quote will be 6 months from the current date.
Check Credit Limit by Calling ERP BAPI
This example aims to show how to call an ERP BAPI, as well as how to set up the custom output for the Check Credit Limit extension point.
- Ensure the system is connected to ERP. Please consult the ERP Integration Guide about this subject if necessary.
- Upload the script to the “Check Credit Limit” Groovy Script. In this example script, we will be calling the BAPI_CREDIT_ACCOUNT_GET_STATUS standard function by using the SAP JCo library. The required inputs are hardcoded for simplicity. You can change these hardcoded values to match what’s available in your ERP system, or you may go one step further and retrieve these values from the Quote using the salesItemsTree binding variable.
import com.imc.iss.web.services.commands.quote.AbstractERPSupport;
import com.sap.conn.jco.JCoDestination;
import com.sap.conn.jco.JCoRepository;
import com.sap.conn.jco.JCoFunction;
import com.sap.conn.jco.JCoParameterList;
import com.sap.conn.jco.JCoTable;
import com.sap.conn.jco.JCoStructure;
import com.imc.vocabulary.Schema;
import com.imc.iss.groovy.model.GroovyTable;
import com.imc.iss.groovy.model.GroovyField;
import com.imc.iss.groovy.model.GroovyRecord;
// Hardcoded input parameters. These may be changed to get actual data
// from the Quote, which depends on the specific implementation
String customer = '1000'
String creditControlArea ='1000'
// Get the JCoFunction object representing the BAPI
String bapiName = 'BAPI_CREDIT_ACCOUNT_GET_STATUS';
JCoDestination dest = AbstractERPSupport.getRFCDestination('ECC_RFC_DESTINATION');
JCoRepository repo = dest.getRepository();
JCoFunction bapi = AbstractERPSupport.getReqFunction(repo, bapiName);
// Set the input parameters and execute the BAPI
JCoParameterList inputs = bapi.getImportParameterList();
inputs.setValue("CUSTOMER", customer.padLeft(10, "0"));
inputs.setValue("CREDITCONTROLAREA", creditControlArea);
//execute the bapi and obtain the output data
bapi.execute(dest);
JCoTable data = bapi.getTableParameterList().getTable("CREDIT_ACCOUNT_DETAIL");
// Extract the data into the required output format
// We will just retrieve the credit limit value, as well as
// its currency for display only
GroovyField limit = new GroovyField(Schema.quoteAccountCreditLimit);
GroovyField currency = new GroovyField(Schema.Currency);
GroovyRecord record = new GroovyRecord();
if(data.getNumRows() > 0)
{
// if the BAPI returns a result, use it
limit.setValue((String) data.getValue("CRED_LIMIT"));
currency.setValue((String) data.getValue("CURRENCY_DI"));
}
else
{
// otherwise, clear the field
limit.setValue('');
currency.setValue('');
}
record.add(limit);
record.add(currency);
return new GroovyTable([limit,currency], [record]); - Open or create a Quote and click on the Actions > Credit Check button.
- The output GroovyTable of the script will be displayed as a pop-up. In this case, it is only a single row showing the credit limit value (which gets automatically inserted into the corresponding field on the Quote) and its currency (which is for display purposes only).
Check Product Attributes in Quote and Set Line Item Attributes Accordingly
This example aims to show how to use the SalesItemsTree and SalesItemNode classes to read and write data to the Quote, and the groovyCtxUtil binding variable to read arbitrary data. It also shows how to use the groovyLogger binding variable to send various types of notifications to the UI.
- Create or modify a Product such that it has “Finished Good” for Type in Overview tab and “Test Sector” for Product Industry Sector in Product Additional Information tab.
- Create a new Quote and add the above Product to it. Feel free to add additional products to the quote as well, which fulfill one, two, or none of the above conditions regarding the product Type and Industry Sector fields.
- Create a new “Organize Line Item Structure” Groovy Script in the system and upload the following example script. The script will check each of the top-level line items in the Quote. If the line item is a Finished Good with that particular Industry Sector, the script will write a message to the Sales Item’s description, but only if it is currently empty.
import com.imc.vocabulary.Schema;
import com.imc.datamodel.BusinessObject;
import com.imc.iss.groovy.salesitem.SalesItemsTree;
import com.imc.iss.groovy.salesitem.SalesItemNode;
// loop over the quote and examine each top level node
for(SalesItemNode topLevelNode : salesItemsTree.getChildren())
{
// Product Type URI is directly supported by SalesItemNode
// This can then be compared directly to the schema value
String productTypeURI = topLevelNode.getProductTypeURI();
groovyLogger.notifyInfo("productTypeURI " + productTypeURI);
if(!productTypeURI.equals(Schema.FINISHEDGOOD.getUriAsString()))
{
groovyLogger.notifyWarning(topLevelNode.getObjectName()
+ " is not processed because product type is not right");
continue;
}
// To retrieve data where it is not directly supported yet by the API,
// we can use the groovyCtxUtil
BusinessObject productBO = BusinessObject.create(topLevelNode.getProductURI());
groovyCtxUtil.loadBO(productBO);
String sector = groovyCtxUtil.getDataAttrStr(productBO, Schema.productIndustrySector);
groovyLogger.notifyInfo("productIndustrySector " + sector);
if(!sector.equals("Test Sector"))
{
groovyLogger.notifyWarning(topLevelNode.getObjectName()
+ " is not processed because product industry sector is not right");
continue;
}
// If the product meets the requirement, then we will set the sales item
// description. However, this script will not overwrite it if it already
// has some content
String attributeURI = Schema.salesItemDescription.getUriAsString();
String currentDesc = topLevelNode.getSalesItemDataAttributeValue(attributeURI);
if(currentDesc == null || currentDesc.isEmpty())
{
topLevelNode.setDataAttribute(attributeURI, "Product belongs to Test Sector!")
groovyLogger.notifySuccess(topLevelNode.getObjectName()
+ " has been processed successfully");
}
else
{
groovyLogger.notifyWarning(topLevelNode.getObjectName()
+ " is not processed because it already has a description");
}
}
return salesItemsTree; - Execute the script on the Quote created above and accept the result. It should fill in the Sales Item description for the product that fulfills the criteria.
Filtering Account List during Quote Creation
This example aims to show how to create Conditions for the Filter Account List extension point.
- Create or modify an Account so that it has an Industry of either “Automotive” or “Manufacturing”. If necessary, create the relevant master data first from Administration > Master Data Management > Industry. Also, give it an Account Group of “GRP1”. Feel free to also create or modify other accounts to also fulfill either one, both, or none of these criteria as well.
- Upload the following script to the Filter Account List extension point. This script creates 2 conditions to filter the accounts. The first condition is created using the filterFactory binding variable, and it says that the Account must have an Industry that has an object name of either “Automotive” or “Manufacturing”. The second condition is created directly, and it says that the Account must have an Account Group of “GRP1”.
import com.imc.context.filters.*;
import com.imc.context.filters.Condition.OPERATOR;
import com.imc.vocabulary.Schema;
// include only accounts that belong to either Automotive or Manufacturing industry
def condition1 = filterFactory.getCondition(Schema.Industry, Schema.hasIndustry,
Schema.objectName, ["Automotive", "Manufacturing"], OPERATOR.CONTAINS);
filterAccountScript.add(condition1);
// include only accounts that has "GRP1" account group
def condition2 = new ConditionAttribute(Schema.accountGroup, "GRP1",
OPERATOR.CONTAINS);
filterAccountScript.add(condition2);
// only accounts that meet both of these conditions will be shown in quote creation - Click on the Create Quote menu. In the Create Quote dialog, only Accounts that does meet both of those conditions will be shown as an option for the Account field.
Create a Custom BRA Whose Values Come from a Lookup Table
This example aims to show how to setup a BRA with the Get Range Values extension point, as well as how to interact with a Lookup Table from a groovy script. In this example, a custom field will be displayed for Quotes, whose values will come from a custom Lookup Table and filtered according to another field’s value.
- Create one custom BDA and one custom BRA in a Quote by adding the following to the application-schema-ext.owl file. Please refer to the General System Features Guide for more details about editing the ontology file.
<owl:NamedIndividual rdf:about="&ase;QuoteCustomAttributes">
<rdf:type rdf:resource="&as;Group"/>
<rdfs:label xml:lang="en">Custom Attributes</rdfs:label>
<as:hasSubGroup rdf:resource="&ase;QuoteCustomAttributesSubGroup"/>
</owl:NamedIndividual>
<owl:NamedIndividual rdf:about="&ase;QuoteCustomAttributesSubGroup">
<rdf:type rdf:resource="&as;SubGroup"/>
<rdfs:label xml:lang="en">Sub Group</rdfs:label>
</owl:NamedIndividual>
<owl:DatatypeProperty rdf:about="&ase;customBDA">
<rdf:type rdf:resource="&owl;FunctionalProperty"/>
<rdfs:label xml:lang="en">Quote Custom BDA</rdfs:label>
<as:sequenceID rdf:datatype="&xsd;int">1</as:sequenceID>
<as:group rdf:resource="&ase;QuoteCustomAttributes"/>
<as:subGroup rdf:resource="&ase;QuoteCustomAttributesSubGroup"/>
<rdfs:domain rdf:resource="&as;Quote"/>
<rdfs:range rdf:resource="&xsd;string"/>
<rdfs:subPropertyOf rdf:resource="&ps;assertion"/>
<as:synchronizeAllowed rdf:datatype="&xsd;boolean">true</as:synchronizeAllowed>
</owl:DatatypeProperty>
<owl:ObjectProperty rdf:about="&ase;customBRA">
<rdf:type rdf:resource="&owl;FunctionalProperty"/>
<rdfs:label xml:lang="en">Quote Custom BRA</rdfs:label>
<as:sequenceID rdf:datatype="&xsd;int">2</as:sequenceID>
<as:group rdf:resource="&ase;QuoteCustomAttributes"/>
<as:subGroup rdf:resource="&ase;QuoteCustomAttributesSubGroup"/>
<rdfs:domain rdf:resource="&as;Quote"/>
<rdfs:range rdf:resource="&ase;customBRAType"/>
<as:enumerable rdf:datatype="&xsd;boolean">true</as:enumerable>
<rdfs:subPropertyOf rdf:resource="&ps;has"/>
<as:synchronizeAllowed rdf:datatype="&xsd;boolean">true</as:synchronizeAllowed>
</owl:ObjectProperty>
<owl:Class rdf:about="&ase;customBRAType">
<rdfs:subClassOf rdf:resource="&ps;Attribution"/>
<ps:hidden rdf:datatype="&xsd;boolean">true</ps:hidden>
</owl:Class>
<rdf:Description rdf:about="&ase;customBRA">
<as:useScriptForRangeValues rdf:resource="&ase;scriptForCustomBRA"/>
</rdf:Description>
<owl:NamedIndividual rdf:about="&ase;scriptForCustomBRA">
<rdf:type rdf:resource="&as;GroovyScript"/>
<as:objectName rdf:datatype="&xsd;string">Get Custom BRA Values</as:objectName>
<as:hasGroovyScriptType rdf:resource="&as;GetRangeValues"/>
</owl:NamedIndividual> - To make sure that the additions to the application-schema-ext.owl file are reflected in the system, go to Administration > Operation Settings > System Settings and click on the “Start” button for the “Load Customizing” field. Log out and login back to the system after that’s done.
- Check that the new fields show up in a Quote. Since there is no script uploaded yet, there will be an error message if you try to access the drop-down for the Quote Custom BRA field.
- Create a simple Lookup Table that will be used by the script.
- Go to the Lookup Tables screen by clicking on the Lookup top menu item.
- Click on the plus icon to create a new Lookup Table. Give it a Name of “CUSTOM_BRA_VALUES”, leave the other fields as their default values, and click on the “Create” button.
- The screen will be automatically refreshed to show the list of fields in the table. A few fields would have been automatically created by default. Click on the plus icon to add a new field. Give it a Name of “BDA_VALUE”, a Type of “Data Record”, and a Data Type of “String data type”. Set both Searchable and Unique Key to Yes.
- Repeat the process above once more to add another field named “BRA_VALUE”. All other settings except for Name should be the same. Once that is done, click on the activate icon to activate the Lookup Table.
- In the next screen, click on the Records tab, then click on the plus icon to insert a new Record. Perform this step several times to insert data into the Lookup Table.
- Go to the Lookup Tables screen by clicking on the Lookup top menu item.
- Upload the following script to the “Get Custom BRA Values” script that were defined in the application-schema-ext.owl file earlier.
import com.imc.iss.groovy.rangeValues.RangeValuesList
Also give it access to the CUSTOM_BRA_VALUES table, as shown below.
import com.imc.iss.groovy.rangeValues.RangeValue
RangeValuesList output = new RangeValuesList();
// These are the URI of the attributes defined in the application-schema-ext.owl
def ase = 'http://www.inmindcloud.com/application/application-schema-ext.owl#';
def bda_uri = ase + 'customBDA';
def bra_uri = ase + 'customBRA';
// Get the value of the BDA, if blank just return immediately
def bda_value = salesItemsTree.getQuoteDataAttributeValue(bda_uri);
if(bda_value == null || bda_value == '')
{
return output;
}
// Otherwise, get the BRA values from the lookup table where the BDA value matches
List<Map<String,Object>> bra_values = dbLookupTable.table('CUSTOM_BRA_VALUES')
.addCondition('BDA_VALUE', '==', bda_value)
.search();
// Package the values into the required output format
bra_values.each {
def id = it.get('ID').toString();
def name = it.get('BRA_VALUE');
output.add(new RangeValue(businessType, id, name, null));
}
return output; - In the Quote, when the Quote Custom BDA field has been filled with the appropriate value, now the Quote Custom BRA field will display the corresponding options.
- If the Quote Custom BDA field is blank or filled with a value that does not exist in the Lookup Table, the drop-down for the Quote Custom BRA field will not have any option
- If a value has already been set for the Quote Custom BRA field, and then value of the Quote Custom BDA field is changed such that the value is no longer applicable, the field will no longer be able to display the label. It will revert backward to displaying the URI of the previously saved option (in this case, containing the ID of the lookup record). How to handle this scenario more gracefully depends on specific requirement of the system and is not within the scope of this example.
Scripts to Execute on Configuration Exits
There are currently three use cases that can be implemented on the extension point when the configuration page exits.
Preventing the Closing of Configuration Page
It can be achieved by returning the false value after executing the script. We provide the static method called getConfigItemError(final SalesItemNode currentSalesItemNode) inside the groovyCtxUtil class to check if all mandatory attributes inside the Configuration Page were assigned. Returning true means, there are some mandatory attributes that have not been assigned thus configuration page is having an error.
boolean configItemError= groovyCtxUtil.getConfigItemError(currentSalesItemNode);
// Having configItemError true means that some of the mandatory field have not been assigned value.
if(configItemError) {
// Returning false will let UI know to prevent user from leaving the configuration page.
return false;
}else{
// Return true means that the configuration page is free to leave
Return true;
}
However, users can also implement different conditions that define the completeness of the configuration page either based on some data point inside Quote or some data returning by retrieving from the lookup table.
BOM Explosion via Groovy Script on Configuration Exits
Since the groovy script will be triggered if the user made some changes to the Configuration Page, the user can implement the groovy script that can add products to the quote.
import java.util.Set;
import com.google.common.collect.Lists
import com.google.common.collect.Sets;
import com.imc.model.Product;
import com.imc.notification.model.Warning;
import com.imc.vocabulary.SchemaExt;
import com.imc.vocabulary.ApplicationExt;
import com.imc.datamodel.BusinessRelationAttribute;
import com.imc.datamodel.transferobjects.BusinessRelationAttributeDTO;
import com.imc.iss.groovy.salesitem.SalesItemsTree;
import com.imc.iss.groovy.salesitem.SalesItemNode;
import com.imc.iss.groovy.lookupTable.GroovyLookupTable;
def customProductBOMTable = dbLookupTable.table('CUSTOM_PRODUCT_BOM');
boolean configItemError= groovyCtxUtil.getConfigItemError(currentSalesItemNode);
groovyLogger.logError("Config Item has error : {}",configItemError);
if(configItemError) {
// Removing the exploded line item
def bomResults= customProductBOMTable.search();
if(bomResults!=null) {
List<SalesItemNode> children= Lists.newArrayList(currentSalesItemNode.getChildren());
for(row in bomResults) {
groovyLogger.logError("Records found : {} : {}",row["SOURCE_PRODUCT_NAME"],row["DEST_PRODUCT_NAME"]);
}
Iterator<SalesItemNode> iterator = children.iterator();
while(iterator.hasNext()) {
groovyLogger.logError("Deleting item...");
iterator.next().delete();
}
}
// Return the result to MSP
return false;
}else {
def bomResults= customProductBOMTable.search();
if(bomResults!=null) {
groovyLogger.logError("Records found : {}",bomResults.size());
for(row in bomResults)
{
groovyLogger.logError("Records found : {} : {}",row["SOURCE_PRODUCT_NAME"],row["DEST_PRODUCT_NAME"]);
SalesItemNode LABOURCOSTUS = currentSalesItemNode.addChildByProductErpId(row["DEST_PRODUCT_NAME"]);
}
}
return true;
}
Configurable Product Replacement
In order to support the configurable product replacement, we introduces the new binding variable called preSubmitCheckEngine which provides the following methods.
- getClassifiedProductsFromConfiguration
- Arguments
- List<SalesItemNode> - list of sales item nodes to replace with classified products
- Returns
- Set<Product> - set of classified products that matched the configuration
- Arguments
- replaceConfigurableProduct
- Arguments
- SalesItemNode– sales item node to replace the product
- Product– product to replace the product of specified sales item node
- Return
- No return will be returned by the method.
- Arguments
- execute()
- Which is a default method to replace the configurable product with the classified product. If the system found one classified product, it will replace it automatically and with more than one classified product, it will notify the users. However, using this method cannot support skipping the auto replacement for a certain product. Thus it is recommended to use the above two methods to do the customization.
import java.util.Set;
import com.imc.model.Product;
import com.imc.vocabulary.SchemaExt;
import com.imc.vocabulary.ApplicationExt;
import com.imc.notification.model.Warning;
import com.imc.context.IMCException
import com.imc.datamodel.BusinessRelationAttribute;
import com.imc.datamodel.transferobjects.BusinessRelationAttributeDTO;
import com.google.common.collect.Lists
import com.google.common.collect.Sets;
import com.imc.iss.groovy.salesitem.SalesItemsTree;
import com.imc.iss.groovy.salesitem.SalesItemNode;
import com.imc.iss.groovy.lookupTable.GroovyLookupTable;
import com.imc.iss.groovy.salesitem.PreSubmitCheckEngine;
def customProductBOMTable = dbLookupTable.table('CUSTOM_PRODUCT_BOM');
boolean configItemError= groovyCtxUtil.getConfigItemError(currentSalesItemNode);
groovyLogger.logError("Config Item has error : {}",configItemError);
if(configItemError) {
// Return false if the intention is to stick the configuration, otherwise, true to allow user to leave configuration page tab
return false;
}else {
List<SalesItemNode> salesItemNodes = Lists.newArrayList(currentSalesItemNode);
Map<SalesItemNode,Set<Product>> salesItemToClassifiedProducts= preSubmitCheckEngine.getClassifiedProductsFromConfiguration(salesItemNodes);
for(SalesItemNode salesItemNode : salesItemNodes) {
Set<Product> products = salesItemToClassifiedProducts.get(salesItemNode);
if(!products.isEmpty()){
if(products.size()==1){
// Do the replacement
preSubmitCheckEngine.replaceConfigurableProduct(currentSalesItemNode,products.iterator().next());
}else{
// Notify warning
List<Warning> warnings = Lists.newArrayList();
for(Product product : products) {
warnings.add(new Warning("Product Name : " + product.getObjectName()));
}
String salesItemPosition= salesItemNode.getSalesItemDataAttributeValue("http://www.inmindcloud.com/application/schema.owl#salesItemPosition");
String salesItemName = salesItemNode.getSalesItemDataAttributeValue("http://www.inmindcloud.com/application/schema.owl#objectName");
groovyLogger.notifyWarning("More than one products found : " + salesItemPosition + " : " + salesItemName,warnings);
}
}
}
return true;
}
FAQ
Is it possible to execute the Groovy script from the IMC script?
No, it is not possible, and it will stay the same way for release up to 2019 end.
Is it possible to execute the Groovy script automatically (based on certain conditions)?
Groovy scripts are called to enhance the process at specific extension endpoints. These endpoints are predefined as they affect the general process. The endpoint also tends to modify the system process as per custom needs. Some of these endpoints are setting enabled, rest auto-enabled if the endpoint script is maintained and a few manually triggered based on user selection. Apart from these the groovy endpoints do not intend to get triggered based on any other condition.
Do you have an IDE for developing the scripts?
In terms of having an IDE with full auto-completion support, no. However, Eclipse IDE in conjunction with some SAP plugins will provide the full debugging capability. Please refer to section 5 Debugging Groovy Scripts for detailed information on how to do this.
Comments
0 comments
Article is closed for comments.