Use a Button to launch an action script

A very common usage of automation scripting is to develop the action logic when a button is pressed in an application. The concept I will describe in this blog is principal the same for the following three scenarios:

  • A button included in the application with the application designer (I will show this in this blog)
  • A Toolbar Menu entry (Icon Toolbar)
  • A Action Menu entry

So what are the steps to create a button and run the script on it?

  • Create a new Signature Option and assign it to the appropriate user group.
  • Create the new Button, Toolbar Menu or Action Menu in Application Designer.
  • Create a new Automation Script with an Action launchpoint.

I would like to take a demo scenario with very simple scripting so we can concentrate mainly on the other stuff. In the Item Master Application when a new item is created the Commodity Group and Commodity Code has to be selected for each item. Let’s say you are mostly entering new Laptop items and you would like to get a “Default Commodity” button to fill in these two fields with default values like this:

Selection_028

That’s basically all, so lets start.

Create a new Signature Option and assign it to the appropriate user group

To create a new Signature Option we need to go the the Application Designer:

  • Go To > System Configuration > Platform Configuration > Application Designer
  • Select the application ITEM (Item Master) in the List View.
  • Now you go the “Select Action” menu and select the “Add/Modify Signature Options” entry.

Menu_030

  • In the dialog which opens hit the “New Row” button to add a new Signature Option.
  • Create a new Option with a name and a comment like follows:

Selection_031

And now the important part!!! Did you recognized the “Advanced Signature Options” section which is closed by default? Open it and check the following point:

Selection_032

If you forget to check this entry your button will never work and your script will never run.

  • Click on OK to close the dialog.
  • Assign the new Signature Option to a Security Group where your user is in (e.G. MAXADMIN for testing). You will find the new signature Option in the ITEM Application section:Selection_033
  • Don’t forget to logoff / logon to the Server before you proceed.

Create the new Button in Application Designer

After a new logon proceed in the Application Designer:

 

  • Go To > System Configuration > Platform Configuration > Application Designer
  • Select the application ITEM (Item Master) in the List View.
  • In the Application design screen select the “Item” Tab from the application.
    Selection_035
  • Open the Control Palette dialog in the Toolbar: Selection_034
  • From the Control Palette Drag and Drop the “Button Group” symbol to the “Commodity Group” Text. The button group should occur above the Commodity Group.
  • Right Click on the “pushbutton…” and select the properties Menu
  • Enter the following Values:
    • Label: “Default Commodity”
    • Event: “DEFCOMM” (This is the Name of the Action we will define later)
    • Signature Option (on page two): DEFCOMM (the one you created before)
      Selection_036

That is basically all you need to do in Application Designer. Your Button should now look as follows:

Selection_037

Save your work.

 

Create and test the automation Script

The new script will be created in:

Go To > System Configuration > Plattform Configuration > Automation Scripts

In the Select “Select Action” box select “Create > Script with Action Launchpoint”.

Menu_038

Fill in the dialog as follows and press “Next” when done:

Selection_039

Interesting here is the Object field. This field is the MBO Object your are in and which your script will get as the implicit “mbo” Variable.

In the next dialog we need to fill in some more values. It is always good practise to name the action, launchpoint and script the same (unless you will have multiple launchpoints with different meanings for a script).

Selection_040

Press the “Next” button when filled in.

In the last dialog you will have to paste in the following script code:

itemMbo = mbo  # @UndefinedVariable
itemMbo.setValue("COMMODITYGROUP", "43211500")
itemMbo.setValue("COMMODITY", "43211503")

Press “Create” to create the automation script. The Action object will be automatically created in background. Don’t forget this when you need to transport your development to another system.

Test the new solution

To test your new button:

Go To > Inventory > Item Master

Create a new Item and press the new Button. The Commodity Group and Code should be automatically be filled in. Congratulations!

Selection_041

 

Deleting Mbo’s

So far we know how to insert, update and view our Mbo records, so the next step is to delete a record.

