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:

mboSet.setWhere("")

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.

What’s The Registry, Should I Clean It, And What’s The Point?

• Along with the cleaner, you also have a disk cleaner, a software uninstaller, search tool and even process manager. This cleaner too is quickly catching up on popularity owing to the features which it has to offer. It comes with some of the fastest registry scans along with the option of scheduling the scans as well. Not only this, you will also find a clear demarcation between normal issues and even the unsafe ones as well. • You can scan, repair and even automatically shutdown the registry after the errors that have been spotted have been fixed.

Users can scan the entire system or they can choose files and folders of their choice for cleaning. This software will eventually delete the unwanted junk files available in the physical memory of the computer. Also, backup of registry files can be created for future restoration.

  • By removing the invalid Windows registry your computer, you can run your computer more efficiently than before.
  • In this type of case, removing the registry entries would help.
  • Doing this, says the company, will also protect against loss api-ms-win-crt-runtime-l1-1-0.dll of data and information theft, among countless other potential problems.
  • It analyzes more than a dozen of different areas in your registry,and can identify up to 18 types of errors.

To do this you’ll need to create a new Administrator account (as you’ll want one on your system), then change your existing account to a Standard one. It’s not a good idea to use a Windows account with Administrator privileges. This simple modification can eradicate many of the threats out there, as malware, spyware, and the like will not be able to install itself. Windows Defender does now have ‘Exploit Guard’ which helps to protect your files against ransomware, but only if you’ve updated to the Creators Update .

An Analysis Of Dll Files Programs

Save your changes and shut down your system to test it out. While in S Mode, your PC can only install apps from the Store. This means you can only browse the web in Microsoft Edge—you can’t install Chrome or Firefox. You can’t even change Edge’s default search engine in S Mode, so you’re stuck with Bing—although you could set Google as your home page, if you wanted. Various developer tools, including command line shells like PowerShell and Bash, are also off limits. If you try running software that isn’t allowed, you’ll see a message explaining you’re only allowed to get software from the Store.

No-Fuss Dll Secrets For 2012

From my personal experience, I never had to restore the registry even once in my many years of using registry cleaners. I am sure you’ll be fine, too, as long as you have a backup to rely on. If your chosen registry cleaner doesn’t have this feature, or you don’t want to use it, you can also download a free registry backup tool like Regbak. It’s necessary to run a registry cleaner if you have remove malware in your PC or your antivirus app detects rogue programs.

The obvious changes are a new Start menu and the switch to the new Chromium-based Edge as the default browser. I turn off the Peer-to-Peer however I can see its usefulness in some cases as you can disable that and make it specifically for just internal systems. If you have a bandwidth limit having one system download the updates and other systems pull from it would be useful. Out of the box, Windows decides how large to make the text, icons and other widgets. A lot of the time, particularly on laptops, the operating system decides to operate at 150 percent or larger scaling, which makes it easier read but lets you fit less on the screen.

Mac, Windows, Chrome OS, Linux—every operating system is fundamentally the same, which is to say a very long way from the lines of typed commands that defined the earliest computers. Making changes in the registry should only be done if you know what you’re doing because simple mistakes can result in very serious problems. For that reason, I’m not going to offer a walk-through here. If you want to hold a loaded gun to your head instead of letting Microsoft do it, instructions for disabling updates in the registry can be found here. Disabling automatic updates on the Home edition of Windows 10.

Configuring Item Kits in IBM Maximo

One of our favorite features in IBM Maximo is Item Kits. Over the years A3J Group has worked with hundreds of companies across several industries but have only come across a handful using Item Kits. Item Kits are exactly what they sound like – kits of preassembled items that are often requested together from warehouses.

Let’s consider the following scenario in which a warehouse storeroom supports Fleet maintenance.

When scheduled maintenance is due on a pickup truck, the technician comes into the warehouse with a work order and a list of items they need:

  • Oil
  • Oil Filter
  • Gloves
  • Rag
  • Air Filter

A warehouse clerk visits 5 different inventory bins in different areas across the warehouse to collect the necessary items, multiple times a day. Think of the time saving significance of an item kit named ‘Pickup Truck Oil Change’. That item kit would include: the oil, oil filter, rags and gloves. Suddenly a trip with 5 stops becomes a trip with 2 stops: 1 for the ‘Kit’ of always needed items and 1 for the air filter, which may or may not be needed for an oil change. Of course the make-up of these item kits is contingent on grouping items that are usually consumed concurrently.

