Preventing fields from Editing Once Record is in Workflow

Preventing fields from Editing Once Record is in Workflow

I like this problem because it involves a few components to make it work.

The business problem that is being solved here is if the user would like a field or fields to not be editable once the work order, in this case, enters workflow.  This example focuses on the WORKTYPE field, but this script could easily be expanded to include other fields.

The components you will need for a successful script are:

  • A list of fields that will be made read only once record is in workflow.
  • A list of viable statuses for the workflow to know when the records should be evaluated.
  • A SQL query to determine if record is on workflow.
  • Setting fields to be read only if evaluation is true.

Creating a list of fields to be made read only

You can create a list of fields to be read only by using an array.  Here I only have one field but using an array gives you the flexibility to quickly add additional fields.

#List of fields that will be read only
readOnlyFields = ['WORKTYPE']
#Example with additional fields
readOnlyFields = ['WORKTYPE', 'DESCRIPTION']

Obtain list of viable workflow statuses

In this scenario we care about records that are actively in workflow.  So we get a list of statuses using the workflow status domain.

# Get a list of valid workflow assignment statuses
statusList = mbo.getTranslator().toExternalList("WFASGNSTATUS", "ACTIVE")

Query to determine if record Is actively in workflow

Construct a query to determine if the record is in workflow.

# Construct a where clause to find the existing workflow assignment 
# for this work order. Ensure that one exists before proceeding. 
whereClause = SqlFormat(mbo.getUserInfo(), "ownertable = :1 and ownerid = " + woId + " and assignstatus in (" + statusList + ")")
whereClause.setObject(1, "WFASSIGNMENT", "OWNERTABLE", "WORKORDER")

Setting field(s) flag to read only

If it is determined that the record is in workflow then we set the fields listed in the array, readOnlyFields to read only.

if not wfAssignmentSet.isEmpty():
    logger.error("WF Assignments assignments for this work order")
    # Make fields read only if the work order is in workflow
 	# There will be more read only fields.  Fields can also be made
    # required using the same principle.
 	mbo.setFieldFlag(readOnlyFields, MboConstants.READONLY, True)

Steps to create automation script

Create a new automation script with Object Launch Point. We called it WOPREVEDIT in this example.

Create a launch point with Initialize Value selected.

Here is the full automation script.

#Make fields read only if record is in workflow
#Launch Point Type: OBJECT
#Launch Point Object: WORKORDER
#Language: Python
#Author: A3J Group
from psdi.mbo import SqlFormat
from psdi.server import MXServer
from psdi.util.logging import MXLoggerFactory
from psdi.mbo import MboConstants
logger = MXLoggerFactory.getLogger("maximo.script.woinit")
# Get the associated work order
wo = mbo.getString("WONUM")
woId = mbo.getLong("WORKORDERID")
#woId = str(woId)
#woId = woId.replace(',','')
#Logging"Value for woId is " + str(woId))
#List of fields that will be read only. Add additional fields with a comma.
readOnlyFields = ['WORKTYPE']
# Get a list of valid workflow assignment statuses
statusList = mbo.getTranslator().toExternalList("WFASGNSTATUS", "ACTIVE")
# Construct a where clause to find the existing workflow assignment 
# for this work order. Ensure that one exists before proceeding. 
whereClause = SqlFormat(mbo.getUserInfo(), "ownertable = :1 and ownerid = " + str(woId) + " and assignstatus in (" + statusList + ")")
whereClause.setObject(1, "WFASSIGNMENT", "OWNERTABLE", "WORKORDER")
# Get record set and filter it by the where clause
wfAssignmentSet = mbo.getMboSet("$WOAPPR", "WFASSIGNMENT", whereClause.format())
if not wfAssignmentSet.isEmpty():"Found WF Assignments for this WO – settings fields readonly")
 	# Make fields read only if the work order is in workflow
 	# There will be more read only fields.  Fields can also be made
    # required using the same principle.
 	mbo.setFieldFlag(readOnlyFields, MboConstants.READONLY, True)
if mbo.getThisMboSet().getParentApp() == "WOTRACK":
 	woOwner = mbo.getOwner()


One of our missions A3J Group is to share knowledge whenever we can. We hope this helps you in your day!

Automations with Selenium IDE


