org.kuali.kfs.module.cam.util.AssetSeparatePaymentDistributor.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.kfs.module.cam.util.AssetSeparatePaymentDistributor.java

Source

/*
 * 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;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.PropertyUtils;
import org.kuali.kfs.module.cam.CamsConstants;
import org.kuali.kfs.module.cam.businessobject.Asset;
import org.kuali.kfs.module.cam.businessobject.AssetGlobal;
import org.kuali.kfs.module.cam.businessobject.AssetGlobalDetail;
import org.kuali.kfs.module.cam.businessobject.AssetPayment;
import org.kuali.rice.core.api.util.type.KualiDecimal;

/**
 * This class is a calculator which will distribute the amounts and balance them by ratio. Inputs received are
 * <li>Source Asset</li>
 * <li>Source Payments</li>
 * <li>Current max of payment number used by source Asset</li>
 * <li>AssetGlobal Document performing the separate action</li>
 * <li>List of new assets to be created for this separate request document</li>
 * Logic is best explained as below
 * <li>Compute the ratio of amounts to be removed from source payments</li>
 * <li>Compute the ratio by which each new asset should receive the allocated amount</li>
 * <li>Separate the allocate amount from the source payment using ratio computed above</li>
 * <li>Apply the allocate amount by ratio to each new asset</li>
 * <li>Adjust the last payment to round against the source from which split is done</li>
 * <li>Adjust the account charge amount of each asset by rounding the last payment with reference to user input separate amount</li>
 * <li>Create offset payments for the source asset</li>
 * <li>Compute accumulated depreciation amount for each payment, including offsets</li>
 */
public class AssetSeparatePaymentDistributor {
    private Asset sourceAsset;
    private AssetGlobal assetGlobal;
    private List<Asset> newAssets;
    private List<AssetPayment> sourcePayments = new ArrayList<AssetPayment>();
    private List<AssetPayment> separatedPayments = new ArrayList<AssetPayment>();
    private List<AssetPayment> offsetPayments = new ArrayList<AssetPayment>();
    private List<AssetPayment> remainingPayments = new ArrayList<AssetPayment>();
    private HashMap<Long, KualiDecimal> totalByAsset = new HashMap<Long, KualiDecimal>();
    private HashMap<Integer, List<AssetPayment>> paymentSplitMap = new HashMap<Integer, List<AssetPayment>>();
    private double[] assetAllocateRatios;
    private double separateRatio;
    private double retainRatio;
    private Integer maxPaymentSeqNo;
    private static PropertyDescriptor[] assetPaymentProperties = PropertyUtils
            .getPropertyDescriptors(AssetPayment.class);

    /**
     * Constructs a AssetSeparatePaymentDistributor.java.
     * 
     * @param sourceAsset Source Asset
     * @param sourcePayments Source Payments
     * @param maxPaymentSeqNo Current max of payment number used by source Asset
     * @param assetGlobal AssetGlobal Document performing the separate action
     * @param newAssets List of new assets to be created for this separate request document
     */
    public AssetSeparatePaymentDistributor(Asset sourceAsset, List<AssetPayment> sourcePayments,
            Integer maxPaymentSeqNo, AssetGlobal assetGlobal, List<Asset> newAssets) {
        super();
        this.sourceAsset = sourceAsset;
        this.sourcePayments = sourcePayments;
        this.maxPaymentSeqNo = maxPaymentSeqNo;
        this.assetGlobal = assetGlobal;
        this.newAssets = newAssets;
    }

