Maintenance Document Data Dictionary
Every document in KFS has metadata associated with it, and the metadata for maintenance documents exists in the document's data dictionary configuration. The data dictionary specifies practically everything for a maintenance document: it declares the user interface for the form, ties rules and document authorizers to the document as well as the document's workflow routing and search attributes. Because the data dictionary is so central, we should start our expedition of putting maintenance documents together by taking apart the data dictionaries for a maintenance document or two.
For an excellent example of a maintenance document's data dictionary, let's take a look at the maintenance document for Asset Maintenance org/kuali/kfs/module/cam/document/datadictionary/ is the directory which contains the data dictionary configuration for the AssetMaintenanceDocument, and here it is: some sections are removed which are duplicate and not required for this discussion.
<bean id="AssetMaintenanceDocument" parent="AssetMaintenanceDocument-parentBean"/> <bean id="AssetMaintenanceDocument-parentBean" abstract="true" parent="MaintenanceDocumentEntry"> <property name="businessObjectClass" value="org.kuali.kfs.module.cam.businessobject.Asset"/> <property name="maintainableClass" value="org.kuali.kfs.module.cam.document.AssetMaintainableImpl"/> <property name="allowsNewOrCopy" value="false"/> <property name="documentTypeName" value="CASM"/> <property name="businessRulesClass" value="org.kuali.kfs.module.cam.document.validation.impl.AssetRule"/> <property name="documentAuthorizerClass" value="org.kuali.kfs.module.cam.document.authorization.AssetAuthorizer"/> <property name="documentPresentationControllerClass" value="org.kuali.kfs.module.cam.document.authorization.AssetPresentationController"/> <property name="workflowAttributes"> <ref bean="AssetMaintenanceDocument-workflowAttributes"/> </property> <property name="additionalSectionsFile" value="/jsp/module/cams/AssetPendingLookup.jsp"/> <property name="maintainableSections"> <list> <ref bean="AssetMaintenanceDocument-AssetDetailInformation"/> <ref bean="AssetMaintenanceDocument-AssetLocation"/> <ref bean="AssetMaintenanceDocument-OrganizationInformation"/> <ref bean="AssetMaintenanceDocument-PaymentInformation"/> ..... </list> </property> <property name="defaultExistenceChecks"> <list> <bean parent="ReferenceDefinition" p:attributeName="campus" p:attributeToHighlightOnFail="campusCode"/> ..... </list> </property> <property name="lockingKeys"> <list> <value>capitalAssetNumber</value> </list> </property> </bean> .... <bean id="AssetMaintenanceDocument-workflowAttributes" parent="AssetMaintenanceDocument-workflowAttributes-parentBean"/> <bean id="AssetMaintenanceDocument-workflowAttributes-parentBean" abstract="true" parent="WorkflowAttributes"> <property name="searchingTypeDefinitions"> <list> <ref bean="SearchingType-CapitalAssetManagementComplexMaintenanceDocument-campusTagNumber"/> <ref bean="SearchingType-CapitalAssetManagementComplexMaintenanceDocument-organizationOwnerChartOfAccountsCode"/> <ref bean="SearchingType-CapitalAssetManagementComplexMaintenanceDocument-organizationCode"/> <ref bean="SearchingType-CapitalAssetManagementComplexMaintenanceDocument-organizationOwnerAccountNumber"/> </list> </property> <property name="routingTypeDefinitions"> <map> <entry key="OrganizationHierarchy" value-ref="RoutingType-CapitalAssetManagementComplexMaintenanceDocument-OrganizationHierarchy"/> </map> </property> </bean> </beans>
Note that all data dictionary document configurations, maintenance or transactional, belong in the org/kuali/kfs/module/{module name}/document/datadictionary/ directory. Now, there's a lot to this data dictionary configuration, so we'll examine it one piece at a time.
Data Dictionary File Name
Maintenance document data dictionary file names usually uses the convention of <Simple Business Object Name> + "MaintenanceDocument.xml". For example, the Asset (BO) maintenance document should be named "AssetMaintenanceDocument.xml".
The basic setup
Let us look at the sample here and understand what each attribute does here and how used by framework to provide a full fledged document that add/edit a BO record.
<property name="businessObjectClass" value="org.kuali.kfs.module.cam.businessobject.Asset"/> <property name="maintainableClass" value="org.kuali.kfs.module.cam.document.AssetMaintainableImpl"/> <property name="allowsNewOrCopy" value="false"/> <property name="documentTypeName" value="CASM"/> <property name="businessRulesClass" value="org.kuali.kfs.module.cam.document.validation.impl.AssetRule"/> <property name="documentAuthorizerClass" value="org.kuali.kfs.module.cam.document.authorization.AssetAuthorizer"/> <property name="documentPresentationControllerClass" value="org.kuali.kfs.module.cam.document.authorization.AssetPresentationController"/>
The most basic thing we do in the maintenance document is set the business object that will be maintained by the document. In this case, it's a org.kuali.kfs.module.cam.businessobject.Asset, and we set that with the businessObjectClass property. Next is maintainableClass which provides enough flexibility to customize the document function specific to a BO. Please take a look at org.kuali.kfs.module.cam.document.AssetMaintainableImpl
and methods that overrides core functions to provide the customization needed by Asset BO.
doRouteStatusChange(DocumentHeader) - supports a complex Asset lock mechanism required by Assets Management
generateMaintenanceLocks() - overrides default maintenance locking mechanism
processAfterEdit(MaintenanceDocument, Map) - computes and sets view-only information required by Asset Maintenance Document
processAfterNew(MaintenanceDocument, Map<String, String[]>) - setup default information
saveBusinessObject() - Sets the asset number in case of new and updates off campus information
A full discussion of the rules and authorizations can be found at the Document Data Dictionary page.
Document Type
Every document has a unique document type name (kfs convention is an abbreviated code) which helps to define the identity of a document and used by workflow and framework to load its behavior.
<property name="documentTypeName" value="CASM"/>
Easy rules
The next maintenance document specific property is "defaultExistenceChecks." Certain document validations are so omnipresent that they can simply be declared - typically validations that certain fields of a document are required.
<property name="defaultExistenceChecks"> <list> <bean parent="ReferenceDefinition" p:attributeName="campus" p:attributeToHighlightOnFail="campusCode"/> <bean parent="ReferenceDefinition" p:attributeName="building" p:attributeToHighlightOnFail="buildingCode"/> <bean parent="ReferenceDefinition" p:attributeName="buildingRoom" p:attributeToHighlightOnFail="buildingRoomNumber"/> <bean parent="ReferenceDefinition" p:attributeName="organizationOwnerChartOfAccounts" p:attributeToHighlightOnFail="organizationOwnerChartOfAccountsCode"/> </list> </property>
Here we have a list of default existence checks. Default existence checks make sure that the associated business object for the document actually exists. For instance, here campus code, building code and building room number are validated when they are not blank, "attributeToHighlightOnFail" informs the framework which field on UI should be highlighted when there is no valid active reference exists in database and an error needs to be highlighted.
Of course, for this to work correctly, the foreign keys to the fields must be specified as required. We'll see how to do that when specifying the UI.
Locking keys
Since maintenance documents edit one or more business objects, we have to worry about race conditions. Asset has a complex locking mechanism because an asset can be created and payments made through many financial documents and Purchasing module, so let us take Accounting Period as a sample. Let's imagine, for instance, that we create an accounting period record, using the Accounting Period Maintenance Document, for period 01 - 2009. Let's say that across campus, some rogue Accounting Period maintainer is also creating a record for 01-2009. As our record goes through routing, we don't want the scoundrel maintainer to be able to have their changes wipe out ours. Therefore, KFS does the basic thing to prevent this race condition: it creates a lock on each business object going through workflow as part of a maintenance document. And, typically, it uses the lockingKeys property of the data dictionary for the maintenance document to create that locking representation. Here's the locking representation configuration for the Accounting Period Maintenance Document:
<property name="lockingKeys"> <list> <value>universityFiscalYear</value> <value>universityFiscalPeriodCode</value> </list> </property>
Not surprisingly, these are also the primary keys for the AccountingPeriod business object. At any rate, this prevents any one else on campus from either creating a new AccountingPeriod record or editing the AccountingPeriod with for Period 01 of 2009 while our maintenance document goes through workflow. The fields used in a locking key can be anything, as long as it marks the business object uniquely. It makes sense, then, that most locking keys are simply the primary keys for the business object.
Defining the UI
Finally, the largest part of the data dictionary: the definition of the UI through the maintenanceSections property:
<bean id="AssetMaintenanceDocument-AssetDetailInformation" parent="AssetMaintenanceDocument-AssetDetailInformation-parentBean"/> <bean id="AssetMaintenanceDocument-AssetDetailInformation-parentBean" abstract="true" parent="MaintainableSectionDefinition"> <property name="id" value="Asset Detail Information"/> <property name="title" value="Asset Detail Information"/> <property name="maintainableItems"> <list> <bean parent="MaintainableSubSectionHeaderDefinition" p:name="Asset Detail Info"/> <bean parent="MaintainableFieldDefinition" p:name="capitalAssetNumber" p:unconditionallyReadOnly="true"/> <bean parent="MaintainableFieldDefinition" p:name="organizationOwnerChartOfAccountsCode" p:required="true"/> ..... </list> </property> </bean>
The UI of a maintenance document is made up of one or more maintainable sections (Asset, for instance, uses several maintenance sections). Each section is named, and each section creates a new tab as its visual representation on the web form.
The maintainableSection, in turn, is made up of a list of maintainableFields wrapped in a maintainableItems. Each maintainableField lists the attribute that should be shown; that attribute itself has typically been defined in the data dictionary configuration for the business object. For instance, the data dictionary for the Asset business object "organizationOwnerChartOfAccountsCode" should be a text box with the size of 2, and therefore, when the maintenance framework renders the page for this document, it will display a text field 2 characters wide. We can also make any given field required which forces extra validation, though all validations described in the attributes of the business object will also be checked.
Here you can notice that there is an Presentation Controller specfied which can do some magic and conditionally hide/display sections and fields.
Let us do a summary of key things you can specify with MaintainableFieldDefinition
.
Field | Description |
---|---|
defaultValue | default value to be used for the field |
defaultValueFinderClass | a value finder class that can print the default value for the field |
noLookup | suppress default lookup icon |
overrideFieldConversions | override field conversion property, usually framework uses OJB reference/ DD relationship details to compute field conversions |
overrideLookupClass | override lookup BO class, read above comment |
readOnlyAfterAdd | when added as a collection and prevents edit, but only delete |
required | paints as required with * symbol and validates while submitted |
template | used by multiple value lookup, see below for more details |
unconditionallyReadOnly | read only when displayed no edit allowed |
webUILeaveFieldCallbackFunction | used by AJAX read below |
webUILeaveFieldFunction | used by AJAX read below |
Collections
Some maintenance documents include collections of child business objects. The classic cases are the global maintenance documents, where the information for a business object may be split into several collections.
Here in this case Asset has a collection of components that can be maintained through Asset Edit and there is no direct maintenance of AssetComponent is provide by the system. Asset has a collection (0 to many) instance of AssetComponent and so here user can add/delete a collection of components but unfortunately system doesn't allow modification of a component.
How does the Asset maintenance document do this? It's actually very easy, and again, set up through the data dictionary. See the XML definition below which provides sufficient information to framework about how the collection should be handled:
<bean id="AssetMaintenanceDocument-Components" parent="AssetMaintenanceDocument-Components-parentBean"/> <bean id="AssetMaintenanceDocument-Components-parentBean" abstract="true" parent="MaintainableSectionDefinition"> <property name="id" value="components"/> <property name="title" value="Components"/> <property name="defaultOpen" value="false"/> <property name="maintainableItems"> <list> <bean parent="MaintainableCollectionDefinition"> <property name="name" value="assetComponents"/> <property name="businessObjectClass" value="org.kuali.kfs.module.cam.businessobject.AssetComponent"/> <property name="maintainableFields"> <list> <bean parent="MaintainableFieldDefinition" p:name="componentNumber" p:unconditionallyReadOnly="true" p:readOnlyAfterAdd="true"/> <bean parent="MaintainableFieldDefinition" p:name="componentDescription" p:required="true" p:readOnlyAfterAdd="true"/> <bean parent="MaintainableFieldDefinition" p:name="componentConditionCode" p:readOnlyAfterAdd="true"/> ...... </list> </property> </bean> </list> </property> </bean>
<bean parent="MaintainableCollectionDefinition"> <property name="name" value="assetRetirementGlobalDetails"/> <property name="businessObjectClass" value="org.kuali.kfs.module.cam.businessobject.AssetRetirementGlobalDetail"/> <property name="sourceClassName" value="org.kuali.kfs.module.cam.businessobject.Asset"/> <property name="summaryTitle" value="Asset Retired"/> <property name="summaryFields"> <list> <bean parent="MaintainableFieldDefinition" p:name="capitalAssetNumber"/> </list> </property> <property name="duplicateIdentificationFields"> <list> <bean parent="MaintainableFieldDefinition" p:name="capitalAssetNumber"/> </list> </property> <property name="maintainableFields"> <list> <bean parent="MaintainableFieldDefinition" p:name="capitalAssetNumber" p:readOnlyAfterAdd="true" p:template="capitalAssetNumber" p:required="true"/> <bean parent="MaintainableFieldDefinition" p:name="asset.organizationOwnerChartOfAccountsCode" p:unconditionallyReadOnly="true" p:template="organizationOwnerChartOfAccountsCode"/> <bean parent="MaintainableFieldDefinition" p:name="asset.organizationOwnerAccountNumber" p:unconditionallyReadOnly="true" p:template="organizationOwnerAccountNumber"/>
Here, all of the maintainableFields are wrapped inside a maintainableCollection bean. That property declares a name for the collection and the businessObjectClass that is represented within the collection. This is what allows the document to present a collection of business objects to edit.
Multiple Value Lookups
Look at above collection definition and you can see how a multiple value lookup is specified for global maintenance document.
We also see a couple extra declarations: the summaryFields and the duplicateIndentificationFields. Once a new record is added to a collection in the document, that record is shown as a summary, and therefore each of the fields with an associated summaryField property are displayed as added. The duplicateIndentificationFields act like mini-locks: they prevent the same record from being added twice within the same collection. In this document, we couldn't add the asset 1594 more than once to the collection.
<bean id="OrganizationReversionMaintenanceDocument-EditOrganizationReversionDetails" parent="OrganizationReversionMaintenanceDocument-EditOrganizationReversionDetails-parentBean"/> <bean id="OrganizationReversionMaintenanceDocument-EditOrganizationReversionDetails-parentBean" abstract="true" parent="MaintainableSectionDefinition"> <property name="maintainableItems"> <list> <bean parent="MaintainableCollectionDefinition"> <property name="name" value="organizationReversionDetail"/> <property name="businessObjectClass" value="org.kuali.kfs.coa.businessobject.OrganizationReversionDetail"/> <property name="includeAddLine" value="false"/> <property name="maintainableFields"> <list> <bean parent="MaintainableFieldDefinition" p:name="organizationReversionCategory.organizationReversionCategoryName" p:unconditionallyReadOnly="true" p:required="true"/> <bean parent="MaintainableFieldDefinition" p:name="organizationReversionObjectCode" p:required="true" p:webUILeaveFieldFunction="updateObjectName"/> ............ <bean parent="MaintainableFieldDefinition" p:name="newCollectionRecord"/> </list> ......
The organization reversion details are based on OrganizationReversionCategories; there should be one detail record per category - no more and no less. Because of this, we don't want users to be able to either add or delete lines. We simply want to create the full list of details, one per category (that list is created in org.kuali.kfs.coa.maintenance.OrganizationReversionMaintainableImpl's setBusinessObject() method), with no ability to delete a line and no ability to add a line.
We prevent adding new lines on line of the XML segment, the maintainableCollection declaration. Here we see the easy and fun to interpret attribute, "includeAddLine," which in this case was set to false, which means that no new records will be able to be added to this collection. This attribute defaults to true, which means that in most cases, we can simply omit it.
On line 8, we see a maintainableField with a name of "newCollectionRecord." When this field is present, it tells the maintenance framework that any records currently existing in the collection are permanent - that is, there should not be delete buttons associated with them. This is precisely what we want for this case with the Organization Reversion records: we don't want users to be able to delete any records we've already added to the collection. However, if we had includeAddLine="false" (or had omitted it) in the maintainableCollection property above, we could add new lines to the collection and each of the new lines could be deleted (though the old lines could not be deleted).
Finally, we'll take note fields have been set so that the "unconditionallyReadOnly" attribute is true. This is because the organizationReversionCategoryName should be visible to the user but should never be changed. Once the user enters a value into the "organizationReversionObjectCode" field, some AJAX functionality will update the organizationReversionObject.financialObjectCodeName field (or, alternatively, if the page is saved, the field will update as well). It's a helpful feature to give the users immediate feedback, one that deserves further investigation. We'll discuss it presently.
AJAX
Adding AJAX events to our documents is covered elsewhere, but we can take a quick look at AccountMaintenanceDocument to see how it uses AJAX. In this example, the Account Maintenance Document wants to instantly give an error to users if the sub fund group assigned to the account is restricted, based on other values of the account.
First, we import JavaScript files.
<property name="webScriptFiles"> <list> <value>../dwr/interface/SubFundGroupService.js</value> <value>../scripts/coa/accountDocument.js</value> </list> </property>
The ../scripts/chart/accountDocument.js is a JavaScript file that defines the functions onblur_subFundGroup and checkRestrictedStatusCode_Callback. onblur_subFundGroup uses the SubFundGroupService, and to that successfully, DWR needs to create a JavaScript/Java bridge for that access. That's the purpose of the inclusion of the ../dwr/interface/SubFundGroupService.js file: it's not a real JavaScript file at all, but instead a bridge created on the fly by DWR.
Maintainable fields can then trip off the AJAX call when certain events happen:
Here, when the user leaves the UI field for the sub-fund group code, the onblur_subFundGroup JavaScript function will be called, and that should populate the name of the sub-fund group in the page under the UI field.
Kuali documentation is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License.
Kuali software is licensed for use pursuant to the Affero General Public License, version 3.
Copyright © 2014 Kuali, Inc. All rights reserved.
Portions of Kuali are copyrighted by other parties as described in the Acknowledgments screen.
Kuali ® is a registered trademark of the Trustees of Indiana University.