/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.module.cam.util.distribution;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.module.cam.businessobject.AssetPaymentAssetDetail;
import org.kuali.kfs.module.cam.businessobject.AssetPaymentDetail;
import org.kuali.kfs.module.cam.document.AssetPaymentDocument;
import org.kuali.rice.core.api.util.type.KualiDecimal;
/**
* This class is a calculator which will distribute the payment amounts by ratio. Inputs received are
* <li>Asset Payment Details</li>
* <li>Asset Details</li>
* <li>total historical cost for asset</li>
*
* It provides a table mapping of asset payment distributions key off of AssetPaymentDetail and
* AssetPaymentAssetDetail
*
* Logic is best explained as below
* <li>Compute the asset ratio of amount to be distributed per asset</li>
* <li>For each Asset Payment Details, create proportional asset payments base on the asset ratio</li>
* <li>* Keep track of unallocated amount within each asset payment loop* <li>
* <li>* For the last asset in each payment detail iteration, use the rest of unallocated amount</li>
*/
public class AssetPaymentDistributionByTotalCost extends AssetDistribution {
private KualiDecimal totalHistoricalCost;
private Map<String, Map<AssetPaymentAssetDetail, KualiDecimal>> paymentDistributionMap;
/**
* Constructor and instantiate the detai lists as empty
*/
public AssetPaymentDistributionByTotalCost(AssetPaymentDocument doc) {
super(doc);
this.totalHistoricalCost = doc.getAssetsTotalHistoricalCost();
//init method
calculateAssetPaymentDistributions();
}
/**
* Pre-calculate the asset payments base on AssetPaymentDetail(AccountSouceLines) and AssetPaymentAssetDetails
*
* This will iterate by the AssetPaymentDetail as the outer iterator such that payment totals will match up by the AccountingSouceLines
* in (GL). The unallocated payment amount will be depleted per each AssetPaymentDetails
*
* NOTE: reversing the iteration sequence will cause a discrepancy in the AssetPaymentDetail totals
*
* @param document
* @param assetPaymentDetailLines
* @param assetPaymentAssetDetails
*/
private void calculateAssetPaymentDistributions(){
Map<String, Map<AssetPaymentAssetDetail, KualiDecimal>> assetPaymentAssetDetailMap = new HashMap<String, Map<AssetPaymentAssetDetail, KualiDecimal>>();
// calculate the asset payment percentage and store into a map
Map<AssetPaymentAssetDetail, Double> assetPaymentsPercentage = new HashMap<AssetPaymentAssetDetail, Double>(doc.getAssetPaymentAssetDetail().size());
for (AssetPaymentAssetDetail assetPaymentAssetDetail : doc.getAssetPaymentAssetDetail()) {
// 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.
// store the result in a temporary map
assetPaymentsPercentage.put(assetPaymentAssetDetail, getAssetDetailPercentage(doc.getAssetPaymentAssetDetail().size(), new Double(totalHistoricalCost.toString()), assetPaymentAssetDetail));
}
// Start the iteration base from the AssetPaymentDetail - accountingSouceLines
for (AssetPaymentDetail assetPaymentDetail : getAssetPaymentDetailLines()) {
int paymentCount = doc.getAssetPaymentAssetDetail().size();
KualiDecimal amount = KualiDecimal.ZERO;
// Keep unallocated amount so it could be used for last payment amount for the asset (to avoid rounding issue)
KualiDecimal unallocatedAmount = assetPaymentDetail.getAmount();
Map<AssetPaymentAssetDetail, KualiDecimal> assetDetailMap = new HashMap<AssetPaymentAssetDetail, KualiDecimal>();
for (AssetPaymentAssetDetail assetPaymentAssetDetail : doc.getAssetPaymentAssetDetail()) {
// 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.
Double percentage = assetPaymentsPercentage.get(assetPaymentAssetDetail);
if (paymentCount-- == 1) {
// Deplete the rest of the payment for last payment
amount = unallocatedAmount;
}
else {
// Normal payment will be calculated by asset percentage
Double paymentAmount = new Double(assetPaymentDetail.getAmount().toString());
amount = new KualiDecimal(paymentAmount.doubleValue() * percentage.doubleValue());
unallocatedAmount = unallocatedAmount.subtract(amount);
}
assetDetailMap.put(assetPaymentAssetDetail, amount);
}
assetPaymentAssetDetailMap.put(assetPaymentDetail.getAssetPaymentDetailKey(), assetDetailMap);
}
paymentDistributionMap = assetPaymentAssetDetailMap;
}
/**
* Retrieve the asset payment distributions
*
* @return
*/
public Map<String, Map<AssetPaymentAssetDetail, KualiDecimal>> getAssetPaymentDistributions() {
return paymentDistributionMap;
}
/**
* Get each Asset's allocation totals base the payment distributions
*
* @return map of asset detail and its totals
*/
public Map<AssetPaymentAssetDetail, KualiDecimal> getTotalAssetAllocations() {
Map<AssetPaymentAssetDetail, KualiDecimal> assetTotalAllocationMap = new HashMap<AssetPaymentAssetDetail, KualiDecimal>();
KualiDecimal allocation, total;
//iterate all the distributions
for (Map<AssetPaymentAssetDetail, KualiDecimal> assetDistrbution : getAssetPaymentDistributions().values()){
for (AssetPaymentAssetDetail assetDetail : assetDistrbution.keySet()){
allocation = assetDistrbution.get(assetDetail);
total = assetTotalAllocationMap.get(assetDetail);
assetTotalAllocationMap.put(assetDetail, total == null? allocation : total.add(allocation));
}
}
return assetTotalAllocationMap;
}
/**
* 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.
*
* @param detailSize
* @param totalHistoricalCost
* @param assetPaymentAssetDetail
* @return
*/
private Double getAssetDetailPercentage(int detailSize, Double totalHistoricalCost, AssetPaymentAssetDetail assetPaymentAssetDetail) {
Double previousTotalCostAmount = new Double("0");
if (assetPaymentAssetDetail.getPreviousTotalCostAmount() != null) {
previousTotalCostAmount = new Double(StringUtils.defaultIfEmpty(assetPaymentAssetDetail.getPreviousTotalCostAmount().toString(), "0"));
}
Double percentage = new Double(0);
if (totalHistoricalCost.compareTo(new Double(0)) != 0)
percentage = (previousTotalCostAmount / totalHistoricalCost);
else
percentage = (1 / (new Double(detailSize)));
return percentage;
}
public String getLabel() {
return getClass().getSimpleName();
}
}