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