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.endow.batch.service.impl; 017 018 import static org.kuali.kfs.module.endow.EndowConstants.NEW_TARGET_TRAN_LINE_PROPERTY_NAME; 019 020 import java.math.BigDecimal; 021 import java.util.ArrayList; 022 import java.util.HashMap; 023 import java.util.List; 024 import java.util.Map; 025 026 import org.kuali.kfs.module.endow.EndowConstants; 027 import org.kuali.kfs.module.endow.EndowParameterKeyConstants; 028 import org.kuali.kfs.module.endow.batch.CreateAccrualTransactionsStep; 029 import org.kuali.kfs.module.endow.batch.service.CreateAccrualTransactionsService; 030 import org.kuali.kfs.module.endow.businessobject.EndowmentTargetTransactionLine; 031 import org.kuali.kfs.module.endow.businessobject.EndowmentTargetTransactionSecurity; 032 import org.kuali.kfs.module.endow.businessobject.EndowmentTransactionLine; 033 import org.kuali.kfs.module.endow.businessobject.EndowmentTransactionSecurity; 034 import org.kuali.kfs.module.endow.businessobject.HoldingTaxLot; 035 import org.kuali.kfs.module.endow.businessobject.Security; 036 import org.kuali.kfs.module.endow.businessobject.TransactionDocumentExceptionReportLine; 037 import org.kuali.kfs.module.endow.businessobject.TransactionDocumentTotalReportLine; 038 import org.kuali.kfs.module.endow.dataaccess.SecurityDao; 039 import org.kuali.kfs.module.endow.document.CashIncreaseDocument; 040 import org.kuali.kfs.module.endow.document.service.HoldingTaxLotService; 041 import org.kuali.kfs.module.endow.document.service.KEMService; 042 import org.kuali.kfs.module.endow.document.validation.event.AddTransactionLineEvent; 043 import org.kuali.kfs.module.endow.util.GloabalVariablesExtractHelper; 044 import org.kuali.kfs.sys.service.ReportWriterService; 045 import org.kuali.rice.kew.exception.WorkflowException; 046 import org.kuali.rice.kns.rule.event.RouteDocumentEvent; 047 import org.kuali.rice.kns.service.BusinessObjectService; 048 import org.kuali.rice.kns.service.DocumentService; 049 import org.kuali.rice.kns.service.KualiConfigurationService; 050 import org.kuali.rice.kns.service.KualiRuleService; 051 import org.kuali.rice.kns.service.ParameterService; 052 import org.kuali.rice.kns.util.KualiDecimal; 053 import org.springframework.transaction.annotation.Transactional; 054 055 /** 056 * This class... 057 */ 058 @Transactional 059 public class CreateAccrualTransactionsServiceImpl implements CreateAccrualTransactionsService { 060 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CreateAccrualTransactionsServiceImpl.class); 061 062 private BusinessObjectService businessObjectService; 063 private KEMService kemService; 064 private HoldingTaxLotService holdingTaxLotService; 065 private SecurityDao securityDao; 066 private DocumentService documentService; 067 private KualiConfigurationService configService; 068 private KualiRuleService kualiRuleService; 069 protected ParameterService parameterService; 070 071 protected ReportWriterService accrualTransactionsExceptionReportWriterService; 072 protected ReportWriterService accrualTransactionsTotalReportWriterService; 073 074 protected TransactionDocumentExceptionReportLine exceptionReportLine = null; 075 protected TransactionDocumentTotalReportLine totalReportLine = null; 076 077 protected boolean isFistTimeForWritingTotalReport = true; 078 protected boolean isFistTimeForWritingExceptionReport = true; 079 080 /** 081 * Constructs a CreateAccrualTransactionsServiceImpl.java. 082 */ 083 public CreateAccrualTransactionsServiceImpl() { 084 085 } 086 087 /** 088 * @see org.kuali.kfs.module.endow.batch.service.CreateAccrualTransactionsService#createAccrualTransactions() 089 */ 090 public boolean createAccrualTransactions() { 091 boolean success = true; 092 093 LOG.debug("createAccrualTransactions() started"); 094 095 int maxNumberOfTranLines = kemService.getMaxNumberOfTransactionLinesPerDocument(); 096 097 // get all securities with next income pay date equal to current date 098 List<Security> securities = getAllSecuritiesWithNextPayDateEqualCurrentDate(); 099 100 for (Security security : securities) { 101 102 // get all tax lots that have an accrued income greater than zero 103 List<HoldingTaxLot> taxLots = holdingTaxLotService.getAllTaxLotsWithAccruedIncomeGreaterThanZeroPerSecurity(security.getId()); 104 105 // build a map that groups tax lots based on registration code ( a map from registration code to taxlots) 106 Map<String, List<HoldingTaxLot>> regCodeMap = groupTaxLotsBasedOnRegistrationCode(taxLots); 107 108 // create Cash Increase documents for each security and registration code 109 for (String registrationCode : regCodeMap.keySet()) { 110 111 // 4. create new CashIncreaseDocument 112 CashIncreaseDocument cashIncreaseDocument = createNewCashIncreaseDocument(security.getId(), registrationCode); 113 114 if (cashIncreaseDocument != null) { 115 116 // create a new totalReportLine and exceptionReportLine for a new CashIncreaseDocument 117 initializeTotalAndExceptionReportLines(cashIncreaseDocument.getDocumentNumber(), security.getId()); 118 119 // group tax lots by kemid and ip indicator; for each kemid and ip indicator we create a new transaction line 120 Map<String, List<HoldingTaxLot>> kemidIpMap = groupTaxLotsBasedOnKemidAndIPIndicator(regCodeMap.get(registrationCode)); 121 122 // keep track of the tax lots to be updated 123 List<HoldingTaxLot> taxLotsForUpdate = new ArrayList<HoldingTaxLot>(); 124 // keep a counter to create a new document if there are more that maximum number of allowed transaction lines 125 // per 126 // document 127 int counter = 0; 128 129 // create a new transaction line for each kemid and ip indicator 130 for (String kemidIp : kemidIpMap.keySet()) { 131 132 // compute the total amount for the transaction line 133 KualiDecimal totalAmount = KualiDecimal.ZERO; 134 String kemid = null; 135 136 for (HoldingTaxLot lot : kemidIpMap.get(kemidIp)) { 137 totalAmount = totalAmount.add(new KualiDecimal(lot.getCurrentAccrual())); 138 139 // initialize kemid 140 if (kemid == null) { 141 kemid = lot.getKemid(); 142 } 143 } 144 145 // collect tax lots for update: when the document is submitted the tax lots accrual is copied to prior 146 // accrual 147 // and the current accrual is reset to zero 148 taxLotsForUpdate.addAll(kemidIpMap.get(kemidIp)); 149 150 // if we have already reached the maximum number of transaction lines on the current document then create a 151 // new 152 // document 153 if (counter == maxNumberOfTranLines) { 154 // submit the current ECI doc and update the values in the tax lots used already 155 submitCashIncreaseDocumentAndUpdateTaxLots(cashIncreaseDocument, taxLotsForUpdate); 156 157 // clear tax lots for update and create a new Cash Increase document 158 taxLotsForUpdate.clear(); 159 cashIncreaseDocument = createNewCashIncreaseDocument(security.getId(), registrationCode); 160 161 if (cashIncreaseDocument != null) { 162 // create a new totalReportLine and exceptionReportLine for a new CashIncreaseDocument 163 initializeTotalAndExceptionReportLines(cashIncreaseDocument.getDocumentNumber(), security.getId()); 164 165 counter = 0; 166 } 167 } 168 169 if (cashIncreaseDocument != null) { 170 // add a new transaction line 171 if (addTransactionLine(cashIncreaseDocument, security, kemid, totalAmount)) { 172 counter++; 173 } 174 else { 175 // if unable to add a new transaction line then remove the tax lots from the tax lots to update list 176 taxLotsForUpdate.remove(kemidIp); 177 } 178 } 179 } 180 181 // submit the current ECI doc and update the values in the tax lots used already 182 submitCashIncreaseDocumentAndUpdateTaxLots(cashIncreaseDocument, taxLotsForUpdate); 183 } 184 185 } 186 187 LOG.debug("createAccrualTransactions() done"); 188 } 189 190 return success; 191 } 192 193 /** 194 * Builds a map that groups tax lots based on registration code ( a map from registration code to taxlots) 195 * 196 * @param taxLots 197 * @return a map from registration code to taxlots 198 */ 199 protected Map<String, List<HoldingTaxLot>> groupTaxLotsBasedOnRegistrationCode(List<HoldingTaxLot> taxLots) { 200 // build a map that groups tax lots based on registration code ( a map from registration code to taxlots) 201 Map<String, List<HoldingTaxLot>> regCodeMap = new HashMap<String, List<HoldingTaxLot>>(); 202 203 for (HoldingTaxLot holdingTaxLot : taxLots) { 204 String registrationCode = holdingTaxLot.getRegistrationCode(); 205 if (regCodeMap.containsKey(registrationCode)) { 206 regCodeMap.get(registrationCode).add(holdingTaxLot); 207 } 208 else { 209 List<HoldingTaxLot> tmpTaxLots = new ArrayList<HoldingTaxLot>(); 210 tmpTaxLots.add(holdingTaxLot); 211 regCodeMap.put(registrationCode, tmpTaxLots); 212 } 213 } 214 215 return regCodeMap; 216 } 217 218 /** 219 * Builds a map that groups tax lots based on kemid and income principal indicator ( a map from kemid and IP to taxlots). 220 * 221 * @param taxLots 222 * @return a map from kemid and IP to taxlots 223 */ 224 protected Map<String, List<HoldingTaxLot>> groupTaxLotsBasedOnKemidAndIPIndicator(List<HoldingTaxLot> taxLots) { 225 // group tax lots by kemid and ip indicator 226 Map<String, List<HoldingTaxLot>> kemidIpMap = new HashMap<String, List<HoldingTaxLot>>(); 227 228 for (HoldingTaxLot holdingTaxLot : taxLots) { 229 String kemidAndIp = holdingTaxLot.getKemid() + holdingTaxLot.getIncomePrincipalIndicator(); 230 if (kemidIpMap.containsKey(kemidAndIp)) { 231 kemidIpMap.get(kemidAndIp).add(holdingTaxLot); 232 } 233 else { 234 List<HoldingTaxLot> tmpTaxLots = new ArrayList<HoldingTaxLot>(); 235 tmpTaxLots.add(holdingTaxLot); 236 kemidIpMap.put(kemidAndIp, tmpTaxLots); 237 } 238 } 239 240 return kemidIpMap; 241 } 242 243 /** 244 * Creates and adds a new transaction line to the cash increase document. 245 * 246 * @param cashIncreaseDocument 247 * @param security 248 * @param kemid 249 * @param totalAmount 250 * @return true if transaction line successfully added, false otherwise 251 */ 252 protected boolean addTransactionLine(CashIncreaseDocument cashIncreaseDocument, Security security, String kemid, KualiDecimal totalAmount) { 253 boolean success = true; 254 255 // Create a new transaction line 256 EndowmentTransactionLine endowmentTransactionLine = new EndowmentTargetTransactionLine(); 257 endowmentTransactionLine.setDocumentNumber(cashIncreaseDocument.getDocumentNumber()); 258 endowmentTransactionLine.setKemid(kemid); 259 endowmentTransactionLine.setEtranCode(security.getClassCode().getSecurityIncomeEndowmentTransactionPostCode()); 260 endowmentTransactionLine.setTransactionIPIndicatorCode(EndowConstants.IncomePrincipalIndicator.INCOME); 261 endowmentTransactionLine.setTransactionAmount(totalAmount); 262 263 boolean rulesPassed = kualiRuleService.applyRules(new AddTransactionLineEvent(NEW_TARGET_TRAN_LINE_PROPERTY_NAME, cashIncreaseDocument, endowmentTransactionLine)); 264 265 if (rulesPassed) { 266 cashIncreaseDocument.addTargetTransactionLine((EndowmentTargetTransactionLine) endowmentTransactionLine); 267 totalReportLine.addIncomeAmount(totalAmount); 268 } 269 else { 270 success = false; 271 272 // write an exception line when a transaction line fails to pass the validation. 273 exceptionReportLine.setKemid(kemid); 274 exceptionReportLine.setIncomeAmount(totalAmount); 275 if (isFistTimeForWritingExceptionReport) { 276 accrualTransactionsExceptionReportWriterService.writeTableHeader(exceptionReportLine); 277 isFistTimeForWritingExceptionReport = false; 278 } 279 accrualTransactionsExceptionReportWriterService.writeTableRow(exceptionReportLine); 280 List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors(); 281 for (String errorMessage : errorMessages) { 282 accrualTransactionsExceptionReportWriterService.writeFormattedMessageLine("Reason: %s", errorMessage); 283 accrualTransactionsExceptionReportWriterService.writeNewLines(1); 284 } 285 } 286 return success; 287 } 288 289 /** 290 * Creates a new CashIncreaseDocument with source type Automated, transaction sub-type Cash, target security id set to the input 291 * security id. 292 * 293 * @param securityId 294 * @return a new CashIncreaseDocument 295 */ 296 protected CashIncreaseDocument createNewCashIncreaseDocument(String securityId, String registrationCode) { 297 CashIncreaseDocument cashIncreaseDocument = null; 298 try { 299 300 cashIncreaseDocument = (CashIncreaseDocument) documentService.getNewDocument(getCashIncreaseDocumentType()); 301 String documentDescription = parameterService.getParameterValue(CreateAccrualTransactionsStep.class, EndowParameterKeyConstants.DESCRIPTION); 302 cashIncreaseDocument.getDocumentHeader().setDocumentDescription(documentDescription); 303 cashIncreaseDocument.setTransactionSourceTypeCode(EndowConstants.TransactionSourceTypeCode.AUTOMATED); 304 cashIncreaseDocument.setTransactionSubTypeCode(EndowConstants.TransactionSubTypeCode.CASH); 305 306 // set security details 307 EndowmentTransactionSecurity targetTransactionSecurity = new EndowmentTargetTransactionSecurity(); 308 targetTransactionSecurity.setSecurityID(securityId); 309 targetTransactionSecurity.setRegistrationCode(registrationCode); 310 cashIncreaseDocument.setTargetTransactionSecurity(targetTransactionSecurity); 311 312 } 313 catch (WorkflowException ex) { 314 if (isFistTimeForWritingExceptionReport) { 315 if (exceptionReportLine == null) { 316 exceptionReportLine = new TransactionDocumentExceptionReportLine(getCashIncreaseDocumentType(), "", securityId); 317 } 318 accrualTransactionsExceptionReportWriterService.writeTableHeader(exceptionReportLine); 319 isFistTimeForWritingExceptionReport = false; 320 } 321 accrualTransactionsExceptionReportWriterService.writeTableRow(exceptionReportLine); 322 accrualTransactionsExceptionReportWriterService.writeFormattedMessageLine("Reason: %s", "WorkflowException while creating a CashIncreaseDocument for Accrual Transactions: " + ex.toString()); 323 accrualTransactionsExceptionReportWriterService.writeNewLines(1); 324 } 325 326 return cashIncreaseDocument; 327 } 328 329 /** 330 * Submits the ECI doc and updates the values in the tax lots list. 331 * 332 * @param cashIncreaseDocument 333 * @param taxLotsForUpdate 334 */ 335 protected void submitCashIncreaseDocumentAndUpdateTaxLots(CashIncreaseDocument cashIncreaseDocument, List<HoldingTaxLot> taxLotsForUpdate) { 336 337 boolean rulesPassed = kualiRuleService.applyRules(new RouteDocumentEvent(cashIncreaseDocument)); 338 339 if (rulesPassed) { 340 341 String noRouteIndVal = parameterService.getParameterValue(CreateAccrualTransactionsStep.class, EndowParameterKeyConstants.NO_ROUTE_IND); 342 boolean noRouteIndicator = EndowConstants.YES.equalsIgnoreCase(noRouteIndVal) ? true : false; 343 344 try { 345 cashIncreaseDocument.setNoRouteIndicator(noRouteIndicator); 346 // TODO figure out if/how we use the ad hoc recipients list 347 documentService.routeDocument(cashIncreaseDocument, "Created by Accrual Transactions Batch process.", null); 348 349 // write a total report line for a CashIncreaseDocument 350 if (isFistTimeForWritingTotalReport) { 351 accrualTransactionsTotalReportWriterService.writeTableHeader(totalReportLine); 352 isFistTimeForWritingTotalReport = false; 353 } 354 355 accrualTransactionsTotalReportWriterService.writeTableRow(totalReportLine); 356 357 // set accrued income to zero and copy current value in prior accrued income 358 for (HoldingTaxLot taxLotForUpdate : taxLotsForUpdate) { 359 taxLotForUpdate.setPriorAccrual(taxLotForUpdate.getCurrentAccrual()); 360 taxLotForUpdate.setCurrentAccrual(BigDecimal.ZERO); 361 } 362 363 // save changes 364 businessObjectService.save(taxLotsForUpdate); 365 } 366 catch (WorkflowException ex) { 367 // save document if it can not be routed.... 368 try { 369 documentService.saveDocument(cashIncreaseDocument); 370 } catch (WorkflowException savewfe) { 371 372 } 373 374 if (isFistTimeForWritingExceptionReport) { 375 accrualTransactionsExceptionReportWriterService.writeTableHeader(exceptionReportLine); 376 isFistTimeForWritingExceptionReport = false; 377 } 378 379 accrualTransactionsExceptionReportWriterService.writeTableRow(exceptionReportLine); 380 accrualTransactionsExceptionReportWriterService.writeFormattedMessageLine("Reason: %s", "WorkflowException while routing a CashIncreaseDocument for Accrual Transactions batch process: " + ex.toString()); 381 accrualTransactionsExceptionReportWriterService.writeNewLines(1); 382 } 383 } 384 else { 385 try { 386 exceptionReportLine.setSecurityId(cashIncreaseDocument.getTargetTransactionSecurity().getSecurityID()); 387 exceptionReportLine.setIncomeAmount(cashIncreaseDocument.getTargetIncomeTotal()); 388 accrualTransactionsExceptionReportWriterService.writeTableRow(exceptionReportLine); 389 List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors(); 390 for (String errorMessage : errorMessages) { 391 accrualTransactionsExceptionReportWriterService.writeFormattedMessageLine("Reason: %s", errorMessage); 392 accrualTransactionsExceptionReportWriterService.writeNewLines(1); 393 } 394 395 // try to save the document 396 documentService.saveDocument(cashIncreaseDocument); 397 398 } 399 catch (WorkflowException ex) { 400 // have to write a table header before write the table row. 401 if (isFistTimeForWritingExceptionReport) { 402 accrualTransactionsExceptionReportWriterService.writeTableHeader(exceptionReportLine); 403 isFistTimeForWritingExceptionReport = false; 404 } 405 accrualTransactionsExceptionReportWriterService.writeTableRow(exceptionReportLine); 406 // Write reason as a formatted message in a second line 407 accrualTransactionsExceptionReportWriterService.writeFormattedMessageLine("Reason: %s", "WorkflowException while saving a CashIncreaseDocument for Accrual Transactions batch process: " + ex.toString()); 408 accrualTransactionsExceptionReportWriterService.writeNewLines(1); 409 410 } 411 412 } 413 } 414 415 /** 416 * Gets the CashIncreaseDocument type. 417 * 418 * @return the CashIncreaseDocument type 419 */ 420 private String getCashIncreaseDocumentType() { 421 return "ECI"; 422 } 423 424 /** 425 * Locates all Security records for which the next income pay date is equal to the current date. 426 * 427 * @return 428 */ 429 protected List<Security> getAllSecuritiesWithNextPayDateEqualCurrentDate() { 430 List<Security> result = new ArrayList<Security>(); 431 432 result = securityDao.getAllSecuritiesWithNextPayDateEqualCurrentDate(); 433 434 return result; 435 } 436 437 /** 438 * Initializes the total report line and the exception report line. 439 * 440 * @param theDocumentId 441 * @param theSecurityId 442 */ 443 protected void initializeTotalAndExceptionReportLines(String theDocumentId, String theSecurityId) { 444 // create a new totalReportLine for each new CashIncreaseDocument 445 this.totalReportLine = new TransactionDocumentTotalReportLine(getCashIncreaseDocumentType(), theDocumentId, theSecurityId); 446 447 // create an exceptionReportLine instance that can be reused for reporting multiple errors for a CashIncreaseDocument 448 this.exceptionReportLine = new TransactionDocumentExceptionReportLine(getCashIncreaseDocumentType(), theDocumentId, theSecurityId); 449 450 } 451 452 /** 453 * Sets the businessObjectService. 454 * 455 * @param businessObjectService 456 */ 457 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 458 this.businessObjectService = businessObjectService; 459 } 460 461 /** 462 * Sets the kemService. 463 * 464 * @param kemService 465 */ 466 public void setKemService(KEMService kemService) { 467 this.kemService = kemService; 468 } 469 470 /** 471 * Sets the holdingTaxLotService. 472 * 473 * @param holdingTaxLotService 474 */ 475 public void setHoldingTaxLotService(HoldingTaxLotService holdingTaxLotService) { 476 this.holdingTaxLotService = holdingTaxLotService; 477 } 478 479 /** 480 * Sets the securityDao. 481 * 482 * @param securityDao 483 */ 484 public void setSecurityDao(SecurityDao securityDao) { 485 this.securityDao = securityDao; 486 } 487 488 /** 489 * Sets the documenyService. 490 * 491 * @param documentService 492 */ 493 public void setDocumentService(DocumentService documentService) { 494 this.documentService = documentService; 495 } 496 497 /** 498 * Sets the configService. 499 * 500 * @param configService 501 */ 502 public void setConfigService(KualiConfigurationService configService) { 503 this.configService = configService; 504 } 505 506 /** 507 * Sets the kualiRuleService. 508 * 509 * @param kualiRuleService 510 */ 511 public void setKualiRuleService(KualiRuleService kualiRuleService) { 512 this.kualiRuleService = kualiRuleService; 513 } 514 515 /** 516 * Sets the parameterService. 517 * 518 * @param parameterService 519 */ 520 public void setParameterService(ParameterService parameterService) { 521 this.parameterService = parameterService; 522 } 523 524 /** 525 * Gets the accrualTransactionsExceptionReportWriterService. 526 * 527 * @return accrualTransactionsExceptionReportWriterService 528 */ 529 public ReportWriterService getAccrualTransactionsExceptionReportWriterService() { 530 return accrualTransactionsExceptionReportWriterService; 531 } 532 533 /** 534 * Sets the accrualTransactionsExceptionReportWriterService. 535 * 536 * @param accrualTransactionsExceptionReportWriterService 537 */ 538 public void setAccrualTransactionsExceptionReportWriterService(ReportWriterService accrualTransactionsExceptionReportWriterService) { 539 this.accrualTransactionsExceptionReportWriterService = accrualTransactionsExceptionReportWriterService; 540 } 541 542 /** 543 * Gets the accrualTransactionsTotalReportWriterService. 544 * 545 * @return accrualTransactionsTotalReportWriterService 546 */ 547 public ReportWriterService getAccrualTransactionsTotalReportWriterService() { 548 return accrualTransactionsTotalReportWriterService; 549 } 550 551 /** 552 * Sets the accrualTransactionsTotalReportWriterService. 553 * 554 * @param accrualTransactionsTotalReportWriterService 555 */ 556 public void setAccrualTransactionsTotalReportWriterService(ReportWriterService accrualTransactionsTotalReportWriterService) { 557 this.accrualTransactionsTotalReportWriterService = accrualTransactionsTotalReportWriterService; 558 } 559 }