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    }