0001: /*
0002: * Copyright 2007 The Kuali Foundation.
0003: *
0004: * Licensed under the Educational Community License, Version 1.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.opensource.org/licenses/ecl1.php
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.kuali.module.gl.service.impl;
0017:
0018: import java.io.FileOutputStream;
0019: import java.text.MessageFormat;
0020: import java.text.SimpleDateFormat;
0021: import java.util.ArrayList;
0022: import java.util.Collections;
0023: import java.util.Date;
0024: import java.util.Iterator;
0025: import java.util.LinkedHashMap;
0026: import java.util.List;
0027: import java.util.Map;
0028: import java.util.Set;
0029:
0030: import org.apache.commons.lang.StringUtils;
0031: import org.kuali.core.mail.InvalidAddressException;
0032: import org.kuali.core.mail.MailMessage;
0033: import org.kuali.core.service.DateTimeService;
0034: import org.kuali.core.service.KualiConfigurationService;
0035: import org.kuali.core.service.MailService;
0036: import org.kuali.core.util.ErrorMap;
0037: import org.kuali.core.util.ErrorMessage;
0038: import org.kuali.core.util.KualiDecimal;
0039: import org.kuali.kfs.KFSConstants;
0040: import org.kuali.kfs.KFSKeyConstants;
0041: import org.kuali.kfs.KFSConstants.SystemGroupParameterNames;
0042: import org.kuali.kfs.service.ParameterService;
0043: import org.kuali.module.gl.batch.collector.CollectorBatch;
0044: import org.kuali.module.gl.batch.collector.CollectorStep;
0045: import org.kuali.module.gl.bo.OriginEntryFull;
0046: import org.kuali.module.gl.bo.Transaction;
0047: import org.kuali.module.gl.service.CollectorReportService;
0048: import org.kuali.module.gl.service.impl.scrubber.DemergerReportData;
0049: import org.kuali.module.gl.service.impl.scrubber.ScrubberReportData;
0050: import org.kuali.module.gl.util.CollectorReportData;
0051: import org.kuali.module.gl.util.DocumentGroupData;
0052: import org.kuali.module.gl.util.LedgerEntryHolder;
0053: import org.kuali.module.gl.util.LedgerReport;
0054: import org.kuali.module.gl.util.Message;
0055: import org.kuali.module.gl.util.OriginEntryTotals;
0056: import org.kuali.module.gl.util.Summary;
0057: import org.kuali.module.gl.util.TransactionReport;
0058: import org.kuali.module.gl.util.TransactionReport.PageHelper;
0059:
0060: import com.lowagie.text.Chunk;
0061: import com.lowagie.text.Document;
0062: import com.lowagie.text.DocumentException;
0063: import com.lowagie.text.Font;
0064: import com.lowagie.text.FontFactory;
0065: import com.lowagie.text.ListItem;
0066: import com.lowagie.text.PageSize;
0067: import com.lowagie.text.Paragraph;
0068: import com.lowagie.text.Phrase;
0069: import com.lowagie.text.pdf.PdfPTable;
0070: import com.lowagie.text.pdf.PdfWriter;
0071:
0072: /**
0073: * The base implementation of the CollectorReportService
0074: */
0075: public class CollectorReportServiceImpl implements
0076: CollectorReportService {
0077: private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
0078: .getLogger(CollectorReportServiceImpl.class);
0079:
0080: private static final String CURRENCY_SYMBOL = "$";
0081:
0082: private DateTimeService dateTimeService;
0083: private ParameterService parameterService;
0084: private KualiConfigurationService configurationService;
0085: private MailService mailService;
0086:
0087: private Font headerFont;
0088: private Font textFont;
0089: private int textFontSize;
0090:
0091: private String directoryName;
0092:
0093: /**
0094: * Constructs a CollectorReportServiceImpl instance
0095: */
0096: public CollectorReportServiceImpl() {
0097: textFontSize = 8;
0098: headerFont = FontFactory.getFont(FontFactory.COURIER, 8,
0099: Font.BOLD);
0100: textFont = FontFactory.getFont(FontFactory.COURIER,
0101: textFontSize, Font.NORMAL);
0102: }
0103:
0104: /**
0105: * Sends out e-mails about the validation and demerger of the Collector run
0106: *
0107: * @param collectorReportData data gathered from the run of the Collector
0108: * @see org.kuali.module.gl.service.CollectorReportService#sendEmails(org.kuali.module.gl.util.CollectorReportData)
0109: */
0110: public void sendEmails(CollectorReportData collectorReportData) {
0111: // send out the validation status messages
0112: Iterator<CollectorBatch> batchIter = collectorReportData
0113: .getAddedBatches();
0114: while (batchIter.hasNext()) {
0115: CollectorBatch batch = batchIter.next();
0116: sendValidationEmail(batch, collectorReportData);
0117: sendDemergerEmail(batch, collectorReportData);
0118: }
0119:
0120: // now send out emails related to demerging
0121: batchIter = collectorReportData.getAddedBatches();
0122: while (batchIter.hasNext()) {
0123: CollectorBatch batch = batchIter.next();
0124:
0125: }
0126: }
0127:
0128: /**
0129: * Generates the reports about a given Collector run
0130: *
0131: * @param collectorReportData data gathered from the run of the Collector
0132: * @see org.kuali.module.gl.service.CollectorReportService#generateCollectorRunReports(org.kuali.module.gl.util.CollectorReportData)
0133: */
0134: public void generateCollectorRunReports(
0135: CollectorReportData collectorReportData) {
0136: try {
0137: Document document = openPdfWriter(getDirectoryName(),
0138: "collector", dateTimeService.getCurrentDate(),
0139: "Collector reports");
0140: appendCollectorHeaderInformation(document,
0141: collectorReportData);
0142:
0143: document.newPage();
0144: appendScrubberReport(document, collectorReportData);
0145:
0146: document.newPage();
0147: appendDemergerReport(document, collectorReportData);
0148:
0149: document.newPage();
0150: appendDeletedOriginEntryAndDetailReport(document,
0151: collectorReportData);
0152:
0153: document.newPage();
0154: appendDetailChangedAccountReport(document,
0155: collectorReportData);
0156:
0157: document.newPage();
0158: appendLedgerReport(document, collectorReportData);
0159:
0160: document.close();
0161: } catch (DocumentException e) {
0162: LOG.error("Error generating reports.", e);
0163: throw new RuntimeException("Error generating reports.", e);
0164: }
0165: }
0166:
0167: /**
0168: * Appends Collector header information to a given PDF document
0169: *
0170: * @param document a PDF document to write to
0171: * @param collectorReportData data gathered from the run of the Collector
0172: * @throws DocumentException thrown if something goes wrong with writing to the PDF document
0173: */
0174: protected void appendCollectorHeaderInformation(Document document,
0175: CollectorReportData collectorReportData)
0176: throws DocumentException {
0177: Iterator<CollectorBatch> batchIter = collectorReportData
0178: .getAddedBatches();
0179: OriginEntryTotals aggregateOriginEntryTotals = new OriginEntryTotals();
0180: int aggregateTotalRecordsCountFromTrailer = 0;
0181: int aggregateNumInputDetails = 0;
0182: int aggregateNumSavedDetails = 0;
0183:
0184: if (!collectorReportData.getAllUnparsableBatchNames().isEmpty()) {
0185: Paragraph unparsableBatchNames = new Paragraph();
0186: unparsableBatchNames.setAlignment(Paragraph.ALIGN_LEFT);
0187: unparsableBatchNames.setFirstLineIndent(0);
0188: unparsableBatchNames.setIndentationLeft(20);
0189: unparsableBatchNames.setLeading(textFontSize);
0190: unparsableBatchNames.add(new Phrase(
0191: "The following files could not be parsed:",
0192: textFont));
0193: for (String unparsableBatchName : collectorReportData
0194: .getAllUnparsableBatchNames()) {
0195: List<String> batchErrors = translateErrorsFromErrorMap(collectorReportData
0196: .getErrorMapForBatchName(unparsableBatchName));
0197: unparsableBatchNames.add(new Phrase("\n "
0198: + unparsableBatchName + "\n", textFont));
0199: com.lowagie.text.List errorMessageList = new com.lowagie.text.List(
0200: false, 20);
0201: errorMessageList.setListSymbol(new Chunk("-",
0202: headerFont));
0203: errorMessageList.setIndentationLeft(50);
0204: for (String errorMessage : batchErrors) {
0205: ListItem listItem = new ListItem("ERROR MESSAGE: "
0206: + errorMessage, textFont);
0207: listItem.setLeading(textFontSize);
0208: errorMessageList.add(listItem);
0209: }
0210: unparsableBatchNames.add(errorMessageList);
0211: }
0212: document.add(unparsableBatchNames);
0213: }
0214:
0215: while (batchIter.hasNext()) {
0216: CollectorBatch batch = batchIter.next();
0217: StringBuilder buf = new StringBuilder();
0218:
0219: OriginEntryTotals batchOriginEntryTotals = collectorReportData
0220: .getOriginEntryTotals(batch);
0221: appendHeaderInformation(buf, batch);
0222: appendTotalsInformation(buf, batch, batchOriginEntryTotals);
0223:
0224: List<String> errorMessages = translateErrorsFromErrorMap(collectorReportData
0225: .getErrorMapForBatchName(batch.getBatchName()));
0226:
0227: aggregateTotalRecordsCountFromTrailer += batch
0228: .getTotalRecords();
0229:
0230: // if batch is valid add up totals
0231: if (collectorReportData.isBatchValid(batch)) {
0232:
0233: if (batchOriginEntryTotals != null) {
0234: aggregateOriginEntryTotals
0235: .incorporateTotals(batchOriginEntryTotals);
0236: }
0237:
0238: Integer batchNumInputDetails = collectorReportData
0239: .getNumInputDetails(batch);
0240: if (batchNumInputDetails != null) {
0241: aggregateNumInputDetails += batchNumInputDetails;
0242: }
0243:
0244: Integer batchNumSavedDetails = collectorReportData
0245: .getNumSavedDetails(batch);
0246: if (batchNumSavedDetails != null) {
0247: aggregateNumSavedDetails += batchNumSavedDetails;
0248: }
0249: }
0250:
0251: Paragraph summary = new Paragraph();
0252: summary.setAlignment(Paragraph.ALIGN_LEFT);
0253: summary.setFirstLineIndent(0);
0254: summary.setLeading(textFontSize);
0255: summary
0256: .add(new Phrase(
0257: "Header *********************************************************************",
0258: textFont));
0259: summary.add(new Phrase(buf.toString(), textFont));
0260:
0261: String validationErrors = getValidationStatus(
0262: errorMessages, false, 15);
0263: if (StringUtils.isNotBlank(validationErrors)) {
0264: summary.add(new Phrase(validationErrors, textFont));
0265: }
0266: document.add(summary);
0267: }
0268:
0269: Paragraph totals = new Paragraph();
0270: totals.setLeading(textFontSize);
0271: totals.add(new Phrase(
0272: "\n\n***** Totals for Creation of GLE Data *****\n",
0273: textFont));
0274: totals
0275: .add(new Phrase(
0276: " Total Records Read "
0277: + StringUtils
0278: .leftPad(
0279: Integer
0280: .toString(aggregateTotalRecordsCountFromTrailer),
0281: 9, '0') + "\n",
0282: textFont));
0283: totals.add(new Phrase(" Total Groups Read "
0284: + StringUtils.leftPad(Integer
0285: .toString(collectorReportData
0286: .getNumPersistedBatches()), 9, '0')
0287: + "\n", textFont));
0288: totals.add(new Phrase(" Total Groups Bypassed "
0289: + StringUtils.leftPad(Integer
0290: .toString(collectorReportData
0291: .getNumNotPersistedBatches()), 9, '0')
0292: + "\n", textFont));
0293: totals.add(new Phrase(" Total WWW Records Out "
0294: + StringUtils.leftPad(Integer
0295: .toString(aggregateNumInputDetails), 9, '0')
0296: + "\n", textFont));
0297: int aggregateOriginEntryCountFromParsedData = aggregateOriginEntryTotals
0298: .getNumCreditEntries()
0299: + aggregateOriginEntryTotals.getNumDebitEntries()
0300: + aggregateOriginEntryTotals.getNumOtherEntries();
0301: totals
0302: .add(new Phrase(
0303: " Total GLE Records Out "
0304: + StringUtils
0305: .leftPad(
0306: Integer
0307: .toString(aggregateOriginEntryCountFromParsedData),
0308: 9, '0') + "\n",
0309: textFont));
0310: totals.add(new Phrase(" Total GLE Debits "
0311: + StringUtils.leftPad(CURRENCY_SYMBOL
0312: + aggregateOriginEntryTotals.getDebitAmount(),
0313: 19, ' ') + "\n", textFont));
0314: totals.add(new Phrase(" Debit Count "
0315: + StringUtils.leftPad(Integer
0316: .toString(aggregateOriginEntryTotals
0317: .getNumDebitEntries()), 9, '0') + "\n",
0318: textFont));
0319: totals.add(new Phrase(" Total GLE Credits "
0320: + StringUtils.leftPad(CURRENCY_SYMBOL
0321: + aggregateOriginEntryTotals.getCreditAmount(),
0322: 19, ' ') + "\n", textFont));
0323: totals
0324: .add(new Phrase(
0325: " Debit Count "
0326: + StringUtils
0327: .leftPad(
0328: Integer
0329: .toString(aggregateOriginEntryTotals
0330: .getNumCreditEntries()),
0331: 9, '0') + "\n",
0332: textFont));
0333: totals.add(new Phrase(" Total GLE Not C or D "
0334: + StringUtils.leftPad(CURRENCY_SYMBOL
0335: + aggregateOriginEntryTotals.getOtherAmount(),
0336: 19, ' ') + "\n", textFont));
0337: totals.add(new Phrase(" Not C or D Count "
0338: + StringUtils.leftPad(Integer
0339: .toString(aggregateOriginEntryTotals
0340: .getNumOtherEntries()), 9, '0')
0341: + "\n\n", textFont));
0342: totals.add(new Phrase("Inserted " + aggregateNumSavedDetails
0343: + " detail records into gl_id_bill_t\n", textFont));
0344: document.add(totals);
0345: }
0346:
0347: /**
0348: * Appends header information to the given buffer
0349: *
0350: * @param buf the buffer where the message should go
0351: * @param batch the data from the Collector file
0352: */
0353: protected void appendHeaderInformation(StringBuilder buf,
0354: CollectorBatch batch) {
0355: buf.append("\n Chart: ").append(
0356: batch.getChartOfAccountsCode()).append("\n");
0357: buf.append(" Org: ").append(batch.getOrganizationCode())
0358: .append("\n");
0359: buf.append(" Campus: ").append(batch.getCampusCode())
0360: .append("\n");
0361: buf.append(" Department: ").append(
0362: batch.getDepartmentName()).append("\n");
0363: buf.append(" Mailing Address: ").append(
0364: batch.getMailingAddress()).append("\n");
0365: buf.append(" Contact: ").append(batch.getPersonUserID())
0366: .append("\n");
0367: buf.append(" Email: ").append(batch.getWorkgroupName())
0368: .append("\n");
0369: buf.append(" Transmission Date: ").append(
0370: batch.getTransmissionDate()).append("\n\n");
0371: }
0372:
0373: /**
0374: * Writes totals information to the report
0375: *
0376: * @param buf the buffer where the e-mail report is being written
0377: * @param batch the data generated by the Collector file upload
0378: * @param totals the totals to write
0379: */
0380: protected void appendTotalsInformation(StringBuilder buf,
0381: CollectorBatch batch, OriginEntryTotals totals) {
0382: if (totals == null) {
0383: buf
0384: .append(" Totals are unavailable for this batch.\n");
0385: } else {
0386: // SUMMARY TOTALS HERE
0387: appendAmountCountLine(buf, "Group Credits = ", Integer
0388: .toString(totals.getNumCreditEntries()),
0389: CURRENCY_SYMBOL
0390: + totals.getCreditAmount().toString());
0391: appendAmountCountLine(buf, "Group Debits = ", Integer
0392: .toString(totals.getNumDebitEntries()),
0393: CURRENCY_SYMBOL
0394: + totals.getDebitAmount().toString());
0395: appendAmountCountLine(buf, "Group Not C/D = ", Integer
0396: .toString(totals.getNumOtherEntries()),
0397: CURRENCY_SYMBOL
0398: + totals.getOtherAmount().toString());
0399: appendAmountCountLine(buf, "Valid Group Count = ", batch
0400: .getTotalRecords().toString(), CURRENCY_SYMBOL
0401: + batch.getTotalAmount());
0402: }
0403: }
0404:
0405: /**
0406: * This opens a PDF document to write the report on
0407: *
0408: * @param destinationDirectory the directory where the report should be written to
0409: * @param fileprefix the beginning of the file name
0410: * @param runDate the date when the Collector was run (to name the file after)
0411: * @param title the title of this report
0412: * @return a PDF document to write to
0413: */
0414: protected Document openPdfWriter(String destinationDirectory,
0415: String fileprefix, Date runDate, String title) {
0416: try {
0417: Document document = new Document(PageSize.A4.rotate());
0418:
0419: PageHelper helper = new PageHelper();
0420: helper.runDate = runDate;
0421: helper.headerFont = headerFont;
0422: helper.title = title;
0423:
0424: String filename = destinationDirectory + "/" + fileprefix
0425: + "_";
0426: SimpleDateFormat sdf = new SimpleDateFormat(
0427: "yyyyMMdd_HHmmss");
0428: filename = filename + sdf.format(runDate);
0429: filename = filename + ".pdf";
0430: PdfWriter writer = PdfWriter.getInstance(document,
0431: new FileOutputStream(filename));
0432: writer.setPageEvent(helper);
0433:
0434: document.open();
0435:
0436: return document;
0437: } catch (Exception e) {
0438: LOG
0439: .error(
0440: "Exception caught trying to create new PDF document",
0441: e);
0442: if (e instanceof RuntimeException) {
0443: throw (RuntimeException) e;
0444: } else {
0445: throw new RuntimeException(e);
0446: }
0447: }
0448: }
0449:
0450: /**
0451: * Writes the Amount/Count line of the Collector to a buffer
0452: *
0453: * @param buf the buffer to write the line to
0454: * @param countTitle the title of this part of the report
0455: * @param count the Collector count
0456: * @param amountString the Collector amount
0457: */
0458: protected void appendAmountCountLine(StringBuilder buf,
0459: String countTitle, String count, String amountString) {
0460: appendPaddingString(buf, ' ', countTitle.length(), 35);
0461: buf.append(countTitle);
0462:
0463: appendPaddingString(buf, '0', count.length(), 5);
0464: buf.append(count);
0465:
0466: appendPaddingString(buf, ' ', amountString.length(), 21);
0467: buf.append(amountString).append("\n");
0468:
0469: }
0470:
0471: /**
0472: * Writes some padding to a buffer
0473: *
0474: * @param buf the buffer to write to
0475: * @param padCharacter the character to repeat in the pad
0476: * @param valueLength the length of the value being padded
0477: * @param desiredLength the length the whole String should be
0478: * @return the buffer
0479: */
0480: protected StringBuilder appendPaddingString(StringBuilder buf,
0481: char padCharacter, int valueLength, int desiredLength) {
0482: for (int i = valueLength; i < desiredLength; i++) {
0483: buf.append(padCharacter);
0484: }
0485: return buf;
0486: }
0487:
0488: /**
0489: * Writes the results of the Scrubber's run on the Collector data to the report PDF
0490: *
0491: * @param document the PDF document to write to
0492: * @param collectorReportData data gathered from the run of the Collector
0493: * @throws DocumentException thrown if the PDF cannot be written to for some reason
0494: */
0495: protected void appendScrubberReport(Document document,
0496: CollectorReportData collectorReportData)
0497: throws DocumentException {
0498: Iterator<CollectorBatch> batchIter = collectorReportData
0499: .getAddedBatches();
0500: ScrubberReportData aggregateScrubberReportData = new ScrubberReportData();
0501: Map<Transaction, List<Message>> aggregateScrubberErrors = new LinkedHashMap<Transaction, List<Message>>();
0502:
0503: while (batchIter.hasNext()) {
0504: CollectorBatch batch = batchIter.next();
0505:
0506: ScrubberReportData batchScrubberReportData = collectorReportData
0507: .getScrubberReportData(batch);
0508: if (batchScrubberReportData != null) {
0509: // if some validation error occured during batch load, the scrubber wouldn't have been run, so there'd be no data
0510: aggregateScrubberReportData
0511: .incorporateReportData(batchScrubberReportData);
0512: }
0513:
0514: Map<Transaction, List<Message>> batchScrubberReportErrors = collectorReportData
0515: .getBatchOriginEntryScrubberErrors(batch);
0516: if (batchScrubberReportErrors != null) {
0517: // if some validation error occured during batch load, the scrubber wouldn't have been run, so there'd be a null map
0518: aggregateScrubberErrors
0519: .putAll(batchScrubberReportErrors);
0520: }
0521: }
0522:
0523: List<Transaction> transactions = new ArrayList<Transaction>(
0524: aggregateScrubberErrors.keySet());
0525:
0526: TransactionReport transactionReport = new TransactionReport();
0527: List<Summary> summaries = buildScrubberReportSummary(aggregateScrubberReportData);
0528:
0529: transactionReport.appendReport(document, headerFont, textFont,
0530: transactions, aggregateScrubberErrors, summaries,
0531: dateTimeService.getCurrentDate());
0532: }
0533:
0534: /**
0535: * Writes the report of the demerger run against the Collector data
0536: *
0537: * @param document a PDF document to write to
0538: * @param collectorReportData data gathered from the run of the Collector
0539: * @throws DocumentException the exception thrown if the PDF cannot be written to
0540: */
0541: protected void appendDemergerReport(Document document,
0542: CollectorReportData collectorReportData)
0543: throws DocumentException {
0544: Iterator<CollectorBatch> batchIter = collectorReportData
0545: .getAddedBatches();
0546: DemergerReportData aggregateDemergerReportData = new DemergerReportData();
0547: ScrubberReportData aggregateScrubberReportData = new ScrubberReportData();
0548:
0549: while (batchIter.hasNext()) {
0550: CollectorBatch batch = batchIter.next();
0551:
0552: ScrubberReportData batchScrubberReportData = collectorReportData
0553: .getScrubberReportData(batch);
0554: if (batchScrubberReportData != null) {
0555: aggregateScrubberReportData
0556: .incorporateReportData(batchScrubberReportData);
0557:
0558: DemergerReportData batchDemergerReportData = collectorReportData
0559: .getDemergerReportData(batch);
0560: if (batchDemergerReportData != null) {
0561: aggregateDemergerReportData
0562: .incorporateReportData(batchDemergerReportData);
0563: }
0564: }
0565: }
0566:
0567: List<Summary> summaries = buildDemergerReportSummary(
0568: aggregateScrubberReportData,
0569: aggregateDemergerReportData);
0570: List<Transaction> emptyTrans = Collections.emptyList();
0571: Map<Transaction, List<Message>> emptyErrors = Collections
0572: .emptyMap();
0573:
0574: TransactionReport transactionReport = new TransactionReport();
0575: transactionReport.appendReport(document, headerFont, textFont,
0576: emptyTrans, emptyErrors, summaries, dateTimeService
0577: .getCurrentDate());
0578: }
0579:
0580: /**
0581: * Writes information about origin entry and details to the report
0582: *
0583: * @param document a PDF document to write to
0584: * @param collectorReportData data gathered from the run of the Collector
0585: * @throws DocumentException the exception thrown if the PDF cannot be written to
0586: */
0587: protected void appendDeletedOriginEntryAndDetailReport(
0588: Document document, CollectorReportData collectorReportData)
0589: throws DocumentException {
0590: // figure out how many billing details were removed/bypassed in all of the batches
0591: Iterator<CollectorBatch> batchIter = collectorReportData
0592: .getAddedBatches();
0593: int aggregateNumDetailsDeleted = 0;
0594:
0595: StringBuilder buf = new StringBuilder();
0596:
0597: buf
0598: .append("ID-Billing detail data matched with GLE errors to remove documents with errors\n");
0599: while (batchIter.hasNext()) {
0600: CollectorBatch batch = batchIter.next();
0601:
0602: Integer batchNumDetailsDeleted = collectorReportData
0603: .getNumDetailDeleted(batch);
0604: if (batchNumDetailsDeleted != null) {
0605: aggregateNumDetailsDeleted += batchNumDetailsDeleted
0606: .intValue();
0607: }
0608: }
0609: buf.append("Total-Recs-Bypassed ").append(
0610: aggregateNumDetailsDeleted).append("\n");
0611:
0612: batchIter = collectorReportData.getAddedBatches();
0613: int aggregateTransactionCount = 0;
0614: KualiDecimal aggregateDebitAmount = KualiDecimal.ZERO;
0615: while (batchIter.hasNext()) {
0616: CollectorBatch batch = batchIter.next();
0617:
0618: Map<DocumentGroupData, OriginEntryTotals> inputEntryTotals = collectorReportData
0619: .getTotalsOnInputOriginEntriesAssociatedWithErrorGroup(batch);
0620: if (inputEntryTotals != null) {
0621: for (Map.Entry<DocumentGroupData, OriginEntryTotals> errorDocumentGroupEntry : inputEntryTotals
0622: .entrySet()) {
0623: // normally, blank credit/debit code is treated as a debit, but the ID billing program (the predecessor to the
0624: // collector)
0625: // was specific about treating only a code of 'D' as a debit
0626:
0627: buf.append("Message sent to ").append(
0628: StringUtils.rightPad(batch
0629: .getWorkgroupName(), 50, ' '))
0630: .append("for Document ").append(
0631: errorDocumentGroupEntry.getKey()
0632: .getDocumentNumber())
0633: .append("\n");
0634: int documentTransactionCount = errorDocumentGroupEntry
0635: .getValue().getNumCreditEntries()
0636: + errorDocumentGroupEntry.getValue()
0637: .getNumDebitEntries()
0638: + errorDocumentGroupEntry.getValue()
0639: .getNumOtherEntries();
0640: aggregateTransactionCount += documentTransactionCount;
0641: aggregateDebitAmount = aggregateDebitAmount
0642: .add(errorDocumentGroupEntry.getValue()
0643: .getDebitAmount());
0644: buf.append("Total Transactions ").append(
0645: documentTransactionCount).append(
0646: " for Total Debit Amount ").append(
0647: CURRENCY_SYMBOL).append(
0648: errorDocumentGroupEntry.getValue()
0649: .getDebitAmount()).append("\n");
0650: }
0651: }
0652: }
0653: buf.append("Total Error Records ").append(
0654: aggregateTransactionCount).append("\n");
0655: buf.append("Total Debit Dollars ").append(CURRENCY_SYMBOL)
0656: .append(aggregateDebitAmount).append("\n");
0657: Paragraph report = new Paragraph(buf.toString(), textFont);
0658: report.setLeading(textFontSize);
0659: document.add(report);
0660:
0661: }
0662:
0663: /**
0664: * Writes information about what details where changed in the Collector to the report
0665: *
0666: * @param document a PDF document to write to
0667: * @param collectorReportData data gathered from the run of the Collector
0668: * @throws DocumentException the exception thrown if the PDF cannot be written to
0669: */
0670: protected void appendDetailChangedAccountReport(Document document,
0671: CollectorReportData collectorReportData)
0672: throws DocumentException {
0673: StringBuilder buf = new StringBuilder();
0674:
0675: buf
0676: .append("ID-Billing Detail Records with Account Numbers Changed Due to Change of Corresponding GLE Data\nTot-Recs-Changed ");
0677: Iterator<CollectorBatch> batchIter = collectorReportData
0678: .getAddedBatches();
0679: int aggregateNumDetailAccountValuesChanged = 0;
0680: while (batchIter.hasNext()) {
0681: CollectorBatch batch = batchIter.next();
0682:
0683: Integer batchNumDetailAccountValuesChanged = collectorReportData
0684: .getNumDetailAccountValuesChanged(batch);
0685: if (batchNumDetailAccountValuesChanged != null) {
0686: aggregateNumDetailAccountValuesChanged += batchNumDetailAccountValuesChanged;
0687: }
0688: }
0689: buf.append(aggregateNumDetailAccountValuesChanged);
0690: Paragraph report = new Paragraph(buf.toString(), textFont);
0691: report.setLeading(textFontSize);
0692: document.add(report);
0693: }
0694:
0695: /**
0696: * Gets the dateTimeService attribute.
0697: *
0698: * @return Returns the dateTimeService.
0699: */
0700: protected DateTimeService getDateTimeService() {
0701: return dateTimeService;
0702: }
0703:
0704: /**
0705: * Sets the dateTimeService attribute value.
0706: *
0707: * @param dateTimeService The dateTimeService to set.
0708: */
0709: public void setDateTimeService(DateTimeService dateTimeService) {
0710: this .dateTimeService = dateTimeService;
0711: }
0712:
0713: /**
0714: * Generate the header for the scrubber status report.
0715: *
0716: * @param scrubberReport the data gathered from the run of the scrubber to include in the report
0717: * @return list of report summaries to be printed
0718: */
0719: protected List<Summary> buildScrubberReportSummary(
0720: ScrubberReportData scrubberReport) {
0721: List<Summary> reportSummary = new ArrayList<Summary>();
0722:
0723: reportSummary.add(new Summary(2, "UNSCRUBBED RECORDS READ",
0724: new Integer(scrubberReport
0725: .getNumberOfUnscrubbedRecordsRead())));
0726: reportSummary.add(new Summary(3, "SCRUBBED RECORDS WRITTEN",
0727: new Integer(scrubberReport
0728: .getNumberOfScrubbedRecordsWritten())));
0729: reportSummary.add(new Summary(4, "ERROR RECORDS WRITTEN",
0730: new Integer(scrubberReport
0731: .getNumberOfErrorRecordsWritten())));
0732: reportSummary.add(new Summary(11,
0733: "TOTAL OUTPUT RECORDS WRITTEN",
0734: new Integer(scrubberReport
0735: .getTotalNumberOfRecordsWritten())));
0736: reportSummary.add(new Summary(12, "EXPIRED ACCOUNTS FOUND",
0737: new Integer(scrubberReport
0738: .getNumberOfExpiredAccountsFound())));
0739:
0740: return reportSummary;
0741: }
0742:
0743: /**
0744: * Generate the header for the demerger status report.
0745: *
0746: * @param scrubberReportData the data gathered from the run of the scrubber on the collector data
0747: * @param demergerReport the data gathered from the run of the demerger on the collector data
0748: * @return list of report summaries to be printed
0749: */
0750: protected List<Summary> buildDemergerReportSummary(
0751: ScrubberReportData scrubberReportData,
0752: DemergerReportData demergerReport) {
0753: List<Summary> reportSummary = new ArrayList<Summary>();
0754: reportSummary.add(new Summary(1, "ERROR RECORDS READ",
0755: new Integer(scrubberReportData
0756: .getNumberOfErrorRecordsWritten())));
0757: reportSummary.add(new Summary(2, "VALID RECORDS READ",
0758: new Integer(scrubberReportData
0759: .getNumberOfScrubbedRecordsWritten())));
0760: reportSummary.add(new Summary(3,
0761: "ERROR RECORDS REMOVED FROM PROCESSING", new Integer(
0762: demergerReport.getErrorTransactionsSaved())));
0763: reportSummary.add(new Summary(4,
0764: "VALID RECORDS ENTERED INTO ORIGIN ENTRY", new Integer(
0765: demergerReport.getValidTransactionsSaved())));
0766:
0767: return reportSummary;
0768: }
0769:
0770: /**
0771: * Adds the ledger report to this Collector report
0772: *
0773: * @param document the PDF document that the report is being written to
0774: * @param collectorReportData the data from the Collector run
0775: * @throws DocumentException thrown if it is impossible to write to the report
0776: */
0777: protected void appendLedgerReport(Document document,
0778: CollectorReportData collectorReportData)
0779: throws DocumentException {
0780: LedgerEntryHolder ledgerEntryHolder = collectorReportData
0781: .getLedgerEntryHolder();
0782: Paragraph header = new Paragraph();
0783: header.setAlignment(Paragraph.ALIGN_CENTER);
0784:
0785: header.add(new Phrase(
0786: "GENERAL LEDGER INPUT TRANSACTIONS FROM COLLECTOR\n\n",
0787: headerFont));
0788: document.add(header);
0789:
0790: if (ledgerEntryHolder != null) {
0791: LedgerReport ledgerReport = new LedgerReport();
0792: PdfPTable reportContents = ledgerReport
0793: .drawPdfTable(ledgerEntryHolder);
0794: document.add(reportContents);
0795: } else {
0796: Paragraph noDataMessage = new Paragraph();
0797: noDataMessage.setAlignment(Paragraph.ALIGN_CENTER);
0798: noDataMessage.add(new Phrase(
0799: "\n\nNO DATA/ORIGIN ENTRIES FOUND.", textFont));
0800: document.add(noDataMessage);
0801: }
0802: }
0803:
0804: /**
0805: * Builds actual error message from error key and parameters.
0806: * @param errorMap a map of errors
0807: * @return List<String> of error message text
0808: */
0809: protected List<String> translateErrorsFromErrorMap(ErrorMap errorMap) {
0810: List<String> collectorErrors = new ArrayList();
0811:
0812: for (Iterator iter = errorMap.getPropertiesWithErrors()
0813: .iterator(); iter.hasNext();) {
0814: String errorKey = (String) iter.next();
0815:
0816: for (Iterator iter2 = errorMap.getMessages(errorKey)
0817: .iterator(); iter2.hasNext();) {
0818: ErrorMessage errorMessage = (ErrorMessage) iter2.next();
0819: String messageText = configurationService
0820: .getPropertyString(errorMessage.getErrorKey());
0821: collectorErrors
0822: .add(MessageFormat.format(messageText,
0823: (Object[]) errorMessage
0824: .getMessageParameters()));
0825: }
0826: }
0827:
0828: return collectorErrors;
0829: }
0830:
0831: /**
0832: * Gets the directoryName attribute.
0833: *
0834: * @return Returns the directoryName.
0835: */
0836: public String getDirectoryName() {
0837: return directoryName;
0838: }
0839:
0840: /**
0841: * Sets the directoryName attribute value.
0842: *
0843: * @param directoryName The directoryName to set.
0844: */
0845: public void setDirectoryName(String directoryName) {
0846: this .directoryName = directoryName;
0847: }
0848:
0849: /**
0850: * Sends email with results of the batch processing.
0851: * @param batch the Collector data from the file
0852: * @param collectorReportData data gathered from the run of the Collector
0853: */
0854: protected void sendValidationEmail(CollectorBatch batch,
0855: CollectorReportData collectorReportData) {
0856: ErrorMap errorMap = collectorReportData
0857: .getErrorMapForBatchName(batch.getBatchName());
0858: List<String> errorMessages = translateErrorsFromErrorMap(errorMap);
0859:
0860: LOG.debug("sendValidationEmail() starting");
0861: MailMessage message = new MailMessage();
0862:
0863: message.setFromAddress(mailService.getBatchMailingList());
0864:
0865: String subject = parameterService
0866: .getParameterValue(
0867: CollectorStep.class,
0868: SystemGroupParameterNames.COLLECTOR_VALIDATOR_EMAIL_SUBJECT_PARAMETER_NAME);
0869: String productionEnvironmentCode = configurationService
0870: .getPropertyString(KFSConstants.PROD_ENVIRONMENT_CODE_KEY);
0871: String environmentCode = configurationService
0872: .getPropertyString(KFSConstants.ENVIRONMENT_KEY);
0873: if (!StringUtils.equals(productionEnvironmentCode,
0874: environmentCode)) {
0875: subject = environmentCode + ": " + subject;
0876: }
0877: message.setSubject(subject);
0878:
0879: String body = createValidationMessageBody(errorMessages, batch,
0880: collectorReportData);
0881: message.setMessage(body);
0882: message.addToAddress(batch.getWorkgroupName());
0883:
0884: try {
0885: mailService.sendMessage(message);
0886:
0887: String notificationMessage = configurationService
0888: .getPropertyString(KFSKeyConstants.Collector.NOTIFICATION_EMAIL_SENT);
0889: String formattedMessage = MessageFormat.format(
0890: notificationMessage, new Object[] { batch
0891: .getWorkgroupName() });
0892: collectorReportData.setEmailSendingStatusForParsedBatch(
0893: batch, formattedMessage);
0894: } catch (InvalidAddressException e) {
0895: LOG
0896: .error(
0897: "sendErrorEmail() Invalid email address. Message not sent",
0898: e);
0899: String errorMessage = configurationService
0900: .getPropertyString(KFSKeyConstants.Collector.EMAIL_SEND_ERROR);
0901: String formattedMessage = MessageFormat.format(
0902: errorMessage, new Object[] { batch
0903: .getWorkgroupName() });
0904: collectorReportData.setEmailSendingStatusForParsedBatch(
0905: batch, formattedMessage);
0906: }
0907: }
0908:
0909: /**
0910: * Sends the e-mail about the demerger step
0911: *
0912: * @param batch the data from the Collector file
0913: * @param collectorReportData data gathered from the run of the Collector
0914: */
0915: protected void sendDemergerEmail(CollectorBatch batch,
0916: CollectorReportData collectorReportData) {
0917: LOG.debug("sendDemergerEmail() starting");
0918: String body = createDemergerMessageBody(batch,
0919: collectorReportData);
0920: if (body == null) {
0921: // there must not have been anything to send, so just return from this method
0922: return;
0923: }
0924: MailMessage message = new MailMessage();
0925:
0926: message.setFromAddress(mailService.getBatchMailingList());
0927:
0928: String subject = parameterService
0929: .getParameterValue(
0930: CollectorStep.class,
0931: SystemGroupParameterNames.COLLECTOR_DEMERGER_EMAIL_SUBJECT_PARAMETER_NAME);
0932: String productionEnvironmentCode = configurationService
0933: .getPropertyString(KFSConstants.PROD_ENVIRONMENT_CODE_KEY);
0934: String environmentCode = configurationService
0935: .getPropertyString(KFSConstants.ENVIRONMENT_KEY);
0936: if (!StringUtils.equals(productionEnvironmentCode,
0937: environmentCode)) {
0938: subject = environmentCode + ": " + subject;
0939: }
0940: message.setSubject(subject);
0941:
0942: message.setMessage(body);
0943: message.addToAddress(batch.getWorkgroupName());
0944:
0945: try {
0946: mailService.sendMessage(message);
0947:
0948: String notificationMessage = configurationService
0949: .getPropertyString(KFSKeyConstants.Collector.NOTIFICATION_EMAIL_SENT);
0950: String formattedMessage = MessageFormat.format(
0951: notificationMessage, new Object[] { batch
0952: .getWorkgroupName() });
0953: collectorReportData.setEmailSendingStatusForParsedBatch(
0954: batch, formattedMessage);
0955: } catch (InvalidAddressException e) {
0956: LOG
0957: .error(
0958: "sendErrorEmail() Invalid email address. Message not sent",
0959: e);
0960: String errorMessage = configurationService
0961: .getPropertyString(KFSKeyConstants.Collector.EMAIL_SEND_ERROR);
0962: String formattedMessage = MessageFormat.format(
0963: errorMessage, new Object[] { batch
0964: .getWorkgroupName() });
0965: collectorReportData.setEmailSendingStatusForParsedBatch(
0966: batch, formattedMessage);
0967: }
0968: }
0969:
0970: /**
0971: * Creates a section about validation messages
0972: *
0973: * @param errorMessages a List of errors that happened during the Collector run
0974: * @param batch the data from the Collector file
0975: * @param collectorReportData data gathered from the run of the Collector
0976: * @return the Validation message body
0977: */
0978: protected String createValidationMessageBody(
0979: List<String> errorMessages, CollectorBatch batch,
0980: CollectorReportData collectorReportData) {
0981: StringBuilder body = new StringBuilder();
0982:
0983: ErrorMap fileErrorMap = collectorReportData
0984: .getErrorMapForBatchName(batch.getBatchName());
0985:
0986: body.append("Header Information:\n\n");
0987: if (!fileErrorMap
0988: .containsMessageKey(KFSKeyConstants.ERROR_BATCH_UPLOAD_PARSING_XML)) {
0989: appendHeaderInformation(body, batch);
0990: appendTotalsInformation(body, batch, collectorReportData
0991: .getOriginEntryTotals(batch));
0992: appendValidationStatus(body, errorMessages, true, 0);
0993: }
0994:
0995: return body.toString();
0996: }
0997:
0998: /**
0999: * Generates a String that reports on the validation status of the document
1000: *
1001: * @param errorMessages a List of error messages encountered in the Collector process
1002: * @param notifyIfSuccessful true if a special message for the process running successfully should be added, false otherwise
1003: * @param numLeftPaddingSpaces the number of spaces to pad on the left
1004: * @return a String with the validation status message
1005: */
1006: protected String getValidationStatus(List<String> errorMessages,
1007: boolean notifyIfSuccessful, int numLeftPaddingSpaces) {
1008: StringBuilder buf = new StringBuilder();
1009: appendValidationStatus(buf, errorMessages, notifyIfSuccessful,
1010: numLeftPaddingSpaces);
1011: return buf.toString();
1012: }
1013:
1014: /**
1015: * Appends the validation status message to a buffer
1016: *
1017: * @param buf a StringBuilder to append error messages to
1018: * @param errorMessages a List of error messages encountered in the Collector process
1019: * @param notifyIfSuccessful true if a special message for the process running successfully should be added, false otherwise
1020: * @param numLeftPaddingSpaces the number of spaces to pad on the left
1021: */
1022: protected void appendValidationStatus(StringBuilder buf,
1023: List<String> errorMessages, boolean notifyIfSuccessful,
1024: int numLeftPaddingSpaces) {
1025: String padding = StringUtils.leftPad("", numLeftPaddingSpaces,
1026: ' ');
1027:
1028: if (notifyIfSuccessful || !errorMessages.isEmpty()) {
1029: buf.append("\n").append(padding).append(
1030: "Reported Errors:\n");
1031: }
1032:
1033: // ERRORS GO HERE
1034: if (errorMessages.isEmpty() && notifyIfSuccessful) {
1035: buf
1036: .append(padding)
1037: .append(
1038: "----- NO ERRORS TO REPORT -----\nThis file will be processed by the accounting cycle.\n");
1039: } else if (!errorMessages.isEmpty()) {
1040: for (String currentMessage : errorMessages) {
1041: buf.append(padding).append(currentMessage + "\n");
1042: }
1043: buf
1044: .append("\n")
1045: .append(padding)
1046: .append(
1047: "----- THIS FILE WAS NOT PROCESSED AND WILL NEED TO BE CORRECTED AND RESUBMITTED -----\n");
1048: }
1049: }
1050:
1051: /**
1052: * Writes the part of the report about the demerger
1053: *
1054: * @param batch the data from the Collector file
1055: * @param collectorReportData data gathered from the run of the Collector
1056: * @return
1057: */
1058: protected String createDemergerMessageBody(CollectorBatch batch,
1059: CollectorReportData collectorReportData) {
1060: StringBuilder buf = new StringBuilder();
1061: appendHeaderInformation(buf, batch);
1062:
1063: Map<Transaction, List<Message>> batchOriginEntryScrubberErrors = collectorReportData
1064: .getBatchOriginEntryScrubberErrors(batch);
1065:
1066: // the keys of the map returned by getTotalsOnInputOriginEntriesAssociatedWithErrorGroup represent all of the error document
1067: // groups in the system
1068: Map<DocumentGroupData, OriginEntryTotals> errorGroupDocumentTotals = collectorReportData
1069: .getTotalsOnInputOriginEntriesAssociatedWithErrorGroup(batch);
1070: Set<DocumentGroupData> errorDocumentGroups = null;
1071: if (errorGroupDocumentTotals == null) {
1072: return null;
1073: }
1074: errorDocumentGroups = errorGroupDocumentTotals.keySet();
1075: if (errorDocumentGroups.isEmpty()) {
1076: return null;
1077: } else {
1078: for (DocumentGroupData errorDocumentGroup : errorDocumentGroups) {
1079: buf.append("Document ").append(
1080: errorDocumentGroup.getDocumentNumber()).append(
1081: " Rejected Due to Editing Errors.\n");
1082: for (Transaction transaction : batchOriginEntryScrubberErrors
1083: .keySet()) {
1084: if (errorDocumentGroup
1085: .matchesTransaction(transaction)) {
1086: if (transaction instanceof OriginEntryFull) {
1087: OriginEntryFull entry = (OriginEntryFull) transaction;
1088: buf.append(" Origin Entry: ").append(
1089: entry.getLine()).append("\n");
1090: for (Message message : batchOriginEntryScrubberErrors
1091: .get(transaction)) {
1092: buf.append(" ").append(
1093: message.getMessage()).append(
1094: "\n");
1095: }
1096: }
1097: }
1098: }
1099: }
1100: }
1101:
1102: return buf.toString();
1103: }
1104:
1105: /**
1106: * Gets the mailService attribute.
1107: *
1108: * @return Returns the mailService.
1109: */
1110: public MailService getMailService() {
1111: return mailService;
1112: }
1113:
1114: /**
1115: * Sets the mailService attribute value.
1116: *
1117: * @param mailService The mailService to set.
1118: */
1119: public void setMailService(MailService mailService) {
1120: this .mailService = mailService;
1121: }
1122:
1123: public void setConfigurationService(
1124: KualiConfigurationService configurationService) {
1125: this .configurationService = configurationService;
1126: }
1127:
1128: public void setParameterService(ParameterService parameterService) {
1129: this.parameterService = parameterService;
1130: }
1131: }
|