/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.gl.batch.service.impl;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.gl.GeneralLedgerConstants;
import org.kuali.kfs.gl.businessobject.Entry;
import org.kuali.kfs.gl.businessobject.OriginEntryFull;
import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
import org.kuali.kfs.gl.businessobject.PendingEntrySummary;
import org.kuali.kfs.gl.report.LedgerSummaryReport;
import org.kuali.kfs.gl.service.NightlyOutService;
import org.kuali.kfs.gl.service.OriginEntryGroupService;
import org.kuali.kfs.gl.service.OriginEntryService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService;
import org.kuali.kfs.sys.service.ReportWriterService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.core.web.format.CurrencyFormatter;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.springframework.transaction.annotation.Transactional;
/**
* This class implements the nightly out batch job.
*/
@Transactional
public class NightlyOutServiceImpl implements NightlyOutService {
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(NightlyOutServiceImpl.class);
private GeneralLedgerPendingEntryService generalLedgerPendingEntryService;
private OriginEntryService originEntryService;
private DateTimeService dateTimeService;
private OriginEntryGroupService originEntryGroupService;
private String batchFileDirectoryName;
private ReportWriterService pendingEntryListReportWriterService;
private ReportWriterService pendingEntrySummaryReportWriterService;
private DataDictionaryService dataDictionaryService;
/**
* Constructs a NightlyOutServiceImpl instance
*/
public NightlyOutServiceImpl() {
}
/**
* Deletes all the pending general ledger entries that have now been copied to origin entries
* @see org.kuali.kfs.gl.service.NightlyOutService#deleteCopiedPendingLedgerEntries()
*/
public void deleteCopiedPendingLedgerEntries() {
LOG.debug("deleteCopiedPendingLedgerEntries() started");
generalLedgerPendingEntryService.deleteByFinancialDocumentApprovedCode(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.PROCESSED);
}
/**
* Copies the approved pending ledger entries to origin entry table and generates a report
* @see org.kuali.kfs.gl.service.NightlyOutService#copyApprovedPendingLedgerEntries()
*/
public void copyApprovedPendingLedgerEntries() {
if (LOG.isInfoEnabled()) {
LOG.info("copyApprovedPendingLedgerEntries() started");
}
Date today = new Date(dateTimeService.getCurrentTimestamp().getTime());
Iterator pendingEntries = generalLedgerPendingEntryService.findApprovedPendingLedgerEntries();
String outputFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.NIGHTLY_OUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION ;
PrintStream outputFilePs = null;
try {
outputFilePs = new PrintStream(outputFile);
}
catch (IOException ioe) {
throw new RuntimeException("Cannot open output file "+outputFile+" for writing", ioe);
}
EntryListReport entryListReport = new EntryListReport();
LedgerSummaryReport nightlyOutLedgerSummaryReport = new LedgerSummaryReport();
Collection<OriginEntryFull> group = new ArrayList<OriginEntryFull>();
while (pendingEntries.hasNext()) {
// get one pending entry
GeneralLedgerPendingEntry pendingEntry = (GeneralLedgerPendingEntry) pendingEntries.next();
OriginEntryFull entry = new OriginEntryFull(pendingEntry);
// write entry to reports
entryListReport.writeEntry(entry, pendingEntryListReportWriterService);
nightlyOutLedgerSummaryReport.summarizeEntry(entry);
group.add(entry);
// copy the pending entry to text file
outputFilePs.printf("%s\n", entry.getLine());
// update the pending entry to indicate it has been copied
pendingEntry.setFinancialDocumentApprovedCode(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.PROCESSED);
pendingEntry.setTransactionDate(today);
generalLedgerPendingEntryService.save(pendingEntry);
}
outputFilePs.close();
//create done file
String doneFileName = outputFile.replace(GeneralLedgerConstants.BatchFileSystem.EXTENSION, GeneralLedgerConstants.BatchFileSystem.DONE_FILE_EXTENSION);
File doneFile = new File (doneFileName);
if (!doneFile.exists()){
try {
doneFile.createNewFile();
} catch (IOException e) {
throw new RuntimeException();
}
}
// finish writing reports
entryListReport.writeReportFooter(pendingEntryListReportWriterService);
nightlyOutLedgerSummaryReport.writeReport(pendingEntrySummaryReportWriterService);
}
public void setGeneralLedgerPendingEntryService(GeneralLedgerPendingEntryService generalLedgerPendingEntryService) {
this.generalLedgerPendingEntryService = generalLedgerPendingEntryService;
}
public void setOriginEntryService(OriginEntryService originEntryService) {
this.originEntryService = originEntryService;
}
public void setOriginEntryGroupService(OriginEntryGroupService originEntryGroupService) {
this.originEntryGroupService = originEntryGroupService;
}
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
public void setBatchFileDirectoryName(String batchFileDirectoryName) {
this.batchFileDirectoryName = batchFileDirectoryName;
}
/**
* Gets the pendingEntryListReportWriterService attribute.
* @return Returns the pendingEntryListReportWriterService.
*/
public ReportWriterService getPendingEntryListReportWriterService() {
return pendingEntryListReportWriterService;
}
/**
* Sets the pendingEntryListReportWriterService attribute value.
* @param pendingEntryListReportWriterService The pendingEntryListReportWriterService to set.
*/
public void setPendingEntryListReportWriterService(ReportWriterService pendingEntryListReportWriterService) {
this.pendingEntryListReportWriterService = pendingEntryListReportWriterService;
}
/**
* Gets the pendingEntrySummaryReportWriterService attribute.
* @return Returns the pendingEntrySummaryReportWriterService.
*/
public ReportWriterService getPendingEntrySummaryReportWriterService() {
return pendingEntrySummaryReportWriterService;
}
/**
* Sets the pendingEntrySummaryReportWriterService attribute value.
* @param pendingEntrySummaryReportWriterService The pendingEntrySummaryReportWriterService to set.
*/
public void setPendingEntrySummaryReportWriterService(ReportWriterService pendingEntrySummaryReportWriterService) {
this.pendingEntrySummaryReportWriterService = pendingEntrySummaryReportWriterService;
}
/**
* Gets the dataDictionaryService attribute.
* @return Returns the dataDictionaryService.
*/
public DataDictionaryService getDataDictionaryService() {
return dataDictionaryService;
}
/**
* Sets the dataDictionaryService attribute value.
* @param dataDictionaryService The dataDictionaryService to set.
*/
public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
this.dataDictionaryService = dataDictionaryService;
}
/**
* A helper class which writes out the nightly out entry list report
*/
protected class EntryListReport {
private PendingEntrySummary pendingEntrySummary;
private EntryReportTotalLine totalLine;
private Map<String, EntryReportDocumentTypeTotalLine> documentTypeTotals;
private EntryReportDocumentNumberTotalLine documentNumberTotal;
private int entryCount = 0;
private String suppressKey = "";
/**
* Constructs a NightlyOutServiceImpl
*/
public EntryListReport() {
pendingEntrySummary = new PendingEntrySummary();
totalLine = new EntryReportTotalLine();
documentTypeTotals = new LinkedHashMap<String, EntryReportDocumentTypeTotalLine>();
}
/**
* Writes an entry to the list report
* @param entry the entry to write
* @param reportWriterService the reportWriterService to write the entry to
*/
public void writeEntry(OriginEntryInformation entry, ReportWriterService reportWriterService) {
pendingEntrySummary.setOriginEntry(entry);
if (pendingEntrySummary.getSuppressableFieldsAsKey().equals(suppressKey)) {
pendingEntrySummary.suppressCommonFields(true);
}
else if (StringUtils.isNotBlank(suppressKey)) {
writeDocumentTotalLine(documentNumberTotal, reportWriterService);
documentNumberTotal = new EntryReportDocumentNumberTotalLine(pendingEntrySummary.getConstantDocumentNumber());
}
if (StringUtils.isBlank(suppressKey)) {
documentNumberTotal = new EntryReportDocumentNumberTotalLine(pendingEntrySummary.getConstantDocumentNumber());
reportWriterService.writeTableHeader(pendingEntrySummary);
}
suppressKey = pendingEntrySummary.getSuppressableFieldsAsKey();
reportWriterService.writeTableRow(pendingEntrySummary);
addPendingEntryToDocumentType(pendingEntrySummary, documentTypeTotals);
addSummaryToTotal(pendingEntrySummary, documentNumberTotal);
addSummaryToTotal(pendingEntrySummary, totalLine);
entryCount += 1;
}
/**
* Adds the given pending entry summary to the appropriate doc type's line total
* @param pendingEntrySummary the pending entry summary to add
* @param docTypeTotals the Map of doc type line total helpers to add the summary to
*/
protected void addPendingEntryToDocumentType(PendingEntrySummary pendingEntrySummary, Map<String, EntryReportDocumentTypeTotalLine> docTypeTotals) {
EntryReportDocumentTypeTotalLine docTypeTotal = docTypeTotals.get(pendingEntrySummary.getConstantDocumentTypeCode());
if (docTypeTotal == null) {
docTypeTotal = new EntryReportDocumentTypeTotalLine(pendingEntrySummary.getConstantDocumentTypeCode());
docTypeTotals.put(pendingEntrySummary.getConstantDocumentTypeCode(), docTypeTotal);
}
addSummaryToTotal(pendingEntrySummary, docTypeTotal);
}
/**
* Adds the given summary to the correct credit, debit, or budget total in the total line
* @param pendingEntrySummary the summary to add
* @param totalLine the entry report total line which holds debit, credit, and budget sum totals
*/
protected void addSummaryToTotal(PendingEntrySummary pendingEntrySummary, EntryReportTotalLine totalLine) {
if (pendingEntrySummary.getDebitAmount() != null) {
totalLine.addDebitAmount(pendingEntrySummary.getDebitAmount());
}
if (pendingEntrySummary.getCreditAmount() != null) {
totalLine.addCreditAmount(pendingEntrySummary.getCreditAmount());
}
if (pendingEntrySummary.getBudgetAmount() != null) {
totalLine.addBudgetAmount(pendingEntrySummary.getBudgetAmount());
}
}
/**
* Writes totals for the document number we just finished writing out
*
* @param documentNumberTotal EntryReportDocumentNumberTotalLine containing totals to write
* @param reportWriterService ReportWriterService for writing output to report
*/
protected void writeDocumentTotalLine(EntryReportDocumentNumberTotalLine documentNumberTotal, ReportWriterService reportWriterService) {
final CurrencyFormatter formatter = new CurrencyFormatter();
final int amountLength = getDataDictionaryService().getAttributeMaxLength(Entry.class, KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_AMOUNT);
reportWriterService.writeNewLines(1);
reportWriterService.writeFormattedMessageLine(" Total: %"+amountLength+"s %"+amountLength+"s %"+amountLength+"s", formatter.format(documentNumberTotal.getCreditAmount()), formatter.format(documentNumberTotal.getDebitAmount()), formatter.format(documentNumberTotal.getBudgetAmount()));
reportWriterService.writeNewLines(1);
}
/**
* Completes the footer summary information for the report
* @param reportWriterService the reportWriterService to write the footer to
*/
public void writeReportFooter(ReportWriterService reportWriterService) {
// write the last total line for the last entry, because we have yet to write it
if (documentNumberTotal != null) {
// dNT may have been null if no entries were processed for the batch
writeDocumentTotalLine(documentNumberTotal, reportWriterService);
}
final CurrencyFormatter formatter = new CurrencyFormatter();
final int amountLength = getDataDictionaryService().getAttributeMaxLength(Entry.class, KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_AMOUNT);
reportWriterService.writeNewLines(1);
for (String documentTypeCode : documentTypeTotals.keySet()) {
final EntryReportDocumentTypeTotalLine docTypeTotal = documentTypeTotals.get(documentTypeCode);
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()));
}
reportWriterService.writeNewLines(1);
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()));
}
/**
* Summarizes entries for the pending entry view
*/
protected class EntryReportTotalLine {
private KualiDecimal debitAmount = new KualiDecimal("0");
private KualiDecimal creditAmount = new KualiDecimal("0");
private KualiDecimal budgetAmount = new KualiDecimal("0");
/**
* @return the debit total
*/
public KualiDecimal getDebitAmount() {
return debitAmount;
}
/**
* @return the credit total
*/
public KualiDecimal getCreditAmount() {
return creditAmount;
}
/**
* @return the budget total
*/
public KualiDecimal getBudgetAmount() {
return budgetAmount;
}
/**
* Adds the given amount to the debit total
* @param debitAmount the amount to add to the debit total
*/
public void addDebitAmount(KualiDecimal debitAmount) {
this.debitAmount = this.debitAmount.add(debitAmount);
}
/**
* Adds the given amount to the credit total
* @param creditAmount the amount to add to the credit total
*/
public void addCreditAmount(KualiDecimal creditAmount) {
this.creditAmount = this.creditAmount.add(creditAmount);
}
/**
* Adds the given amount to the budget total
* @param budgetAmount the amount to add to the budget total
*/
public void addBudgetAmount(KualiDecimal budgetAmount) {
this.budgetAmount = this.budgetAmount.add(budgetAmount);
}
}
/**
* Summarizes pending entry data per document type
*/
protected class EntryReportDocumentTypeTotalLine extends EntryReportTotalLine {
private String documentTypeCode;
private int entryCount = 0;
/**
* Constructs a NightlyOutServiceImpl
* @param documentTypeCode the document type code to
*/
public EntryReportDocumentTypeTotalLine(String documentTypeCode) {
this.documentTypeCode = documentTypeCode;
}
/**
* @return the document type associated with this summarizer
*/
public String getDocumentTypeCode() {
return this.documentTypeCode;
}
/**
* @return the number of entries associated with the current document type
*/
public int getEntryCount() {
return this.entryCount;
}
/**
* Overridden to automagically udpate the entry count
* @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addBudgetAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
*/
@Override
public void addBudgetAmount(KualiDecimal budgetAmount) {
super.addBudgetAmount(budgetAmount);
entryCount += 1;
}
/**
* Overridden to automagically update the entry count
* @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addCreditAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
*/
@Override
public void addCreditAmount(KualiDecimal creditAmount) {
super.addCreditAmount(creditAmount);
entryCount += 1;
}
/**
* Overridden to automagically update the entry count
* @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addDebitAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
*/
@Override
public void addDebitAmount(KualiDecimal debitAmount) {
super.addDebitAmount(debitAmount);
entryCount += 1;
}
}
/**
* Summarizes pending entry data per document number
*/
protected class EntryReportDocumentNumberTotalLine extends EntryReportTotalLine {
private String documentNumber;
private int entryCount = 0;
/**
* Constructs a NightlyOutServiceImpl
* @param documentNumber the document number to total
*/
public EntryReportDocumentNumberTotalLine(String documentNumber) {
this.documentNumber = documentNumber;
}
/**
* @return the document number associated with this summarizer
*/
public String getDocumentNumber() {
return this.documentNumber;
}
/**
* @return the number of entries associated with the current document number
*/
public int getEntryCount() {
return this.entryCount;
}
/**
* Overridden to automagically udpate the entry count
* @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addBudgetAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
*/
@Override
public void addBudgetAmount(KualiDecimal budgetAmount) {
super.addBudgetAmount(budgetAmount);
entryCount += 1;
}
/**
* Overridden to automagically update the entry count
* @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addCreditAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
*/
@Override
public void addCreditAmount(KualiDecimal creditAmount) {
super.addCreditAmount(creditAmount);
entryCount += 1;
}
/**
* Overridden to automagically update the entry count
* @see org.kuali.kfs.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addDebitAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
*/
@Override
public void addDebitAmount(KualiDecimal debitAmount) {
super.addDebitAmount(debitAmount);
entryCount += 1;
}
}
}
}