001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.endow.document.validation.impl;
017    
018    import java.math.BigDecimal;
019    
020    import org.kuali.kfs.module.endow.EndowKeyConstants;
021    import org.kuali.kfs.module.endow.EndowPropertyConstants;
022    import org.kuali.kfs.module.endow.businessobject.HoldingTaxLot;
023    import org.kuali.kfs.module.endow.businessobject.HoldingTaxLotRebalance;
024    import org.kuali.rice.kns.document.MaintenanceDocument;
025    import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
026    import org.kuali.rice.kns.util.GlobalVariables;
027    import org.kuali.rice.kns.util.MessageMap;
028    
029    public class HoldingTaxLotRebalanceRule extends MaintenanceDocumentRuleBase {
030    
031        /**
032         * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
033         */
034        @Override
035        protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document)
036        {
037            boolean isValid = true;
038            
039            // First check for errors from parent class, and determine if any previous errors
040            // were found.
041            isValid &= super.processCustomRouteDocumentBusinessRules(document);
042            MessageMap errorMap = GlobalVariables.getMessageMap();
043            isValid &= errorMap.hasNoErrors();
044            
045            // 'newDocument' represents the re-balanced  document, and 'oldDocument' represents the original document.
046            HoldingTaxLotRebalance newBusinessObject = getNewHoldingTaxLotRebalanceMaintenceDocument(document);
047            HoldingTaxLotRebalance oldBusinessObject = getOldHoldingTaxLotRebalanceMaintenceDocument(document);
048    
049            // If no previous errors have been found, then perform our own custom error checks.
050            if (isValid) {
051                
052                // First verify that the unit and cost values are valid.
053                isValid &= validateUnitValue(newBusinessObject);
054                isValid &= validateCostValue(newBusinessObject);
055                
056                // Are the unit and cost values valid?  If not, then throw the error(s)
057                // early before further testing.  There's no reason to perform future
058                // checks on invalid values.
059                if (!isValid) {
060                    return isValid;
061                }
062                
063                // Verify that if either units or cost are zero, they're both zero.
064                isValid &= validateAllZero(newBusinessObject);
065                
066                // If only one of the units or cost fields are zero, show error to 
067                // user before continuing.
068                if (!isValid) {
069                    return isValid;
070                }
071                
072                // Verify that total units and total cost for all tax lot balances
073                // are still the same.
074                isValid &= validateTotalUnits(oldBusinessObject, newBusinessObject);
075                isValid &= validateTotalCost(oldBusinessObject, newBusinessObject);
076            }
077            
078            return isValid;
079        }
080        
081        /**
082         * Helper method, used to generate the correct path of the cost/unit value
083         * of a given tax lot.
084         * 
085         * @param index
086         * @return
087         */
088        private String getHoldingTaxLotErrorPath(int index)
089        {
090            return EndowPropertyConstants.HOLDING_TAX_LOT_REBAL_LOTS_TAB + "[" + index + "].";
091        }
092        
093        /**
094         * Helper method to get the re-balanced i.e. new business object.
095         * 
096         * @param document
097         * @return Re-balanced HoldingTaxLotRebalance business object
098         */
099        private HoldingTaxLotRebalance getNewHoldingTaxLotRebalanceMaintenceDocument(MaintenanceDocument document)
100        {
101            return (HoldingTaxLotRebalance) document.getNewMaintainableObject().getBusinessObject();
102        }
103        
104        /**
105         * 
106         * Helper method to get the original i.e. old business object.
107         * 
108         * @param document
109         * @return Original HoldingTaxLotRebalance business object 
110         */
111        private HoldingTaxLotRebalance getOldHoldingTaxLotRebalanceMaintenceDocument(MaintenanceDocument document)
112        {
113            return (HoldingTaxLotRebalance) document.getOldMaintainableObject().getBusinessObject();
114        }
115        
116        /**
117         * Verifies that the total units for all the tax lots is still the same i.e. balanced. 
118         *
119         * @param oldDocument
120         * @param newDocument
121         * @return True if total units are balanced
122         */
123        protected boolean validateTotalUnits(HoldingTaxLotRebalance oldBusinessObject, HoldingTaxLotRebalance newBusinessObject)
124        {
125            boolean isValid = true;
126            
127            // Calculate the total number of units from the new and old documents to ensure
128            // that they still match the original (old) document.
129            BigDecimal totalOldUnits  = BigDecimal.ZERO;
130            for (HoldingTaxLot taxLot : newBusinessObject.getHoldingTaxLots()) {
131                totalOldUnits = totalOldUnits.add(taxLot.getUnits());
132            }
133    
134            BigDecimal totalNewUnits  = BigDecimal.ZERO;
135            for (HoldingTaxLot taxLot : oldBusinessObject.getHoldingTaxLots()) {
136                totalNewUnits = totalNewUnits.add(taxLot.getUnits());
137            }
138            
139            // Determine if the calculated total value of all the tax lots is still equal
140            // to the original total i.e. is still balanced.
141            if (totalNewUnits.compareTo(totalOldUnits) != 0) {
142                putFieldError(EndowPropertyConstants.HOLDING_TAX_LOT_REBAL_LOTS_TAB, 
143                        EndowKeyConstants.HoldingTaxLotRebalanceConstants.ERROR_HLDG_TAX_LOT_REBALANCE_TOTAL_UNITS_NOT_BALANCED);
144                isValid = false;
145            }
146            
147            return isValid;
148        }
149        
150        /**
151         * Verifies that the total cost for all the tax lots is still the same i.e. balanced.
152         * 
153         * @param oldDocument
154         * @param newDocument
155         * @return True if total cost is balanced
156         */
157        protected boolean validateTotalCost(HoldingTaxLotRebalance oldBusinessObject, HoldingTaxLotRebalance newBusinessObject)
158        {
159            boolean isValid = true;
160            // Calculate the total cost from the new and old documents to ensure
161            // that they still match the original (old) document.
162            BigDecimal totalOldCost = BigDecimal.ZERO;
163            for (HoldingTaxLot taxLot : newBusinessObject.getHoldingTaxLots()) {
164                totalOldCost = totalOldCost.add(taxLot.getCost());
165            }
166            
167            BigDecimal totalNewCost = BigDecimal.ZERO;
168            for (HoldingTaxLot taxLot : newBusinessObject.getHoldingTaxLots()) {
169                totalNewCost = totalNewCost.add(taxLot.getCost());
170            }
171            
172            // Determine if the calculated total value of all the tax lots is still equal
173            // to the original total i.e. is still balanced.
174            if (totalNewCost.compareTo(totalOldCost) != 0) {
175                putFieldError(EndowPropertyConstants.HOLDING_TAX_LOT_REBAL_LOTS_TAB, 
176                        EndowKeyConstants.HoldingTaxLotRebalanceConstants.ERROR_HLDG_TAX_LOT_REBALANCE_TOTAL_COST_NOT_BALANCED);
177                isValid = false;
178            }
179            
180            return isValid;
181        }
182        
183        /**
184         * Validates if the units value isn't negative.
185         * 
186         * @param newDocument
187         * @return True if units is non-negative.
188         */
189        protected boolean validateUnitValue(HoldingTaxLotRebalance newBusinessObject)
190        {
191            boolean isValid = true;
192            for (HoldingTaxLot taxLot : newBusinessObject.getHoldingTaxLots()) {
193                if (taxLot.getUnits().signum() < 0) {
194                    int index = newBusinessObject.getHoldingTaxLots().indexOf(taxLot);
195                    putFieldError(getHoldingTaxLotErrorPath(index) + EndowPropertyConstants.HOLDING_TAX_LOT_UNITS, 
196                            EndowKeyConstants.HoldingTaxLotRebalanceConstants.ERROR_HLDG_TAX_LOT_REBALANCE_UNITS_INVALID);
197                    isValid = false;
198                }
199            }
200            
201            return isValid;
202        }
203        
204        /**
205         * Validates if the cost value isn't negative.
206         * 
207         * @param newDocument
208         * @return True if cost is non-negative.
209         */
210        protected boolean validateCostValue(HoldingTaxLotRebalance newBusinessObject)
211        {
212            boolean isValid = true;
213            for (HoldingTaxLot taxLot : newBusinessObject.getHoldingTaxLots()) {
214                if (taxLot.getCost().signum() < 0) {
215                    int index = newBusinessObject.getHoldingTaxLots().indexOf(taxLot);
216                    putFieldError(getHoldingTaxLotErrorPath(index) + EndowPropertyConstants.HOLDING_TAX_LOT_COST, 
217                            EndowKeyConstants.HoldingTaxLotRebalanceConstants.ERROR_HLDG_TAX_LOT_REBALANCE_COST_INVALID);
218                    isValid = false;
219                }
220            }
221            return isValid;
222        }
223        
224        /**
225         * This method ensures that if the unit or cost field for a particular tax
226         * is zero that both are zero.  If one field is zero, both must be zero.
227         * 
228         * @param newDocument
229         * @return false if only one of the units or cost fields are zero.
230         */
231        protected boolean validateAllZero(HoldingTaxLotRebalance newBusinessObject)
232        {
233            boolean isValid = true;
234            for (HoldingTaxLot taxLot : newBusinessObject.getHoldingTaxLots()) {
235                boolean zeroUnits = (taxLot.getUnits().signum() == 0);
236                boolean zeroCost  = (taxLot.getCost().signum()  == 0);
237                if (zeroUnits || zeroCost) {
238                    if (!(zeroUnits && zeroCost)) {
239                        int index = newBusinessObject.getHoldingTaxLots().indexOf(taxLot);
240                        putFieldError(getHoldingTaxLotErrorPath(index) + EndowPropertyConstants.HOLDING_TAX_LOT_UNITS, 
241                                EndowKeyConstants.HoldingTaxLotRebalanceConstants.ERROR_HLDG_TAX_LOT_REBALANCE_UNITS_COST_ZERO);
242                        isValid = false;
243                    }
244                }
245            }
246            
247            return isValid;
248        }
249    
250    }