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 }