/*
* 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.text.MessageFormat;
import java.util.ArrayList;
import java.util.Formattable;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.gl.batch.CollectorBatch;
import org.kuali.kfs.gl.batch.CollectorStep;
import org.kuali.kfs.gl.batch.service.CollectorReportService;
import org.kuali.kfs.gl.businessobject.DemergerReportData;
import org.kuali.kfs.gl.businessobject.OriginEntryFull;
import org.kuali.kfs.gl.businessobject.Transaction;
import org.kuali.kfs.gl.report.CollectorReportData;
import org.kuali.kfs.gl.report.LedgerSummaryReport;
import org.kuali.kfs.gl.report.PreScrubberReport;
import org.kuali.kfs.gl.report.Summary;
import org.kuali.kfs.gl.service.PreScrubberService;
import org.kuali.kfs.gl.service.ScrubberReportData;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSConstants.SystemGroupParameterNames;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.Message;
import org.kuali.kfs.sys.service.ReportWriterService;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.mail.MailMessage;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.core.web.format.CurrencyFormatter;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.krad.service.MailService;
import org.kuali.rice.krad.util.ErrorMessage;
import org.kuali.rice.krad.util.MessageMap;
/**
* The base implementation of the CollectorReportService
*/
public class CollectorReportServiceImpl implements CollectorReportService {
private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CollectorReportServiceImpl.class);
protected DateTimeService dateTimeService;
protected ParameterService parameterService;
protected ConfigurationService configurationService;
protected MailService mailService;
protected PreScrubberService preScrubberService;
protected ReportWriterService collectorReportWriterService;
/**
* Sends out e-mails about the validation and demerger of the Collector run
*
* @param collectorReportData data gathered from the run of the Collector
* @see org.kuali.kfs.gl.batch.service.CollectorReportService#sendEmails(org.kuali.kfs.gl.report.CollectorReportData)
*/
@Override
public void sendEmails(CollectorReportData collectorReportData) {
// send out the validation status messages
Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches();
while (batchIter.hasNext()) {
CollectorBatch batch = batchIter.next();
sendValidationEmail(batch, collectorReportData);
sendDemergerEmail(batch, collectorReportData);
}
sendEmailSendFailureNotice(collectorReportData);
}
/**
* Generates the reports about a given Collector run
*
* @param collectorReportData data gathered from the run of the Collector
* @see org.kuali.kfs.gl.batch.service.CollectorReportService#generateCollectorRunReports(org.kuali.kfs.gl.report.CollectorReportData)
*/
@Override
public void generateCollectorRunReports(CollectorReportData collectorReportData) {
appendCollectorHeaderInformation(collectorReportData);
appendPreScrubberReport(collectorReportData);
appendScrubberReport(collectorReportData);
appendDemergerReport(collectorReportData);
appendDeletedOriginEntryAndDetailReport(collectorReportData);
appendDetailChangedAccountReport(collectorReportData);
appendLedgerReport(collectorReportData);
}
/**
* Appends Collector header information to the report writer
*
* @param collectorReportData data gathered from the run of the Collector
*/
protected void appendCollectorHeaderInformation(CollectorReportData collectorReportData) {
Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches();
OriginEntryTotals aggregateOriginEntryTotals = new OriginEntryTotals();
int aggregateTotalRecordsCountFromTrailer = 0;
int aggregateNumInputDetails = 0;
int aggregateNumSavedDetails = 0;
if (!collectorReportData.getAllUnparsableFileNames().isEmpty()) {
collectorReportWriterService.writeFormattedMessageLine("The following files could not be parsed:\n\n");
for (String unparsableFileName : collectorReportData.getAllUnparsableFileNames()) {
List<String> batchErrors = translateErrorsFromMessageMap(collectorReportData.getMessageMapForFileName(unparsableFileName));
collectorReportWriterService.writeFormattedMessageLine(" " + unparsableFileName + "\n");
for (String errorMessage : batchErrors) {
collectorReportWriterService.writeFormattedMessageLine(" - ERROR MESSAGE: " + errorMessage);
}
}
}
while (batchIter.hasNext()) {
CollectorBatch batch = batchIter.next();
StringBuilder buf = new StringBuilder();
OriginEntryTotals batchOriginEntryTotals = batch.getOriginEntryTotals();
appendHeaderInformation(buf, batch, collectorReportData);
appendTotalsInformation(buf, batch);
List<String> errorMessages = translateErrorsFromMessageMap(batch.getMessageMap());
aggregateTotalRecordsCountFromTrailer += batch.getTotalRecords();
// if batch is valid add up totals
if (collectorReportData.isBatchValid(batch)) {
if (batchOriginEntryTotals != null) {
aggregateOriginEntryTotals.incorporateTotals(batchOriginEntryTotals);
}
Integer batchNumInputDetails = collectorReportData.getNumInputDetails(batch);
if (batchNumInputDetails != null) {
aggregateNumInputDetails += batchNumInputDetails;
}
Integer batchNumSavedDetails = collectorReportData.getNumSavedDetails(batch);
if (batchNumSavedDetails != null) {
aggregateNumSavedDetails += batchNumSavedDetails;
}
}
collectorReportWriterService.writeFormattedMessageLine("Header *********************************************************************");
collectorReportWriterService.writeMultipleFormattedMessageLines(buf.toString());
String validationErrors = getValidationStatus(errorMessages, false, 15);
if (StringUtils.isNotBlank(validationErrors)) {
collectorReportWriterService.writeMultipleFormattedMessageLines(validationErrors);
}
}
collectorReportWriterService.writeNewLines(2);
collectorReportWriterService.writeFormattedMessageLine("***** Totals for Creation of GLE Data *****");
collectorReportWriterService.writeFormattedMessageLine(" Total Records Read %09d", aggregateTotalRecordsCountFromTrailer);
collectorReportWriterService.writeFormattedMessageLine(" Total Groups Read %09d", collectorReportData.getNumPersistedBatches());
collectorReportWriterService.writeFormattedMessageLine(" Total Groups Bypassed %09d", collectorReportData.getNumNotPersistedBatches());
int totalRecordsBypassed = collectorReportData.getNumNotPersistedOriginEntryRecords() + collectorReportData.getNumNotPersistedCollectorDetailRecords();
collectorReportWriterService.writeFormattedMessageLine(" Total Records Bypassed %09d", totalRecordsBypassed);
collectorReportWriterService.writeFormattedMessageLine(" Total WWW Records Out %09d", aggregateNumInputDetails);
int aggregateOriginEntryCountFromParsedData = aggregateOriginEntryTotals.getNumCreditEntries() + aggregateOriginEntryTotals.getNumDebitEntries() + aggregateOriginEntryTotals.getNumOtherEntries();
collectorReportWriterService.writeFormattedMessageLine(" Total GLE Records Out %09d", aggregateOriginEntryCountFromParsedData);
collectorReportWriterService.writeFormattedMessageLine(" Total GLE Debits %19s", new KualiDecimalFormatter(aggregateOriginEntryTotals.getDebitAmount()));
collectorReportWriterService.writeFormattedMessageLine(" Debit Count %09d", aggregateOriginEntryTotals.getNumDebitEntries());
collectorReportWriterService.writeFormattedMessageLine(" Total GLE Credits %19s", new KualiDecimalFormatter(aggregateOriginEntryTotals.getCreditAmount()));
collectorReportWriterService.writeFormattedMessageLine(" Credit Count %09d", aggregateOriginEntryTotals.getNumCreditEntries());
collectorReportWriterService.writeFormattedMessageLine(" Total GLE Not C or D %19s", new KualiDecimalFormatter(aggregateOriginEntryTotals.getOtherAmount()));
collectorReportWriterService.writeFormattedMessageLine(" Not C or D Count %09d", aggregateOriginEntryTotals.getNumOtherEntries());
collectorReportWriterService.writeNewLines(1);
collectorReportWriterService.writeFormattedMessageLine("Inserted %d detail records into gl_id_bill_t", aggregateNumSavedDetails);
}
/**
* Appends header information to the given buffer
*
* @param buf the buffer where the message should go
* @param batch the data from the Collector file
*/
protected void appendHeaderInformation(StringBuilder buf, CollectorBatch batch, CollectorReportData collectorReportData) {
String emailBatchStatus = collectorReportData.getEmailSendingStatus().get(batch.getBatchName());
buf.append("\n Chart: ").append(batch.getChartOfAccountsCode()).append("\n");
buf.append(" Org: ").append(batch.getOrganizationCode()).append("\n");
buf.append(" Campus: ").append(batch.getCampusCode()).append("\n");
buf.append(" Department: ").append(batch.getDepartmentName()).append("\n");
buf.append(" Mailing Address: ").append(batch.getMailingAddress()).append("\n");
buf.append(" Contact: ").append(batch.getPersonUserID()).append("\n");
buf.append(" Email: ").append(batch.getEmailAddress());
if (StringUtils.isNotEmpty(emailBatchStatus)){
String displayStatus = StringUtils.containsIgnoreCase(emailBatchStatus, "ERROR")? "**Email Failure" : "**Email Success";
buf.append(" ( " + displayStatus + " )").append("\n");
}else{
buf.append("\n");
}
buf.append(" Transmission Date: ").append(batch.getTransmissionDate()).append("\n\n");
}
/**
* Writes totals information to the report
*
* @param buf the buffer where the e-mail report is being written
* @param batch the data generated by the Collector file upload
* @param totals the totals to write
*/
protected void appendTotalsInformation(StringBuilder buf, CollectorBatch batch) {
OriginEntryTotals totals = batch.getOriginEntryTotals();
if (totals == null) {
buf.append(" Totals are unavailable for this batch.\n");
}
else {
// SUMMARY TOTALS HERE
appendAmountCountLine(buf, "Group Credits = ", Integer.toString(totals.getNumCreditEntries()), totals.getCreditAmount());
appendAmountCountLine(buf, "Group Debits = ", Integer.toString(totals.getNumDebitEntries()), totals.getDebitAmount());
appendAmountCountLine(buf, "Group Not C/D = ", Integer.toString(totals.getNumOtherEntries()), totals.getOtherAmount());
appendAmountCountLine(buf, "Valid Group Count = ", batch.getTotalRecords().toString(), batch.getTotalAmount());
}
}
/**
* Writes the Amount/Count line of the Collector to a buffer
*
* @param buf the buffer to write the line to
* @param countTitle the title of this part of the report
* @param count the Collector count
* @param amountString the Collector amount
*/
protected void appendAmountCountLine(StringBuilder buf, String countTitle, String count, KualiDecimal amount) {
appendPaddingString(buf, ' ', countTitle.length(), 35);
buf.append(countTitle);
appendPaddingString(buf, '0', count.length(), 5);
buf.append(count);
if (amount == null) {
buf.append(StringUtils.leftPad("N/A", 21));
}
else {
Map<String, String> settings = new HashMap<String, String>();
settings.put(CurrencyFormatter.SHOW_SYMBOL, Boolean.TRUE.toString());
org.kuali.rice.core.web.format.Formatter f = org.kuali.rice.core.web.format.Formatter.getFormatter(KualiDecimal.class, settings);
String amountString = (String) f.format(amount);
appendPaddingString(buf, ' ', amountString.length(), 21);
buf.append(amountString);
}
buf.append("\n");
}
/**
* Writes some padding to a buffer
*
* @param buf the buffer to write to
* @param padCharacter the character to repeat in the pad
* @param valueLength the length of the value being padded
* @param desiredLength the length the whole String should be
* @return the buffer
*/
protected StringBuilder appendPaddingString(StringBuilder buf, char padCharacter, int valueLength, int desiredLength) {
for (int i = valueLength; i < desiredLength; i++) {
buf.append(padCharacter);
}
return buf;
}
protected void appendPreScrubberReport(CollectorReportData collectorReportData) {
if (preScrubberService.deriveChartOfAccountsCodeIfSpaces()) {
collectorReportWriterService.pageBreak();
collectorReportWriterService.writeSubTitle("Collector Pre-Scrubber Report");
new PreScrubberReport().generateReport(collectorReportData.getPreScrubberReportData(), collectorReportWriterService);
}
}
/**
* Writes the results of the Scrubber's run on the Collector data to the report writer
*
* @param collectorReportData data gathered from the run of the Collector
*/
protected void appendScrubberReport(CollectorReportData collectorReportData) {
Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches();
ScrubberReportData aggregateScrubberReportData = new ScrubberReportData();
Map<Transaction, List<Message>> aggregateScrubberErrors = new LinkedHashMap<Transaction, List<Message>>();
collectorReportWriterService.pageBreak();
while (batchIter.hasNext()) {
CollectorBatch batch = batchIter.next();
ScrubberReportData batchScrubberReportData = collectorReportData.getScrubberReportData(batch);
if (batchScrubberReportData != null) {
// if some validation error occurred during batch load, the scrubber wouldn't have been run, so there'd be no data
aggregateScrubberReportData.incorporateReportData(batchScrubberReportData);
}
Map<Transaction, List<Message>> batchScrubberReportErrors = collectorReportData.getBatchOriginEntryScrubberErrors(batch);
if (batchScrubberReportErrors != null) {
// if some validation error occurred during batch load, the scrubber wouldn't have been run, so there'd be a null map
aggregateScrubberErrors.putAll(batchScrubberReportErrors);
}
}
List<Transaction> transactions = new ArrayList<Transaction>(aggregateScrubberErrors.keySet());
for (Transaction errorTrans : aggregateScrubberErrors.keySet()) {
List<Message> errors = aggregateScrubberErrors.get(errorTrans);
collectorReportWriterService.writeError(errorTrans, errors);
}
collectorReportWriterService.writeStatisticLine("UNSCRUBBED RECORDS READ %,9d", aggregateScrubberReportData.getNumberOfUnscrubbedRecordsRead());
collectorReportWriterService.writeStatisticLine("SCRUBBED RECORDS WRITTEN %,9d", aggregateScrubberReportData.getNumberOfScrubbedRecordsWritten());
collectorReportWriterService.writeStatisticLine("ERROR RECORDS WRITTEN %,9d", aggregateScrubberReportData.getNumberOfErrorRecordsWritten());
collectorReportWriterService.writeStatisticLine("TOTAL OUTPUT RECORDS WRITTEN %,9d", aggregateScrubberReportData.getTotalNumberOfRecordsWritten());
collectorReportWriterService.writeStatisticLine("EXPIRED ACCOUNTS FOUND %,9d", aggregateScrubberReportData.getNumberOfExpiredAccountsFound());
}
/**
* Writes the report of the demerger run against the Collector data
*
* @param collectorReportData data gathered from the run of the Collector
* @throws DocumentException the exception thrown if the PDF cannot be written to
*/
protected void appendDemergerReport(CollectorReportData collectorReportData) {
Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches();
DemergerReportData aggregateDemergerReportData = new DemergerReportData();
ScrubberReportData aggregateScrubberReportData = new ScrubberReportData();
while (batchIter.hasNext()) {
CollectorBatch batch = batchIter.next();
DemergerReportData batchDemergerReportData = collectorReportData.getDemergerReportData(batch);
if (batchDemergerReportData != null) {
aggregateDemergerReportData.incorporateReportData(batchDemergerReportData);
}
}
collectorReportWriterService.pageBreak();
collectorReportWriterService.writeStatisticLine("ERROR RECORDS READ %,9d", aggregateDemergerReportData.getErrorTransactionsRead());
collectorReportWriterService.writeStatisticLine("VALID RECORDS READ %,9d", aggregateDemergerReportData.getValidTransactionsRead());
collectorReportWriterService.writeStatisticLine("ERROR RECORDS REMOVED FROM PROCESSING %,9d", aggregateDemergerReportData.getErrorTransactionsSaved());
collectorReportWriterService.writeStatisticLine("VALID RECORDS ENTERED INTO ORIGIN ENTRY %,9d", aggregateDemergerReportData.getValidTransactionsSaved());
}
/**
* Writes information about origin entry and details to the report
*
* @param collectorReportData data gathered from the run of the Collector
* @throws DocumentException the exception thrown if the PDF cannot be written to
*/
protected void appendDeletedOriginEntryAndDetailReport(CollectorReportData collectorReportData) {
// figure out how many billing details were removed/bypassed in all of the batches
Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches();
int aggregateNumDetailsDeleted = 0;
StringBuilder buf = new StringBuilder();
collectorReportWriterService.pageBreak();
collectorReportWriterService.writeFormattedMessageLine("ID-Billing detail data matched with GLE errors to remove documents with errors");
while (batchIter.hasNext()) {
CollectorBatch batch = batchIter.next();
Integer batchNumDetailsDeleted = collectorReportData.getNumDetailDeleted(batch);
if (batchNumDetailsDeleted != null) {
aggregateNumDetailsDeleted += batchNumDetailsDeleted.intValue();
}
}
collectorReportWriterService.writeFormattedMessageLine("Total-Recs-Bypassed %d", aggregateNumDetailsDeleted);
batchIter = collectorReportData.getAddedBatches();
int aggregateTransactionCount = 0;
KualiDecimal aggregateDebitAmount = KualiDecimal.ZERO;
while (batchIter.hasNext()) {
CollectorBatch batch = batchIter.next();
Map<DocumentGroupData, OriginEntryTotals> inputEntryTotals = collectorReportData.getTotalsOnInputOriginEntriesAssociatedWithErrorGroup(batch);
if (inputEntryTotals != null) {
for (Map.Entry<DocumentGroupData, OriginEntryTotals> errorDocumentGroupEntry : inputEntryTotals.entrySet()) {
// normally, blank credit/debit code is treated as a debit, but the ID billing program (the predecessor to the
// collector)
// was specific about treating only a code of 'D' as a debit
collectorReportWriterService.writeFormattedMessageLine("Message sent to %-40s for Document %s", batch.getEmailAddress(), errorDocumentGroupEntry.getKey().getDocumentNumber());
int documentTransactionCount = errorDocumentGroupEntry.getValue().getNumCreditEntries() + errorDocumentGroupEntry.getValue().getNumDebitEntries() + errorDocumentGroupEntry.getValue().getNumOtherEntries();
aggregateTransactionCount += documentTransactionCount;
aggregateDebitAmount = aggregateDebitAmount.add(errorDocumentGroupEntry.getValue().getDebitAmount());
collectorReportWriterService.writeFormattedMessageLine("Total Transactions %d for Total Debit Amount %s", documentTransactionCount, new KualiDecimalFormatter(errorDocumentGroupEntry.getValue().getDebitAmount()));
}
}
}
collectorReportWriterService.writeFormattedMessageLine("Total Error Records %d", aggregateTransactionCount);
collectorReportWriterService.writeFormattedMessageLine("Total Debit Dollars %s", new KualiDecimalFormatter(aggregateDebitAmount));
}
/**
* Writes information about what details where changed in the Collector to the report
*
* @param collectorReportData data gathered from the run of the Collector
* @throws DocumentException the exception thrown if the PDF cannot be written to
*/
protected void appendDetailChangedAccountReport(CollectorReportData collectorReportData) {
StringBuilder buf = new StringBuilder();
collectorReportWriterService.writeNewLines(3);
collectorReportWriterService.writeFormattedMessageLine("ID-Billing Detail Records with Account Numbers Changed Due to Change of Corresponding GLE Data");
Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches();
int aggregateNumDetailAccountValuesChanged = 0;
while (batchIter.hasNext()) {
CollectorBatch batch = batchIter.next();
Integer batchNumDetailAccountValuesChanged = collectorReportData.getNumDetailAccountValuesChanged(batch);
if (batchNumDetailAccountValuesChanged != null) {
aggregateNumDetailAccountValuesChanged += batchNumDetailAccountValuesChanged;
}
}
collectorReportWriterService.writeFormattedMessageLine("Tot-Recs-Changed %d", aggregateNumDetailAccountValuesChanged);
}
/**
* Sets the dateTimeService attribute value.
*
* @param dateTimeService The dateTimeService to set.
*/
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
/**
* Generate the header for the demerger status report.
*
* @param scrubberReportData the data gathered from the run of the scrubber on the collector data
* @param demergerReport the data gathered from the run of the demerger on the collector data
* @return list of report summaries to be printed
*/
protected List<Summary> buildDemergerReportSummary(ScrubberReportData scrubberReportData, DemergerReportData demergerReport) {
List<Summary> reportSummary = new ArrayList<Summary>();
reportSummary.add(new Summary(1, "ERROR RECORDS READ", new Integer(scrubberReportData.getNumberOfErrorRecordsWritten())));
reportSummary.add(new Summary(2, "VALID RECORDS READ", new Integer(scrubberReportData.getNumberOfScrubbedRecordsWritten())));
reportSummary.add(new Summary(3, "ERROR RECORDS REMOVED FROM PROCESSING", new Integer(demergerReport.getErrorTransactionsSaved())));
reportSummary.add(new Summary(4, "VALID RECORDS ENTERED INTO ORIGIN ENTRY", new Integer(demergerReport.getValidTransactionsSaved())));
return reportSummary;
}
/**
* Adds the ledger report to this Collector report
*
* @param collectorReportData the data from the Collector run
* @throws DocumentException thrown if it is impossible to write to the report
*/
protected void appendLedgerReport(CollectorReportData collectorReportData) {
collectorReportWriterService.pageBreak();
collectorReportWriterService.writeSubTitle("GENERAL LEDGER INPUT TRANSACTIONS FROM COLLECTOR");
collectorReportWriterService.writeNewLines(1);
LedgerSummaryReport ledgerSummaryReport = collectorReportData.getLedgerSummaryReport();
ledgerSummaryReport.writeReport(collectorReportWriterService);
}
/**
* Builds actual error message from error key and parameters.
* @param errorMap a map of errors
* @return List<String> of error message text
*/
protected List<String> translateErrorsFromMessageMap(MessageMap messageMap) {
List<String> collectorErrors = new ArrayList<String>();
for (Iterator<String> iter = messageMap.getPropertiesWithErrors().iterator(); iter.hasNext();) {
String errorKey = iter.next();
for (Iterator<ErrorMessage> iter2 = messageMap.getMessages(errorKey).iterator(); iter2.hasNext();) {
ErrorMessage errorMessage = (ErrorMessage) iter2.next();
String messageText = configurationService.getPropertyValueAsString(errorMessage.getErrorKey());
collectorErrors.add(MessageFormat.format(messageText, (Object[]) errorMessage.getMessageParameters()));
}
}
return collectorErrors;
}
/**
* Sends email with results of the batch processing.
* @param batch the Collector data from the file
* @param collectorReportData data gathered from the run of the Collector
*/
protected void sendValidationEmail(CollectorBatch batch, CollectorReportData collectorReportData) {
if (StringUtils.isBlank(batch.getEmailAddress())) {
LOG.error("Email not sent because email is blank, batch name " + batch.getBatchName());
return;
}
MessageMap messageMap = batch.getMessageMap();
List<String> errorMessages = translateErrorsFromMessageMap(messageMap);
LOG.debug("sendValidationEmail() starting");
MailMessage message = new MailMessage();
String returnAddress = parameterService.getParameterValueAsString(KFSConstants.ParameterNamespaces.GL, "Batch", KFSConstants.FROM_EMAIL_ADDRESS_PARM_NM);
if(StringUtils.isEmpty(returnAddress)) {
returnAddress = mailService.getBatchMailingList();
}
message.setFromAddress(returnAddress);
String subject = parameterService.getParameterValueAsString(CollectorStep.class, SystemGroupParameterNames.COLLECTOR_VALIDATOR_EMAIL_SUBJECT_PARAMETER_NAME);
//KFSMI-5918
if (errorMessages.size() >0){
subject = parameterService.getParameterValueAsString(CollectorStep.class, SystemGroupParameterNames.COLLECTOR_VALIDATOR_ERROR_EMAIL_SUBJECT_PARAMETER_NAME);
}
message.setSubject(subject);
String body = createValidationMessageBody(errorMessages, batch, collectorReportData);
message.setMessage(body);
message.addToAddress(batch.getEmailAddress());
try {
mailService.sendMessage(message);
String notificationMessage = configurationService.getPropertyValueAsString(KFSKeyConstants.Collector.NOTIFICATION_EMAIL_SENT);
String formattedMessage = MessageFormat.format(notificationMessage, new Object[] { batch.getEmailAddress() });
collectorReportData.setEmailSendingStatusForParsedBatch(batch, formattedMessage);
}
catch (Exception e) {
LOG.error("sendErrorEmail() Invalid email address. Message not sent", e);
String errorMessage = configurationService.getPropertyValueAsString(KFSKeyConstants.Collector.EMAIL_SEND_ERROR);
String formattedMessage = MessageFormat.format(errorMessage, new Object[] { batch.getEmailAddress() });
collectorReportData.setEmailSendingStatusForParsedBatch(batch, formattedMessage);
}
}
/**
* Sends the e-mail about the demerger step
*
* @param batch the data from the Collector file
* @param collectorReportData data gathered from the run of the Collector
*/
protected void sendDemergerEmail(CollectorBatch batch, CollectorReportData collectorReportData) {
if (StringUtils.isBlank(batch.getEmailAddress())) {
LOG.error("Email not sent because email is blank, batch name " + batch.getBatchName());
return;
}
LOG.debug("sendDemergerEmail() starting");
String body = createDemergerMessageBody(batch, collectorReportData);
if (body == null) {
// there must not have been anything to send, so just return from this method
return;
}
MailMessage message = new MailMessage();
String returnAddress = parameterService.getParameterValueAsString(KFSConstants.ParameterNamespaces.GL, "Batch", KFSConstants.FROM_EMAIL_ADDRESS_PARM_NM);
if(StringUtils.isEmpty(returnAddress)) {
returnAddress = mailService.getBatchMailingList();
}
message.setFromAddress(returnAddress);
String subject = parameterService.getParameterValueAsString(CollectorStep.class, SystemGroupParameterNames.COLLECTOR_DEMERGER_EMAIL_SUBJECT_PARAMETER_NAME);
message.setSubject(subject);
message.setMessage(body);
message.addToAddress(batch.getEmailAddress());
try {
mailService.sendMessage(message);
String notificationMessage = configurationService.getPropertyValueAsString(KFSKeyConstants.Collector.NOTIFICATION_EMAIL_SENT);
String formattedMessage = MessageFormat.format(notificationMessage, new Object[] { batch.getEmailAddress() });
collectorReportData.setEmailSendingStatusForParsedBatch(batch, formattedMessage);
}
catch (Exception e) {
LOG.error("sendErrorEmail() Invalid email address. Message not sent", e);
String errorMessage = configurationService.getPropertyValueAsString(KFSKeyConstants.Collector.EMAIL_SEND_ERROR);
String formattedMessage = MessageFormat.format(errorMessage, new Object[] { batch.getEmailAddress() });
collectorReportData.setEmailSendingStatusForParsedBatch(batch, formattedMessage);
}
}
/**
* Sends email message to batch mailing list notifying of email send failures during the collector processing
*
* @param collectorReportData - data from collector run
*/
protected void sendEmailSendFailureNotice(CollectorReportData collectorReportData) {
MailMessage message = new MailMessage();
String returnAddress = parameterService.getParameterValueAsString(KFSConstants.ParameterNamespaces.GL, "Batch", KFSConstants.FROM_EMAIL_ADDRESS_PARM_NM);
if(StringUtils.isEmpty(returnAddress)) {
returnAddress = mailService.getBatchMailingList();
}
message.setFromAddress(returnAddress);
String subject = configurationService.getPropertyValueAsString(KFSKeyConstants.ERROR_COLLECTOR_EMAILSEND_NOTIFICATION_SUBJECT);
message.setSubject(subject);
boolean hasEmailSendErrors = false;
String body = configurationService.getPropertyValueAsString(KFSKeyConstants.ERROR_COLLECTOR_EMAILSEND_NOTIFICATION_BODY);
for (String batchId : collectorReportData.getEmailSendingStatus().keySet()) {
String emailStatus = collectorReportData.getEmailSendingStatus().get(batchId);
if (StringUtils.containsIgnoreCase(emailStatus, "error")) {
body += "Batch: " + batchId + " - " + emailStatus + "\n";
hasEmailSendErrors = true;
}
}
message.setMessage(body);
message.addToAddress(mailService.getBatchMailingList());
try {
if (hasEmailSendErrors) {
mailService.sendMessage(message);
LOG.info("Email failure notice has been sent to : " + message.getToAddresses() );
}
}
catch (Exception e) {
LOG.error("sendErrorEmail() Invalid email address. Message not sent", e);
}
}
/**
* Creates a section about validation messages
*
* @param errorMessages a List of errors that happened during the Collector run
* @param batch the data from the Collector file
* @param collectorReportData data gathered from the run of the Collector
* @return the Validation message body
*/
protected String createValidationMessageBody(List<String> errorMessages, CollectorBatch batch, CollectorReportData collectorReportData) {
StringBuilder body = new StringBuilder();
MessageMap fileMessageMap = batch.getMessageMap();
body.append("Header Information:\n\n");
if (!fileMessageMap.containsMessageKey(KFSKeyConstants.ERROR_BATCH_UPLOAD_PARSING_XML)) {
appendHeaderInformation(body, batch, collectorReportData);
appendTotalsInformation(body, batch);
appendValidationStatus(body, errorMessages, true, 0);
}
return body.toString();
}
/**
* Generates a String that reports on the validation status of the document
*
* @param errorMessages a List of error messages encountered in the Collector process
* @param notifyIfSuccessful true if a special message for the process running successfully should be added, false otherwise
* @param numLeftPaddingSpaces the number of spaces to pad on the left
* @return a String with the validation status message
*/
protected String getValidationStatus(List<String> errorMessages, boolean notifyIfSuccessful, int numLeftPaddingSpaces) {
StringBuilder buf = new StringBuilder();
appendValidationStatus(buf, errorMessages, notifyIfSuccessful, numLeftPaddingSpaces);
return buf.toString();
}
/**
* Appends the validation status message to a buffer
*
* @param buf a StringBuilder to append error messages to
* @param errorMessages a List of error messages encountered in the Collector process
* @param notifyIfSuccessful true if a special message for the process running successfully should be added, false otherwise
* @param numLeftPaddingSpaces the number of spaces to pad on the left
*/
protected void appendValidationStatus(StringBuilder buf, List<String> errorMessages, boolean notifyIfSuccessful, int numLeftPaddingSpaces) {
String padding = StringUtils.leftPad("", numLeftPaddingSpaces, ' ');
if (notifyIfSuccessful || !errorMessages.isEmpty()) {
buf.append("\n").append(padding).append("Reported Errors:\n");
}
// ERRORS GO HERE
if (errorMessages.isEmpty() && notifyIfSuccessful) {
buf.append(padding).append("----- NO ERRORS TO REPORT -----\nThis file will be processed by the accounting cycle.\n");
}
else if (!errorMessages.isEmpty()) {
for (String currentMessage : errorMessages) {
buf.append(padding).append(currentMessage + "\n");
}
buf.append("\n").append(padding).append("----- THIS FILE WAS NOT PROCESSED AND WILL NEED TO BE CORRECTED AND RESUBMITTED -----\n");
}
}
/**
* Writes the part of the report about the demerger
*
* @param batch the data from the Collector file
* @param collectorReportData data gathered from the run of the Collector
* @return
*/
protected String createDemergerMessageBody(CollectorBatch batch, CollectorReportData collectorReportData) {
StringBuilder buf = new StringBuilder();
appendHeaderInformation(buf, batch, collectorReportData);
Map<Transaction, List<Message>> batchOriginEntryScrubberErrors = collectorReportData.getBatchOriginEntryScrubberErrors(batch);
// the keys of the map returned by getTotalsOnInputOriginEntriesAssociatedWithErrorGroup represent all of the error document
// groups in the system
Map<DocumentGroupData, OriginEntryTotals> errorGroupDocumentTotals = collectorReportData.getTotalsOnInputOriginEntriesAssociatedWithErrorGroup(batch);
Set<DocumentGroupData> errorDocumentGroups = null;
if (errorGroupDocumentTotals == null) {
return null;
}
errorDocumentGroups = errorGroupDocumentTotals.keySet();
if (errorDocumentGroups.isEmpty()) {
return null;
}
else {
for (DocumentGroupData errorDocumentGroup : errorDocumentGroups) {
buf.append("Document ").append(errorDocumentGroup.getDocumentNumber()).append(" Rejected Due to Editing Errors.\n");
for (Transaction transaction : batchOriginEntryScrubberErrors.keySet()) {
if (errorDocumentGroup.matchesTransaction(transaction)) {
if (transaction instanceof OriginEntryFull) {
OriginEntryFull entry = (OriginEntryFull) transaction;
buf.append(" Origin Entry: ").append(entry.getLine()).append("\n");
for (Message message : batchOriginEntryScrubberErrors.get(transaction)) {
buf.append(" ").append(message.getMessage()).append("\n");
}
}
}
}
}
}
return buf.toString();
}
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
public void setConfigurationService(ConfigurationService configurationService) {
this.configurationService = configurationService;
}
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
/**
* Sets the collectorReportWriterService attribute value.
* @param collectorReportWriterService The collectorReportWriterService to set.
*/
public void setCollectorReportWriterService(ReportWriterService collectorReportWriterService) {
this.collectorReportWriterService = collectorReportWriterService;
}
public void setPreScrubberService(PreScrubberService preScrubberService) {
this.preScrubberService = preScrubberService;
}
protected static class KualiDecimalFormatter implements Formattable {
private KualiDecimal number;
public KualiDecimalFormatter(KualiDecimal numberToFormat) {
this.number = numberToFormat;
}
@Override
public void formatTo(Formatter formatter, int flags, int width, int precision) {
Map<String, String> settings = new HashMap<String, String>();
settings.put(CurrencyFormatter.SHOW_SYMBOL, Boolean.TRUE.toString());
org.kuali.rice.core.web.format.Formatter cf = org.kuali.rice.core.web.format.Formatter.getFormatter(KualiDecimal.class, settings);
formatter.format((String) cf.format(number));
}
}
}