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 }