001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.endow.batch.service.impl;
017    
018    import static org.kuali.kfs.module.endow.EndowConstants.NEW_SOURCE_TRAN_LINE_PROPERTY_NAME;
019    import static org.kuali.kfs.module.endow.EndowConstants.NEW_TARGET_TRAN_LINE_PROPERTY_NAME;
020    
021    import java.math.BigDecimal;
022    import java.math.BigInteger;
023    import java.math.RoundingMode;
024    import java.util.ArrayList;
025    import java.util.Collection;
026    import java.util.HashMap;
027    import java.util.List;
028    import java.util.Map;
029    
030    import org.kuali.kfs.module.endow.EndowConstants;
031    import org.kuali.kfs.module.endow.EndowParameterKeyConstants;
032    import org.kuali.kfs.module.endow.EndowPropertyConstants;
033    import org.kuali.kfs.module.endow.batch.CreateAutomatedCashInvestmentTransactionsStep;
034    import org.kuali.kfs.module.endow.batch.reporter.ReportDocumentStatistics;
035    import org.kuali.kfs.module.endow.batch.service.CreateAutomatedCashInvestmentTransactionsService;
036    import org.kuali.kfs.module.endow.businessobject.AutomatedCashInvestmentModel;
037    import org.kuali.kfs.module.endow.businessobject.EndowmentExceptionReportHeader;
038    import org.kuali.kfs.module.endow.businessobject.EndowmentSourceTransactionLine;
039    import org.kuali.kfs.module.endow.businessobject.EndowmentSourceTransactionSecurity;
040    import org.kuali.kfs.module.endow.businessobject.EndowmentTargetTransactionLine;
041    import org.kuali.kfs.module.endow.businessobject.EndowmentTargetTransactionSecurity;
042    import org.kuali.kfs.module.endow.businessobject.EndowmentTransactionLine;
043    import org.kuali.kfs.module.endow.businessobject.HoldingTaxLot;
044    import org.kuali.kfs.module.endow.businessobject.KEMID;
045    import org.kuali.kfs.module.endow.businessobject.KemidCurrentCash;
046    import org.kuali.kfs.module.endow.businessobject.Security;
047    import org.kuali.kfs.module.endow.businessobject.TransactionDocumentExceptionReportLine;
048    import org.kuali.kfs.module.endow.businessobject.TransactionDocumentTotalReportLine;
049    import org.kuali.kfs.module.endow.dataaccess.AutomatedCashInvestmentModelDao;
050    import org.kuali.kfs.module.endow.document.AssetDecreaseDocument;
051    import org.kuali.kfs.module.endow.document.AssetIncreaseDocument;
052    import org.kuali.kfs.module.endow.document.EndowmentTaxLotLinesDocumentBase;
053    import org.kuali.kfs.module.endow.document.service.KEMIDService;
054    import org.kuali.kfs.module.endow.document.service.KEMService;
055    import org.kuali.kfs.module.endow.document.service.SecurityService;
056    import org.kuali.kfs.module.endow.document.service.UpdateAssetDecreaseDocumentTaxLotsService;
057    import org.kuali.kfs.module.endow.document.service.UpdateAssetIncreaseDocumentTaxLotsService;
058    import org.kuali.kfs.module.endow.document.validation.event.AddTransactionLineEvent;
059    import org.kuali.kfs.module.endow.util.GloabalVariablesExtractHelper;
060    import org.kuali.kfs.sys.service.ReportWriterService;
061    import org.kuali.rice.kew.exception.WorkflowException;
062    import org.kuali.rice.kns.bo.DocumentHeader;
063    import org.kuali.rice.kns.rule.event.RouteDocumentEvent;
064    import org.kuali.rice.kns.service.BusinessObjectService;
065    import org.kuali.rice.kns.service.DataDictionaryService;
066    import org.kuali.rice.kns.service.DocumentService;
067    import org.kuali.rice.kns.service.KualiConfigurationService;
068    import org.kuali.rice.kns.service.KualiRuleService;
069    import org.kuali.rice.kns.service.ParameterService;
070    import org.kuali.rice.kns.util.KualiDecimal;
071    import org.springframework.transaction.annotation.Transactional;
072    
073    @Transactional
074    public class CreateAutomatedCashInvestmentTransactionsServiceImpl implements CreateAutomatedCashInvestmentTransactionsService {
075    
076        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CreateAutomatedCashInvestmentTransactionsServiceImpl.class);
077        private static final String SUBMIT_DOCUMENT_DESCRIPTION = "Created by Create Automated Cash Investment Transactions Batch Process.";
078    
079        private Map<String, ReportDocumentStatistics> statistics = new HashMap<String, ReportDocumentStatistics>();
080    
081        private ReportWriterService createAutomatedCashInvestmentExceptionReportWriterService;
082        private ReportWriterService createAutomatedCashInvestmentProcessedReportWriterService;
083        private UpdateAssetIncreaseDocumentTaxLotsService updateEaiTaxLotService;
084        private UpdateAssetDecreaseDocumentTaxLotsService updateEadTaxLotService;
085        private AutomatedCashInvestmentModelDao automatedCashInvestmentModelDao;
086        private BusinessObjectService businessObjectService;
087        private DataDictionaryService dataDictionaryService;
088        private KualiConfigurationService configService;
089        private KualiRuleService kualiRuleService;
090        private ParameterService parameterService;
091        private DocumentService documentService;
092        private SecurityService securityService;
093        private KEMIDService kemidService;
094        private KEMService kemService;
095    
096        /**
097         * @see org.kuali.kfs.module.endow.batch.service.CreateAutomatedCashInvestmentTransactionsService#createACITransactions()
098         */
099        public boolean createAciTransactions() {
100    
101            LOG.info("Starting \"Create Automated Cash Investments Transactions\" batch job...");
102            writeHeaders();
103    
104            for (AutomatedCashInvestmentModel aciModel : getAutomatedCashInvestmentModelMatchingCurrentDate()) {
105    
106                List<KEMID> principleKemids = new ArrayList<KEMID>(kemidService.getByPrincipleAciId(aciModel.getAciModelID()));
107                List<KEMID> incomeKemids = new ArrayList<KEMID>(kemidService.getByIncomeAciId(aciModel.getAciModelID()));
108    
109                // Process for increase documents.
110                processAssetIncreaseDocs(principleKemids, aciModel, false);
111                processAssetIncreaseDocs(incomeKemids, aciModel, true);
112    
113                // Process for decrease documents.
114                processAssetDecreaseDocs(principleKemids, aciModel, false);
115                processAssetDecreaseDocs(incomeKemids, aciModel, true);
116            }
117    
118            writeStatistics();
119            LOG.info("Finished \"Create Automated Cash Investments Transactions\" batch job!");
120    
121            return true;
122        }
123    
124        /**
125         * Process all the asset increase documents for income and principle types.
126         * 
127         * @param kemid
128         * @param aciModel
129         */
130        protected void processAssetIncreaseDocs(List<KEMID> kemids, AutomatedCashInvestmentModel aciModel, boolean isIncome) {
131    
132            // An asset increase document for each of the pooled investments.
133            AssetIncreaseDocument assetIncreaseDoc1 = null;
134            AssetIncreaseDocument assetIncreaseDoc2 = null;
135            AssetIncreaseDocument assetIncreaseDoc3 = null;
136            AssetIncreaseDocument assetIncreaseDoc4 = null;
137    
138            // Get the percentages. We'll only process investments with
139            // percentages greater than zero.
140            BigDecimal inv1Percent = aciModel.getInvestment1Percent();
141            BigDecimal inv2Percent = aciModel.getInvestment2Percent();
142            BigDecimal inv3Percent = aciModel.getInvestment3Percent();
143            BigDecimal inv4Percent = aciModel.getInvestment4Percent();
144    
145            for (int i = 0; i < kemids.size(); i++) {
146    
147                // Get the KEMID at the current index.
148                KEMID kemid = kemids.get(i);
149    
150                // Get the principle/income cash equivalent.
151                BigDecimal cashEquivalent = getCashEquivalent(kemid, isIncome);
152    
153                // Check if the cash equivalent is greater than zero.
154                if (cashEquivalent.compareTo(BigDecimal.ZERO) > 0) {
155    
156                    assetIncreaseDoc1 = processCashInvestmentForAssetIncrease(aciModel, isIncome, assetIncreaseDoc1, inv1Percent, i, kemid, cashEquivalent, 1);
157                    assetIncreaseDoc2 = processCashInvestmentForAssetIncrease(aciModel, isIncome, assetIncreaseDoc2, inv2Percent, i, kemid, cashEquivalent, 2);
158                    assetIncreaseDoc3 = processCashInvestmentForAssetIncrease(aciModel, isIncome, assetIncreaseDoc3, inv3Percent, i, kemid, cashEquivalent, 3);
159                    assetIncreaseDoc4 = processCashInvestmentForAssetIncrease(aciModel, isIncome, assetIncreaseDoc4, inv4Percent, i, kemid, cashEquivalent, 4);
160                }
161            }
162    
163            // Route documents that may still need routing.
164            performCleanUpForAssetIncrease(isIncome, assetIncreaseDoc1);
165            performCleanUpForAssetIncrease(isIncome, assetIncreaseDoc2);
166            performCleanUpForAssetIncrease(isIncome, assetIncreaseDoc3);
167            performCleanUpForAssetIncrease(isIncome, assetIncreaseDoc4);
168        }
169    
170        /**
171         * Process all the asset decrease documents for income and principle types.
172         * 
173         * @param kemids
174         * @param aciModel
175         * @param isIncome
176         */
177        protected void processAssetDecreaseDocs(List<KEMID> kemids, AutomatedCashInvestmentModel aciModel, boolean isIncome) {
178    
179            // An asset increase document for each of the pooled investments.
180            AssetDecreaseDocument assetDecreaseDoc1 = null;
181            AssetDecreaseDocument assetDecreaseDoc2 = null;
182            AssetDecreaseDocument assetDecreaseDoc3 = null;
183            AssetDecreaseDocument assetDecreaseDoc4 = null;
184    
185            // Get the percentages. We'll only process investments with
186            // percentages greater than zero.
187            BigDecimal inv1Percent = aciModel.getInvestment1Percent();
188            BigDecimal inv2Percent = aciModel.getInvestment2Percent();
189            BigDecimal inv3Percent = aciModel.getInvestment3Percent();
190            BigDecimal inv4Percent = aciModel.getInvestment4Percent();
191    
192            for (int i = 0; i < kemids.size(); i++) {
193    
194                // Get the KEMID at the current index.
195                KEMID kemid = kemids.get(i);
196    
197                // Get the principle/income cash equivalent.
198                BigDecimal cashEquivalent = getCashEquivalent(kemid, isIncome);
199    
200                // Check if the cash equivalent is less than zero.
201                if (cashEquivalent.compareTo(BigDecimal.ZERO) < 0) {
202    
203                    assetDecreaseDoc1 = processCashInvestmentForAssetDecrease(aciModel, isIncome, assetDecreaseDoc1, inv1Percent, i, kemid, cashEquivalent, 1);
204                    assetDecreaseDoc2 = processCashInvestmentForAssetDecrease(aciModel, isIncome, assetDecreaseDoc2, inv2Percent, i, kemid, cashEquivalent, 2);
205                    assetDecreaseDoc3 = processCashInvestmentForAssetDecrease(aciModel, isIncome, assetDecreaseDoc3, inv3Percent, i, kemid, cashEquivalent, 3);
206                    assetDecreaseDoc4 = processCashInvestmentForAssetDecrease(aciModel, isIncome, assetDecreaseDoc4, inv4Percent, i, kemid, cashEquivalent, 4);
207                }
208            }
209    
210            // Route documents that may still need routing.
211            performCleanUpForAssetDecrease(isIncome, assetDecreaseDoc1);
212            performCleanUpForAssetDecrease(isIncome, assetDecreaseDoc2);
213            performCleanUpForAssetDecrease(isIncome, assetDecreaseDoc3);
214            performCleanUpForAssetDecrease(isIncome, assetDecreaseDoc4);
215        }
216    
217        /**
218         * Verify that we don't need to do any clean-up. There could still be some let over transaction lines, less than the max amount
219         * that need to still be processed on the current eDoc.
220         * 
221         * @param isIncome
222         * @param assetIncreaseDoc
223         */
224        protected boolean performCleanUpForAssetDecrease(boolean isIncome, AssetDecreaseDocument assetDecreaseDoc) {
225            
226            boolean success = false;
227            
228            if (assetDecreaseDoc != null && !assetDecreaseDoc.getSourceTransactionLines().isEmpty()) {
229                // Validate and route the document.
230                success = routeAssetDecreaseDocument(assetDecreaseDoc, isIncome);
231            }
232            
233            return success;
234        }
235    
236        /**
237         * Verify that we don't need to do any clean-up. There could still be some let over transaction lines, less than the max amount
238         * that need to still be processed on the current eDoc.
239         * 
240         * @param isIncome
241         * @param assetIncreaseDoc
242         */
243        protected boolean performCleanUpForAssetIncrease(boolean isIncome, AssetIncreaseDocument assetIncreaseDoc) {
244            
245            boolean success = false;
246            
247            if (assetIncreaseDoc != null && !assetIncreaseDoc.getTargetTransactionLines().isEmpty()) {
248                // Validate and route the document.
249                success = routeAssetIncreaseDocument(assetIncreaseDoc, isIncome);
250            }
251            
252            return success;
253        }
254    
255        /**
256         * Helper method for processing the cash investments for asset decrease documents.
257         * 
258         * @param aciModel
259         * @param isIncome
260         * @param assetDecreaseDoc
261         * @param invPercent
262         * @param i
263         * @param kemid
264         * @param cashEquivalent
265         * @param inv
266         * @return
267         */
268        private AssetDecreaseDocument processCashInvestmentForAssetDecrease(AutomatedCashInvestmentModel aciModel, boolean isIncome, AssetDecreaseDocument assetDecreaseDoc, BigDecimal invPercent, int i, KEMID kemid, BigDecimal cashEquivalent, int inv) {
269    
270            if (invPercent != null && invPercent.compareTo(BigDecimal.ZERO) != 0) {
271                String invRegistrationCode = getInvRegistrationCode(aciModel, inv);
272                String invSecurityId = getInvSecurityId(aciModel, inv);
273    
274                // Initialize the asset decrease doc if null.
275                if (assetDecreaseDoc == null) {
276                    assetDecreaseDoc = createAssetDecrease(invSecurityId, invRegistrationCode);
277                }
278    
279                // Create, validate, and add the transaction line to the eDoc.
280                addTransactionLineForAssetDecrease(assetDecreaseDoc, aciModel, kemid.getKemid(), cashEquivalent, inv, isIncome);
281    
282                // Check to see if we've reached our max number of transaction lines
283                // per eDoc. If so, validate, and submit the current eDoc and start
284                // another eDoc.
285                if ((i != 0) && (i % getMaxNumberOfTransactionLines() == 0)) {
286                    // Validate and route the document.
287                    routeAssetDecreaseDocument(assetDecreaseDoc, isIncome);
288                    assetDecreaseDoc = createAssetDecrease(invSecurityId, invRegistrationCode);
289                }
290            }
291    
292            return assetDecreaseDoc;
293        }
294    
295        /**
296         * Helper method for processing the cash investments for asset increase documents.
297         * 
298         * @param aciModel
299         * @param isIncome
300         * @param assetIncreaseDoc
301         * @param invPercent
302         * @param i
303         * @param kemid
304         * @param cashEquivalent
305         * @param inv
306         * @return
307         */
308        private AssetIncreaseDocument processCashInvestmentForAssetIncrease(AutomatedCashInvestmentModel aciModel, boolean isIncome, AssetIncreaseDocument assetIncreaseDoc, BigDecimal invPercent, int i, KEMID kemid, BigDecimal cashEquivalent, int inv) {
309    
310            if (invPercent != null && invPercent.compareTo(BigDecimal.ZERO) != 0) {
311                String invRegistrationCode = getInvRegistrationCode(aciModel, inv);
312                String invSecurityId = getInvSecurityId(aciModel, inv);
313    
314                // Initialize the asset increase doc if null.
315                if (assetIncreaseDoc == null) {
316                    assetIncreaseDoc = createAssetIncrease(invSecurityId, invRegistrationCode);
317                }
318    
319                // Create, validate, and add the transaction line to the eDoc.
320                addTransactionLineForAssetIncrease(assetIncreaseDoc, aciModel, kemid.getKemid(), cashEquivalent, inv, isIncome);
321    
322                // Check to see if we've reached our max number of transaction lines
323                // per eDoc. If so, validate, and submit the current eDoc and start
324                // another eDoc.
325                if ((i != 0) && (i % getMaxNumberOfTransactionLines() == 0)) {
326                    // Validate and route the document.
327                    routeAssetIncreaseDocument(assetIncreaseDoc, isIncome);
328                    assetIncreaseDoc = createAssetIncrease(invSecurityId, invRegistrationCode);
329                }
330            }
331    
332            return assetIncreaseDoc;
333        }
334    
335        /**
336         * Validates the asset decrease document and routes it.
337         * 
338         * @param assetDecreaseDoc
339         * @param isIncome
340         * @return
341         */
342        protected boolean routeAssetDecreaseDocument(AssetDecreaseDocument assetDecreaseDoc, boolean isIncome) {
343    
344            boolean success = false;
345            
346            // Perform validation on the document.
347            boolean rulesPassed = kualiRuleService.applyRules(new RouteDocumentEvent(assetDecreaseDoc));
348    
349            // If the document passed validations, route it accordingly.
350            if (rulesPassed) {
351                boolean noRouteIndicator = getNoRouteIndicator(true);
352                try {
353                    assetDecreaseDoc.setNoRouteIndicator(noRouteIndicator);
354                    documentService.routeDocument(assetDecreaseDoc, SUBMIT_DOCUMENT_DESCRIPTION, null);
355                    writeProcessedTableRowAssetDecrease(assetDecreaseDoc, isIncome);
356                    success = true;
357                }
358                catch (WorkflowException ex) {
359                    writeExceptionTableReason(assetDecreaseDoc.getDocumentNumber() + " - " + ex.getLocalizedMessage());
360                    LOG.error(ex.getLocalizedMessage());
361                }
362            }
363            else {
364                // Write the errors to the exception file.
365                List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
366                for (String errorMessage : errorMessages) {
367                    writeExceptionTableReason(errorMessage);
368                }
369                // Try to save the document if it fails validation.
370                try {
371                    documentService.saveDocument(assetDecreaseDoc);
372                }
373                catch (WorkflowException we) {
374                    writeExceptionTableReason(assetDecreaseDoc.getDocumentNumber() + " - " + we.getLocalizedMessage());
375                    LOG.error(we.getLocalizedMessage());
376                }
377            }
378            
379            return success;
380        }
381    
382        /**
383         * Validates the asset increase document and routes it.
384         * 
385         * @param assetIncreaseDoc
386         * @param isIncome
387         * @return
388         */
389        protected boolean routeAssetIncreaseDocument(AssetIncreaseDocument assetIncreaseDoc, boolean isIncome) {
390    
391            boolean success = false;
392            
393            // Perform validation on the document.
394            boolean rulesPassed = kualiRuleService.applyRules(new RouteDocumentEvent(assetIncreaseDoc));
395    
396            // If the document passed validations, route it accordingly.
397            if (rulesPassed) {
398                boolean noRouteIndicator = getNoRouteIndicator(false);
399                try {
400                    assetIncreaseDoc.setNoRouteIndicator(noRouteIndicator);
401                    documentService.routeDocument(assetIncreaseDoc, SUBMIT_DOCUMENT_DESCRIPTION, null);
402                    writeProcessedTableRowAssetIncrease(assetIncreaseDoc, isIncome);
403                    success = true;
404                }
405                catch (WorkflowException we) {
406                    writeExceptionTableReason(assetIncreaseDoc.getDocumentNumber() + " - " + we.getLocalizedMessage());
407                    LOG.error(we.getLocalizedMessage());
408                }
409            }
410            else {
411                // Write the errors to the exception file.
412                List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
413                for (String errorMessage : errorMessages) {
414                    writeExceptionTableReason(errorMessage);
415                }
416                // Try to save the document if it fails validation.
417                try {
418                    documentService.saveDocument(assetIncreaseDoc);
419                }
420                catch (WorkflowException we) {
421                    writeExceptionTableReason(assetIncreaseDoc.getDocumentNumber() + " - " + we.getLocalizedMessage());
422                    LOG.error(we.getLocalizedMessage());
423                }
424            }
425            
426            return success;
427        }
428    
429        /**
430         * Creates a new transaction line, validates, and adds it to the asset decrease document.
431         * 
432         * @param assetDecreaseDoc
433         * @param aciModel
434         * @param kemid
435         * @param cashEquivalent
436         * @param inv
437         * @param isIncome
438         */
439        private void addTransactionLineForAssetDecrease(AssetDecreaseDocument assetDecreaseDoc, AutomatedCashInvestmentModel aciModel, String kemid, BigDecimal cashEquivalent, int inv, boolean isIncome) {
440            UnitAmountAssociation unitAmount = calculateUnitAmount(aciModel, cashEquivalent, inv);
441    
442            // Create the correct transaction line based on if it's a source or target type.
443            EndowmentTransactionLine transactionLine = createTransactionLine(assetDecreaseDoc.getDocumentNumber(), kemid, aciModel.getIpIndicator(), unitAmount.getAmount(), unitAmount.getUnits(), true);
444    
445            // Validate the transaction line.
446            boolean rulesPassed = kualiRuleService.applyRules(new AddTransactionLineEvent(NEW_SOURCE_TRAN_LINE_PROPERTY_NAME, assetDecreaseDoc, transactionLine));
447    
448            // If the transaction line passes validation, add it to the document.
449            if (rulesPassed) {
450                assetDecreaseDoc.addSourceTransactionLine((EndowmentSourceTransactionLine) transactionLine);
451                updateEadTaxLotService.updateTransactionLineTaxLots(assetDecreaseDoc, transactionLine);
452            }
453            else {
454                writeExceptionTableRowAssetDecrease(assetDecreaseDoc, transactionLine, isIncome);
455                List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
456                for (String errorMessage : errorMessages) {
457                    writeExceptionTableReason(errorMessage);
458                }
459            }
460        }
461    
462        /**
463         * Creates a new transaction line, validates, and adds it to the asset increase document.
464         * 
465         * @param assetIncreaseDoc
466         * @param aciModel
467         * @param cashEquivalent
468         */
469        private void addTransactionLineForAssetIncrease(AssetIncreaseDocument assetIncreaseDoc, AutomatedCashInvestmentModel aciModel, String kemid, BigDecimal cashEquivalent, int inv, boolean isIncome) {
470            UnitAmountAssociation unitAmount = calculateUnitAmount(aciModel, cashEquivalent, inv);
471    
472            // Create the correct transaction line based on if it's a source or target type.
473            EndowmentTransactionLine transactionLine = createTransactionLine(assetIncreaseDoc.getDocumentNumber(), kemid, aciModel.getIpIndicator(), unitAmount.getAmount(), unitAmount.getUnits(), false);
474    
475            // Validate the transaction line.
476            boolean rulesPassed = kualiRuleService.applyRules(new AddTransactionLineEvent(NEW_TARGET_TRAN_LINE_PROPERTY_NAME, assetIncreaseDoc, transactionLine));
477    
478            // If the transaction line passes validation, add it to the document.
479            if (rulesPassed) {
480                assetIncreaseDoc.addTargetTransactionLine((EndowmentTargetTransactionLine) transactionLine);
481                updateEaiTaxLotService.updateTransactionLineTaxLots(assetIncreaseDoc, transactionLine);
482            }
483            else {
484                writeExceptionTableRowAssetIncrease(assetIncreaseDoc, transactionLine, isIncome);
485                List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
486                for (String errorMessage : errorMessages) {
487                    writeExceptionTableReason(errorMessage);
488                }
489            }
490        }
491    
492        /**
493         * Creates a new transaction line.
494         * 
495         * @param kemid
496         * @param etranCode
497         * @param amount
498         * @param units
499         * @param isSource
500         * @return
501         */
502        private EndowmentTransactionLine createTransactionLine(String docNumber, String kemid, String ipIndicator, BigDecimal amount, BigDecimal units, boolean isSource) {
503    
504            EndowmentTransactionLine transactionLine = null;
505            if (isSource) {
506                transactionLine = new EndowmentSourceTransactionLine();
507            }
508            else {
509                transactionLine = new EndowmentTargetTransactionLine();
510            }
511    
512            // Set values on the transaction line.
513            transactionLine.setDocumentNumber(docNumber);
514            transactionLine.setKemid(kemid);
515            transactionLine.setTransactionIPIndicatorCode(ipIndicator);
516            transactionLine.setTransactionAmount(new KualiDecimal(amount));
517            transactionLine.setTransactionUnits(new KualiDecimal(units));
518    
519            return transactionLine;
520        }
521    
522        /**
523         * Get the registration code for the specified investment.
524         * 
525         * @param aciModel
526         * @param inv
527         * @return
528         */
529        private String getInvRegistrationCode(AutomatedCashInvestmentModel aciModel, int inv) {
530    
531            String invRegistrationCode = "";
532            switch (inv) {
533                case 1:
534                    invRegistrationCode = aciModel.getInvestment1().getFundRegistrationCode();
535                    break;
536                case 2:
537                    invRegistrationCode = aciModel.getInvestment2().getFundRegistrationCode();
538                    break;
539                case 3:
540                    invRegistrationCode = aciModel.getInvestment3().getFundRegistrationCode();
541                    break;
542                case 4:
543                    invRegistrationCode = aciModel.getInvestment4().getFundRegistrationCode();
544                    break;
545            }
546    
547            return invRegistrationCode;
548        }
549    
550        /**
551         * Get the security id from the specified investment.
552         * 
553         * @param aciModel
554         * @param inv
555         * @return
556         */
557        private String getInvSecurityId(AutomatedCashInvestmentModel aciModel, int inv) {
558    
559            String invSecurityId = "";
560            switch (inv) {
561                case 1:
562                    invSecurityId = aciModel.getInvestment1SecurityID();
563                    break;
564                case 2:
565                    invSecurityId = aciModel.getInvestment2SecurityID();
566                    break;
567                case 3:
568                    invSecurityId = aciModel.getInvestment3SecurityID();
569                    break;
570                case 4:
571                    invSecurityId = aciModel.getInvestment4SecurityID();
572                    break;
573            }
574    
575            return invSecurityId;
576        }
577    
578        /**
579         * Gets the correct sale offset code for the specified investment.
580         * 
581         * @param aciModel
582         * @param inv
583         * @return
584         */
585        private String getSaleOffsetCode(AutomatedCashInvestmentModel aciModel, int inv) {
586            String offsetCode = "";
587            switch (inv) {
588                case 1:
589                    offsetCode = aciModel.getInvestment1().getFundAssetSaleOffsetTranCode();
590                    break;
591                case 2:
592                    offsetCode = aciModel.getInvestment2().getFundAssetSaleOffsetTranCode();
593                    break;
594                case 3:
595                    offsetCode = aciModel.getInvestment3().getFundAssetSaleOffsetTranCode();
596                    break;
597                case 4:
598                    offsetCode = aciModel.getInvestment4().getFundAssetSaleOffsetTranCode();
599                    break;
600            }
601    
602            return offsetCode;
603        }
604    
605        /**
606         * Calculates the unite amount for the specified investment.
607         * 
608         * @param aciModel
609         * @param cashEquivalent
610         * @param inv
611         * @return
612         */
613        private UnitAmountAssociation calculateUnitAmount(AutomatedCashInvestmentModel aciModel, BigDecimal cashEquivalent, int inv) {
614    
615            BigDecimal invPercent = null;
616            BigDecimal invUnitValue = null;
617            BigDecimal invCashNeeded = null;
618            BigDecimal invAmount = null;
619            BigDecimal invUnits = null;
620    
621            boolean allowsFractions = false;
622    
623            switch (inv) {
624                case 1:
625                    invPercent = aciModel.getInvestment1Percent();
626                    invUnitValue = aciModel.getInvestment1().getSecurity().getUnitValue();
627                    allowsFractions = aciModel.getInvestment1().isAllowFractionalShares();
628                    break;
629                case 2:
630                    invPercent = aciModel.getInvestment2Percent();
631                    invUnitValue = aciModel.getInvestment2().getSecurity().getUnitValue();
632                    allowsFractions = aciModel.getInvestment1().isAllowFractionalShares();
633                    break;
634                case 3:
635                    invPercent = aciModel.getInvestment3Percent();
636                    invUnitValue = aciModel.getInvestment3().getSecurity().getUnitValue();
637                    allowsFractions = aciModel.getInvestment1().isAllowFractionalShares();
638                    break;
639                case 4:
640                    invPercent = aciModel.getInvestment4Percent();
641                    invUnitValue = aciModel.getInvestment4().getSecurity().getUnitValue();
642                    allowsFractions = aciModel.getInvestment1().isAllowFractionalShares();
643                    break;
644            }
645    
646            if (invPercent != null) {
647                try {
648                    invCashNeeded = new BigDecimal(invPercent.multiply(cashEquivalent).doubleValue());
649                    invUnits = invCashNeeded.divide(invUnitValue, 5, RoundingMode.HALF_UP);
650    
651                    if (!allowsFractions) {
652                        // Round the units down.
653                        invUnits = invUnits.setScale(0, BigDecimal.ROUND_DOWN);
654                    }
655                    invAmount = (invUnits.multiply(invUnitValue)).setScale(2, BigDecimal.ROUND_HALF_UP);
656                }
657                catch (ArithmeticException ex) {
658                    writeExceptionTableReason("Caught ArithmeticException while calculating units.");
659                    LOG.error("Caught exception while calculating units.", ex);
660                }
661            }
662    
663            return (new UnitAmountAssociation(invAmount, invUnits));
664        }
665    
666        /**
667         * Get the max number of transaction lines allowed per document.
668         * 
669         * @return
670         */
671        private int getMaxNumberOfTransactionLines() {
672            return kemService.getMaxNumberOfTransactionLinesPerDocument();
673        }
674    
675        /**
676         * Gets the appropriate approval indicator based on if it's for a sale or purchase type.
677         * 
678         * @param isSale
679         * @return
680         */
681        private boolean getNoRouteIndicator(boolean isSale) {
682            boolean noRouteIndicator = isSale ? getSaleNoRouteIndicator() : getPurchaseNoRouteIndicator();
683            return noRouteIndicator;
684        }
685    
686        /**
687         * This method returns the true or false value of the purchase no route indicator.
688         * 
689         * @return
690         */
691        private boolean getPurchaseNoRouteIndicator() {
692            String noRouteIndicator = parameterService.getParameterValue(CreateAutomatedCashInvestmentTransactionsStep.class, EndowParameterKeyConstants.PURCHASE_NO_ROUTE_IND);
693            return (EndowConstants.YES.equalsIgnoreCase(noRouteIndicator) ? true : false);
694        }
695    
696        /**
697         * This method returns the true or false value of the sale no route indicator.
698         * 
699         * @return
700         */
701        private boolean getSaleNoRouteIndicator() {
702            String noRouteIndicator = parameterService.getParameterValue(CreateAutomatedCashInvestmentTransactionsStep.class, EndowParameterKeyConstants.SALE_NO_ROUTE_IND);
703            return (EndowConstants.YES.equalsIgnoreCase(noRouteIndicator) ? true : false);
704        }
705    
706        /**
707         * Creates and initializes an asset decrease document type.
708         * 
709         * @param securityId
710         * @param registrationCode
711         * @return
712         */
713        private AssetDecreaseDocument createAssetDecrease(String securityId, String registrationCode) {
714    
715            AssetDecreaseDocument assetDecrease = null;
716            try {
717                assetDecrease = (AssetDecreaseDocument) documentService.getNewDocument(AssetDecreaseDocument.class);
718    
719                // Set the document description.
720                DocumentHeader docHeader = assetDecrease.getDocumentHeader();
721                docHeader.setDocumentDescription(getPurchaseDescription());
722                assetDecrease.setDocumentHeader(docHeader);
723    
724                // Set the sub type code to cash.
725                assetDecrease.setTransactionSubTypeCode(EndowConstants.TransactionSubTypeCode.CASH);
726    
727                // Create and set the target security transaction line.
728                EndowmentSourceTransactionSecurity sourceTransactionSecurity = new EndowmentSourceTransactionSecurity();
729                sourceTransactionSecurity.setSecurityLineTypeCode(EndowConstants.TRANSACTION_SECURITY_TYPE_SOURCE);
730                sourceTransactionSecurity.setRegistrationCode(registrationCode);
731                sourceTransactionSecurity.setSecurityID(securityId);
732    
733                assetDecrease.setSourceTransactionSecurity(sourceTransactionSecurity);
734            }
735            catch (WorkflowException ex) {
736                writeExceptionTableReason("Workflow error while trying to create EAI document: " + ex.getLocalizedMessage());
737                LOG.error(ex.getLocalizedMessage());
738            }
739    
740            return assetDecrease;
741        }
742    
743        /**
744         * Creates and initializes an asset increase document type.
745         * 
746         * @param securityId
747         * @param registrationCode
748         * @return
749         */
750        private AssetIncreaseDocument createAssetIncrease(String securityId, String registrationCode) {
751    
752            AssetIncreaseDocument assetIncrease = null;
753            try {
754                assetIncrease = (AssetIncreaseDocument) documentService.getNewDocument(AssetIncreaseDocument.class);
755    
756                // Set the document description.
757                DocumentHeader docHeader = assetIncrease.getDocumentHeader();
758                docHeader.setDocumentDescription(getPurchaseDescription());
759                assetIncrease.setDocumentHeader(docHeader);
760    
761                // Set the sub type code to cash.
762                assetIncrease.setTransactionSubTypeCode(EndowConstants.TransactionSubTypeCode.CASH);
763    
764                // Create and set the target security transaction line.
765                EndowmentTargetTransactionSecurity targetTransactionSecurity = new EndowmentTargetTransactionSecurity();
766                targetTransactionSecurity.setSecurityLineTypeCode(EndowConstants.TRANSACTION_SECURITY_TYPE_TARGET);
767                targetTransactionSecurity.setRegistrationCode(registrationCode);
768                targetTransactionSecurity.setSecurityID(securityId);
769    
770                assetIncrease.setTargetTransactionSecurity(targetTransactionSecurity);
771            }
772            catch (WorkflowException ex) {
773                writeExceptionTableReason("Workflow error while trying to create EAI document: " + ex.getLocalizedMessage());
774                LOG.error(ex.getLocalizedMessage());
775            }
776    
777            return assetIncrease;
778        }
779    
780        /**
781         * Returns the appropriate principle/income cash equivalent.
782         * 
783         * @param kemid
784         * @param isIncome
785         * @return BigDecimal
786         */
787        private BigDecimal getCashEquivalent(KEMID kemid, boolean isIncome) {
788    
789            BigDecimal cashEquivalent;
790    
791            if (!isIncome) {
792                cashEquivalent = calculatePrincipleCashEquivalent(kemid.getKemid());
793            }
794            else {
795                cashEquivalent = calculateIncomeCashEquivalent(kemid.getKemid());
796            }
797    
798            return cashEquivalent;
799        }
800    
801        /**
802         * Calculates the total market value for cash class code.
803         * 
804         * @param kemid
805         * @param ipIndicator
806         * @return
807         */
808        private BigDecimal calculateTotalMarketValue(String kemid, String ipIndicator) {
809    
810            // Used to calculate the total market value.
811            BigDecimal totalMarketValue = new BigDecimal(BigInteger.ZERO);
812    
813            // Build the criteria used for getting the KEMIDs.
814            Map<String, String> map = new HashMap<String, String>();
815            map.put(EndowPropertyConstants.HOLDING_TAX_LOT_KEMID, kemid);
816            map.put(EndowPropertyConstants.HOLDING_TAX_LOT_INCOME_PRINCIPAL_INDICATOR, ipIndicator);
817    
818            // Get all the holding tax lots that match the kemid and ipIndicator. Next, filter out
819            // those that are not cash equivalent, based on their class code type.
820            Collection<HoldingTaxLot> hldgTaxLots = businessObjectService.findMatching(HoldingTaxLot.class, map);
821            for (HoldingTaxLot hldgTaxLot : hldgTaxLots) {
822                Security security = securityService.getByPrimaryKey(hldgTaxLot.getSecurityId());
823                if (security != null) {
824                    if (security.getClassCode().getClassCodeType().equalsIgnoreCase(EndowConstants.ClassCodeTypes.CASH_EQUIVALENTS)) {
825                        totalMarketValue = totalMarketValue.add(hldgTaxLot.getUnits().multiply(security.getUnitValue()));
826                    }
827                }
828            }
829    
830            return totalMarketValue;
831        }
832    
833        /**
834         * Calculates the principle cash equivalents.
835         * 
836         * @param kemid
837         * @return
838         */
839        private BigDecimal calculatePrincipleCashEquivalent(String kemid) {
840            BigDecimal totalMarketValue = calculateTotalMarketValue(kemid, EndowConstants.IncomePrincipalIndicator.PRINCIPAL);
841    
842            return getKemidCurrentPrincipalCash(kemid).add(totalMarketValue);
843        }
844    
845        /**
846         * Calculates the income cash equivalents.
847         * 
848         * @param kemid
849         * @return
850         */
851        private BigDecimal calculateIncomeCashEquivalent(String kemid) {
852            BigDecimal totalMarketValue = calculateTotalMarketValue(kemid, EndowConstants.IncomePrincipalIndicator.INCOME);
853    
854            return getKemidCurrentIncomeCash(kemid).add(totalMarketValue);
855        }
856    
857        /**
858         * This method...
859         * 
860         * @param kemid
861         * @return
862         */
863        private BigDecimal getKemidCurrentPrincipalCash(String kemid) {
864            KemidCurrentCash kemidCurrentCash = businessObjectService.findBySinglePrimaryKey(KemidCurrentCash.class, kemid);
865    
866            if (kemidCurrentCash == null) {
867                writeExceptionTableReason("Recieved \'null\' value for END_CRNT_CSH_T for KEMID " + kemid);
868                return new BigDecimal(BigInteger.ZERO);
869            }
870    
871            return kemidCurrentCash.getCurrentPrincipalCash().bigDecimalValue();
872        }
873    
874        /**
875         * This method...
876         * 
877         * @param kemid
878         * @return
879         */
880        private BigDecimal getKemidCurrentIncomeCash(String kemid) {
881            KemidCurrentCash kemidCurrentCash = businessObjectService.findBySinglePrimaryKey(KemidCurrentCash.class, kemid);
882    
883            if (kemidCurrentCash == null) {
884                writeExceptionTableReason("Recieved \'null\' value for END_CRNT_CSH_T for KEMID " + kemid);
885                return new BigDecimal(BigInteger.ZERO);
886            }
887            return kemidCurrentCash.getCurrentIncomeCash().bigDecimalValue();
888        }
889    
890        /**
891         * This method retrieves all the cash sweep models whose frequency code matches the current date.
892         * 
893         * @return Collection of CashSweepModel business objects
894         */
895        private Collection<AutomatedCashInvestmentModel> getAutomatedCashInvestmentModelMatchingCurrentDate() {
896            return automatedCashInvestmentModelDao.getAutomatedCashInvestmentModelWithNextPayDateEqualToCurrentDate(kemService.getCurrentDate());
897        }
898    
899        /**
900         * This method...
901         * 
902         * @param assetIncreaseDoc
903         * @param isIncome
904         */
905        private void writeProcessedTableRowAssetIncrease(AssetIncreaseDocument assetIncreaseDoc, boolean isIncome) {
906    
907            TransactionDocumentTotalReportLine createAutomatedCashInvestmentProcessedReportValues = new TransactionDocumentTotalReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_INCREASE, assetIncreaseDoc.getDocumentNumber(), assetIncreaseDoc.getTargetTransactionSecurity().getSecurityID());
908    
909            List<EndowmentTransactionLine> transLines = assetIncreaseDoc.getTargetTransactionLines();
910            for (EndowmentTransactionLine tranLine : transLines) {
911                if (isIncome) {
912                    createAutomatedCashInvestmentProcessedReportValues.addIncomeAmount(tranLine.getTransactionAmount());
913                    createAutomatedCashInvestmentProcessedReportValues.addIncomeUnits(tranLine.getTransactionUnits());
914                }
915                else {
916                    createAutomatedCashInvestmentProcessedReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
917                    createAutomatedCashInvestmentProcessedReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
918                }
919            }
920            updatePostingStats(assetIncreaseDoc);
921    
922            createAutomatedCashInvestmentProcessedReportWriterService.writeTableRow(createAutomatedCashInvestmentProcessedReportValues);
923            createAutomatedCashInvestmentProcessedReportWriterService.writeNewLines(1);
924        }
925    
926        /**
927         * This method...
928         * 
929         * @param assetDecreaseDoc
930         * @param isIncome
931         */
932        private void writeProcessedTableRowAssetDecrease(AssetDecreaseDocument assetDecreaseDoc, boolean isIncome) {
933    
934            TransactionDocumentTotalReportLine createAutomatedCashInvestmentProcessedReportValues = new TransactionDocumentTotalReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_DECREASE, assetDecreaseDoc.getDocumentNumber(), assetDecreaseDoc.getTargetTransactionSecurity().getSecurityID());
935    
936            List<EndowmentTransactionLine> transLines = assetDecreaseDoc.getTargetTransactionLines();
937            for (EndowmentTransactionLine tranLine : transLines) {
938                if (isIncome) {
939                    createAutomatedCashInvestmentProcessedReportValues.addIncomeAmount(tranLine.getTransactionAmount());
940                    createAutomatedCashInvestmentProcessedReportValues.addIncomeUnits(tranLine.getTransactionUnits());
941                }
942                else {
943                    createAutomatedCashInvestmentProcessedReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
944                    createAutomatedCashInvestmentProcessedReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
945                }
946            }
947            updatePostingStats(assetDecreaseDoc);
948    
949            createAutomatedCashInvestmentProcessedReportWriterService.writeTableRow(createAutomatedCashInvestmentProcessedReportValues);
950            createAutomatedCashInvestmentProcessedReportWriterService.writeNewLines(1);
951        }
952    
953        /**
954         * This method...
955         * 
956         * @param assetDecreaseDoc
957         * @param tranLine
958         * @param isIncome
959         */
960        private void writeExceptionTableRowAssetDecrease(AssetDecreaseDocument assetDecreaseDoc, EndowmentTransactionLine tranLine, boolean isIncome) {
961    
962            TransactionDocumentExceptionReportLine createAutomatedCashInvestmentExceptionReportValues = new TransactionDocumentExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_DECREASE, assetDecreaseDoc.getDocumentNumber(), assetDecreaseDoc.getTargetTransactionSecurity().getSecurityID());
963    
964            if (tranLine != null) {
965                createAutomatedCashInvestmentExceptionReportValues.setKemid(tranLine.getKemid());
966                if (isIncome) {
967                    createAutomatedCashInvestmentExceptionReportValues.addIncomeAmount(tranLine.getTransactionAmount());
968                    createAutomatedCashInvestmentExceptionReportValues.addIncomeUnits(tranLine.getTransactionUnits());
969                }
970                else {
971                    createAutomatedCashInvestmentExceptionReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
972                    createAutomatedCashInvestmentExceptionReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
973                }
974            }
975            updateErrorStats(assetDecreaseDoc);
976    
977            createAutomatedCashInvestmentExceptionReportWriterService.writeTableRow(createAutomatedCashInvestmentExceptionReportValues);
978            createAutomatedCashInvestmentExceptionReportWriterService.writeNewLines(1);
979        }
980    
981        /**
982         * This method...
983         * 
984         * @param assetIncreaseDoc
985         * @param tranLine
986         * @param isIncome
987         */
988        private void writeExceptionTableRowAssetIncrease(AssetIncreaseDocument assetIncreaseDoc, EndowmentTransactionLine tranLine, boolean isIncome) {
989    
990            TransactionDocumentExceptionReportLine createAutomatedCashInvestmentExceptionReportValues = new TransactionDocumentExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_INCREASE, assetIncreaseDoc.getDocumentNumber(), assetIncreaseDoc.getTargetTransactionSecurity().getSecurityID());
991    
992            if (tranLine != null) {
993                createAutomatedCashInvestmentExceptionReportValues.setKemid(tranLine.getKemid());
994                if (isIncome) {
995                    createAutomatedCashInvestmentExceptionReportValues.addIncomeAmount(tranLine.getTransactionAmount());
996                    createAutomatedCashInvestmentExceptionReportValues.addIncomeUnits(tranLine.getTransactionUnits());
997                }
998                else {
999                    createAutomatedCashInvestmentExceptionReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
1000                    createAutomatedCashInvestmentExceptionReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
1001                }
1002            }
1003            updateErrorStats(assetIncreaseDoc);
1004    
1005            createAutomatedCashInvestmentExceptionReportWriterService.writeTableRow(createAutomatedCashInvestmentExceptionReportValues);
1006            createAutomatedCashInvestmentExceptionReportWriterService.writeNewLines(1);
1007        }
1008    
1009        /**
1010         * Writes the reason row and inserts a blank line.
1011         * 
1012         * @param reasonMessage
1013         */
1014        private void writeExceptionTableReason(String reasonMessage) {
1015            EndowmentExceptionReportHeader createAutomatedCashInvestmentExceptionReportReason = new EndowmentExceptionReportHeader();
1016            createAutomatedCashInvestmentExceptionReportReason.setColumnHeading1("Reason: ");
1017            createAutomatedCashInvestmentExceptionReportReason.setColumnHeading2(reasonMessage);
1018            createAutomatedCashInvestmentExceptionReportWriterService.writeTableRow(createAutomatedCashInvestmentExceptionReportReason);
1019            createAutomatedCashInvestmentExceptionReportWriterService.writeNewLines(1);
1020        }
1021    
1022        /**
1023         * Initialize the report document headers.
1024         */
1025        private void writeHeaders() {
1026            createAutomatedCashInvestmentExceptionReportWriterService.writeNewLines(1);
1027            createAutomatedCashInvestmentProcessedReportWriterService.writeNewLines(1);
1028            createAutomatedCashInvestmentExceptionReportWriterService.writeTableHeader(new TransactionDocumentExceptionReportLine());
1029            createAutomatedCashInvestmentProcessedReportWriterService.writeTableHeader(new TransactionDocumentTotalReportLine());
1030        }
1031    
1032        /**
1033         * This method...
1034         * 
1035         * @param assetDocument
1036         */
1037        private void updatePostingStats(EndowmentTaxLotLinesDocumentBase assetDocument) {
1038    
1039            String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(assetDocument.getClass());
1040            ReportDocumentStatistics stats = statistics.get(documentTypeName);
1041    
1042            // If null that means there isn't one in the map, so create it and add
1043            // it to the map.
1044            if (stats == null) {
1045                stats = new ReportDocumentStatistics(documentTypeName);
1046                statistics.put(documentTypeName, stats);
1047            }
1048            stats.addNumberOfSourceTransactionLines(assetDocument.getSourceTransactionLines().size());
1049            stats.addNumberOfTargetTransactionLines(assetDocument.getTargetTransactionLines().size());
1050    
1051            stats.incrementNumberOfDocuments();
1052        }
1053    
1054        /**
1055         * This method...
1056         * 
1057         * @param assetDocument
1058         */
1059        private void updateErrorStats(EndowmentTaxLotLinesDocumentBase assetDocument) {
1060    
1061            String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(assetDocument.getClass());
1062            ReportDocumentStatistics stats = statistics.get(documentTypeName);
1063    
1064            // If null that means there isn't one in the map, so create it and add
1065            // it to the map.
1066            if (stats == null) {
1067                stats = new ReportDocumentStatistics(documentTypeName);
1068                statistics.put(documentTypeName, stats);
1069            }
1070    
1071            stats.incrementNumberOfErrors();
1072        }
1073    
1074        /**
1075         * Write out the statistics.
1076         */
1077        private void writeStatistics() {
1078    
1079            for (Map.Entry<String, ReportDocumentStatistics> entry : statistics.entrySet()) {
1080    
1081                ReportDocumentStatistics stats = entry.getValue();
1082    
1083                createAutomatedCashInvestmentProcessedReportWriterService.writeStatisticLine("%s Documents:", stats.getDocumentTypeName());
1084                createAutomatedCashInvestmentProcessedReportWriterService.writeStatisticLine("   Number of Documents Generated:            %d", stats.getNumberOfDocuments());
1085                createAutomatedCashInvestmentProcessedReportWriterService.writeStatisticLine("   Number of Transaction Lines Generated:    %d", stats.getTotalNumberOfTransactionLines());
1086                createAutomatedCashInvestmentProcessedReportWriterService.writeStatisticLine("   Number of Error Records Written:          %d", stats.getNumberOfErrors());
1087                createAutomatedCashInvestmentProcessedReportWriterService.writeStatisticLine("", "");
1088    
1089                createAutomatedCashInvestmentExceptionReportWriterService.writeStatisticLine("%s Documents:", stats.getDocumentTypeName());
1090                createAutomatedCashInvestmentExceptionReportWriterService.writeStatisticLine("   Number of Documents Generated:            %d", stats.getNumberOfDocuments());
1091                createAutomatedCashInvestmentExceptionReportWriterService.writeStatisticLine("   Number of Transaction Lines Generated:    %d", stats.getTotalNumberOfTransactionLines());
1092                createAutomatedCashInvestmentExceptionReportWriterService.writeStatisticLine("   Number of Error Records Written:          %d", stats.getNumberOfErrors());
1093                createAutomatedCashInvestmentExceptionReportWriterService.writeStatisticLine("", "");
1094            }
1095    
1096        }
1097    
1098        /**
1099         * Gets the purchase description parameter.
1100         * 
1101         * @return
1102         */
1103        private String getPurchaseDescription() {
1104            return parameterService.getParameterValue(CreateAutomatedCashInvestmentTransactionsStep.class, EndowParameterKeyConstants.PURCHASE_DESCRIPTION);
1105        }
1106    
1107        /**
1108         * Gets the sale description parameter.
1109         * 
1110         * @return
1111         */
1112        private String getSaleDescription() {
1113            return parameterService.getParameterValue(CreateAutomatedCashInvestmentTransactionsStep.class, EndowParameterKeyConstants.SALE_DESCRIPTION);
1114        }
1115    
1116        /**
1117         * Sets the createAutomatedCashInvestmentExceptionReportWriterService attribute value.
1118         * 
1119         * @param createAutomatedCashInvestmentExceptionReportWriterService The
1120         *        createAutomatedCashInvestmentExceptionReportWriterService to set.
1121         */
1122        public void setCreateAutomatedCashInvestmentExceptionReportWriterService(ReportWriterService createAutomatedCashInvestmentExceptionReportWriterService) {
1123            this.createAutomatedCashInvestmentExceptionReportWriterService = createAutomatedCashInvestmentExceptionReportWriterService;
1124        }
1125    
1126        /**
1127         * Sets the createAutomatedCashInvestmentProcessedReportWriterService attribute value.
1128         * 
1129         * @param createAutomatedCashInvestmentProcessedReportWriterService The
1130         *        createAutomatedCashInvestmentProcessedReportWriterService to set.
1131         */
1132        public void setCreateAutomatedCashInvestmentProcessedReportWriterService(ReportWriterService createAutomatedCashInvestmentProcessedReportWriterService) {
1133            this.createAutomatedCashInvestmentProcessedReportWriterService = createAutomatedCashInvestmentProcessedReportWriterService;
1134        }
1135    
1136        /**
1137         * Sets the businessObjectService attribute value.
1138         * 
1139         * @param businessObjectService The businessObjectService to set.
1140         */
1141        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
1142            this.businessObjectService = businessObjectService;
1143        }
1144    
1145        /**
1146         * Sets the kualiRuleService attribute value.
1147         * 
1148         * @param kualiRuleService The kualiRuleService to set.
1149         */
1150        public void setKualiRuleService(KualiRuleService kualiRuleService) {
1151            this.kualiRuleService = kualiRuleService;
1152        }
1153    
1154        /**
1155         * Sets the parameterService attribute value.
1156         * 
1157         * @param parameterService The parameterService to set.
1158         */
1159        public void setParameterService(ParameterService parameterService) {
1160            this.parameterService = parameterService;
1161        }
1162    
1163        /**
1164         * Sets the documentService attribute value.
1165         * 
1166         * @param documentService The documentService to set.
1167         */
1168        public void setDocumentService(DocumentService documentService) {
1169            this.documentService = documentService;
1170        }
1171    
1172        /**
1173         * Sets the kemidService attribute value.
1174         * 
1175         * @param kemidService The kemidService to set.
1176         */
1177        public void setKemidService(KEMIDService kemidService) {
1178            this.kemidService = kemidService;
1179        }
1180    
1181        /**
1182         * Sets the kemService attribute value.
1183         * 
1184         * @param kemService The kemService to set.
1185         */
1186        public void setKemService(KEMService kemService) {
1187            this.kemService = kemService;
1188        }
1189    
1190        /**
1191         * Sets the securityService attribute value.
1192         * 
1193         * @param securityService The securityService to set.
1194         */
1195        public void setSecurityService(SecurityService securityService) {
1196            this.securityService = securityService;
1197        }
1198    
1199        /**
1200         * Sets the configService attribute value.
1201         * 
1202         * @param configService The configService to set.
1203         */
1204        public void setConfigService(KualiConfigurationService configService) {
1205            this.configService = configService;
1206        }
1207    
1208        /**
1209         * Sets the automatedCashInvestmentModelDao attribute value.
1210         * 
1211         * @param automatedCashInvestmentModelDao The automatedCashInvestmentModelDao to set.
1212         */
1213        public void setAutomatedCashInvestmentModelDao(AutomatedCashInvestmentModelDao automatedCashInvestmentModelDao) {
1214            this.automatedCashInvestmentModelDao = automatedCashInvestmentModelDao;
1215        }
1216    
1217        /**
1218         * Sets the dataDictionaryService attribute value.
1219         * 
1220         * @param dataDictionaryService The dataDictionaryService to set.
1221         */
1222        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
1223            this.dataDictionaryService = dataDictionaryService;
1224        }
1225    
1226        /**
1227         * Sets the updateEaiTaxLotService attribute value.
1228         * 
1229         * @param updateEaiTaxLotService The updateEaiTaxLotService to set.
1230         */
1231        public void setUpdateEaiTaxLotService(UpdateAssetIncreaseDocumentTaxLotsService updateEaiTaxLotService) {
1232            this.updateEaiTaxLotService = updateEaiTaxLotService;
1233        }
1234    
1235        /**
1236         * Sets the updateEadTaxLotService attribute value.
1237         * 
1238         * @param updateEadTaxLotService The updateEadTaxLotService to set.
1239         */
1240        public void setUpdateEadTaxLotService(UpdateAssetDecreaseDocumentTaxLotsService updateEadTaxLotService) {
1241            this.updateEadTaxLotService = updateEadTaxLotService;
1242        }
1243    
1244        /**
1245         * This class...
1246         */
1247        private class UnitAmountAssociation {
1248            private BigDecimal amount;
1249            private BigDecimal units;
1250    
1251            public UnitAmountAssociation(BigDecimal amount, BigDecimal units) {
1252                this.amount = amount;
1253                this.units = units;
1254            }
1255    
1256            /**
1257             * Gets the amount attribute.
1258             * 
1259             * @return Returns the amount.
1260             */
1261            public BigDecimal getAmount() {
1262                return amount;
1263            }
1264    
1265            /**
1266             * Gets the unit attribute.
1267             * 
1268             * @return Returns the unit.
1269             */
1270            public BigDecimal getUnits() {
1271                return units;
1272            }
1273        }
1274    }