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.beans.PropertyDescriptor; 019 import java.lang.reflect.Method; 020 import java.util.ArrayList; 021 import java.util.Arrays; 022 import java.util.HashMap; 023 import java.util.List; 024 import java.util.regex.Pattern; 025 026 import org.apache.commons.beanutils.PropertyUtils; 027 import org.kuali.kfs.module.cam.CamsConstants; 028 import org.kuali.kfs.module.cam.businessobject.Asset; 029 import org.kuali.kfs.module.cam.businessobject.AssetGlobal; 030 import org.kuali.kfs.module.cam.businessobject.AssetGlobalDetail; 031 import org.kuali.kfs.module.cam.businessobject.AssetPayment; 032 import org.kuali.rice.kns.util.KualiDecimal; 033 034 /** 035 * This class is a calculator which will distribute the amounts and balance them by ratio. Inputs received are 036 * <li>Source Asset</li> 037 * <li>Source Payments</li> 038 * <li>Current max of payment number used by source Asset</li> 039 * <li>AssetGlobal Document performing the separate action</li> 040 * <li>List of new assets to be created for this separate request document</li> 041 * Logic is best explained as below 042 * <li>Compute the ratio of amounts to be removed from source payments</li> 043 * <li>Compute the ratio by which each new asset should receive the allocated amount</li> 044 * <li>Separate the allocate amount from the source payment using ratio computed above</li> 045 * <li>Apply the allocate amount by ratio to each new asset</li> 046 * <li>Adjust the last payment to round against the source from which split is done</li> 047 * <li>Adjust the account charge amount of each asset by rounding the last payment with reference to user input separate amount</li> 048 * <li>Create offset payments for the source asset</li> 049 * <li>Compute accumulated depreciation amount for each payment, including offsets</li> 050 */ 051 public class AssetSeparatePaymentDistributor { 052 private Asset sourceAsset; 053 private AssetGlobal assetGlobal; 054 private List<Asset> newAssets; 055 private List<AssetPayment> sourcePayments = new ArrayList<AssetPayment>(); 056 private List<AssetPayment> separatedPayments = new ArrayList<AssetPayment>(); 057 private List<AssetPayment> offsetPayments = new ArrayList<AssetPayment>(); 058 private List<AssetPayment> remainingPayments = new ArrayList<AssetPayment>(); 059 private HashMap<Long, KualiDecimal> totalByAsset = new HashMap<Long, KualiDecimal>(); 060 private HashMap<Integer, List<AssetPayment>> paymentSplitMap = new HashMap<Integer, List<AssetPayment>>(); 061 private double[] assetAllocateRatios; 062 private double separateRatio; 063 private double retainRatio; 064 private Integer maxPaymentSeqNo; 065 private static PropertyDescriptor[] assetPaymentProperties = PropertyUtils.getPropertyDescriptors(AssetPayment.class); 066 067 068 /** 069 * Constructs a AssetSeparatePaymentDistributor.java. 070 * 071 * @param sourceAsset Source Asset 072 * @param sourcePayments Source Payments 073 * @param maxPaymentSeqNo Current max of payment number used by source Asset 074 * @param assetGlobal AssetGlobal Document performing the separate action 075 * @param newAssets List of new assets to be created for this separate request document 076 */ 077 public AssetSeparatePaymentDistributor(Asset sourceAsset, List<AssetPayment> sourcePayments, Integer maxPaymentSeqNo, AssetGlobal assetGlobal, List<Asset> newAssets) { 078 super(); 079 this.sourceAsset = sourceAsset; 080 this.sourcePayments = sourcePayments; 081 this.maxPaymentSeqNo = maxPaymentSeqNo; 082 this.assetGlobal = assetGlobal; 083 this.newAssets = newAssets; 084 } 085 086 087 public void distribute() { 088 KualiDecimal totalSourceAmount = this.assetGlobal.getTotalCostAmount(); 089 KualiDecimal totalSeparateAmount = this.assetGlobal.getSeparateSourceTotalAmount(); 090 KualiDecimal remainingAmount = totalSourceAmount.subtract(totalSeparateAmount); 091 // Compute separate ratio 092 separateRatio = totalSeparateAmount.doubleValue() / totalSourceAmount.doubleValue(); 093 // Compute the retained ratio 094 retainRatio = remainingAmount.doubleValue() / totalSourceAmount.doubleValue(); 095 List<AssetGlobalDetail> assetGlobalDetails = this.assetGlobal.getAssetGlobalDetails(); 096 int size = assetGlobalDetails.size(); 097 assetAllocateRatios = new double[size]; 098 AssetGlobalDetail assetGlobalDetail = null; 099 // Compute ratio by each asset 100 for (int i = 0; i < size; i++) { 101 assetGlobalDetail = assetGlobalDetails.get(i); 102 Long capitalAssetNumber = assetGlobalDetail.getCapitalAssetNumber(); 103 totalByAsset.put(capitalAssetNumber, KualiDecimal.ZERO); 104 assetAllocateRatios[i] = assetGlobalDetail.getSeparateSourceAmount().doubleValue() / totalSeparateAmount.doubleValue(); 105 } 106 // Prepare the source and offset payments for split 107 prepareSourcePaymentsForSplit(); 108 // Distribute payments by ratio 109 allocatePaymentAmountsByRatio(); 110 // Round and balance by each payment line 111 roundPaymentAmounts(); 112 // Round and balance by separate source amount 113 roundAccountChargeAmount(); 114 // create offset payments 115 createOffsetPayments(); 116 } 117 118 119 /** 120 * Split the amount to be assigned from the source payments 121 */ 122 private void prepareSourcePaymentsForSplit() { 123 // Call the allocate with ratio for each payments 124 for (AssetPayment assetPayment : this.sourcePayments) { 125 if (assetPayment.getAccountChargeAmount() != null && assetPayment.getAccountChargeAmount().isNonZero()) { 126 // Separate amount 127 AssetPayment separatePayment = new AssetPayment(); 128 ObjectValueUtils.copySimpleProperties(assetPayment, separatePayment); 129 this.separatedPayments.add(separatePayment); 130 131 // Remaining amount 132 AssetPayment remainingPayment = new AssetPayment(); 133 ObjectValueUtils.copySimpleProperties(assetPayment, remainingPayment); 134 this.remainingPayments.add(remainingPayment); 135 136 applyRatioToPaymentAmounts(assetPayment, new AssetPayment[] { separatePayment, remainingPayment }, new double[] { separateRatio, retainRatio }); 137 } 138 } 139 140 141 } 142 143 /** 144 * Creates offset payment by copying and negating the separated payments 145 */ 146 private void createOffsetPayments() { 147 // create offset payment by negating the amount fields 148 for (AssetPayment separatePayment : this.separatedPayments) { 149 AssetPayment offsetPayment = new AssetPayment(); 150 ObjectValueUtils.copySimpleProperties(separatePayment, offsetPayment); 151 try { 152 negatePaymentAmounts(offsetPayment); 153 } 154 catch (Exception e) { 155 throw new RuntimeException(); 156 } 157 offsetPayment.setDocumentNumber(assetGlobal.getDocumentNumber()); 158 offsetPayment.setFinancialDocumentTypeCode(CamsConstants.PaymentDocumentTypeCodes.ASSET_GLOBAL_SEPARATE); 159 offsetPayment.setVersionNumber(null); 160 offsetPayment.setObjectId(null); 161 offsetPayment.setPaymentSequenceNumber(++maxPaymentSeqNo); 162 this.offsetPayments.add(offsetPayment); 163 } 164 this.sourceAsset.getAssetPayments().addAll(this.offsetPayments); 165 } 166 167 /** 168 * Applies the asset allocate ratio for each payment line to be created and adds to the new asset. In addition it keeps track of 169 * how amount is consumed by each asset and how each payment is being split 170 */ 171 private void allocatePaymentAmountsByRatio() { 172 int index = 0; 173 for (AssetPayment source : this.separatedPayments) { 174 175 // for each source payment, create target payments by ratio 176 AssetPayment[] targets = new AssetPayment[assetAllocateRatios.length]; 177 for (int j = 0; j < assetAllocateRatios.length; j++) { 178 AssetPayment newPayment = new AssetPayment(); 179 ObjectValueUtils.copySimpleProperties(source, newPayment); 180 Asset currentAsset = this.newAssets.get(j); 181 Long capitalAssetNumber = currentAsset.getCapitalAssetNumber(); 182 newPayment.setCapitalAssetNumber(capitalAssetNumber); 183 newPayment.setDocumentNumber(assetGlobal.getDocumentNumber()); 184 newPayment.setFinancialDocumentTypeCode(CamsConstants.PaymentDocumentTypeCodes.ASSET_GLOBAL_SEPARATE); 185 targets[j] = newPayment; 186 newPayment.setVersionNumber(null); 187 newPayment.setObjectId(null); 188 currentAsset.getAssetPayments().add(index, newPayment); 189 } 190 applyRatioToPaymentAmounts(source, targets, assetAllocateRatios); 191 192 // keep track of split happened for the source 193 this.paymentSplitMap.put(source.getPaymentSequenceNumber(), Arrays.asList(targets)); 194 195 // keep track of total amount by asset 196 for (int j = 0; j < targets.length; j++) { 197 Asset currentAsset = this.newAssets.get(j); 198 Long capitalAssetNumber = currentAsset.getCapitalAssetNumber(); 199 this.totalByAsset.put(capitalAssetNumber, this.totalByAsset.get(capitalAssetNumber).add(targets[j].getAccountChargeAmount())); 200 } 201 index++; 202 } 203 } 204 205 /** 206 * Rounds the last payment by adjusting the amounts against source amount 207 */ 208 private void roundPaymentAmounts() { 209 for (int i = 0; i < this.separatedPayments.size(); i++) { 210 applyBalanceToPaymentAmounts(separatedPayments.get(i), this.paymentSplitMap.get(separatedPayments.get(i).getPaymentSequenceNumber())); 211 } 212 } 213 214 /** 215 * Rounds the last payment by adjusting the amount compared against separate source amount and copies account charge amount to 216 * primary depreciation base amount if not zero 217 */ 218 private void roundAccountChargeAmount() { 219 for (int j = 0; j < this.newAssets.size(); j++) { 220 Asset currentAsset = this.newAssets.get(j); 221 AssetGlobalDetail detail = this.assetGlobal.getAssetGlobalDetails().get(j); 222 AssetPayment lastPayment = currentAsset.getAssetPayments().get(currentAsset.getAssetPayments().size() - 1); 223 KualiDecimal totalForAsset = this.totalByAsset.get(currentAsset.getCapitalAssetNumber()); 224 KualiDecimal diff = detail.getSeparateSourceAmount().subtract(totalForAsset); 225 lastPayment.setAccountChargeAmount(lastPayment.getAccountChargeAmount().add(diff)); 226 currentAsset.setTotalCostAmount(totalForAsset.add(diff)); 227 AssetPayment lastSource = this.separatedPayments.get(this.separatedPayments.size() - 1); 228 lastSource.setAccountChargeAmount(lastSource.getAccountChargeAmount().add(diff)); 229 // adjust primary depreciation base amount, same as account charge amount 230 if (lastPayment.getPrimaryDepreciationBaseAmount() != null && lastPayment.getPrimaryDepreciationBaseAmount().isNonZero()) { 231 lastPayment.setPrimaryDepreciationBaseAmount(lastPayment.getAccountChargeAmount()); 232 lastSource.setPrimaryDepreciationBaseAmount(lastSource.getAccountChargeAmount()); 233 } 234 } 235 } 236 237 /** 238 * Utility method which can take one payment and distribute its amount by ratio to the target payments 239 * 240 * @param source Source Payment 241 * @param targets Target Payment 242 * @param ratios Ratio to be applied for each target 243 */ 244 private void applyRatioToPaymentAmounts(AssetPayment source, AssetPayment[] targets, double[] ratios) { 245 try { 246 for (PropertyDescriptor propertyDescriptor : assetPaymentProperties) { 247 Method readMethod = propertyDescriptor.getReadMethod(); 248 if (readMethod != null && propertyDescriptor.getPropertyType() != null && KualiDecimal.class.isAssignableFrom(propertyDescriptor.getPropertyType())) { 249 KualiDecimal amount = (KualiDecimal) readMethod.invoke(source); 250 if (amount != null && amount.isNonZero()) { 251 KualiDecimal[] ratioAmounts = KualiDecimalUtils.allocateByRatio(amount, ratios); 252 Method writeMethod = propertyDescriptor.getWriteMethod(); 253 if (writeMethod != null) { 254 for (int i = 0; i < ratioAmounts.length; i++) { 255 writeMethod.invoke(targets[i], ratioAmounts[i]); 256 } 257 } 258 } 259 } 260 } 261 } 262 catch (Exception e) { 263 throw new RuntimeException(e); 264 } 265 266 } 267 268 /** 269 * Utility method which can compute the difference between source amount and consumed amounts, then will adjust the last amount 270 * 271 * @param source Source payments 272 * @param consumedList Consumed Payments 273 */ 274 private void applyBalanceToPaymentAmounts(AssetPayment source, List<AssetPayment> consumedList) { 275 try { 276 for (PropertyDescriptor propertyDescriptor : assetPaymentProperties) { 277 Method readMethod = propertyDescriptor.getReadMethod(); 278 if (readMethod != null && propertyDescriptor.getPropertyType() != null && KualiDecimal.class.isAssignableFrom(propertyDescriptor.getPropertyType())) { 279 KualiDecimal amount = (KualiDecimal) readMethod.invoke(source); 280 if (amount != null && amount.isNonZero()) { 281 Method writeMethod = propertyDescriptor.getWriteMethod(); 282 KualiDecimal consumedAmount = KualiDecimal.ZERO; 283 KualiDecimal currAmt = KualiDecimal.ZERO; 284 if (writeMethod != null) { 285 for (int i = 0; i < consumedList.size(); i++) { 286 currAmt = (KualiDecimal) readMethod.invoke(consumedList.get(i)); 287 consumedAmount = consumedAmount.add(currAmt != null ? currAmt : KualiDecimal.ZERO); 288 } 289 } 290 if (!consumedAmount.equals(amount)) { 291 AssetPayment lastPayment = consumedList.get(consumedList.size() - 1); 292 writeMethod.invoke(lastPayment, currAmt.add(amount.subtract(consumedAmount))); 293 } 294 } 295 } 296 } 297 } 298 catch (Exception e) { 299 throw new RuntimeException(e); 300 } 301 302 } 303 304 /** 305 * Utility method which will negate the payment amounts for a given payment 306 * 307 * @param assetPayment Payment to be negated 308 */ 309 public void negatePaymentAmounts(AssetPayment assetPayment) { 310 try { 311 for (PropertyDescriptor propertyDescriptor : assetPaymentProperties) { 312 Method readMethod = propertyDescriptor.getReadMethod(); 313 if (readMethod != null && propertyDescriptor.getPropertyType() != null && KualiDecimal.class.isAssignableFrom(propertyDescriptor.getPropertyType())) { 314 KualiDecimal amount = (KualiDecimal) readMethod.invoke(assetPayment); 315 Method writeMethod = propertyDescriptor.getWriteMethod(); 316 if (writeMethod != null && amount != null) { 317 writeMethod.invoke(assetPayment, (amount.negated())); 318 } 319 320 } 321 } 322 } 323 catch (Exception e) { 324 throw new RuntimeException(e); 325 } 326 } 327 328 /** 329 * Sums up YTD values and Previous Year value to decide accumulated depreciation amount 330 */ 331 private void computeAccumulatedDepreciationAmount() { 332 KualiDecimal previousYearAmount = null; 333 for (Asset asset : this.newAssets) { 334 List<AssetPayment> assetPayments = asset.getAssetPayments(); 335 for (AssetPayment currPayment : assetPayments) { 336 previousYearAmount = currPayment.getPreviousYearPrimaryDepreciationAmount(); 337 previousYearAmount = previousYearAmount == null ? KualiDecimal.ZERO : previousYearAmount; 338 KualiDecimal computedAmount = previousYearAmount.add(sumPeriodicDepreciationAmounts(currPayment)); 339 if (computedAmount.isNonZero()) { 340 currPayment.setAccumulatedPrimaryDepreciationAmount(computedAmount); 341 } 342 } 343 } 344 for (AssetPayment currPayment : this.offsetPayments) { 345 previousYearAmount = currPayment.getPreviousYearPrimaryDepreciationAmount(); 346 previousYearAmount = previousYearAmount == null ? KualiDecimal.ZERO : previousYearAmount; 347 KualiDecimal computedAmount = previousYearAmount.add(sumPeriodicDepreciationAmounts(currPayment)); 348 if (computedAmount.isNonZero()) { 349 currPayment.setAccumulatedPrimaryDepreciationAmount(computedAmount); 350 } 351 } 352 } 353 354 /** 355 * Sums up periodic amounts for a payment 356 * 357 * @param currPayment Payment 358 * @return Sum of payment 359 */ 360 public static KualiDecimal sumPeriodicDepreciationAmounts(AssetPayment currPayment) { 361 KualiDecimal ytdAmount = KualiDecimal.ZERO; 362 try { 363 for (PropertyDescriptor propertyDescriptor : assetPaymentProperties) { 364 Method readMethod = propertyDescriptor.getReadMethod(); 365 if (readMethod != null && Pattern.matches(CamsConstants.GET_PERIOD_DEPRECIATION_AMOUNT_REGEX, readMethod.getName().toLowerCase()) && propertyDescriptor.getPropertyType() != null && KualiDecimal.class.isAssignableFrom(propertyDescriptor.getPropertyType())) { 366 KualiDecimal amount = (KualiDecimal) readMethod.invoke(currPayment); 367 if (amount != null) { 368 ytdAmount = ytdAmount.add(amount); 369 } 370 } 371 } 372 } 373 catch (Exception e) { 374 throw new RuntimeException(e); 375 } 376 return ytdAmount; 377 } 378 379 /** 380 * Gets the remainingPayments attribute. 381 * 382 * @return Returns the remainingPayments. 383 */ 384 public List<AssetPayment> getRemainingPayments() { 385 return remainingPayments; 386 } 387 388 389 /** 390 * Sets the remainingPayments attribute value. 391 * 392 * @param remainingPayments The remainingPayments to set. 393 */ 394 public void setRemainingPayments(List<AssetPayment> remainingPayments) { 395 this.remainingPayments = remainingPayments; 396 } 397 398 399 /** 400 * Gets the offsetPayments attribute. 401 * 402 * @return Returns the offsetPayments. 403 */ 404 public List<AssetPayment> getOffsetPayments() { 405 return offsetPayments; 406 } 407 408 409 /** 410 * Sets the offsetPayments attribute value. 411 * 412 * @param offsetPayments The offsetPayments to set. 413 */ 414 public void setOffsetPayments(List<AssetPayment> offsetPayments) { 415 this.offsetPayments = offsetPayments; 416 } 417 418 419 /** 420 * Gets the separatedPayments attribute. 421 * 422 * @return Returns the separatedPayments. 423 */ 424 public List<AssetPayment> getSeparatedPayments() { 425 return separatedPayments; 426 } 427 428 429 /** 430 * Sets the separatedPayments attribute value. 431 * 432 * @param separatedPayments The separatedPayments to set. 433 */ 434 public void setSeparatedPayments(List<AssetPayment> separatedPayments) { 435 this.separatedPayments = separatedPayments; 436 } 437 438 439 /** 440 * Gets the assetGlobal attribute. 441 * 442 * @return Returns the assetGlobal. 443 */ 444 public AssetGlobal getAssetGlobal() { 445 return assetGlobal; 446 } 447 448 449 /** 450 * Sets the assetGlobal attribute value. 451 * 452 * @param assetGlobal The assetGlobal to set. 453 */ 454 public void setAssetGlobal(AssetGlobal assetGlobal) { 455 this.assetGlobal = assetGlobal; 456 } 457 458 459 /** 460 * Gets the newAssets attribute. 461 * 462 * @return Returns the newAssets. 463 */ 464 public List<Asset> getNewAssets() { 465 return newAssets; 466 } 467 468 469 /** 470 * Sets the newAssets attribute value. 471 * 472 * @param newAssets The newAssets to set. 473 */ 474 public void setNewAssets(List<Asset> newAssets) { 475 this.newAssets = newAssets; 476 } 477 478 479 /** 480 * Gets the assetAllocateRatios attribute. 481 * 482 * @return Returns the assetAllocateRatios. 483 */ 484 public double[] getAssetAllocateRatios() { 485 return assetAllocateRatios; 486 } 487 488 489 /** 490 * Sets the assetAllocateRatios attribute value. 491 * 492 * @param assetAllocateRatios The assetAllocateRatios to set. 493 */ 494 public void setAssetAllocateRatios(double[] assetAllocateRatios) { 495 this.assetAllocateRatios = assetAllocateRatios; 496 } 497 498 499 /** 500 * Gets the separateRatio attribute. 501 * 502 * @return Returns the separateRatio. 503 */ 504 public double getSeparateRatio() { 505 return separateRatio; 506 } 507 508 509 /** 510 * Sets the separateRatio attribute value. 511 * 512 * @param separateRatio The separateRatio to set. 513 */ 514 public void setSeparateRatio(double separateRatio) { 515 this.separateRatio = separateRatio; 516 } 517 518 519 /** 520 * Gets the retainRatio attribute. 521 * 522 * @return Returns the retainRatio. 523 */ 524 public double getRetainRatio() { 525 return retainRatio; 526 } 527 528 529 /** 530 * Sets the retainRatio attribute value. 531 * 532 * @param retainRatio The retainRatio to set. 533 */ 534 public void setRetainRatio(double retainRatio) { 535 this.retainRatio = retainRatio; 536 } 537 538 539 /** 540 * Gets the sourcePayments attribute. 541 * 542 * @return Returns the sourcePayments. 543 */ 544 public List<AssetPayment> getSourcePayments() { 545 return sourcePayments; 546 } 547 548 549 /** 550 * Sets the sourcePayments attribute value. 551 * 552 * @param sourcePayments The sourcePayments to set. 553 */ 554 public void setSourcePayments(List<AssetPayment> sourcePayments) { 555 this.sourcePayments = sourcePayments; 556 } 557 558 559 }