August 30, 2018

Sending SMS from Maximo calling an external Web service

In my last project I had to implement a customization to send SMS messages using Skebby. The service APIs are obviously based on REST and JSON so I ended up writing an automation script to integrate Maximo with the Skebby service.
In the process of writing the code I have faced (and solved) some interesting issues so the developed script is quite complex and is an interesting demonstration of several useful techniques:
  1. External REST service invocation (POST and GET)
  2. JSON manipulation
  3. Retrieve Maximo system properties
  4. Usage of Java classes
  5. Appropriate logging using MXLoggerFactory
  6. Display error messages with parameters
The script is based on WORKORDER object and requires two custom system properties (sms.username, sms.password) and two messages (SmsSendError, SmsSendOk).
Here it is.
from java.lang import String
from java.io import BufferedReader, InputStreamReader
from java.net import URL, URLEncoder
from java.nio.charset import StandardCharsets
from com.ibm.json.java import JSONObject, JSONArray

from psdi.server import MXServer
from psdi.util.logging import MXLoggerFactory

logger = MXLoggerFactory.getLogger("maximo.script")
logger.debug("Entering SEND_SMS script")


BASEURL = "https://api.skebby.it/API/v1.0/REST/"
SMS_SENDER = "Maximo"
SMS_DEST = "+3933511111111"

sms_text = "Ticket " + mbo.getString("WONUM") + " - " + mbo.getString("DESCRIPTION")

properties = MXServer.getMXServer().getConfig()
username = properties.getProperty("sms.username")
password = properties.getProperty("sms.password")

urltxt = BASEURL + "login?username=" + URLEncoder.encode(username, "UTF-8") + "&password=" + URLEncoder.encode(password, "UTF-8")
logger.debug("Authenticating: " + urltxt)
url = URL(urltxt)
conn = url.openConnection()
conn.setRequestMethod("GET")

if conn.getResponseCode() != 200:
  conn.disconnect()
  msg = "HTTP Error: " + str(conn.getResponseCode()) + " - " + str(conn.getResponseMessage())
  logger.error(msg)
  service.error("asts", "SmsSendError", [str(conn.getResponseCode()), str(conn.getResponseMessage()), "Authentication failed"])

br = BufferedReader(InputStreamReader(conn.getInputStream()))
authResp = br.readLine()
conn.disconnect()

#logger.debug("Authentication output:" + authResp)
# auth tokens are returned separated by a ';' character
user_key,session_key = authResp.split(';')

logger.debug("Authentication tokens:" + user_key + "," + session_key)

logger.info("Sending SMS to " + SMS_DEST + " - " + sms_text)

url = URL(BASEURL + "sms")
conn = url.openConnection()
conn.setRequestMethod("POST")
conn.setRequestProperty("user_key", user_key)
conn.setRequestProperty("Session_key", session_key)
conn.setRequestProperty("content-type", "application/json")
conn.setDoOutput(True)

recipients = JSONArray()
recipients.add(SMS_DEST)

obj = JSONObject()
obj.put("message_type", "TI")
obj.put("returnCredits", False)
obj.put("sender", SMS_SENDER)
obj.put("recipient", recipients)
obj.put("message", sms_text)
jsonStr = obj.serialize(False)

logger.debug("SMS Json Request=" + jsonStr)

os = conn.getOutputStream()
postData = String.getBytes(jsonStr, StandardCharsets.UTF_8)
os.write(postData)
os.flush()

if conn.getResponseCode() != 201:
  br = BufferedReader(InputStreamReader(conn.getErrorStream()))
  output = br.readLine()
  logger.error("Error: " + output)
  conn.disconnect()
  msg = "HTTP Error: " + str(conn.getResponseCode()) + " - " + str(conn.getResponseMessage() + " - " + output)
  logger.error(msg)
  service.error("asts", "SmsSendError", [str(conn.getResponseCode()), str(conn.getResponseMessage()), output])

br = BufferedReader(InputStreamReader(conn.getInputStream()))
output = br.readLine()
logger.info("OK: " + output)
conn.disconnect()

service.setWarning("asts", "SmsSendOk", None)


The script has been developed to avoid external dependencies like this example.

August 23, 2018

Disable automatic asset/location population in Work Order and Ticket applications

