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 }