Wednesday 5 February 2020

Script to run report and save as attachment in Maximo

One of my customers asked me to take a kind of snapshot of work orders at a specific point of the workflow to keep track of special approvals and exact situation of the approved record.
I decided to implement such requirement through the implementation of an automation script to generate a BIRT report automatically and attach it the work order. I my scenario I have triggered it from the workflow but it can be also triggered from an escalation.
#-------------------------------------------------------------------------------
#
# Script: WOREPORTGENSAVE
#
# Launch Point: WORKORDER - Action
#
# Generate a BIRT report and save it as an attachment.
#
#-------------------------------------------------------------------------------

from psdi.mbo import Mbo, MboConstants
from psdi.util.logging import MXLoggerFactory
from psdi.server import MXServer

from com.ibm.tivoli.maximo.report.birt.admin import ReportAdminServiceRemote
from com.ibm.tivoli.maximo.report.birt.runtime import ReportParameterData

from java.io import FileOutputStream

#-------------------------------------------------------------------------------

logger = MXLoggerFactory.getLogger("maximo.dev")

reportName = "wodetail.rptdesign"    # name of the report to be lauched
appName = "WOTRACK"                  # application
reportFolder = "Attachments"         # folder where the report will be stored

#-------------------------------------------------------------------------------

logger.debug("Entering WOREPORTGENSAVE")

wonum = mbo.getString("WONUM")

logger.debug("Retrieving destination file and folder")

doctypesMboSet = MXServer.getMXServer().getMboSet('DOCTYPES', mbo.getUserInfo())
doctypesMboSet.setWhere("DOCTYPE='" + reportFolder + "'")

outputFilePath=doctypesMboSet.getMbo(0).getString('DEFAULTFILEPATH')

logger.debug("Output folder: " + outputFilePath)

outputFileName = wonum + "-details.pdf"
outputFile = outputFilePath + "/" + outputFileName

logger.debug("Output file: " + outputFile)
logger.debug("Generating report" + reportName)

# get the handler to BIRT report engine
reportAdminService = MXServer.getMXServer().lookup("BIRTREPORT")

# pass WONUM as report parameter
parameterData = ReportParameterData()
parameterData.addParameter("where", "(WORKORDER.wonum='"+wonum+"')")

reportBytes = reportAdminService.runReport(MXServer.getMXServer().getSystemUserInfo(), reportName, appName, parameterData, outputFileName, ReportAdminServiceRemote.OUTPUT_FORMAT_PDF)

# writes the binary data to the output file
fos = FileOutputStream(outputFile)
fos.write(reportBytes)
fos.close()

logger.debug("Creating DOCLINKS record")

doclinksMboSet = mbo.getMboSet("DOCLINKS")

doclinksMbo = doclinksMboSet.add()

doclinksMbo.setValue("URLTYPE", "FILE")
doclinksMbo.setValue("URLNAME", outputFile)
doclinksMbo.setValue("NEWURLNAME", outputFile)
doclinksMbo.setValue("DOCTYPE", reportFolder)
doclinksMbo.setValue("ADDINFO", True)
doclinksMbo.setValue("DESCRIPTION", "Test Results")

How to correctly define a Table Domain

Have you ever wondered what’s the difference between ‘Validation Where Clause’ and ‘List Where Clause’ in the Maximo Table Domain definition dialog?
I believe this is one of the most common misused features in Maximo. I have see so many times tables domains defined in the wrong way. Let me show a good example of a built-in table domain.
The ACTIVEUSER domain is used to allow the selection of an active user in the Cron Tasks application. Here are the domain details.
  • Domain: ACTIVEUSER
  • Object: MAXUSER
  • Validation Where Clause: userid=:runasuserid
  • List Where Clause: status in (select value from synonymdomain where domainid=’MAXUSERSTATUS’ and maxvalue=’ACTIVE’)
  • Error Message Group: signature
  • Error Message Key: NotActiveUser
Lets now analyze two key characteristics of this table domain, the validation where clause and the error message.

Error Message

