NOTE: This is provided as-is without any official support from IBM.
Core Maximo has had qualifications that can be associated to labor records before I even used the product. Out of box though, it's not really used to ensure that the Labor record is qualified to perform the maintenance. We enhanced qualifications in Transportation & HS&E/O&G to enable compliance but neither really quite addressed the use case well in my opinion. Inspired by a customer and the following idea (https://ibm-ai-apps.ideas.ibm.com/ideas/SCHEDULE-I-27), I set out to figure out how I could make qualifications enforceable in core Maximo.
HS&E has the ability to associate qualifications to assignments and planned/actual labor but it's a 1:1 mapping to qualifications. Work orders might need to require multiple qualifications. When you add additional planned labor you'd also need to remember to add the qualifications.
Transportation has the ability to associate multiple qualifications and it's done at a higher level than planned labor/assignments. Unfortunately, it requires that qualifications are tied to the WO tasks. For most organizations, the qualifications don't change between tasks on a work order so tracking that granularly is overkill. On Job Plans, we required that the job task referenced an ORGID to associate a qualification. This makes sense since Qualifications are at an Organization level, but that limits an organization's ability to re-use a Job Plan across organizations if they wanted to enforce qualifications.
In both products, we ask the administrator to configure what should happen at a global level. O&G/HS&E it's enabled or disabled. When enabled you can't pick a labor record that doesn't match the qualification. While this makes sense, it's possible someone who wasn't qualified performed the work. Preventing them from being able to record their time means that you don't have accurate information in the system.
Transportation provides qualification options, but it allows for unqualified labor to perform the work even if set to ERROR. Our description on the MAXVAR for Actual Labor Qualification validation explains it pretty well "Specifies whether, at the time the work order is completed, the system should display a warning message, an error message, or take no action (NONE) if actual labor does not meet the qualification requirements.". Essentially, we only display the message when you try to complete the work.
Before I get into the technical "how", I wanted to discuss what I did from a functional perspective.
On Job Plans, Job Tasks, WO, & WO tasks I enabled a user to associate multiple qualifications to that record. For each of these objects we have a new persistent field (ISMQUALIFICATION) that will display NONE, MULTIPLE, or the name of the qualification if only one qualification is associated.
When you open the dialog, it'll vary a bit based on whether you're on the Job Plan/Job Task or Work Order/WO Task. Since Job Plan can be at a System Level, we allow the user to provide the ORGID when there is no ORGID on the JOBPLAN. They can provide entries for each organization when the job plan is at a System level. We'll evaluate when the job plan is applied to copy over only the relevant Qualifications for that organization. When an ORGID does exist on the JOBPLAN we'll set the ORGID and make it read-only.

On Work Order/Tasks, we need to evaluate these qualifications against Assignments, Planned Labor, & Actual Labor so we add some additional non-persistent fields for tracking that compliance. We also support re-evaluating this compliance in case certifications may have been added/revoked since the system evaluated that record.

Exception type is a key difference in our approach over the previous Industry solutions. For every qualification on every WO/Task, you are in control of how we should handle it. If you say that the exception type is ERROR, we'll prevent the user from assigning, planning, or recording actual work without that qualification. This allows you to block when the requirement is absolutely critical. If you choose to utilize WARNING, we'll allow it but display a message to the user when the record is saved. If you choose SILENT, we won't display anything to the user. For both WARNING & SILENT we track that there was a discrepancy on the WPLABOR/ASSIGNMENT/LABTRANS record in a new attribute we added (ISMQUALIFICATIONMET).
The ISMQUALIFICATIONMET attribute will display NA when no qualification is required or qualification can't be evaluated (such as a planned labor at the craft level without a laborcode). It will display ALL if the labor met all qualifications required for the record. It will display PARTIAL if they met some of the qualifications but not all of them. And it will display NONE if they met zero of the qualifications required for that record.

