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 }