Enterprise service replacement rules handle many straightforward inbound transformations. Sometimes the value you need depends on a Maximo record, so the message must be enriched before the integration framework creates and validates its MBOs.
In this example, an external system sends a purchase order without SITEID. Each PO line contains a requisition number and line number, so an object structure automation script looks up the corresponding PRLINE and adds its site to the inbound PO and PO lines.
The better integration contract is for the sender to provide a valid site explicitly. Use a lookup like this only when the source cannot supply it and the relationship gives one unambiguous result.
The incoming message
The useful part of the message looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<SyncPOIFACE
xmlns="http://www.ibm.com/maximo"
creationDateTime="2019-04-03T11:02:42+01:00"
baseLanguage="EN"
messageID="7064815542857609687649"
>
<POIFACESet>
<PO action="">
<ORGID>943</ORGID>
<PONUM>102124045</PONUM>
<STATUS>APPR</STATUS>
<POLINE>
<ORGID>943</ORGID>
<POLINENUM>1</POLINENUM>
<PRNUM>1579</PRNUM>
<PRLINENUM>1</PRLINENUM>
<SITEID />
</POLINE>
</PO>
</POIFACESet>
</SyncPOIFACE>The script must run before MBO creation because SITEID participates in record identity and validation. IBM provides beforeProcess(ctx) for logic that changes the inbound StructureData before the framework creates the business objects.
Create the integration script
In Automation Scripts, select Create > Script for Integration, then configure:
| Setting | Value |
|---|---|
| Integration type | Object Structure |
| Object structure | POIFACE |
| Direction | Inbound Processing |
| Language | JavaScript |
| Script name | A clear site-specific name, such as POIFACE.SETSITE |
Use the integration-script dialog rather than creating a normal launch-point script. Current Maximo versions store the object-structure association in the script configuration, so the script does not depend on an OSIN.<object structure> naming convention. Older environments may already use names such as OSIN.POIFACE; preserve that convention when maintaining an existing implementation.
A safer implementation
This script binds every query value with SqlFormat, closes each MBO set, uses PRNUM, PRLINENUM and ORGID as the lookup key, checks missing data, and prevents lines from different sites being placed on one PO.
var MXServer = Packages.psdi.server.MXServer;
var SqlFormat = Packages.psdi.mbo.SqlFormat;
function getRequiredValue(structureData, attribute, context) {
var value = String(structureData.getCurrentData(attribute) || "").trim();
if (!value) {
throw new Error(context + " is missing " + attribute);
}
return value;
}
function findPrLineSite(userInfo, orgId, prNumber, prLineNumber) {
var prLineSet = MXServer.getMXServer().getMboSet("PRLINE", userInfo);
try {
var where = new SqlFormat(
"ORGID = :1 AND PRNUM = :2 AND PRLINENUM = :3"
);
where.setObject(1, "PRLINE", "ORGID", orgId);
where.setObject(2, "PRLINE", "PRNUM", prNumber);
where.setObject(3, "PRLINE", "PRLINENUM", prLineNumber);
prLineSet.setWhere(where.format());
prLineSet.reset();
var prLine = prLineSet.moveFirst();
if (prLine === null) {
throw new Error(
"No PRLINE found for " + orgId + "/" + prNumber + "/" + prLineNumber
);
}
var siteId = String(prLine.getString("SITEID") || "").trim();
if (!siteId) {
throw new Error(
"PRLINE " +
orgId +
"/" +
prNumber +
"/" +
prLineNumber +
" has no SITEID"
);
}
return siteId;
} finally {
prLineSet.cleanup();
prLineSet.close();
}
}
function beforeProcess(ctx) {
var data = ctx.getData();
var poLines = data.getChildrenData("POLINE");
if (poLines === null || poLines.size() === 0) {
throw new Error("The inbound PO contains no POLINE records");
}
var headerOrgId = getRequiredValue(data, "ORGID", "PO");
var headerSiteId = null;
for (var index = 0; index < poLines.size(); index++) {
var poLine = poLines.get(index);
var lineContext = "POLINE " + (index + 1);
var orgId = String(poLine.getCurrentData("ORGID") || headerOrgId).trim();
var prNumber = getRequiredValue(poLine, "PRNUM", lineContext);
var prLineNumber = getRequiredValue(poLine, "PRLINENUM", lineContext);
var siteId = findPrLineSite(
ctx.getUserInfo(),
orgId,
prNumber,
prLineNumber
);
if (headerSiteId === null) {
headerSiteId = siteId;
} else if (headerSiteId !== siteId) {
throw new Error("The inbound PO contains PR lines from different sites");
}
poLine.setCurrentData("SITEID", siteId);
poLine.setCurrentData("TOSITEID", siteId);
}
data.setCurrentData("SITEID", headerSiteId);
}For a production interface, replace the generic JavaScript errors with configured Maximo messages so the enterprise service receives a stable message group, key and useful parameters.
How the script works
beforeProcess(ctx) runs once for the inbound noun before its MBOs are created. ctx.getData() returns the mutable StructureData for that noun, while getChildrenData("POLINE") returns the line structures still waiting to be processed.
For each line, the script:
- Requires
PRNUMandPRLINENUMrather than assuming they exist. - Combines them with
ORGIDto identify the sourcePRLINE. - Uses
SqlFormatso message content never becomes executable SQL. - Reads the source line's
SITEID. - Sets
SITEIDandTOSITEIDon the inbound PO line. - Verifies that every line resolves to the same header site.
- Sets the PO header
SITEIDafter all lines pass validation.
The integration user supplies ctx.getUserInfo(), so its object and site permissions affect the lookup. Do not replace it with the system user merely to bypass an authorization failure.
Why the original lookup was unsafe
A query assembled like this should not be used with inbound data:
prSet.setWhere("PRNUM = '" + prNumber + "'");Apart from SQL injection, PRNUM alone might not be unique across organizations or sites. Matching a PRLINE by description is even less reliable because descriptions can repeat or change. Bind values and use the actual business key available in the message.
Every set opened through MXServer.getMXServer().getMboSet() must also be released. Leaving sets open for each PO line can retain database resources and becomes expensive under integration load.
Test the transformation
Use a nonproduction external system or object structure service and test at least:
| Input | Expected result |
|---|---|
| One valid PR line | Header and line receive its site |
| Several PR lines from the same site | All records receive the same site |
| PR line does not exist | Transaction fails with a controlled message |
Missing PRNUM or PRLINENUM |
Transaction fails before MBO creation |
| Lines resolve to different sites | Whole PO is rejected |
| Quote or other special character in an input | Treated as data, never SQL |
| Integration user cannot read the source line | Permission failure is visible |
| Reprocessed message | Result remains consistent |
Inspect the message-tracking record and the created PO. Confirm the header and every line have the expected site, no partial record was committed, and the source identifiers remain unchanged.