While automations themselves were developed to mitigate repetitive and time consuming tasks, ironically developing them faces the same challenges. The Selenium IDE web browser plugin, however, makes the process of developing automations significantly easier. The tool simplifies the process of automating tasks in a do it once, repeat forever format. In this fashion Selenium IDE reduces much of the stress and hassle associated with developing automations, providing a user-friendly interface for creating and executing test scripts.

Every element on a webpage has a unique identifier, which is necessary for directing mouse and keyboard events. These identifiers can range from simple, short strings to complex and lengthy strings. In the case of Maximo, the identifiers are typically the later, making manual testing and automation a challenging task. However, with the Selenium IDE, this process is streamlined and simplified.

With the Selenium IDE running, you can provide the application with a start page, such as the Maximo login screen. Then, press the record button and begin performing all the steps required to complete the desired process. The tool will capture all your actions, including mouse and keyboard events, and record them in detail with multiple identifiers for each element. The recorded actions can then be replayed either in Selenium IDE directly or you can use the easy-to-read notation to write your code.

In addition to its automation capabilities, the Selenium IDE also provides a flexible and scalable platform for writing and executing test scripts. The tool supports multiple programming languages, including Java, Python, C#, and Ruby, making it easy to integrate with other development tools and technologies. This allows developers to take advantage of the power of the Selenium IDE, even if they already have existing development tools and processes in place.

In conclusion, the Selenium IDE is a powerful and user-friendly tool that makes the process of developing automations easier, faster, and more efficient. With its ability to automate repetitive tasks, record and replay actions, and support multiple programming languages, the Selenium IDE provides a flexible and scalable platform for automating software testing and development tasks. Whether you are a software developer, tester, or other professional involved in software development, the Selenium IDE is a tool that you should consider using to streamline your work and improve your productivity.

Using a Global System Properties in an Automation Script

Maximo has a neat feature where you can set any value in the global property, and it will default to that value you have entered. In order to use a global property, you first must create one. In our example we created a global property for the person who does the PO Reorder.

After you create the global property, ensure you click on Live Refresh, so you can use the new global property created.

This is helpful because I could now use this global property in my automation script.  In order to get the global property, First must create a variable like down below. “configData = MXServer.getMXServer().getSystemProperties()”

After that create another variable and get the property you have created.

“reorderAgent = configData.getProperty(“POReorderBuyer”)”

Once you completed these steps you can now use the variable “reorderAgent” which is grabbing the system property and you could use in the automation script any where you want. In our case we are setting the “PURCHASEAGENT” to the “reorderAgent” when the po description has reorder in it.

I highly recommend using this as it is helpful because now you don’t have to create a variable with the value hardcoded. So, example if this person ever goes on vacation, we could simply just adjust the global property value and this automation script will adjust the reorderAgent based on the new person we put. Instead of going into the automation script and changing the variable that was hardcoded.

We have a culture of education and paying it forward at A3J Group. Subscribe below to be the first to get the latest releases from our knowledge bank.

Product Launch: Ninja Fix – Duplicate Service Request Identifier

So you have an A-team that is super proactive about reporting issues and creating service requests, right? Great! However, this often can lead to scenarios where multiple users submit service requests about the same issue.

The Ninja Fix Duplicate Service Request Identifier combats that issue. This product adds a table to the Service Request application in Maximo. The table will show potential duplicate work order records created from SRs for the same asset and/or location. Only service requests that are open and not canceled, closed or completed will be displayed.

If the new SR is a duplicate, the user that created the SR can choose to link the duplicate SR to the work.


A call comes in from a user complaining that a room is too cold. The representative that takes the call creates a service request record, creates a work order for work to be performed and routes the work order to a technician. While the technician is on route to the location another call comes in from a different user for the same location. An entry will appear in the Potential Duplicate Work Orders table alerting the user that there may already be a work order dispatched to fix the problem at the location. This development can eliminate duplicate work and help your team stay focused.

If you find this solution valuable to your facility, you are able to purchase and download the installer which will automatically configure your Maximo Environment to include this helpful feature. Click here to purchase.

A3J Group Launches New Ninja Fix Solution: Approval Summary Tab Creator

A3J Group continues to produce products that can be purchased through our Ninja Fix suite of self-service IBM Maximo configuration options. The Approval Summary Tab solution was released March of 2022 and acts as a one-stop shop for viewing specific records in IBM Maximo.