    public void distribute() {
        KualiDecimal totalSourceAmount = this.assetGlobal.getTotalCostAmount();
        KualiDecimal totalSeparateAmount = this.assetGlobal.getSeparateSourceTotalAmount();
        KualiDecimal remainingAmount = totalSourceAmount.subtract(totalSeparateAmount);
        // Compute separate ratio
        separateRatio = totalSeparateAmount.doubleValue() / totalSourceAmount.doubleValue();
        // Compute the retained ratio
        retainRatio = remainingAmount.doubleValue() / totalSourceAmount.doubleValue();
        List<AssetGlobalDetail> assetGlobalDetails = this.assetGlobal.getAssetGlobalDetails();
        int size = assetGlobalDetails.size();
        assetAllocateRatios = new double[size];
        AssetGlobalDetail assetGlobalDetail = null;
        // Compute ratio by each asset
        for (int i = 0; i < size; i++) {
            assetGlobalDetail = assetGlobalDetails.get(i);
            Long capitalAssetNumber = assetGlobalDetail.getCapitalAssetNumber();
            totalByAsset.put(capitalAssetNumber, KualiDecimal.ZERO);
            assetAllocateRatios[i] = assetGlobalDetail.getSeparateSourceAmount().doubleValue()
                    / totalSeparateAmount.doubleValue();
        }
        // Prepare the source and offset payments for split
        prepareSourcePaymentsForSplit();
        // Distribute payments by ratio
        allocatePaymentAmountsByRatio();
        // Round and balance by each payment line
        roundPaymentAmounts();
        // Round and balance by separate source amount
        roundAccountChargeAmount();
        // create offset payments
        createOffsetPayments();
    }

    /**
     * Split the amount to be assigned from the source payments
     */
    private void prepareSourcePaymentsForSplit() {
        // Call the allocate with ratio for each payments
        for (AssetPayment assetPayment : this.sourcePayments) {
            if (assetPayment.getAccountChargeAmount() != null
                    && assetPayment.getAccountChargeAmount().isNonZero()) {
                // Separate amount
                AssetPayment separatePayment = new AssetPayment();
                ObjectValueUtils.copySimpleProperties(assetPayment, separatePayment);
                this.separatedPayments.add(separatePayment);

                // Remaining amount
                AssetPayment remainingPayment = new AssetPayment();
                ObjectValueUtils.copySimpleProperties(assetPayment, remainingPayment);
                this.remainingPayments.add(remainingPayment);

                applyRatioToPaymentAmounts(assetPayment, new AssetPayment[] { separatePayment, remainingPayment },
                        new double[] { separateRatio, retainRatio });
            }
        }

    }

    /**
     * Creates offset payment by copying and negating the separated payments
     */
    private void createOffsetPayments() {
        // create offset payment by negating the amount fields
        for (AssetPayment separatePayment : this.separatedPayments) {
            AssetPayment offsetPayment = new AssetPayment();
            ObjectValueUtils.copySimpleProperties(separatePayment, offsetPayment);
            try {
                negatePaymentAmounts(offsetPayment);
            } catch (Exception e) {
                throw new RuntimeException();
            }
            offsetPayment.setDocumentNumber(assetGlobal.getDocumentNumber());
            offsetPayment
                    .setFinancialDocumentTypeCode(CamsConstants.PaymentDocumentTypeCodes.ASSET_GLOBAL_SEPARATE);
            offsetPayment.setVersionNumber(null);
            offsetPayment.setObjectId(null);
            offsetPayment.setPaymentSequenceNumber(++maxPaymentSeqNo);
            this.offsetPayments.add(offsetPayment);
        }
        this.sourceAsset.getAssetPayments().addAll(this.offsetPayments);
    }

    /**
     * Applies the asset allocate ratio for each payment line to be created and adds to the new asset. In addition it keeps track of
     * how amount is consumed by each asset and how each payment is being split
     */
    private void allocatePaymentAmountsByRatio() {
        int index = 0;
        for (AssetPayment source : this.separatedPayments) {

            // for each source payment, create target payments by ratio
            AssetPayment[] targets = new AssetPayment[assetAllocateRatios.length];
            for (int j = 0; j < assetAllocateRatios.length; j++) {
                AssetPayment newPayment = new AssetPayment();
                ObjectValueUtils.copySimpleProperties(source, newPayment);
                Asset currentAsset = this.newAssets.get(j);
                Long capitalAssetNumber = currentAsset.getCapitalAssetNumber();
                newPayment.setCapitalAssetNumber(capitalAssetNumber);
                newPayment.setDocumentNumber(assetGlobal.getDocumentNumber());
                newPayment
                        .setFinancialDocumentTypeCode(CamsConstants.PaymentDocumentTypeCodes.ASSET_GLOBAL_SEPARATE);
                targets[j] = newPayment;
                newPayment.setVersionNumber(null);
                newPayment.setObjectId(null);
                currentAsset.getAssetPayments().add(index, newPayment);
            }
            applyRatioToPaymentAmounts(source, targets, assetAllocateRatios);

            // keep track of split happened for the source
            this.paymentSplitMap.put(source.getPaymentSequenceNumber(), Arrays.asList(targets));

            // keep track of total amount by asset
            for (int j = 0; j < targets.length; j++) {
                Asset currentAsset = this.newAssets.get(j);
                Long capitalAssetNumber = currentAsset.getCapitalAssetNumber();
                this.totalByAsset.put(capitalAssetNumber,
                        this.totalByAsset.get(capitalAssetNumber).add(targets[j].getAccountChargeAmount()));
            }
            index++;
        }
    }

