001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.endow.batch.service.impl;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.util.ArrayList;
021    import java.util.HashMap;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.kuali.kfs.module.endow.EndowConstants;
026    import org.kuali.kfs.module.endow.EndowParameterKeyConstants;
027    import org.kuali.kfs.module.endow.EndowPropertyConstants;
028    import org.kuali.kfs.module.endow.batch.IncomeDistributionForPooledFundStep;
029    import org.kuali.kfs.module.endow.batch.service.IncomeDistributionForPooledFundService;
030    import org.kuali.kfs.module.endow.businessobject.EndowmentSourceTransactionLine;
031    import org.kuali.kfs.module.endow.businessobject.EndowmentTargetTransactionLine;
032    import org.kuali.kfs.module.endow.businessobject.EndowmentTransactionLineBase;
033    import org.kuali.kfs.module.endow.businessobject.HoldingTaxLot;
034    import org.kuali.kfs.module.endow.businessobject.KemidPayoutInstruction;
035    import org.kuali.kfs.module.endow.businessobject.PooledFundValue;
036    import org.kuali.kfs.module.endow.businessobject.TransactionDocumentExceptionReportLine;
037    import org.kuali.kfs.module.endow.businessobject.TransactionDocumentTotalReportLine;
038    import org.kuali.kfs.module.endow.dataaccess.IncomeDistributionForPooledFundDao;
039    import org.kuali.kfs.module.endow.document.CashIncreaseDocument;
040    import org.kuali.kfs.module.endow.document.CashTransferDocument;
041    import org.kuali.kfs.module.endow.document.EndowmentSecurityDetailsDocumentBase;
042    import org.kuali.kfs.module.endow.document.service.HoldingTaxLotService;
043    import org.kuali.kfs.module.endow.document.service.KEMService;
044    import org.kuali.kfs.module.endow.document.service.PooledFundValueService;
045    import org.kuali.kfs.module.endow.document.validation.event.AddTransactionLineEvent;
046    import org.kuali.kfs.module.endow.util.GloabalVariablesExtractHelper;
047    import org.kuali.kfs.sys.context.SpringContext;
048    import org.kuali.kfs.sys.service.ReportWriterService;
049    import org.kuali.rice.kew.exception.WorkflowException;
050    import org.kuali.rice.kns.rule.event.RouteDocumentEvent;
051    import org.kuali.rice.kns.service.BusinessObjectService;
052    import org.kuali.rice.kns.service.DocumentService;
053    import org.kuali.rice.kns.service.KualiRuleService;
054    import org.kuali.rice.kns.service.ParameterService;
055    import org.kuali.rice.kns.service.TransactionalDocumentDictionaryService;
056    import org.kuali.rice.kns.util.GlobalVariables;
057    import org.kuali.rice.kns.util.KualiDecimal;
058    import org.kuali.rice.kns.util.ObjectUtils;
059    import org.springframework.transaction.annotation.Transactional;
060    
061    @Transactional
062    public class IncomeDistributionForPooledFundServiceImpl implements IncomeDistributionForPooledFundService {
063    
064        protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(IncomeDistributionForPooledFundServiceImpl.class);
065    
066        protected BusinessObjectService businessObjectService;
067        protected DocumentService documentService;
068        protected ParameterService parameterService;
069        protected KualiRuleService kualiRuleService;
070    
071        protected KEMService kemService;
072        protected HoldingTaxLotService holdingTaxLotService;
073        protected PooledFundValueService pooledFundValueService;
074    
075        protected IncomeDistributionForPooledFundDao incomeDistributionForPooledFundDao;
076    
077        protected ReportWriterService incomeDistributionForPooledFundExceptionReportWriterService;
078        protected ReportWriterService incomeDistributionForPooledFundTotalReportWriterService;
079    
080        private TransactionDocumentTotalReportLine totalReportLine = null;
081        private TransactionDocumentExceptionReportLine exceptionReportLine = null;
082    
083        /**
084         * This batch creates pooled fund distribution transactions
085         * 
086         * @see org.kuali.kfs.module.endow.batch.service.IncomeDistributionForPooledFundService#createIncomeDistributionForPooledFund()
087         */
088        public boolean createIncomeDistributionForPooledFund() {
089    
090            LOG.info("Beginning the Income Distribution for Pooled Fund Transactions batch ...");
091    
092            // get the list of PooledFundValue with distribute income on date == the current date && income distribution complete ==
093            // 'N'&& the most recent value effective date
094            List<PooledFundValue> pooledFundValueList = incomeDistributionForPooledFundDao.getPooledFundValueForIncomeDistribution(kemService.getCurrentDate());
095            if (pooledFundValueList == null || pooledFundValueList.isEmpty()) {
096                // none exists so end the process
097                return true;
098            }
099    
100            // group by security id
101            for (PooledFundValue pooledFundValue : pooledFundValueList) {
102                // get all tax lots with security id equal to pooledSecurityId with holding units > 0
103                List<HoldingTaxLot> holdingTaxLotList = holdingTaxLotService.getTaxLotsPerSecurityIDWithUnitsGreaterThanZero(pooledFundValue.getPooledSecurityID());
104    
105                // group by registration code
106                if (holdingTaxLotList != null) {
107                    // create map <registration code, List<HoldingTaxLot>>
108                    Map<String, List<HoldingTaxLot>> registrationCodeMap = new HashMap<String, List<HoldingTaxLot>>();
109    
110                    for (HoldingTaxLot holdingTaxLot : holdingTaxLotList) {
111                        String registrationCode = holdingTaxLot.getRegistrationCode();
112                        if (registrationCodeMap.containsKey(registrationCode)) {
113                            registrationCodeMap.get(registrationCode).add(holdingTaxLot);
114                        }
115                        else {
116                            List<HoldingTaxLot> taxLots = new ArrayList<HoldingTaxLot>();
117                            taxLots.add(holdingTaxLot);
118                            registrationCodeMap.put(registrationCode, taxLots);
119                        }
120                    }
121    
122                    // initialize report lines for ECI; needs to fill details including documentId
123                    initializeReports(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_INCREASE);
124    
125                    // generate a new ECI document per security id and registration code
126                    for (String registrationCode : registrationCodeMap.keySet()) {
127                        List<HoldingTaxLot> holdingTaxLotsByRegCode = registrationCodeMap.get(registrationCode);
128                        if (holdingTaxLotsByRegCode != null) {
129                            createECI(pooledFundValue.getPooledSecurityID(), pooledFundValue.getValueEffectiveDate(), registrationCode, holdingTaxLotsByRegCode);
130                        }
131                    }
132                }
133            }
134    
135            // set incomeDistributionComplete to 'Y' and save
136            pooledFundValueService.setIncomeDistributionCompleted(pooledFundValueList, true);
137    
138            // TODO: write the sub total and grand total if necessary
139    
140            LOG.info("The Income Distribution for Pooled Fund Transactions Batch Job was finished.");
141    
142            return true;
143        }
144    
145        /**
146         * Creates an ECI per security id and registration code
147         * 
148         * @param securityId
149         * @param registrationCode
150         * @param holdingTaxLotList
151         */
152        protected boolean createECI(String securityId, Date effectiveDate, String registrationCode, List<HoldingTaxLot> holdingTaxLotList) {
153    
154            LOG.info("Creating ECI ...");
155    
156            boolean result = true;
157    
158            // set security id for reports
159            totalReportLine.setSecurityId(securityId);
160            exceptionReportLine.setSecurityId(securityId);
161    
162            // initialize ECT list, which will be used while adding ECI transaction lines
163            // this must be submitted after the ECI is submitted successfully
164            List<CashTransferDocument> cashTransferDocumentList = new ArrayList<CashTransferDocument>();
165    
166            // initialize CashIncreaseDocument
167            CashIncreaseDocument cashIncreaseDocument = initializeCashDocument(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_INCREASE, EndowConstants.MAXMUM_NUMBER_OF_EDOC_INITIALIZATION_TRY);
168            if (ObjectUtils.isNull(cashIncreaseDocument)) {
169                return false;
170            }
171            else {
172                totalReportLine.setDocumentId(cashIncreaseDocument.getDocumentNumber());
173                exceptionReportLine.setDocumentId(cashIncreaseDocument.getDocumentNumber());
174            }
175    
176            // add the doc description and security
177            cashIncreaseDocument.getDocumentHeader().setDocumentDescription(parameterService.getParameterValue(IncomeDistributionForPooledFundStep.class, EndowParameterKeyConstants.INCOME_DESCRIPTION));
178            cashIncreaseDocument.setTransactionSourceTypeCode(EndowConstants.TransactionSourceTypeCode.AUTOMATED);
179            addSecurityDetailToECI(cashIncreaseDocument, EndowConstants.TRANSACTION_LINE_TYPE_TARGET, securityId, registrationCode);
180    
181            // add transaction lines
182            addTransactionLinesToECI(cashIncreaseDocument, cashTransferDocumentList, holdingTaxLotList, effectiveDate);
183    
184            // validate ECI first and then submit it
185            GlobalVariables.clear();
186            if (validateECI(cashIncreaseDocument)) {
187                submitCashDocument(cashIncreaseDocument, EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_INCREASE, EndowParameterKeyConstants.INCOME_NO_ROUTE_IND);
188    
189                // and then validate and submit ECT
190                if (cashTransferDocumentList != null) {
191                    for (CashTransferDocument cashTransferDocument : cashTransferDocumentList) {
192                        GlobalVariables.clear(); // in case
193                        if (validateECT(cashTransferDocument)) {
194                            submitCashDocument(cashTransferDocument, EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER, EndowParameterKeyConstants.INCOME_TRANSFER_NO_ROUTE_IND);
195                        }
196                        else {
197                            writeValidationErrorReason();
198                            LOG.error("Failed to validate ECT: Document # " + cashTransferDocument.getDocumentNumber());
199                            result = false;
200                        }
201                    }
202                }
203    
204                // write the total report
205                incomeDistributionForPooledFundTotalReportWriterService.writeTableRow(totalReportLine);
206    
207                // TODO: prepare for sub total by security id and grand total if necessary
208    
209            }
210            else {
211                writeValidationErrorReason();
212                LOG.error("Failed to validate ECI: Document # " + cashIncreaseDocument.getDocumentNumber());
213                result = false;
214            }
215    
216            return result;
217        }
218    
219        /**
220         * Adds transaction lines and create ECT if necessary
221         * 
222         * @param cashIncreaseDocument
223         * @param cashTransferDocumentList
224         * @param holdingTaxLotList
225         */
226        protected void addTransactionLinesToECI(CashIncreaseDocument cashIncreaseDocument, List<CashTransferDocument> cashTransferDocumentList, List<HoldingTaxLot> holdingTaxLotList, Date effectiveDate) {
227    
228            // create a kemid map <kemid, map<incomePrincipalIndicator, holdingTaxLots>> in preparation for adding transaction lines
229            Map<String, Map<String, List<HoldingTaxLot>>> kemidMap = new HashMap<String, Map<String, List<HoldingTaxLot>>>();
230    
231            // group by kemid and incomePrincipalIndicator
232            groupHoldingTaxLot(holdingTaxLotList, kemidMap);
233    
234            // add transaction lines per kemid and incomePrincipalIndicator
235            for (String kemid : kemidMap.keySet()) {
236                for (String incomePrincipalIndicator : kemidMap.get(kemid).keySet()) {
237                    List<HoldingTaxLot> holdingTaxLotGroupedByIPInd = kemidMap.get(kemid).get(incomePrincipalIndicator);
238                    KualiDecimal transactionAmount = getTransactionAmount(holdingTaxLotGroupedByIPInd, effectiveDate);
239                    if (transactionAmount.isLessThan(KualiDecimal.ZERO)) {
240                        transactionAmount = transactionAmount.negated();
241                    }
242                    if (holdingTaxLotGroupedByIPInd != null && transactionAmount.isGreaterThan(KualiDecimal.ZERO)) {
243                        int maxNumberOfTranLines = kemService.getMaxNumberOfTransactionLinesPerDocument();
244                        for (HoldingTaxLot holdingTaxLot : holdingTaxLotGroupedByIPInd) {
245    
246                            if (cashIncreaseDocument.getNextTargetLineNumber() > maxNumberOfTranLines) {
247    
248                                // validate and submit
249                                if (validateECI(cashIncreaseDocument)) {
250                                    submitCashDocument(cashIncreaseDocument, EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_INCREASE, EndowParameterKeyConstants.INCOME_NO_ROUTE_IND);
251    
252                                    // generate a new ECI
253                                    cashIncreaseDocument = initializeCashDocument(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_INCREASE, EndowConstants.MAXMUM_NUMBER_OF_EDOC_INITIALIZATION_TRY);
254                                    if (ObjectUtils.isNull(cashIncreaseDocument)) {
255                                        return; // we can't do anything
256                                    }
257                                    else {
258                                        // add the doc description and security detail
259                                        cashIncreaseDocument.getDocumentHeader().setDocumentDescription(parameterService.getParameterValue(IncomeDistributionForPooledFundStep.class, EndowParameterKeyConstants.INCOME_DESCRIPTION));
260                                        cashIncreaseDocument.setTransactionSourceTypeCode(EndowConstants.TransactionSourceTypeCode.AUTOMATED);
261                                        addSecurityDetailToECI(cashIncreaseDocument, EndowConstants.TRANSACTION_LINE_TYPE_TARGET, holdingTaxLot.getSecurityId(), holdingTaxLot.getRegistrationCode());
262                                        // reset reports
263                                        resetTotalReport(cashIncreaseDocument);
264                                        resetExceptionlReport(cashIncreaseDocument);
265                                    }
266                                }
267                                else {
268                                    writeValidationErrorReason();
269                                    LOG.error("Failed to validate ECI: Document # " + cashIncreaseDocument.getDocumentNumber());
270                                }
271                            }
272    
273                            // now create and add a new transaction line
274                            EndowmentTargetTransactionLine endowmentTargetTransactionLine = new EndowmentTargetTransactionLine();
275                            endowmentTargetTransactionLine.setKemid(holdingTaxLot.getKemid());
276                            endowmentTargetTransactionLine.setEtranCode(incomeDistributionForPooledFundDao.getIncomeEntraCode(holdingTaxLot.getSecurityId()));
277                            endowmentTargetTransactionLine.setTransactionIPIndicatorCode(EndowConstants.IncomePrincipalIndicator.INCOME);
278                            // endowmentTargetTransactionLine.setTransactionLineTypeCode(EndowConstants.TRANSACTION_LINE_TYPE_TARGET);
279                            endowmentTargetTransactionLine.setTransactionAmount(transactionAmount);
280    
281                            GlobalVariables.clear(); // clear the previous errors
282                            if (validateTransactionLine(cashIncreaseDocument, endowmentTargetTransactionLine, EndowConstants.NEW_TARGET_TRAN_LINE_PROPERTY_NAME)) {
283                                cashIncreaseDocument.addTargetTransactionLine(endowmentTargetTransactionLine);
284    
285                                // prepare the total report
286                                prepareTotalReport(transactionAmount, new KualiDecimal(holdingTaxLot.getUnits()));
287    
288                                // set ECT type to reports
289                                setDocumentTypeForReport(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER);
290    
291                                // get the list of KemidPayoutInstruction with pay income short date <= current date && (pay income term
292                                // date == null or > current date) && kemid != pay_inc_to_kemid
293                                List<KemidPayoutInstruction> kemidPayoutInstructionList = incomeDistributionForPooledFundDao.getKemidPayoutInstructionForECT(holdingTaxLot.getKemid(), kemService.getCurrentDate());
294                                if (kemidPayoutInstructionList != null && !kemidPayoutInstructionList.isEmpty()) {
295                                    // create an ECT per sec_id, regis_cd when each transaction line is added
296                                    createECT(holdingTaxLot, transactionAmount, cashTransferDocumentList, kemidPayoutInstructionList);
297                                }
298    
299                                // set ECI type to reports
300                                setDocumentTypeForReport(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_INCREASE);
301                            }
302                            else {
303                                // add the info to the exception report
304                                writeExceptionReport(kemid, transactionAmount, new KualiDecimal(holdingTaxLot.getUnits()));
305                                writeValidationErrorReason();
306                            }
307                        }
308                    }
309                }
310            }
311    
312        }
313    
314        /**
315         * Creates ECT
316         * 
317         * @param holdingTaxLot
318         * @param transactionAmount
319         * @param cashTransferDocumentList
320         */
321        protected CashTransferDocument createECT(HoldingTaxLot holdingTaxLot, KualiDecimal transactionAmount, List<CashTransferDocument> cashTransferDocumentList, List<KemidPayoutInstruction> kemidPayoutInstructionList) {
322    
323            CashTransferDocument cashTransferDocument = initializeCashDocument(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER, EndowConstants.MAXMUM_NUMBER_OF_EDOC_INITIALIZATION_TRY);
324            if (ObjectUtils.isNotNull(cashTransferDocument)) {
325                cashTransferDocument.getDocumentHeader().setDocumentDescription(parameterService.getParameterValue(IncomeDistributionForPooledFundStep.class, EndowParameterKeyConstants.INCOME_TRANSFER_DESCRIPTION));
326                cashTransferDocument.setTransactionSourceTypeCode(EndowConstants.TransactionSourceTypeCode.AUTOMATED);
327                // add security
328                addSecurityDetailToECT(cashTransferDocument, holdingTaxLot.getSecurityId(), holdingTaxLot.getRegistrationCode());
329                // add transaction lines
330                addTransactionLinesToECT(cashTransferDocumentList, cashTransferDocument, holdingTaxLot, kemidPayoutInstructionList, transactionAmount);
331                // prepare to submit the current ECT later
332                cashTransferDocumentList.add(cashTransferDocument);
333            }
334            return cashTransferDocument;
335        }
336    
337        /**
338         * Adds transaction lines to ECT
339         * 
340         * @param cashTransferDocumentList
341         * @param cashTransferDocument
342         * @param holdingTaxLot
343         * @param kemidPayoutInstructionList
344         * @param toalTransactionAmount
345         */
346        protected void addTransactionLinesToECT(List<CashTransferDocument> cashTransferDocumentList, CashTransferDocument cashTransferDocument, HoldingTaxLot holdingTaxLot, List<KemidPayoutInstruction> kemidPayoutInstructionList, KualiDecimal toalTransactionAmount) {
347    
348            int maxNumberOfTranLines = kemService.getMaxNumberOfTransactionLinesPerDocument();
349    
350            for (KemidPayoutInstruction kemidPayoutInstruction : kemidPayoutInstructionList) {
351                if (cashTransferDocument.getNextSourceLineNumber() > maxNumberOfTranLines) {
352                    // prepare to submit the current ECT later
353                    cashTransferDocumentList.add(cashTransferDocument);
354    
355                    // generate a new ECT
356                    cashTransferDocument = initializeCashDocument(EndowConstants.DocumentTypeNames.ENDOWMENT_CASH_TRANSFER, EndowConstants.MAXMUM_NUMBER_OF_EDOC_INITIALIZATION_TRY);
357                    if (cashTransferDocument == null) {
358                        return; // ??
359                    }
360                    else {
361                        // add the doc description and security
362                        cashTransferDocument.getDocumentHeader().setDocumentDescription(parameterService.getParameterValue(IncomeDistributionForPooledFundStep.class, EndowParameterKeyConstants.INCOME_TRANSFER_DESCRIPTION));
363                        cashTransferDocument.setTransactionSourceTypeCode(EndowConstants.TransactionSourceTypeCode.AUTOMATED);
364                        // populate security
365                        addSecurityDetailToECT(cashTransferDocument, holdingTaxLot.getSecurityId(), holdingTaxLot.getRegistrationCode());                    
366                        //addSecurityDetailToECT(cashTransferDocument, EndowConstants.TRANSACTION_LINE_TYPE_TARGET, holdingTaxLot.getSecurityId(), holdingTaxLot.getRegistrationCode());
367                        // reset reports
368                        resetTotalReport(cashTransferDocument);
369                        resetExceptionlReport(cashTransferDocument);
370                    }
371                }
372    
373                // add a source transaction line
374                EndowmentSourceTransactionLine sourceTransactionLine = new EndowmentSourceTransactionLine();
375                sourceTransactionLine.setKemid(holdingTaxLot.getKemid());
376                sourceTransactionLine.setEtranCode(parameterService.getParameterValue(IncomeDistributionForPooledFundStep.class, EndowParameterKeyConstants.INCOME_TRANSFER_ENDOWMENT_TRANSACTION_CODE));
377                sourceTransactionLine.setTransactionLineDescription("To <" + kemidPayoutInstruction.getPayIncomeToKemid() + ">");
378                sourceTransactionLine.setTransactionIPIndicatorCode(EndowConstants.IncomePrincipalIndicator.INCOME);
379                sourceTransactionLine.setTransactionAmount(toalTransactionAmount.multiply(kemidPayoutInstruction.getPercentOfIncomeToPayToKemid()));
380    
381                // add a target transaction line
382                EndowmentTargetTransactionLine targetTransactionLine = new EndowmentTargetTransactionLine();
383                targetTransactionLine.setKemid(holdingTaxLot.getKemid());
384                targetTransactionLine.setEtranCode(parameterService.getParameterValue(IncomeDistributionForPooledFundStep.class, EndowParameterKeyConstants.INCOME_TRANSFER_ENDOWMENT_TRANSACTION_CODE));
385                targetTransactionLine.setTransactionLineDescription("From <" + kemidPayoutInstruction.getPayIncomeToKemid() + ">");
386                targetTransactionLine.setTransactionIPIndicatorCode(EndowConstants.IncomePrincipalIndicator.INCOME);
387                targetTransactionLine.setTransactionAmount(toalTransactionAmount.multiply(kemidPayoutInstruction.getPercentOfIncomeToPayToKemid()));
388    
389                GlobalVariables.clear();
390                if (validateTransactionLine(cashTransferDocument, sourceTransactionLine, EndowConstants.NEW_SOURCE_TRAN_LINE_PROPERTY_NAME)) {
391                    if (validateTransactionLine(cashTransferDocument, targetTransactionLine, EndowConstants.NEW_TARGET_TRAN_LINE_PROPERTY_NAME)) {
392                        cashTransferDocument.addSourceTransactionLine(sourceTransactionLine);
393                        cashTransferDocument.addTargetTransactionLine(targetTransactionLine);
394    
395                        prepareTotalReport(toalTransactionAmount.multiply(kemidPayoutInstruction.getPercentOfIncomeToPayToKemid()), new KualiDecimal(holdingTaxLot.getUnits()));
396    
397                    }
398                    else {
399                        writeExceptionReport(holdingTaxLot.getKemid(), toalTransactionAmount.multiply(kemidPayoutInstruction.getPercentOfIncomeToPayToKemid()), new KualiDecimal(holdingTaxLot.getUnits()));
400                        writeValidationErrorReason();
401                    }
402                }
403                else {
404                    writeExceptionReport(holdingTaxLot.getKemid(), toalTransactionAmount.multiply(kemidPayoutInstruction.getPercentOfIncomeToPayToKemid()), new KualiDecimal(holdingTaxLot.getUnits()));
405                    writeValidationErrorReason();
406                }
407            }
408        }
409    
410        /**
411         * Calculates the total of holding units * distribution amount
412         * 
413         * @param holdingTaxLotList
414         * @return KualiDecimal(totalTransactionAmount)
415         */
416        protected KualiDecimal getTransactionAmount(List<HoldingTaxLot> holdingTaxLotList, Date effectiveDate) {
417    
418            // total holding units
419            BigDecimal totalUnits = BigDecimal.ZERO;
420            for (HoldingTaxLot holdingTaxLot : holdingTaxLotList) {
421                totalUnits = totalUnits.add(holdingTaxLot.getUnits());
422            }
423    
424            // distribution amount of pooledFundValue with the security id and effective date
425            BigDecimal totalDistributionAmount = BigDecimal.ZERO;
426            Map<String, Object> fieldValues = new HashMap<String, Object>();
427            fieldValues.put(EndowPropertyConstants.POOL_SECURITY_ID, holdingTaxLotList.get(0).getSecurityId());
428            fieldValues.put(EndowPropertyConstants.VALUE_EFFECTIVE_DATE, effectiveDate);
429            PooledFundValue pooledFundValue = (PooledFundValue) businessObjectService.findByPrimaryKey(PooledFundValue.class, fieldValues);
430            totalDistributionAmount = totalDistributionAmount.add(pooledFundValue.getIncomeDistributionPerUnit());
431    
432            return new KualiDecimal(totalUnits.multiply(totalDistributionAmount));
433    
434        }
435    
436        /**
437         * Adds security to ECI
438         * 
439         * @param cashIncreaseDocument
440         * @param typeCode
441         * @param securityId
442         * @param registrationCode
443         */
444        protected void addSecurityDetailToECI(CashIncreaseDocument cashIncreaseDocument, String typeCode, String securityId, String registrationCode) {
445            cashIncreaseDocument.getTargetTransactionSecurity().setSecurityLineTypeCode(typeCode);
446            cashIncreaseDocument.getTargetTransactionSecurity().setSecurityID(securityId);
447            cashIncreaseDocument.getTargetTransactionSecurity().setRegistrationCode(registrationCode);
448        }
449    
450        /**
451         * Adds security to ECT
452         * 
453         * @param cashTransferDocument
454         * @param typeCode
455         * @param securityId
456         * @param registrationCode
457         */
458        protected void addSecurityDetailToECT(CashTransferDocument cashTransferDocument, String securityId, String registrationCode) {
459            cashTransferDocument.getSourceTransactionSecurity().setSecurityLineTypeCode(EndowConstants.TRANSACTION_LINE_TYPE_SOURCE);
460            cashTransferDocument.getSourceTransactionSecurity().setSecurityID(securityId);
461            cashTransferDocument.getSourceTransactionSecurity().setRegistrationCode(registrationCode);
462    
463            cashTransferDocument.getTargetTransactionSecurity().setSecurityLineTypeCode(EndowConstants.TRANSACTION_LINE_TYPE_TARGET);
464            cashTransferDocument.getTargetTransactionSecurity().setSecurityID(securityId);
465            cashTransferDocument.getTargetTransactionSecurity().setRegistrationCode(registrationCode);
466        }
467    
468        /**
469         * Initialize a cash document. If fails, try as many times as EndowConstants.MAXMUM_NUMBER_OF_EDOC_INITILIZATION_TRY.
470         * 
471         * @param <C>
472         * @param documentType
473         * @param counter
474         * @return
475         */
476        protected <C extends EndowmentSecurityDetailsDocumentBase> C initializeCashDocument(String documentType, int counter) {
477    
478            C cashDocument = null;
479    
480            if (counter > 0) {
481                try {
482                    cashDocument = (C) documentService.getNewDocument(SpringContext.getBean(TransactionalDocumentDictionaryService.class).getDocumentClassByName(documentType));
483                }
484                catch (WorkflowException wfe) {
485                    incomeDistributionForPooledFundExceptionReportWriterService.writeFormattedMessageLine("Failed to generate a new %s: %s", documentType, wfe.getMessage());
486                    LOG.error((EndowConstants.MAXMUM_NUMBER_OF_EDOC_INITIALIZATION_TRY - counter + 1) + ": The creation of " + documentType + " failed. Tyring it again ...");
487                    cashDocument = (C) initializeCashDocument(documentType, --counter);
488                }
489                catch (Exception e) {
490                    incomeDistributionForPooledFundExceptionReportWriterService.writeFormattedMessageLine("Failed to generate a new %s: %s", documentType, e.getMessage());
491                    LOG.error("generateCashDocument Runtime error in initializing document: " + documentType + ": " + e.getMessage());
492                }
493            }
494    
495            return cashDocument;
496        }
497    
498        /**
499         * Submits Cash document
500         * 
501         * @param <T>
502         * @param cashDocument
503         */
504        protected <T extends EndowmentSecurityDetailsDocumentBase> void submitCashDocument(T cashDocument, String documentType, String noRouteInd) {
505            try {
506                cashDocument.setNoRouteIndicator(isNoRoute(noRouteInd));
507                documentService.routeDocument(cashDocument, "Submitted by the batch job", null);
508            }
509            catch (WorkflowException wfe) {
510                LOG.error("Failed to route document #: " + cashDocument.getDocumentNumber());
511                LOG.error(wfe.getMessage());
512                try {
513                    GlobalVariables.clear();
514                    documentService.saveDocument(cashDocument);
515                }
516                catch (WorkflowException wfe2) {
517                    LOG.error("Failed to save document #: " + cashDocument.getDocumentNumber());
518                    LOG.error(wfe2.getMessage());
519                }
520                finally {
521                    writeSubmitError(cashDocument, documentType);
522                }
523            }
524            catch (Exception e) {
525                writeSubmitError(cashDocument, documentType);
526                LOG.error(e.getMessage());
527            }
528        }
529    
530        /**
531         * Groups holdingTaxLotList by kemid and incomePrincipalIndicator where the value of holding units > 0
532         * 
533         * @param holdingTaxLotList
534         * @param kemidMap
535         */
536        protected void groupHoldingTaxLot(List<HoldingTaxLot> holdingTaxLotList, Map<String, Map<String, List<HoldingTaxLot>>> kemidMap) {
537            for (HoldingTaxLot holdingTaxLot : holdingTaxLotList) {
538                if (holdingTaxLot.getUnits().doubleValue() > 0.0) {
539                    String kemid = holdingTaxLot.getKemid();
540                    String incomePrincipalIndicator = holdingTaxLot.getIncomePrincipalIndicator();
541                    if (kemidMap.containsKey(kemid)) {
542                        if (kemidMap.get(kemid).containsKey(incomePrincipalIndicator)) {
543                            // add it to the same kemid and incomePrincipalIndicator list
544                            kemidMap.get(kemid).get(incomePrincipalIndicator).add(holdingTaxLot);
545                        }
546                        else {
547                            // create a new incomePrincipalIndicator map and put it to kemidMap
548                            List<HoldingTaxLot> taxLots = new ArrayList<HoldingTaxLot>();
549                            taxLots.add(holdingTaxLot);
550                            kemidMap.get(kemid).put(incomePrincipalIndicator, taxLots);
551                        }
552                    }
553                    else {
554                        Map<String, List<HoldingTaxLot>> ipIndMap = new HashMap<String, List<HoldingTaxLot>>();
555                        List<HoldingTaxLot> taxLots = new ArrayList<HoldingTaxLot>();
556                        taxLots.add(holdingTaxLot);
557                        ipIndMap.put(incomePrincipalIndicator, taxLots);
558                        kemidMap.put(kemid, ipIndMap);
559                    }
560                }
561            }
562        }
563    
564        /**
565         * Validates Cash Transaction line
566         * 
567         * @param <C>
568         * @param <T>
569         * @param cashDocument
570         * @param endowmentTransactionLine
571         * @param trnsactionPropertyName
572         * @return
573         */
574        protected <C extends EndowmentSecurityDetailsDocumentBase, T extends EndowmentTransactionLineBase> boolean validateTransactionLine(C cashDocument, T endowmentTransactionLine, String trnsactionPropertyName) {
575            return kualiRuleService.applyRules(new AddTransactionLineEvent(trnsactionPropertyName, cashDocument, endowmentTransactionLine));
576        }
577    
578        /**
579         * validates the ECI business rules
580         * 
581         * @param cashIncreaseDocument
582         * @return boolean
583         */
584        protected boolean validateECI(CashIncreaseDocument cashIncreaseDocument) {
585            return kualiRuleService.applyRules(new RouteDocumentEvent(cashIncreaseDocument));
586        }
587    
588        /**
589         * validates the ECT business rules
590         * 
591         * @param cashTransferDocument
592         * @return boolean
593         */
594        protected boolean validateECT(CashTransferDocument cashTransferDocument) {
595            return kualiRuleService.applyRules(new RouteDocumentEvent(cashTransferDocument));
596        }
597    
598        /**
599         * checks no route indicator
600         * 
601         * @return boolean
602         */
603        public boolean isNoRoute(String paramNoRouteInd) {
604            return parameterService.getIndicatorParameter(IncomeDistributionForPooledFundStep.class, paramNoRouteInd);
605        }
606    
607        /**
608         * Resets the total report
609         * 
610         * @param <C>
611         * @param cashDocument
612         */
613        protected <C extends EndowmentSecurityDetailsDocumentBase> void resetTotalReport(C cashDocument) {
614            totalReportLine.setDocumentId(cashDocument.getDocumentNumber());
615            totalReportLine.setTotalNumberOfTransactionLines(0);
616            totalReportLine.setIncomeAmount(KualiDecimal.ZERO);
617            totalReportLine.setIncomeUnits(KualiDecimal.ZERO);
618            totalReportLine.setPrincipalAmount(KualiDecimal.ZERO);
619            totalReportLine.setPrincipalUnits(KualiDecimal.ZERO);
620        }
621    
622        /**
623         * Adds income and units
624         * 
625         * @param transactionAmount
626         * @param units
627         */
628        protected void prepareTotalReport(KualiDecimal transactionAmount, KualiDecimal units) {
629            totalReportLine.addIncomeAmount(transactionAmount);
630            totalReportLine.addIncomeUnits(units);
631        }
632    
633        /**
634         * Resets the exception report
635         * 
636         * @param <C>
637         * @param cashDocument
638         */
639        protected <C extends EndowmentSecurityDetailsDocumentBase> void resetExceptionlReport(C cashDocument) {
640            exceptionReportLine.setDocumentId(cashDocument.getDocumentNumber());
641            exceptionReportLine.setIncomeAmount(cashDocument.getTargetIncomeTotal());
642            exceptionReportLine.setIncomeUnits(cashDocument.getTargetIncomeTotalUnits());
643            exceptionReportLine.setPrincipalAmount(cashDocument.getTargetPrincipalTotal());
644            exceptionReportLine.setPrincipalUnits(cashDocument.getTargetPrincipalTotalUnits());
645        }
646    
647        /**
648         * Write exception errors
649         * 
650         * @param kemid
651         * @param transactionAmount
652         * @param units
653         */
654        protected void writeExceptionReport(String kemid, KualiDecimal transactionAmount, KualiDecimal units) {
655            exceptionReportLine.setKemid(kemid);
656            exceptionReportLine.setIncomeAmount(transactionAmount);
657            exceptionReportLine.setIncomeUnits(units);
658            incomeDistributionForPooledFundExceptionReportWriterService.writeTableRow(exceptionReportLine);
659            List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
660            for (String errorMessage : errorMessages) {
661                incomeDistributionForPooledFundExceptionReportWriterService.writeFormattedMessageLine("Reason:  %s", errorMessage);
662                incomeDistributionForPooledFundExceptionReportWriterService.writeNewLines(1);
663            }
664        }
665    
666        /**
667         * Initialize reports
668         * 
669         * @param documentType
670         * @param securityId
671         */
672        protected void initializeReports(String documentType) {
673    
674            // initialize totalReportLine
675            this.totalReportLine = new TransactionDocumentTotalReportLine(documentType, "", "");
676            incomeDistributionForPooledFundTotalReportWriterService.writeSubTitle("<incomeDistributionForPooledFundJob> Totals Processed");
677            incomeDistributionForPooledFundTotalReportWriterService.writeNewLines(1);
678            incomeDistributionForPooledFundTotalReportWriterService.writeTableHeader(totalReportLine);
679    
680            // initialize exceptionReportLine
681            this.exceptionReportLine = new TransactionDocumentExceptionReportLine(documentType, "", "");
682            incomeDistributionForPooledFundExceptionReportWriterService.writeSubTitle("<incomeDistributionForPooledFundJob> Exception Report");
683            incomeDistributionForPooledFundExceptionReportWriterService.writeNewLines(1);
684            incomeDistributionForPooledFundExceptionReportWriterService.writeTableHeader(exceptionReportLine);
685        }
686    
687        /**
688         * Set the document type to reports
689         * 
690         * @param documentType
691         */
692        protected void setDocumentTypeForReport(String documentType) {
693            totalReportLine.setDocumentType(documentType);
694            exceptionReportLine.setDocumentType(documentType);
695        }
696    
697        /**
698         * Writes the validation errors
699         */
700        protected void writeValidationErrorReason() {
701            List<String> errorMessages = GloabalVariablesExtractHelper.extractGlobalVariableErrors();
702            for (String errorMessage : errorMessages) {
703                incomeDistributionForPooledFundExceptionReportWriterService.writeFormattedMessageLine("Reason:  %s", errorMessage);
704                incomeDistributionForPooledFundExceptionReportWriterService.writeNewLines(1);
705            }
706        }
707    
708        /**
709         * Write errors that occur during the submission
710         * 
711         * @param <T>
712         * @param cashDocument
713         * @param documentType
714         */
715        protected <T extends EndowmentSecurityDetailsDocumentBase> void writeSubmitError(T cashDocument, String documentType) {
716            exceptionReportLine.setDocumentId(cashDocument.getDocumentNumber());
717            exceptionReportLine.setDocumentType(documentType);
718            exceptionReportLine.setSecurityId(cashDocument.getTargetTransactionSecurity().getSecurityID());
719            exceptionReportLine.setIncomeAmount(cashDocument.getTargetIncomeTotal());
720            incomeDistributionForPooledFundExceptionReportWriterService.writeTableRow(exceptionReportLine);
721            incomeDistributionForPooledFundExceptionReportWriterService.writeFormattedMessageLine("Falied to route document #: %s", cashDocument.getDocumentNumber());
722        }
723    
724        /**
725         * Sets the businessObjectService attribute value.
726         * 
727         * @param businessObjectService The businessObjectService to set.
728         */
729        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
730            this.businessObjectService = businessObjectService;
731        }
732    
733        /**
734         * Sets the documentService attribute value.
735         * 
736         * @param documentService The documentService to set.
737         */
738        public void setDocumentService(DocumentService documentService) {
739            this.documentService = documentService;
740        }
741    
742        /**
743         * Sets the parameterService attribute value.
744         * 
745         * @param parameterService The parameterService to set.
746         */
747        public void setParameterService(ParameterService parameterService) {
748            this.parameterService = parameterService;
749        }
750    
751        /**
752         * Sets the kualiRuleService attribute value.
753         * 
754         * @param kualiRuleService The kualiRuleService to set.
755         */
756        public void setKualiRuleService(KualiRuleService kualiRuleService) {
757            this.kualiRuleService = kualiRuleService;
758        }
759    
760        /**
761         * Sets the kemService attribute value.
762         * 
763         * @param kemService The kemService to set.
764         */
765        public void setKemService(KEMService kemService) {
766            this.kemService = kemService;
767        }
768    
769        /**
770         * Sets the holdingTaxLotService attribute value.
771         * 
772         * @param holdingTaxLotService The holdingTaxLotService to set.
773         */
774        public void setHoldingTaxLotService(HoldingTaxLotService holdingTaxLotService) {
775            this.holdingTaxLotService = holdingTaxLotService;
776        }
777    
778        /**
779         * Sets the pooledFundValueService attribute value.
780         * 
781         * @param pooledFundValueService The pooledFundValueService to set.
782         */
783        public void setPooledFundValueService(PooledFundValueService pooledFundValueService) {
784            this.pooledFundValueService = pooledFundValueService;
785        }
786    
787        /**
788         * Sets the incomeDistributionForPooledFundDao attribute value.
789         * 
790         * @param incomeDistributionForPooledFundDao The incomeDistributionForPooledFundDao to set.
791         */
792        public void setIncomeDistributionForPooledFundDao(IncomeDistributionForPooledFundDao incomeDistributionForPooledFundDao) {
793            this.incomeDistributionForPooledFundDao = incomeDistributionForPooledFundDao;
794        }
795    
796        /**
797         * Sets the incomeDistributionForPooledFundExceptionReportWriterService attribute value.
798         * 
799         * @param incomeDistributionForPooledFundExceptionReportWriterService The
800         *        incomeDistributionForPooledFundExceptionReportWriterService to set.
801         */
802        public void setIncomeDistributionForPooledFundExceptionReportWriterService(ReportWriterService incomeDistributionForPooledFundExceptionReportWriterService) {
803            this.incomeDistributionForPooledFundExceptionReportWriterService = incomeDistributionForPooledFundExceptionReportWriterService;
804        }
805    
806        /**
807         * Sets the incomeDistributionForPooledFundTotalReportWriterService attribute value.
808         * 
809         * @param incomeDistributionForPooledFundTotalReportWriterService The incomeDistributionForPooledFundTotalReportWriterService to
810         *        set.
811         */
812        public void setIncomeDistributionForPooledFundTotalReportWriterService(ReportWriterService incomeDistributionForPooledFundTotalReportWriterService) {
813            this.incomeDistributionForPooledFundTotalReportWriterService = incomeDistributionForPooledFundTotalReportWriterService;
814        }
815    
816    
817    }