Maintenance Document Business Rules
Most documents, maintenance documents included, have rules to check the document for validity. Here we explore how to create business rules for a maintenance document.
Creating a rules class
First of all, for maintenance document rules, we need to extend org.kuali.kfs.kns.maintenance.rules.MaintenanceDocumentRuleBase, which is a class that implements org.kuali.kfs.kns.maintenance.rules.MaintenanceDocumentRule. This class has a lot of built in functionality: for instance, it automatically validates a maintenance document against the attributes in business object data dictionary files and enforces authorization rules. Rules classes that extend it also get useful utility methods for adding errors to the document and testing against parameterized rules. Indeed, the class has so much functionality that if we don't specify a rules class in the data dictionary for our maintenance document, it will simply use MaintenanceDocumentRuleBase.
Still, there are many reasons why a maintenance document may need to extend the rule logic in MaintenanceDocumentRuleBase, but how do we do that? First of all, it's important to realize that the rules system is based off of events. For maintenance documents, there's really only three events that non-global maintenance documents need to concern themselves with: saving the document, submitting the document for routing, and approving the document. And therefore, there's three methods that we override of MaintenanceDocumentRuleBase for most of our rules:
- processCustomSaveDocumentBusinessRules
- processCustomRouteDocumentBusinessRules
- processCustomApproveDocumentBusinessRules
Each of these methods takes in a document and returns a boolean for if the rules passed or not. Before we start looking at how to write rules, though, we need to examine what sort of document the rules methods get handed.
There is one more rule method to note here before we look at the document the rules methods are handed:
- processCustomAddCollectionLineBusinessRules
We'll take a look at that when we explore how to validate collections.
MaintenanceDocument and Maintainable
As it turns out, maintenance document rule methods are handed a document as a parameter. But whereas transactional documents must be programmatically defined, and therefore the rules writer can look at a class that represents the document, we never programmatically defined a document for our maintenance document. That's kind of the point of the whole framework. So what do these rule classes get passed?
As it turns out, all maintenance documents use the same document class: org.kuali.core.document.MaintenanceDocument. For maintenance documents, this fact is set in stone and unalterable; unlike transactional documents, where one can define their own document class, a maintenance document always uses "MaintenanceDocumentBase" as its document class. It's a fairly sizable class, so those very interested in maintenance documents should certainly take a look at it. There are a couple of methods with particular importance to rules, however:
- isNew(), isEdit(), isNewWithExisting(): these boolean methods let us know the source of the maintenance document. isNew() means that the business object in the document is just being created, isEdit() are edits on those business objects, and isNewWithExisting() is a copy of an existing business object. Rules may come into effect on a copy when they are not in effect on a newly created business object, so this is useful information.
- getNewMaintainableObject(), getOldMaintainableObject(), isOldBusinessObjectInDocument(): this is truly the heart of MaintenanceDocument - the business object being created, updated, or copied. Or at least a wrapper around it; to get the business object itself, we need to call "getNewMaintainableObject().getBusinessObject()." The new business object is always populated, and it is always the business object that will exist as a result of the maintenance document process. If the maintenance document represents either an edit or a copy, then the old business object will be populated (and isOldBusinessObjectInDocument() will return true). In the case of an edit, getOldMaintainableObject() will return the pre-edited version of the business object; in the case of a copy, the getOldMaintainableObject() will return the business object being copied. When a business object is being created with a maintenance document, isOldBusinessObjectInDocument() will return false and getOldMaintainableObject() will return null.
Wait a moment, though. What are those wrapper things that getNewMaintainableObject() and getOldMaintainableObject() return? Because neither return PersistableBusinessObjects but instead "Maintainables." What are those?
Rest assured, org.kuali.core.maintenance.Maintainable gives us access to our business objects - that, through that method we mentioned: getBusinessObject(). There are lots of other interesting things that Maintainables do as well; because MaintenanceDocument is unalterable, many of the customization hooks for maintenance documents exist in these "Maintainable" wrappers. However, for the sake of the rules, all we need to know is that we can get access to our business objects through the
getNewMaintainableObject().getBusinessObject() and
getOldMaintainableObject().getBusinessObject() methods.
Maintenance documents with rules validate the business objects, specifically the new business object (as it's a fair assumption that the old business object was already validated). Therefore, now that we know how to access these business objects, let's see how to write rules.
Writing the rulesÂ
To write rules, you extends rule base class and then return true/false depending the validation that you perform. A good sample here is AssetRule.java. Here this is a method invoked from processCustomSaveDocumentBusinessRules(). Validation performed here will return false and set the error message appropriately in the message map. Adding the error is done by putFieldError() helper method provided by MaintenanceDocumentRuleBase.
protected boolean validateFabricationDetails() { boolean valid = true; if (newAsset.getFabricationEstimatedTotalAmount() != null && newAsset.getFabricationEstimatedTotalAmount().isNegative()) { putFieldError(CamsPropertyConstants.Asset.FABRICATION_ESTIMATED_TOTAL_AMOUNT, CamsKeyConstants.ERROR_FABRICATION_ESTIMATED_TOTAL_AMOUNT_NEGATIVE); valid &= false; } if (newAsset.getEstimatedFabricationCompletionDate() != null && newAsset.getEstimatedFabricationCompletionDate().before(DateUtils.clearTimeFields(dateTimeService.getCurrentDate()))) { putFieldError(CamsPropertyConstants.Asset.ESTIMATED_FABRICATION_COMPLETION_DATE, CamsKeyConstants.ERROR_ESTIMATED_FABRICATION_COMPLETION_DATE_PAST); valid &= false; } if (newAsset.getFabricationEstimatedRetentionYears() != null && newAsset.getFabricationEstimatedRetentionYears().intValue() < 0) { putFieldError(CamsPropertyConstants.Asset.FABRICATION_ESTIMATED_RETENTION_YEARS, CamsKeyConstants.ERROR_ESTIMATED_FABRICATION_LIFE_LIMIT_NEGATIVE); valid &= false; } return valid; }
Validating collections
Some maintenance documents (in KFS, typically global maintenance documents, though there are others as well) have collections associated with them. Just as the accounting document framework can validate an accounting line as soon as the line is added to the document, we want these collections to have the ability to validate collection lines as soon as they are added. Therefore, we have our fourth rule processing method:
public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject line)
This returns a true or false based on if the line should be added to the collection, just like regular rules can stop submitting for route or saving if a false is returned from the rule method. We have more parameters than the other rule methods though: not only the maintenance document, but also the name of the collection that is being edited and the "line" - the new business object being added to the given collection. The name is included in case the maintenance document has multiple collections that can be added to. Any of the rules classes for global documents, such as org.kuali.kfs.coa.document.validation.impl.OrganizationReversionGlobalRule have good examples of the use of this method.
Creating prompt-before-rules
The rules framework we've discussed so far is powerful, but has some limitations. This is where prompt before validation rules come in. Prompt before validation rules provide additional functionality and validation checking. Please review the Prompt Before-rules documentation for detailed explanations of their purpose, how they work and how to implement them for maintenance documents.
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.