Technical Blog Post
Abstract
Conditional Restrictions for Classification Specifications (Attributes)
Body
IBM Maximo and Control Desk products have classifications in order to categorize records such as items, assets, locations, work orders, tickets etc. Classifications are not only useful for easily identifying, restricting or filtering records, they also bring in specifications functionality. Specification attributes are extra fields defined for a classification used with one or more Maximo objects. In spite of adding new fields to main database tables, related specification records are added to the specification tables only for the classified records. Hence, using classification specifications prevents redundancy and excessive number of columns for main database tables such as item, asset, locations, workorder, ticket, etc. which improves both database and overall system performance. Specifications are usually located in the Specifications tab of the original Maximo applications.
Figure 1 - Specifications Tab on Assets Application
Regular fields (attributes) can be conditionally or deterministically configured as required or readonly in various ways. On the contrary, specification attributes can be statically set as required fields. The customization I will present adds an interface to Maximo (or Control Desk) product to handle mandatoriness of specification attributes dynamically by using the existing conditional expression structure. It also adds another feature to conditionally validate the specification attributes and raise error messages for invalid input values. One can implement a similar feature to conditionally make the specification attributes readonly.
Note: On latter sections, there exists some Turkish labels since I implemented this solution in Turkish only. However, I have noted English translations for some important labels for comprehension.
1. Adding a custom business object to hold restriction definitions
First of all, we add a custom object (SPECCONDITIONS) via Database Configuration application which will be used to hold the restriction conditions for mandatoriness and validation of specification attributes. The attribute list for the custom object can be seen In Table 1 below.
Field ID | Description | Type | Length | Required? | Usage |
ACTIVE | Is Active? | YORN | 1 | Y | Field default value is “1”. It is set to “0” for inactive conditions. |
ASSETATTRID | Asset Attribute ID | UPPER | 8 | Y | Same as ASSETATTRIBUTE.ASSETATTRID field. It defines the asset attribute of specification. |
CHANGEBY | Change By | UPPER | 30 | Y | The last person who changed the record. |
CHANGEDATE | Change Date | DATETIME | 10 | Y | Date and time of the last change on the record. |
CLASSSTRUCTUREID | Classification Identifier | UPPER | 20 | N | Same as the CLASSSPEC.CLASSSTRUCTUREID field. It defines the classification of the specification. |
CREATEBY | Create By | UPPER | 30 | Y | The person who created the record. |
CREATEDATE | Create Date | DATETIME | 10 | Y | Creation date and time of the record. |
DESCRIPTION | Description | ALN | 50 | N | The description or comment for the specification condition record. |
SPECCONDITIONSID | Specification Conditions Unique Identifier | BIGINT | 19 | Y | System set unique identifier. |
MSGID | Message ID | ALN | 20 | N | Same as the MAXMESSAGES.MSGID field. It defines the message for invalid input conditions. |
OBJECTNAME | Object Name | UPPER | 30 | Y | Same as the CLASSUSEWITH.OBJECTVALUE field. It defines the object used for classification of the specification attribute. |
ORGID | Organization ID | UPPER | 8 | N | Same as the CLASSSTRUCTURE.ORGID field. It defines the organization of the classification. |
REQCONDITION | Required Condition | UPPER | 12 | N | Same as the CONDITION.CONDITIONNUM field. The usual Maximo condition structure is used to check if the specification attribute is required or not. |
REQISMAIN | Is Required Condition for Main Record? | YORN | 1 | Y | The value is “1” if the required condition is for the main record (item, asset, locations, workorder, ticket, etc.), the value is “0” if the required condition is for the specification record (itemspec, assetspec, locationspec, workorderspec, ticketspec, etc.) |
SITEID | Site ID | UPPER | 8 | N | Same as the CLASSSTRUCTURE.SITEID field. It defines the site of the classification. |
VALCONDITION | Validation Condition | UPPER | 12 | N | Same as the CONDITION.CONDITIONNUM field. The usual Maximo condition structure is used to check if the specification attribute is valid or not. |
VALISMAIN | Is Validation Condition for Main Record? | YORN | 1 | Y | The value is “1” if the validation condition is for the main record (item, asset, locations, workorder, ticket, etc.), the value is “0” if the validation condition is for the specification record (itemspec, assetspec, locationspec, workorderspec, ticketspec, etc.) |
Table 1 - Attribute List of Specification Conditions Object
After that we turn on Admin mode, apply Database configurations and turn Admin mode off. Then we add relationships, seen in Table 2 below, to/from our new custom object, which will be used in the Automation Scripts and applications to access remote objects.
Relation Name | Parent Object | Child Object | Where Clause |
ASSETATTRIBUTE | SPECCONDITIONS | ASSETATTRIBUTE | assetattrid = :assetattrid |
CLASSSTRUCTURE | SPECCONDITIONS | CLASSSTRUCTURE | classstructureid = :classstructureid |
MAXMESSAGES | SPECCONDITIONS | MAXMESSAGES | msgid = :msgid |
MAXOBJECT | SPECCONDITIONS | MAXOBJECT | objectname = :objectname |
REQCONDITION | SPECCONDITIONS | CONDITION | conditionnum = :reqcondition |
VALCONDITION | SPECCONDITIONS | CONDITION | conditionnum = :valcondition |
SPECCONDITION | ASSETSPEC | SPECCONDITIONS | active = 1 and objectname = 'ASSET' and assetattrid = :assetattrid and nvl(classstructureid, :classstructureid) = :classstructureid |
SPECCONDITION | ITEMSPEC | SPECCONDITIONS | active = 1 and objectname = 'ITEM' and assetattrid = :assetattrid and nvl(classstructureid, :classstructureid) = :classstructureid |
SPECCONDITION | LOCATIONSPEC | SPECCONDITIONS | active = 1 and objectname = 'LOCATIONS' and assetattrid = :assetattrid and nvl(classstructureid, :classstructureid) = :classstructureid |
SPECCONDITIONS | MAXGROUP | SPECCONDITIONS | 1 = 1 |
Table 2 - Relationship Definitions related to Specification Conditions
With the relations defined our custom specification restrictions table is ready to use. In the next steps we will implement the user interface customizations and write the custom Automation scripts.
2. Adding Specification Restrictions tab to Security Groups application to view and edit specification restrictions
In this section, we will add the user interface customization to Security Groups application. We export ”securgroup.xml” from Application Designer application and take a backup of the xml and then search for the following tab group in the xml:
<tabgroup id="datarest_tabgroup">
We add our custom tab to the data restrictions tab group. The following xml piece should be copied into the tabgroup tags:
<tab id="datarest_spec_tab" label="Ek Alan Kısıtlamaları" sigoption="SPECREST"> <table id="datarest_spec_table" label="Ek Alanlar" orderby="objectname, assetattrid" relationship="SPECCONDITIONS"> <tablebody displayrowsperpage="10" filterable="true" id="datarest_spec_table_tablebody"> <tablecol filterable="false" id="datarest_spec_table_tablebody_1" mxevent="toggledetailstate" mxevent_desc="Ayrıntı Göster" sortable="false" type="event"/> <tablecol dataattribute="objectname" id="datarest_spec_table_tablebody_2" lookup="valuelist"/> <tablecol dataattribute="assetattrid" id="datarest_spec_table_tablebody_4" lookup="assetattribute"/> <tablecol dataattribute="description" id="datarest_spec_table_tablebody_3"/> <tablecol dataattribute="classstructureid" id="datarest_spec_table_tablebody_2a" lookup="classification"/> <tablecol dataattribute="reqismain" id="datarest_spec_table_tablebody_9"/> <tablecol applink="condexpmgr" dataattribute="reqcondition" id="datarest_spec_table_tablebody_5" lookup="conditionexp" menutype="normal"/> <tablecol dataattribute="valismain" id="datarest_spec_table_tablebody_10"/> <tablecol applink="condexpmgr" dataattribute="valcondition" id="datarest_spec_table_tablebody_11" lookup="conditionexp" menutype="normal"/> <tablecol dataattribute="active" id="datarest_spec_table_tablebody_8"/> <tablecol filterable="false" id="datarest_spec_table_tablebody_7" mxevent="toggledeleterow" mxevent_desc="Satırı Silmek İçin İşaretle" mxevent_icon="btn_garbage.gif" sortable="false" type="event"/> </tablebody> <tabledetails id="datarest_spec_table_tabledetails_1"> <section id="datarest_spec_table_tabledetails_grid30" label="Ayrıntılar"> <sectionrow id="datarest_spec_table_tabledetails_r0"> <sectioncol id="datarest_spec_table_tabledetails_r0_c1"> <section id="datarest_spec_table_tabledetails_grid30a0"> <textbox dataattribute="description" id="datarest_spec_table_tabledetailsrow1_3"/> <multiparttextbox dataattribute="objectname" descdataattribute="maxobject.description" descinputmode="readonly" id="datarest_spec_table_tabledetailsrow1_1" lookup="valuelist"/> <multiparttextbox dataattribute="assetattrid" descdataattribute="assetattribute.description" descinputmode="readonly" id="datarest_spec_table_tabledetailsrow1_1a" lookup="ATTRIBUTENAME"/> <multiparttextbox dataattribute="classstructureid" descdataattribute="classstructure.description" descinputmode="readonly" id="datarest_spec_table_tabledetailsrow1_2" lookup="classification"/> </section> </sectioncol> <sectioncol id="datarest_spec_table_tabledetails_r1_c1"> <section id="datarest_spec_table_tabledetails_grid30a"> <checkbox dataattribute="active" id="datarest_spec_table_tabledetailsrow1_9"/> <textbox dataattribute="siteid" id="datarest_spec_table_tabledetailsrow1_10" lookup="site"/> <textbox dataattribute="orgid" id="datarest_spec_table_tabledetailsrow1_11" lookup="organization"/> </section> </sectioncol> </sectionrow> </section> <section id="datarest_spec_table_tabledetails_grid31"> <sectionrow id="datarest_spec_table_tabledetails_r1"> <sectioncol id="datarest_spec_table_tabledetails_r1_c2"> <section id="datarest_spec_table_tabledetails_grid30b"> <checkbox dataattribute="reqismain" id="datarest_spec_table_tabledetailsrow1_22"/> <multiparttextbox applink="condexpmgr" dataattribute="reqcondition" descdataattribute="reqcondition.description" descinputmode="readonly" id="datarest_spec_table_tabledetailsrow1_4" lookup="conditionexp" menutype="normal"/> <textbox dataattribute="reqcondition.classname" id="datarest_spec_table_tabledetailsrow1_6" inputmode="readonly"/> <multilinetextbox columns="35" dataattribute="reqcondition.expression" id="datarest_spec_table_tabledetailsrow1_5" inputmode="readonly" rows="4"/> </section> </sectioncol> <sectioncol id="datarest_spec_table_tabledetails_r1_d2"> <section id="datarest_spec_table_tabledetails_grid30c"> <checkbox dataattribute="valismain" id="datarest_spec_table_tabledetailsrow2_22"/> <multiparttextbox applink="condexpmgr" dataattribute="valcondition" descdataattribute="VALCONDITION.DESCRIPTION" descinputmode="readonly" id="datarest_spec_table_tabledetailsrow2_4" lookup="conditionexp" menutype="normal"/> <textbox dataattribute="valcondition.classname" id="datarest_spec_table_tabledetailsrow2_6" inputmode="readonly"/> <multilinetextbox columns="35" dataattribute="valcondition.expression" id="datarest_spec_table_tabledetailsrow2_5" inputmode="readonly" rows="4"/> <multiparttextbox dataattribute="MSGID" descdataattribute="MAXMESSAGES.VALUE" descinputmode="readonly" id="1467819877346" lookup="maxmessages"/> </section> </sectioncol> </sectionrow> </section> </tabledetails> <buttongroup id="datarest_spec_table_2"> <pushbutton default="true" id="datarest_spec_table_2_1" label="Yeni Satır" mxevent="addrow"/> </buttongroup> </table> </tab>
After importing “securgroup.xml” back into the system, we can edit the user interface further (change labels or give input modes etc.) using the Application Designer canvas.
sigoption="SPECREST"
The signature option above is used for making this tab conditionally visible to a specific security group.
The resulting user interface customization can be viewed in Figure 2 below.
Figure 2 - Specification Restrictions tab with details section in Security Groups application
3. Implementation of Automation Scripts in order to process the conditions defined for specification restrictions
The creation of custom objects and application modifications were for defining and viewing the specification restrictions. After that, we need to add the logic to the system which will process the conditions defined in specification restrictions. Here, we implement two Automation Scripts for required and validation conditions respectively:
3.1 Automation Script for Specification Required Conditions
We create the following script from Automation Scripts application.
# SPECREQ.py - Automation Script for Specification Required Conditions from psdi.mbo import Mbo, MboRemote, MboSet, MboSetRemote from psdi.common.condition import MaxCondition from psdi.server import MXServer from java.util import Arrays, List # function to get the title of the specification attribute def getAttributeTitle(spec): if(spec.getMboSet("ASSETATTRIBUTE").isEmpty()): return spec.getString("ASSETATTRID") return spec.getString("ASSETATTRIBUTE.DESCRIPTION") # function to get the column name of the specification attribute: ALNVALUE, TABLEVALUE, NUMVALUE def getAttributeName(spec): types = {"ALN": "ALNVALUE", "TABLE": "TABLEVALUE", "NUMERIC": "NUMVALUE"} if(spec.getMboSet("ASSETATTRIBUTE").isEmpty()): return None datatype = spec.getString("ASSETATTRIBUTE.DATATYPE") return types[datatype] # function to check the required specification condition # returns True when the required criteria is met but the attribute is blank def specRequired(cond, spec): if(cond.isNull("REQCONDITION")): return False conditionNum = cond.getString("REQCONDITION") condition = MXServer.getMXServer().getConditionCache().get(conditionNum) if(condition == None): return False conditionMbo = mbo if cond.getBoolean("REQISMAIN") else spec return condition.evaluate(conditionMbo, False) and (spec.isNull(getAttributeName(spec)) or spec.getString(getAttributeName(spec)) == "") # main code: fetching the relevant specification record and processing the "specRequired" function # throws an error message with the list of the blank and required attribute titles if there is any canSave = True msgparams = '' relationName = mboname + "SPECCLASS" titles = {"WORKORDER": "Is Emri", "ASSET": "Demirbas", "LOCATION": "Konum", "ITEM": "Parca"} title = titles[mboname] if mboname in ('WORKORDER', 'ASSET', 'LOCATION', 'ITEM') else '' specSet = mbo.getMboSet(relationName) if(not specSet.isEmpty()): cnt = specSet.count() for i in range(cnt): spec = specSet.getMbo(i) if(spec.getMboSet("SPECCONDITION").isEmpty()): continue cond = spec.getMboSet("SPECCONDITION").getMbo(0) if(specRequired(cond, spec)): canSave = False msgparams = msgparams + '\n' + getAttributeTitle(spec) if(not canSave): errorkey = 'requiredMsg' errorgroup = 'errorMsg' params = [title, msgparams]
This script will work when the user tries to save the main record, i.e. item, asset, location, work order, ticket etc. After that, we create the associated object level launchpoints for objects ITEM, ASSET, LOCATIONS, WORKORDER etc. with BEFORE SAVE events having ADD or UPDATE options.
3.2 Automation Script for Specification Validation Conditions
We create the following script from Automation Scripts application.
# SPECVAL.py - Automation Script for Specification Validation Conditions from psdi.mbo import Mbo, MboRemote, MboSet, MboSetRemote from psdi.common.condition import MaxCondition from psdi.server import MXServer from java.util import Arrays, List from java.lang import System # function to check the specification conditions for validation # returns False when the validation criteria is not met def specValid(cond, spec): if(mbovalue is None): return True if(cond.isNull("VALCONDITION")): return True conditionNum = cond.getString("VALCONDITION") condition = MXServer.getMXServer().getConditionCache().get(conditionNum) if(condition == None): return True conditionMbo = mbo if cond.getBoolean("VALISMAIN") else spec return condition.evaluate(conditionMbo, False) # function to throw invalid error message def throwError(cond): global errorkey, errorgroup if(cond.getMboSet("MAXMESSAGES").isEmpty()): errorkey = 'invalidMsg' errorgroup = 'errorMsg' else: errorkey = cond.getString("MAXMESSAGES.MSGKEY") errorgroup = cond.getString("MAXMESSAGES.MSGGROUP") # main code: fetching the relevant specification record and processing the "specValid" function # throws an error message if the attribute value is invalid if(not mbo.getMboSet("SPECCONDITION").isEmpty()): cond = mbo.getMboSet("SPECCONDITION").getMbo(0) if(not specValid(cond, mbo)): throwError(cond)
This script will work when the user tabs out of a specification field such as ALNVALUE attribute of objects ITEMSPEC, ASSETSPEC, LOCATIONSPEC, WORKORDERSPEC etc. Hence, we also create an Attribute level launchpoint with VALIDATE event.
In Figure 3 below, we can see the custom Specification Restrictions sub-tab on Data Restrictions tab of Security Groups application.
Figure 3 - Specification Restrictions tab in Security Groups application
This solution extends regular data restrictions functionality to specification attributes for mandatoriness and validation. Similar approach can be applied for making specification attributes conditionally readonly.
UID
ibm11129509