I wanted to make sure a customer could extend our capability into new scenarios that we haven't thought about yet. For example, one of the feedback items I heard working with my customer is that the user executing the status change to complete might be required to have the qualification. To support this, I added two system properties (ismwoqual.validateCurrentUser & ismwoqual.validateCurrentUser.exceptionLevel) and a non-persistent attribute (ISMQUALVALIDATECURUSER) to the WORKORDER/WOACTIVITY objects. If you ensure these system properties are configured and set the non-persistent attribute to true via another automation script, we'll evaluate the qualifications in context of that user record.
If you're interested in deploying this in your own environment, I've uploaded a DBC file for the schema changes here: https://community.ibm.com/community/user/asset-facilities/viewdocument/maximo-qualifications-dbc-file?CommunityKey=ed77c224-45e2-47b0-b574-cc31496f9a41&tab=librarydocuments
I added three new Menus that needs to be added to the MENUS.xml (Export System XML from Application Designer)
<menu id="ISMJPQUALREQ" image="btnicon_matchlabortowork.gif" label="Qualifications">
<menuitem event="ismviewjpqualreq" id="ismjpqualreq_0" image="btnicon_matchlabortowork.gif" label="Manage Qualifications"/>
</menu>
<menu id="ISMWOQUALREQ" image="btnicon_matchlabortowork.gif" label="Qualifications">
<menuitem event="ismviewwoqualreq" id="ismwoqualreq_0" image="btnicon_matchlabortowork.gif" label="Manage Qualifications"/>
</menu>
<menu id="ISMWOTASKQUALREQ" image="btnicon_matchlabortowork.gif" label="Qualifications">
<menuitem event="ismviewwotaskqualreq" id="ismwotaskqualreq_0" image="btnicon_matchlabortowork.gif" label="Manage Qualifications"/>
</menu>
I added three dialogs to the LIBRARY.xml (Export System XML from Application Designer)
<dialog id="ismviewjpqualreq" label="Qualification Requirements">
<table id="ismviewjpqualreq_table" relationship="ISMJPQUALREQ">
<tablebody id="ismviewjpqualreq_table_tb1">
<tablecol dataattribute="orgid" id="ismviewjpqualreq_table_tb1_tc1" lookup="org"/>
<tablecol applink="qual" dataattribute="qualificationid" id="ismviewjpqualreq_table_tb1_tc2" lookup="QUALS" menutype="normal"/>
<tablecol dataattribute="exceptiontype" id="ismviewjpqualreq_table_tb1_tc5" lookup="valuelist"/>
<tablecol id="ismviewjpqualreq_table_tb1_tc6" mxevent="toggledeleterow" mxevent_desc="Mark Row for Delete" mxevent_icon="btn_garbage.gif" type="event"/>
</tablebody>
<buttongroup id="ismviewjpqualreq_table_bg1">
<pushbutton default="true" id="ismviewjpqualreq_table_bg1_bt1" label="New Row" mxevent="addrow"/>
</buttongroup>
</table>
<buttongroup id="ismviewjpqualreq_bg">
<pushbutton default="true" id="ismviewjpqualreq_bg_bt1" label="Save" mxevent="dialogok"/>
<pushbutton id="ismviewjpqualreq_bg_bt2" label="Cancel" mxevent="dialogcancel"/>
</buttongroup>
</dialog>
<dialog id="ismviewwoqualreq" label="Qualification Requirements">
<table id="ismviewwoqualreq_table" relationship="ISMWOQUALREQ">
<tablebody id="ismviewwoqualreq_table_tb1">
<tablecol applink="qual" dataattribute="qualificationid" id="ismviewwoqualreq_table_tb1_tc1" lookup="QUALS" menutype="normal"/>
<tablecol dataattribute="metassignments" id="ismviewwoqualreq_table_tb1_tc2" inputmode="readonly"/>
<tablecol dataattribute="metplans" id="ismviewwoqualreq_table_tb1_tc3" inputmode="readonly"/>
<tablecol dataattribute="metactuals" id="ismviewwoqualreq_table_tb1_tc4" inputmode="readonly"/>
<tablecol dataattribute="exceptiontype" id="ismviewwoqualreq_table_tb1_tc5" lookup="valuelist"/>
<tablecol id="ismviewwoqualreq_table_tb1_tc6" mxevent="toggledeleterow" mxevent_desc="Mark Row for Delete" mxevent_icon="btn_garbage.gif" type="event"/>
</tablebody>
<buttongroup id="ismviewwoqualreq_table_bg1">
<pushbutton default="true" id="ismviewwoqualreq_table_bg1_bt1" label="New Row" mxevent="addrow"/>
</buttongroup>
</table>
<buttongroup id="ismviewwoqualreq_bg">
<pushbutton default="true" id="ismviewwoqualreq_bg_bt1" label="Save" mxevent="dialogok"/>
<pushbutton id="ismviewwoqualreq_bg_bt2" label="Validate Qualifications" mxevent="ismqualval"/>
<pushbutton id="ismviewwoqualreq_bg_bt3" label="Cancel" mxevent="dialogcancel"/>
</buttongroup>
</dialog>
<dialog id="ismviewwotaskqualreq" label="Qualification Requirements">
<table id="ismviewwotaskqualreq_table" relationship="ISMWOQUALTASKPARENT">
<tablebody id="ismviewwotaskqualreq_table_tb1">
<tablecol applink="qual" dataattribute="qualificationid" id="ismviewwotaskqualreq_table_tb1_tc1" lookup="QUALS" menutype="normal"/>
<tablecol dataattribute="metassignments" id="ismviewwotaskqualreq_table_tb1_tc2" inputmode="readonly"/>
<tablecol dataattribute="metplans" id="ismviewwotaskqualreq_table_tb1_tc3" inputmode="readonly"/>
<tablecol dataattribute="metactuals" id="ismviewwotaskqualreq_table_tb1_tc4" inputmode="readonly"/>
<tablecol dataattribute="exceptiontype" id="ismviewwotaskqualreq_table_tb1_tc5" lookup="valuelist"/>
<tablecol dataattribute="wonum" id="ismviewwotaskqualreq_table_tb1_tc6" inputmode="readonly"/>
<tablecol id="ismviewwotaskqualreq_table_tb1_tc7" mxevent="toggledeleterow" mxevent_desc="Mark Row for Delete" mxevent_icon="btn_garbage.gif" type="event"/>
</tablebody>
<buttongroup id="ismviewwotaskqualreq_table_bg1">
<pushbutton default="true" id="ismviewwotaskqualreq_table_bg1_bt1" label="New Row" mxevent="addrow"/>
</buttongroup>
</table>
<buttongroup id="ismviewwotaskqualreq_bg">
<pushbutton default="true" id="ismviewwotaskqualreq_bg_bt1" label="Save" mxevent="dialogok"/>
<pushbutton id="ismviewwotaskqualreq_bg_bt2" label="Validate Qualifications" mxevent="ismqualval"/>
<pushbutton id="ismviewwotaskqualreq_bg_bt3" label="Cancel" mxevent="dialogcancel"/>
</buttongroup>
</dialog>
You'll need to add the ISMQUALIFICATION along with the appropriate menu to the applications (JOBPLAN/WOTRACK) for each of the objects you plan to support (JOBPLAN, JOBTASK, WORKORDER, and/or WOACTIVITY).
Main WORKORDER
WOACTIVITY (Tasks)