The A3J Approval Summary solution introduces a new tab, Approval Summary, to the Work Order Tracking, Purchase Requisition, Purchase Order and Invoice applications. The Approval Summary tab is designed to show both the Active Assignee, to whom the record is currently assigned, and a history of approvals. At a glance you will know immediately who is responsible for approving the record as well as where the record is at in the approval chain. Instead of having users navigate the workflow history and assignment applications to determine the approval status for a record you can simply point them to the Approval Summary tab for all the pertinent information.

The Approval Summary solution consists of a tab with two tables. The first table displays the current active assignee(s) for the record.


This table contains the user ID and display name of the active assignee(s). In addition, a description of the assignment is included for easy reference.

The second table lists the approval history of the record. The user can see who previously approved the record, any memo that was included with approval of the record as well as some workflow details.


The Approval Summary tab focuses only on workflow transactions originating from an Input node and that are either assigned or reassigned in order to reduce clutter and help the user focus solely on the assignment. Additional nodes may be included by simply updating the A3JWFTRANSACTION relationship on the parent object. You can also contact A3J Group for assistance with expanding the Approval Summary design to other use cases.




Learn more about the Approval Summary Tab creator and other solutions from Ninja Fix here. If the solution is a fit, purchase, download and experience immediate IBM Maximo enhancements from A3J Group today!

MBOs in Automation Scripts: Long Descriptions

Maximo provides a facility whereby unlimited text may be associated with an attribute. This text is stored in an additional table called the LONGDESCRIPTION table. The relationship between the long description and the associated object isn’t straightforward. Fortunately, the MBOs provide a facility to hide this complexity and make it easy to set and get such special attributes.

Certain attributes of certain objects have been designated as the owners of long descriptions. The ISLDOWNER column in the MAXATTRIBUTE table indicates whether an attribute can have an associated long description. In such cases, the effect is that of a new attribute being added to the object, an attribute whose name is attributename_longdescription. For example, in the WO object there is an attribute called “description”. This is an owner of a long description, so an attribute called description_longdescription is available for use. The setValue() and getString() methods, among others, may be used to alter or retrieve the value of the attribute. It is not currently possible to use the QBE mechanism on a long description.

mbo.setValue("description_ longdescription", "This sentence is a long description.") logger.debug(mbo.getString("description_ longdescription"))

It should be noted that in performance terms it is more expensive to retrieve a long description than a normal attribute.

MBOs in Automation Scripts: Query by Example

As mentioned our Working with MBO Collections article, it is possible to call the setWhere() method to restrict the members of the collection. There is an alternative method for those folks that might be uncomfortable writing SQL clauses. This feature is called Query By Example or QBE. To access it, use the setQbe() method as shown in the following example:

mboSet.setQbe("wonum", "1%")

This applies a restriction such that for each member of the resulting work order collection the WONUM attribute will begin with the character “1”. As discussed earlier, such a filter is effective only when the collection is first retrieved, so it may be necessary to call the reset() method.

The above example is very simple, but it is possible to make successive calls to setQbe() on different attributes – the result is the concatenating together of the set of restrictions with AND logical connectors. A second version of setQbe() concatenates the restrictions together with OR logical connectors – in this version the arguments are passed in a slightly different format. It is not possible to set more than one restriction on a given attribute, nor is it possible to perform a QBE on an attribute of a related object rather than on attributes of the objects in the collection themselves. Thus, for example, selecting work orders based on whether the Asset is rotating would not work, because the attribute in question is a member of the Asset object, not the WO object. To fulfill the request would require accessing related objects of a type different from the ones that make up the collection. Such actions are not permitted. If this is too restrictive, use the setWhere() method.

To clear the contents of the entire QBE, call the resetQbe() method.

It is important to understand the relationship between setWhere() and setQbe(). Internally, there is a complete where clause that is used for fetching records to the collection. The complete where clause is built by calls to both setWhere() and setQbe(). New restrictions set by calling setQbe() are AND’ed to existing restrictions that were set using setWhere(), and vice-versa. The resetQbe() method clears only those restrictions set by setQbe() calls. The setWhere() method can clear restrictions by passing an empty string:


This call, however, clears only those restrictions set by a setWhere() call. Thus, if it is necessary to start with an totally empty where clause, both setWhere(“”) and resetQbe() must be invoked.