How to create an Item Kit in Item Master…

  • From the Item Master application click the New Item icon. Update the Item ID if needed and provide a description. Ideally, the word “Kit” should be part of the description. This indicates to users that there will be multiple items when searching.
  • From the Item tab populate all other fields that are needed like the Commodity Group and Issue Unit. Did you take notice of a checkbox for Kit? An error will be displayed if this flag is set at this stage.
  • Save the record and navigate to the Item Assembly Structure tab.
  • In the Children section click the New Row button. Next, start adding the Items that will be part of the Kit. Include all items to the item kit that seem logical. Then, be sure to update the Quantity and Remarks fields if necessary.
  • Save the record and navigate back to the Item tab.
  • Check the Kit? Make sure your checkbox is error free.
  • The final step is to update to an active status and add the Kit to a Storeroom.

If your Kit Item Master is created and added to an inventory storeroom, then it is time to assemble the item kits. Navigate to the Inventory application to begin assembling the item kits you desire.

How to Assemble a Kit in Maximo Inventory:

  1. Click into the Inventory application and open the item kit that needs to be assembled.
  2. In the More Actions, menu click the Assemble Kit option.
  3. The Possible Quantity field contains total number of item kits that can be created. This is based on the availability of items in your inventory storeroom.
  4. Enter a value in the Assemble Quantity field. This will indicate how many item kits will be assembled. Next, click OK. The number entered must be less than or equal to the Possible Quantity value.
  5. Now, the Current Balance of your kit increased while the items that make up the kit decreased in balance.

Maximo Kits allow for measurable time savings in the Asset Maintenance Lifecyle: during the stocking of a single item kit for a Job Plan, reducing the research needed when planning a CM Work Order or lowering return trips to the storeroom. The small upfront investment of time to set up an Item Kit will pay off in big ways immediately.

Securing Attachments in IBM Maximo

Need help securing attachments in IBM Maximo?

 

So you have secured your IBM Maximo installation using SSL, perhaps by following one of the secure articles in our A3J Group blog library, and are feeling pretty comfortable that your IBM Maximo users are protected. Just as you are about ready to lean back in your chair and enjoy the fruits of your labor you receive an urgent communication with a screenshot showing an IBM Maximo attachment being served up with the unsecured HTTP rather than HTTPS. Sound familiar? Thankfully you will be back to your feeling of tranquility in no time as this fix is a piece of cake.

*Note you could have been prevented the above scenario from occurring by not binding IBM Maximo to an unsecure port. This would not have allowed the attachment to work but would have prevented an unsecure connection.

Creating an Automation Script End Point in IBM Maximo

The IBM Maximo Integration Framework offers a wide variety of capabilities for publishing or consuming information to or from external systems. Some of the available methods for communication are:

  • Flat File Exchange
  • XML File Exchange with XSLT Mapping
  • Database Table Exchange (both internal and external to Maximo)
  • HTTP or SOAP Service Consumption

However, there may be times when the available end point handlers do not fit exactly what you need. What options do you have to customize the way the data is exchanged (method, format, content, etc.)?

1. Java customizations: This is old and tired. However, it is possible to write your own Router Handler class to deliver an outbound message to its destination. It is also possible to deliver the message using one of the available handlers above, and then write a custom IBM Maximo cron task to move the message to its destination. For me, that is too many moving parts.

2. Third-party solutions: Implementing a third-party middleware solution, such as Node Red or MuleSoft, can be an option for removing specific system differences when exchanging information between two systems. This is especially useful in large enterprises where many systems are exchanging data. This can allow for a single source of business logic to exchange information across a wide range of systems. For smaller integrations however, this can just add another layer of management and skills necessary to support the organization.

3. Automation Scripts: This is an easy, simple way to build your own end point handler in IBM Maximo without introducing unnecessary Java customization. Luckily, IBM provides a hook that allows us to do just that.

To create an Automation Script End Point in IBM Maximo, you will first need to register the Script Router Handler class. Please note that this class was introduced in the IBM Maximo 7.6.0.8 release. In the 7.6.1.1 release, the SCRIPT handler was added by IBM, which means that these steps can be skipped if you are on 7.6.1.1 or later. If you are running a version between 7.6.0.8 and 7.6.1.1 you can follow these steps to create the Script Router Handler in IBM Maximo:

  1. Log into IBM Maximo as an administrator
  2. Navigate to the Integration > End Points application
  3. Under the More Actions menu, choose the Add/Modify Handlers option
  4. Click the New Row button
    a. Handler: SCRIPT
    b. Consumed by: INTEGRATION
    c. Handler Class Name: com.ibm.tivoli.maximo.script.ScriptRouterHandler
  5. Click the OK button

