001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.endow.batch.service.impl;
017    
018    import java.text.MessageFormat;
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.Date;
022    import java.util.HashMap;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Set;
026    
027    import org.apache.commons.lang.StringUtils;
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.CreateRecurringCashTransferTransactionsStep;
032    import org.kuali.kfs.module.endow.batch.reporter.ReportDocumentStatistics;
033    import org.kuali.kfs.module.endow.batch.service.CreateRecurringCashTransferTransactionsService;
034    import org.kuali.kfs.module.endow.businessobject.EndowmentRecurringCashTransfer;
035    import org.kuali.kfs.module.endow.businessobject.EndowmentRecurringCashTransferGLTarget;
036    import org.kuali.kfs.module.endow.businessobject.EndowmentRecurringCashTransferKEMIDTarget;
037    import org.kuali.kfs.module.endow.businessobject.EndowmentSourceTransactionLine;
038    import org.kuali.kfs.module.endow.businessobject.EndowmentTargetTransactionLine;
039    import org.kuali.kfs.module.endow.businessobject.KemidCurrentCash;
040    import org.kuali.kfs.module.endow.businessobject.RecurringCashTransferTransactionDocumentExceptionReportLine;
041    import org.kuali.kfs.module.endow.businessobject.RecurringCashTransferTransactionDocumentTotalReportLine;
042    import org.kuali.kfs.module.endow.businessobject.TargetEndowmentAccountingLine;
043    import org.kuali.kfs.module.endow.businessobject.TransactionArchive;
044    import org.kuali.kfs.module.endow.document.CashTransferDocument;
045    import org.kuali.kfs.module.endow.document.EndowmentToGLTransferOfFundsDocument;
046    import org.kuali.kfs.module.endow.document.service.HoldingTaxLotService;
047    import org.kuali.kfs.module.endow.document.service.KEMService;
048    import org.kuali.kfs.module.endow.document.service.KemidCurrentCashService;
049    import org.kuali.kfs.module.endow.document.validation.event.AddEndowmentAccountingLineEvent;
050    import org.kuali.kfs.module.endow.document.validation.event.AddTransactionLineEvent;
051    import org.kuali.kfs.sys.service.ReportWriterService;
052    import org.kuali.rice.kew.exception.WorkflowException;
053    import org.kuali.rice.kns.bo.AdHocRouteRecipient;
054    import org.kuali.rice.kns.bo.DocumentHeader;
055    import org.kuali.rice.kns.rule.event.RouteDocumentEvent;
056    import org.kuali.rice.kns.service.BusinessObjectService;
057    import org.kuali.rice.kns.service.DataDictionaryService;
058    import org.kuali.rice.kns.service.DocumentService;
059    import org.kuali.rice.kns.service.KualiConfigurationService;
060    import org.kuali.rice.kns.service.KualiRuleService;
061    import org.kuali.rice.kns.service.ParameterService;
062    import org.kuali.rice.kns.util.ErrorMessage;
063    import org.kuali.rice.kns.util.GlobalVariables;
064    import org.kuali.rice.kns.util.KualiDecimal;
065    import org.kuali.rice.kns.util.MessageMap;
066    import org.kuali.rice.kns.util.ObjectUtils;
067    import org.springframework.transaction.annotation.Transactional;
068    
069    @Transactional
070    public class CreateRecurringCashTransferTransactionsServiceImpl implements CreateRecurringCashTransferTransactionsService {
071    
072        protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CreateCashSweepTransactionsServiceImpl.class);
073    
074        private Map<String, ReportDocumentStatistics> statistics = new HashMap<String, ReportDocumentStatistics>();
075        private BusinessObjectService businessObjectService;
076        private KEMService kemService;
077        private DocumentService documentService;
078        private ParameterService parameterService;
079        private KualiRuleService kualiRuleService;
080        private KemidCurrentCashService kemidCurrentCashService;
081        private HoldingTaxLotService holdingTaxLotService;
082    
083        private KualiConfigurationService configService;
084        private DataDictionaryService dataDictionaryService;
085    
086        private ReportWriterService recurringCashTransferTransactionsExceptionReportWriterService;
087        private ReportWriterService recurringCashTransferTransactionsTotalReportWriterService;
088    
089        private RecurringCashTransferTransactionDocumentExceptionReportLine exceptionReportLine = null;
090        private RecurringCashTransferTransactionDocumentTotalReportLine totalReportLine = null;
091    
092        private RecurringCashTransferTransactionDocumentTotalReportLine subTotalReportLine = null;
093        private RecurringCashTransferTransactionDocumentTotalReportLine allTotalReportLine = null;
094    
095        private boolean isFistTimeForWritingExceptionReport = true;
096        private boolean isFistTimeForWritingTotalReport = true;
097    
098        /**
099         * @see org.kuali.kfs.module.endow.batch.service.CreateRecurringCashTransferTransactionsService#createCashSweepTransactions()
100         */
101        public boolean createRecurringCashTransferTransactions() {
102    
103            LOG.info("Starting \"Create Recurring Cash Transfer Transactions\" batch job...");
104    
105            Collection<EndowmentRecurringCashTransfer> recurringCashTransfers = getAllRecurringCashTransferTransactionsForCurrentDate();
106    
107            // get lists of each case of transaction type
108            Collection<EndowmentRecurringCashTransfer> cashTransfers = new ArrayList();
109            Collection<EndowmentRecurringCashTransfer> glCashTransfers = new ArrayList();
110    
111            for (EndowmentRecurringCashTransfer endowmentRecurringCashTransfer : recurringCashTransfers) {
112                String sourceTransactionType = endowmentRecurringCashTransfer.getTransactionType();
113                if (sourceTransactionType.equals(EndowConstants.ENDOWMENT_CASH_TRANSFER_TRANSACTION_TYPE)) {
114                    cashTransfers.add(endowmentRecurringCashTransfer);
115                }
116                else {
117                    glCashTransfers.add(endowmentRecurringCashTransfer);
118                }
119            }
120    
121            // initiate total report
122            allTotalReportLine = new RecurringCashTransferTransactionDocumentTotalReportLine();
123    
124            // cashTransfers (ECT) exist, then calculate
125            if (!cashTransfers.isEmpty()) {
126                subTotalReportLine = new RecurringCashTransferTransactionDocumentTotalReportLine();
127                calculateCashTransfers(cashTransfers);
128                writeSubTotalReportLine();
129            }
130    
131            // cashTransfers (EGLT) exist, then calculate
132            if (!glCashTransfers.isEmpty()) {
133                subTotalReportLine = new RecurringCashTransferTransactionDocumentTotalReportLine();
134                calculateGlCashTransfers(glCashTransfers);
135                writeSubTotalReportLine();
136            }
137    
138            writeGrandTotalReportLine();
139    
140            writeStatistics();
141    
142            LOG.info("Finished \"Create Recurring Cash Transfer Transactions\" batch job!");
143    
144            return true;
145        }
146    
147        // calculate when ECT
148        protected void calculateCashTransfers(Collection<EndowmentRecurringCashTransfer> cashTransfers) {
149    
150            for (EndowmentRecurringCashTransfer cashTransfer : cashTransfers) {
151                String sourceKemid = cashTransfer.getSourceKemid();
152                String transferNumber = cashTransfer.getTransferNumber();
153                Date lastProcessDate = cashTransfer.getLastProcessDate();
154    
155                KualiDecimal totalSourceTransaction = KualiDecimal.ZERO;
156                KualiDecimal totalTargetTransaction = KualiDecimal.ZERO;
157                KualiDecimal cashEquivalents = calculateTotalCashEquivalents(cashTransfer);
158    
159                CashTransferDocument cashTransferDoc = createCashTransferDocument(sourceKemid, transferNumber);
160                List<EndowmentRecurringCashTransferKEMIDTarget> kemidTargets = cashTransfer.getKemidTarget();
161    
162    
163                for (EndowmentRecurringCashTransferKEMIDTarget kemidTarget : kemidTargets) {
164                    KualiDecimal transactionAmount = calculateCashTransferTransactionAmount(kemidTarget, cashEquivalents, sourceKemid, lastProcessDate);
165    
166                    // from spec, total of source and target are same, but totalTargetTransaction will be useful for implementing
167                    // total part.
168                    totalSourceTransaction = totalSourceTransaction.add(transactionAmount);
169                    totalTargetTransaction = totalTargetTransaction.add(transactionAmount);
170    
171                    // add target line
172                    boolean addTransactionLine = addKemidTargetTransactionLine(kemidTarget, cashTransferDoc, transactionAmount, sourceKemid, transferNumber);
173                    if (!addTransactionLine) {
174                        totalTargetTransaction = totalTargetTransaction.subtract(transactionAmount);
175                        totalSourceTransaction = totalSourceTransaction.subtract(transactionAmount);
176                    }
177                }
178                // check ALLOW_NEGATIVE_BALANCE_IND and if it is ok then route
179                boolean allowNegativeBalanceInd = parameterService.getIndicatorParameter(CreateRecurringCashTransferTransactionsStep.class, EndowParameterKeyConstants.ALLOW_NEGATIVE_BALANCE_IND);
180                // boolean allowNegativeBalanceInd = true;
181                if (!allowNegativeBalanceInd && totalSourceTransaction.isLessEqual(cashEquivalents)) {
182                    // report exception
183                    // constants??
184                    writeExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER, sourceKemid, transferNumber, "", "calculated source total is not less than the total available cash equivalents");
185    
186                }
187                else {
188                    // add source line
189                    addSourceTransactionLineForCashTransferDoc(cashTransfer, cashTransferDoc, totalSourceTransaction);
190                    if (routeCashTransferDoc(cashTransferDoc, sourceKemid, transferNumber, totalTargetTransaction)) {
191                        subTotalReportLine.incrementTotalTransferAmount(totalTargetTransaction);
192                        subTotalReportLine.incrementTargetLinesGenerated(cashTransferDoc.getTargetTransactionLines().size());
193    
194                        updatePostingStatsForCashTransferDoc(cashTransferDoc);
195                    }
196                }
197            }
198            allTotalReportLine.incrementTotalTransferAmount(subTotalReportLine.getTotalTransferAmount());
199            allTotalReportLine.incrementTargetLinesGenerated(subTotalReportLine.getTargetLinesGenerated());
200        }
201    
202        protected KualiDecimal calculateCashTransferTransactionAmount(EndowmentRecurringCashTransferKEMIDTarget kemidTarget, KualiDecimal cashEquivalents, String sourceKemid, Date lastProcessDate) {
203    
204            // check if it is calculation scenario 1
205            if (ObjectUtils.isNotNull(kemidTarget.getTargetPercent()) && ObjectUtils.isNotNull(kemidTarget.getTargetUseEtranCode())) {
206                // retrieves transactionArchives and calculated source cash
207                KualiDecimal totalCashIncomeEtranCode = KualiDecimal.ZERO;
208    
209                List<TransactionArchive> transactionArchiveList = retrieveTransactionArchives(sourceKemid, lastProcessDate, kemidTarget.getTargetUseEtranCode());
210                // if transactionArchives exist, then calculate total income and total percent of same etran code in target
211                if (transactionArchiveList.size() > 0) {
212                    // need to change name...like cashIncrease..
213                    totalCashIncomeEtranCode = calculateTotalIncomeTransactionArchives(transactionArchiveList);
214                }
215                return totalCashIncomeEtranCode.multiply(kemidTarget.getTargetPercent());
216            }
217            // check if it is calculation scenario 2
218            else if (ObjectUtils.isNotNull(kemidTarget.getTargetPercent())) {
219                return cashEquivalents.multiply(kemidTarget.getTargetPercent());
220            }
221            // check if it is calculation scenario 3
222            else if (ObjectUtils.isNotNull(kemidTarget.getTargetAmount())) {
223                return kemidTarget.getTargetAmount();
224            }
225            else
226                return KualiDecimal.ZERO;
227        }
228    
229    
230        // calculate when EGLT
231        protected void calculateGlCashTransfers(Collection<EndowmentRecurringCashTransfer> glCashTransfers) {
232            for (EndowmentRecurringCashTransfer glCashTransfer : glCashTransfers) {
233    
234                String sourceTransactionType = glCashTransfer.getTransactionType();
235                String sourceKemid = glCashTransfer.getSourceKemid();
236                String transferNumber = glCashTransfer.getTransferNumber();
237                Date lastProcessDate = glCashTransfer.getLastProcessDate();
238    
239                KualiDecimal totalSourceTransaction = KualiDecimal.ZERO;
240                KualiDecimal totalTargetTransaction = KualiDecimal.ZERO;
241                KualiDecimal cashEquivalents = calculateTotalCashEquivalents(glCashTransfer);
242    
243                EndowmentToGLTransferOfFundsDocument gLTransferOfFundsDocument = createEndowmentToGLTransferOfFundsDocument(sourceKemid, transferNumber);
244                List<EndowmentRecurringCashTransferGLTarget> glTargets = glCashTransfer.getGlTarget();
245    
246                for (EndowmentRecurringCashTransferGLTarget glTarget : glTargets) {
247                    KualiDecimal transactionAmount = calculateGlCashTransferTransactionAmount(glTarget, cashEquivalents, sourceKemid, lastProcessDate);
248    
249                    totalSourceTransaction = totalSourceTransaction.add(transactionAmount);
250                    totalTargetTransaction = totalTargetTransaction.add(transactionAmount);
251    
252                    // add target line
253                    boolean addTransactionLine = addGlTransactionLine(glTarget, gLTransferOfFundsDocument, transactionAmount, sourceKemid, transferNumber);
254                    if (!addTransactionLine) {
255                        totalTargetTransaction = totalTargetTransaction.subtract(transactionAmount);
256                        totalSourceTransaction = totalSourceTransaction.subtract(transactionAmount);
257                    }
258                }
259    
260                // check ALLOW_NEGATIVE_BALANCE_IND and if it is ok then route
261                boolean allowNegativeBalanceInd = parameterService.getIndicatorParameter(CreateRecurringCashTransferTransactionsStep.class, EndowParameterKeyConstants.ALLOW_NEGATIVE_BALANCE_IND);
262                // boolean allowNegativeBalanceInd = true;
263                if (!allowNegativeBalanceInd && totalSourceTransaction.isLessEqual(cashEquivalents)) {
264                    // report exception
265                    writeExceptionReportLine(EndowConstants.ENDOWMENT_GENERAL_LEDGER_CASH_TRANSFER_TRANSACTION_TYPE, sourceKemid, transferNumber, "calculated source total is less than the total available cash equivalents");
266                }
267                else {
268                    // add source... line
269                    addSourceTransactionLineForGLTransferOfFundsDocument(glCashTransfer, gLTransferOfFundsDocument, totalSourceTransaction);
270                    // route doc
271                    if (routeGLTransferOfFundsDocument(gLTransferOfFundsDocument, sourceKemid, transferNumber, totalTargetTransaction)) {
272    
273                        subTotalReportLine.incrementTotalTransferAmount(totalTargetTransaction);
274                        subTotalReportLine.incrementTargetLinesGenerated(gLTransferOfFundsDocument.getTargetAccountingLines().size());
275    
276                        updatePostingStatsForGlTransferDoc(gLTransferOfFundsDocument);
277                    }
278                }
279            }
280            allTotalReportLine.incrementTotalTransferAmount(subTotalReportLine.getTotalTransferAmount());
281            allTotalReportLine.incrementTargetLinesGenerated(subTotalReportLine.getTargetLinesGenerated());
282        }
283    
284        protected KualiDecimal calculateGlCashTransferTransactionAmount(EndowmentRecurringCashTransferGLTarget glTarget, KualiDecimal cashEquivalents, String sourceKemid, Date lastProcessDate) {
285            // check if it is calculation scenario 1
286            if (ObjectUtils.isNotNull(glTarget.getTargetPercent()) && ObjectUtils.isNotNull(glTarget.getTargetUseEtranCode())) {
287                // retrieves transactionArchives and calculated source cash
288                KualiDecimal totalCashIncomeEtranCode = KualiDecimal.ZERO;
289    
290                List<TransactionArchive> transactionArchiveList = retrieveTransactionArchives(sourceKemid, lastProcessDate, glTarget.getTargetUseEtranCode());
291                // if transactionArchives exist, then calculate total income and total percent of same etran code in target
292                if (transactionArchiveList.size() > 0) {
293                    totalCashIncomeEtranCode = calculateTotalIncomeTransactionArchives(transactionArchiveList);
294                }
295                return totalCashIncomeEtranCode.multiply(glTarget.getTargetPercent());
296            }
297            // check if it is calculation scenario 2
298            else if (ObjectUtils.isNotNull(glTarget.getTargetPercent())) {
299                return cashEquivalents.multiply(glTarget.getTargetPercent());
300            }
301            // check if it is calculation scenario 3
302            else if (ObjectUtils.isNotNull(glTarget.getTargetFdocLineAmount())) {
303                return glTarget.getTargetFdocLineAmount();
304            }
305            else
306                return KualiDecimal.ZERO;
307        }
308    
309        protected KualiDecimal calculateTotalCashEquivalents(EndowmentRecurringCashTransfer endowmentRecurringCashTransfer) {
310            // spec 10.2
311            KualiDecimal totalCashEquivalents = KualiDecimal.ZERO;
312            String kemid = endowmentRecurringCashTransfer.getSourceKemid();
313            KemidCurrentCash currentCash = kemidCurrentCashService.getByPrimaryKey(kemid);
314            if (endowmentRecurringCashTransfer.getSourceIncomeOrPrincipal().equals(EndowConstants.EndowmentTransactionTypeCodes.INCOME_TYPE_CODE)) {
315                totalCashEquivalents = currentCash.getCurrentIncomeCash().add(new KualiDecimal(holdingTaxLotService.getMarketValueForCashEquivalentsForAvailableIncomeCash(kemid)));
316            }
317            else {
318                totalCashEquivalents = currentCash.getCurrentPrincipalCash().add(new KualiDecimal(holdingTaxLotService.getMarketValueForCashEquivalentsForAvailablePrincipalCash(kemid)));
319            }
320            return totalCashEquivalents;
321        }
322    
323        /**
324         * This method retrieves all the recurring cash transfer transactions whose frequency code matches the current date.
325         * 
326         * @return List of CashSweepModel business objects
327         */
328        protected Collection<EndowmentRecurringCashTransfer> getAllRecurringCashTransferTransactionsForCurrentDate() {
329            LOG.info("Getting all EndowmentRecurringCashTransfer with Next process date = current date");
330    
331            List<EndowmentRecurringCashTransfer> endowmentRecurringCashTransfer = new ArrayList<EndowmentRecurringCashTransfer>();
332            Date currentDate = kemService.getCurrentDate();
333            Map fieldValues = new HashMap();
334            fieldValues.put(EndowPropertyConstants.ENDOWMENT_RECURRING_CASH_TRANSF_NEXT_PROC_DATE, currentDate);
335            Collection<EndowmentRecurringCashTransfer> recurringCashTransfers = businessObjectService.findMatching(EndowmentRecurringCashTransfer.class, fieldValues);
336            LOG.info("Number of EndowmentRecurringCashTransfer with Next process date = current date" + recurringCashTransfers.size());
337            return recurringCashTransfers;
338        }
339    
340        // protected List getSortFieldList(){
341        // List returnList = new Arraylist();
342        // returnList.add(EndowPropertyConstants.ENDOWMENT_RECURRING_CASH_TRANSF_TRANSACTION_TYPE);
343        // returnList.add(EndowPropertyConstants.ENDOWMENT_RECURRING_CASH_TRANSF_TRANSACTION_TYPE);
344        //        
345        // return returnList();
346        // }
347    
348        // List<EndowmentRecurringCashTransfer> recurringCashTransfers = (List)
349        // businessObjectService.findMatching(EndowmentRecurringCashTransfer.class, fieldValues);
350        // //Collection<EndowmentRecurringCashTransfer> recurringCashTransfers =
351        // businessObjectService.findMatching(EndowmentRecurringCashTransfer.class, fieldValues);
352        // List sortFieldList = new getSortFieldList();
353        // Collections.sort(recurringCashTransfers, new BeanPropertyComparator(getDefaultSortColumns(), true));
354    
355    
356        // spec 6.2 3.1.a
357        protected List<TransactionArchive> retrieveTransactionArchives(String sourceKemid, Date lastProcessDate, String targetEtranCode) {
358            KualiDecimal totalCashIncomeEtranCode = KualiDecimal.ZERO;
359    
360            List<TransactionArchive> transactionArchiveList = null;
361            Map fieldValues = new HashMap();
362            fieldValues.put(EndowPropertyConstants.KEMID, sourceKemid);
363            fieldValues.put(EndowPropertyConstants.TRANSACTION_LINE_ENDOWMENT_TRANSACTION_CODE, targetEtranCode);
364            transactionArchiveList = (List) businessObjectService.findMatching(TransactionArchive.class, fieldValues);
365            for (TransactionArchive transactionArchive : transactionArchiveList) {
366                if (!transactionArchive.getPostedDate().after(lastProcessDate)) {
367                    transactionArchiveList.remove(transactionArchive);
368                }
369            }
370            return transactionArchiveList;
371        }
372    
373        // spec 6.2 3.1.a
374        protected KualiDecimal calculateTotalIncomeTransactionArchives(List<TransactionArchive> transactionArchiveList) {
375            KualiDecimal totalCashIncomeEtranCode = KualiDecimal.ZERO;
376            for (TransactionArchive transactionArchive : transactionArchiveList) {
377                if (transactionArchive.getIncomePrincipalIndicatorCode().equals(EndowConstants.EndowmentTransactionTypeCodes.INCOME_TYPE_CODE)) {
378                    totalCashIncomeEtranCode = totalCashIncomeEtranCode.add(new KualiDecimal(transactionArchive.getIncomeCashAmount()));
379                }
380                else {
381                    totalCashIncomeEtranCode = totalCashIncomeEtranCode.add(new KualiDecimal(transactionArchive.getPrincipalCashAmount()));
382                }
383            }
384            return totalCashIncomeEtranCode;
385        }
386    
387        /**
388         * This method...
389         * 
390         * @param assetIncreaseDoc
391         * @param assetSaleOffsetCode
392         * @param kemid
393         * @param cashLimit
394         * @param currentCash
395         */
396    
397        protected void addSourceTransactionLineForCashTransferDoc(EndowmentRecurringCashTransfer endowmentRecurringCashTransfer, CashTransferDocument cashTransferDoc, KualiDecimal totalAmount) {
398            boolean rulesPassed = true;
399            EndowmentSourceTransactionLine transactionLine = new EndowmentSourceTransactionLine();
400            transactionLine.setTransactionLineTypeCode(EndowConstants.TRANSACTION_LINE_TYPE_SOURCE);
401            transactionLine.setKemid(endowmentRecurringCashTransfer.getSourceKemid());
402            transactionLine.setEtranCode(endowmentRecurringCashTransfer.getSourceEtranCode());
403            transactionLine.setTransactionLineDescription(endowmentRecurringCashTransfer.getSourceLineDescription());
404            transactionLine.setTransactionIPIndicatorCode(endowmentRecurringCashTransfer.getSourceIncomeOrPrincipal());
405            transactionLine.setTransactionAmount(totalAmount);
406    
407            rulesPassed = kualiRuleService.applyRules(new AddTransactionLineEvent(EndowConstants.NEW_SOURCE_TRAN_LINE_PROPERTY_NAME, cashTransferDoc, transactionLine));
408    
409            if (rulesPassed) {
410                cashTransferDoc.addSourceTransactionLine(transactionLine);
411            }
412            else {
413                // report to error
414                writeExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER, endowmentRecurringCashTransfer.getSourceKemid(), endowmentRecurringCashTransfer.getTransferNumber(), EndowConstants.EXISTING_SOURCE_TRAN_LINE_PROPERTY_NAME);
415                String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(cashTransferDoc.getClass());
416                updateErrorStats(documentTypeName);
417            }
418        }
419    
420        protected void addSourceTransactionLineForGLTransferOfFundsDocument(EndowmentRecurringCashTransfer endowmentRecurringCashTransfer, EndowmentToGLTransferOfFundsDocument gLTransferOfFundsDocument, KualiDecimal totalAmount) {
421            boolean rulesPassed = true;
422            EndowmentSourceTransactionLine transactionLine = new EndowmentSourceTransactionLine();
423            transactionLine.setTransactionLineTypeCode(EndowConstants.TRANSACTION_LINE_TYPE_SOURCE);
424            transactionLine.setKemid(endowmentRecurringCashTransfer.getSourceKemid());
425            transactionLine.setEtranCode(endowmentRecurringCashTransfer.getSourceEtranCode());
426            transactionLine.setTransactionLineDescription(endowmentRecurringCashTransfer.getSourceLineDescription());
427            transactionLine.setTransactionIPIndicatorCode(endowmentRecurringCashTransfer.getSourceIncomeOrPrincipal());
428            transactionLine.setTransactionAmount(totalAmount);
429    
430            rulesPassed = kualiRuleService.applyRules(new AddTransactionLineEvent(EndowConstants.NEW_SOURCE_TRAN_LINE_PROPERTY_NAME, gLTransferOfFundsDocument, transactionLine));
431    
432            if (rulesPassed) {
433                gLTransferOfFundsDocument.addSourceTransactionLine(transactionLine);
434            }
435            else {
436                // report to error
437                writeExceptionReportLine(EndowConstants.ENDOWMENT_GENERAL_LEDGER_CASH_TRANSFER_TRANSACTION_TYPE, endowmentRecurringCashTransfer.getSourceKemid(), endowmentRecurringCashTransfer.getTransferNumber(), EndowConstants.EXISTING_SOURCE_TRAN_LINE_PROPERTY_NAME);
438                String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(gLTransferOfFundsDocument.getClass());
439                updateErrorStats(documentTypeName);
440            }
441        }
442    
443        protected boolean addKemidTargetTransactionLine(EndowmentRecurringCashTransferKEMIDTarget endowmentRecurringCashTransferKEMIDTarget, CashTransferDocument cashTransferDoc, KualiDecimal totalAmount, String sourceKemid, String transferNumber) {
444            boolean rulesPassed = true;
445            EndowmentTargetTransactionLine transactionLine = new EndowmentTargetTransactionLine();
446            // set all necessary fields
447            transactionLine.setTransactionLineTypeCode(EndowConstants.TRANSACTION_LINE_TYPE_TARGET);
448            transactionLine.setKemid(endowmentRecurringCashTransferKEMIDTarget.getTargetKemid());
449            transactionLine.setEtranCode(endowmentRecurringCashTransferKEMIDTarget.getTargetEtranCode());
450            transactionLine.setTransactionLineDescription(endowmentRecurringCashTransferKEMIDTarget.getTargetLineDescription());
451            transactionLine.setTransactionIPIndicatorCode(endowmentRecurringCashTransferKEMIDTarget.getTargetIncomeOrPrincipal());
452            transactionLine.setTransactionAmount(totalAmount);
453    
454            // check rules
455            rulesPassed = kualiRuleService.applyRules(new AddTransactionLineEvent(EndowConstants.NEW_TARGET_TRAN_LINE_PROPERTY_NAME, cashTransferDoc, transactionLine));
456    
457            if (rulesPassed) {
458                cashTransferDoc.addTargetTransactionLine(transactionLine);
459            }
460            else {
461                // report to error
462                writeExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER, sourceKemid, endowmentRecurringCashTransferKEMIDTarget.getTransferNumber(), endowmentRecurringCashTransferKEMIDTarget.getTargetSequenceNumber().toString());
463                String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(cashTransferDoc.getClass());
464                updateErrorStats(documentTypeName);
465            }
466    
467            return rulesPassed;
468        }
469    
470        protected boolean addGlTransactionLine(EndowmentRecurringCashTransferGLTarget endowmentRecurringCashTransferGLTarget, EndowmentToGLTransferOfFundsDocument endowmentToGLTransferOfFundsDocument, KualiDecimal totalAmount, String sourceKemid, String transferNumber) {
471            boolean rulesPassed = true;
472            TargetEndowmentAccountingLine endowmentAccountingLine = new TargetEndowmentAccountingLine();
473            // set all necessary fields
474            endowmentAccountingLine.setChartOfAccountsCode(endowmentRecurringCashTransferGLTarget.getTargetChartOfAccountsCode());
475            endowmentAccountingLine.setAccountNumber(endowmentRecurringCashTransferGLTarget.getTargetAccountsNumber());
476            endowmentAccountingLine.setFinancialObjectCode(endowmentRecurringCashTransferGLTarget.getTargetFinancialObjectCode());
477            endowmentAccountingLine.setAmount(totalAmount);
478            // number.setScale(digit, BigDecimal.ROUND_HALF_UP)
479    
480            // check rules
481            rulesPassed = kualiRuleService.applyRules(new AddEndowmentAccountingLineEvent(EndowConstants.NEW_TARGET_ACC_LINE_PROPERTY_NAME, endowmentToGLTransferOfFundsDocument, endowmentAccountingLine));
482    
483            if (rulesPassed) {
484                endowmentToGLTransferOfFundsDocument.addTargetAccountingLine(endowmentAccountingLine);
485            }
486            else {
487                // report to error
488                writeExceptionReportLine(EndowConstants.ENDOWMENT_GENERAL_LEDGER_CASH_TRANSFER_TRANSACTION_TYPE, sourceKemid, endowmentRecurringCashTransferGLTarget.getTransferNumber(), endowmentRecurringCashTransferGLTarget.getTargetSequenceNumber().toString());
489                String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(endowmentToGLTransferOfFundsDocument.getClass());
490                updateErrorStats(documentTypeName);
491    
492            }
493    
494            return rulesPassed;
495        }
496    
497        protected CashTransferDocument createCashTransferDocument(String sourceKemid, String transferNumber) {
498    
499            CashTransferDocument cashTransferDoc = null;
500            try {
501                cashTransferDoc = (CashTransferDocument) documentService.getNewDocument(CashTransferDocument.class);
502    
503                // Set values for doc
504                DocumentHeader docHeader = cashTransferDoc.getDocumentHeader();
505                String description = parameterService.getParameterValue(CreateRecurringCashTransferTransactionsStep.class, EndowParameterKeyConstants.DESCRIPTION);
506                docHeader.setDocumentDescription(description);
507                cashTransferDoc.setDocumentHeader(docHeader);
508                cashTransferDoc.setTransactionSourceTypeCode(EndowConstants.TransactionSourceTypeCode.RECURRING);
509            }
510            catch (WorkflowException ex) {
511                LOG.error(ex.getLocalizedMessage());
512                writeExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER, sourceKemid, transferNumber, "", ex.getLocalizedMessage() + " from createCashTransferDocument()");
513            }
514    
515            return cashTransferDoc;
516        }
517    
518        protected boolean routeCashTransferDoc(CashTransferDocument cashTransferDoc, String sourceKemid, String transferNumber, KualiDecimal totalAmount) {
519            boolean routeSuccessInd = true;
520            boolean rulesPassed = kualiRuleService.applyRules(new RouteDocumentEvent(cashTransferDoc));
521    
522            if (rulesPassed) {
523                // no adhoc recipient need to add when submit doc. doc will route to the doc uploader, i.e. initiator automtically.
524                List<AdHocRouteRecipient> adHocRoutingRecipients = new ArrayList<AdHocRouteRecipient>();
525                try {
526                    cashTransferDoc.setNoRouteIndicator(getNoRouteParameterAsBoolean());
527                    documentService.routeDocument(cashTransferDoc, "Route CashIncreaseDocument", adHocRoutingRecipients);
528                    writeTotalReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER, cashTransferDoc.getDocumentNumber(), transferNumber, sourceKemid, new Integer(cashTransferDoc.getTargetTransactionLines().size()), totalAmount);
529    
530                }
531                catch (WorkflowException ex) {
532                    LOG.error(ex.getLocalizedMessage());
533                    writeExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER, sourceKemid, transferNumber, "", ex.getLocalizedMessage() + " from routeCashTransferDoc()");
534                    String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(cashTransferDoc.getClass());
535                    updateErrorStats(documentTypeName);
536                    routeSuccessInd = false;
537                }
538            }
539            else {
540                // report to error
541                writeExceptionReportLine(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER, sourceKemid, transferNumber, "");
542                String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(cashTransferDoc.getClass());
543                updateErrorStats(documentTypeName);
544                routeSuccessInd = false;
545            }
546    
547            return routeSuccessInd;
548        }
549    
550        protected boolean routeGLTransferOfFundsDocument(EndowmentToGLTransferOfFundsDocument gLTransferOfFundsDocument, String sourceKemid, String transferNumber, KualiDecimal totalAmount) {
551            boolean routeSuccessInd = true;
552            boolean rulesPassed = kualiRuleService.applyRules(new RouteDocumentEvent(gLTransferOfFundsDocument));
553    
554            if (rulesPassed) {
555                // no adhoc recipient need to add when submit doc. doc will route to the doc uploader, i.e. initiator automtically.
556                List<AdHocRouteRecipient> adHocRoutingRecipients = new ArrayList<AdHocRouteRecipient>();
557                try {
558                    gLTransferOfFundsDocument.setNoRouteIndicator(getNoRouteParameterAsBoolean());
559                    documentService.routeDocument(gLTransferOfFundsDocument, "Route gLTransferOfFundsDocument", adHocRoutingRecipients);
560    
561                    writeTotalReportLine(EndowConstants.ENDOWMENT_GENERAL_LEDGER_CASH_TRANSFER_TRANSACTION_TYPE, gLTransferOfFundsDocument.getDocumentNumber(), transferNumber, sourceKemid, new Integer(gLTransferOfFundsDocument.getTargetAccountingLines().size()), totalAmount);
562    
563                }
564                catch (WorkflowException ex) {
565                    LOG.error(ex.getLocalizedMessage());
566    
567                    writeExceptionReportLine(EndowConstants.ENDOWMENT_GENERAL_LEDGER_CASH_TRANSFER_TRANSACTION_TYPE, sourceKemid, transferNumber, "", ex.getLocalizedMessage() + " from routeGLTransferOfFundsDocument()");
568                    String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(gLTransferOfFundsDocument.getClass());
569                    updateErrorStats(documentTypeName);
570                    routeSuccessInd = false;
571                }
572            }
573            else {
574                // report to error
575                writeExceptionReportLine(EndowConstants.ENDOWMENT_GENERAL_LEDGER_CASH_TRANSFER_TRANSACTION_TYPE, sourceKemid, transferNumber, "");
576                String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(gLTransferOfFundsDocument.getClass());
577                updateErrorStats(documentTypeName);
578                routeSuccessInd = false;
579            }
580    
581            return routeSuccessInd;
582        }
583    
584        protected EndowmentToGLTransferOfFundsDocument createEndowmentToGLTransferOfFundsDocument(String sourceKemid, String transferNumber) {
585            EndowmentToGLTransferOfFundsDocument endowmentToGLTransferOfFundsDocument = null;
586            try {
587                endowmentToGLTransferOfFundsDocument = (EndowmentToGLTransferOfFundsDocument) documentService.getNewDocument(EndowmentToGLTransferOfFundsDocument.class);
588    
589                // Set values for doc
590                DocumentHeader docHeader = endowmentToGLTransferOfFundsDocument.getDocumentHeader();
591                String description = parameterService.getParameterValue(CreateRecurringCashTransferTransactionsStep.class, EndowParameterKeyConstants.DESCRIPTION);
592                docHeader.setDocumentDescription(description);
593                endowmentToGLTransferOfFundsDocument.setDocumentHeader(docHeader);
594                endowmentToGLTransferOfFundsDocument.setTransactionSourceTypeCode(EndowConstants.TransactionSourceTypeCode.RECURRING);
595            }
596            catch (WorkflowException ex) {
597                LOG.error(ex.getLocalizedMessage());
598    
599                writeExceptionReportLine(EndowConstants.ENDOWMENT_GENERAL_LEDGER_CASH_TRANSFER_TRANSACTION_TYPE, sourceKemid, transferNumber, "", ex.getLocalizedMessage() + " from createEndowmentToGLTransferOfFundsDocument()");
600            }
601    
602            return endowmentToGLTransferOfFundsDocument;
603    
604        }
605    
606        protected boolean getNoRouteParameterAsBoolean() {
607            String noRouteIndParm = parameterService.getParameterValue(CreateRecurringCashTransferTransactionsStep.class, EndowParameterKeyConstants.NO_ROUTE_IND);
608            if (noRouteIndParm.equals(EndowConstants.YES)) {
609                return true;
610            }
611            else
612                return false;
613        }
614    
615        /**
616         * Extracts errors for error report writing.
617         * 
618         * @return a list of error messages
619         */
620        protected List<String> extractGlobalVariableErrors() {
621            List<String> result = new ArrayList<String>();
622    
623            MessageMap errorMap = GlobalVariables.getMessageMap();
624    
625            Set<String> errorKeys = errorMap.keySet();
626            List<ErrorMessage> errorMessages = null;
627            Object[] messageParams;
628            String errorKeyString;
629            String errorString;
630    
631            for (String errorProperty : errorKeys) {
632                errorMessages = (List<ErrorMessage>) errorMap.get(errorProperty);
633                for (ErrorMessage errorMessage : errorMessages) {
634                    errorKeyString = configService.getPropertyString(errorMessage.getErrorKey());
635                    messageParams = errorMessage.getMessageParameters();
636    
637                    // MessageFormat.format only seems to replace one
638                    // per pass, so I just keep beating on it until all are gone.
639                    if (StringUtils.isBlank(errorKeyString)) {
640                        errorString = errorMessage.getErrorKey();
641                    }
642                    else {
643                        errorString = errorKeyString;
644                    }
645                    System.out.println(errorString);
646                    while (errorString.matches("^.*\\{\\d\\}.*$")) {
647                        errorString = MessageFormat.format(errorString, messageParams);
648                    }
649                    result.add(errorString);
650                }
651            }
652    
653            // clear the stuff out of globalvars, as we need to reformat it and put it back
654            GlobalVariables.getMessageMap().clear();
655            return result;
656        }
657    
658        protected void writeExceptionReportLine(String documentType, String sourceKemid, String transferNumber, String targetSeqNumber) {
659            writeExceptionReportLine(documentType, sourceKemid, transferNumber, targetSeqNumber, "");
660        }
661    
662        protected void writeExceptionReportLine(String documentType, String sourceKemid, String transferNumber, String targetSeqNumber, String reason) {
663            // write an exception line when a transaction line fails to pass the validation.
664            exceptionReportLine = new RecurringCashTransferTransactionDocumentExceptionReportLine(documentType, sourceKemid, transferNumber, targetSeqNumber);
665    
666            if (isFistTimeForWritingExceptionReport) {
667                recurringCashTransferTransactionsExceptionReportWriterService.writeTableHeader(exceptionReportLine);
668                isFistTimeForWritingExceptionReport = false;
669            }
670            recurringCashTransferTransactionsExceptionReportWriterService.writeTableRow(exceptionReportLine);
671    
672            if (StringUtils.isBlank(reason)) {
673                List<String> errorMessages = extractGlobalVariableErrors();
674                for (String errorMessage : errorMessages) {
675                    recurringCashTransferTransactionsExceptionReportWriterService.writeFormattedMessageLine("Reason:  %s", errorMessage);
676                    recurringCashTransferTransactionsExceptionReportWriterService.writeNewLines(1);
677                }
678            }
679            else {
680                recurringCashTransferTransactionsExceptionReportWriterService.writeFormattedMessageLine("Reason:  %s", reason);
681                recurringCashTransferTransactionsExceptionReportWriterService.writeNewLines(1);
682            }
683        }
684    
685        protected void writeTotalReportLine(String documentType, String documentId, String transferNumber, String sourcekemid, Integer targetLinesGenerated, KualiDecimal totalTransferAmount) {
686            totalReportLine = new RecurringCashTransferTransactionDocumentTotalReportLine(documentType, documentId, transferNumber, sourcekemid, targetLinesGenerated, totalTransferAmount);
687    
688            if (isFistTimeForWritingTotalReport) {
689                recurringCashTransferTransactionsTotalReportWriterService.writeTableHeader(totalReportLine);
690                isFistTimeForWritingTotalReport = false;
691            }
692    
693            recurringCashTransferTransactionsTotalReportWriterService.writeTableRow(totalReportLine);
694        }
695    
696        protected void writeSubTotalReportLine() {
697            writeTotalReportLine("SubTotal", "", "", "", subTotalReportLine.getTargetLinesGenerated(), subTotalReportLine.getTotalTransferAmount());
698            recurringCashTransferTransactionsTotalReportWriterService.writeNewLines(1);
699        }
700    
701        protected void writeGrandTotalReportLine() {
702            writeTotalReportLine("Total", "", "", "", allTotalReportLine.getTargetLinesGenerated(), allTotalReportLine.getTotalTransferAmount());
703        }
704    
705        /**
706         * Sets the businessObjectService attribute value.
707         * 
708         * @param businessObjectService The businessObjectService to set.
709         */
710        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
711            this.businessObjectService = businessObjectService;
712        }
713    
714        /**
715         * Sets the kemService attribute value.
716         * 
717         * @param kemService The kemService to set.
718         */
719        public void setKemService(KEMService kemService) {
720            this.kemService = kemService;
721        }
722    
723        /**
724         * Sets the documentService attribute value.
725         * 
726         * @param documentService The documentService to set.
727         */
728        public void setDocumentService(DocumentService documentService) {
729            this.documentService = documentService;
730        }
731    
732        /**
733         * Sets the parameterService attribute value.
734         * 
735         * @param parameterService The parameterService to set.
736         */
737        public void setParameterService(ParameterService parameterService) {
738            this.parameterService = parameterService;
739        }
740    
741        /**
742         * Sets the kualiRuleService attribute value.
743         * 
744         * @param kualiRuleService The kualiRuleService to set.
745         */
746        public void setKualiRuleService(KualiRuleService kualiRuleService) {
747            this.kualiRuleService = kualiRuleService;
748        }
749    
750        /**
751         * Sets the kemidCurrentCashOpenRecordsService
752         * 
753         * @param kemidCurrentCashOpenRecordsService The kemidCurrentCashOpenRecordsService to set.
754         */
755        public void setKemidCurrentCashService(KemidCurrentCashService kemidCurrentCashService) {
756            this.kemidCurrentCashService = kemidCurrentCashService;
757        }
758    
759        public void setHoldingTaxLotService(HoldingTaxLotService holdingTaxLotService) {
760            this.holdingTaxLotService = holdingTaxLotService;
761        }
762    
763        public void setRecurringCashTransferTransactionsExceptionReportWriterService(ReportWriterService recurringCashTransferTransactionsExceptionReportWriterService) {
764            this.recurringCashTransferTransactionsExceptionReportWriterService = recurringCashTransferTransactionsExceptionReportWriterService;
765        }
766    
767        public void setRecurringCashTransferTransactionsTotalReportWriterService(ReportWriterService recurringCashTransferTransactionsTotalReportWriterService) {
768            this.recurringCashTransferTransactionsTotalReportWriterService = recurringCashTransferTransactionsTotalReportWriterService;
769        }
770    
771        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
772            this.dataDictionaryService = dataDictionaryService;
773        }
774    
775        /**
776         * Sets the configService.
777         * 
778         * @param configService
779         */
780        public void setConfigService(KualiConfigurationService configService) {
781            this.configService = configService;
782        }
783    
784        private void updatePostingStatsForCashTransferDoc(CashTransferDocument cashTransferDoc) {
785    
786            String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(cashTransferDoc.getClass());
787            ReportDocumentStatistics stats = statistics.get(documentTypeName);
788    
789            // If null that means there isn't one in the map, so create it and add
790            // it to the map.
791            if (stats == null) {
792                stats = new ReportDocumentStatistics(documentTypeName);
793                statistics.put(documentTypeName, stats);
794            }
795            stats.addNumberOfSourceTransactionLines(cashTransferDoc.getSourceTransactionLines().size());
796            stats.addNumberOfTargetTransactionLines(cashTransferDoc.getTargetTransactionLines().size());
797    
798            stats.incrementNumberOfDocuments();
799        }
800    
801        private void updatePostingStatsForGlTransferDoc(EndowmentToGLTransferOfFundsDocument glTransferDoc) {
802    
803            String documentTypeName = dataDictionaryService.getDocumentTypeNameByClass(glTransferDoc.getClass());
804            ReportDocumentStatistics stats = statistics.get(documentTypeName);
805    
806            // If null that means there isn't one in the map, so create it and add
807            // it to the map.
808            if (stats == null) {
809                stats = new ReportDocumentStatistics(documentTypeName);
810                statistics.put(documentTypeName, stats);
811            }
812            stats.addNumberOfSourceTransactionLines(glTransferDoc.getSourceTransactionLines().size());
813            stats.addNumberOfTargetTransactionLines(glTransferDoc.getTargetAccountingLines().size());
814    
815            stats.incrementNumberOfDocuments();
816        }
817    
818        private void updateErrorStats(String documentTypeName) {
819            ReportDocumentStatistics stats = statistics.get(documentTypeName);
820    
821            // If null that means there isn't one in the map, so create it and add
822            // it to the map.
823            if (stats == null) {
824                stats = new ReportDocumentStatistics(documentTypeName);
825                statistics.put(documentTypeName, stats);
826            }
827    
828            stats.incrementNumberOfErrors();
829        }
830    
831        /**
832         * Write out the statistics.
833         */
834        private void writeStatistics() {
835    
836            for (Map.Entry<String, ReportDocumentStatistics> entry : statistics.entrySet()) {
837    
838                ReportDocumentStatistics stats = entry.getValue();
839    
840                recurringCashTransferTransactionsTotalReportWriterService.writeStatisticLine("%s Documents:", stats.getDocumentTypeName());
841                recurringCashTransferTransactionsTotalReportWriterService.writeStatisticLine("   Number of Documents Generated:            %d", stats.getNumberOfDocuments());
842                recurringCashTransferTransactionsTotalReportWriterService.writeStatisticLine("   Number of Transaction Lines Generated:    %d", stats.getTotalNumberOfTransactionLines());
843                recurringCashTransferTransactionsTotalReportWriterService.writeStatisticLine("   Number of Error Records Written:          %d", stats.getNumberOfErrors());
844                recurringCashTransferTransactionsTotalReportWriterService.writeStatisticLine("", "");
845            }
846    
847        }
848    
849    
850    }