At first glance it may not be obvious why one should use the setQbe() method and not setWhere(). The setQbe() method hides many of the database sensitivities from the programmer. It places literals in the quote characters as necessary and adds any appropriate database function calls to support querying on date, time and timestamp fields. Case insensitive queries can also be handled more easily using this mechanism. The programmer has better control over the treatment of a search string because two methods have been provided for this purpose: setQbeExactMatch() and setQbeCaseSensitive().

woSet.setQbeExactMatch(True) woSet.setQbeCaseSensitive(True)

MBOs in Automation Scripts: Object Relationships

In our previous posts in this series, we talked about how an MboSet is a collection of Mbo objects. This is analogous to a spreadsheet of data representing an MboSet, and a single row within that spreadsheet representing an Mbo. This article will discuss how to work with records related to an Mbo or MboSet.

All examples so far in this series have dealt with individual objects. It is possible to use an object as a starting point for traversing to other objects that are related in some way to retrieve, for example, the planned labor or the asset associated with a work order. There are two possible approaches to achieve this. The more general one is to use the getMboSet() method of a Mbo as shown in the following examples.

# Get the planned labor objects associated with the work order

wpLaborSet = mbo.getMboSet("WPLABOR")

wpLaborCount = wpLaborSet.count()

# Get the associated asset along with some of its data

assetList = mbo.getMboSet("ASSET")

asset = assetList.moveFirst()

serialNumber = asset.getString("SERIALNUM")

The mbo.getMboSet(“WPLABOR”) method call above returns a collection of WPLabor objects, specifically, those labor requirements associated with the first work order on the current work order Mbo. There is a 1:N relationship between work orders and Work Plan Labor objects as a given work order may have multiple labor requirements associated with it. Thus, the wpLaborSet may have from 1 to N members. Note that wpLaborSet is a standard MboSet object, containing all the standard member methods, any of which may be called in the standard way.

In some cases, related objects are in a 1:1 relationship such as work order to asset (mostly… except in multi-asset cases). A given work order only has one asset associated with it at a time in most cases. Thus, when getMboSet(“ASSET”) is called on a work order, the returned MboSet will contains a single object if the ASSETNUM field is populated on the work order.

A list of objects with relationships to other objects (including the relationship name, which is the argument to be passed to the getMboSet() method, the type of objects in the returned MboSet, and the nature of the numerical relationship, i.e. 1:N vs. 1:1) is provided in the Relationships tab of the Database Configuration application in Maximo.

The second approach to working with associated objects amounts to a shortcut which can be used when only the data value of some attribute of a related object is needed. This approach is referred to as “dot notation” and it allows the retrieval of an object’s data without retrieving the object itself. It is most convenient in cases where the relationship is of the 1:1 type. While it does work for 1:N relationships, its usefulness is somewhat restricted.

The following code illustrates how the dot notation is used:

woSet = mbo.getMboSet("WORKORDER")

wo = woSet.getMbo(0)

serialNumber = wo.getString("ASSET.SERIALNUM")

assetLongDescription = wo.getString("ASSET.DESCRIPTION_LONGDESCRIPTION")

problemDescription = wo.getString("PROBLEM.DESCRIPTION")

Dot notation is therefore a call to a WO’s (or, more generally, a Mbo’s) getString() method with a special argument. The argument has two parts, separated by the dot. The first is the relationship name; the second is the name of the associated object’s attribute whose value is being retrieved. Please note that relationships can also be chained together using more than two parts (e.g. “LABOR.PERSON.DISPLAYNAME”), however the majority of cases will be two strings separated by a dot. It’s also important to note that the relationship name is just that; oftentimes Maximo uses the object name itself as the relationship name, but in some cases the relationship name is different than the object name. This is especially true where one object relates to another object via multiple fields (e.g. CHANGEBY and REPORTEDBY both refer to the PERSON object, therefore two different relationship names are needed to distinguish the PERSON.DISPLAYNAME field as the values may be different).

The first part in the above example retrieves a reference to a specific WO object. The next three lines call its getString() method with different examples of the dot notation. The call to wo.getString(“ASSET.SERIALNUM”) retrieves the value of the serialNum attribute for the associated Asset object. This line achieves the same result as in the general example at the start of this article but without accessing the Asset object itself at all.

The call to wo.getString(“ASSET.DESCRIPTION_LONGDESCRIPTION”) illustrates how the dot notation also works for more unusual attributes like long descriptions.