    /**
     * Rounds the last payment by adjusting the amounts against source amount
     */
    private void roundPaymentAmounts() {
        for (int i = 0; i < this.separatedPayments.size(); i++) {
            applyBalanceToPaymentAmounts(separatedPayments.get(i),
                    this.paymentSplitMap.get(separatedPayments.get(i).getPaymentSequenceNumber()));
        }
    }

    /**
     * Rounds the last payment by adjusting the amount compared against separate source amount and copies account charge amount to
     * primary depreciation base amount if not zero
     */
    private void roundAccountChargeAmount() {
        for (int j = 0; j < this.newAssets.size(); j++) {
            Asset currentAsset = this.newAssets.get(j);
            AssetGlobalDetail detail = this.assetGlobal.getAssetGlobalDetails().get(j);
            AssetPayment lastPayment = currentAsset.getAssetPayments()
                    .get(currentAsset.getAssetPayments().size() - 1);
            KualiDecimal totalForAsset = this.totalByAsset.get(currentAsset.getCapitalAssetNumber());
            KualiDecimal diff = detail.getSeparateSourceAmount().subtract(totalForAsset);
            lastPayment.setAccountChargeAmount(lastPayment.getAccountChargeAmount().add(diff));
            currentAsset.setTotalCostAmount(totalForAsset.add(diff));
            AssetPayment lastSource = this.separatedPayments.get(this.separatedPayments.size() - 1);
            lastSource.setAccountChargeAmount(lastSource.getAccountChargeAmount().add(diff));
            // adjust primary depreciation base amount, same as account charge amount
            if (lastPayment.getPrimaryDepreciationBaseAmount() != null
                    && lastPayment.getPrimaryDepreciationBaseAmount().isNonZero()) {
                lastPayment.setPrimaryDepreciationBaseAmount(lastPayment.getAccountChargeAmount());
                lastSource.setPrimaryDepreciationBaseAmount(lastSource.getAccountChargeAmount());
            }
        }
    }

