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.gl.batch.service.impl; 017 018 import java.io.File; 019 import java.io.IOException; 020 import java.io.PrintStream; 021 import java.sql.Date; 022 import java.util.ArrayList; 023 import java.util.Collection; 024 import java.util.Iterator; 025 import java.util.LinkedHashMap; 026 import java.util.Map; 027 028 import org.apache.commons.lang.StringUtils; 029 import org.kuali.kfs.gl.GeneralLedgerConstants; 030 import org.kuali.kfs.gl.businessobject.Entry; 031 import org.kuali.kfs.gl.businessobject.OriginEntryFull; 032 import org.kuali.kfs.gl.businessobject.OriginEntryInformation; 033 import org.kuali.kfs.gl.businessobject.PendingEntrySummary; 034 import org.kuali.kfs.gl.report.LedgerSummaryReport; 035 import org.kuali.kfs.gl.service.NightlyOutService; 036 import org.kuali.kfs.gl.service.OriginEntryGroupService; 037 import org.kuali.kfs.gl.service.OriginEntryService; 038 import org.kuali.kfs.sys.KFSConstants; 039 import org.kuali.kfs.sys.KFSPropertyConstants; 040 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry; 041 import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService; 042 import org.kuali.kfs.sys.service.ReportWriterService; 043 import org.kuali.rice.kns.service.DataDictionaryService; 044 import org.kuali.rice.kns.service.DateTimeService; 045 import org.kuali.rice.kns.util.KualiDecimal; 046 import org.kuali.rice.kns.util.ObjectUtils; 047 import org.kuali.rice.kns.web.format.CurrencyFormatter; 048 import org.springframework.transaction.annotation.Transactional; 049 050 /** 051 * This class implements the nightly out batch job. 052 */ 053 @Transactional 054 public class NightlyOutServiceImpl implements NightlyOutService { 055 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(NightlyOutServiceImpl.class); 056 057 private GeneralLedgerPendingEntryService generalLedgerPendingEntryService; 058 private OriginEntryService originEntryService; 059 private DateTimeService dateTimeService; 060 private OriginEntryGroupService originEntryGroupService; 061 private String batchFileDirectoryName; 062 private ReportWriterService pendingEntryListReportWriterService; 063 private ReportWriterService pendingEntrySummaryReportWriterService; 064 private DataDictionaryService dataDictionaryService; 065 066 /** 067 * Constructs a NightlyOutServiceImpl instance 068 */ 069 public NightlyOutServiceImpl() { 070 } 071 072 /** 073 * Deletes all the pending general ledger entries that have now been copied to origin entries 074 * @see org.kuali.kfs.gl.service.NightlyOutService#deleteCopiedPendingLedgerEntries() 075 */ 076 public void deleteCopiedPendingLedgerEntries() { 077 LOG.debug("deleteCopiedPendingLedgerEntries() started"); 078 079 generalLedgerPendingEntryService.deleteByFinancialDocumentApprovedCode(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.PROCESSED); 080 } 081 082 /** 083 * Copies the approved pending ledger entries to origin entry table and generates a report 084 * @see org.kuali.kfs.gl.service.NightlyOutService#copyApprovedPendingLedgerEntries() 085 */ 086 public void copyApprovedPendingLedgerEntries() { 087 if (LOG.isDebugEnabled()) { 088 LOG.debug("copyApprovedPendingLedgerEntries() started"); 089 } 090 Date today = new Date(dateTimeService.getCurrentTimestamp().getTime()); 091 092 Iterator pendingEntries = generalLedgerPendingEntryService.findApprovedPendingLedgerEntries(); 093 String outputFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.NIGHTLY_OUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION ; 094 PrintStream outputFilePs; 095 096 try { 097 outputFilePs = new PrintStream(outputFile); 098 } 099 catch (IOException ioe) { 100 throw new RuntimeException("Cannot open output file "+outputFile+" for writing", ioe); 101 } 102 103 EntryListReport entryListReport = new EntryListReport(); 104 LedgerSummaryReport nightlyOutLedgerSummaryReport = new LedgerSummaryReport(); 105 106 Collection<OriginEntryFull> group = new ArrayList(); 107 while (pendingEntries.hasNext()) { 108 // get one pending entry 109 GeneralLedgerPendingEntry pendingEntry = (GeneralLedgerPendingEntry) pendingEntries.next(); 110 111 OriginEntryFull entry = new OriginEntryFull(pendingEntry); 112 113 // write entry to reports 114 entryListReport.writeEntry(entry, pendingEntryListReportWriterService); 115 nightlyOutLedgerSummaryReport.summarizeEntry(entry); 116 117 group.add(entry); 118 119 // copy the pending entry to text file 120 try { 121 outputFilePs.printf("%s\n", entry.getLine()); 122 } catch (Exception e) { 123 throw new RuntimeException(e); 124 } 125 126 // update the pending entry to indicate it has been copied 127 pendingEntry.setFinancialDocumentApprovedCode(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.PROCESSED); 128 pendingEntry.setTransactionDate(today); 129 130 generalLedgerPendingEntryService.save(pendingEntry); 131 } 132 133 outputFilePs.close(); 134 135 //create done file 136 String doneFileName = outputFile.replace(GeneralLedgerConstants.BatchFileSystem.EXTENSION, GeneralLedgerConstants.BatchFileSystem.DONE_FILE_EXTENSION); 137 File doneFile = new File (doneFileName); 138 if (!doneFile.exists()){ 139 try { 140 doneFile.createNewFile(); 141 } catch (IOException e) { 142 throw new RuntimeException(); 143 } 144 } 145 146 // finish writing reports 147 entryListReport.writeReportFooter(pendingEntryListReportWriterService); 148 nightlyOutLedgerSummaryReport.writeReport(pendingEntrySummaryReportWriterService); 149 } 150 151 152 153 public void setGeneralLedgerPendingEntryService(GeneralLedgerPendingEntryService generalLedgerPendingEntryService) { 154 this.generalLedgerPendingEntryService = generalLedgerPendingEntryService; 155 } 156 157 public void setOriginEntryService(OriginEntryService originEntryService) { 158 this.originEntryService = originEntryService; 159 } 160 161 public void setOriginEntryGroupService(OriginEntryGroupService originEntryGroupService) { 162 this.originEntryGroupService = originEntryGroupService; 163 } 164 165 public void setDateTimeService(DateTimeService dateTimeService) { 166 this.dateTimeService = dateTimeService; 167 } 168 169 public void setBatchFileDirectoryName(String batchFileDirectoryName) { 170 this.batchFileDirectoryName = batchFileDirectoryName; 171 } 172 173 /** 174 * Gets the pendingEntryListReportWriterService attribute. 175 * @return Returns the pendingEntryListReportWriterService. 176 */ 177 public ReportWriterService getPendingEntryListReportWriterService() { 178 return pendingEntryListReportWriterService; 179 } 180 181 /** 182 * Sets the pendingEntryListReportWriterService attribute value. 183 * @param pendingEntryListReportWriterService The pendingEntryListReportWriterService to set. 184 */ 185 public void setPendingEntryListReportWriterService(ReportWriterService pendingEntryListReportWriterService) { 186 this.pendingEntryListReportWriterService = pendingEntryListReportWriterService; 187 } 188 189 /** 190 * Gets the pendingEntrySummaryReportWriterService attribute. 191 * @return Returns the pendingEntrySummaryReportWriterService. 192 */ 193 public ReportWriterService getPendingEntrySummaryReportWriterService() { 194 return pendingEntrySummaryReportWriterService; 195 } 196 197 /** 198 * Sets the pendingEntrySummaryReportWriterService attribute value. 199 * @param pendingEntrySummaryReportWriterService The pendingEntrySummaryReportWriterService to set. 200 */ 201 public void setPendingEntrySummaryReportWriterService(ReportWriterService pendingEntrySummaryReportWriterService) { 202 this.pendingEntrySummaryReportWriterService = pendingEntrySummaryReportWriterService; 203 } 204 205 /** 206 * Gets the dataDictionaryService attribute. 207 * @return Returns the dataDictionaryService. 208 */ 209 public DataDictionaryService getDataDictionaryService() { 210 return dataDictionaryService; 211 } 212 213 /** 214 * Sets the dataDictionaryService attribute value. 215 * @param dataDictionaryService The dataDictionaryService to set. 216 */ 217 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 218 this.dataDictionaryService = dataDictionaryService; 219 } 220 221 /** 222 * A helper class which writes out the nightly out entry list report 223 */ 224 protected class EntryListReport { 225 private PendingEntrySummary pendingEntrySummary; 226 private EntryReportTotalLine totalLine; 227 private Map<String, EntryReportDocumentTypeTotalLine> documentTypeTotals; 228 private EntryReportDocumentNumberTotalLine documentNumberTotal; 229 private int entryCount = 0; 230 private String suppressKey = ""; 231 232 /** 233 * Constructs a NightlyOutServiceImpl 234 */ 235 public EntryListReport() { 236 pendingEntrySummary = new PendingEntrySummary(); 237 totalLine = new EntryReportTotalLine(); 238 documentTypeTotals = new LinkedHashMap<String, EntryReportDocumentTypeTotalLine>(); 239 } 240 241 /** 242 * Writes an entry to the list report 243 * @param entry the entry to write 244 * @param reportWriterService the reportWriterService to write the entry to 245 */ 246 public void writeEntry(OriginEntryInformation entry, ReportWriterService reportWriterService) { 247 pendingEntrySummary.setOriginEntry(entry); 248 if (pendingEntrySummary.getSuppressableFieldsAsKey().equals(suppressKey)) { 249 pendingEntrySummary.suppressCommonFields(true); 250 } 251 else if (StringUtils.isNotBlank(suppressKey)) { 252 writeDocumentTotalLine(documentNumberTotal, reportWriterService); 253 documentNumberTotal = new EntryReportDocumentNumberTotalLine(pendingEntrySummary.getConstantDocumentNumber()); 254 } 255 256 if (StringUtils.isBlank(suppressKey)) { 257 documentNumberTotal = new EntryReportDocumentNumberTotalLine(pendingEntrySummary.getConstantDocumentNumber()); 258 reportWriterService.writeTableHeader(pendingEntrySummary); 259 } 260 suppressKey = pendingEntrySummary.getSuppressableFieldsAsKey(); 261 262 reportWriterService.writeTableRow(pendingEntrySummary); 263 264 addPendingEntryToDocumentType(pendingEntrySummary, documentTypeTotals); 265 addSummaryToTotal(pendingEntrySummary, documentNumberTotal); 266 addSummaryToTotal(pendingEntrySummary, totalLine); 267 entryCount += 1; 268 } 269 270 /** 271 * Adds the given pending entry summary to the appropriate doc type's line total 272 * @param pendingEntrySummary the pending entry summary to add 273 * @param docTypeTotals the Map of doc type line total helpers to add the summary to 274 */ 275 protected void addPendingEntryToDocumentType(PendingEntrySummary pendingEntrySummary, Map<String, EntryReportDocumentTypeTotalLine> docTypeTotals) { 276 EntryReportDocumentTypeTotalLine docTypeTotal = docTypeTotals.get(pendingEntrySummary.getConstantDocumentTypeCode()); 277 if (docTypeTotal == null) { 278 docTypeTotal = new EntryReportDocumentTypeTotalLine(pendingEntrySummary.getConstantDocumentTypeCode()); 279 docTypeTotals.put(pendingEntrySummary.getConstantDocumentTypeCode(), docTypeTotal); 280 } 281 addSummaryToTotal(pendingEntrySummary, docTypeTotal); 282 } 283 284 285 /** 286 * Adds the given summary to the correct credit, debit, or budget total in the total line 287 * @param pendingEntrySummary the summary to add 288 * @param totalLine the entry report total line which holds debit, credit, and budget sum totals 289 */ 290 protected void addSummaryToTotal(PendingEntrySummary pendingEntrySummary, EntryReportTotalLine totalLine) { 291 if (pendingEntrySummary.getDebitAmount() != null) { 292 totalLine.addDebitAmount(pendingEntrySummary.getDebitAmount()); 293 } 294 if (pendingEntrySummary.getCreditAmount() != null) { 295 totalLine.addCreditAmount(pendingEntrySummary.getCreditAmount()); 296 } 297 if (pendingEntrySummary.getBudgetAmount() != null) { 298 totalLine.addBudgetAmount(pendingEntrySummary.getBudgetAmount()); 299 } 300 } 301 302 /** 303 * Writes totals for the document number we just finished writing out 304 * 305 * @param documentNumberTotal EntryReportDocumentNumberTotalLine containing totals to write 306 * @param reportWriterService ReportWriterService for writing output to report 307 */ 308 protected void writeDocumentTotalLine(EntryReportDocumentNumberTotalLine documentNumberTotal, ReportWriterService reportWriterService) { 309 final CurrencyFormatter formatter = new CurrencyFormatter(); 310 final int amountLength = getDataDictionaryService().getAttributeMaxLength(Entry.class, KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_AMOUNT); 311 312 reportWriterService.writeNewLines(1); 313 reportWriterService.writeFormattedMessageLine(" Total: %"+amountLength+"s %"+amountLength+"s %"+amountLength+"s", formatter.format(documentNumberTotal.getCreditAmount()), formatter.format(documentNumberTotal.getDebitAmount()), formatter.format(documentNumberTotal.getBudgetAmount())); 314 reportWriterService.writeNewLines(1); 315 } 316 317 /** 318 * Completes the footer summary information for the report 319 * @param reportWriterService the reportWriterService to write the footer to 320 */ 321 public void writeReportFooter(ReportWriterService reportWriterService) { 322 // write the last total line for the last entry, because we have yet to write it 323 if (documentNumberTotal != null) { 324 // dNT may have been null if no entries were processed for the batch 325 writeDocumentTotalLine(documentNumberTotal, reportWriterService); 326 } 327 328 final CurrencyFormatter formatter = new CurrencyFormatter(); 329 final int amountLength = getDataDictionaryService().getAttributeMaxLength(Entry.class, KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_AMOUNT); 330 331 reportWriterService.writeNewLines(1); 332 for (String documentTypeCode : documentTypeTotals.keySet()) { 333 final EntryReportDocumentTypeTotalLine docTypeTotal = documentTypeTotals.get(documentTypeCode); 334 reportWriterService.writeFormattedMessageLine(" Totals for Document Type %4s Cnt %6d: %"+amountLength+"s %"+amountLength+"s %"+amountLength+"s",documentTypeCode, docTypeTotal.getEntryCount(), formatter.format(docTypeTotal.getCreditAmount()), formatter.format(docTypeTotal.getDebitAmount()), formatter.format(docTypeTotal.getBudgetAmount())); 335 } 336 337 reportWriterService.writeNewLines(1); 338 reportWriterService.writeFormattedMessageLine(" Grand Totals Cnt %6d: %"+amountLength+"s %"+amountLength+"s %"+amountLength+"s", new Integer(entryCount), formatter.format(totalLine.getCreditAmount()), formatter.format(totalLine.getDebitAmount()), formatter.format(totalLine.getBudgetAmount())); 339 } 340 341 /** 342 * Summarizes entries for the pending entry view 343 */ 344 protected class EntryReportTotalLine { 345 private KualiDecimal debitAmount = new KualiDecimal("0"); 346 private KualiDecimal creditAmount = new KualiDecimal("0"); 347 private KualiDecimal budgetAmount = new KualiDecimal("0"); 348 349 /** 350 * @return the debit total 351 */ 352 public KualiDecimal getDebitAmount() { 353 return debitAmount; 354 } 355 356 /** 357 * @return the credit total 358 */ 359 public KualiDecimal getCreditAmount() { 360 return creditAmount; 361 } 362 363 /** 364 * @return the budget total 365 */ 366 public KualiDecimal getBudgetAmount() { 367 return budgetAmount; 368 } 369 370 /** 371 * Adds the given amount to the debit total 372 * @param debitAmount the amount to add to the debit total 373 */ 374 public void addDebitAmount(KualiDecimal debitAmount) { 375 this.debitAmount = this.debitAmount.add(debitAmount); 376 } 377 378 /** 379 * Adds the given amount to the credit total 380 * @param creditAmount the amount to add to the credit total 381 */ 382 public void addCreditAmount(KualiDecimal creditAmount) { 383 this.creditAmount = this.creditAmount.add(creditAmount); 384 } 385 386 /** 387 * Adds the given amount to the budget total 388 * @param budgetAmount the amount to add to the budget total 389 */ 390 public void addBudgetAmount(KualiDecimal budgetAmount) { 391 this.budgetAmount = this.budgetAmount.add(budgetAmount); 392 } 393 } 394 395 /** 396 * Summarizes pending entry data per document type 397 */ 398 protected class EntryReportDocumentTypeTotalLine extends EntryReportTotalLine { 399 private String documentTypeCode; 400 private int entryCount = 0; 401 402 /** 403 * Constructs a NightlyOutServiceImpl 404 * @param documentTypeCode the document type code to 405 */ 406 public EntryReportDocumentTypeTotalLine(String documentTypeCode) { 407 this.documentTypeCode = documentTypeCode; 408 } 409 410 /** 411 * @return the document type associated with this summarizer 412 */ 413 public String getDocumentTypeCode() { 414 return this.documentTypeCode; 415 } 416 417 /** 418 * @return the number of entries associated with the current document type 419 */ 420 public int getEntryCount() { 421 return this.entryCount; 422 } 423 424 /** 425 * Overridden to automagically udpate the entry count 426 * @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addBudgetAmount(org.kuali.rice.kns.util.KualiDecimal) 427 */ 428 @Override 429 public void addBudgetAmount(KualiDecimal budgetAmount) { 430 super.addBudgetAmount(budgetAmount); 431 entryCount += 1; 432 } 433 434 /** 435 * Overridden to automagically update the entry count 436 * @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addCreditAmount(org.kuali.rice.kns.util.KualiDecimal) 437 */ 438 @Override 439 public void addCreditAmount(KualiDecimal creditAmount) { 440 super.addCreditAmount(creditAmount); 441 entryCount += 1; 442 } 443 444 /** 445 * Overridden to automagically update the entry count 446 * @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addDebitAmount(org.kuali.rice.kns.util.KualiDecimal) 447 */ 448 @Override 449 public void addDebitAmount(KualiDecimal debitAmount) { 450 super.addDebitAmount(debitAmount); 451 entryCount += 1; 452 } 453 } 454 455 /** 456 * Summarizes pending entry data per document number 457 */ 458 protected class EntryReportDocumentNumberTotalLine extends EntryReportTotalLine { 459 private String documentNumber; 460 private int entryCount = 0; 461 462 /** 463 * Constructs a NightlyOutServiceImpl 464 * @param documentNumber the document number to total 465 */ 466 public EntryReportDocumentNumberTotalLine(String documentNumber) { 467 this.documentNumber = documentNumber; 468 } 469 470 /** 471 * @return the document number associated with this summarizer 472 */ 473 public String getDocumentNumber() { 474 return this.documentNumber; 475 } 476 477 /** 478 * @return the number of entries associated with the current document number 479 */ 480 public int getEntryCount() { 481 return this.entryCount; 482 } 483 484 /** 485 * Overridden to automagically udpate the entry count 486 * @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addBudgetAmount(org.kuali.rice.kns.util.KualiDecimal) 487 */ 488 @Override 489 public void addBudgetAmount(KualiDecimal budgetAmount) { 490 super.addBudgetAmount(budgetAmount); 491 entryCount += 1; 492 } 493 494 /** 495 * Overridden to automagically update the entry count 496 * @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addCreditAmount(org.kuali.rice.kns.util.KualiDecimal) 497 */ 498 @Override 499 public void addCreditAmount(KualiDecimal creditAmount) { 500 super.addCreditAmount(creditAmount); 501 entryCount += 1; 502 } 503 504 /** 505 * Overridden to automagically update the entry count 506 * @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addDebitAmount(org.kuali.rice.kns.util.KualiDecimal) 507 */ 508 @Override 509 public void addDebitAmount(KualiDecimal debitAmount) { 510 super.addDebitAmount(debitAmount); 511 entryCount += 1; 512 } 513 } 514 } 515 }