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.util.ArrayList;
023    import java.util.Collection;
024    import java.util.HashMap;
025    import java.util.List;
026    import java.util.Map;
027    
028    import org.kuali.kfs.module.endow.EndowConstants;
029    import org.kuali.kfs.module.endow.EndowParameterKeyConstants;
030    import org.kuali.kfs.module.endow.EndowPropertyConstants;
031    import org.kuali.kfs.module.endow.batch.CreateCashSweepTransactionsStep;
032    import org.kuali.kfs.module.endow.batch.reporter.ReportDocumentStatistics;
033    import org.kuali.kfs.module.endow.batch.service.CreateCashSweepTransactionsService;
034    import org.kuali.kfs.module.endow.businessobject.CashSweepModel;
035    import org.kuali.kfs.module.endow.businessobject.EndowmentExceptionReportHeader;
036    import org.kuali.kfs.module.endow.businessobject.EndowmentSourceTransactionLine;
037    import org.kuali.kfs.module.endow.businessobject.EndowmentSourceTransactionSecurity;
038    import org.kuali.kfs.module.endow.businessobject.EndowmentTargetTransactionLine;
039    import org.kuali.kfs.module.endow.businessobject.EndowmentTargetTransactionSecurity;
040    import org.kuali.kfs.module.endow.businessobject.EndowmentTransactionLine;
041    import org.kuali.kfs.module.endow.businessobject.HoldingTaxLot;
042    import org.kuali.kfs.module.endow.businessobject.KEMID;
043    import org.kuali.kfs.module.endow.businessobject.KemidCurrentCash;
044    import org.kuali.kfs.module.endow.businessobject.PooledFundControl;
045    import org.kuali.kfs.module.endow.businessobject.TransactionDocumentExceptionReportLine;
046    import org.kuali.kfs.module.endow.businessobject.TransactionDocumentTotalReportLine;
047    import org.kuali.kfs.module.endow.dataaccess.CashSweepModelDao;
048    import org.kuali.kfs.module.endow.document.AssetDecreaseDocument;
049    import org.kuali.kfs.module.endow.document.AssetIncreaseDocument;
050    import org.kuali.kfs.module.endow.document.EndowmentTaxLotLinesDocumentBase;
051    import org.kuali.kfs.module.endow.document.service.KEMIDService;
052    import org.kuali.kfs.module.endow.document.service.KEMService;
053    import org.kuali.kfs.module.endow.document.service.PooledFundControlService;
054    import org.kuali.kfs.module.endow.document.service.UpdateAssetDecreaseDocumentTaxLotsService;
055    import org.kuali.kfs.module.endow.document.service.UpdateAssetIncreaseDocumentTaxLotsService;
056    import org.kuali.kfs.module.endow.document.validation.event.AddTransactionLineEvent;
057    import org.kuali.kfs.module.endow.util.GloabalVariablesExtractHelper;
058    import org.kuali.kfs.sys.service.ReportWriterService;
059    import org.kuali.rice.kew.exception.WorkflowException;
060    import org.kuali.rice.kns.bo.DocumentHeader;
061    import org.kuali.rice.kns.rule.event.RouteDocumentEvent;
062    import org.kuali.rice.kns.service.BusinessObjectService;
063    import org.kuali.rice.kns.service.DataDictionaryService;
064    import org.kuali.rice.kns.service.DocumentService;
065    import org.kuali.rice.kns.service.KualiConfigurationService;
066    import org.kuali.rice.kns.service.KualiRuleService;
067    import org.kuali.rice.kns.service.ParameterService;
068    import org.kuali.rice.kns.util.KualiDecimal;
069    import org.springframework.transaction.annotation.Transactional;
070    
071    @Transactional
072    public class CreateCashSweepTransactionsServiceImpl implements CreateCashSweepTransactionsService {
073    
074        protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CreateCashSweepTransactionsServiceImpl.class);
075        private static final String SUBMIT_DOCUMENT_DESCRIPTION = "Created by Create Cash Sweep Batch Process.";
076    
077        private Map<String, ReportDocumentStatistics> statistics = new HashMap<String, ReportDocumentStatistics>();
078        private UpdateAssetIncreaseDocumentTaxLotsService updateEaiTaxLotService;
079        private UpdateAssetDecreaseDocumentTaxLotsService updateEadTaxLotService;
080        private ReportWriterService createCashSweepExceptionReportWriterService;
081        private ReportWriterService createCashSweepProcessedReportWriterService;
082        private PooledFundControlService pooledFundControlService;
083        private BusinessObjectService businessObjectService;
084        private DataDictionaryService dataDictionaryService;
085        private KualiConfigurationService configService;
086        private CashSweepModelDao cashSweepModelDao;
087        private KualiRuleService kualiRuleService;
088        private ParameterService parameterService;
089        private DocumentService documentService;
090        private KEMIDService kemidService;
091        private KEMService kemService;
092    
093        /**
094         * @see org.kuali.kfs.module.endow.batch.service.CreateCashSweepTransactionsService#createCashSweepTransactions()
095         */
096        public boolean createCashSweepTransactions() {
097    
098            LOG.info("Starting \"Create Cash Sweep Transactions\" batch job...");
099            writeHeaders();
100    
101            Collection<CashSweepModel> cashSweepModels = getCashSweepModelMatchingCurrentDate();
102            for (CashSweepModel cashSweepModel : cashSweepModels) {
103                processIncomeSweepPurchases(cashSweepModel);
104                processIncomeSweepSales(cashSweepModel);
105                processPrincipalSweepPurchases(cashSweepModel);
106                processPrincipalSweepSale(cashSweepModel);
107            }
108    
109            writeStatistics();
110            LOG.info("Finished \"Create Cash Sweep Transactions\" batch job!");
111    
112            return true;
113        }
114    
115        /**
116         * Process all the principle cash sweep models for sales.
117         * 
118         * @param cashSweepModel
119         */
120        protected boolean processPrincipalSweepSale(CashSweepModel cashSweepModel) {
121            LOG.info("Entering \"processPrincipalSweepSale\".");
122    
123            boolean success = false;
124            
125            // Step 2. Get security and registration code from cash sweep.
126            String sweepRegistraionCode = cashSweepModel.getPrincipleSweepRegistrationCode();
127            String sweepSecurityId = cashSweepModel.getPrincipleSweepInvestment();
128    
129            // Steps 4 - 12.
130            success = processAssetDecreaseDocuments(cashSweepModel.getCashSweepModelID(), sweepRegistraionCode, sweepSecurityId, cashSweepModel.getSweepPrincipleCashLimit(), false);
131    
132            LOG.info("Leaving \"processPrincipalSweepSale\".");
133            
134            return success;
135        }
136    
137        /**
138         * Process all the principle cash sweep models for purchases.
139         * 
140         * @param cashSweepModel
141         */
142        protected boolean processPrincipalSweepPurchases(CashSweepModel cashSweepModel) {
143            LOG.info("Entering \"processPrincipalSweepPurchases\".");
144    
145            boolean success = false;
146            
147            // Step 2. Get security and registration code from cash sweep.
148            String sweepRegistraionCode = cashSweepModel.getPrincipleSweepRegistrationCode();
149            String sweepSecurityId = cashSweepModel.getPrincipleSweepInvestment();
150    
151            // Steps 4 - 12.
152            success = processAssetIncreaseDocuments(cashSweepModel.getCashSweepModelID(), sweepRegistraionCode, sweepSecurityId, cashSweepModel.getSweepPrincipleCashLimit(), false);
153    
154            LOG.info("Leaving \"processPrincipalSweepPurchases\".");
155            
156            return success;
157        }
158    
159        /**
160         * Process all the income cash sweep models for sales.
161         * 
162         * @param cashSweepModel
163         */
164        protected boolean processIncomeSweepSales(CashSweepModel cashSweepModel) {
165            LOG.info("Entering \"processIncomeSweepSales\".");
166    
167            boolean success = false;
168            
169            // Step 2. Get security and registration code from cash sweep.
170            String sweepRegistraionCode = cashSweepModel.getIncomeSweepRegistrationCode();
171            String sweepSecurityId = cashSweepModel.getIncomeSweepInvestment();
172            
173            // Steps 4 - 12.
174            success = processAssetDecreaseDocuments(cashSweepModel.getCashSweepModelID(), sweepRegistraionCode, sweepSecurityId, cashSweepModel.getSweepIncomeCashLimit(), true);
175    
176            LOG.info("Leaving \"processIncomeSweepSales\".");
177            
178            return success;
179        }
180    
181        /**
182         * Process all the income cash sweep models for purchases.
183         * 
184         * @param cashSweepModel
185         */
186        protected boolean processIncomeSweepPurchases(CashSweepModel cashSweepModel) {
187            LOG.info("Entering \"processIncomeSweepPurchases\".");
188    
189            boolean success = false;
190            
191            // Step 2. Get security and registration code from cash sweep.
192            String sweepRegistraionCode = cashSweepModel.getIncomeSweepRegistrationCode();
193            String sweepSecurityId = cashSweepModel.getIncomeSweepInvestment();
194    
195            // Steps 4 - 12.
196            success = processAssetIncreaseDocuments(cashSweepModel.getCashSweepModelID(), sweepRegistraionCode, sweepSecurityId, cashSweepModel.getSweepIncomeCashLimit(), true);
197    
198            LOG.info("Leaving \"processIncomeSweepPurchases\".");
199            
200            return success;
201        }
202    
203        /**
204         * Creates asset decrease documents with transaction lines and routes it.
205         * 
206         * @param cashSweepModelId
207         * @param sweepRegistraionCode
208         * @param sweepSecurityId
209         * @param cashLimit
210         * @param isIncome
211         */
212        protected boolean processAssetDecreaseDocuments(Integer cashSweepModelId, String sweepRegistraionCode, String sweepSecurityId, BigDecimal cashLimit, boolean isIncome) {
213            
214            boolean success = true;        
215            AssetDecreaseDocument assetDecreaseDoc = null;
216    
217            // Get the maximum number of allowed transaction lines per eDoc.
218            int maxNumberOfTransactionLines = getMaxNumberOfTransactionLines();
219    
220            // Iterate through all the KEMIDs for processing.
221            List<KEMID> kemids = new ArrayList<KEMID>(kemidService.getByCashSweepId(cashSweepModelId));
222            for (int i = 0; i < kemids.size(); i++) {
223    
224                // Get the current KEMID.
225                KEMID kemid = kemids.get(i);
226    
227                // Get the current income/principle cash for this KEMID and compare it to the cash limit.
228                BigDecimal currentCash = getKemidCurrentCash(kemid, isIncome);
229                if (currentCash != null && currentCash.compareTo(BigDecimal.ZERO) < 0) {
230                    
231                    // If this is null that means we need to create a new eDoc for
232                    // the first time.
233                    if (assetDecreaseDoc == null) {
234                        assetDecreaseDoc = createAssetDecrease(sweepSecurityId, sweepRegistraionCode);
235                    }
236    
237                    // Create, validate, and add the transaction line to the eDoc.
238                    addTransactionLineForAssetDecrease(assetDecreaseDoc, kemid, cashLimit, currentCash, sweepSecurityId, isIncome);
239    
240                    // Check to see if we've reached our max number of transaction lines
241                    // per eDoc. If so, validate, and submit the current eDoc and start
242                    // another eDoc.
243                    if (i != 0 && i % maxNumberOfTransactionLines == 0) {
244                        // Validate and route the document. Then create a new doc.
245                        success = routeAssetDecreaseDocument(assetDecreaseDoc, isIncome);
246                        assetDecreaseDoc = createAssetDecrease(sweepSecurityId, sweepRegistraionCode);
247                    }
248                }
249            }
250    
251            // Verify that we don't need to do any clean-up. There could still be
252            // some let over transaction lines, less than the max amount that need
253            // to still be processed on the current eDoc. This also prevents from
254            // routing an asset decrease document with no transaction lines.
255            if (assetDecreaseDoc != null && !assetDecreaseDoc.getSourceTransactionLines().isEmpty()) {
256                // Validate and route the document.
257                success = routeAssetDecreaseDocument(assetDecreaseDoc, isIncome);
258            }
259            
260            return success;
261        }
262    
263        /**
264         * Creates asset increase documents with transaction lines and routes it.
265         * 
266         * @param cashSweepModelId
267         * @param sweepRegistraionCode
268         * @param sweepSecurityId
269         * @param cashLimit
270         * @param isIncome
271         */
272        protected boolean processAssetIncreaseDocuments(Integer cashSweepModelId, String sweepRegistraionCode, String sweepSecurityId, BigDecimal cashLimit, boolean isIncome) {
273                    
274            boolean success = true;  
275            AssetIncreaseDocument assetIncreaseDoc = null;
276    
277            // Get the maximum number of allowed transaction lines per eDoc.
278            int maxNumberOfTransactionLines = getMaxNumberOfTransactionLines();
279    
280            // Iterate through all the KEMIDs for processing.
281            List<KEMID> kemids = new ArrayList<KEMID>(kemidService.getByCashSweepId(cashSweepModelId));
282            for (int i = 0; i < kemids.size(); i++) {
283    
284                // Get the current KEMID.
285                KEMID kemid = kemids.get(i);
286    
287                // Get the current income/principle cash for this KEMID and compare it to the cash limit.
288                BigDecimal currentCash = getKemidCurrentCash(kemid, isIncome);
289                if (currentCash != null && ((currentCash.compareTo(cashLimit) > 0 || currentCash.compareTo(cashLimit) == 0))) {
290                
291                    // If this is null that means we need to create a new eDoc for
292                    // the first time.
293                    if (assetIncreaseDoc == null) {
294                        assetIncreaseDoc = createAssetIncrease(sweepSecurityId, sweepRegistraionCode);
295                    }
296    
297                    // Create, validate, and add the transaction line to the eDoc.
298                    addTransactionLineForAssetIncrease(assetIncreaseDoc, kemid, cashLimit, currentCash, sweepSecurityId, isIncome);
299    
300                    // Check to see if we've reached our max number of transaction lines
301                    // per eDoc. If so, validate, and submit the current eDoc and start
302                    // another eDoc.
303                    if (i != 0 && i % maxNumberOfTransactionLines == 0) {
304                        // Validate and route the document. Then create a new doc.
305                        success = routeAssetIncreaseDocument(assetIncreaseDoc, isIncome);
306                        assetIncreaseDoc = createAssetIncrease(sweepSecurityId, sweepRegistraionCode);
307                    }
308                }
309            }
310    
311            // Verify that we don't need to do any clean-up. There could still be
312            // some let over transaction lines, less than the max amount that need
313            // to still be processed on the current eDoc. This also prevents from
314            // routing an asset decrease document with no transaction lines.
315            if (assetIncreaseDoc != null && !assetIncreaseDoc.getTargetTransactionLines().isEmpty()) {
316                // Validate and route the document.
317                success = routeAssetIncreaseDocument(assetIncreaseDoc, isIncome);
318            }
319            
320            return success;
321        }
322        
323        /**
324         * 
325         * Calculates the number of units available.
326         *
327         * @param kemid
328         * @param sweepSecurityId
329         * @return
330         */
331        private KualiDecimal calculateAvailableKemidSecurities(KEMID kemid, String sweepSecurityId) {
332            
333            BigDecimal unitsAvailable = BigDecimal.ZERO;
334            
335            Map<String, String> criteria = new HashMap<String, String>();
336            criteria.put(EndowPropertyConstants.HOLDING_TAX_LOT_KEMID, kemid.getKemid());
337    
338            List<HoldingTaxLot> holdingTaxLots = (List<HoldingTaxLot>)businessObjectService.findMatching(HoldingTaxLot.class, criteria);
339            for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
340                if(holdingTaxLot.getSecurityId().equalsIgnoreCase(sweepSecurityId)) {
341                    unitsAvailable = unitsAvailable.add(holdingTaxLot.getUnits());
342                }
343            }
344            
345            return (new KualiDecimal(unitsAvailable));
346        }
347    
348        /**
349         * This method...
350         * 
351         * @param assetDecreaseDoc
352         * @param kemid
353         * @param cashLimit
354         * @param currentCash
355         * @param isIncome
356         */
357        private void addTransactionLineForAssetDecrease(AssetDecreaseDocument assetDecreaseDoc, KEMID kemid, BigDecimal cashLimit, BigDecimal currentCash, String sweepSecurityId, boolean isIncome) {
358            // Calculate the amount.
359            KualiDecimal amount = calculateCashAvailable(cashLimit, currentCash, false);
360            
361            // Calculate available units and determine the transaction amount.  
362            // If the units needed are equal to or less than the units held then 
363            // use the calculated units.  If the units needed are greater than the 
364            // units held, then use the units held as the transaction amount.
365            KualiDecimal available = calculateAvailableKemidSecurities(kemid, sweepSecurityId);
366            if (amount.compareTo(available) > 0) { amount = available; }
367            
368            // Create the correct transaction line based on if it's a source or target type.
369            EndowmentTransactionLine transactionLine = createTransactionLine(assetDecreaseDoc.getDocumentNumber(), kemid.getKemid(), amount, true, isIncome);
370    
371            // Validate the transaction line.
372            boolean rulesPassed = kualiRuleService.applyRules(new AddTransactionLineEvent(NEW_SOURCE_TRAN_LINE_PROPERTY_NAME, assetDecreaseDoc, transactionLine));
373    
374            // If the validation passes, add the transaction line to the document; otherwise,
375            // write the error messages to the error report.
376            if (rulesPassed) {
377                assetDecreaseDoc.addSourceTransactionLine((EndowmentSourceTransactionLine) transactionLine);
378                updateEadTaxLotService.updateTransactionLineTaxLots(assetDecreaseDoc, transactionLine);
379            }
380            else {
381                writeExceptionTableRowAssetDecrease(assetDecreaseDoc, transactionLine, isIncome);
382                List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
383                for (String errorMessage : errorMessages) {
384                    writeExceptionTableReason(errorMessage);          
385                }
386            }
387        }
388    
389        /**
390         * This method...
391         * 
392         * @param assetIncreaseDoc
393         * @param kemid
394         * @param cashLimit
395         * @param currentCash
396         * @param isIncome
397         */
398        private void addTransactionLineForAssetIncrease(AssetIncreaseDocument assetIncreaseDoc, KEMID kemid, BigDecimal cashLimit, BigDecimal currentCash, String sweepSecurityId, boolean isIncome) {
399            // Calculate the amount.
400            KualiDecimal amount = calculateCashAvailable(cashLimit, currentCash, true);
401    
402            // Create the correct transaction line based on if it's a source or target type.
403            EndowmentTransactionLine transactionLine = createTransactionLine(assetIncreaseDoc.getDocumentNumber(), kemid.getKemid(), amount, false, isIncome);
404    
405            // Validate the transaction line.
406            boolean rulesPassed = kualiRuleService.applyRules(new AddTransactionLineEvent(NEW_TARGET_TRAN_LINE_PROPERTY_NAME, assetIncreaseDoc, transactionLine));
407    
408            // If the validation passes, add the transaction line to the document; otherwise,
409            // write the error messages to the error report.
410            if (rulesPassed) {
411                assetIncreaseDoc.addTargetTransactionLine((EndowmentTargetTransactionLine) transactionLine);
412                updateEaiTaxLotService.updateTransactionLineTaxLots(assetIncreaseDoc, transactionLine);
413            }
414            else {
415                writeExceptionTableRowAssetIncrease(assetIncreaseDoc, transactionLine, isIncome);
416                List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
417                for (String errorMessage : errorMessages) {
418                    writeExceptionTableReason(errorMessage);              
419                }
420            }
421        }
422    
423        /**
424         * Validates and routes the asset decrease document.
425         * 
426         * @param assetDecreaseDoc
427         * @param balh
428         */
429        protected boolean routeAssetDecreaseDocument(AssetDecreaseDocument assetDecreaseDoc, boolean isIncome) {
430    
431            boolean success = false;
432            
433            // Perform validation on the document.
434            boolean rulesPassed = kualiRuleService.applyRules(new RouteDocumentEvent(assetDecreaseDoc));
435    
436            // If the document passed validations, route it accordingly.
437            if (rulesPassed) {
438                boolean noRouteIndicator = getNoRouteIndicator(true);
439                try {
440                    assetDecreaseDoc.setNoRouteIndicator(noRouteIndicator);
441                    documentService.routeDocument(assetDecreaseDoc, SUBMIT_DOCUMENT_DESCRIPTION, null);
442                    writeProcessedTableRowAssetDecrease(assetDecreaseDoc, isIncome);
443                    success = true;
444                }
445                catch (WorkflowException we) {
446                    writeExceptionTableReason(assetDecreaseDoc.getDocumentNumber() + " - " + we.getLocalizedMessage());
447                    LOG.error(we.getLocalizedMessage());
448                }
449            }
450            else {
451                // Write the errors to the exception file.
452                List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
453                writeExceptionTableRowAssetDecrease(assetDecreaseDoc, null, isIncome);
454                for (String errorMessage : errorMessages) {
455                    writeExceptionTableReason(errorMessage);
456                }
457                // Try to save the document if it fails validation.
458                try {
459                    documentService.saveDocument(assetDecreaseDoc);
460                }
461                catch (WorkflowException we) {
462                    writeExceptionTableReason(assetDecreaseDoc.getDocumentNumber() + " - " + we.getLocalizedMessage());
463                    LOG.error(we.getLocalizedMessage());
464                }
465            }
466            
467            return success;
468        }
469    
470        /**
471         * Validates and routes the asset increase document.
472         * 
473         * @param assetIncreaseDoc
474         */
475        protected boolean routeAssetIncreaseDocument(AssetIncreaseDocument assetIncreaseDoc, boolean isIncome) {
476    
477            boolean success = false;
478            
479            // Perform validation on the document.
480            boolean rulesPassed = kualiRuleService.applyRules(new RouteDocumentEvent(assetIncreaseDoc));
481    
482            // If the document passed validations, route it accordingly.
483            if (rulesPassed) {
484                boolean noRouteIndicator = getNoRouteIndicator(false);
485                try {
486                    assetIncreaseDoc.setNoRouteIndicator(noRouteIndicator);
487                    documentService.routeDocument(assetIncreaseDoc, SUBMIT_DOCUMENT_DESCRIPTION, null);
488                    writeProcessedTableRowAssetIncrease(assetIncreaseDoc, isIncome);
489                    success = true;
490                }
491                catch (WorkflowException we) {
492                    writeExceptionTableReason(assetIncreaseDoc.getDocumentNumber() + " - " + we.getLocalizedMessage());
493                    LOG.error(we.getLocalizedMessage());
494                }
495            }
496            else {
497                // Write the errors to the exception file.
498                List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
499                writeExceptionTableRowAssetIncrease(assetIncreaseDoc, null, isIncome);
500                for (String errorMessage : errorMessages) {
501                    writeExceptionTableReason(errorMessage);
502                }
503                // Try to save the document if it fails validation.
504                try {
505                    documentService.saveDocument(assetIncreaseDoc);
506                }
507                catch (WorkflowException we) {
508                    writeExceptionTableReason(assetIncreaseDoc.getDocumentNumber() + " - " + we.getLocalizedMessage());
509                    LOG.error(we.getLocalizedMessage());
510                }
511            }
512            
513            return success;
514        }
515    
516        /**
517         * Creates and returns a new principle or income transaction line.
518         * 
519         * @param docNumber
520         * @param kemid
521         * @param amount
522         * @param isSource
523         * @return
524         */
525        private EndowmentTransactionLine createTransactionLine(String docNumber, String kemid, KualiDecimal amount, boolean isSource, boolean isIncome) {
526    
527            EndowmentTransactionLine transactionLine = null;
528            if (isSource) {
529                transactionLine = new EndowmentSourceTransactionLine();
530            }
531            else {
532                transactionLine = new EndowmentTargetTransactionLine();
533            }
534    
535            // Set values on the transaction line.
536            transactionLine.setDocumentNumber(docNumber);
537            transactionLine.setKemid(kemid);
538            if (!isIncome) {
539                transactionLine.setTransactionIPIndicatorCode(EndowConstants.IncomePrincipalIndicator.PRINCIPAL);
540            }
541            else {
542                transactionLine.setTransactionIPIndicatorCode(EndowConstants.IncomePrincipalIndicator.INCOME);
543            }
544            // These should be whole numbers.
545            transactionLine.setTransactionAmount(amount);
546            transactionLine.setTransactionUnits(amount);
547    
548            return transactionLine;
549        }
550    
551        /**
552         * Creates and returns a new asset increase document.
553         * 
554         * @param securityId
555         * @param registrationCode
556         * @return
557         */
558        private AssetIncreaseDocument createAssetIncrease(String securityId, String registrationCode) {
559    
560            AssetIncreaseDocument assetIncrease = null;
561            try {
562                assetIncrease = (AssetIncreaseDocument) documentService.getNewDocument(AssetIncreaseDocument.class);
563    
564                // Set the document description.
565                DocumentHeader docHeader = assetIncrease.getDocumentHeader();
566                docHeader.setDocumentDescription(getPurchaseDescription());
567                assetIncrease.setDocumentHeader(docHeader);
568    
569                // Set the sub type code to cash.
570                assetIncrease.setTransactionSubTypeCode(EndowConstants.TransactionSubTypeCode.CASH);
571    
572                // Create and set the target security transaction line.
573                EndowmentTargetTransactionSecurity targetTransactionSecurity = new EndowmentTargetTransactionSecurity();
574                targetTransactionSecurity.setRegistrationCode(registrationCode);
575                targetTransactionSecurity.setSecurityID(securityId);
576    
577                assetIncrease.setTargetTransactionSecurity(targetTransactionSecurity);
578            }
579            catch (WorkflowException ex) {
580                writeExceptionTableReason("Workflow error while trying to create EAI document: " + ex.getLocalizedMessage());
581                LOG.error(ex.getLocalizedMessage());
582            }
583    
584            return assetIncrease;
585        }
586    
587        /**
588         * Creates and returns a new asset decrease document.
589         * 
590         * @param securityId
591         * @param registrationCode
592         * @return
593         */
594        private AssetDecreaseDocument createAssetDecrease(String securityId, String registrationCode) {
595    
596            AssetDecreaseDocument assetDecrease = null;
597            try {
598                assetDecrease = (AssetDecreaseDocument) documentService.getNewDocument(AssetDecreaseDocument.class);
599    
600                // Set the document description.
601                DocumentHeader docHeader = assetDecrease.getDocumentHeader();
602                docHeader.setDocumentDescription(getSaleDescription());
603                assetDecrease.setDocumentHeader(docHeader);
604    
605                // Set the sub type code to cash.
606                assetDecrease.setTransactionSubTypeCode(EndowConstants.TransactionSubTypeCode.CASH);
607    
608                // Create and set the source security transaction line.
609                EndowmentSourceTransactionSecurity sourceTransactionSecurity = new EndowmentSourceTransactionSecurity();
610                sourceTransactionSecurity.setRegistrationCode(registrationCode);
611                sourceTransactionSecurity.setSecurityID(securityId);
612    
613                assetDecrease.setSourceTransactionSecurity(sourceTransactionSecurity);
614    
615            }
616            catch (WorkflowException ex) {
617                writeExceptionTableReason("Workflow error while trying to create EAD document: " + ex.getLocalizedMessage());
618                LOG.error(ex.getLocalizedMessage());
619            }
620    
621            return assetDecrease;
622        }
623    
624        /**
625         * Gets the current principle or income cash for the specified KEMID.
626         * 
627         * @param kemid
628         * @return
629         */
630        private BigDecimal getKemidCurrentCash(KEMID kemid, boolean isIncome) {
631            KemidCurrentCash kemidCurrentCash = businessObjectService.findBySinglePrimaryKey(KemidCurrentCash.class, kemid.getKemid());
632    
633            if (kemidCurrentCash == null) {
634                writeExceptionTableReason("Unable to calculate current cash amount: END_CRNT_CSH_T is \'null\' for KEMID \'" + kemid.getKemid() + "\'");
635                return null;
636            }
637    
638            return !isIncome ? kemidCurrentCash.getCurrentPrincipalCash().bigDecimalValue() : kemidCurrentCash.getCurrentIncomeCash().bigDecimalValue();
639        }
640    
641        /**
642         * Determines the cash available.
643         * 
644         * @param cashLimit
645         * @param currentCash
646         * @return
647         */
648        private KualiDecimal calculateCashAvailable(BigDecimal cashLimit, BigDecimal currentCash, boolean isIncrease) {
649    
650            BigDecimal amount = null;
651    
652            // Subtract the current amount from the limit amount, and round up.
653            if (isIncrease) {
654                amount = currentCash.subtract(cashLimit);
655                amount = amount.setScale(0, BigDecimal.ROUND_UP);
656            }
657            // Take the absolute value of the current cash, add limit amount,
658            // and round down.
659            else {
660                amount = (currentCash.abs()).add(cashLimit);
661                amount = amount.setScale(0, BigDecimal.ROUND_DOWN);
662            }
663    
664            return new KualiDecimal(amount);
665        }
666    
667        /**
668         * This method retrieves all the cash sweep models whose frequency code matches the current date.
669         * 
670         * @return Collection of CashSweepModel business objects
671         */
672        private Collection<CashSweepModel> getCashSweepModelMatchingCurrentDate() {
673            return cashSweepModelDao.getCashSweepModelWithNextPayDateEqualToCurrentDate(kemService.getCurrentDate());
674        }
675    
676        /**
677         * Gets the appropriate approval indicator based on if it's for a sale or purchase type.
678         * 
679         * @param isSale
680         * @return
681         */
682        private boolean getNoRouteIndicator(boolean isSale) {
683            boolean approvalIndicator = isSale ? getSaleNoRouteIndicator() : getPurchaseNoRouteIndicator();
684            return approvalIndicator;
685        }
686    
687        /**
688         * This method returns the true or false value of the purchase no route indicator.
689         * 
690         * @return
691         */
692        private boolean getPurchaseNoRouteIndicator() {
693            String noRouteIndicator = parameterService.getParameterValue(CreateCashSweepTransactionsStep.class, EndowParameterKeyConstants.PURCHASE_NO_ROUTE_IND);
694            return (EndowConstants.YES.equalsIgnoreCase(noRouteIndicator) ? true : false);
695        }
696    
697        /**
698         * This method returns the true or false value of the sale no route indicator.
699         * 
700         * @return
701         */
702        private boolean getSaleNoRouteIndicator() {
703            String noRouteIndicator = parameterService.getParameterValue(CreateCashSweepTransactionsStep.class, EndowParameterKeyConstants.SALE_NO_ROUTE_IND);
704            return (EndowConstants.YES.equalsIgnoreCase(noRouteIndicator) ? true : false);
705        }
706    
707        /**
708         * Gets the purchase description parameter.
709         * 
710         * @return
711         */
712        private String getPurchaseDescription() {
713            return parameterService.getParameterValue(CreateCashSweepTransactionsStep.class, EndowParameterKeyConstants.PURCHASE_DESCRIPTION);
714        }
715    
716        /**
717         * Gets the sale description parameter.
718         * 
719         * @return
720         */
721        private String getSaleDescription() {
722            return parameterService.getParameterValue(CreateCashSweepTransactionsStep.class, EndowParameterKeyConstants.SALE_DESCRIPTION);
723        }
724    
725        /**
726         * This method...
727         * 
728         * @return
729         */
730        private int getMaxNumberOfTransactionLines() {
731            return kemService.getMaxNumberOfTransactionLinesPerDocument();
732        }
733    
734        /**
735         * This method...
736         * 
737         * @param assetIncreaseDoc
738         * @param isIncome
739         */
740        private void writeProcessedTableRowAssetIncrease(AssetIncreaseDocument assetIncreaseDoc, boolean isIncome) {
741    
742            TransactionDocumentTotalReportLine createCashSweepProcessedReportValues = new TransactionDocumentTotalReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_INCREASE, assetIncreaseDoc.getDocumentNumber(), assetIncreaseDoc.getTargetTransactionSecurity().getSecurityID());
743    
744            List<EndowmentTransactionLine> transLines = assetIncreaseDoc.getTargetTransactionLines();
745            for (EndowmentTransactionLine tranLine : transLines) {
746                if (isIncome) {
747                    createCashSweepProcessedReportValues.addIncomeAmount(tranLine.getTransactionAmount());
748                    createCashSweepProcessedReportValues.addIncomeUnits(tranLine.getTransactionUnits());
749                }
750                else {
751                    createCashSweepProcessedReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
752                    createCashSweepProcessedReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
753                }
754            }
755            updatePostingStats(assetIncreaseDoc);
756    
757            createCashSweepProcessedReportWriterService.writeTableRow(createCashSweepProcessedReportValues);
758            createCashSweepProcessedReportWriterService.writeNewLines(1);
759        }
760    
761        /**
762         * This method...
763         * 
764         * @param assetDecreaseDoc
765         * @param isIncome
766         */
767        private void writeProcessedTableRowAssetDecrease(AssetDecreaseDocument assetDecreaseDoc, boolean isIncome) {
768    
769            TransactionDocumentTotalReportLine createCashSweepProcessedReportValues = new TransactionDocumentTotalReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_DECREASE, assetDecreaseDoc.getDocumentNumber(), assetDecreaseDoc.getTargetTransactionSecurity().getSecurityID());
770    
771            List<EndowmentTransactionLine> transLines = assetDecreaseDoc.getSourceTransactionLines();
772            for (EndowmentTransactionLine tranLine : transLines) {
773                if (isIncome) {
774                    createCashSweepProcessedReportValues.addIncomeAmount(tranLine.getTransactionAmount());
775                    createCashSweepProcessedReportValues.addIncomeUnits(tranLine.getTransactionUnits());
776                }
777                else {
778                    createCashSweepProcessedReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
779                    createCashSweepProcessedReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
780                }
781            }
782            updatePostingStats(assetDecreaseDoc);
783    
784            createCashSweepProcessedReportWriterService.writeTableRow(createCashSweepProcessedReportValues);
785            createCashSweepProcessedReportWriterService.writeNewLines(1);
786        }
787    
788        /**
789         * This method...
790         * 
791         * @param assetIncreaseDoc
792         * @param tranLine
793         * @param isIncome
794         */
795        private void writeExceptionTableRowAssetIncrease(AssetIncreaseDocument assetIncreaseDoc, EndowmentTransactionLine tranLine, boolean isIncome) {
796    
797            TransactionDocumentExceptionReportLine createCashSweepExceptionReportValues = new TransactionDocumentExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_INCREASE, assetIncreaseDoc.getDocumentNumber(), assetIncreaseDoc.getTargetTransactionSecurity().getSecurityID());
798    
799            if (tranLine != null) {
800                createCashSweepExceptionReportValues.setKemid(tranLine.getKemid());
801                if (isIncome) {
802                    createCashSweepExceptionReportValues.addIncomeAmount(tranLine.getTransactionAmount());
803                    createCashSweepExceptionReportValues.addIncomeUnits(tranLine.getTransactionUnits());
804                }
805                else {
806                    createCashSweepExceptionReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
807                    createCashSweepExceptionReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
808                }
809            }
810            updateErrorStats(assetIncreaseDoc);
811    
812            createCashSweepExceptionReportWriterService.writeTableRow(createCashSweepExceptionReportValues);
813            createCashSweepExceptionReportWriterService.writeNewLines(1);
814        }
815    
816        /**
817         * This method...
818         * 
819         * @param assetDecreaseDoc
820         * @param tranLine
821         * @param isIncome
822         */
823        private void writeExceptionTableRowAssetDecrease(AssetDecreaseDocument assetDecreaseDoc, EndowmentTransactionLine tranLine, boolean isIncome) {
824    
825            TransactionDocumentExceptionReportLine createCashSweepExceptionReportValues = new TransactionDocumentExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_ASSET_DECREASE, assetDecreaseDoc.getDocumentNumber(), assetDecreaseDoc.getTargetTransactionSecurity().getSecurityID());
826    
827            if (tranLine != null) {
828                createCashSweepExceptionReportValues.setKemid(tranLine.getKemid());
829                if (isIncome) {
830                    createCashSweepExceptionReportValues.addIncomeAmount(tranLine.getTransactionAmount());
831                    createCashSweepExceptionReportValues.addIncomeUnits(tranLine.getTransactionUnits());
832                }
833                else {
834                    createCashSweepExceptionReportValues.addPrincipalAmount(tranLine.getTransactionAmount());
835                    createCashSweepExceptionReportValues.addPrincipalUnits(tranLine.getTransactionUnits());
836                }
837            }
838            updateErrorStats(assetDecreaseDoc);
839    
840            createCashSweepExceptionReportWriterService.writeTableRow(createCashSweepExceptionReportValues);
841            createCashSweepExceptionReportWriterService.writeNewLines(1);
842        }
843    
844        /**
845         * Writes the reason row and inserts a blank line.
846         * 
847         * @param reasonMessage
848         */
849        private void writeExceptionTableReason(String reasonMessage) {
850            EndowmentExceptionReportHeader createCashSweepExceptionReportReason = new EndowmentExceptionReportHeader();
851            createCashSweepExceptionReportReason.setColumnHeading1("Reason: ");
852            createCashSweepExceptionReportReason.setColumnHeading2(reasonMessage);
853    
854            createCashSweepExceptionReportWriterService.writeTableRow(createCashSweepExceptionReportReason);
855            createCashSweepExceptionReportWriterService.writeNewLines(1);
856        }
857    
858        /**
859         * Initialize the report document headers.
860         */
861        private void writeHeaders() {
862            createCashSweepExceptionReportWriterService.writeNewLines(1);
863            createCashSweepProcessedReportWriterService.writeNewLines(1);
864            createCashSweepExceptionReportWriterService.writeTableHeader(new TransactionDocumentExceptionReportLine());
865            createCashSweepProcessedReportWriterService.writeTableHeader(new TransactionDocumentTotalReportLine());
866        }
867    
868        /**
869         * This method...
870         * 
871         * @param assetDocument
872         */
873        private void updatePostingStats(EndowmentTaxLotLinesDocumentBase assetDocument) {
874    
875            String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(assetDocument.getClass());
876            ReportDocumentStatistics stats = statistics.get(documentTypeName);
877    
878            // If null that means there isn't one in the map, so create it and add
879            // it to the map.
880            if (stats == null) {
881                stats = new ReportDocumentStatistics(documentTypeName);
882                statistics.put(documentTypeName, stats);
883            }
884            stats.addNumberOfSourceTransactionLines(assetDocument.getSourceTransactionLines().size());
885            stats.addNumberOfTargetTransactionLines(assetDocument.getTargetTransactionLines().size());
886    
887            stats.incrementNumberOfDocuments();
888        }
889    
890        /**
891         * This method...
892         * 
893         * @param assetDocument
894         */
895        private void updateErrorStats(EndowmentTaxLotLinesDocumentBase assetDocument) {
896    
897            String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(assetDocument.getClass());
898            ReportDocumentStatistics stats = statistics.get(documentTypeName);
899    
900            // If null that means there isn't one in the map, so create it and add
901            // it to the map.
902            if (stats == null) {
903                stats = new ReportDocumentStatistics(documentTypeName);
904                statistics.put(documentTypeName, stats);
905            }
906    
907            stats.incrementNumberOfErrors();
908        }
909    
910        /**
911         * Write out the statistics.
912         */
913        private void writeStatistics() {
914    
915            for (Map.Entry<String, ReportDocumentStatistics> entry : statistics.entrySet()) {
916    
917                ReportDocumentStatistics stats = entry.getValue();
918    
919                createCashSweepProcessedReportWriterService.writeStatisticLine("%s Documents:", stats.getDocumentTypeName());
920                createCashSweepProcessedReportWriterService.writeStatisticLine("   Number of Documents Generated:            %d", stats.getNumberOfDocuments());
921                createCashSweepProcessedReportWriterService.writeStatisticLine("   Number of Transaction Lines Generated:    %d", stats.getTotalNumberOfTransactionLines());
922                createCashSweepProcessedReportWriterService.writeStatisticLine("   Number of Error Records Written:          %d", stats.getNumberOfErrors());
923                createCashSweepProcessedReportWriterService.writeStatisticLine("", "");
924    
925                createCashSweepExceptionReportWriterService.writeStatisticLine("%s Documents:", stats.getDocumentTypeName());
926                createCashSweepExceptionReportWriterService.writeStatisticLine("   Number of Documents Generated:            %d", stats.getNumberOfDocuments());
927                createCashSweepExceptionReportWriterService.writeStatisticLine("   Number of Transaction Lines Generated:    %d", stats.getTotalNumberOfTransactionLines());
928                createCashSweepExceptionReportWriterService.writeStatisticLine("   Number of Error Records Written:          %d", stats.getNumberOfErrors());
929                createCashSweepExceptionReportWriterService.writeStatisticLine("", "");
930            }
931    
932        }
933    
934        /**
935         * Sets the businessObjectService attribute value.
936         * 
937         * @param businessObjectService The businessObjectService to set.
938         */
939        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
940            this.businessObjectService = businessObjectService;
941        }
942    
943        /**
944         * Sets the kemService attribute value.
945         * 
946         * @param kemService The kemService to set.
947         */
948        public void setKemService(KEMService kemService) {
949            this.kemService = kemService;
950        }
951    
952        /**
953         * Sets the kemidService attribute value.
954         * 
955         * @param kemidService The kemidService to set.
956         */
957        public void setKemidService(KEMIDService kemidService) {
958            this.kemidService = kemidService;
959        }
960    
961        /**
962         * Sets the documentService attribute value.
963         * 
964         * @param documentService The documentService to set.
965         */
966        public void setDocumentService(DocumentService documentService) {
967            this.documentService = documentService;
968        }
969    
970        /**
971         * Sets the kualiRuleService attribute value.
972         * 
973         * @param kualiRuleService The kualiRuleService to set.
974         */
975        public void setKualiRuleService(KualiRuleService kualiRuleService) {
976            this.kualiRuleService = kualiRuleService;
977        }
978    
979        /**
980         * Sets the parameterService attribute value.
981         * 
982         * @param parameterService The parameterService to set.
983         */
984        public void setParameterService(ParameterService parameterService) {
985            this.parameterService = parameterService;
986        }
987    
988        /**
989         * Sets the createCashSweepExceptionReportWriterService attribute value.
990         * 
991         * @param createCashSweepExceptionReportWriterService The createCashSweepExceptionReportWriterService to set.
992         */
993        public void setCreateCashSweepExceptionReportWriterService(ReportWriterService createCashSweepExceptionReportWriterService) {
994            this.createCashSweepExceptionReportWriterService = createCashSweepExceptionReportWriterService;
995        }
996    
997        /**
998         * Sets the createCashSweepProcessedReportWriterService attribute value.
999         * 
1000         * @param createCashSweepProcessedReportWriterService The createCashSweepProcessedReportWriterService to set.
1001         */
1002        public void setCreateCashSweepProcessedReportWriterService(ReportWriterService createCashSweepProcessedReportWriterService) {
1003            this.createCashSweepProcessedReportWriterService = createCashSweepProcessedReportWriterService;
1004        }
1005    
1006        /**
1007         * Sets the configService attribute value.
1008         * 
1009         * @param configService The configService to set.
1010         */
1011        public void setConfigService(KualiConfigurationService configService) {
1012            this.configService = configService;
1013        }
1014    
1015        /**
1016         * Sets the cashSweepModelDao attribute value.
1017         * 
1018         * @param cashSweepModelDao The cashSweepModelDao to set.
1019         */
1020        public void setCashSweepModelDao(CashSweepModelDao cashSweepModelDao) {
1021            this.cashSweepModelDao = cashSweepModelDao;
1022        }
1023    
1024        /**
1025         * Sets the dataDictionaryService attribute value.
1026         * 
1027         * @param dataDictionaryService The dataDictionaryService to set.
1028         */
1029        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
1030            this.dataDictionaryService = dataDictionaryService;
1031        }
1032    
1033        /**
1034         * Sets the updateEaiTaxLotService attribute value.
1035         * 
1036         * @param updateEaiTaxLotService The updateEaiTaxLotService to set.
1037         */
1038        public void setUpdateEaiTaxLotService(UpdateAssetIncreaseDocumentTaxLotsService updateEaiTaxLotService) {
1039            this.updateEaiTaxLotService = updateEaiTaxLotService;
1040        }
1041    
1042        /**
1043         * Sets the updateEadTaxLotService attribute value.
1044         * 
1045         * @param updateEadTaxLotService The updateEadTaxLotService to set.
1046         */
1047        public void setUpdateEadTaxLotService(UpdateAssetDecreaseDocumentTaxLotsService updateEadTaxLotService) {
1048            this.updateEadTaxLotService = updateEadTaxLotService;
1049        }
1050    
1051    }