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.sql.Date;
019    import java.util.List;
020    
021    import org.kuali.kfs.module.endow.batch.service.RollFrequencyDatesService;
022    import org.kuali.kfs.module.endow.businessobject.AutomatedCashInvestmentModel;
023    import org.kuali.kfs.module.endow.businessobject.CashSweepModel;
024    import org.kuali.kfs.module.endow.businessobject.EndowmentRecurringCashTransfer;
025    import org.kuali.kfs.module.endow.businessobject.FeeMethod;
026    import org.kuali.kfs.module.endow.businessobject.Security;
027    import org.kuali.kfs.module.endow.businessobject.Tickler;
028    import org.kuali.kfs.module.endow.dataaccess.AutomatedCashInvestmentModelDao;
029    import org.kuali.kfs.module.endow.dataaccess.CashSweepModelDao;
030    import org.kuali.kfs.module.endow.dataaccess.FeeMethodDao;
031    import org.kuali.kfs.module.endow.dataaccess.RecurringCashTransferDao;
032    import org.kuali.kfs.module.endow.dataaccess.SecurityDao;
033    import org.kuali.kfs.module.endow.dataaccess.TicklerDao;
034    import org.kuali.kfs.module.endow.document.service.FrequencyDatesService;
035    import org.kuali.kfs.module.endow.document.service.KEMService;
036    import org.kuali.kfs.sys.service.ReportWriterService;
037    import org.kuali.rice.kns.bo.PersistableBusinessObject;
038    import org.kuali.rice.kns.service.BusinessObjectService;
039    import org.kuali.rice.kns.util.ObjectUtils;
040    import org.springframework.transaction.annotation.Transactional;
041    
042    /**
043     * This class implements the RollFrequencyDatesService batch job.
044     */
045    @Transactional
046    public class RollFrequencyDatesServiceImpl implements RollFrequencyDatesService {
047        protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RollFrequencyDatesServiceImpl.class);
048        
049        protected BusinessObjectService businessObjectService;
050        protected KEMService kemService;
051        protected FrequencyDatesService frequencyDatesService;
052        
053        protected SecurityDao securityDao;
054        protected FeeMethodDao feeMethodDao;
055        protected TicklerDao ticklerDao;
056        protected RecurringCashTransferDao recurringCashTransferDao;
057        protected AutomatedCashInvestmentModelDao automatedCashInvestmentModelDao;
058        protected CashSweepModelDao cashSweepModelDao;
059            
060        protected ReportWriterService rollFrequencyDatesTotalReportWriterService;
061        protected ReportWriterService rollFrequencyDatesExceptionReportWriterService;
062        
063        /**
064         * Updates some date fields based on the frequency for the activity 
065         * @return true if the fields are updated successfully; false otherwise
066         */
067        public boolean updateFrequencyDate() {
068            
069            LOG.info("Begin the batch Roll Frequncy Dates ...");
070            
071            // update Security Income Next Pay Dates
072            updateSecurityIncomeNextPayDates();
073                      
074            // update Tickler Next Due Dates
075            updateTicklerNextDueDates();
076            
077            // update Fee Method Next Process Dates
078            updateFeeMethodProcessDates();
079            
080            // update Recurring Cash Transfer Next Process Dates
081            updateRecurringCashTransferProcessDates();
082    
083            // update Cash Sweep Model Next Due Dates
084            updateCashSweepModelNextDueDates();
085    
086            // update Cash Investment Model Next Due Dates
087            updateAutomatedCashInvestmentModelNextDueDates();
088            
089            LOG.info("The batch Roll Frequncy Dates was finished.");
090            
091            return true;
092        }
093    
094        /**
095         * This method updates the income next pay dates in Security
096         */
097        protected boolean updateSecurityIncomeNextPayDates() {
098            
099            boolean success = true;
100            
101            int counter = 0;
102            // get all the active security records whose next income pay date is equal to the current date
103            List<Security> securityRecords = securityDao.getSecuritiesWithNextPayDateEqualToCurrentDate();
104            if (securityRecords != null) {
105                for (Security security : securityRecords) {
106    
107                    Date incomeNextPayDate = security.getIncomeNextPayDate();
108                    
109                    // if maturity date is equals to income next pay date, do nothing
110                    Date maturityDate = security.getMaturityDate();                
111                    if (ObjectUtils.isNotNull(maturityDate) && ObjectUtils.isNotNull(incomeNextPayDate)) {
112                        if (maturityDate.compareTo(incomeNextPayDate) == 0) {
113                            continue;
114                        }
115                    }
116                    
117                    // replace income next date
118                    // first, with the next date calculated based on the frequency code
119                    // if it is invalid, with the dividend pay date 
120                    String frequencyCode = security.getIncomePayFrequency();
121                    Date nextDate = frequencyDatesService.calculateNextDueDate(frequencyCode, kemService.getCurrentDate());
122                    if (nextDate == null) {
123                        nextDate = security.getDividendPayDate();
124                        if (ObjectUtils.isNull(nextDate) || (ObjectUtils.isNotNull(incomeNextPayDate) && nextDate.compareTo(incomeNextPayDate) == 0)) {
125                            // we don't need to update income next pay date
126                            continue;
127                        }
128                    }
129                    // update income next pay date
130                    security.setIncomeNextPayDate(nextDate);
131                    if (updateBusinessObject(security)) {
132                        counter++;
133                        generateTotalReport("END_SEC_T", counter);
134                    } else {
135                        LOG.error("Failed to update Security " + security.getId());
136                        generateExceptionReport("END_SEC_T", security.getId());
137                        success = false; 
138                    }
139                }
140            }
141            
142            LOG.info("Total Security Income Next Pay Dates updated in END_SEC_T: " + counter);
143            
144            return success;
145        }
146        
147        /**
148         * This method updates the next due dates in Tickler
149         */
150        protected boolean updateTicklerNextDueDates() {
151            
152            boolean success = true;
153            
154            int counter = 0;
155            List<Tickler> ticklerRecords = ticklerDao.getTicklerWithNextPayDateEqualToCurrentDate();
156            if (ticklerRecords != null) {
157                for (Tickler tickler : ticklerRecords) {
158                    String frequencyCode = tickler.getFrequencyCode();           
159                    Date nextDate = frequencyDatesService.calculateNextDueDate(frequencyCode, kemService.getCurrentDate()); 
160                    if (nextDate != null) {
161                        tickler.setNextDueDate(nextDate);
162                        if (updateBusinessObject(tickler)) {
163                            counter++;
164                            generateTotalReport("END_TKLR_T", counter);
165                        } else {
166                            LOG.error("Failed to update Tickler " + tickler.getNumber());
167                            generateExceptionReport("END_TKLR_T", tickler.getNumber());
168                            success = false;
169                        }
170                    }                
171                }
172            }
173            
174            LOG.info("Total Tickler Next Due Dates updated in END_TKLR_T: " + counter);
175            
176            return success;
177        }
178        
179        /**
180         * This method updates the next process dates in FeeMethod
181         */
182        protected boolean updateFeeMethodProcessDates() {
183            
184            boolean success = true;
185            
186            int counter = 0;
187            List<FeeMethod> feeMethodRecords = feeMethodDao.getFeeMethodWithNextPayDateEqualToCurrentDate();
188            if (feeMethodRecords != null) {
189                for (FeeMethod feeMethod : feeMethodRecords) {                        
190                    String frequencyCode = feeMethod.getFeeFrequencyCode();           
191                    Date nextDate = frequencyDatesService.calculateNextDueDate(frequencyCode, kemService.getCurrentDate()); 
192                    if (nextDate != null) {
193                        feeMethod.setFeeLastProcessDate(feeMethod.getFeeNextProcessDate());
194                        feeMethod.setFeeNextProcessDate(nextDate);
195                        if (updateBusinessObject(feeMethod)) {
196                            counter++;
197                            generateTotalReport("END_FEE_MTHD_T", counter);
198                        } else {
199                            LOG.error("Failed to update FeeMethod " + feeMethod.getCode());
200                            generateExceptionReport("END_FEE_MTHD_T", feeMethod.getCode());
201                            success = false;
202                        }
203                    }
204                }
205            }
206            
207            LOG.info("Total Fee Next Process Dates and Fee Last Process Dates updated in END_FEE_MTHD_T: " + counter);
208            
209            return success;
210        }
211        
212        /**
213         * This method updates the next process dates in EndowmentRecurringCashTransfer
214         */
215        protected boolean updateRecurringCashTransferProcessDates() {
216            
217            boolean success = true;
218            
219            int counter = 0;
220            List<EndowmentRecurringCashTransfer> recurringCashTransferRecords = recurringCashTransferDao.getRecurringCashTransferWithNextPayDateEqualToCurrentDate();
221            if (recurringCashTransferRecords != null) {
222                for (EndowmentRecurringCashTransfer recurringCashTransfer : recurringCashTransferRecords) {                       
223                    String frequencyCode = recurringCashTransfer.getFrequencyCode();           
224                    Date nextDate = frequencyDatesService.calculateNextDueDate(frequencyCode, kemService.getCurrentDate()); 
225                    if (nextDate != null) {
226                        recurringCashTransfer.setLastProcessDate(recurringCashTransfer.getNextProcessDate());
227                        recurringCashTransfer.setNextProcessDate(nextDate);
228                        if (updateBusinessObject(recurringCashTransfer)) {
229                            counter++;
230                            generateTotalReport("END_REC_CSH_XFR_T", counter);
231                        } else {
232                            LOG.error("Failed to update EndowmentRecurringCashTransfer " + recurringCashTransfer.getTransferNumber());
233                            generateExceptionReport("END_REC_CSH_XFR_T", recurringCashTransfer.getTransferNumber());
234                            success = false;
235                        }
236                    }                
237                }
238            }
239            
240            LOG.info("Total Next Process Dates and Last Process Dates updated in END_REC_CSH_XFR_T: " + counter);
241            
242            return success;
243        }
244     
245        protected boolean updateCashSweepModelNextDueDates() {
246            
247            boolean success = true;
248            
249            int counter = 0;
250            List<CashSweepModel> csmRecords = cashSweepModelDao.getCashSweepModelWithNextPayDateEqualToCurrentDate(kemService.getCurrentDate());
251            if (csmRecords != null) {
252                for (CashSweepModel csm : csmRecords) {                        
253                    String frequencyCode = csm.getCashSweepFrequencyCode();           
254                    Date nextDate = frequencyDatesService.calculateNextDueDate(frequencyCode, kemService.getCurrentDate()); 
255                    if (nextDate != null) {
256                        csm.setCashSweepNextDueDate(nextDate);
257                        if (updateBusinessObject(csm)) {
258                            counter++;
259                            generateTotalReport("END_CSH_SWEEP_MDL_T", counter);
260                        } else {
261                            LOG.error("Failed to update FeeMethod " + csm.getCashSweepModelID());
262                            generateExceptionReport("END_CSH_SWEEP_MDL_T", csm.getCashSweepModelID().toString());
263                            success = false;
264                        }
265                    }
266                }
267            }
268           
269            LOG.info("Total Cash Sweep Model Next Due Dates updated in END_CSH_SWEEP_MDL_T: " + counter);
270            
271            return success;
272        }
273        
274        protected boolean updateAutomatedCashInvestmentModelNextDueDates() {
275            
276            boolean success = true;
277            
278            int counter = 0;
279            List<AutomatedCashInvestmentModel> aciRecords = automatedCashInvestmentModelDao.getAutomatedCashInvestmentModelWithNextPayDateEqualToCurrentDate(kemService.getCurrentDate());
280            if (aciRecords != null) {
281                for (AutomatedCashInvestmentModel aci : aciRecords) {                        
282                    String frequencyCode = aci.getAciFrequencyCode();           
283                    Date nextDate = frequencyDatesService.calculateNextDueDate(frequencyCode, kemService.getCurrentDate()); 
284                    if (nextDate != null) {
285                        aci.setAciNextDueDate(nextDate);
286                        if (updateBusinessObject(aci)) {
287                            counter++;
288                            generateTotalReport("END_AUTO_CSH_INVEST_MDL_T", counter);
289                        } else {
290                            LOG.error("Failed to update FeeMethod " + aci.getAciModelID());
291                            generateExceptionReport("END_AUTO_CSH_INVEST_MDL_T", aci.getAciModelID().toString());
292                            success = false;
293                        }
294                    }
295                }
296            }
297            
298            LOG.info("Total ACI Next Due Dates updated in END_AUTO_CSH_INVEST_MDL_T: " + counter);
299            
300            return success;
301        }
302        
303        /**
304         * Generates the statistic report for updated tables 
305         * @param tableName
306         * @param counter
307         */
308        protected void generateTotalReport(String tableName, int counter) {
309            
310            try {
311                rollFrequencyDatesTotalReportWriterService.writeFormattedMessageLine(tableName + ": %s", counter);            
312            } catch (Exception e) {
313                LOG.error("Failed to generate the statistic report: " + e.getMessage());
314                rollFrequencyDatesExceptionReportWriterService.writeFormattedMessageLine("Failed to generate the total report: " + e.getMessage());
315            }
316        }
317        
318        /**
319         * Generates the exception report 
320         * @param tableName
321         * @param counter
322         */
323        protected void generateExceptionReport(String tableName, String errorMessage) {
324            
325            try {
326                rollFrequencyDatesExceptionReportWriterService.writeFormattedMessageLine(tableName + ": %s", errorMessage);            
327            } catch (Exception e) {
328                LOG.error("Failed to generate the exception report.",e);
329            }
330        }
331        
332        protected void initializeReports() {
333            rollFrequencyDatesTotalReportWriterService.writeSubTitle("<rollFrequencyDatesJob> Number of Records Updated");
334            rollFrequencyDatesTotalReportWriterService.writeNewLines(1);
335            
336            rollFrequencyDatesExceptionReportWriterService.writeSubTitle("<rollFrequencyDatesJob> Records Failed for update");
337            rollFrequencyDatesExceptionReportWriterService.writeNewLines(1);
338        }
339        
340        /**
341         * Updates business object
342         * @param businessObject
343         * @return boolean
344         */
345        protected boolean updateBusinessObject(PersistableBusinessObject businessObject) {
346        
347            boolean result = true;
348            try {
349                businessObjectService.save(businessObject);
350            } catch (Exception e) { // such as IllegalArgumentException
351                LOG.error("Unable to save " + businessObject, e);
352                result = false;
353            }
354            
355            return result;
356        }
357        
358        /**
359         * Sets the businessObjectService attribute value.
360         * @param businessObjectService The businessObjectService to set.
361         */
362        public void setBusinessObjectService(BusinessObjectService businessObjectService) 
363        {
364            this.businessObjectService = businessObjectService;
365        }
366    
367        /**
368         * Sets the kemService attribute value.
369         * @param kemService The kemService to set.
370         */
371        public void setKemService(KEMService kemService) {
372            this.kemService = kemService;
373        }
374        
375        /**
376         * Sets the securityDao attribute value.
377         * @param securityDao The securityDao to set.
378         */
379        public void setSecurityDao(SecurityDao securityDao) {
380            this.securityDao = securityDao;
381        }
382    
383        /**
384         * Sets the feeMethodDao attribute value.
385         * @param feeMethodDao The feeMethodDao to set.
386         */
387        public void setFeeMethodDao(FeeMethodDao feeMethodDao) {
388            this.feeMethodDao = feeMethodDao;
389        }
390    
391        /**
392         * Sets the ticklerDao attribute value.
393         * @param ticklerDao The ticklerDao to set.
394         */
395        public void setTicklerDao(TicklerDao ticklerDao) {
396            this.ticklerDao = ticklerDao;
397        }
398    
399        /**
400         * Sets the recurringCashTransferDao attribute value.
401         * @param recurringCashTransferDao The recurringCashTransferDao to set.
402         */
403        public void setRecurringCashTransferDao(RecurringCashTransferDao recurringCashTransferDao) {
404            this.recurringCashTransferDao = recurringCashTransferDao;
405        }
406    
407        /**
408         * Sets the rollFrequencyDatesTotalReportWriterService attribute value.
409         * @param rollFrequencyDatesTotalReportWriterService The rollFrequencyDatesTotalReportWriterService to set.
410         */
411        public void setRollFrequencyDatesTotalReportWriterService(ReportWriterService rollFrequencyDatesTotalReportWriterService) {
412            this.rollFrequencyDatesTotalReportWriterService = rollFrequencyDatesTotalReportWriterService;
413        }
414    
415        /**
416         * Sets the rollFrequencyDatesExceptionReportWriterService attribute value.
417         * @param rollFrequencyDatesExceptionReportWriterService The rollFrequencyDatesExceptionReportWriterService to set.
418         */
419        public void setRollFrequencyDatesExceptionReportWriterService(ReportWriterService rollFrequencyDatesExceptionReportWriterService) {
420            this.rollFrequencyDatesExceptionReportWriterService = rollFrequencyDatesExceptionReportWriterService;
421        }
422    
423        /**
424         * Sets the automatedCashInvestmentModelDao attribute value.
425         * @param automatedCashInvestmentModelDao The automatedCashInvestmentModelDao to set.
426         */
427        public void setAutomatedCashInvestmentModelDao(AutomatedCashInvestmentModelDao automatedCashInvestmentModelDao) {
428            this.automatedCashInvestmentModelDao = automatedCashInvestmentModelDao;
429        }
430    
431        /**
432         * Sets the cashSweepModelDao attribute value.
433         * @param cashSweepModelDao The cashSweepModelDao to set.
434         */
435        public void setCashSweepModelDao(CashSweepModelDao cashSweepModelDao) {
436            this.cashSweepModelDao = cashSweepModelDao;
437        }
438        
439        /**
440         * Gets the frequencyDatesService attribute. 
441         * @return Returns the frequencyDatesService.
442         */
443        protected FrequencyDatesService getFrequencyDatesService() {
444            return frequencyDatesService;
445        }
446    
447        /**
448         * Sets the frequencyDatesService attribute value.
449         * @param frequencyDatesService The frequencyDatesService to set.
450         */
451        public void setFrequencyDatesService(FrequencyDatesService frequencyDatesService) {
452            this.frequencyDatesService = frequencyDatesService;
453        }
454    }