end point-autoscript-IBM Maximo-mif-automation scripting-integration-scripting-python-code-script-Maximo-customization

Once you have the SCRIPT handler, you can use it to register a new End Point in IBM Maximo:

  1. Log into IBM Maximo as an administrator
  2. Navigate to the Integration > End Points application
  3. Click the New End Point button
    a. End Point: TEST-SCRIPT
    b. Description: TEST SCRIPT HANDLER END POINT
    c. Handler: SCRIPT
  4. After populating the Handler with SCRIPT, a SCRIPT property will appear
    a. Value: <The name of your Automation Script>, e.g. TEST-SCRIPT
  5. Click Save

end point-autoscript-IBM Maximo-mif-automation scripting-integration-scripting-python-code-script-Maximo-customization

At this point, you have an End Point that calls an Automation Script that has not yet been created. The next step is to define that Automation Script and implement your own logic:

  1. Log into IBM Maximo as an administrator
  2. Navigate to the System Configuration > Platform Configuration > Automation Scripts Application
  3. From the More Actions menu, choose the Create > Script option
  4. In the ensuing dialog, enter the basic script information:
    a. Script: TEST-SCRIPT
    b. Description: TEST AUTOMATION SCRIPT FOR END POINT
    c. Language: python
  5. Enter the source code from below and press the Create button

end point-autoscript-IBM Maximo-mif-automation scripting-integration-scripting-python-code-script-Maximo-customization

Source Code:
# ------------------
# This script will write a message to the file system 
# and FTP the file to another server for processing.
# 
# Implicit Variables:
#   INTERFACE - the name of the triggered Publish Channel
#   requestData - the message payload
# 
# Alex Walter
# alex@a3jgroup.com
# 21 JAN 2021
# ------------------
from java.io import File
from java.io import FileWriter
from org.apache.commons.io import FileUtils
from org.apache.commons.net.ftp import FTPClient
from org.apache.commons.net.ftp import FTPReply
from psdi.util.logging import MXLoggerFactory

logger = MXLoggerFactory.getLogger('maximo.script.a3jtutorial')
if logger.isDebugEnabled():
    logger.debug('Starting TEST-SCRIPT script')
    logger.debug('INTERFACE: ' + str(INTERFACE))

# If the file exists, then delete it and create it new
ftpFileName = "C:\Temp\ftpfile.xml"
ftpFile = File(ftpFileName)
if ftpFile.exists():
	ftpFile.delete()
ftpFile.createNewFile()

# Write the file to disk
fileWriter = None
try:
	fileWriter = FileWriter(ftpFile)
	fileWriter.write(requestData)
finally:
	if fileWriter:
		fileWriter.close()

# Setup FTP variables - usually good idea to create System Properties
ftpHostName = "ftp.company.com"
ftpUserName = "username"
ftpPassword = "password"

ftpClient = None
fileInput = None
try:
	# Make an FTP connection
	ftpClient = FTPClient()
	ftpClient.connect(ftpHostName)
	reply = ftpClient.getReplyCode()
	if logger.isDebugEnabled():
		logger.debug('ftp reply: ' + str(reply))
	
	if not FTPReply.isPositiveCompletion(reply):
		if logger.isDebugEnabled():
			logger.debug('not a positive ftp reply')
		ftpClient.disconnect()
	else:
		if logger.isDebugEnabled():
			logger.debug('positive ftp reply!')
		# Log into the FTP server
		if ftpClient.login(ftpUserName, ftpPassword):
			if logger.isDebugEnabled():
				logger.debug('Logged into FTP site')
			# ftpClient.setFileType(2);
			fileInput = FileUtils.openInputStream(ftpFile)
			# Put the file in the default directory
			ftpClient.storeFile(ftpFileName, fileInput)
			if logger.isDebugEnabled():
				logger.debug('File sent to Server')
		# Log out
		ftpClient.logout()
		if logger.isDebugEnabled():
			logger.debug('Logged out of FTP site')
finally:
	if ftpClient:
		ftpClient.disconnect()
	if fileInput:
		fileInput.close()

 

NOTE: If you need help installing this automation script in your IBM Maximo, or have questions with other Maximo CMMS configurations don’t hesitate to reach out! Leave a comment below, or email info@a3jgroup.com

 

