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 }