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 }