Automatically Email Vendor on Approval of Purchase Order in IBM Maximo

Automate emails to vendors from approved purchase orders in Maximo CMMS

This article will walk an IBM Maximo administrator through the steps necessary to send a purchase order by email to a vendor as an attachment. It is a common requirement by customers to be able to automatically send the purchase order to vendors through email. Some of the benefits of doing this are:

  • Standardize the way that approved purchase orders are communicated to vendors. A common template can be used for the email subject and body, as well as a common workflow for when the communication is sent.
  • Reduce the time taken by procurement agents in generating reports, saving them locally, finding appropriate contact information, and typing up an email.
  • When IBM Maximo sends the email, a copy of the email that was sent is stored in the Communication Log along with the purchase order. If you ever need to know when the email was sent and to whom, the documentation of the email is in IBM Maximo attached directly to the purchase order. No more searching through email to find the communication.

To achieve this, we will deploy an automation script in IBM Maximo that will generate the report and send the email. Before we get to the script, we need to ensure that the following prerequisites are met:

  1. The email address of the vendor should be recorded in the Companies application on the Contacts tab. The script below will use the email address of the Primary Contact (set on the Addresses tab) but can be adjusted to include a larger or more targeted set of email addresses. However, they must be accessible from somewhere in the system by the script.
  2. A Communication Template should be created and activated to be used as the email subject and body of the communication. By default, I’ve named the template POTOVENDOR, but you can name it anything you want and change it in the script.
  3. A BIRT report that represents the purchase order to be sent to vendors. By default, the auto script uses the poprint.rptdesign report that comes with IBM Maximo. This can be changed in the script to a custom report but be sure to match up the report parameters if necessary.

The trigger point for the script below is a status change of the purchase order to APPR. However, this could just as easily be triggered from IBM Maximo workflow, escalations, or other means.

Here is the automation script needed to make this happen:

from com.ibm.tivoli.maximo.report.birt.queue import ReportQueueService
from com.ibm.tivoli.maximo.report.birt.runtime import ReportParameterData
from psdi.mbo import SqlFormat

# Get the current user's information
userInfo = mbo.getUserInfo()
locale = userInfo.getLocale()

# If the PO Vendor does not have a contact with email, then kindly exit
if not mbo.isNull("VENDOR.PRIMARYCONTACT.EMAIL"):
    emailTo = mbo.getString("VENDOR.PRIMARYCONTACT.EMAIL")
    # Append the current user's email address to the chain to ensure delivery
    if userInfo.getEmail() and not userInfo.getEmail() == "":
        emailTo = emailTo + ", " + userInfo.getEmail()
    
    # Create default message subject and body
    # These will be replaced by a communication template if one is found by the code below
    emailSubject = "Purchase Order"
    emailComments = "Please acknowledge receipt of this Purchase Order."
    
    # Change this value to a valid Maximo Communication Template ID
    commTemplateID = "POTOVENDOR"
    
    # Get the communication template
    commTemplateClause = SqlFormat(mbo, "templateid = :1")
    commTemplateClause.setObject(1, "COMMTEMPLATE", "TEMPLATEID", commTemplateID)
    commTemplateSet = mbo.getMboSet("$potovendor", "COMMTEMPLATE", commTemplateClause.format())
    commTemplateMbo = commTemplateSet.getMbo(0)
    if commTemplateMbo:
        # Build the email subject and body from the Communication Template
        sql = SqlFormat(mbo, commTemplateMbo.getString("SUBJECT"))
        sql.setIgnoreUnresolved(True)
        emailSubject = sql.resolveContent()
        sql = SqlFormat(mbo, commTemplateMbo.getString("MESSAGE"))
        sql.setIgnoreUnresolved(True)
        emailComments = sql.resolveContent()
    
    # Build the Report Parameters
    parameterData = ReportParameterData()
    parameterData.addParameter("appname", "PO")
    parameterData.addParameter("paramdelimiter", "")
    parameterData.addParameter("paramstring", "")
    parameterData.addParameter("where", "(po.poid = " + str(mbo.getLong("POID")) + ")")
    
    # Queue the Report to Run
    queueManager = ReportQueueService.getQueueService()
    queueManager.queueReport("poprint.rptdesign", "PO", userInfo.getUserName(), emailTo, emailSubject, emailComments, parameterData, locale.getCountry(), locale.getLanguage(), locale.getVariant(), userInfo.getTimeZone().getID(), userInfo.getLangCode(), long(0), userInfo)