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 java.math.BigDecimal; 019 import java.math.BigInteger; 020 import java.util.ArrayList; 021 import java.util.Collection; 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.EndowPropertyConstants; 028 import org.kuali.kfs.module.endow.batch.service.EndowmenteDocPostingService; 029 import org.kuali.kfs.module.endow.businessobject.ClassCode; 030 import org.kuali.kfs.module.endow.businessobject.EndowmentTransactionLine; 031 import org.kuali.kfs.module.endow.businessobject.EndowmentTransactionSecurity; 032 import org.kuali.kfs.module.endow.businessobject.EndowmentTransactionTaxLotLine; 033 import org.kuali.kfs.module.endow.businessobject.HoldingTaxLot; 034 import org.kuali.kfs.module.endow.businessobject.HoldingTaxLotRebalance; 035 import org.kuali.kfs.module.endow.businessobject.KemidCurrentCash; 036 import org.kuali.kfs.module.endow.businessobject.PendingTransactionDocumentEntry; 037 import org.kuali.kfs.module.endow.businessobject.Security; 038 import org.kuali.kfs.module.endow.businessobject.TransactionArchive; 039 import org.kuali.kfs.module.endow.businessobject.TransactionArchiveSecurity; 040 import org.kuali.kfs.module.endow.businessobject.TransactioneDocPostingDocumentExceptionReportLine; 041 import org.kuali.kfs.module.endow.businessobject.TransactioneDocPostingDocumentTotalReportLine; 042 import org.kuali.kfs.module.endow.document.CorpusAdjustmentDocument; 043 import org.kuali.kfs.module.endow.document.EndowmentTransactionLinesDocumentBase; 044 import org.kuali.kfs.module.endow.document.EndowmentTransactionalDocumentBase; 045 import org.kuali.kfs.module.endow.document.service.KEMService; 046 import org.kuali.kfs.module.endow.document.service.PendingTransactionDocumentService; 047 import org.kuali.kfs.module.endow.util.KEMCalculationRoundingHelper; 048 import org.kuali.kfs.sys.service.ReportWriterService; 049 import org.kuali.rice.kns.service.BusinessObjectService; 050 import org.kuali.rice.kns.service.DataDictionaryService; 051 import org.kuali.rice.kns.util.KualiDecimal; 052 import org.kuali.rice.kns.util.KualiInteger; 053 import org.springframework.transaction.annotation.Transactional; 054 055 @Transactional 056 public class EndowmenteDocPostingServiceImpl implements EndowmenteDocPostingService { 057 058 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(EndowmenteDocPostingServiceImpl.class); 059 060 private ReportWriterService eDocPostingExceptionReportWriterService; 061 private ReportWriterService eDocPostingProcessedReportWriterService; 062 private PendingTransactionDocumentService pendingTransactionDocumentService; 063 private BusinessObjectService businessObjectService; 064 private DataDictionaryService dataDictionaryService; 065 private KEMService kemService; 066 067 /** 068 * @see org.kuali.kfs.module.endow.batch.service.EDocProcessPostingService#processDocumentPosting() 069 */ 070 public boolean processDocumentPosting() { 071 LOG.info("Begin processing and posting eDocs..."); 072 writeHeaders(); 073 074 boolean success = true; 075 076 // Data structure used to keep track of stats. 077 Map<String, List<TransactioneDocPostingDocumentTotalReportLine>> postingStats = new HashMap<String, List<TransactioneDocPostingDocumentTotalReportLine>>(); 078 079 // First get all the pending entries. These entries already represent 080 // transaction documents that are awaiting to be processed. 081 Collection<PendingTransactionDocumentEntry> pendingEntries = pendingTransactionDocumentService.getPendingDocuments(); 082 083 // Next, get all the corresponding transaction documents for the 084 // pending entries. 085 for (PendingTransactionDocumentEntry pendingEntry : pendingEntries) { 086 087 // Determine the class type from it's document type name, and use it to retrieve the 088 // correct object type from the DB. 089 Class<?> clazz = dataDictionaryService.getDocumentClassByTypeName(pendingEntry.getDocumentType()); 090 EndowmentTransactionalDocumentBase tranDoc = (EndowmentTransactionalDocumentBase) businessObjectService.findBySinglePrimaryKey(clazz, pendingEntry.getDocumentNumber()); 091 092 // Only want to process EndowmentTransactionLinesDocumentBase parent types. 093 if (tranDoc instanceof EndowmentTransactionLinesDocumentBase) { 094 processTransactionLines((EndowmentTransactionLinesDocumentBase) tranDoc, pendingEntry, pendingEntry.getDocumentType(), postingStats); 095 096 // After the TRAN_DOC_T:TRAN_PSTD is set to 'Y', the pending entry should be 097 // removed from the DB. 098 businessObjectService.delete(pendingEntry); 099 } 100 } 101 writeStatistics(postingStats); 102 LOG.info("Processed and processed all eDocs successfully."); 103 return success; 104 } 105 106 /** 107 * This method is the main entry point for the batch process. 108 * 109 * @param lineDocuments 110 */ 111 private void processTransactionLines(EndowmentTransactionLinesDocumentBase lineDoc, PendingTransactionDocumentEntry pendingEntry, String documentType, Map<String, List<TransactioneDocPostingDocumentTotalReportLine>> postingStats) { 112 List<EndowmentTransactionLine> tranLines = new ArrayList<EndowmentTransactionLine>(); 113 tranLines.addAll(lineDoc.getSourceTransactionLines()); 114 tranLines.addAll(lineDoc.getTargetTransactionLines()); 115 116 for (EndowmentTransactionLine tranLine : tranLines) { 117 118 if (!tranLine.isLinePosted()) { 119 // Step 2. 120 processTransactionArchives(lineDoc, tranLine, pendingEntry, documentType); 121 // Step 3. 122 processCashSubTypes(lineDoc, tranLine, documentType); 123 // Step 4. 124 processSecurityRecords(lineDoc, tranLine, documentType); 125 // Step 5. 126 tranLine.setLinePosted(true); 127 businessObjectService.save(tranLine); 128 } 129 } 130 131 // Step 6. 132 lineDoc.setTransactionPosted(true); 133 businessObjectService.save(lineDoc); 134 writeProcessedEntry(lineDoc, documentType, tranLines, postingStats); 135 } 136 137 /** 138 * This method processes/posts the transactions lines to the END_SEC_T table. See specification for more details. 139 * 140 * @param tranDoc 141 * @param tranLine 142 */ 143 private void processSecurityRecords(EndowmentTransactionalDocumentBase tranDoc, EndowmentTransactionLine tranLine, String documentType) { 144 LOG.info("Entering \"processSecurityRecords\""); 145 EndowmentTransactionSecurity tranSecurity = findSecurityTransactionRecord(tranLine, documentType); 146 if (tranSecurity != null) { 147 Security security = findSecurityRecord(tranSecurity.getSecurityID()); 148 if (security != null) { 149 HoldingLotValues holdingLotValues = calculateLotValues(tranLine); 150 151 // Per specification, if the document type is EHA, the units 152 // held will not be modified (no units added). 153 if (documentType.equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_HOLDING_ADJUSTMENT)) { 154 security.setUnitsHeld(security.getUnitsHeld() == null ? BigDecimal.ZERO : security.getUnitsHeld()); 155 } 156 else { 157 security.setUnitsHeld((security.getUnitsHeld() == null ? holdingLotValues.getLotUnits() : security.getUnitsHeld().add(holdingLotValues.getLotUnits()))); 158 } 159 160 security.setCarryValue((security.getCarryValue() == null ? holdingLotValues.getLotHoldingCost() : security.getCarryValue().add(holdingLotValues.getLotHoldingCost()))); 161 security.setLastTransactionDate(kemService.getCurrentDate()); 162 163 businessObjectService.save(security); 164 } 165 } 166 LOG.info("Exit \"processSecurityRecords\""); 167 } 168 169 /** 170 * This method processes/posts the transaction lines with sub types of cash. See specification for more details. 171 * 172 * @param tranDoc 173 * @param tranLine 174 */ 175 private void processCashSubTypes(EndowmentTransactionalDocumentBase tranDoc, EndowmentTransactionLine tranLine, String documentType) { 176 LOG.info("Entering \"processCashSubTypes\""); 177 if (tranDoc.getTransactionSubTypeCode().equals(EndowConstants.TransactionSubTypeCode.CASH)) { 178 String kemid = tranLine.getKemid(); 179 String piCode = tranLine.getIncomePrincipalIndicator().getCode(); 180 181 // Get the KemidCurrentCash object from the DB table. 182 KemidCurrentCash kemidCurrentCash = (KemidCurrentCash) businessObjectService.findBySinglePrimaryKey(KemidCurrentCash.class, kemid); 183 // TODO: this is a quick fix. Norm will upload the updated spec soon (Date: Oct 25, 2010 by Bonnie) 184 if (kemidCurrentCash != null) { 185 kemidCurrentCash = checkAndCalculateKemidCurrentCash(kemidCurrentCash, tranLine, documentType, piCode); 186 187 businessObjectService.save(kemidCurrentCash); 188 } 189 } 190 LOG.info("Exit \"processCashSubTypes\""); 191 } 192 193 // this method was part of processCashSubTypes, but was separated for unit test. 194 protected KemidCurrentCash checkAndCalculateKemidCurrentCash(KemidCurrentCash kemidCurrentCash, EndowmentTransactionLine tranLine, String documentType, String piCode){ 195 if (piCode.equals(EndowConstants.IncomePrincipalIndicator.INCOME)) { 196 if (documentType.equals("EAI") || documentType.equals("ELD") || documentType.equals("ECDD")) { 197 kemidCurrentCash.setCurrentIncomeCash(kemidCurrentCash.getCurrentIncomeCash().add(tranLine.getTransactionAmount().negated())); 198 } 199 else if (tranLine.getTransactionLineTypeCode().equals(EndowConstants.TRANSACTION_LINE_TYPE_SOURCE) && (documentType.equals("ECT") || documentType.equals("EGLT"))) { 200 kemidCurrentCash.setCurrentIncomeCash(kemidCurrentCash.getCurrentIncomeCash().add(tranLine.getTransactionAmount().negated())); 201 } 202 else { 203 kemidCurrentCash.setCurrentIncomeCash(kemidCurrentCash.getCurrentIncomeCash().add(tranLine.getTransactionAmount())); 204 } 205 } 206 else { 207 // Deal with Principal 208 if (documentType.equals("EAI") || documentType.equals("ELD") || documentType.equals("ECDD")) { 209 kemidCurrentCash.setCurrentPrincipalCash(kemidCurrentCash.getCurrentPrincipalCash().add(tranLine.getTransactionAmount().negated())); 210 } 211 else if (tranLine.getTransactionLineTypeCode().equals(EndowConstants.TRANSACTION_LINE_TYPE_SOURCE) && (documentType.equals("ECT") || documentType.equals("EGLT"))) { 212 kemidCurrentCash.setCurrentPrincipalCash(kemidCurrentCash.getCurrentPrincipalCash().add(tranLine.getTransactionAmount().negated())); 213 } 214 else { 215 kemidCurrentCash.setCurrentPrincipalCash(kemidCurrentCash.getCurrentPrincipalCash().add(tranLine.getTransactionAmount())); 216 } 217 } 218 219 return kemidCurrentCash; 220 } 221 222 /** 223 * This method processes/posts all the transaction lines to the archive tables. See specification for more details. 224 * 225 * @param lineDoc 226 * @param tranLine 227 */ 228 private void processTransactionArchives(EndowmentTransactionLinesDocumentBase lineDoc, EndowmentTransactionLine tranLine, PendingTransactionDocumentEntry pendingEntry, String documentType) { 229 LOG.info("Entering \"processTransactionArchives\""); 230 // END_TRAN_ARCHV_T 231 TransactionArchive tranArchive = createTranArchive(tranLine, pendingEntry.getDocumentType(), lineDoc.getTransactionSubTypeCode()); 232 233 // this methods were in createTranArchive method, but moved to here for unit test. 234 tranArchive.setSubTypeCode(lineDoc.getTransactionSubTypeCode()); 235 tranArchive.setSrcTypeCode(lineDoc.getTransactionSourceType().getCode()); 236 tranArchive.setDescription(lineDoc.getDocumentHeader().getDocumentDescription()); 237 238 businessObjectService.save(tranArchive); 239 240 // END_TRAN_SEC_T 241 EndowmentTransactionSecurity tranSecurity = findSecurityTransactionRecord(tranLine, documentType); 242 if (tranSecurity != null) { 243 TransactionArchiveSecurity tranArchiveSecurity = createTranArchiveSecurity(tranSecurity, tranLine, documentType); 244 businessObjectService.save(tranArchiveSecurity); 245 246 // END_HLDG_TAX_LOT_T 247 updateOrCreateHoldingTaxLots(tranLine, tranSecurity, documentType); 248 } 249 LOG.info("Exiting \"processTransactionArchives\""); 250 } 251 252 /** 253 * This method creates/updates tax lot lines. 254 * 255 * @param tranLine 256 * @param tranSecurity 257 */ 258 private void updateOrCreateHoldingTaxLots(EndowmentTransactionLine tranLine, EndowmentTransactionSecurity tranSecurity, String documentType) { 259 if (!tranLine.getTaxLotLines().isEmpty()) { 260 261 // Get the primary key values. 262 String kemid = tranLine.getKemid(); 263 String securityId = tranSecurity.getSecurityID(); 264 String regCode = tranSecurity.getRegistrationCode(); 265 String piCode = tranLine.getIncomePrincipalIndicator().getCode(); 266 267 List<EndowmentTransactionTaxLotLine> taxLotLines = tranLine.getTaxLotLines(); 268 269 // Determine the next available tax lot number based on the current highest tax lot number. This 270 // list is already sorted in ASC per OJB mapping XML file, so I need to grab the last element and 271 // increment its value by 1 to get the next available tax lot number. 272 for (EndowmentTransactionTaxLotLine taxLotLine : tranLine.getTaxLotLines()) { 273 274 Integer holdingLotNumber = taxLotLine.getTransactionHoldingLotNumber(); 275 276 // Try and locate an already existing holding tax lot BO. If one already exists, 277 // then it will simply be modified, otherwise a new one will be created. 278 HoldingTaxLot holdingTaxLot = findHoldingTaxLotRecord(kemid, securityId, regCode, piCode, holdingLotNumber); 279 280 // Get new lot indicator. 281 boolean isNewLot = taxLotLine.isNewLotIndicator(); 282 283 // If we find an existing one, then modify it. 284 if (holdingTaxLot != null) { 285 BigDecimal newUnits = holdingTaxLot.getUnits().add(taxLotLine.getLotUnits()); 286 BigDecimal newCost = holdingTaxLot.getCost().add(taxLotLine.getLotHoldingCost()); 287 288 // For EAD, units and holding costs cannot be less than zero. 289 if (documentType.equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_DECREASE) && (newUnits.compareTo(BigDecimal.ZERO) < 0 || newCost.compareTo(BigDecimal.ZERO) < 0)) { 290 continue; 291 } 292 293 // For ELI, units cannot be less than zero and holding cost cannot be greater than 0. 294 if (documentType.equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_LIABILITY_INCREASE) && (newUnits.compareTo(BigDecimal.ZERO) < 0 || newCost.compareTo(BigDecimal.ZERO) > 0)) { 295 continue; 296 } 297 298 // Per specification, if the document type is EHA, the units 299 // held will not be modified (no units added). 300 if (!documentType.equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_HOLDING_ADJUSTMENT)) { 301 holdingTaxLot.setUnits(newUnits); 302 } 303 304 holdingTaxLot.setCost(newCost); 305 } 306 // One doesn't exists, so create a new one. 307 else if (documentType.equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_INCREASE) || documentType.equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_LIABILITY_INCREASE) || documentType.equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_SECURITY_TRANSFER) || documentType.equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_CORPORATE_REORGANZATION)) { 308 309 holdingTaxLot = createHoldingTaxLot(kemid, securityId, regCode, piCode, taxLotLine.getLotUnits(), taxLotLine.getLotHoldingCost()); 310 holdingTaxLot.setAcquiredDate(kemService.getCurrentDate()); 311 312 holdingTaxLot.setLotNumber(new KualiInteger(taxLotLine.getTransactionHoldingLotNumber())); 313 } 314 // One doesn't exist, and it doesn't fall under the document type criteria. 315 else { 316 // Skip over it. 317 continue; 318 } 319 320 holdingTaxLot.setLastTransactionDate(kemService.getCurrentDate()); 321 322 // Create a new HoldingTaxLotRebalance entry. 323 // 324 // NOTE: The HoldingTaxLotRebalance entry MUST exist prior 325 // to saving the HoldingTaxLot entry. 326 businessObjectService.save(updateOrCreateHoldingTaxLotRebalance(holdingTaxLot, isNewLot)); 327 328 // Save the modified/new holding tax lot entry. 329 businessObjectService.save(holdingTaxLot); 330 } 331 } 332 LOG.info("Entering \"processTransactionArchives\""); 333 } 334 335 /** 336 * This method creates and returns a new HoldingTaxLotRebalance BO from the specified HoldingTaxLot BO. 337 * 338 * @param holdingTaxLot 339 */ 340 private HoldingTaxLotRebalance updateOrCreateHoldingTaxLotRebalance(HoldingTaxLot holdingTaxLot, boolean isNewLot) { 341 HoldingTaxLotRebalance holdingTaxLotRebalance = null; 342 if (holdingTaxLot != null) { 343 344 holdingTaxLotRebalance = findHoldingTaxLotRebalanceRecord(holdingTaxLot); 345 346 // There is no entry in the DB, so create a new one. 347 if (holdingTaxLotRebalance == null) { 348 holdingTaxLotRebalance = createHoldingTaxLotRebalance(holdingTaxLot); 349 } 350 // An entry was found in the DB. Now, we need to adjust for the difference between the 351 // the original entry and this modified entry. 352 else { 353 List<HoldingTaxLot> holdingTaxLots = holdingTaxLotRebalance.getHoldingTaxLots(); 354 355 // If the holding tax lot is already in the re-balance table, then you need to 356 // adjust it. If the holding tax lot is new, then simply update the re-balance 357 // table to include the additional units and cost the come from the new lot. 358 if (holdingTaxLots.contains(holdingTaxLot)) { 359 for (HoldingTaxLot hldgTaxLot : holdingTaxLots) { 360 if (holdingTaxLot.equals(hldgTaxLot)) { 361 362 // Calculate the difference between the two. 363 BigDecimal units = holdingTaxLot.getUnits().subtract(hldgTaxLot.getUnits()); 364 BigDecimal cost = holdingTaxLot.getCost().subtract(hldgTaxLot.getCost()); 365 366 // Now, adjust the total units and cost by the above calculated difference. 367 holdingTaxLotRebalance.setTotalUnits(units.add(holdingTaxLotRebalance.getTotalUnits())); 368 holdingTaxLotRebalance.setTotalCost(cost.add(holdingTaxLotRebalance.getTotalCost())); 369 370 break; 371 } 372 } 373 } 374 else { 375 // Increase the total lot number by one and add the additional units and cost. 376 KualiInteger totalLotNumber = holdingTaxLotRebalance.getTotalLotNumber(); 377 BigDecimal units = holdingTaxLotRebalance.getTotalUnits(); 378 BigDecimal cost = holdingTaxLotRebalance.getTotalCost(); 379 380 totalLotNumber = totalLotNumber.add(new KualiInteger(1)); 381 units = units.add(holdingTaxLot.getUnits()); 382 cost = cost.add(holdingTaxLot.getCost()); 383 384 holdingTaxLotRebalance.setTotalLotNumber(totalLotNumber); 385 holdingTaxLotRebalance.setTotalUnits(units); 386 holdingTaxLotRebalance.setTotalCost(cost); 387 } 388 } 389 } 390 391 return holdingTaxLotRebalance; 392 } 393 394 /** 395 * This method creates a new HoldingTaxLotRebalance BO and initializes it with the specified HoldingTaxLot fields. 396 * 397 * @param holdingTaxLot 398 * @return HoldingTaxLotRebalance 399 */ 400 private HoldingTaxLotRebalance createHoldingTaxLotRebalance(HoldingTaxLot holdingTaxLot) { 401 HoldingTaxLotRebalance holdingTaxLotRebalance = new HoldingTaxLotRebalance(); 402 403 holdingTaxLotRebalance.setKemid(holdingTaxLot.getKemid()); 404 holdingTaxLotRebalance.setSecurityId(holdingTaxLot.getSecurityId()); 405 holdingTaxLotRebalance.setRegistrationCode(holdingTaxLot.getRegistrationCode()); 406 holdingTaxLotRebalance.setIncomePrincipalIndicator(holdingTaxLot.getIncomePrincipalIndicator()); 407 holdingTaxLotRebalance.setTotalLotNumber(new KualiInteger(1)); 408 holdingTaxLotRebalance.setTotalUnits(holdingTaxLot.getUnits()); 409 holdingTaxLotRebalance.setTotalCost(holdingTaxLot.getCost()); 410 411 return holdingTaxLotRebalance; 412 } 413 414 /** 415 * This method creates and initializes a new HoldingTaxLot BO with the specified values. 416 * 417 * @param kemid 418 * @param securityId 419 * @param regCode 420 * @param piCode 421 * @param units 422 * @param cost 423 * @return HoldingTaxLot 424 */ 425 private HoldingTaxLot createHoldingTaxLot(String kemid, String securityId, String regCode, String piCode, BigDecimal units, BigDecimal cost) { 426 HoldingTaxLot holdingTaxLot = new HoldingTaxLot(); 427 428 holdingTaxLot.setKemid(kemid); 429 holdingTaxLot.setSecurityId(securityId); 430 holdingTaxLot.setRegistrationCode(regCode); 431 holdingTaxLot.setIncomePrincipalIndicator(piCode); 432 holdingTaxLot.setUnits(units); 433 holdingTaxLot.setCost(cost); 434 435 return holdingTaxLot; 436 } 437 438 /** 439 * This method goes to the DB and tries to locate a holding tax lot re-balance (END_HLDG_TAX_LOT_REBAL_T). 440 * 441 * @param holdingTaxLot 442 * @return HoldingTaxLotRebalance 443 */ 444 private HoldingTaxLotRebalance findHoldingTaxLotRebalanceRecord(HoldingTaxLot holdingTaxLot) { 445 Map<String, Object> primaryKeys = new HashMap<String, Object>(); 446 primaryKeys.put(EndowPropertyConstants.HOLDING_TAX_LOT_REBAL_KEMID, holdingTaxLot.getKemid()); 447 primaryKeys.put(EndowPropertyConstants.HOLDING_TAX_LOT_REBAL_SECURITY_ID, holdingTaxLot.getSecurityId()); 448 primaryKeys.put(EndowPropertyConstants.HOLDING_TAX_LOT_REBAL_REGISTRATION_CODE, holdingTaxLot.getRegistrationCode()); 449 primaryKeys.put(EndowPropertyConstants.HOLDING_TAX_LOT_REBAL_INCOME_PRINCIPAL_INDICATOR, holdingTaxLot.getIncomePrincipalIndicator()); 450 451 HoldingTaxLotRebalance holdingTaxLotRebalance = (HoldingTaxLotRebalance) businessObjectService.findByPrimaryKey(HoldingTaxLotRebalance.class, primaryKeys); 452 453 return holdingTaxLotRebalance; 454 } 455 456 /** 457 * This method goes to the DB and tries to locate a holding tax lot (END_HLDG_TAX_LOT_T). 458 * 459 * @param kemid 460 * @param securityId 461 * @param regCode 462 * @param piCode 463 * @param holdingLotNumber 464 * @return HoldingTaxLot 465 */ 466 private HoldingTaxLot findHoldingTaxLotRecord(String kemid, String securityId, String regCode, String piCode, Integer holdingLotNumber) { 467 Map<String, Object> primaryKeys = new HashMap<String, Object>(); 468 primaryKeys.put(EndowPropertyConstants.HOLDING_TAX_LOT_KEMID, kemid); 469 primaryKeys.put(EndowPropertyConstants.HOLDING_TAX_LOT_SECURITY_ID, securityId); 470 primaryKeys.put(EndowPropertyConstants.HOLDING_TAX_LOT_REGISTRATION_CODE, regCode); 471 primaryKeys.put(EndowPropertyConstants.HOLDING_TAX_LOT_INCOME_PRINCIPAL_INDICATOR, piCode); 472 primaryKeys.put(EndowPropertyConstants.HOLDING_TAX_LOT_NUMBER, holdingLotNumber); 473 474 HoldingTaxLot holdingTaxLot = (HoldingTaxLot) businessObjectService.findByPrimaryKey(HoldingTaxLot.class, primaryKeys); 475 476 return holdingTaxLot; 477 } 478 479 /** 480 * This method locates a Security Transaction (END_TRAN_SEC_T) record from the DB 481 * by using the document number. 482 * @see KFSMI-6423 483 * @param securityDoc 484 * @param tranLine 485 * @return EndowmentTransactionSecurity 486 */ 487 protected EndowmentTransactionSecurity findSecurityTransactionRecord(EndowmentTransactionLine tranLine, String documentType) { 488 Map<String, String> primaryKeys = new HashMap<String, String>(); 489 primaryKeys.put(EndowPropertyConstants.DOCUMENT_NUMBER, tranLine.getDocumentNumber()); 490 if (EndowConstants.DocumentTypeNames.ENDOWMENT_SECURITY_TRANSFER.equalsIgnoreCase(documentType)) { 491 primaryKeys.put(EndowPropertyConstants.TRANSACTION_SECURITY_LINE_TYPE_CODE, EndowConstants.TRANSACTION_SECURITY_TYPE_SOURCE); 492 } 493 else { 494 primaryKeys.put(EndowPropertyConstants.TRANSACTION_SECURITY_LINE_TYPE_CODE, tranLine.getTransactionLineTypeCode()); 495 } 496 497 EndowmentTransactionSecurity tranSecurity = (EndowmentTransactionSecurity) businessObjectService.findByPrimaryKey(EndowmentTransactionSecurity.class, primaryKeys); 498 499 return tranSecurity; 500 } 501 502 /** 503 * This method locates the security (END_SEC_T) record from the DB. 504 * 505 * @param securityId 506 * @return Security 507 */ 508 private Security findSecurityRecord(String securityId) { 509 return (Security) businessObjectService.findBySinglePrimaryKey(Security.class, securityId); 510 } 511 512 /** 513 * This method will create a TransactionArchiveSecurity object from the transaction security and line object. 514 * 515 * @param tranSecurity 516 * @param tranLine 517 * @return TransactionArchiveSecurity 518 */ 519 protected TransactionArchiveSecurity createTranArchiveSecurity(EndowmentTransactionSecurity tranSecurity, EndowmentTransactionLine tranLine, String documentType) { 520 TransactionArchiveSecurity tranArchiveSecurity = new TransactionArchiveSecurity(); 521 tranArchiveSecurity.setDocumentNumber(tranLine.getDocumentNumber()); 522 tranArchiveSecurity.setLineNumber(tranLine.getTransactionLineNumber()); 523 tranArchiveSecurity.setLineTypeCode(tranLine.getTransactionLineTypeCode()); 524 tranArchiveSecurity.setSecurityId(tranSecurity.getSecurityID()); 525 tranArchiveSecurity.setRegistrationCode(tranSecurity.getRegistrationCode()); 526 527 // Determine the ETRAN code. 528 ClassCode classCode = (ClassCode) businessObjectService.findBySinglePrimaryKey(ClassCode.class, tranSecurity.getSecurity().getClassCode().getCode()); 529 530 tranArchiveSecurity.setEtranCode(classCode.getEndowmentTransactionCode().getCode()); 531 532 // Calculate all the lot values. 533 HoldingLotValues holdingLotValues = calculateLotValues(tranLine); 534 535 // Per specification, if the document type is EHA, then the units and 536 // value should be zero. 537 if (documentType.equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_HOLDING_ADJUSTMENT)) { 538 tranArchiveSecurity.setUnitsHeld(BigDecimal.ZERO); 539 tranArchiveSecurity.setUnitValue(BigDecimal.ZERO); 540 } 541 else { 542 tranArchiveSecurity.setUnitsHeld(holdingLotValues.getLotUnits()); 543 tranArchiveSecurity.setUnitValue(holdingLotValues.getLotUnitValue()); 544 } 545 546 tranArchiveSecurity.setHoldingCost(holdingLotValues.getLotHoldingCost()); 547 tranArchiveSecurity.setLongTermGainLoss(holdingLotValues.getLotLongTermGainLoss()); 548 tranArchiveSecurity.setShortTermGainLoss(holdingLotValues.getLotShortTermGainLoss()); 549 550 return tranArchiveSecurity; 551 } 552 553 /** 554 * This method creates a wrapper object to house holding lot values. 555 * 556 * @param tranLine 557 * @return HoldingLotValues 558 */ 559 private HoldingLotValues calculateLotValues(EndowmentTransactionLine tranLine) { 560 HoldingLotValues holdingLotValues = new HoldingLotValues(tranLine); 561 562 return holdingLotValues; 563 } 564 565 /** 566 * This method creates a new Transaction Archive BO from the transaction line, and line document. 567 * 568 * @param lineDoc 569 * @param tranLine 570 * @return Transaction Archive 571 */ 572 protected TransactionArchive createTranArchive(EndowmentTransactionLine tranLine, String documentType, String subTypeCode) { 573 TransactionArchive tranArchive = new TransactionArchive(); 574 tranArchive.setTypeCode(documentType); 575 576 tranArchive.setDocumentNumber(tranLine.getDocumentNumber()); 577 tranArchive.setLineNumber(tranLine.getTransactionLineNumber()); 578 tranArchive.setLineTypeCode(tranLine.getTransactionLineTypeCode()); 579 tranArchive.setKemid(tranLine.getKemid()); 580 tranArchive.setEtranCode(tranLine.getEtranCode()); 581 tranArchive.setIncomePrincipalIndicatorCode(tranLine.getIncomePrincipalIndicator().getCode()); 582 583 BigDecimal transacationAmount = tranLine.getTransactionAmount().bigDecimalValue(); 584 585 // If the transaction document type is Endowment Corpus Adjustment, set 586 // the TRAN_INC_AMT, and TRAN_PRIN_AMT to zero. 587 if (documentType.equals(dataDictionaryService.getDocumentTypeNameByClass(CorpusAdjustmentDocument.class))) { 588 tranArchive.setPrincipalCashAmount(new BigDecimal(BigInteger.ZERO, 2)); 589 tranArchive.setIncomeCashAmount(new BigDecimal(BigInteger.ZERO, 2)); 590 } 591 // The document type wasn't Corpus Adjust--if subtypecode is CASH then process.... 592 else if (subTypeCode.equalsIgnoreCase(EndowConstants.TransactionSubTypeCode.CASH)) { 593 calculateTransactionArchiveAmount(tranArchive, transacationAmount); 594 } 595 596 tranArchive.setLineDescription(tranLine.getTransactionLineDescription()); 597 tranArchive.setCorpusIndicator(tranLine.getCorpusIndicator()); 598 599 if (tranArchive.getCorpusIndicator()) { 600 tranArchive.setCorpusAmount(transacationAmount); 601 } 602 603 tranArchive.setPostedDate(kemService.getCurrentDate()); 604 605 // If the line type code is F(Decrease), then all the transaction amounts need to be 606 // negative. 607 if (tranArchive.getCorpusIndicator() && tranLine.getTransactionLineTypeCode().equalsIgnoreCase(EndowConstants.TRANSACTION_LINE_TYPE_SOURCE)) { 608 tranArchive.setCorpusAmount(tranArchive.getCorpusAmount().negate()); 609 } 610 611 return tranArchive; 612 } 613 614 /** 615 * Helper method to figure out if transaction archive income/principal amount to be negated 616 * If document type is ECI, EAD, ELI, GLET, ECT AND line type code = T then copy 617 * transaction amount value to either income or principal based on ip indicator. 618 * If document type is EAI, ELD, or ECDD then negate the amount OR 619 * if document type is ECT or EGLT AND line type code = F then negate the amounts and 620 * copy the value to income or principal based on ip indicator. 621 * 622 * @param tranArchive, transacationAmount 623 */ 624 protected void calculateTransactionArchiveAmount(TransactionArchive tranArchive, BigDecimal transactionAmount) { 625 if (tranArchive.getTypeCode().equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_DECREASE) || 626 (tranArchive.getTypeCode().equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER) || 627 tranArchive.getTypeCode().equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_INCREASE) || 628 tranArchive.getTypeCode().equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_LIABILITY_INCREASE) || 629 tranArchive.getTypeCode().equalsIgnoreCase(EndowConstants.DocumentTypeNames.GENERAL_LEDGER_TO_ENDOWMENT_TRANSFER)) && 630 tranArchive.getLineTypeCode().equalsIgnoreCase(EndowConstants.TRANSACTION_LINE_TYPE_TARGET)) { 631 //now set the amount to either income or principal fields.... 632 if (tranArchive.getIncomePrincipalIndicatorCode().equalsIgnoreCase(EndowConstants.IncomePrincipalIndicator.INCOME)) { 633 tranArchive.setIncomeCashAmount(transactionAmount); 634 } 635 else { 636 tranArchive.setPrincipalCashAmount(transactionAmount); 637 } 638 } 639 else { 640 if ((tranArchive.getTypeCode().equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_INCREASE) || 641 tranArchive.getTypeCode().equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_LIABILITY_DECREASE) || 642 tranArchive.getTypeCode().equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_DECREASE)) || 643 ((tranArchive.getTypeCode().equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER) || 644 tranArchive.getTypeCode().equalsIgnoreCase(EndowConstants.DocumentTypeNames.GENERAL_LEDGER_TO_ENDOWMENT_TRANSFER) || 645 tranArchive.getTypeCode().equalsIgnoreCase(EndowConstants.DocumentTypeNames.ENDOWMENT_TO_GENERAL_LEDGER_TRANSFER)) && 646 tranArchive.getLineTypeCode().equalsIgnoreCase(EndowConstants.TRANSACTION_LINE_TYPE_SOURCE))) { 647 //now set the amount to either income or principal fields.... 648 if (tranArchive.getIncomePrincipalIndicatorCode().equalsIgnoreCase(EndowConstants.IncomePrincipalIndicator.INCOME)) { 649 tranArchive.setIncomeCashAmount(transactionAmount.negate()); 650 } 651 else { 652 tranArchive.setPrincipalCashAmount(transactionAmount.negate()); 653 } 654 } 655 } 656 } 657 658 /** 659 * Initialize the report document headers. 660 */ 661 private void writeHeaders() { 662 eDocPostingExceptionReportWriterService.writeNewLines(1); 663 eDocPostingProcessedReportWriterService.writeNewLines(1); 664 eDocPostingExceptionReportWriterService.writeTableHeader(new TransactioneDocPostingDocumentExceptionReportLine()); 665 eDocPostingProcessedReportWriterService.writeTableHeader(new TransactioneDocPostingDocumentTotalReportLine()); 666 } 667 668 /** 669 * Writes a line in the totals processed report document. 670 * 671 * @param lineDoc 672 * @param documentType 673 * @param tranLines 674 */ 675 private void writeProcessedEntry(EndowmentTransactionLinesDocumentBase lineDoc, String documentType, List<EndowmentTransactionLine> tranLines, Map<String, List<TransactioneDocPostingDocumentTotalReportLine>> postingStats) { 676 677 TransactioneDocPostingDocumentTotalReportLine eDocTotalReportLine = new TransactioneDocPostingDocumentTotalReportLine(); 678 679 eDocTotalReportLine.setDocumentName(lineDoc.getDocumentNumber()); 680 eDocTotalReportLine.setDocumentNumber(lineDoc.getDocumentNumber()); 681 eDocTotalReportLine.setTotalIncomeCash(lineDoc.getTargetPrincipalTotal().add(lineDoc.getSourcePrincipalTotal())); 682 eDocTotalReportLine.setTotalPrincipleCash(lineDoc.getTargetIncomeTotal().add(lineDoc.getSourceIncomeTotal())); 683 eDocTotalReportLine.setTotalUnits(lineDoc.getTotalUnits()); 684 eDocTotalReportLine.setTotalHoldingCost(lineDoc.getTotalDollarAmount()); 685 686 eDocPostingProcessedReportWriterService.writeTableRow(eDocTotalReportLine); 687 eDocPostingProcessedReportWriterService.writeNewLines(1); 688 updatePostingStats(postingStats, eDocTotalReportLine); 689 } 690 691 /** 692 * Writes a line in the exception report document. 693 * 694 * @param lineDoc 695 * @param tranLine 696 * @param reason 697 */ 698 private void writeExceptionReportEntry(EndowmentTransactionLinesDocumentBase lineDoc, EndowmentTransactionLine tranLine, String reason) { 699 TransactioneDocPostingDocumentExceptionReportLine eDocExceptionReportLine = new TransactioneDocPostingDocumentExceptionReportLine(); 700 701 eDocExceptionReportLine.setDocumentName(dataDictionaryService.getDocumentTypeNameByClass(lineDoc.getClass())); 702 eDocExceptionReportLine.setDocumentNumber(tranLine.getDocumentNumber()); 703 eDocExceptionReportLine.setLineType(tranLine.getTransactionLineTypeCode()); 704 eDocExceptionReportLine.setLineNumber(tranLine.getKemid()); 705 eDocExceptionReportLine.setReason(reason); 706 707 eDocPostingExceptionReportWriterService.writeTableRow(eDocExceptionReportLine); 708 eDocPostingExceptionReportWriterService.writeNewLines(1); 709 } 710 711 /** 712 * Print the statistics. 713 * 714 * @param postingStats 715 */ 716 private void writeStatistics(Map<String, List<TransactioneDocPostingDocumentTotalReportLine>> postingStats) { 717 718 KualiDecimal grandIncomeCash = KualiDecimal.ZERO; 719 KualiDecimal grandPrincipleCash = KualiDecimal.ZERO; 720 KualiDecimal grandUnits = KualiDecimal.ZERO; 721 KualiDecimal grandCost = KualiDecimal.ZERO; 722 723 for (Map.Entry<String, List<TransactioneDocPostingDocumentTotalReportLine>> entry : postingStats.entrySet()) { 724 725 KualiDecimal incomeCash = KualiDecimal.ZERO; 726 KualiDecimal principleCash = KualiDecimal.ZERO; 727 KualiDecimal units = KualiDecimal.ZERO; 728 KualiDecimal cost = KualiDecimal.ZERO; 729 730 for (TransactioneDocPostingDocumentTotalReportLine reportLine : entry.getValue()) { 731 incomeCash = incomeCash.add(reportLine.getTotalIncomeCash()); 732 principleCash = principleCash.add(reportLine.getTotalPrincipleCash()); 733 units = units.add(reportLine.getTotalUnits()); 734 cost = cost.add(reportLine.getTotalHoldingCost()); 735 736 grandIncomeCash = grandIncomeCash.add(incomeCash); 737 grandPrincipleCash = grandPrincipleCash.add(principleCash); 738 grandUnits = grandUnits.add(units); 739 grandCost = grandCost.add(cost); 740 } 741 742 // Write the per document type statistics line. 743 eDocPostingProcessedReportWriterService.writeStatisticLine("Sub Total By Doc Name: %s", entry.getKey()); 744 eDocPostingProcessedReportWriterService.writeStatisticLine(" Total Income: %s", incomeCash.bigDecimalValue().toPlainString()); 745 eDocPostingProcessedReportWriterService.writeStatisticLine(" Total Principle: %s", principleCash.bigDecimalValue().toPlainString()); 746 eDocPostingProcessedReportWriterService.writeStatisticLine(" Total Units: %s", units.bigDecimalValue().toPlainString()); 747 eDocPostingProcessedReportWriterService.writeStatisticLine(" Total Holding Cost: %s", cost.bigDecimalValue().toPlainString()); 748 eDocPostingProcessedReportWriterService.writeStatisticLine("", ""); 749 } 750 751 // Write grand total statistics. 752 eDocPostingProcessedReportWriterService.writeStatisticLine("Grand Total Income Cash: %s", grandIncomeCash.bigDecimalValue().toPlainString()); 753 eDocPostingProcessedReportWriterService.writeStatisticLine("Grand Total Principle Cash: %s", grandPrincipleCash.bigDecimalValue().toPlainString()); 754 eDocPostingProcessedReportWriterService.writeStatisticLine("Grand Total Units: %s", grandUnits.bigDecimalValue().toPlainString()); 755 eDocPostingProcessedReportWriterService.writeStatisticLine("Grand Total Holding Cost: %s", grandCost.bigDecimalValue().toPlainString()); 756 } 757 758 /** 759 * Adds processed report line to the statistics table. 760 * 761 * @param postingStats 762 * @param eDocTotalReportLine 763 */ 764 private void updatePostingStats(Map<String, List<TransactioneDocPostingDocumentTotalReportLine>> postingStats, TransactioneDocPostingDocumentTotalReportLine eDocTotalReportLine) { 765 766 // Get the document name. 767 String documentName = eDocTotalReportLine.getDocumentName(); 768 769 // Get the table value by key (document name). If the value is null, create it. 770 List<TransactioneDocPostingDocumentTotalReportLine> reportLine = postingStats.get(documentName); 771 if (reportLine == null) { 772 reportLine = new ArrayList<TransactioneDocPostingDocumentTotalReportLine>(); 773 774 // Add it back to the hash map. 775 postingStats.put(documentName, reportLine); 776 } 777 778 // Update the table value. 779 reportLine.add(eDocTotalReportLine); 780 } 781 782 /** 783 * Sets the businessObjectService attribute value. 784 * 785 * @param businessObjectService The businessObjectService to set. 786 */ 787 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 788 this.businessObjectService = businessObjectService; 789 } 790 791 /** 792 * Sets the kemService attribute value. 793 * 794 * @param kemService The kemService to set. 795 */ 796 public void setKemService(KEMService kemService) { 797 this.kemService = kemService; 798 } 799 800 /** 801 * Sets the pendingTransactionDocumentService attribute value. 802 * 803 * @param pendingTransactionDocumentService The pendingTransactionDocumentService to set. 804 */ 805 public void setPendingTransactionDocumentService(PendingTransactionDocumentService pendingTransactionDocumentService) { 806 this.pendingTransactionDocumentService = pendingTransactionDocumentService; 807 } 808 809 /** 810 * Sets the dataDictionaryService attribute value. 811 * 812 * @param dataDictionaryService The dataDictionaryService to set. 813 */ 814 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 815 this.dataDictionaryService = dataDictionaryService; 816 } 817 818 /** 819 * Sets the eDocPostingExceptionReportWriterService attribute value. 820 * 821 * @param eDocPostingExceptionReportWriterService The eDocPostingExceptionReportWriterService to set. 822 */ 823 public void seteDocPostingExceptionReportWriterService(ReportWriterService eDocPostingExceptionReportWriterService) { 824 this.eDocPostingExceptionReportWriterService = eDocPostingExceptionReportWriterService; 825 } 826 827 /** 828 * Sets the eDocPostingProcessedReportWriterService attribute value. 829 * 830 * @param eDocPostingProcessedReportWriterService The eDocPostingProcessedReportWriterService to set. 831 */ 832 public void seteDocPostingProcessedReportWriterService(ReportWriterService eDocPostingProcessedReportWriterService) { 833 this.eDocPostingProcessedReportWriterService = eDocPostingProcessedReportWriterService; 834 } 835 836 /** 837 * Simple wrapper class used to store all the calculated lot values. 838 */ 839 private class HoldingLotValues { 840 841 private BigDecimal lotUnits; 842 private BigDecimal lotUnitValue; 843 private BigDecimal lotHoldingCost; 844 private BigDecimal lotLongTermGainLoss; 845 private BigDecimal lotShortTermGainLoss; 846 847 HoldingLotValues(EndowmentTransactionLine tranLine) { 848 lotUnits = new BigDecimal(BigInteger.ZERO, 5); 849 lotHoldingCost = new BigDecimal(BigInteger.ZERO, 2); 850 lotLongTermGainLoss = new BigDecimal(BigInteger.ZERO, 2); 851 lotShortTermGainLoss = new BigDecimal(BigInteger.ZERO, 2); 852 853 for (EndowmentTransactionTaxLotLine taxLotLine : tranLine.getTaxLotLines()) { 854 855 lotUnits = lotUnits.add(taxLotLine.getLotUnits() == null ? new BigDecimal(BigInteger.ZERO, 4) : taxLotLine.getLotUnits()); 856 lotHoldingCost = lotHoldingCost.add(taxLotLine.getLotHoldingCost() == null ? new BigDecimal(BigInteger.ZERO, 2) : taxLotLine.getLotHoldingCost()); 857 lotLongTermGainLoss = lotLongTermGainLoss.add(taxLotLine.getLotLongTermGainLoss() == null ? new BigDecimal(BigInteger.ZERO, 2) : taxLotLine.getLotLongTermGainLoss()); 858 lotShortTermGainLoss = lotShortTermGainLoss.add(taxLotLine.getLotShortTermGainLoss() == null ? new BigDecimal(BigInteger.ZERO, 2) : taxLotLine.getLotShortTermGainLoss()); 859 } 860 KualiDecimal transactionUnits = tranLine.getTransactionUnits(); 861 if (transactionUnits != null && !transactionUnits.isZero()) { 862 lotUnitValue = KEMCalculationRoundingHelper.divide(tranLine.getTransactionAmount().bigDecimalValue(), transactionUnits.bigDecimalValue(), EndowConstants.Scale.SECURITY_UNIT_VALUE); 863 } 864 } 865 866 /** 867 * Gets the lotUnits attribute. 868 * 869 * @return Returns the lotUnits. 870 */ 871 public BigDecimal getLotUnits() { 872 return lotUnits; 873 } 874 875 /** 876 * Gets the lotUnitValue attribute. 877 * 878 * @return Returns the lotUnitValue. 879 */ 880 public BigDecimal getLotUnitValue() { 881 return lotUnitValue; 882 } 883 884 /** 885 * Gets the lotHoldingCost attribute. 886 * 887 * @return Returns the lotHoldingCost. 888 */ 889 public BigDecimal getLotHoldingCost() { 890 return lotHoldingCost; 891 } 892 893 /** 894 * Gets the lotLongTermGainLoss attribute. 895 * 896 * @return Returns the lotLongTermGainLoss. 897 */ 898 public BigDecimal getLotLongTermGainLoss() { 899 return lotLongTermGainLoss; 900 } 901 902 /** 903 * Gets the lotShortTermGainLoss attribute. 904 * 905 * @return Returns the lotShortTermGainLoss. 906 */ 907 public BigDecimal getLotShortTermGainLoss() { 908 return lotShortTermGainLoss; 909 } 910 } 911 912 }