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 }