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.cam.util;
017    
018    import java.util.HashMap;
019    import java.util.List;
020    import java.util.Map;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.kuali.kfs.module.cam.businessobject.AssetPaymentAssetDetail;
024    import org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail;
025    import org.kuali.rice.kns.util.KualiDecimal;
026    
027    /**
028     * This class is a calculator which will distribute the payment amounts by ratio. Inputs received are
029     * <li>Asset Payment Details</li>
030     * <li>Asset Details</li>
031     * <li>total historical cost for asset</li>
032     * 
033     * It provides a table mapping of asset payment distributions key off of AssetPaymentDetail and 
034     * AssetPaymentAssetDetail 
035     * 
036     * Logic is best explained as below
037     * <li>Compute the asset ratio of amount to be distributed per asset</li>
038     * <li>For each Asset Payment Details, create proportional asset payments base on the asset ratio</li>
039     *      <li>* Keep track of unallocated amount within each asset payment loop* <li>
040     *      <li>* For the last asset in each payment detail iteration, use the rest of unallocated amount</li>
041     */
042    public class AssetPaymentDistributor {
043    
044        private List<AssetPaymentDetail> assetPaymentDetailLines;
045    
046        private List<AssetPaymentAssetDetail> assetPaymentAssetDetails;
047        
048        private KualiDecimal totalHistoricalCost;
049        
050        private Map<String, Map<AssetPaymentAssetDetail, KualiDecimal>> paymentDistributionMap;
051        
052        /**
053         * Constructor and instantiate the detai lists as empty
054         */
055        public AssetPaymentDistributor(List<AssetPaymentDetail> assetPaymentDetailLines, List<AssetPaymentAssetDetail> assetPaymentAssetDetails, KualiDecimal totalHistoricalCost) {
056            this.assetPaymentDetailLines = assetPaymentDetailLines;
057            this.assetPaymentAssetDetails = assetPaymentAssetDetails;
058            this.totalHistoricalCost = totalHistoricalCost;
059            
060            //init method
061            calculateAssetPaymentDistributions();
062        }
063    
064        /**
065         * Pre-calculate the asset payments base on AssetPaymentDetail(AccountSouceLines) and AssetPaymentAssetDetails
066         * 
067         * This will iterate by the AssetPaymentDetail as the outer iterator such that payment totals will match up by the AccountingSouceLines
068         * in (GL).  The unallocated payment amount will be depleted per each AssetPaymentDetails
069         * 
070         *  NOTE: reversing the iteration sequence will cause a discrepancy in the AssetPaymentDetail totals 
071         * 
072         * @param document
073         * @param assetPaymentDetailLines
074         * @param assetPaymentAssetDetails
075         */
076        private void calculateAssetPaymentDistributions(){
077    
078            Map<String, Map<AssetPaymentAssetDetail, KualiDecimal>> assetPaymentAssetDetailMap = new HashMap<String, Map<AssetPaymentAssetDetail, KualiDecimal>>();
079    
080            // calculate the asset payment percentage and store into a map
081            Map<AssetPaymentAssetDetail, Double> assetPaymentsPercentage = new HashMap<AssetPaymentAssetDetail, Double>(assetPaymentAssetDetails.size());
082            
083            for (AssetPaymentAssetDetail assetPaymentAssetDetail : assetPaymentAssetDetails) {
084                // Doing the re-distribution of the cost based on the previous total cost of each asset compared with the total previous cost of the assets.
085                // store the result in a temporary map 
086                assetPaymentsPercentage.put(assetPaymentAssetDetail, getAssetDetailPercentage(assetPaymentAssetDetails.size(), new Double(totalHistoricalCost.toString()), assetPaymentAssetDetail));
087            }
088    
089            // Start the iteration base from the AssetPaymentDetail - accountingSouceLines
090            for (AssetPaymentDetail assetPaymentDetail : assetPaymentDetailLines) {
091    
092                int paymentCount = assetPaymentAssetDetails.size();
093                KualiDecimal amount = KualiDecimal.ZERO;
094    
095                // Keep unallocated amount so it could be used for last payment amount for the asset (to avoid rounding issue)
096                KualiDecimal unallocatedAmount = assetPaymentDetail.getAmount();
097    
098                Map<AssetPaymentAssetDetail, KualiDecimal> assetDetailMap = new HashMap<AssetPaymentAssetDetail, KualiDecimal>();
099                for (AssetPaymentAssetDetail assetPaymentAssetDetail : assetPaymentAssetDetails) {
100                    // Doing the re-distribution of the cost based on the previous total cost of each asset compared with the total
101                    // previous cost of the assets.
102                    Double percentage = assetPaymentsPercentage.get(assetPaymentAssetDetail);
103    
104                    if (paymentCount-- == 1) {
105                        // Deplete the rest of the payment for last payment
106                        amount = unallocatedAmount;
107                    }
108                    else {
109                        // Normal payment will be calculated by asset percentage
110                        Double paymentAmount = new Double(assetPaymentDetail.getAmount().toString());
111                        amount = new KualiDecimal(paymentAmount.doubleValue() * percentage.doubleValue());
112                        unallocatedAmount = unallocatedAmount.subtract(amount);
113                    }
114                    assetDetailMap.put(assetPaymentAssetDetail, amount);
115                }
116                assetPaymentAssetDetailMap.put(assetPaymentDetail.getAssetPaymentDetailKey(), assetDetailMap);
117            }
118            
119            paymentDistributionMap = assetPaymentAssetDetailMap;
120            
121            System.out.println(paymentDistributionMap);
122        }
123        
124        /**
125         * Retrieve the asset payment distributions
126         * 
127         * @return
128         */
129        public Map<String, Map<AssetPaymentAssetDetail, KualiDecimal>> getAssetPaymentDistributions() {
130            return paymentDistributionMap;
131        }
132        
133        /**
134         * Get each Asset's allocation totals base the payment distributions
135         * 
136         * @return map of asset detail and its totals
137         */
138        public Map<AssetPaymentAssetDetail, KualiDecimal> getTotalAssetAllocations() {
139            Map<AssetPaymentAssetDetail, KualiDecimal> assetTotalAllocationMap = new HashMap<AssetPaymentAssetDetail, KualiDecimal>();
140            KualiDecimal allocation, total;
141            
142            //iterate all the distributions
143            for (Map<AssetPaymentAssetDetail, KualiDecimal> assetDistrbution : getAssetPaymentDistributions().values()){
144                
145                for (AssetPaymentAssetDetail assetDetail : assetDistrbution.keySet()){
146                    allocation = assetDistrbution.get(assetDetail);
147                    total = assetTotalAllocationMap.get(assetDetail);
148                    
149                    assetTotalAllocationMap.put(assetDetail, total == null? allocation : total.add(allocation));
150                }
151            }
152            return assetTotalAllocationMap;
153        }
154        
155        /**
156         * Doing the re-distribution of the cost based on the previous total cost of each asset compared with the total previous cost of
157         * the assets.
158         * 
159         * @param detailSize
160         * @param totalHistoricalCost
161         * @param assetPaymentAssetDetail
162         * @return
163         */
164        private Double getAssetDetailPercentage(int detailSize, Double totalHistoricalCost, AssetPaymentAssetDetail assetPaymentAssetDetail) {
165            Double previousTotalCostAmount = new Double("0");
166            if (assetPaymentAssetDetail.getPreviousTotalCostAmount() != null) {
167                previousTotalCostAmount = new Double(StringUtils.defaultIfEmpty(assetPaymentAssetDetail.getPreviousTotalCostAmount().toString(), "0"));
168            }
169    
170            Double percentage = new Double(0);
171            if (totalHistoricalCost.compareTo(new Double(0)) != 0)
172                percentage = (previousTotalCostAmount / totalHistoricalCost);
173            else
174                percentage = (1 / (new Double(detailSize)));
175            return percentage;
176        }
177    
178    }