    /**
     * Utility method which can take one payment and distribute its amount by ratio to the target payments
     * 
     * @param source Source Payment
     * @param targets Target Payment
     * @param ratios Ratio to be applied for each target
     */
    private void applyRatioToPaymentAmounts(AssetPayment source, AssetPayment[] targets, double[] ratios) {
        try {
            for (PropertyDescriptor propertyDescriptor : assetPaymentProperties) {
                Method readMethod = propertyDescriptor.getReadMethod();
                if (readMethod != null && propertyDescriptor.getPropertyType() != null
                        && KualiDecimal.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
                    KualiDecimal amount = (KualiDecimal) readMethod.invoke(source);
                    if (amount != null && amount.isNonZero()) {
                        KualiDecimal[] ratioAmounts = KualiDecimalUtils.allocateByRatio(amount, ratios);
                        Method writeMethod = propertyDescriptor.getWriteMethod();
                        if (writeMethod != null) {
                            for (int i = 0; i < ratioAmounts.length; i++) {
                                writeMethod.invoke(targets[i], ratioAmounts[i]);
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * Utility method which can compute the difference between source amount and consumed amounts, then will adjust the last amount
     * 
     * @param source Source payments
     * @param consumedList Consumed Payments
     */
    private void applyBalanceToPaymentAmounts(AssetPayment source, List<AssetPayment> consumedList) {
        try {
            for (PropertyDescriptor propertyDescriptor : assetPaymentProperties) {
                Method readMethod = propertyDescriptor.getReadMethod();
                if (readMethod != null && propertyDescriptor.getPropertyType() != null
                        && KualiDecimal.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
                    KualiDecimal amount = (KualiDecimal) readMethod.invoke(source);
                    if (amount != null && amount.isNonZero()) {
                        Method writeMethod = propertyDescriptor.getWriteMethod();
                        KualiDecimal consumedAmount = KualiDecimal.ZERO;
                        KualiDecimal currAmt = KualiDecimal.ZERO;
                        if (writeMethod != null) {
                            for (int i = 0; i < consumedList.size(); i++) {
                                currAmt = (KualiDecimal) readMethod.invoke(consumedList.get(i));
                                consumedAmount = consumedAmount.add(currAmt != null ? currAmt : KualiDecimal.ZERO);
                            }
                        }
                        if (!consumedAmount.equals(amount)) {
                            AssetPayment lastPayment = consumedList.get(consumedList.size() - 1);
                            writeMethod.invoke(lastPayment, currAmt.add(amount.subtract(consumedAmount)));
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * Utility method which will negate the payment amounts for a given payment
     * 
     * @param assetPayment Payment to be negated
     */
    public void negatePaymentAmounts(AssetPayment assetPayment) {
        try {
            for (PropertyDescriptor propertyDescriptor : assetPaymentProperties) {
                Method readMethod = propertyDescriptor.getReadMethod();
                if (readMethod != null && propertyDescriptor.getPropertyType() != null
                        && KualiDecimal.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
                    KualiDecimal amount = (KualiDecimal) readMethod.invoke(assetPayment);
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    if (writeMethod != null && amount != null) {
                        writeMethod.invoke(assetPayment, (amount.negated()));
                    }

                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Sums up YTD values and Previous Year value to decide accumulated depreciation amount
     */
    private void computeAccumulatedDepreciationAmount() {
        KualiDecimal previousYearAmount = null;
        for (Asset asset : this.newAssets) {
            List<AssetPayment> assetPayments = asset.getAssetPayments();
            for (AssetPayment currPayment : assetPayments) {
                previousYearAmount = currPayment.getPreviousYearPrimaryDepreciationAmount();
                previousYearAmount = previousYearAmount == null ? KualiDecimal.ZERO : previousYearAmount;
                KualiDecimal computedAmount = previousYearAmount.add(sumPeriodicDepreciationAmounts(currPayment));
                if (computedAmount.isNonZero()) {
                    currPayment.setAccumulatedPrimaryDepreciationAmount(computedAmount);
                }
            }
        }
        for (AssetPayment currPayment : this.offsetPayments) {
            previousYearAmount = currPayment.getPreviousYearPrimaryDepreciationAmount();
            previousYearAmount = previousYearAmount == null ? KualiDecimal.ZERO : previousYearAmount;
            KualiDecimal computedAmount = previousYearAmount.add(sumPeriodicDepreciationAmounts(currPayment));
            if (computedAmount.isNonZero()) {
                currPayment.setAccumulatedPrimaryDepreciationAmount(computedAmount);
            }
        }
    }

    /**
     * Sums up periodic amounts for a payment
     * 
     * @param currPayment Payment
     * @return Sum of payment
     */
    public static KualiDecimal sumPeriodicDepreciationAmounts(AssetPayment currPayment) {
        KualiDecimal ytdAmount = KualiDecimal.ZERO;
        try {
            for (PropertyDescriptor propertyDescriptor : assetPaymentProperties) {
                Method readMethod = propertyDescriptor.getReadMethod();
                if (readMethod != null
                        && Pattern.matches(CamsConstants.GET_PERIOD_DEPRECIATION_AMOUNT_REGEX,
                                readMethod.getName().toLowerCase())
                        && propertyDescriptor.getPropertyType() != null
                        && KualiDecimal.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
                    KualiDecimal amount = (KualiDecimal) readMethod.invoke(currPayment);
                    if (amount != null) {
                        ytdAmount = ytdAmount.add(amount);
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return ytdAmount;
    }

    /**
     * Gets the remainingPayments attribute.
     * 
     * @return Returns the remainingPayments.
     */
    public List<AssetPayment> getRemainingPayments() {
        return remainingPayments;
    }

    /**
     * Sets the remainingPayments attribute value.
     * 
     * @param remainingPayments The remainingPayments to set.
     */
    public void setRemainingPayments(List<AssetPayment> remainingPayments) {
        this.remainingPayments = remainingPayments;
    }

    /**
     * Gets the offsetPayments attribute.
     * 
     * @return Returns the offsetPayments.
     */
    public List<AssetPayment> getOffsetPayments() {
        return offsetPayments;
    }

    /**
     * Sets the offsetPayments attribute value.
     * 
     * @param offsetPayments The offsetPayments to set.
     */
    public void setOffsetPayments(List<AssetPayment> offsetPayments) {
        this.offsetPayments = offsetPayments;
    }

    /**
     * Gets the separatedPayments attribute.
     * 
     * @return Returns the separatedPayments.
     */
    public List<AssetPayment> getSeparatedPayments() {
        return separatedPayments;
    }

    /**
     * Sets the separatedPayments attribute value.
     * 
     * @param separatedPayments The separatedPayments to set.
     */
    public void setSeparatedPayments(List<AssetPayment> separatedPayments) {
        this.separatedPayments = separatedPayments;
    }

    /**
     * Gets the assetGlobal attribute.
     * 
     * @return Returns the assetGlobal.
     */
    public AssetGlobal getAssetGlobal() {
        return assetGlobal;
    }

    /**
     * Sets the assetGlobal attribute value.
     * 
     * @param assetGlobal The assetGlobal to set.
     */
    public void setAssetGlobal(AssetGlobal assetGlobal) {
        this.assetGlobal = assetGlobal;
    }

    /**
     * Gets the newAssets attribute.
     * 
     * @return Returns the newAssets.
     */
    public List<Asset> getNewAssets() {
        return newAssets;
    }

    /**
     * Sets the newAssets attribute value.
     * 
     * @param newAssets The newAssets to set.
     */
    public void setNewAssets(List<Asset> newAssets) {
        this.newAssets = newAssets;
    }

    /**
     * Gets the assetAllocateRatios attribute.
     * 
     * @return Returns the assetAllocateRatios.
     */
    public double[] getAssetAllocateRatios() {
        return assetAllocateRatios;
    }

    /**
     * Sets the assetAllocateRatios attribute value.
     * 
     * @param assetAllocateRatios The assetAllocateRatios to set.
     */
    public void setAssetAllocateRatios(double[] assetAllocateRatios) {
        this.assetAllocateRatios = assetAllocateRatios;
    }

    /**
     * Gets the separateRatio attribute.
     * 
     * @return Returns the separateRatio.
     */
    public double getSeparateRatio() {
        return separateRatio;
    }

    /**
     * Sets the separateRatio attribute value.
     * 
     * @param separateRatio The separateRatio to set.
     */
    public void setSeparateRatio(double separateRatio) {
        this.separateRatio = separateRatio;
    }

    /**
     * Gets the retainRatio attribute.
     * 
     * @return Returns the retainRatio.
     */
    public double getRetainRatio() {
        return retainRatio;
    }

    /**
     * Sets the retainRatio attribute value.
     * 
     * @param retainRatio The retainRatio to set.
     */
    public void setRetainRatio(double retainRatio) {
        this.retainRatio = retainRatio;
    }

    /**
     * Gets the sourcePayments attribute.
     * 
     * @return Returns the sourcePayments.
     */
    public List<AssetPayment> getSourcePayments() {
        return sourcePayments;
    }

    /**
     * Sets the sourcePayments attribute value.
     * 
     * @param sourcePayments The sourcePayments to set.
     */
    public void setSourcePayments(List<AssetPayment> sourcePayments) {
        this.sourcePayments = sourcePayments;
    }

}