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.Calendar;
022    import java.util.Collection;
023    import java.util.List;
024    
025    import org.kuali.kfs.module.endow.EndowConstants;
026    import org.kuali.kfs.module.endow.batch.service.AccrualProcessingService;
027    import org.kuali.kfs.module.endow.businessobject.AccrualsProcessingTotalReportLine;
028    import org.kuali.kfs.module.endow.businessobject.ClassCode;
029    import org.kuali.kfs.module.endow.businessobject.HoldingTaxLot;
030    import org.kuali.kfs.module.endow.businessobject.Security;
031    import org.kuali.kfs.module.endow.document.service.ClassCodeService;
032    import org.kuali.kfs.module.endow.document.service.HoldingTaxLotService;
033    import org.kuali.kfs.module.endow.document.service.KEMService;
034    import org.kuali.kfs.module.endow.document.service.SecurityService;
035    import org.kuali.kfs.module.endow.util.KEMCalculationRoundingHelper;
036    import org.kuali.kfs.sys.service.ReportWriterService;
037    import org.kuali.rice.kns.service.BusinessObjectService;
038    import org.springframework.transaction.annotation.Transactional;
039    
040    @Transactional
041    public class AccrualProcessingServiceImpl implements AccrualProcessingService {
042    
043        protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccrualProcessingServiceImpl.class);
044    
045        private ClassCodeService classCodeService;
046        private SecurityService securityService;
047        private HoldingTaxLotService holdingTaxLotService;
048        private KEMService kemService;
049        private BusinessObjectService businessObjectService;
050    
051        private ReportWriterService accrualProcessingReportWriterService;
052        private AccrualsProcessingTotalReportLine totalReportLine = null;
053    
054        private boolean isFistTimeForWritingTotalReport = true;
055    
056        /**
057         * @see org.kuali.kfs.module.endow.batch.service.AccrualProcessingService#processAccruals()
058         */
059        public boolean processAccruals() {
060    
061            boolean success = true;
062            List<Security> securities = getSecuritiesToProcess();
063    
064            for (Security security : securities) {
065                if (EndowConstants.AccrualMethod.AUTOMATED_CASH_MANAGEMENT.equals(security.getClassCode().getAccrualMethod().getCode())) {
066                    processAccrualForAutomatedCashManagement(security);
067                }
068                if (EndowConstants.AccrualMethod.TIME_DEPOSITS.equals(security.getClassCode().getAccrualMethod().getCode())) {
069                    processAccrualForTimeDeposits(security);
070                }
071                if (EndowConstants.AccrualMethod.TREASURY_NOTES_AND_BONDS.equals(security.getClassCode().getAccrualMethod().getCode())) {
072    
073                    boolean skipEntry = false;
074    
075                    // IF the END_SEC_T: SEC_MAT_DT is less than the current date, skip the record and do not accrue income.
076                    if (security.getMaturityDate() != null && security.getMaturityDate().before(kemService.getCurrentDate())) {
077                        skipEntry = true;
078                    }
079    
080                    if (!skipEntry) {
081                        processAccrualForTreasuryNotesAndBonds(security);
082                    }
083                }
084                if (EndowConstants.AccrualMethod.DIVIDENDS.equals(security.getClassCode().getAccrualMethod().getCode())) {
085                    processAccrualForDividends(security);
086                }
087            }
088    
089            return success;
090        }
091    
092        /**
093         * Gets all the securities for which the class code has an accrual method of Automated Cash Management, Time Deposits, Treasury
094         * Notes and Bonds or Dividends.
095         * 
096         * @return all securities that meet the criteria
097         */
098        protected List<Security> getSecuritiesToProcess() {
099            LOG.info("Get all securities for which the class codes have an accrual method of Automated Cash Management, Time Deposits, Treasury Notes and Bonds or Dividends.");
100    
101            // get all class codes with an accrual method of Automated Cash Management, Time Deposits, Treasury Notes and Bonds,
102            // Dividends
103            Collection<ClassCode> classCodes = classCodeService.getClassCodesForAccrualProcessing();
104            List<String> classCodesForAccrualProc = new ArrayList<String>();
105    
106            for (ClassCode classCode : classCodes) {
107                classCodesForAccrualProc.add(classCode.getCode());
108            }
109    
110            // get all securities with units greater than zero that have a class code in the above list
111            List<Security> securities = securityService.getSecuritiesByClassCodeWithUnitsGreaterThanZero(classCodesForAccrualProc);
112    
113            LOG.info("Number of securities with class codes have an accrual method of Automated Cash Management, Time Deposits, Treasury Notes and Bonds or Dividends = " + securities.size());
114    
115            return securities;
116        }
117    
118        /**
119         * Processes the accrual for securities that have SEC_ACRL_MTHD equal to A. Captures the END_SEC_T: SEC_RT for the security
120         * record. For each END_HLDG_TAX_LOT_T record for the security where the HLDG_UNITS are greater than zero calculates the accrual
121         * amount as (END_HLDG_TAX_LOT_T: HLDG_UNITS times END_SEC_T: SEC_RT) divided by the number of days in the calendar year (365 or
122         * 366) Add the calculated accrual amount to the value in END_HLDG_TAX_LOT_T: HLDG_ACCRD-INC_DUE.
123         * 
124         * @param security
125         */
126        protected void processAccrualForAutomatedCashManagement(Security security) {
127    
128            LOG.info("Calculate accruals for securities that have accrual method Automated Cash Management.");
129    
130            BigDecimal securityRate = security.getIncomeRate();
131            String accrualMethodName = security.getClassCode().getAccrualMethod().getName();
132            List<HoldingTaxLot> holdingTaxLots = holdingTaxLotService.getTaxLotsPerSecurityIDWithUnitsGreaterThanZero(security.getId());
133    
134            if (holdingTaxLots != null) {
135                // totals reporting
136                initializeTotalReportLine(security.getId(), accrualMethodName);
137                if (isFistTimeForWritingTotalReport) {
138                    accrualProcessingReportWriterService.writeTableHeader(totalReportLine);
139                    isFistTimeForWritingTotalReport = false;
140                }
141    
142                // acrual processing
143                for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
144                    // compute accrual amount= (security rate * holding units)/nr of days in year
145                    BigDecimal accrualAmount = securityRate.multiply(holdingTaxLot.getUnits());
146                    accrualAmount = KEMCalculationRoundingHelper.divide(accrualAmount, new BigDecimal(kemService.getNumberOfDaysInCalendarYear()), 5);
147                    // set holding tax lot new accrual amount
148                    holdingTaxLot.setCurrentAccrual(holdingTaxLot.getCurrentAccrual().add(accrualAmount));
149                    // save updated tax lot
150                    businessObjectService.save(holdingTaxLot);
151    
152                    // update total reporting
153                    totalReportLine.addAccrualAmount(accrualAmount);
154                }
155                // write total report line
156                accrualProcessingReportWriterService.writeTableRow(totalReportLine);
157    
158                LOG.info("Number of tax lots that have accrual amount updated for secirity id = " + security.getId() + " with accrual method = Automated Cash Management is " + holdingTaxLots.size());
159            }
160    
161    
162        }
163    
164        /**
165         * Processes accrual for securities with SEC_ACRL_MTHD equal to M. Capture the END_SEC_T: SEC_RT for the security record. For
166         * each END_HLDG_TAX_LOT_T record for the security where the HLDG_UNITS are greater than zero calculate the accrual amount as
167         * (END_HLDG_TAX_LOT_T: HLDG_UNITS times END_SEC_T: SEC_RT) divided by the number of days in the calendar year (365 or 366) Add
168         * the calculated accrual amount to the value in END_HLDG_TAX_LOT_T: HLDG_ACCRD-INC_DUE.
169         * 
170         * @param security
171         */
172        protected void processAccrualForTimeDeposits(Security security) {
173    
174            LOG.info("Calculate accruals for securities that have accrual method Time Deposits.");
175    
176            BigDecimal securityRate = security.getIncomeRate();
177            String accrualMethodName = security.getClassCode().getAccrualMethod().getName();
178            List<HoldingTaxLot> holdingTaxLots = holdingTaxLotService.getTaxLotsPerSecurityIDWithUnitsGreaterThanZero(security.getId());
179    
180            if (holdingTaxLots != null) {
181                // totals reporting
182                initializeTotalReportLine(security.getId(), accrualMethodName);
183                if (isFistTimeForWritingTotalReport) {
184                    accrualProcessingReportWriterService.writeTableHeader(totalReportLine);
185                    isFistTimeForWritingTotalReport = false;
186                }
187    
188                // accrual processing
189                for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
190                    // compute accrual amount= (security rate * holding units)/nr of days in year
191                    BigDecimal accrualAmount = securityRate.multiply(holdingTaxLot.getUnits());
192                    accrualAmount = KEMCalculationRoundingHelper.divide(accrualAmount, new BigDecimal(kemService.getNumberOfDaysInCalendarYear()), 5);
193                    // set holding tax lot new accrual amount
194                    holdingTaxLot.setCurrentAccrual(holdingTaxLot.getCurrentAccrual().add(accrualAmount));
195                    // save updated tax lot
196                    businessObjectService.save(holdingTaxLot);
197                    // update total reporting
198                    totalReportLine.addAccrualAmount(accrualAmount);
199                }
200                // write total report line
201                accrualProcessingReportWriterService.writeTableRow(totalReportLine);
202    
203                LOG.info("Number of tax lots that have accrual amount updated for secirity id = " + security.getId() + " with accrual method = Time Deposits is " + holdingTaxLots.size());
204            }
205    
206        }
207    
208        /**
209         * Processes accrual for securities with SEC_ACRL_MTHD equal to T. From the END_SEC_T: record, 1. Capture the END_SEC_T: SEC_RT
210         * for the security record 2. Establish the calculation interval by counting the number of days between the last date income was
211         * paid until the next (END_SEC_T: SEC_INC_PAY_DT) using the frequency code for the security (END_SEC_T: SEC_INC_PAY_FREQ). For
212         * each END_HLDG_TAX_LOT_T record for the security where the HLDG_UNITS are greater than zero calculate the accrual amount as
213         * (END_HLDG_TAX_LOT_T: HLDG_UNITS) times [(END_SEC_T: SEC_RT) divided by 2) divided by the number of days in the calculation
214         * interval. Add the calculated accrual amount to the value in END_HLDG_TAX_LOT_T: HLDG_ACCRD-INC_DUE.
215         * 
216         * @param security
217         */
218        protected void processAccrualForTreasuryNotesAndBonds(Security security) {
219    
220            LOG.info("Calculate accruals for securities that have accrual method Treasury Notes and Bonds.");
221    
222            BigDecimal securityRate = security.getIncomeRate();
223            Date nextIncomePayDate = security.getIncomeNextPayDate();
224            String incomePayFrequency = security.getIncomePayFrequency();
225            String accrualMethodName = security.getClassCode().getAccrualMethod().getName();
226    
227            // if security has an income pay frequency set
228            if (incomePayFrequency != null && !incomePayFrequency.isEmpty()) {
229                // compute the number of days since last time income was paid
230                long nrOfDays = getNumberOfDaysSinceLastDateIncomeWasPaid(incomePayFrequency, nextIncomePayDate);
231                // get all tax lots for security that have units greater than zero
232                List<HoldingTaxLot> holdingTaxLots = holdingTaxLotService.getTaxLotsPerSecurityIDWithUnitsGreaterThanZero(security.getId());
233    
234                if (holdingTaxLots != null) {
235    
236                    // totals reporting
237                    initializeTotalReportLine(security.getId(), accrualMethodName);
238                    if (isFistTimeForWritingTotalReport) {
239                        accrualProcessingReportWriterService.writeTableHeader(totalReportLine);
240                        isFistTimeForWritingTotalReport = false;
241                    }
242    
243                    // accruals processing
244                    for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
245    
246                        // compute accrual amount as ((holding units * security rate)/2)/number of days since last income paid date
247                        BigDecimal accrualAmount = (holdingTaxLot.getUnits().multiply(securityRate));
248                        accrualAmount = KEMCalculationRoundingHelper.divide(accrualAmount, new BigDecimal(2), 5);
249                        accrualAmount = KEMCalculationRoundingHelper.divide(accrualAmount, new BigDecimal(nrOfDays), 5);
250    
251                        // set holding tax lot new accrual amount
252                        holdingTaxLot.setCurrentAccrual(holdingTaxLot.getCurrentAccrual().add(accrualAmount));
253                        // save updated tax lot
254                        businessObjectService.save(holdingTaxLot);
255    
256                        // update total reporting
257                        totalReportLine.addAccrualAmount(accrualAmount);
258                    }
259    
260                    // write total reporting line
261                    accrualProcessingReportWriterService.writeTableRow(totalReportLine);
262    
263                    LOG.info("Number of tax lots that have accrual amount updated for secirity id = " + security.getId() + " with accrual method = Treasury Notes and Bonds is " + holdingTaxLots.size());
264                }
265    
266            }
267    
268        }
269    
270        /**
271         * Gets the number of days since the last date the income was paid based on a frequency code and the next payment date.
272         * 
273         * @param frequencyCode
274         * @param nextIncomePayDate
275         * @return number of days since the last date the income was paid
276         */
277        protected long getNumberOfDaysSinceLastDateIncomeWasPaid(String incomePayFrequency, Date nextIncomePayDate) {
278            long nrOfDays = 0;
279            long milPerDay = 1000 * 60 * 60 * 24;
280    
281            String frequencyType = incomePayFrequency.substring(0, 1);
282    
283            // since this method is only used for treasury notes and bonds we only and Income is paid on these instruments semiannually
284            // we are only interested in semi annually frequency type
285            if (frequencyType.equalsIgnoreCase(EndowConstants.FrequencyTypes.SEMI_ANNUALLY)) {
286    
287                Calendar calendar1 = Calendar.getInstance();
288                calendar1.setTime(nextIncomePayDate);
289    
290                Calendar calendar2 = Calendar.getInstance();
291                calendar2.setTime(nextIncomePayDate);
292                calendar2.add(Calendar.MONTH, -6);
293    
294                // where there is daylight savings time, one day is 23 hours long and another is 25 hours long so we need to include
295                // offset to get the correct number of days; If the dates are in the spring around the "spring forward" date and in the
296                // autumn around the "fall back" date we get inaccurate calculations without using the offset
297                long endL = calendar1.getTimeInMillis() + calendar1.getTimeZone().getOffset(calendar1.getTimeInMillis());
298                long startL = calendar2.getTimeInMillis() + calendar2.getTimeZone().getOffset(calendar2.getTimeInMillis());
299    
300                nrOfDays = (endL - startL) / milPerDay;
301                return nrOfDays;
302            }
303    
304            return nrOfDays;
305        }
306    
307        /**
308         * Processes accruals for securities with SEC_ACRL_MTHD equal to D. Select END_SEC_T: records where SEC_EX_DVDND_DT is equal to
309         * the current date. From the END_SEC_T: record, capture the SEC_DVDND_AMT. For each END_HLDG_TAX_LOT_T record for the security
310         * where the HLDG_UNITS are greater than zero calculate the accrual amount as (END_HLDG_TAX_LOT_T: HLDG_UNITS) times (END_SEC_T:
311         * SEC_DVDND_AMT). Add the calculated accrual amount to the value in END_HLDG_TAX_LOT_T: HLDG_ACCRD-INC_DUE.
312         * 
313         * @param security
314         * @return
315         */
316        protected void processAccrualForDividends(Security security) {
317    
318            LOG.info("Calculate accruals for securities that have accrual method Dividends.");
319    
320            // get security ex divident date
321            Date securityExDividendDate = security.getExDividendDate();
322            String accrualMethodName = security.getClassCode().getAccrualMethod().getName();
323    
324            // if security ex dividend date is equal to current date process accruals, otherwise do nothing
325            if (kemService.getCurrentDate().equals(securityExDividendDate)) {
326                BigDecimal securityDividendAmount = security.getDividendAmount();
327                List<HoldingTaxLot> holdingTaxLots = holdingTaxLotService.getTaxLotsPerSecurityIDWithUnitsGreaterThanZero(security.getId());
328    
329                if (holdingTaxLots != null) {
330    
331                    // totals reporting
332                    initializeTotalReportLine(security.getId(), accrualMethodName);
333                    if (isFistTimeForWritingTotalReport) {
334                        accrualProcessingReportWriterService.writeTableHeader(totalReportLine);
335                        isFistTimeForWritingTotalReport = false;
336                    }
337    
338                    // accrual processing
339                    for (HoldingTaxLot holdingTaxLot : holdingTaxLots) {
340                        // calculate the accrual amound as: holding units * security dividend amount
341                        BigDecimal accrualAmount = KEMCalculationRoundingHelper.multiply(holdingTaxLot.getUnits(), securityDividendAmount, 5);
342                        // set holding tax lot new accrual amount
343                        holdingTaxLot.setCurrentAccrual(holdingTaxLot.getCurrentAccrual().add(accrualAmount));
344                        // save updated tax lot
345                        businessObjectService.save(holdingTaxLot);
346    
347                        // update totals reporting
348                        totalReportLine.addAccrualAmount(accrualAmount);
349                    }
350                    // write total reporting line
351                    accrualProcessingReportWriterService.writeTableRow(totalReportLine);
352    
353                    LOG.info("Number of tax lots that have accrual amount updated for security id = " + security.getId() + " with accrual method = Dividends is " + holdingTaxLots.size());
354                }
355            }
356        }
357    
358        /**
359         * Sets the classCodeService.
360         * 
361         * @param classCodeService
362         */
363        public void setClassCodeService(ClassCodeService classCodeService) {
364            this.classCodeService = classCodeService;
365        }
366    
367        /**
368         * Sets the holdingTaxLotService.
369         * 
370         * @param holdingTaxLotService
371         */
372        public void setHoldingTaxLotService(HoldingTaxLotService holdingTaxLotService) {
373            this.holdingTaxLotService = holdingTaxLotService;
374        }
375    
376        /**
377         * Sets the kemService.
378         * 
379         * @param kemService
380         */
381        public void setKemService(KEMService kemService) {
382            this.kemService = kemService;
383        }
384    
385        /**
386         * Sets the businessObjectService.
387         * 
388         * @param businessObjectService
389         */
390        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
391            this.businessObjectService = businessObjectService;
392        }
393    
394        /**
395         * Sets the securityService.
396         * 
397         * @param securityService
398         */
399        public void setSecurityService(SecurityService securityService) {
400            this.securityService = securityService;
401        }
402    
403        /**
404         * Sets the accrualProcessingReportWriterService.
405         * 
406         * @param accrualProcessingReportWriterService
407         */
408        public void setAccrualProcessingReportWriterService(ReportWriterService accrualProcessingReportWriterService) {
409            this.accrualProcessingReportWriterService = accrualProcessingReportWriterService;
410        }
411    
412        /**
413         * Creates a new AccrualsProcessingTotalReportLine.
414         * 
415         * @param securityId
416         * @param accrualMethod
417         */
418        protected void initializeTotalReportLine(String securityId, String accrualMethod) {
419            // create a new totalReportLine
420            this.totalReportLine = new AccrualsProcessingTotalReportLine(securityId, accrualMethod);
421        }
422    }