If we open the Cron Tasks application and try to type some wrong value in the ‘Run As User’ field we will get a specific error: Run as User XXXYYYZZZ is not an active User
Removing the error message group/key from the domain definition we get the generic domain validation error: The value XXXYYYZZZ is not valid for Run as User
The error message group/key properties a Table Domain definition allow to display a more specific error message to the user when the entered value is not valid.
To further improve the error message you can use the {1} param to display the invalid value and the {0} param to display the field name the domain is tied to.

Validation Where Clause

The userid=:runasuserid statement in the Validation Where Clause ensures that the value entered in the field (CRONTASKINSTANCE.RUNASUSERID) matches a value in the data source table (MAXUSER.USERID).
To understand the reason why the Validation Where Clause is so important we can simply try to remove it and see what happens. After having modified the domain we can enter any value in the Run as User field !!!
This means that:
The Validation Where Clause must be always set to validate the entered value against the target table domain.
In our example userid is the attribute of the MAXUSER table that the domain is based on. Instead :runasuserid is the attribute of the CRONTASKINSTANCE table that you want to validate.

UI Conditional Expression Manager in Maximo and IBM Control Desk

In Assets application


Asset 12345 is an IT asset. It will have the following features:

  • Label of Asset field is blue and says ‘IT Asset’
  • Safety Tab is displayed
  • Change Status is enabled
  • The ‘Usage’ field has 7 values in the lookup
  • Serial Number is a required attribute


Asset 12346 is a Facilities asset. It will have the following features:

  • Standard label
  • Safety Tab is not displayed
  • Change Status disabled
  • The Usage field has 2 values in the lookup
  • Serial Number is not required


Configuration


Conditional Expression Manager


  • Condition – TYPEIT
    • Description - Asset type is IT
    • Type – Expression
    • Expression - :assettype = ‘IT’

Application Designer




New SIGOPTIONS for Asset application

  • SAFTAB with the description Safety Tab



New Control Properties

  • Safety Tab 
    • SIGOPTION of SAFTAB
  • Asset field

·         SIGOPTION of READ

·         Conditional Properties

·         Group = CUIGRP

·         Condition = TYPEIT

·         two true properties

1.    Property – label

·         Property Value – IT Asset

2.    Property – labelcss

·         Property value – txtblue



Security Groups


·         CUIGRP Group

o   Applications Tab

§  Assets Application

·         Change Status granted in condition TYPEIT

·         Safety Tab granted in condition TYPEIT

o   Data Restrictions Tab

§  Attribute Restriction

·         Object:= Asset

·         Attribute = SERIALNUM

·         Type = REQUIRED

·         Condition - TYPEIT


Domains



  • USAGE domain
    • BaseWS, StandardWS, SalesWS, Server and ProWS  values have TYPEIT condition
    • FLEET and MOTORPOOL values have no condition







Maximo Scripting – CanDelete/CanAdd Object Launch Point

We can control whether we can add or delete a Mbo using scripting Object Launchpoint “Allow Object Deletion” and “Allow Object Creation”.

Can Add

This is an Object Launch Point – “Allow Object Creation” where you can control whether you can add a new Mbo, given the current state of the system. This point pairs up with the canAdd callback that we have in the MboSet framework.
Lets take a use case where we want to validate that a POLINE can be added to a PO only when the PO has the vendor information set.
The script code to do that is shown below:
if mboset.getOwner() is not None and mboset.getOwner().getName()=="PO" and mboset.getOwner().isNull("vendor"): 
   service.error("po", "novendor_noline") 
Note that here, there is no implicit variable called “mbo” as the launch point is invoked before the Mbo is created. At that point all you have is the MboSet (implicit variable “mboset”) for the POLINE.
If you are wondering why this cannot be done using the init Object launch point, the answer is that its little too late. At that point the Mbo has already been created and added to the set. Rejecting it at the point would have not helped.

Can Delete