The call to wo.getString(“PROBLEM.DESCRIPTION”) is a somewhat more complicated example. The relationship name is “problem”, but the type of associated object being accessed is not a hypothetical “Problem” object (which in fact does not exist) but rather a Failure object. Thus, the “description” attribute being specified is actually the “description” attribute of a Failure object (specifically, that Failure object specified by the value in the WO object’s “problemcode” attribute). This example illustrates how the dot notation can be used even when the relationship name is different from the name of the associated object.

If the dot notation is used when the relationship is 1:N then the data value of the first object’s attribute is returned. This can be a serious limitation since there is no guarantee as to which associated object is the first one. The only way to find out is to check explicitly.

If the dot notation is used in a case where there is no associated object, e.g. if the “ASSET.SERIALNUM” is requested for a work order which has no Asset associated with it, an MxApplicationException is thrown.

MBOs in Automation Scripts: Changing Status

In our previous posts in this series, we talked about how an MboSet is a collection of Mbo objects. This is analogous to a spreadsheet of data representing an MboSet, and a single row within that spreadsheet representing an Mbo. This article will discuss how to change the status of an Mbo that is stateful.

There are a number of objects that are able to perform status changes. These status changes control the flow of the object through a lifecycle or business process. For example, a work order flows from Waiting Approval to Approved to In Progress to Completed to Closed. Each one on these stages is a status in the application’s terms. As a result of transitioning from one status to another, certain business rules may be invoked that update other parts of the system. As an example, when a work order is approved, all parts required for that work order are reserved in Inventory.

Objects that have the ability to change state implement the StatefulMboRemote interface.

To change the status of an object, the changeStatus() method is called through the Mbo. For example:

# Change the status as of the given date
from psdi.server import MXServer
mbo.changeStatus("APPR", MXServer.getMXServer().getDate(), "A reason or memo")

The MXServer.getMXServer().getDate() variable in the changeStatus() call above is a Java Date and represents the date at which the newly set status is to take effect. The third argument is a string variable which allows the user to include a short note about this particular status change. The memo argument is optional – a null value may be passed. If an error occurs at any point, an MXException is thrown.

The caller does not need to understand what business rules are invoked within the object – the goal is to hide such details and make it easy to perform the status change operation.

Depending on the business rules coded into the object, it may not be possible to perform certain status changes. For example, it is not possible to move from CLOSE to WAPPR.

A list of available statuses for an object can be retrieved by calling the getStatusList() method.

MBOs in Automation Scripts: Attributes with Value Lists

In our previous posts in this series, we talked about how an MboSet is a collection of Mbo objects. This is analogous to a spreadsheet of data representing an MboSet, and a single row within that spreadsheet representing an Mbo. This article will discuss how to work with attributes on an Mbo that have an associated value list.

A given attribute of an object may have a list of valid values associated with it. This is typically used to display a drop down or value list to the user from which a specific value may be selected. A method called getList() has been provided to allow retrieval of this kind of list for those attributes that support one. The method returns a MboSet object from which values can be extracted for display to the user.

Suppose the desired result is a list of valid asset identifiers for the “assetnum” attribute on a work order object. The following code could be used to retrieve this information:

assetList = mbo.getList("assetnum")

The call to the getList() method with the attribute name “assetnum” as its argument returns a MboSet object with an MboSet of valid asset objects for this work order. The list of valid values for the work order object’s “assetnum” attribute can be built by extracting the “assetnum” value from each of the objects in the assetList variable. This would be accomplished via standard mbo.getString(“assetnum”) calls on each of the objects in the list. Note that assetList is a fully functional collection object, any of whose member methods may be called in the usual way. For example, it is possible to add new members to the collection or modify member attributes.

If the specified attribute passed to getList(attribute) does not support a value list, a null is returned. To determine whether a given attribute does support a list, the hasList(attribute) method in the MboValueData class should be called – see the previous article on Reading Multiple Attributes of an Object in the Collection.

The getList(attribute) method is available for both MboSets and Mbo classes. Even in an empty collection object the getList(attribute) method can be called successfully, with some exceptions. For example, whether or not the “problemcode” attribute has a value list depends on whether a value has been selected in the “failurecode” attribute of the given work order. Thus, if the getList(attribute) method is being accessed through a MboSet object, in the case of the “problemcode” it does matter what the current object is.