You delete Mbo’s by calling the delete() method. This method removes the current Mbo from an MboSet. Basically the Mbo is not delete when you call the delete method it is more over marked for deletion. The real delete operation is executed when the transaction is commit to the database. In general this is the save operation on the MboSet.

Here comes an example which deletes a specifc Workorder from the Workorder MboSet:

    # Example: Delete a specific Mbo
    woset = session.getMboSet('WORKORDER')
    woset.setWhere("WONUM = '2009'")
    woset.reset()
    wo = woset.moveFirst()
    
    if wo is not None:   
        # Mark Mbo for deletion
        wo.delete()

        # Real delete is done at this point
        woset.save()

Using field Flags to set attribute content

Each attribute in Maximo / ICD can have several Validation Java classes and Jython scripts associated which will trigger when a new value for an attribute is set. Field flags can control some of the conditions under which the attributes can be modified and prevent execution of that classes / scripts.

The following field Flags are commonly used:

  • NOACCESSCHECK is used to update the value even if the attribute is marked readonly
  • NOVALIDATION supressess checking of the value. Be sure you know what you do, because you will prevent the business logic from checking for a valid value.
  • DELAYVALIDATION does not perform any validation when the attribute is modified. The validation takes place on the save action. This can be useful if the validity of an attribute depends on other attributes.
  • NOACTION is used to bypass the execution of all business rules. Use it carefully!
  • NOVALIDATION_AND_NOACTION is a combination of the NOVALIDATION and NOACTION flag.

Now how do we use these flags in Jython? They are used in the setValue() Method as a third parameter. So sometimes you will see scripts like this:

itemMbo.setValue(description", "Hello World!", 1L)

In this 1L stands for NOVALIDATION and it is bad practise to write it in a numeric format. How about this style?

from psdi.mbo import MboConstants
itemMbo.setValue("description", "Hello World!", MboConstants.NOVALIDATION)
companyMbo.setValue("company", "COM1", MboConstants.NOVALIDATION_AND_NOACTION)

If multiple field flags should be combined for a field they could be combined by a bitwise or operation. See this example:

from psdi.mbo import MboConstants
itemMbo.setValue("description", "Hello World!", MboConstants.NOVALIDATION | MboConstants.NOACCESSCHECK)

So far we always used the setValue method to directly manipulate the field flag while we are setting a value to a field. In some cases it is also useful to just set the field flag without applying a new value. This can be done with the setFieldFlag method. You can set and remove certain flags from a field. Mostly taken is this for the read only state:

from psdi.mbo import MboConstants
itemMbo.setFieldFlag("description", MboConstants.READONLY,False)
itemMbo.setValue("description", "Hello World!")
itemMbo.setFieldFlag("description", MboConstants.READONLY,True)

The setFieldFlag method can also take a list of fields as a parameter, if you would like to modify a bunch of fields:

from psdi.mbo import MboConstants
fields = ['DESCRIPTION','ITEMTYPE', 'ORDERUNIT']
itemMbo.setFieldFlag(fields, MboConstants.READONLY,False)

Implicit launchpoint Variables

Implicit launchpoint variables are predefined if a script runs in the context of ICD (not if you are using the RMI Interface).

Overview of Implicit Variables

The following list describes the most commonly used variables:

Input Variables (incoming to the script):

mbo – The current Mbo in the context of the script execution. This is an object.
mboname – The name of the current Mbo in context of the script as a string.
app – Name of the ICD application which initiated the script execution
user – Name of the user whose action initiated the script execution.
interactive – Is true if script is executed interactive via UI and false if executed in background (e.g. scheduler, integration framework).
scriptName – name of the script
launchPoint – name of the launch point
action – Only for action launchpoints the name of the action that executed the script.
onAdd – Boolean variable. Is True if the Mbo in the script is added and a new Mbo (not yet saved). Very useful for conditional launchpoints, but may also be useful for Object and Attribute launchpoints.
onUpdate – Boolean variable indicating that related Mbo is being updated.
onDelete – Boolean variable indicating that related Mbo is marked for deletion.
wfinstance – Object of type psdi.workflow.WFInstance that indicates the current workflow from which the action was started. This is only valid for action launchpoints if the action is launched from a workflow.

Output Variables (Script provides them back to ICD)

errorkey – For throwing MXExceptions from the script
errorgroup – For throwing MXExceptions from the script
params – For throwing MXExceptions from the script. This is a Array of Strings
evalresult – evaluation result of the Condition Launch point

Usage patterns

When using implicit variables in your script you should first care about the fact that the variables are nowhere defined and therefor eclipse will throw you an error on the first usage. Please have a look to the “Undefined Variables” Section in this articel to solve the issue.

Perform different actions based on the Launchpoint executing the script

A Jython script could have more than one launchpoint to start its execution. Sometimes the script needs to perform different kind of actions depending on the launchpoint. Another use case for this pattern is to assign different values to variables based on the launchpoint to omit the usage of launchpoint variables. A sample for this is shown in the article “Tips for using Eclipse with Jython” in the section “Prevent Using Script In-/Out Variables”

Execute script actions only if running from GUI

It is sometimes a requirement that a script behaves differently if it is called from a User GUI Interaction or from a system interaction like a crontask, escalation or an Integration Framework call. The following pattern can solve this issue:

interactive = interactive # @UndefinedVariable

if interactive == True:
# Things to do if script is running in user Context
else:
# Things to do if script is called by Crontask, MIF, ...
.

Execute different script actions depending on the ICD Application

Sometimes it is required that a script executes different paths depending on the Maximo application it is executed from. The code pattern is quit simple in that case:

app = app  # @UndefinedVariable

if app == "ASSET":
    # Do actions if called from asset Application
if app == "ITEM":
    # Do actions if called from item Application

Throwing an exception from a script

The usage of the errorkey/errorgroup/params variables is shown in this article.

Logging in Maximo Jython scripts

When you develop scripts with more than a couple of lines of code it is always useful to write some information to a log. Maximo has a very powerful logging engine based on the Apache log4j Java based logging utility.

In the first part of this Blog I will show you how the logging engine will be configured. In the second part we will discover the technics to utilize the logging system from our Jython scripts.

Preparation of Maximo Logging

The Logging configuration in Maximo can be found in the following menu:

Goto > System Configuration > Plattform Configuration > Logging

The first think we have to do is to create a new Appender. This is basically the definition of the logfile where the log is written to. You can either define a common customscript logfile or define different logs for larger scripts. From the Logging application select the Action „Manage Appenders“ and the following screen occurs:

logging1

By selecting „New Row“ you can add an additional Appender. Fill in the values as shown below and press OK at the end.

logging2

You are now back at the entry screen for logging. Go to the Filter bar an search for the „autoscript“ Root Logger entry.

logging3

As you can see there are no specific Loggers defined under this entry. You can now define your own Customscript Logger by pressing the „New Row“ button in the Loggers section. Fill in the following values:

logging4

Important is to select the „Appenders“ entry „customscript“. This is the additional appender for this Logger which will write to the customscript.log file.

To apply your new settings there is no save button. You have to select the Action „Apply Settings“ to enable the new Logger you just have defined.

Using the customscript Logger

To use the scripting engine you first have to initialize a new Logger. This can be done by the following statement:

myLogger = MXLoggerFactory.getLogger(„maximo.script.customscript")

After that the logger can be easily used:

myLogger.info(“<message>”) writes one line to the log with the selected message level. Only levels equals or higher then selected in the customscript logger are really written to the logfile.

A complete example looks like this:

from psdi.util.logging import MXLoggerFactory
myLogger = MXLoggerFactory.getLogger("maximo.script.customscript")
myLogger.debug(“This is a debug entry")
myLogger.info(“This is an info entry")
myLogger.warn(“This is a warning entry")
myLogger.error(“This is an error entry")
myLogger.fatal(“This is a fatal entry")

Read Maximo system properties in a Jython script

It is a good practise to store all properties like usernames, passwords, Url’s, etc in the Maximo System Properties. User defined properties can be defined in addition to all the tons of existing system properties. You will find the properties application under

System Configuration –> Plattform Configuration –> System Properties

To define a new property just click “New Row” in the Global Properties section:

properties1

The usage of these properties in a Jython script is quit easy. To read and print the new “custom.username” property the following script can be used:

from psdi.server import MXServer
configData = MXServer.getMXServer().getSystemProperties()
maxProperty = configData.getProperty("custom.username")
print maxProperty

properties2

When tested in the Maximo Script editor the script should print out the username “bigadmin”. If you get a no output at StdOut or “None” you should verify if you made a live refresh of the property data.

Selecting specific Mbo’s using Relationships and Where Clauses

A very common scenario is that you would like to get an instance of a MboSet with not all records included, but only a subset. Spoken in SQL you would like to apply a where clause to limit the number of records you get as a result in a MboSet. In this article I will show you different ways on how you can achieve this.

  1. Usage of relationships

A very common pattern is to just use relationships when you initialize your MboSet. I have shown such a pattern in this article. You can easily define the where clause directly in the relationship and you will only get the records based on the where clause.

  1. Adding a Where-Clause to an existing MboSet

It is quit handy to add a Where-Clause to an existing MboSet. The following code example will show this scenario:

woset = session.getMboSet('WORKORDER')
woset.setWhere("WONUM = '2009'")
woset.reset()
wo = woset.moveFirst()
if wo is not None:
    print "Workorder ",wo.getString("WONUM")

At the beginning you initialize a MboSet returning all Workorder’s in the system. In line 2 you append a Where-Clause to the result set. When you have used a relationship with an existing where clauses in it the new where clause will be appended. The woset.reset() method call in line 3 is required to “execute” the where clause and update the result set in woset. The rest of the script just shows the first record.

  1. Building a temporary relationship

This is one of my favorite patterns which can help you make life much easier. When you develop your script you will not always have the perfect relationship for navigation defined in the system. So you can either define a new relationship in database configuration or you can do this step directly from your Jython script. You define a new relationship just to be used temporarily in your script with no impact to the rest of the system.

The syntax based on the JavaDoc is:

public MboSetRemote getMboSet(java.lang.String name,
                              java.lang.String objectName,
                              java.lang.String relationship)
                    throws MXException, java.rmi.RemoteException

A real example statement to get all worklog entries for a specific workorder could look like this:

worklogset = wo.getMboSet("$TEMPREL1", "WORKLOG", "RECORDKEY='2009'")

$TEMPREL1 – An unique identifier for the relationship. It needs to be unique and for this reason it is good practice to start the name with a dollar sign.

WORKLOG – The database object which should be queried using any provided where clause.

RECORDKEY=’2009’ – The where clause to be applied to reset the MboSet of interest.

Adding Mbo records to a MboSet

In this blog I will show you in detail how to add new Mbo records to a MboSet.

The easy pattern

To do so we will directly start with a simple code example:

woset = session.getMboSet('WORKORDER')
wo = woset.add()
if wo is not None:
    wo.setValue("DESCRIPTION","New Testworkorder")
    wo.setValue("WOPRIORITY", 10)
 
    # Only save if necessary (e.g. Script executed via RMI)
    woset.save()

This is the simplest form of adding a record. At the beginning we need to have a MboSet object stored to the woset variable.
By using the add method woset.add() a new record will be added to an existing MboSet. The record is added at the beginning of the MboSet. If you would like to add the new record to the end of the existing set you can use the woset.addAtEnd() method.

Very important is line 3 to check if we really have created a new object instance. Without that check our wo.setValue(..) method calls would result in Null Pointer exceptions. The wo.setValue method is quite easy to use to set the individual fields in the MboSet. The good thing at this method is that it does not differentiate between datatypes.

The woset.save() method call is a bit special and need some explanation. Depending on the way you run your script you should or should not save the MboSet at the end:

  • In general no saving on object & attribute launchpoints
  • In general no saving in workflow actions
  • In general save in RMI Scripting, in direct invocation or when you act on a dialog button.

But as you can imagine there is no rule without exceptions and you eventually have to trial and error to find the correct usage.

Add Mbo records to multiple MboSet’s using transactions

In the previous example we just added a single record to a single Mbo. What if we wan’t to a add an additional Worklog entry to the new Workorder. What about transaction processing. We only want to create the Workorder together with the worklog entry or fail both. The following script shows such an example:

woset = session.getMboSet('WORKORDER')
wo = woset.add()
if wo is not None:
    wo.setValue("DESCRIPTION","New Testworkorder 3")
    wo.setValue("WOPRIORITY", 10)
 
    worklogset = wo.getMboSet("WORKLOGAPPT")
    worklog = worklogset.add()
    if worklog is not None:
        worklog.setValue("DESCRIPTION", "WLOG Descr.")
        woset.save()
        print "Workorder " + wo.getString("WONUM")  + " created !"

This example is quite similar to the first one. The difference is that we navigate from our newly created workorder Mbo to a Worklog MboSet via a predefined relationship. Using a relationship at this point automatically holds together the two MboSets via a transaction. There is nothing else via have to do to create this kind of dependency! Cool, isn’t it?

The next line to mention is the woset.save() statement. You could ask: “Why do we do not save the Workorder object?” My answer is: “Save the object you want! Since both MboSet objects are linked by a relationship it doesn’t matter which object you save. The result is the same!”

Navigate to different MboSet using Relationships

In this blog I will show you a way to navigate from a given Mbo to a different, related MboSet. The easiest way to perform such a navigation is to use a predefined relationship as it is defined in the database configuration of a table. For our example lets assume we are starting from the WORKORDER MboSet. We would like to print out all workorder activities for the first workorder we read.

The following script can be technical combined with the RMI introduction to get a quick result since it requires a valid session object.

woset = session.getMboSet('WORKORDER')
wo = woset.moveFirst();

if wo is not None:
    print "Workorder ",wo.getString("WONUM")    

    workActSet = wo.getMboSet("WOACTIVITY")
    workAct = workActSet.moveFirst()

    while (workAct != None):
        print "Workorder activity: ", workAct.getString("DESCRIPTION")
        workAct = workActSet.moveNext()

Some of the important commands in this example are:

  • if wo is not None: Very important! Check if we have found a workorder Mbo. If you do not do this check the next access like wo.getString(..) could lead to a null pointer exception.
  • worklogset = wo.getMboSet(“WORKLOGAPPT“) gets a reference to a different MboSet based on the current MBO in wo and the existing relationship “WORKLOGAPPT“
  • while (workAct != None) the loop construct to loop on all workorder actions. Have a look here to loop on MboSet’s.

Output fields from a Mbo with different data types

TPAE supports nearly twenty different data types for it’s fields which can be used during database configuration. Commonly used types are ALN, DATE, INT, UPPER, YORN just to mention some of them. A full list of available datatypes can be found on the support webpage.

When we read records using a script we have to be very carefully to always use the correct command which is related to the data type of the field. Some important commands are:

  • wo.getString(“<FIELD>”) – returns a String value
  • wo.getInt(“<FIELD>”) – returns a Integer value
  • wo.getLong(“<FIELD>”) – returns a BigInteger value
  • wo.getDouble(“<FIELD>” – returns a Double/Decimal value (better than getFloat()).
  • wo.getDate(“<FIELD>”) – returns a Date value
  • wo.getBoolean(“<FIELD>”) – returns Boolean Value from a YORN Field (True / False)
Attention: It is a common mistake to use the getString method to read numeric values which will work without errors. The issue you will fall into is that numbers are represented  with thousands separators so a number 1000 is represented as a string “1.000”.

In context of the workorder MboSet a code sample could be the following one, which reads different fields with different data types:

 woset = session.getMboSet('WORKORDER')
    wo = woset.moveFirst()

    while (wo != None):
        # Read out values
        print "Workorder ",wo.getString("WONUM")
        print "Priority ",wo.getInt("WOPRIORITY")
        print "StartDate ", wo.getDate("TARGSTARTDATE")
        print "WorkorderID ", wo.getLong("WORKORDERID")
        # get next Workorder
        wo = woset.moveNext()
1 2