Similar to the “Can Add”, this Object Launch Point helps validate whether a Mbo can be deleted or not. Going with the POLINE object, say we want to enforce a validation that the line should not be deleted, if the PO is of priority 1.
if mbo.getOwner() is not None and mbo.getOwner().getName()=="PO" and !mbo.getOwner().isNull("priority") and mbo.getOwner().getInt("priority")==1: 
   service.error("po","nolinedelete_forpriority1") 
Note that in this case, the mbo is already there (which we are trying to validate for deletion) and hence we can leverage implicit variable mbo to do the validation.

Setup Integration Framework JMS queues and buses in WebSphere



So here is a quick guide to setup the JMS queues and buses for use with the Integration Framework.  These steps are for a single instance of Maximo, if you have a second instance of Maximo running for your test environment, then you can follow these same steps, but you just need to change the names accordingly.  If you are creating this for a second instance, then you will need to change a few configuration files and rebuild EARS to point to the new set of JMS resources.

Creating the JMS buses:

1. Navigate in the left pane to Service Integration/buses
2. Click the “new” button to create a new bus
3. In the name field, enter “intjmsbus”
4. Deselect the “Bus Security” checkbox and click “next”
5. Click “finish” and save the changes
6. Navigate to the bus you just created and select it
7. Change the “high message threshold” to 500,000 messages
8. Click “apply” and save the changes

Adding members to the buses:

1. Navigate in the left pane to Service Integration/buses
2. Select the bus created above called “intjmsbus”
3. Under “Topology”, select “bus members”
4. Click the “Add” button
5. Select the “server” radio button and select the MAXIMO server from the drop down (If a single instance, choose MAXIMO, otherwise select the other instance)
6. Click “next”
7. Select the “file store” radio button
8. Accept the default file store location
9. Click “next”
10. Click “finish” and save the changes

Creating bus destinations:

cqin
1. Navigate in the left pane to Service Integration/buses
2. Select your bus intjmsbus
3. Under “destination resources” click “destinations”
4. click the “new” button
5. Set the destination type as “queue” and click “next”
6. In the identifier field, enter “cqinbd” and click “next”
7. Assign the queue in the next screen to the MAXIMO application server
8. Click “next”
9. Click “finish” and save the changes
10. Navigate back to this destination, and set the exception destination radio button to “specify” and set the value in the textbox to cqinerrbd
11. Set the “maximum failed deliveries” to 5
12. Click “apply” and save the changes

cqinerr
1. Navigate in the left pane to Service Integration/buses
2. Select your bus intjmsbus
3. Under “destination resources” click “destinations”
4. click the “new” button
5. Set the destination type as “queue” and click “next”
6. In the identifier field, enter “cqinerrbd” and click “next”
7. Assign the queue in the next screen to the MAXIMO application server
8. Click “next”
9. Click “finish” and save the changes
10. Navigate back to this destination, and set the exception destination radio button to “specify” and set the value int he textbox to cqinerrbd
11. Set the “maximum failed deliveries” to 5
12. Click “apply” and save the changes

sqin
1. Navigate in the left pane to Service Integration/buses
2. Select your bus intjmsbus
3. Under “destination resources” click “destinations”
4. click the “new” button
5. Set the destination type as “queue” and click “next”
6. In the identifier field, enter “sqinbd” and click “next”
7. Assign the queue in the next screen to the MAXIMO application server
8. Click “next”
9. Click “finish” and save the changes
10. Navigate back to this destination, and set the exception destination radio button to “none”
11. Click “apply” and save the changes

sqout
1. Navigate in the left pane to Service Integration/buses
2. Select your bus intjmsbus
3. Under “destination resources” click “destinations”
4. Click the “new” button
5. Set the destination type as “queue” and click “next”
6. In the identifier field, enter “sqoutbd”
7. Assign the queue in the next screen to the MAXIMO application server
8. Click “next”
9. Click “finish” and save the changes
10. Navigate back to this destination, and set the exception destination radio button to “none”
11. Click “apply” and save the changes

Creating the MEA connection factory:

1. Navigate in the left pane to Resources/JMS providers/default messaging
2. Select the Default Messaging Provider at the cell scope
3. Under “additional properties”, select “queue connection factories”
4. Click the “new” button
5. In the name field, enter “intjmsconfact” (If using a second instance, change this name to something like “maxtestintjmsconfact”)
6. In the JNDI name field, enter

Default instance:  jms/maximo/int/cf/intcf
Second instance:  jms/maxtest/int/cf/intcf

7. Select the bus intjmsbus from the dropdown
8. Click “apply” and save the changes

Creating JMS queues:

Continuous inbound queue:
1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “queues”
3. Click the “new” button
4. Enter the name as “cqin”
5. In the JNDI name field, enter “jms/maximo/int/queues/cqin” (Or “jms/maxtest/int/queues/cqin”)
6. Select the intjmsbus as the bus for this queue from the bus name drop down
7. Select the cqinbd queue from the queue name drop down
8. Click “apply” and save the changes

Continuous inbound error queue:
1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “queues”
3. Click the “new” button
4. Enter the name as “cqinerr”
5. In the JNDI name field, enter “jms/maximo/int/queues/cqinerr” (Or “jms/maxtest/int/queues/cqinerr”)
6. Select the intjmsbus as the bus for this queue from the bus name drop down
7. Select the cqinerrbd queue from the queue name drop down
8. Click “apply” and save the changes

Sequential inbound queue:

1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “queues”
3. Click the “new” button
4. Enter the name as “sqin”
5. In the JNDI name field, enter “jms/maximo/int/queues/sqin” (Or “jms/maxtest/int/queues/sqin”)
6. Select the intjmsbus as the bus for this queue from the bus name drop down
7. Select the sqinbd queue from the queue name drop down
8. Click “apply” and save the changes

Sequential outbound queue:

1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “queues”
3. Click the “new” button
4. Enter the name as “sqout”
5. In the JNDI name field, enter “jms/maximo/int/queues/sqout” (Or the whatever name you called it)
6. Select the intjmsbus as the bus for this queue from the bus name drop down
7. In Select the sqoutbd queue from the queue name drop down
8. Click “apply” and save the changes

Creating the JMS Activation Specifications

First activation specification:

1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “activation specifications”
3. Click the “new” button
4. In the name field, enter “intjmsact” (Or “maxtestintjmsact”)
5. In the JNDI name field, enter “intjmsact” (Or the whatever name you called it)
6. In the “Destination JNDI name” field, enter “jms/maximo/int/queues/cqin”
7. Make sure the destination type is “queue”
8. Select the bus “intjmsbus” from the bus name drop down
9. Set Maximum batch size to 10
10. Set Maximum end points to 5
11. Click OK and save the changes

Second activation specification:

1. Navigate to Resources/JMS providers
2. Select the default message provider at the cell scope and under “additional properties”, click “activation specifications”
3. Click the “new” button
4. In the name field, enter “intjmsacterr” (Or “maxtestintjmsacterr”)
5. In the JNDI name field, enter “intjmsacterr” (Or the whatever name you called it)
6. In the “Destination JNDI name” field, enter “jms/maximo/int/queues/cqinerr”
7. Make sure the destination type is “queue”
8. Select the bus “intjmsbus” from the bus name drop down
9. For this activation spec, set Maximum batch size to 1
10. For this activation spec, set Maximum end points to 10
11. Click OK and save the changes

Now that you have setup all the JMS resources, you have to enable the use the these queues by making some changes to the Maximo configuration files.

Preparing the MEA application

For the MAXIMOMEA application instance, edit the following files and uncomment the message driven beans for the continuous queues:

1. locate the file under maximo/applications/maximo/mboejb/ejbmodule/meta-inf/ejb-jar.xml

2. Edit the file and make sure the following four sections are uncommented to look like below:

<!-- MEA MDB -->
<message-driven id="MessageDriven_JMSContQueueProcessor_1">
<ejb-name>JMSContQueueProcessor-1</ejb-name>
<ejb-class>psdi.iface.jms.JMSContQueueProcessor</ejb-class>
<transaction-type>Container</transaction-type>
<message-destination-type>javax.jms.Queue</message-destination-type>
<env-entry>
<env-entry-name>MESSAGEPROCESSOR</env-entry-name>
<env-entry-type>java.lang.String </env-entry-type>
<env-entry-value>psdi.iface.jms.QueueToMaximoProcessor</env-entry-value>
</env-entry>
</message-driven>
 
<!-- MEA MDB for error queue -->
<message-driven id="MessageDriven_JMSContQueueProcessor_2">
<ejb-name>JMSContQueueProcessor-2</ejb-name>
<ejb-class>psdi.iface.jms.JMSContQueueProcessor</ejb-class>
<transaction-type>Container</transaction-type>
<message-destination-type>javax.jms.Queue</message-destination-type>
<env-entry>
<env-entry-name>MESSAGEPROCESSOR</env-entry-name>
<env-entry-type>java.lang.String </env-entry-type>
<env-entry-value>psdi.iface.jms.QueueToMaximoProcessor</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>MDBDELAY</env-entry-name>
<env-entry-type>java.lang.Long </env-entry-type>
<env-entry-value>30000</env-entry-value>
</env-entry>
</message-driven>
 
<!-- MEA MDB -->
<container-transaction>
<method>
<ejb-name>JMSContQueueProcessor-1</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
 
<!-- MEA MDB for error queue -->
<container-transaction>
<method>
<ejb-name>JMSContQueueProcessor-2</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>

3. locate the file under maximo/applications/maximo/mboejb/ejbmodule/meta-inf/ibm-ejb-jar-bnd.xmi

4. Edit the file using a text editor and make sure the following 2 sections are uncommented to look like below:

<!-- MEA MDB -->
<ejbBindings xmi:type="ejbbnd:MessageDrivenBeanBinding" xmi:id="MessageDrivenBeanBinding_1" activationSpecJndiName="intjmsact">
<enterpriseBean xmi:type="ejb:MessageDriven" href="META-INF/ejb-jar.xml#MessageDriven_JMSContQueueProcessor_1"/>
</ejbBindings>
 
<!-- MEA MDB for error queue -->
<ejbBindings xmi:type="ejbbnd:MessageDrivenBeanBinding" xmi:id="MessageDrivenBeanBinding_1" activationSpecJndiName="intjmsacterr">
<enterpriseBean xmi:type="ejb:MessageDriven" href="META-INF/ejb-jar.xml#MessageDriven_JMSContQueueProcessor_2"/>
</ejbBindings>

Make sure you enter the right activation name if you are setting this up for a second instance.
5. Build and deploy the maximo.ear



Maximo Scripting – Warnings and Errors

This post is an excerpt from the Maximo 76 Scripting Features guide.
Maximo errors in 7.5 version used to get done by setting errorgroup, errorkey and params variables (full example here).
woPriority = mbo.getInt("WOPRIORITY")
if woPriority > 50:
    # Raise an error using a message
    errorgroup = "workorder"
    errorkey = "invalidPrio"
Setting those did not stop the execution, as the execution will continue till the end of that script and then it will check the flags and throw the error. Often this may not be the expected behavior. To fix this issue, in 7.6 the global “service” variable can be used to throw errors and warnings.
service.error("po", "novendor") 

Warning message

Showing a warning message can be done fairly easily with automation scripts. Say we want to show a warning to the users when they save a PO with no lines.
To do this, first we need to create a warning message from the messages dialog in the db config application. Say we name it “nolines” under the message group “po”.
We create an Object Launch Point – Save event on Add and Update. The code below (py) validates the POLINE count and uses the “service” global variable to show the warning.
if mbo.getMboSet("POLINE").count()==0 and interactive:
    service.setWarning("po", "nolines", None)

IBM Readme for IBM Maximo Asset Management 7.6.1.3 Fix Pack

  Fix Readme Abstract This fix pack updates IBM® Maximo® Asset Management version 7.6.1, 7.6.1.1, and 7.6.1.2 Content IBM Maximo Asset Manag...