JOBPLAN

Optionally, if you want to display the ISMQUALIFICATIONMET you can add that to WOTRACK for Planned Labor, Assignments, & Actual Labor.

We have one signature option that needs to be added to the WO application (IE WOTRACK) that you added the ISMQUALIFICATION field. Please make sure to grant this to any user you want to be able to execute ad-hoc validation of the qualifications.
Option: ISMQUALVAL
Description: Validate Qualifications
Prerequisites: SAVE
Advanced Signature Options (expand section at the bottom): This is an action that must be invoked by user in the UI
We have seven messages that need to be configured in Database Configuration. For simplicity I've only displayed the text to make it easier to copy.
Group: ismwoqual
Key: curUserMissingQual
Prefix: BMXZZ
Suffix: E
Value: Qualification verification was enforced on this work order or task and the current user does not meet the required qualification {0}.
Group: ismwoqual
Key: existingRecordRequiredQuals
Prefix: BMXZZ
Suffix: E
Value: Existing records are missing qualification {0}, such as labor {1} on the {2} object. This qualification needs to be removed or labor records updated before this qualification can be added.
Group: ismwoqual
Key: missRequiredQuals
Prefix: BMXZZ
Suffix: E
Value: Qualification {0} is required for labor {1} to be saved on {2}.
Group: ismwoqual
Key: missWarnQuals
Prefix: BMXZZ
Suffix: W
Value: Qualification {0} is missing for labor {1} on the {2} record. Please review to ensure the appropriate qualifications and labor were chosen.
Group: ismwoqual
Key: woQualNoAdd
Prefix: BMXZZ
Suffix: E
Value: Qualifications cannot be added to a WO past approval.
Group: ismwoqual
Key: woQualNoDelete
Prefix: BMXZZ
Suffix: E
Value: Qualifications cannot be removed from a WO after approval.
Group: ismwoqual
Key: woQualNoDeleteChild
Prefix: BMXZZ
Suffix: E
Value: Qualifications cannot be removed from the task record when they are associated to the parent work order.
We have 19 automation scripts. Please be aware that a few of these (such as ISMWOQUALSTATUS) will be marked as optional with remarks of what they do for you to decide if you want.
Script 1: ISMJPQUAL.DELETE
Description: Delete Qualifications when Job Plan/Job Task is Deleted
Launch Point 1: Object
Object: JOBTASK
Event: Save
Save options: Delete. Before Save
Launch Point 2: Object
Object: JOBPLAN
Event: Save
Save options: Delete. Before Save
Source:
if mbo.getString("ISMQUALIFICATION") and mbo.getString("ISMQUALIFICATION")!="NONE":
qualSet=mbo.getMboSet("ISMJPQUALREQ")
qualSet.deleteAll(mbo.NOACCESSCHECK)
Script 2: ISMJPQUALREQ.INIT
Description: Set Qualifications as Read-Only
Launch Point: Object
Object: ISMJPQUALREQ
Event: Initialize Value
Source:
owner=mbo.getOwner()
if owner and owner.isBasedOn("JOBPLAN"):
mbo.setFlag(mbo.READONLY,owner.isRevisionStatusNotAllowed())
Script 3: ISMJPQUALREQ.NEW
Description: Set Default Values on Job Plan Qualifications
Launch Point: NONE
Source:
owner=mbo.getOwner()
if owner and owner.getName() in ["JOBPLAN","JOBTASK"]:
mbo.setValue("JPNUM",owner.getString("JPNUM"))
mbo.setValue("SITEID",owner.getString("SITEID"))
if owner.getString("ORGID"):
mbo.setValue("ORGID",owner.getString("ORGID"),mbo.NOACCESSCHECK)
mbo.setFieldFlag("ORGID",mbo.READONLY,True)
else:
mbo.setFieldFlag("ORGID",mbo.REQUIRED,True)
if owner.getName()=="JOBTASK":
mbo.setValue("JOBTASKID",owner.getDouble("JOBTASKID"))
mbo.setValue("PLUSCJPREVNUM",owner.getDouble("PLUSCJPREVNUM"))
mbo.setValue("JPTASK",owner.getString("JPTASK"))
else:
mbo.setValue("JOBPLANID",owner.getDouble("JOBPLANID"))
mbo.setValue("PLUSCJPREVNUM",owner.getDouble("PLUSCREVNUM"))
Script 4: ISMJPQUALREQ.SAVE
Description: Set Qualification Field on Save
Launch Point: Object
Object: ISMJPQUALREQ
Event: SAVE
Save Options: Add, Update, Delete. Before Save
Source:
owner=mbo.getOwner()
if owner and owner.getName() in ["JOBPLAN","JOBTASK"]:
qualSet=mbo.getThisMboSet()
service.invokeScript("ISMWOQUALLIBRARY","setQualificationField",[owner,qualSet])
Script 5: ISMQUALVAL
Description: Validate Qualifications
Launch Point: ACTION
Source:
if mbo and mbo.isBasedOn("WORKORDER"):
mbo.setValue("ISMQUALVALIDATE",True,mbo.NOACCESSCHECK)
Script 6: ISMQUALVALIDATECURUSER
Description: Validate Current User for Qualifications
Launch Point 1: Attribute
Object: WOACTIVITY
Attribute: ISMQUALVALIDATECURUSER
Event: Validate
Launch Point 2: Attribute
Object: WORKORDER
Attribute: ISMQUALVALIDATECURUSER
Event: Validate
Source:
from psdi.server import MXServer
def showError(qualMbo):
minExceptionLevel=service.getProperty("ismwoqual.validateCurrentUser.exceptionLevel")
if minExceptionLevel=="ERROR" and qualMbo.getString("EXCEPTIONTYPE")=="ERROR":
return True
elif minExceptionLevel=="WARN" and qualMbo.getString("EXCEPTIONTYPE") in ["ERROR","WARN"]:
return True
elif minExceptionLevel=="SILENT":
return True
elif not minExceptionLevel:
return True
else:
return False
if mbo.getBoolean("ISMQUALVALIDATECURUSER"):
qualDict=service.invokeScript("ISMWOQUALLIBRARY","getQualMboDict",[mbo])
laborSet=mbo.getMboSet("$ISMCURUSERLABOR","LABOR","personid=:&PERSONID& and orgid=:orgid")
laborMbo=laborSet.moveFirst()
if laborMbo and len(qualDict)>0:
workDate=MXServer.getMXServer().getDate()
for qual in qualDict.keys():
qualMbo=qualDict[qual]
found=service.invokeScript("ISMWOQUALLIBRARY","checkLaborQual",[laborMbo,qualMbo,workDate])
if not found and showError(qualMbo):
service.error("ismwoqual","curUserMissingQual",[qualMbo.getString("QUALIFICATIONID")])
elif not laborMbo and len(qualDict)>0:
for qual in qualDict.keys():
qualMbo=qualDict[qual]
if showError(qualMbo):
service.error("ismwoqual","curUserMissingQual",[qualMbo.getString("QUALIFICATIONID")])
Script 7: ISMWOQUAL.DELETE
Description: Delete Qualifications when WO/Task is Deleted
Launch Point 1: Object
Object: WOACTIVITY
Event: SAVE
Save Options: Delete. Before Save
Launch Point 2: Object
Object: WORKORDER
Event: SAVE
Save Options: Delete. Before Save
Source:
if mbo.getString("ISMQUALIFICATION") and mbo.getString("ISMQUALIFICATION")!="NONE":
qualSet=mbo.getMboSet("ISMWOQUALREQ")
qualSet.deleteAll(mbo.NOACCESSCHECK)
Script 8: ISMWOQUALJPCROSS
Description: Crossover Qualifications from Job Plan/Job Task to WO
Launch Point 1: Attribute
Object: WOACTIVITY
Attribute: JOBTASKID
Event: Run Action
Launch Point 2: Attribute
Object: WORKORDER
Attribute: JPNUM
Event: Run Action
Source:
recordMbo=None
if mbo.getString("JPNUM"):
recordMbo=mbo.getMboSet("JOBPLAN").getMbo(0)
elif mbo.getDouble("JOBTASKID"):
recordMbo=mbo.getMboSet("JOBTASK").getMbo(0)
if recordMbo and recordMbo.getString("ISMQUALIFICATION") and recordMbo.getString("ISMQUALIFICATION")!="NONE":
woQualSet=mbo.getMboSet("ISMWOQUALREQ")
if woQualSet.getSize()==0:
jobQualSet=recordMbo.getMboSet("ISMJPQUALREQ")
jobQualMbo=jobQualSet.moveFirst()
while jobQualMbo:
if jobQualMbo.getString("ORGID")==mbo.getString("ORGID"):
woQualMbo=woQualSet.add()
woQualMbo.setValue("QUALIFICATIONID",jobQualMbo.getString("QUALIFICATIONID"),jobQualMbo.NOACCESSCHECK)
woQualMbo.setValue("EXCEPTIONTYPE",jobQualMbo.getString("EXCEPTIONTYPE"),jobQualMbo.NOACCESSCHECK)
jobQualMbo=jobQualSet.moveNext()
Script 9: ISMWOQUALLIBRARY
Description: Library Scripts for WO Qualifications
IMPORTANT: You must check the checkbox for "Allow Invoking Script Functions". This allows us to call the individual methods inside this script like we need.
Launch Point: NONE
Source:
from psdi.server import MXServer
from psdi.util import MXApplicationException
assignmentRelationship="SHOWASSIGNMENT"
planLaborRelationship="SHOWPLANLABOR"
labtransRelationship="SHOWACTUALLABOR"
def getWorkDate(recordMbo,woMbo):
if recordMbo.getName()=="ASSIGNMENT" and recordMbo.getDate("SCHEDULEDATE"):
return recordMbo.getDate("SCHEDULEDATE")
elif recordMbo.getName()=="LABTRANS":
return recordMbo.getDate("STARTDATE")
elif woMbo.getDate("SCHEDSTART"):
return woMbo.getDate("SCHEDSTART")
elif woMbo.getDate("TARGSTARTDATE"):
return woMbo.getDate("TARGSTARTDATE")
else:
return MXServer.getMXServer().getDate()
def checkQualForSet(attributename,qualMbo,woMbo,service):
qualMbo.setValue(attributename,True,qualMbo.NOACCESSCHECK)
recordSet=None
if attributename=="METACTUALS":
recordSet=woMbo.getMboSet(labtransRelationship)
elif attributename=="METPLANS":
recordSet=woMbo.getMboSet(planLaborRelationship)
elif attributename=="METASSIGNMENTS":
recordSet=woMbo.getMboSet(assignmentRelationship)
recordMbo=recordSet.moveFirst()
while recordMbo:
if recordMbo.getString("LABORCODE") and not checkQualForMbo(qualMbo,recordMbo,woMbo):
qualMbo.setValue(attributename,False,qualMbo.NOACCESSCHECK)
break
recordMbo=recordSet.moveNext()
def checkMboSetsForQuals(woMbo,service):
qualDict=getQualMboDict(woMbo)
checkMboSetForQuals(woMbo.getMboSet(assignmentRelationship),woMbo,service,qualDict)
checkMboSetForQuals(woMbo.getMboSet(planLaborRelationship),woMbo,service,qualDict)
validateLabor=service.getProperty("ismwoqual.validate.labtrans")
if validateLabor and validateLabor=="1":
checkMboSetForQuals(woMbo.getMboSet(labtransRelationship),woMbo,service,qualDict)
def buildQualDict(woMbo,qualDict,qualSet):
qualMbo=qualSet.moveFirst()
while qualMbo:
if qualMbo.getString("QUALIFICATIONID") not in qualDict.keys() and not qualMbo.toBeDeleted():
qualDict[qualMbo.getString("QUALIFICATIONID")]=qualMbo
qualMbo=qualSet.moveNext()
return qualDict
def getQualMboDict(woMbo):
qualDict={}
qualSet=None
if woMbo.getBoolean("ISTASK"):
qualSet=woMbo.getMboSet("ISMWOQUALTASKPARENT")
else:
qualSet=woMbo.getMboSet("ISMWOQUALREQ")
qualDict=buildQualDict(woMbo,qualDict,qualSet)
return qualDict
def checkMboSetForQuals(recordSet,woMbo,service,qualDict):
counter=0
recordMbo=recordSet.getMbo(counter)
while recordMbo:
if not recordMbo.toBeDeleted():
checkMboForQuals(recordMbo,woMbo,service,qualDict)
counter+=1
recordMbo=recordSet.getMbo(counter)
def checkMboForQuals(recordMbo,woMbo,service,qualDict):
if not recordMbo.getString("LABORCODE"):
recordMbo.setValue("ISMQUALIFICATIONMET","NA",recordMbo.NOACCESSCHECK)
else:
allMatch=True
someMatch=False
for qualification in qualDict.keys():
qualMbo=qualDict[qualification]
if not qualMbo.toBeDeleted():
matches=checkQualForMbo(qualMbo,recordMbo,woMbo)
if matches:
someMatch=True
else:
allMatch=False
handleMissingQualification(woMbo,qualMbo,recordMbo,service)
if len(qualDict)==0:
recordMbo.setValue("ISMQUALIFICATIONMET","NA",recordMbo.NOACCESSCHECK)
elif someMatch and not allMatch:
recordMbo.setValue("ISMQUALIFICATIONMET","PARTIAL",recordMbo.NOACCESSCHECK)
elif allMatch:
recordMbo.setValue("ISMQUALIFICATIONMET","ALL",recordMbo.NOACCESSCHECK)
else:
recordMbo.setValue("ISMQUALIFICATIONMET","NONE",recordMbo.NOACCESSCHECK)
def handleMissingQualification(woMbo,qualMbo,recordMbo,service):
params=[qualMbo.getString("QUALIFICATIONID"),recordMbo.getString("LABORCODE"),recordMbo.getName()]
if qualMbo.getString("EXCEPTIONTYPE")=="ERROR":
if qualMbo.toBeAdded():
service.error("ismwoqual","existingRecordRequiredQuals",params)
else:
service.error("ismwoqual","missRequiredQuals",params)
elif qualMbo.getString("EXCEPTIONTYPE")=="WARN":
mxe=MXApplicationException("ismwoqual","missWarnQuals",params)
recordMbo.getThisMboSet().addWarning(mxe)
def checkLaborQual(laborMbo,qualMbo,workDate):
laborQualSet=laborMbo.getMboSet("LABORQUAL")
laborQualMbo=laborQualSet.moveFirst()
while laborQualMbo:
if laborQualMbo.getString("QUALIFICATIONID")==qualMbo.getString("QUALIFICATIONID"):
if laborQualMbo.getInternalStatus()=="ACTIVE" and (not laborQualMbo.getDate("EFFDATE") or laborQualMbo.getDate("EFFDATE").before(workDate) or laborQualMbo.getDate("EFFDATE").equals(workDate)) and (not laborQualMbo.getDate("ENDDATE") or laborQualMbo.getDate("ENDDATE").after(workDate)):
return True
else:
return False
laborQualMbo=laborQualSet.moveNext()
return False
def checkQualForMbo(qualMbo,recordMbo,woMbo):
laborSet=recordMbo.getMboSet("LABOR")
laborSet.reset()
laborMbo=laborSet.getMbo(0)
return checkLaborQual(laborMbo,qualMbo,getWorkDate(recordMbo,woMbo))
def setQualificationField(woJpMbo,qualSet):
qualList=[]
counter=0
while qualSet.getMbo(counter):
qualMbo=qualSet.getMbo(counter)
if qualMbo.getString("QUALIFICATIONID") and not qualMbo.toBeDeleted():
qualList.append(qualMbo.getString("QUALIFICATIONID"))
counter+=1
if len(qualList)==1:
woJpMbo.setValue("ISMQUALIFICATION",qualList[0],woJpMbo.NOACCESSCHECK)
elif len(qualList)==0:
woJpMbo.setValue("ISMQUALIFICATION","NONE",woJpMbo.NOACCESSCHECK)
elif len(qualList)>1:
woJpMbo.setValue("ISMQUALIFICATION","MULTIPLE",woJpMbo.NOACCESSCHECK)
Script 10: ISMWOQUALMETACTUAL
OPTIONAL: This script populates some non-persistent attributes on ISMWOQUALREQ to make it easier to see which qualifications are met on all planned labor, assignment, or labor transaction records. It has no other functional impact.
Description: Populate non-persistent attributes when being viewed in qualification dialog
Launch Point: Attribute
Object: ISMWOQUALREQ
Attribute: METACTUALS
Event: Initialize Value
Source:
owner=mbo.getOwner()
if owner and owner.isBasedOn("WORKORDER"):
service.invokeScript("ISMWOQUALLIBRARY","checkQualForSet",["METACTUALS",mbo,owner,service])
service.invokeScript("ISMWOQUALLIBRARY","checkQualForSet",["METASSIGNMENTS",mbo,owner,service])
service.invokeScript("ISMWOQUALLIBRARY","checkQualForSet",["METPLANS",mbo,owner,service])
Script 11: ISMWOQUALREQ.CANADD
Description: Restrict Adding new Qualifications
Launch Point: Object
Object: ISMWOQUALREQ
Event: Allow Object Creation
Source:
owner=mboset.getOwner()
if owner and owner.isBasedOn("WORKORDER") and owner.getString("FIRSTAPPRSTATUS"):
service.error("ismwoqual","woQualNoAdd")
Script 12: ISMWOQUALREQ.CANDELETE
Description: Restrict Deleting Qualifications
Launch Point: Object
Object: ISMWOQUALREQ
Event: Allow Object Deletion
Source:
owner=mbo.getOwner()
if owner and owner.isBasedOn("WORKORDER") and owner.getString("FIRSTAPPRSTATUS"):
service.error("ismwoqual","woQualNoDelete")
if owner and owner.isBasedOn("WORKORDER") and owner.getString("WONUM")!=mbo.getString("WONUM"):
service.error("ismwoqual","woQualNoDeleteChild")
Script 13: ISMWOQUALREQ.NEW
Description: Default Values on New Qualifications
Launch Point: NONE
Source:
owner=mbo.getOwner()
if owner and owner.isBasedOn("WORKORDER"):
mbo.setValue("WONUM",owner.getString("WONUM"),mbo.NOACCESSCHECK)
mbo.setValue("SITEID",owner.getString("SITEID"),mbo.NOACCESSCHECK)
mbo.setValue("ORGID",owner.getString("ORGID"),mbo.NOACCESSCHECK)
Script 14: ISMWOQUALREQ.SAVE
Description: Validate WO Qualifications on Save
Launch Point: Object
Object: ISMWOQUALREQ
Event: SAVE
Save Options: Add, Update, Delete. Before Save
Source:
owner=mbo.getOwner()
if owner and owner.isBasedOn("WORKORDER"):
qualSet=mbo.getThisMboSet()
owner.setValue("ISMQUALVALIDATE",True,owner.NOACCESSCHECK)
service.invokeScript("ISMWOQUALLIBRARY","setQualificationField",[owner,qualSet])
Script 15: ISMWOQUALSTATUS
OPTIONAL: This script triggers the current user validation when the WO is being completed and the system property is enabled.
Description: Validate Current User Qualifications on WO Status Changes
Launch Point 1: Attribute
Object: WORKORDER
Attribute: STATUS
Event: Validate
Launch Point 2: Attribute
Object: WOACTIVITY
Attribute: STATUS
Event: Validate
Source:
if interactive and mbo.getInternalStatus() in ["COMP"] and service.getProperty("ismwoqual.validateCurrentUser")=="1":
mbo.setValue("ISMQUALVALIDATECURUSER",True,mbo.NOACCESSCHECK)
Script 16: ISMWOQUALVALIDATE
Description: Validate WO Qualifications
Launch Point 1: Attribute
Object: WORKORDER
Attribute: ISMQUALVALIDATE
Event: Validate
Launch Point 2: Attribute
Object: WOACTIVITY
Attribute: ISMQUALVALIDATE
Event: Validate
Source:
if mbo.getBoolean("ISMQUALVALIDATE"):
service.invokeScript("ISMWOQUALLIBRARY","checkMboSetsForQuals",[mbo,service])
Script 17: JOBPLAN.DUPLICATE
Description: ISM Copy Qualifications on Duplication/Revision of JOBPLAN
Launch Point: NONE
Source:
from psdi.server import MXServer
def copyQualifications(jobplanMbo,oldQualSet,newQualSet,isRevision):
oldQualMbo=oldQualSet.moveFirst()
while oldQualMbo:
newQualMbo=newQualSet.add()
newQualMbo.setValue("ORGID",oldQualMbo.getString("ORGID"),newQualMbo.NOACCESSCHECK)
newQualMbo.setValue("QUALIFICATIONID",oldQualMbo.getString("QUALIFICATIONID"),newQualMbo.NOACCESSCHECK)
newQualMbo.setValue("EXCEPTIONTYPE",oldQualMbo.getString("EXCEPTIONTYPE"),newQualMbo.NOACCESSCHECK)
if isRevision:
newQualMbo.setValue("PLUSCJPREVNUM",jobplanMbo.getNextRevNum(),newQualMbo.NOACCESSCHECK)
oldQualMbo=oldQualSet.moveNext()
if mbo.getString("ISMQUALIFICATION") and mbo.getString("ISMQUALIFICATION")!="NONE":
newQualSet=dupmbo.getMboSet("ISMJPQUALREQ")
oldQualSet=mbo.getMboSet("ISMJPQUALREQ")
isRevision=MXServer.getBulletinBoard().isPosted("jobplan.REVISEJOBPLAN",mbo.getUserInfo())
copyQualifications(mbo,oldQualSet,newQualSet,isRevision)
Script 18: JOBTASK.DUPLICATE
Description: ISM Copy Qualifications on Duplication/Revision of JOBTASK
Launch Point: NONE
Source:
from psdi.server import MXServer
def copyQualifications(jobplanMbo,oldQualSet,newQualSet,isRevision):
oldQualMbo=oldQualSet.moveFirst()
while oldQualMbo:
newQualMbo=newQualSet.add()
newQualMbo.setValue("ORGID",oldQualMbo.getString("ORGID"),newQualMbo.NOACCESSCHECK)
newQualMbo.setValue("QUALIFICATIONID",oldQualMbo.getString("QUALIFICATIONID"),newQualMbo.NOACCESSCHECK)
newQualMbo.setValue("EXCEPTIONTYPE",oldQualMbo.getString("EXCEPTIONTYPE"),newQualMbo.NOACCESSCHECK)
if isRevision:
newQualMbo.setValue("PLUSCJPREVNUM",jobplanMbo.getNextRevNum(),newQualMbo.NOACCESSCHECK)
oldQualMbo=oldQualSet.moveNext()
if mbo.getString("ISMQUALIFICATION") and mbo.getString("ISMQUALIFICATION")!="NONE" and dupmbo.getName()=="JOBTASK":
newQualSet=dupmbo.getMboSet("ISMJPQUALREQ")
oldQualSet=mbo.getMboSet("ISMJPQUALREQ")
isRevision=MXServer.getBulletinBoard().isPosted("jobplan.REVISEJOBPLAN")
owner=mbo.getOwner()
if owner and owner.getName()=="JOBPLAN":
copyQualifications(owner,oldQualSet,newQualSet,isRevision)
Script 19: ISMQUALRECORD.SAVE
Description: Validate Transaction Record (WPLABOR, ASSIGNMENT, LABTRANS, etc.) Prior to Save for Qualifications
Launch Point 1: Object
Object: ASSIGNMENT
Event: SAVE
Save Options: Add/Update/Delete. Before Save
Launch Point 2: Object
Object: LABTRANS
Event: SAVE
Save Options: Add/Update/Delete. Before Save
Launch Point 3: Object
Object: WPLABOR
Event: SAVE
Save Options: Add/Update/Delete. Before Save
Source:
owner=mbo.getOwner()
if owner and owner.isBasedOn("WORKORDER"):
if mbo.getName()=="ASSIGNMENT" and not mbo.getThisMboSet().getRelationName():
qualDict=service.invokeScript("ISMWOQUALLIBRARY","getQualMboDict",[owner])
service.invokeScript("ISMWOQUALLIBRARY","checkMboForQuals",[mbo,owner,service,qualDict])
elif mbo.getName()=="LABTRANS" and service.getProperty("ismwoqual.validate.labtrans")=="0":
service.log("Skipped labor validation due to property disabled")
else:
owner.setValue("ISMQUALVALIDATE",True,owner.NOACCESSCHECK)
elif owner and owner.isBasedOn("LABORCRAFTRATE") and mbo.getName()=="ASSIGNMENT":
woMbo=mbo.getMboSet("WORKORDER").getMbo(0)
qualDict=service.invokeScript("ISMWOQUALLIBRARY","getQualMboDict",[woMbo])
service.invokeScript("ISMWOQUALLIBRARY","checkMboForQuals",[mbo,woMbo,service,qualDict])
#MaximoIntegrationandScripting#Maximo#AssetandFacilitiesManagement