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.purap.util; 017 018 import java.util.ArrayList; 019 import java.util.Collection; 020 import java.util.HashMap; 021 import java.util.List; 022 import java.util.Map; 023 024 import org.apache.commons.lang.StringUtils; 025 import org.apache.commons.lang.builder.ToStringBuilder; 026 import org.apache.commons.lang.enums.Enum; 027 import org.apache.log4j.Logger; 028 import org.kuali.kfs.module.purap.PurapConstants; 029 import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine; 030 import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem; 031 import org.kuali.kfs.module.purap.businessobject.ReceivingThreshold; 032 import org.kuali.kfs.module.purap.document.PurchaseOrderDocument; 033 import org.kuali.kfs.module.purap.document.service.ThresholdService; 034 import org.kuali.kfs.module.purap.service.PurapAccountingService; 035 import org.kuali.kfs.sys.KFSPropertyConstants; 036 import org.kuali.kfs.sys.context.SpringContext; 037 import org.kuali.rice.kns.util.KualiDecimal; 038 039 /** 040 * A helper class to decide whether to set the receiving document required flag for a purchasing document or not. 041 */ 042 public class ThresholdHelper { 043 044 //////////////////////////////////////////////////////////////////////// 045 //CLASS VARIABLES 046 //////////////////////////////////////////////////////////////////////// 047 private static Logger LOG = Logger.getLogger(ThresholdHelper.class); 048 049 public static final ThresholdCriteria CHART = new ThresholdCriteria("CHART"); 050 public static final ThresholdCriteria CHART_AND_ACCOUNTTYPE = new ThresholdCriteria("CHART_AND_ACCOUNT-TYPE"); 051 public static final ThresholdCriteria CHART_AND_SUBFUND = new ThresholdCriteria("CHART_AND_SUB-FUND"); 052 public static final ThresholdCriteria CHART_AND_COMMODITYCODE = new ThresholdCriteria("CHART_AND_COMMODITY-CODE"); 053 public static final ThresholdCriteria CHART_AND_OBJECTCODE = new ThresholdCriteria("CHART_AND_OBJECT-CODE"); 054 public static final ThresholdCriteria CHART_AND_ORGANIZATIONCODE = new ThresholdCriteria("CHART_AND_ORGANIZATION-CODE"); 055 public static final ThresholdCriteria CHART_AND_VENDOR = new ThresholdCriteria("CHART_AND_VENDOR"); 056 057 //////////////////////////////////////////////////////////////////////// 058 //INSTANCE VARIABLES 059 //////////////////////////////////////////////////////////////////////// 060 private PurapAccountingService purapAccountingService; 061 private ThresholdService thresholdService; 062 063 private List<ThresholdSummary> chartCodeSummary = new ArrayList(); 064 private List<ThresholdSummary> chartCodeAndFundSummary = new ArrayList(); 065 private List<ThresholdSummary> chartCodeAndSubFundSummary = new ArrayList(); 066 private List<ThresholdSummary> chartCodeAndCommodityCodeSummary = new ArrayList(); 067 private List<ThresholdSummary> chartCodeAndObjectCodeSummary = new ArrayList(); 068 private List<ThresholdSummary> chartCodeAndOrgCodeSummary = new ArrayList(); 069 private List<ThresholdSummary> chartCodeAndVendorSummary = new ArrayList(); 070 071 private ThresholdSummary thresholdSummary; 072 private ReceivingThreshold receivingThreshold; 073 074 private boolean allItemsNonQty; 075 076 public ThresholdHelper(PurchaseOrderDocument document){ 077 purapAccountingService = SpringContext.getBean(PurapAccountingService.class); 078 thresholdService = SpringContext.getBean(ThresholdService.class); 079 setupForThresholdCheck(document); 080 } 081 082 private void setupForThresholdCheck(PurchaseOrderDocument document){ 083 084 allItemsNonQty = checkForNonQtyItems(document); 085 086 if (allItemsNonQty){ 087 return; 088 } 089 090 List<SummaryAccount> accounts = purapAccountingService.generateSummaryAccounts(document); 091 092 if (accounts != null){ 093 094 for (SummaryAccount account : accounts) { 095 096 updateThresholdSummary(CHART,account,null); 097 updateThresholdSummary(CHART_AND_ACCOUNTTYPE,account,null); 098 updateThresholdSummary(CHART_AND_SUBFUND,account,null); 099 updateThresholdSummary(CHART_AND_OBJECTCODE,account,null); 100 updateThresholdSummary(CHART_AND_ORGANIZATIONCODE,account,document.getTotalDollarAmount()); 101 102 processVendorForThresholdSummary(account, 103 document.getVendorHeaderGeneratedIdentifier().toString(), 104 document.getVendorDetailAssignedIdentifier().toString()); 105 106 } 107 } 108 109 processCommodityCodeForThreshold(document.getItems()); 110 111 } 112 113 private boolean checkForNonQtyItems(PurchaseOrderDocument document){ 114 List<PurchaseOrderItem> items = document.getItems(); 115 116 for (int i = 0; i < items.size(); i++) { 117 if (!items.get(i).getItemType().isAdditionalChargeIndicator() && 118 !StringUtils.equals(items.get(i).getItemTypeCode(),PurapConstants.ItemTypeCodes.ITEM_TYPE_SERVICE_CODE)){ 119 return false; 120 } 121 } 122 return true; 123 } 124 125 private void updateThresholdSummary(ThresholdCriteria thresholdCriteria, 126 SummaryAccount account, 127 KualiDecimal documentAmount){ 128 129 if (thresholdCriteria != CHART_AND_COMMODITYCODE && 130 thresholdCriteria != CHART_AND_VENDOR){ 131 132 ThresholdSummary thresholdSummary = new ThresholdSummary(thresholdCriteria); 133 thresholdSummary.setProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE, 134 account.getAccount().getChartOfAccountsCode()); 135 136 if (thresholdCriteria == CHART_AND_ACCOUNTTYPE){ 137 account.getAccount().refreshReferenceObject(KFSPropertyConstants.ACCOUNT); 138 if (StringUtils.isEmpty(account.getAccount().getAccount().getAccountTypeCode())){ 139 return; 140 } 141 thresholdSummary.setProperty(ThresholdField.ACCOUNT_TYPE_CODE, 142 account.getAccount().getAccount().getAccountTypeCode()); 143 144 }else if (thresholdCriteria == CHART_AND_SUBFUND){ 145 account.getAccount().refreshReferenceObject(KFSPropertyConstants.ACCOUNT); 146 if (StringUtils.isEmpty(account.getAccount().getAccount().getSubFundGroupCode())){ 147 return; 148 } 149 thresholdSummary.setProperty(ThresholdField.SUBFUND_GROUP_CODE, 150 account.getAccount().getAccount().getSubFundGroupCode()); 151 }else if (thresholdCriteria == CHART_AND_OBJECTCODE){ 152 if (StringUtils.isEmpty(account.getAccount().getFinancialObjectCode())){ 153 return; 154 } 155 thresholdSummary.setProperty(ThresholdField.FINANCIAL_OBJECT_CODE, 156 account.getAccount().getFinancialObjectCode()); 157 }else if (thresholdCriteria == CHART_AND_ORGANIZATIONCODE){ 158 account.getAccount().refreshReferenceObject(KFSPropertyConstants.ACCOUNT); 159 if (StringUtils.isEmpty(account.getAccount().getAccount().getOrganizationCode())){ 160 return; 161 } 162 thresholdSummary.setProperty(ThresholdField.ORGANIZATION_CODE, 163 account.getAccount().getAccount().getOrganizationCode()); 164 thresholdSummary.addTotalAmount(documentAmount); 165 } 166 167 if (thresholdCriteria != CHART_AND_ORGANIZATIONCODE){ 168 thresholdSummary.addTotalAmount(account.getAccount().getAmount()); 169 } 170 addToSummaryList(thresholdSummary); 171 } 172 } 173 174 private void addToSummaryList(ThresholdSummary thresholdSummary){ 175 176 List<ThresholdSummary> summaryList = getThresholdSummaryCollection(thresholdSummary.getThresholdCriteria()); 177 178 boolean matchFound = false; 179 for (int i = 0; i < summaryList.size(); i++) { 180 if (thresholdSummary.equals(summaryList.get(i))){ 181 summaryList.get(i).addTotalAmount(thresholdSummary.getTotalAmount()); 182 matchFound = true; 183 break; 184 } 185 } 186 187 if (!matchFound){ 188 summaryList.add(thresholdSummary); 189 } 190 } 191 192 private List<ThresholdSummary> getThresholdSummaryCollection(ThresholdCriteria thresholdCriteria){ 193 194 if (thresholdCriteria == CHART){ 195 return chartCodeSummary; 196 }else if (thresholdCriteria == CHART_AND_ACCOUNTTYPE){ 197 return chartCodeAndFundSummary; 198 }else if (thresholdCriteria == CHART_AND_SUBFUND){ 199 return chartCodeAndSubFundSummary; 200 }else if (thresholdCriteria == CHART_AND_COMMODITYCODE){ 201 return chartCodeAndCommodityCodeSummary; 202 }else if (thresholdCriteria == CHART_AND_OBJECTCODE){ 203 return chartCodeAndObjectCodeSummary; 204 }else if (thresholdCriteria == CHART_AND_ORGANIZATIONCODE){ 205 return chartCodeAndOrgCodeSummary; 206 }else if (thresholdCriteria == CHART_AND_VENDOR){ 207 return chartCodeAndVendorSummary; 208 } 209 210 throw new RuntimeException("Invalid ThresholdCriteria Enum - " + thresholdCriteria); 211 } 212 213 private void processVendorForThresholdSummary(SummaryAccount account, 214 String vendorHeaderGeneratedIdentifier, 215 String vendorDetailAssignedIdentifier){ 216 217 ThresholdSummary thresholdSummary = new ThresholdSummary(CHART_AND_VENDOR); 218 thresholdSummary.setProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE,account.getAccount().getChartOfAccountsCode()); 219 thresholdSummary.setProperty(ThresholdField.VENDOR_HEADER_GENERATED_ID,vendorHeaderGeneratedIdentifier); 220 thresholdSummary.setProperty(ThresholdField.VENDOR_DETAIL_ASSIGNED_ID,vendorDetailAssignedIdentifier); 221 thresholdSummary.addTotalAmount(account.getAccount().getAmount()); 222 223 addToSummaryList(thresholdSummary); 224 225 } 226 227 private void processCommodityCodeForThreshold(List<PurchaseOrderItem> items){ 228 if (items != null){ 229 for (PurchaseOrderItem item : items) { 230 if (item.isItemActiveIndicator()){ 231 List<PurApAccountingLine> accountingLines = item.getSourceAccountingLines(); 232 for (int i = 0; i < accountingLines.size(); i++) { 233 if (StringUtils.isNotBlank(item.getPurchasingCommodityCode())){ 234 ThresholdSummary thresholdSummary = new ThresholdSummary(CHART_AND_COMMODITYCODE); 235 thresholdSummary.setProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE,accountingLines.get(i).getChartOfAccountsCode()); 236 thresholdSummary.setProperty(ThresholdField.COMMODITY_CODE,item.getPurchasingCommodityCode()); 237 thresholdSummary.addTotalAmount(item.getExtendedPrice()); 238 addToSummaryList(thresholdSummary); 239 } 240 } 241 } 242 } 243 } 244 } 245 246 public boolean isReceivingDocumentRequired() { 247 248 // From spec - 7. If all the line items are non-quantity do not do the threshold check. 249 if (allItemsNonQty){ 250 return false; 251 } 252 253 for (ThresholdCriteria thresholdEnum : ThresholdCriteria.getEnumList()) { 254 boolean result = isReceivingDocumentRequired(thresholdEnum); 255 if (result){ 256 return true; 257 } 258 } 259 260 return false; 261 } 262 263 /** 264 * This method is public since it's required in the ThresholdTest class. To know the receiving required doc status for a PO, 265 * it's always better to call isReceivingDocumentRequired() instead of this method. 266 */ 267 public boolean isReceivingDocumentRequired(ThresholdCriteria thresholdEnum) { 268 269 List<ThresholdSummary> summaryList = getThresholdSummaryCollection(thresholdEnum); 270 271 if (summaryList != null){ 272 for (ThresholdSummary summary : summaryList) { 273 Collection collection = null; 274 275 if (thresholdEnum == CHART){ 276 collection = thresholdService.findByChart(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)); 277 }else if (thresholdEnum == CHART_AND_ACCOUNTTYPE){ 278 collection = thresholdService.findByChartAndFund(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE), 279 summary.getProperty(ThresholdField.ACCOUNT_TYPE_CODE)); 280 }else if (thresholdEnum == CHART_AND_SUBFUND){ 281 collection = thresholdService.findByChartAndSubFund(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE), 282 summary.getProperty(ThresholdField.SUBFUND_GROUP_CODE)); 283 }else if (thresholdEnum == CHART_AND_COMMODITYCODE){ 284 collection = thresholdService.findByChartAndCommodity(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE), 285 summary.getProperty(ThresholdField.COMMODITY_CODE)); 286 }else if (thresholdEnum == CHART_AND_OBJECTCODE){ 287 collection = thresholdService.findByChartAndObjectCode(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE), 288 summary.getProperty(ThresholdField.FINANCIAL_OBJECT_CODE)); 289 }else if (thresholdEnum == CHART_AND_ORGANIZATIONCODE){ 290 collection = thresholdService.findByChartAndOrg(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE), 291 summary.getProperty(ThresholdField.ORGANIZATION_CODE)); 292 }else if (thresholdEnum == CHART_AND_VENDOR){ 293 collection = thresholdService.findByChartAndVendor(summary.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE), 294 summary.getProperty(ThresholdField.VENDOR_HEADER_GENERATED_ID), 295 summary.getProperty(ThresholdField.VENDOR_DETAIL_ASSIGNED_ID)); 296 } 297 298 if (collection != null){ 299 for (ReceivingThreshold threshold :(List<ReceivingThreshold>) collection){ 300 if (threshold.getThresholdAmount() == null || threshold.getThresholdAmount().isLessThan(summary.getTotalAmount())){ 301 thresholdSummary = summary; 302 receivingThreshold = threshold; 303 return true; 304 } 305 } 306 } 307 } 308 } 309 310 return false; 311 312 } 313 314 public ThresholdSummary getThresholdSummary() { 315 return thresholdSummary; 316 } 317 318 public ReceivingThreshold getReceivingThreshold() { 319 return receivingThreshold; 320 } 321 322 public class ThresholdSummary { 323 324 private ThresholdCriteria thresholdCriteria; 325 private Map<ThresholdField,String> property2Value = new HashMap<ThresholdField,String>(); 326 private KualiDecimal totalAmount = KualiDecimal.ZERO; 327 328 ThresholdSummary(ThresholdCriteria thresholdCriteria){ 329 this.thresholdCriteria = thresholdCriteria; 330 } 331 332 void setProperty(ThresholdField thresholdField, 333 String fieldValue){ 334 if (!isValidProperty(thresholdField)){ 335 throw new RuntimeException("Property[" + thresholdField + "] not allowed for the threshold criteria[" + thresholdCriteria + "]"); 336 } 337 338 property2Value.put(thresholdField,fieldValue); 339 } 340 341 String getProperty(ThresholdField thresholdEnum){ 342 return property2Value.get(thresholdEnum); 343 } 344 345 public ThresholdCriteria getThresholdCriteria() { 346 return thresholdCriteria; 347 } 348 349 public String getThresholdCriteriaName() { 350 return thresholdCriteria.getName(); 351 } 352 353 public KualiDecimal getTotalAmount() { 354 return totalAmount; 355 } 356 357 void addTotalAmount(KualiDecimal totalAmount) { 358 if (totalAmount != null){ 359 this.totalAmount = this.totalAmount.add(totalAmount); 360 } 361 } 362 363 boolean isValidProperty(ThresholdField thresholdField){ 364 365 if (getThresholdCriteria() == CHART && 366 ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField){ 367 return true; 368 }else if ((getThresholdCriteria() == CHART_AND_ACCOUNTTYPE) && 369 (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField || 370 ThresholdField.ACCOUNT_TYPE_CODE == thresholdField)){ 371 return true; 372 }else if ((getThresholdCriteria() == CHART_AND_SUBFUND) && 373 (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField || 374 ThresholdField.SUBFUND_GROUP_CODE == thresholdField)){ 375 return true; 376 }else if ((getThresholdCriteria() == CHART_AND_COMMODITYCODE) && 377 (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField || 378 ThresholdField.COMMODITY_CODE == thresholdField)){ 379 return true; 380 }else if ((getThresholdCriteria() == CHART_AND_OBJECTCODE) && 381 (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField || 382 ThresholdField.FINANCIAL_OBJECT_CODE == thresholdField)){ 383 return true; 384 }else if ((getThresholdCriteria() == CHART_AND_ORGANIZATIONCODE) && 385 (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField || 386 ThresholdField.ORGANIZATION_CODE == thresholdField)){ 387 return true; 388 }else if ((getThresholdCriteria() == CHART_AND_VENDOR) && 389 (ThresholdField.CHART_OF_ACCOUNTS_CODE == thresholdField || 390 ThresholdField.VENDOR_HEADER_GENERATED_ID == thresholdField || 391 ThresholdField.VENDOR_DETAIL_ASSIGNED_ID == thresholdField)){ 392 return true; 393 } 394 395 return false; 396 } 397 398 @Override 399 public boolean equals(Object obj){ 400 401 if (obj != null){ 402 403 if (!(obj instanceof ThresholdSummary)){ 404 return false; 405 } 406 407 ThresholdSummary thresholdItem = (ThresholdSummary)obj; 408 409 if (getThresholdCriteria() == thresholdItem.getThresholdCriteria()){ 410 411 if (getThresholdCriteria() == CHART){ 412 413 if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE), 414 thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE))){ 415 return true; 416 } 417 418 }else if (getThresholdCriteria() == CHART_AND_ACCOUNTTYPE){ 419 420 if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE), 421 thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) && 422 StringUtils.equals(property2Value.get(ThresholdField.ACCOUNT_TYPE_CODE), 423 thresholdItem.getProperty(ThresholdField.ACCOUNT_TYPE_CODE))){ 424 return true; 425 } 426 427 }else if (getThresholdCriteria() == CHART_AND_SUBFUND){ 428 429 if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE), 430 thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) && 431 StringUtils.equals(property2Value.get(ThresholdField.SUBFUND_GROUP_CODE), 432 thresholdItem.getProperty(ThresholdField.SUBFUND_GROUP_CODE))){ 433 return true; 434 } 435 436 }else if (getThresholdCriteria() == CHART_AND_COMMODITYCODE){ 437 438 if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE), 439 thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) && 440 StringUtils.equals(property2Value.get(ThresholdField.COMMODITY_CODE), 441 thresholdItem.getProperty(ThresholdField.COMMODITY_CODE))){ 442 return true; 443 } 444 445 }else if (getThresholdCriteria() == CHART_AND_OBJECTCODE){ 446 447 if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE), 448 thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) && 449 StringUtils.equals(property2Value.get(ThresholdField.FINANCIAL_OBJECT_CODE), 450 thresholdItem.getProperty(ThresholdField.FINANCIAL_OBJECT_CODE))){ 451 return true; 452 } 453 454 }else if (getThresholdCriteria() == CHART_AND_ORGANIZATIONCODE){ 455 456 if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE), 457 thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) && 458 StringUtils.equals(property2Value.get(ThresholdField.ORGANIZATION_CODE), 459 thresholdItem.getProperty(ThresholdField.ORGANIZATION_CODE))){ 460 return true; 461 } 462 463 }else if (getThresholdCriteria() == CHART_AND_VENDOR){ 464 465 if (StringUtils.equals(property2Value.get(ThresholdField.CHART_OF_ACCOUNTS_CODE), 466 thresholdItem.getProperty(ThresholdField.CHART_OF_ACCOUNTS_CODE)) && 467 StringUtils.equals(property2Value.get(ThresholdField.VENDOR_HEADER_GENERATED_ID), 468 thresholdItem.getProperty(ThresholdField.VENDOR_HEADER_GENERATED_ID)) && 469 StringUtils.equals(property2Value.get(ThresholdField.VENDOR_DETAIL_ASSIGNED_ID), 470 thresholdItem.getProperty(ThresholdField.VENDOR_DETAIL_ASSIGNED_ID))){ 471 return true; 472 } 473 474 } 475 } 476 } 477 return false; 478 } 479 480 @Override 481 public String toString(){ 482 ToStringBuilder stringBuilder = new ToStringBuilder(this); 483 stringBuilder.append("ThresholdCriteria",getThresholdCriteria().getName()); 484 stringBuilder.append("Amount",getTotalAmount()); 485 stringBuilder.append("Field2Values",property2Value); 486 487 return stringBuilder.toString(); 488 } 489 } 490 491 } 492 493 final class ThresholdCriteria extends Enum { 494 495 ThresholdCriteria(String name) { 496 super(name); 497 } 498 499 public static List<ThresholdCriteria> getEnumList() { 500 return getEnumList(ThresholdCriteria.class); 501 } 502 } 503