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.text.MessageFormat; 019 import java.util.ArrayList; 020 import java.util.Formattable; 021 import java.util.Formatter; 022 import java.util.HashMap; 023 import java.util.Iterator; 024 import java.util.LinkedHashMap; 025 import java.util.List; 026 import java.util.Map; 027 import java.util.Set; 028 029 import org.apache.commons.lang.StringUtils; 030 import org.kuali.kfs.gl.batch.CollectorBatch; 031 import org.kuali.kfs.gl.batch.CollectorStep; 032 import org.kuali.kfs.gl.batch.service.CollectorReportService; 033 import org.kuali.kfs.gl.businessobject.DemergerReportData; 034 import org.kuali.kfs.gl.businessobject.OriginEntryFull; 035 import org.kuali.kfs.gl.businessobject.Transaction; 036 import org.kuali.kfs.gl.report.CollectorReportData; 037 import org.kuali.kfs.gl.report.LedgerSummaryReport; 038 import org.kuali.kfs.gl.report.PreScrubberReport; 039 import org.kuali.kfs.gl.report.Summary; 040 import org.kuali.kfs.gl.service.PreScrubberService; 041 import org.kuali.kfs.gl.service.ScrubberReportData; 042 import org.kuali.kfs.sys.KFSConstants; 043 import org.kuali.kfs.sys.KFSConstants.SystemGroupParameterNames; 044 import org.kuali.kfs.sys.KFSKeyConstants; 045 import org.kuali.kfs.sys.Message; 046 import org.kuali.kfs.sys.service.ReportWriterService; 047 import org.kuali.rice.kns.mail.InvalidAddressException; 048 import org.kuali.rice.kns.mail.MailMessage; 049 import org.kuali.rice.kns.service.DateTimeService; 050 import org.kuali.rice.kns.service.KualiConfigurationService; 051 import org.kuali.rice.kns.service.MailService; 052 import org.kuali.rice.kns.service.ParameterService; 053 import org.kuali.rice.kns.util.ErrorMessage; 054 import org.kuali.rice.kns.util.KualiDecimal; 055 import org.kuali.rice.kns.util.MessageMap; 056 import org.kuali.rice.kns.web.format.CurrencyFormatter; 057 058 /** 059 * The base implementation of the CollectorReportService 060 */ 061 public class CollectorReportServiceImpl implements CollectorReportService { 062 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CollectorReportServiceImpl.class); 063 064 private DateTimeService dateTimeService; 065 private ParameterService parameterService; 066 private KualiConfigurationService configurationService; 067 private MailService mailService; 068 private PreScrubberService preScrubberService; 069 private ReportWriterService collectorReportWriterService; 070 071 /** 072 * Constructs a CollectorReportServiceImpl instance 073 */ 074 public CollectorReportServiceImpl() { 075 } 076 077 /** 078 * Sends out e-mails about the validation and demerger of the Collector run 079 * 080 * @param collectorReportData data gathered from the run of the Collector 081 * @see org.kuali.kfs.gl.batch.service.CollectorReportService#sendEmails(org.kuali.kfs.gl.report.CollectorReportData) 082 */ 083 public void sendEmails(CollectorReportData collectorReportData) { 084 // send out the validation status messages 085 Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches(); 086 while (batchIter.hasNext()) { 087 CollectorBatch batch = batchIter.next(); 088 sendValidationEmail(batch, collectorReportData); 089 sendDemergerEmail(batch, collectorReportData); 090 } 091 092 sendEmailSendFailureNotice(collectorReportData); 093 } 094 095 /** 096 * Generates the reports about a given Collector run 097 * 098 * @param collectorReportData data gathered from the run of the Collector 099 * @see org.kuali.kfs.gl.batch.service.CollectorReportService#generateCollectorRunReports(org.kuali.kfs.gl.report.CollectorReportData) 100 */ 101 public void generateCollectorRunReports(CollectorReportData collectorReportData) { 102 appendCollectorHeaderInformation(collectorReportData); 103 appendPreScrubberReport(collectorReportData); 104 appendScrubberReport(collectorReportData); 105 appendDemergerReport(collectorReportData); 106 appendDeletedOriginEntryAndDetailReport(collectorReportData); 107 appendDetailChangedAccountReport(collectorReportData); 108 appendLedgerReport(collectorReportData); 109 } 110 111 /** 112 * Appends Collector header information to the report writer 113 * 114 * @param collectorReportData data gathered from the run of the Collector 115 */ 116 protected void appendCollectorHeaderInformation(CollectorReportData collectorReportData) { 117 Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches(); 118 OriginEntryTotals aggregateOriginEntryTotals = new OriginEntryTotals(); 119 int aggregateTotalRecordsCountFromTrailer = 0; 120 int aggregateNumInputDetails = 0; 121 int aggregateNumSavedDetails = 0; 122 123 if (!collectorReportData.getAllUnparsableFileNames().isEmpty()) { 124 collectorReportWriterService.writeFormattedMessageLine("The following files could not be parsed:\n\n"); 125 for (String unparsableFileName : collectorReportData.getAllUnparsableFileNames()) { 126 List<String> batchErrors = translateErrorsFromErrorMap(collectorReportData.getMessageMapForFileName(unparsableFileName)); 127 collectorReportWriterService.writeFormattedMessageLine(" " + unparsableFileName + "\n"); 128 for (String errorMessage : batchErrors) { 129 collectorReportWriterService.writeFormattedMessageLine(" - ERROR MESSAGE: " + errorMessage); 130 } 131 } 132 } 133 134 while (batchIter.hasNext()) { 135 CollectorBatch batch = batchIter.next(); 136 StringBuilder buf = new StringBuilder(); 137 138 OriginEntryTotals batchOriginEntryTotals = batch.getOriginEntryTotals(); 139 appendHeaderInformation(buf, batch); 140 appendTotalsInformation(buf, batch); 141 142 List<String> errorMessages = translateErrorsFromErrorMap(batch.getMessageMap()); 143 144 aggregateTotalRecordsCountFromTrailer += batch.getTotalRecords(); 145 146 // if batch is valid add up totals 147 if (collectorReportData.isBatchValid(batch)) { 148 149 if (batchOriginEntryTotals != null) { 150 aggregateOriginEntryTotals.incorporateTotals(batchOriginEntryTotals); 151 } 152 153 Integer batchNumInputDetails = collectorReportData.getNumInputDetails(batch); 154 if (batchNumInputDetails != null) { 155 aggregateNumInputDetails += batchNumInputDetails; 156 } 157 158 Integer batchNumSavedDetails = collectorReportData.getNumSavedDetails(batch); 159 if (batchNumSavedDetails != null) { 160 aggregateNumSavedDetails += batchNumSavedDetails; 161 } 162 } 163 164 collectorReportWriterService.writeFormattedMessageLine("Header *********************************************************************"); 165 collectorReportWriterService.writeMultipleFormattedMessageLines(buf.toString()); 166 167 String validationErrors = getValidationStatus(errorMessages, false, 15); 168 if (StringUtils.isNotBlank(validationErrors)) { 169 collectorReportWriterService.writeMultipleFormattedMessageLines(validationErrors); 170 } 171 } 172 173 collectorReportWriterService.writeNewLines(2); 174 collectorReportWriterService.writeFormattedMessageLine("***** Totals for Creation of GLE Data *****"); 175 collectorReportWriterService.writeFormattedMessageLine(" Total Records Read %09d", aggregateTotalRecordsCountFromTrailer); 176 collectorReportWriterService.writeFormattedMessageLine(" Total Groups Read %09d", collectorReportData.getNumPersistedBatches()); 177 collectorReportWriterService.writeFormattedMessageLine(" Total Groups Bypassed %09d", collectorReportData.getNumNotPersistedBatches()); 178 int totalRecordsBypassed = collectorReportData.getNumNotPersistedOriginEntryRecords() + collectorReportData.getNumNotPersistedCollectorDetailRecords(); 179 collectorReportWriterService.writeFormattedMessageLine(" Total Records Bypassed %09d", totalRecordsBypassed); 180 collectorReportWriterService.writeFormattedMessageLine(" Total WWW Records Out %09d", aggregateNumInputDetails); 181 int aggregateOriginEntryCountFromParsedData = aggregateOriginEntryTotals.getNumCreditEntries() + aggregateOriginEntryTotals.getNumDebitEntries() + aggregateOriginEntryTotals.getNumOtherEntries(); 182 collectorReportWriterService.writeFormattedMessageLine(" Total GLE Records Out %09d", aggregateOriginEntryCountFromParsedData); 183 collectorReportWriterService.writeFormattedMessageLine(" Total GLE Debits %19s", new KualiDecimalFormatter(aggregateOriginEntryTotals.getDebitAmount())); 184 collectorReportWriterService.writeFormattedMessageLine(" Debit Count %09d", aggregateOriginEntryTotals.getNumDebitEntries()); 185 collectorReportWriterService.writeFormattedMessageLine(" Total GLE Credits %19s", new KualiDecimalFormatter(aggregateOriginEntryTotals.getCreditAmount())); 186 collectorReportWriterService.writeFormattedMessageLine(" Debit Count %09d", aggregateOriginEntryTotals.getNumCreditEntries()); 187 collectorReportWriterService.writeFormattedMessageLine(" Total GLE Not C or D %19s", new KualiDecimalFormatter(aggregateOriginEntryTotals.getOtherAmount())); 188 collectorReportWriterService.writeFormattedMessageLine(" Not C or D Count %09d", aggregateOriginEntryTotals.getNumOtherEntries()); 189 collectorReportWriterService.writeNewLines(1); 190 collectorReportWriterService.writeFormattedMessageLine("Inserted %d detail records into gl_id_bill_t", aggregateNumSavedDetails); 191 } 192 193 /** 194 * Appends header information to the given buffer 195 * 196 * @param buf the buffer where the message should go 197 * @param batch the data from the Collector file 198 */ 199 protected void appendHeaderInformation(StringBuilder buf, CollectorBatch batch) { 200 buf.append("\n Chart: ").append(batch.getChartOfAccountsCode()).append("\n"); 201 buf.append(" Org: ").append(batch.getOrganizationCode()).append("\n"); 202 buf.append(" Campus: ").append(batch.getCampusCode()).append("\n"); 203 buf.append(" Department: ").append(batch.getDepartmentName()).append("\n"); 204 buf.append(" Mailing Address: ").append(batch.getMailingAddress()).append("\n"); 205 buf.append(" Contact: ").append(batch.getPersonUserID()).append("\n"); 206 buf.append(" Email: ").append(batch.getEmailAddress()).append("\n"); 207 buf.append(" Transmission Date: ").append(batch.getTransmissionDate()).append("\n\n"); 208 } 209 210 /** 211 * Writes totals information to the report 212 * 213 * @param buf the buffer where the e-mail report is being written 214 * @param batch the data generated by the Collector file upload 215 * @param totals the totals to write 216 */ 217 protected void appendTotalsInformation(StringBuilder buf, CollectorBatch batch) { 218 OriginEntryTotals totals = batch.getOriginEntryTotals(); 219 if (totals == null) { 220 buf.append(" Totals are unavailable for this batch.\n"); 221 } 222 else { 223 // SUMMARY TOTALS HERE 224 appendAmountCountLine(buf, "Group Credits = ", Integer.toString(totals.getNumCreditEntries()), totals.getCreditAmount()); 225 appendAmountCountLine(buf, "Group Debits = ", Integer.toString(totals.getNumDebitEntries()), totals.getDebitAmount()); 226 appendAmountCountLine(buf, "Group Not C/D = ", Integer.toString(totals.getNumOtherEntries()), totals.getOtherAmount()); 227 appendAmountCountLine(buf, "Valid Group Count = ", batch.getTotalRecords().toString(), batch.getTotalAmount()); 228 } 229 } 230 231 /** 232 * Writes the Amount/Count line of the Collector to a buffer 233 * 234 * @param buf the buffer to write the line to 235 * @param countTitle the title of this part of the report 236 * @param count the Collector count 237 * @param amountString the Collector amount 238 */ 239 protected void appendAmountCountLine(StringBuilder buf, String countTitle, String count, KualiDecimal amount) { 240 appendPaddingString(buf, ' ', countTitle.length(), 35); 241 buf.append(countTitle); 242 243 appendPaddingString(buf, '0', count.length(), 5); 244 buf.append(count); 245 246 if (amount == null) { 247 buf.append(StringUtils.leftPad("N/A", 21)); 248 } 249 else { 250 Map<String, String> settings = new HashMap<String, String>(); 251 settings.put(CurrencyFormatter.SHOW_SYMBOL, Boolean.TRUE.toString()); 252 org.kuali.rice.kns.web.format.Formatter f = org.kuali.rice.kns.web.format.Formatter.getFormatter(KualiDecimal.class, settings); 253 String amountString = (String) f.format(amount); 254 appendPaddingString(buf, ' ', amountString.length(), 21); 255 buf.append(amountString); 256 } 257 258 buf.append("\n"); 259 260 } 261 262 /** 263 * Writes some padding to a buffer 264 * 265 * @param buf the buffer to write to 266 * @param padCharacter the character to repeat in the pad 267 * @param valueLength the length of the value being padded 268 * @param desiredLength the length the whole String should be 269 * @return the buffer 270 */ 271 protected StringBuilder appendPaddingString(StringBuilder buf, char padCharacter, int valueLength, int desiredLength) { 272 for (int i = valueLength; i < desiredLength; i++) { 273 buf.append(padCharacter); 274 } 275 return buf; 276 } 277 278 protected void appendPreScrubberReport(CollectorReportData collectorReportData) { 279 if (preScrubberService.deriveChartOfAccountsCodeIfSpaces()) { 280 collectorReportWriterService.pageBreak(); 281 collectorReportWriterService.writeSubTitle("Collector Pre-Scrubber Report"); 282 new PreScrubberReport().generateReport(collectorReportData.getPreScrubberReportData(), collectorReportWriterService); 283 } 284 } 285 286 /** 287 * Writes the results of the Scrubber's run on the Collector data to the report writer 288 * 289 * @param collectorReportData data gathered from the run of the Collector 290 */ 291 protected void appendScrubberReport(CollectorReportData collectorReportData) { 292 Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches(); 293 ScrubberReportData aggregateScrubberReportData = new ScrubberReportData(); 294 Map<Transaction, List<Message>> aggregateScrubberErrors = new LinkedHashMap<Transaction, List<Message>>(); 295 296 collectorReportWriterService.pageBreak(); 297 298 while (batchIter.hasNext()) { 299 CollectorBatch batch = batchIter.next(); 300 301 ScrubberReportData batchScrubberReportData = collectorReportData.getScrubberReportData(batch); 302 if (batchScrubberReportData != null) { 303 // if some validation error occurred during batch load, the scrubber wouldn't have been run, so there'd be no data 304 aggregateScrubberReportData.incorporateReportData(batchScrubberReportData); 305 } 306 307 Map<Transaction, List<Message>> batchScrubberReportErrors = collectorReportData.getBatchOriginEntryScrubberErrors(batch); 308 if (batchScrubberReportErrors != null) { 309 // if some validation error occurred during batch load, the scrubber wouldn't have been run, so there'd be a null map 310 aggregateScrubberErrors.putAll(batchScrubberReportErrors); 311 } 312 } 313 314 List<Transaction> transactions = new ArrayList<Transaction>(aggregateScrubberErrors.keySet()); 315 for (Transaction errorTrans : aggregateScrubberErrors.keySet()) { 316 List<Message> errors = aggregateScrubberErrors.get(errorTrans); 317 collectorReportWriterService.writeError(errorTrans, errors); 318 } 319 collectorReportWriterService.writeStatisticLine("UNSCRUBBED RECORDS READ %,9d", aggregateScrubberReportData.getNumberOfUnscrubbedRecordsRead()); 320 collectorReportWriterService.writeStatisticLine("SCRUBBED RECORDS WRITTEN %,9d", aggregateScrubberReportData.getNumberOfScrubbedRecordsWritten()); 321 collectorReportWriterService.writeStatisticLine("ERROR RECORDS WRITTEN %,9d", aggregateScrubberReportData.getNumberOfErrorRecordsWritten()); 322 collectorReportWriterService.writeStatisticLine("TOTAL OUTPUT RECORDS WRITTEN %,9d", aggregateScrubberReportData.getTotalNumberOfRecordsWritten()); 323 collectorReportWriterService.writeStatisticLine("EXPIRED ACCOUNTS FOUND %,9d", aggregateScrubberReportData.getNumberOfExpiredAccountsFound()); 324 } 325 326 /** 327 * Writes the report of the demerger run against the Collector data 328 * 329 * @param collectorReportData data gathered from the run of the Collector 330 * @throws DocumentException the exception thrown if the PDF cannot be written to 331 */ 332 protected void appendDemergerReport(CollectorReportData collectorReportData) { 333 Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches(); 334 DemergerReportData aggregateDemergerReportData = new DemergerReportData(); 335 ScrubberReportData aggregateScrubberReportData = new ScrubberReportData(); 336 337 while (batchIter.hasNext()) { 338 CollectorBatch batch = batchIter.next(); 339 DemergerReportData batchDemergerReportData = collectorReportData.getDemergerReportData(batch); 340 if (batchDemergerReportData != null) { 341 aggregateDemergerReportData.incorporateReportData(batchDemergerReportData); 342 } 343 } 344 345 collectorReportWriterService.pageBreak(); 346 collectorReportWriterService.writeStatisticLine("ERROR RECORDS READ %,9d", aggregateDemergerReportData.getErrorTransactionsRead()); 347 collectorReportWriterService.writeStatisticLine("VALID RECORDS READ %,9d", aggregateDemergerReportData.getValidTransactionsRead()); 348 collectorReportWriterService.writeStatisticLine("ERROR RECORDS REMOVED FROM PROCESSING %,9d", aggregateDemergerReportData.getErrorTransactionsSaved()); 349 collectorReportWriterService.writeStatisticLine("VALID RECORDS ENTERED INTO ORIGIN ENTRY %,9d", aggregateDemergerReportData.getValidTransactionsSaved()); 350 } 351 352 /** 353 * Writes information about origin entry and details to the report 354 * 355 * @param collectorReportData data gathered from the run of the Collector 356 * @throws DocumentException the exception thrown if the PDF cannot be written to 357 */ 358 protected void appendDeletedOriginEntryAndDetailReport(CollectorReportData collectorReportData) { 359 // figure out how many billing details were removed/bypassed in all of the batches 360 Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches(); 361 int aggregateNumDetailsDeleted = 0; 362 363 StringBuilder buf = new StringBuilder(); 364 365 collectorReportWriterService.pageBreak(); 366 collectorReportWriterService.writeFormattedMessageLine("ID-Billing detail data matched with GLE errors to remove documents with errors"); 367 while (batchIter.hasNext()) { 368 CollectorBatch batch = batchIter.next(); 369 370 Integer batchNumDetailsDeleted = collectorReportData.getNumDetailDeleted(batch); 371 if (batchNumDetailsDeleted != null) { 372 aggregateNumDetailsDeleted += batchNumDetailsDeleted.intValue(); 373 } 374 } 375 collectorReportWriterService.writeFormattedMessageLine("Total-Recs-Bypassed %d", aggregateNumDetailsDeleted); 376 377 batchIter = collectorReportData.getAddedBatches(); 378 int aggregateTransactionCount = 0; 379 KualiDecimal aggregateDebitAmount = KualiDecimal.ZERO; 380 while (batchIter.hasNext()) { 381 CollectorBatch batch = batchIter.next(); 382 383 Map<DocumentGroupData, OriginEntryTotals> inputEntryTotals = collectorReportData.getTotalsOnInputOriginEntriesAssociatedWithErrorGroup(batch); 384 if (inputEntryTotals != null) { 385 for (Map.Entry<DocumentGroupData, OriginEntryTotals> errorDocumentGroupEntry : inputEntryTotals.entrySet()) { 386 // normally, blank credit/debit code is treated as a debit, but the ID billing program (the predecessor to the 387 // collector) 388 // was specific about treating only a code of 'D' as a debit 389 390 collectorReportWriterService.writeFormattedMessageLine("Message sent to %-40s for Document %s", batch.getEmailAddress(), errorDocumentGroupEntry.getKey().getDocumentNumber()); 391 int documentTransactionCount = errorDocumentGroupEntry.getValue().getNumCreditEntries() + errorDocumentGroupEntry.getValue().getNumDebitEntries() + errorDocumentGroupEntry.getValue().getNumOtherEntries(); 392 aggregateTransactionCount += documentTransactionCount; 393 aggregateDebitAmount = aggregateDebitAmount.add(errorDocumentGroupEntry.getValue().getDebitAmount()); 394 collectorReportWriterService.writeFormattedMessageLine("Total Transactions %d for Total Debit Amount %s", documentTransactionCount, new KualiDecimalFormatter(errorDocumentGroupEntry.getValue().getDebitAmount())); 395 } 396 } 397 } 398 collectorReportWriterService.writeFormattedMessageLine("Total Error Records %d", aggregateTransactionCount); 399 collectorReportWriterService.writeFormattedMessageLine("Total Debit Dollars %s", new KualiDecimalFormatter(aggregateDebitAmount)); 400 } 401 402 /** 403 * Writes information about what details where changed in the Collector to the report 404 * 405 * @param collectorReportData data gathered from the run of the Collector 406 * @throws DocumentException the exception thrown if the PDF cannot be written to 407 */ 408 protected void appendDetailChangedAccountReport(CollectorReportData collectorReportData) { 409 StringBuilder buf = new StringBuilder(); 410 411 collectorReportWriterService.writeNewLines(3); 412 collectorReportWriterService.writeFormattedMessageLine("ID-Billing Detail Records with Account Numbers Changed Due to Change of Corresponding GLE Data"); 413 Iterator<CollectorBatch> batchIter = collectorReportData.getAddedBatches(); 414 int aggregateNumDetailAccountValuesChanged = 0; 415 while (batchIter.hasNext()) { 416 CollectorBatch batch = batchIter.next(); 417 418 Integer batchNumDetailAccountValuesChanged = collectorReportData.getNumDetailAccountValuesChanged(batch); 419 if (batchNumDetailAccountValuesChanged != null) { 420 aggregateNumDetailAccountValuesChanged += batchNumDetailAccountValuesChanged; 421 } 422 } 423 collectorReportWriterService.writeFormattedMessageLine("Tot-Recs-Changed %d", aggregateNumDetailAccountValuesChanged); 424 } 425 426 /** 427 * Gets the dateTimeService attribute. 428 * 429 * @return Returns the dateTimeService. 430 */ 431 protected DateTimeService getDateTimeService() { 432 return dateTimeService; 433 } 434 435 /** 436 * Sets the dateTimeService attribute value. 437 * 438 * @param dateTimeService The dateTimeService to set. 439 */ 440 public void setDateTimeService(DateTimeService dateTimeService) { 441 this.dateTimeService = dateTimeService; 442 } 443 444 /** 445 * Generate the header for the demerger status report. 446 * 447 * @param scrubberReportData the data gathered from the run of the scrubber on the collector data 448 * @param demergerReport the data gathered from the run of the demerger on the collector data 449 * @return list of report summaries to be printed 450 */ 451 protected List<Summary> buildDemergerReportSummary(ScrubberReportData scrubberReportData, DemergerReportData demergerReport) { 452 List<Summary> reportSummary = new ArrayList<Summary>(); 453 reportSummary.add(new Summary(1, "ERROR RECORDS READ", new Integer(scrubberReportData.getNumberOfErrorRecordsWritten()))); 454 reportSummary.add(new Summary(2, "VALID RECORDS READ", new Integer(scrubberReportData.getNumberOfScrubbedRecordsWritten()))); 455 reportSummary.add(new Summary(3, "ERROR RECORDS REMOVED FROM PROCESSING", new Integer(demergerReport.getErrorTransactionsSaved()))); 456 reportSummary.add(new Summary(4, "VALID RECORDS ENTERED INTO ORIGIN ENTRY", new Integer(demergerReport.getValidTransactionsSaved()))); 457 458 return reportSummary; 459 } 460 461 /** 462 * Adds the ledger report to this Collector report 463 * 464 * @param collectorReportData the data from the Collector run 465 * @throws DocumentException thrown if it is impossible to write to the report 466 */ 467 protected void appendLedgerReport(CollectorReportData collectorReportData) { 468 collectorReportWriterService.pageBreak(); 469 collectorReportWriterService.writeSubTitle("GENERAL LEDGER INPUT TRANSACTIONS FROM COLLECTOR"); 470 collectorReportWriterService.writeNewLines(1); 471 472 LedgerSummaryReport ledgerSummaryReport = collectorReportData.getLedgerSummaryReport(); 473 ledgerSummaryReport.writeReport(collectorReportWriterService); 474 } 475 476 /** 477 * Builds actual error message from error key and parameters. 478 * @param errorMap a map of errors 479 * @return List<String> of error message text 480 */ 481 protected List<String> translateErrorsFromErrorMap(MessageMap errorMap) { 482 List<String> collectorErrors = new ArrayList<String>(); 483 484 for (Iterator<String> iter = errorMap.getPropertiesWithErrors().iterator(); iter.hasNext();) { 485 String errorKey = iter.next(); 486 487 for (Iterator<ErrorMessage> iter2 = errorMap.getMessages(errorKey).iterator(); iter2.hasNext();) { 488 ErrorMessage errorMessage = (ErrorMessage) iter2.next(); 489 String messageText = configurationService.getPropertyString(errorMessage.getErrorKey()); 490 collectorErrors.add(MessageFormat.format(messageText, (Object[]) errorMessage.getMessageParameters())); 491 } 492 } 493 494 return collectorErrors; 495 } 496 497 /** 498 * Sends email with results of the batch processing. 499 * @param batch the Collector data from the file 500 * @param collectorReportData data gathered from the run of the Collector 501 */ 502 protected void sendValidationEmail(CollectorBatch batch, CollectorReportData collectorReportData) { 503 if (StringUtils.isBlank(batch.getEmailAddress())) { 504 LOG.error("Email not sent because email is blank, batch name " + batch.getBatchName()); 505 return; 506 } 507 MessageMap errorMap = batch.getMessageMap(); 508 List<String> errorMessages = translateErrorsFromErrorMap(errorMap); 509 510 LOG.debug("sendValidationEmail() starting"); 511 MailMessage message = new MailMessage(); 512 513 message.setFromAddress(mailService.getBatchMailingList()); 514 515 String subject = parameterService.getParameterValue(CollectorStep.class, SystemGroupParameterNames.COLLECTOR_VALIDATOR_EMAIL_SUBJECT_PARAMETER_NAME); 516 String productionEnvironmentCode = configurationService.getPropertyString(KFSConstants.PROD_ENVIRONMENT_CODE_KEY); 517 String environmentCode = configurationService.getPropertyString(KFSConstants.ENVIRONMENT_KEY); 518 if (!StringUtils.equals(productionEnvironmentCode, environmentCode)) { 519 subject = environmentCode + ": " + subject; 520 } 521 message.setSubject(subject); 522 523 String body = createValidationMessageBody(errorMessages, batch, collectorReportData); 524 message.setMessage(body); 525 message.addToAddress(batch.getEmailAddress()); 526 527 try { 528 mailService.sendMessage(message); 529 530 String notificationMessage = configurationService.getPropertyString(KFSKeyConstants.Collector.NOTIFICATION_EMAIL_SENT); 531 String formattedMessage = MessageFormat.format(notificationMessage, new Object[] { batch.getEmailAddress() }); 532 collectorReportData.setEmailSendingStatusForParsedBatch(batch, formattedMessage); 533 } 534 catch (InvalidAddressException e) { 535 LOG.error("sendErrorEmail() Invalid email address. Message not sent", e); 536 String errorMessage = configurationService.getPropertyString(KFSKeyConstants.Collector.EMAIL_SEND_ERROR); 537 String formattedMessage = MessageFormat.format(errorMessage, new Object[] { batch.getEmailAddress() }); 538 collectorReportData.setEmailSendingStatusForParsedBatch(batch, formattedMessage); 539 } 540 } 541 542 /** 543 * Sends the e-mail about the demerger step 544 * 545 * @param batch the data from the Collector file 546 * @param collectorReportData data gathered from the run of the Collector 547 */ 548 protected void sendDemergerEmail(CollectorBatch batch, CollectorReportData collectorReportData) { 549 if (StringUtils.isBlank(batch.getEmailAddress())) { 550 LOG.error("Email not sent because email is blank, batch name " + batch.getBatchName()); 551 return; 552 } 553 LOG.debug("sendDemergerEmail() starting"); 554 String body = createDemergerMessageBody(batch, collectorReportData); 555 if (body == null) { 556 // there must not have been anything to send, so just return from this method 557 return; 558 } 559 MailMessage message = new MailMessage(); 560 561 message.setFromAddress(mailService.getBatchMailingList()); 562 563 String subject = parameterService.getParameterValue(CollectorStep.class, SystemGroupParameterNames.COLLECTOR_DEMERGER_EMAIL_SUBJECT_PARAMETER_NAME); 564 String productionEnvironmentCode = configurationService.getPropertyString(KFSConstants.PROD_ENVIRONMENT_CODE_KEY); 565 String environmentCode = configurationService.getPropertyString(KFSConstants.ENVIRONMENT_KEY); 566 if (!StringUtils.equals(productionEnvironmentCode, environmentCode)) { 567 subject = environmentCode + ": " + subject; 568 } 569 message.setSubject(subject); 570 571 message.setMessage(body); 572 message.addToAddress(batch.getEmailAddress()); 573 574 try { 575 mailService.sendMessage(message); 576 577 String notificationMessage = configurationService.getPropertyString(KFSKeyConstants.Collector.NOTIFICATION_EMAIL_SENT); 578 String formattedMessage = MessageFormat.format(notificationMessage, new Object[] { batch.getEmailAddress() }); 579 collectorReportData.setEmailSendingStatusForParsedBatch(batch, formattedMessage); 580 } 581 catch (InvalidAddressException e) { 582 LOG.error("sendErrorEmail() Invalid email address. Message not sent", e); 583 String errorMessage = configurationService.getPropertyString(KFSKeyConstants.Collector.EMAIL_SEND_ERROR); 584 String formattedMessage = MessageFormat.format(errorMessage, new Object[] { batch.getEmailAddress() }); 585 collectorReportData.setEmailSendingStatusForParsedBatch(batch, formattedMessage); 586 } 587 } 588 589 /** 590 * Sends email message to batch mailing list notifying of email send failures during the collector processing 591 * 592 * @param collectorReportData - data from collector run 593 */ 594 protected void sendEmailSendFailureNotice(CollectorReportData collectorReportData) { 595 MailMessage message = new MailMessage(); 596 597 message.setFromAddress(mailService.getBatchMailingList()); 598 599 String subject = configurationService.getPropertyString(KFSKeyConstants.ERROR_COLLECTOR_EMAILSEND_NOTIFICATION_SUBJECT); 600 String productionEnvironmentCode = configurationService.getPropertyString(KFSConstants.PROD_ENVIRONMENT_CODE_KEY); 601 String environmentCode = configurationService.getPropertyString(KFSConstants.ENVIRONMENT_KEY); 602 if (!StringUtils.equals(productionEnvironmentCode, environmentCode)) { 603 subject = environmentCode + ": " + subject; 604 } 605 message.setSubject(subject); 606 607 boolean hasEmailSendErrors = false; 608 609 String body = configurationService.getPropertyString(KFSKeyConstants.ERROR_COLLECTOR_EMAILSEND_NOTIFICATION_BODY); 610 for (String batchId : collectorReportData.getEmailSendingStatus().keySet()) { 611 String emailStatus = collectorReportData.getEmailSendingStatus().get(batchId); 612 if (StringUtils.containsIgnoreCase(emailStatus, "error")) { 613 body += "Batch: " + batchId + " - " + emailStatus + "\n"; 614 hasEmailSendErrors = true; 615 } 616 } 617 message.setMessage(body); 618 619 message.addToAddress(mailService.getBatchMailingList()); 620 621 try { 622 if (hasEmailSendErrors) { 623 mailService.sendMessage(message); 624 } 625 } 626 catch (InvalidAddressException e) { 627 LOG.error("sendErrorEmail() Invalid email address. Message not sent", e); 628 } 629 } 630 631 /** 632 * Creates a section about validation messages 633 * 634 * @param errorMessages a List of errors that happened during the Collector run 635 * @param batch the data from the Collector file 636 * @param collectorReportData data gathered from the run of the Collector 637 * @return the Validation message body 638 */ 639 protected String createValidationMessageBody(List<String> errorMessages, CollectorBatch batch, CollectorReportData collectorReportData) { 640 StringBuilder body = new StringBuilder(); 641 642 MessageMap fileErrorMap = batch.getMessageMap(); 643 644 body.append("Header Information:\n\n"); 645 if (!fileErrorMap.containsMessageKey(KFSKeyConstants.ERROR_BATCH_UPLOAD_PARSING_XML)) { 646 appendHeaderInformation(body, batch); 647 appendTotalsInformation(body, batch); 648 appendValidationStatus(body, errorMessages, true, 0); 649 } 650 651 return body.toString(); 652 } 653 654 /** 655 * Generates a String that reports on the validation status of the document 656 * 657 * @param errorMessages a List of error messages encountered in the Collector process 658 * @param notifyIfSuccessful true if a special message for the process running successfully should be added, false otherwise 659 * @param numLeftPaddingSpaces the number of spaces to pad on the left 660 * @return a String with the validation status message 661 */ 662 protected String getValidationStatus(List<String> errorMessages, boolean notifyIfSuccessful, int numLeftPaddingSpaces) { 663 StringBuilder buf = new StringBuilder(); 664 appendValidationStatus(buf, errorMessages, notifyIfSuccessful, numLeftPaddingSpaces); 665 return buf.toString(); 666 } 667 668 /** 669 * Appends the validation status message to a buffer 670 * 671 * @param buf a StringBuilder to append error messages to 672 * @param errorMessages a List of error messages encountered in the Collector process 673 * @param notifyIfSuccessful true if a special message for the process running successfully should be added, false otherwise 674 * @param numLeftPaddingSpaces the number of spaces to pad on the left 675 */ 676 protected void appendValidationStatus(StringBuilder buf, List<String> errorMessages, boolean notifyIfSuccessful, int numLeftPaddingSpaces) { 677 String padding = StringUtils.leftPad("", numLeftPaddingSpaces, ' '); 678 679 if (notifyIfSuccessful || !errorMessages.isEmpty()) { 680 buf.append("\n").append(padding).append("Reported Errors:\n"); 681 } 682 683 // ERRORS GO HERE 684 if (errorMessages.isEmpty() && notifyIfSuccessful) { 685 buf.append(padding).append("----- NO ERRORS TO REPORT -----\nThis file will be processed by the accounting cycle.\n"); 686 } 687 else if (!errorMessages.isEmpty()) { 688 for (String currentMessage : errorMessages) { 689 buf.append(padding).append(currentMessage + "\n"); 690 } 691 buf.append("\n").append(padding).append("----- THIS FILE WAS NOT PROCESSED AND WILL NEED TO BE CORRECTED AND RESUBMITTED -----\n"); 692 } 693 } 694 695 /** 696 * Writes the part of the report about the demerger 697 * 698 * @param batch the data from the Collector file 699 * @param collectorReportData data gathered from the run of the Collector 700 * @return 701 */ 702 protected String createDemergerMessageBody(CollectorBatch batch, CollectorReportData collectorReportData) { 703 StringBuilder buf = new StringBuilder(); 704 appendHeaderInformation(buf, batch); 705 706 Map<Transaction, List<Message>> batchOriginEntryScrubberErrors = collectorReportData.getBatchOriginEntryScrubberErrors(batch); 707 708 // the keys of the map returned by getTotalsOnInputOriginEntriesAssociatedWithErrorGroup represent all of the error document 709 // groups in the system 710 Map<DocumentGroupData, OriginEntryTotals> errorGroupDocumentTotals = collectorReportData.getTotalsOnInputOriginEntriesAssociatedWithErrorGroup(batch); 711 Set<DocumentGroupData> errorDocumentGroups = null; 712 if (errorGroupDocumentTotals == null) { 713 return null; 714 } 715 errorDocumentGroups = errorGroupDocumentTotals.keySet(); 716 if (errorDocumentGroups.isEmpty()) { 717 return null; 718 } 719 else { 720 for (DocumentGroupData errorDocumentGroup : errorDocumentGroups) { 721 buf.append("Document ").append(errorDocumentGroup.getDocumentNumber()).append(" Rejected Due to Editing Errors.\n"); 722 for (Transaction transaction : batchOriginEntryScrubberErrors.keySet()) { 723 if (errorDocumentGroup.matchesTransaction(transaction)) { 724 if (transaction instanceof OriginEntryFull) { 725 OriginEntryFull entry = (OriginEntryFull) transaction; 726 buf.append(" Origin Entry: ").append(entry.getLine()).append("\n"); 727 for (Message message : batchOriginEntryScrubberErrors.get(transaction)) { 728 buf.append(" ").append(message.getMessage()).append("\n"); 729 } 730 } 731 } 732 } 733 } 734 } 735 736 return buf.toString(); 737 } 738 739 /** 740 * Gets the mailService attribute. 741 * 742 * @return Returns the mailService. 743 */ 744 public MailService getMailService() { 745 return mailService; 746 } 747 748 /** 749 * Sets the mailService attribute value. 750 * 751 * @param mailService The mailService to set. 752 */ 753 public void setMailService(MailService mailService) { 754 this.mailService = mailService; 755 } 756 757 public void setConfigurationService(KualiConfigurationService configurationService) { 758 this.configurationService = configurationService; 759 } 760 761 public void setParameterService(ParameterService parameterService) { 762 this.parameterService = parameterService; 763 } 764 765 /** 766 * Sets the collectorReportWriterService attribute value. 767 * @param collectorReportWriterService The collectorReportWriterService to set. 768 */ 769 public void setCollectorReportWriterService(ReportWriterService collectorReportWriterService) { 770 this.collectorReportWriterService = collectorReportWriterService; 771 } 772 773 public void setPreScrubberService(PreScrubberService preScrubberService) { 774 this.preScrubberService = preScrubberService; 775 } 776 777 protected class KualiDecimalFormatter implements Formattable { 778 private KualiDecimal number; 779 780 public KualiDecimalFormatter(KualiDecimal numberToFormat) { 781 this.number = numberToFormat; 782 } 783 784 public void formatTo(Formatter formatter, int flags, int width, int precision) { 785 Map<String, String> settings = new HashMap<String, String>(); 786 settings.put(CurrencyFormatter.SHOW_SYMBOL, Boolean.TRUE.toString()); 787 org.kuali.rice.kns.web.format.Formatter cf = org.kuali.rice.kns.web.format.Formatter.getFormatter(KualiDecimal.class, settings); 788 formatter.format((String) cf.format(number)); 789 } 790 } 791 }