Maximo has a feature that automatically populates the asset when a user enters a location and viceversa. This is a feature may be useful for most clients but some other clients don’t like this behavior. Unfortunately Maximo does not have a built in switch to disable this.

The best solution to solve the problem is to use an automation script with an attribute launch point.
This is documented in this post but i made few improvements to it.
My script resets the asset only when the location has been changed and ASSETNUM is changing from null to a new value. This is because there is already a warning dialog when the asset is changed from another value.

This is how I have defined my automation script. I have tested it for Service Requests application but it should work fine for Work Orders as well.

  • Script: SR_LOCATION
  • Language: python
  • Launch Point: SR_LOCATION_ACTION
  • Object: SR (this could be also be WORKORDER or TICKET)
  • Attribute: LOCATION
  • Event: Run Action (Validate should also work)
  • Variables: none

Script:
from psdi.mbo import MboConstants

if not mbo.isNull("LOCATION"):
  if mbo.getMboValue("ASSETNUM").getPreviousValue().asString()=="" and not mbo.isNull("ASSETNUM"):
    mbo.setValueNull("ASSETNUM", MboConstants.NOVALIDATION_AND_NOACTION)

August 22, 2018

How to restrict allowed status changes for workorders

Maximo has a predefined set of statuses for work orders that are defined in the WOSTATUS synonym domain. The values and their meaning are well documented in the official documentation.
Unfortunately the allowed state transitions are hardcoded in the psdi.app.workorder.WOStatusHandler class and cannot be easily changed since they are defined as private arrays.
Here is a brief summary of the allowed status changes.

WAPPR APPR WSCH WMATL INPRG COMP CANCEL CLOSE
WAPPR - Y Y Y Y Y Y Y
APPR Y - Y Y Y Y - Y
WSCH Y Y - Y Y Y - Y
WMATL Y Y Y - Y Y - Y
INPRG Y - Y - Y
COMP - - Y
CANCEL - Y
CLOSE -


From status APPR you can set any target status.


This allows too much freedom in some cases so we want to restrict the allowed state transitions.
For example you might want to prevent users from changing the status of work orders that are in APPR (Approved) status to CLOSE (Closed) or CAN (Canceled).

First, navigate to Condition Expression Manager and create the following conditional expression:

  • Condition: WONOTAPPR
  • Description: Work Order is not in APPR status
  • Type: Expression
  • Expression: :status not in ('APPR')
  • Always Evaluate: false

Open the the Domains application and bring up the WOSTATUS domain.
Select the CAN status row and click on the View/Modify Conditions button.
Select the "new row" button and select the new condition you created above, and click OK.



Do the same to COMP and CLOSE status if you wish.
If you now open any work order in APPR status you will notice that the list of allowed target statuses is shorter.




The good part is that the same rule applies also if you click on the quick actions.



References



October 25, 2017

How to extract emails to notify users

There are several situation where you need to quickly notify a set of users sending them an email. These are few examples of such situations.
  • There is a urgent maintenance activity to perform on Maximo that requires to enable the admin mode and logoff all the users.
  • You are going to rollout an additional validation into an application that will affect the way technicians must enter data when closing work orders.
  • An important event has appended (incident or critical problem) and you must alert people as soon as possible.

The built-in Bulletin Board application often solves these problem that allows to broadcast information throughout your organization. You can specify an audience for a message, based on organization, site or person group. Messages will appear on the start centers and a notification icon will be displayed at the top of the window.
Furthermore, the 'Create Communication' action allows to send an email to the selected audience.


There are just two limitation of this approach.

  1. You want to use a Security Group to select who to notify.
  2. You want to send the email from your inbox in order to interact with the recipients more easily.
I have solved this little problem with an easy trick. I have added the email address to the list view of the Security Groups application.

It is really easy... follow this steps:
  • Open the Application Designer application
  • Search for application USER
  • Add a column in the list view and type PERSON.PRIMARYEMAIL in the Attribute field
  • Save



Now if you go the Users application you will see the email addresses of your users listed. You can also filter by security group in the advanced search to refine your list.
Exporting the table and pasting the email addresses cells into your preferred email client will do the trick.



A similar configuration can also be used in the People application (PERSON). However, you can't search for security groups from there.


Now don't spam your users everyday...