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;
17  
18  import java.io.File;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Map.Entry;
24  import java.util.Set;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.kuali.ole.gl.batch.service.impl.DocumentGroupData;
28  import org.kuali.ole.gl.batch.service.impl.OriginEntryFileIterator;
29  import org.kuali.ole.gl.batch.service.impl.OriginEntryTotals;
30  import org.kuali.ole.gl.businessobject.CollectorDetail;
31  import org.kuali.ole.gl.businessobject.OriginEntryFull;
32  import org.kuali.ole.gl.businessobject.OriginEntryInformation;
33  import org.kuali.ole.gl.report.CollectorReportData;
34  import org.kuali.ole.gl.service.ScrubberService;
35  import org.kuali.ole.gl.service.impl.CollectorScrubberStatus;
36  import org.kuali.ole.gl.service.impl.ScrubberStatus;
37  import org.kuali.ole.sys.Message;
38  import org.kuali.ole.sys.batch.BatchSpringContext;
39  import org.kuali.ole.sys.batch.Step;
40  import org.kuali.ole.sys.context.ProxyUtils;
41  import org.kuali.rice.core.api.config.property.ConfigurationService;
42  import org.kuali.rice.core.api.datetime.DateTimeService;
43  import org.kuali.rice.krad.service.PersistenceService;
44  
45  
46  /**
47   * This class scrubs the billing details in a collector batch. Note that all services used by this class are passed in as parameters
48   * to the constructor. NOTE: IT IS IMPERATIVE that a new instance of this class is constructed and used to parse each batch. Sharing
49   * instances to scrub multiple batches may lead to unpredictable results.
50   */
51  public class CollectorScrubberProcess {
52      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CollectorScrubberProcess.class);
53      
54      protected CollectorBatch batch;
55      protected String inputFileName;
56      protected String validFileName;
57      protected String errorFileName;
58      protected String expiredFileName;
59      protected ConfigurationService kualiConfigurationService;
60      protected PersistenceService persistenceService;
61      protected CollectorReportData collectorReportData;
62      protected ScrubberService scrubberService;
63      protected DateTimeService dateTimeService;
64  
65      protected Set<DocumentGroupData> errorDocumentGroups;
66      protected String collectorFileDirectoryName;
67      
68      /**
69       * Constructs a CollectorScrubberProcess.java.
70       * 
71       * @param batch the batch to scrub
72       * @param inputGroup the origin entry group that holds all of the origin entries coming from the parsed input groups in the
73       *        given batch
74       * @param validGroup the origin entry group that holds all of the origin entries coming that are in the origin entry scrubber
75       *        valid group
76       * @param errorGroup the origin entry group that holds all of the origin entries coming that are in the origin entry scrubber
77       *        error group
78       * @param expiredGroup are in the origin entry scrubber valid group that are in the origin entry scrubber expired group
79       * @param originEntryService the origin entry service that holds the origin entries in the batch (not necessarily the default
80       *        implementation)
81       * @param originEntryGroupService the origin entry group service that holds the 3 group parameters (not necessarily the default
82       *        implementation)
83       * @param kualiConfigurationService the config service
84       * @param persistenceService the persistence service
85       */
86      public CollectorScrubberProcess(CollectorBatch batch, ConfigurationService kualiConfigurationService, PersistenceService persistenceService, ScrubberService scrubberService, CollectorReportData collectorReportData, DateTimeService dateTimeService, String collectorFileDirectoryName) {
87          this.batch = batch;
88          this.kualiConfigurationService = kualiConfigurationService;
89          this.persistenceService = persistenceService;
90          this.collectorReportData = collectorReportData;
91          this.scrubberService = scrubberService;
92          this.dateTimeService = dateTimeService;
93          this.collectorFileDirectoryName = collectorFileDirectoryName;
94      }
95  
96      /**
97       * Scrubs the entries read in by the Collector
98       * 
99       * @return a CollectorScrubberStatus object encapsulating the results of the scrubbing process
100      */
101     public CollectorScrubberStatus scrub() {
102         // for the collector origin entry group scrub, we make sure that we're using a custom impl of the origin entry service and
103         // group service.
104         ScrubberStatus scrubberStatus = new ScrubberStatus();
105         Step step = BatchSpringContext.getStep(CollectorScrubberStep.STEP_NAME);
106         CollectorScrubberStep collectorStep = (CollectorScrubberStep) ProxyUtils.getTargetIfProxied(step);
107         collectorStep.setScrubberStatus(scrubberStatus);
108         collectorStep.setBatch(batch);
109         collectorStep.setCollectorReportData(collectorReportData);
110 
111         try {
112             step.execute(getClass().getName(), dateTimeService.getCurrentDate());
113         }
114         catch (Exception e) {
115             LOG.error("Exception occured executing step", e);
116             throw new RuntimeException("Exception occured executing step", e);
117         }
118         
119         CollectorScrubberStatus collectorScrubberStatus = new CollectorScrubberStatus();
120         // extract the group BOs form the scrubber
121 
122         // the FileName that contains all of the origin entries from the collector file
123         inputFileName = scrubberStatus.getInputFileName();
124         collectorScrubberStatus.setInputFileName(inputFileName);
125 
126         // the FileName that contains all of the origin entries from the scrubber valid FileName
127         validFileName = scrubberStatus.getValidFileName();
128         collectorScrubberStatus.setValidFileName(validFileName);
129 
130         // the FileName that contains all of the origin entries from the scrubber error FileName
131         errorFileName = scrubberStatus.getErrorFileName();
132         collectorScrubberStatus.setErrorFileName(errorFileName);
133 
134         // the FileName that contains all of the origin entries from the scrubber expired FileName (expired accounts)
135         expiredFileName = scrubberStatus.getExpiredFileName();
136         collectorScrubberStatus.setExpiredFileName(expiredFileName);
137 
138         retrieveErrorDocumentGroups();
139         
140         retrieveTotalsOnInputOriginEntriesAssociatedWithErrorGroup();
141         
142         removeInterDepartmentalBillingAssociatedWithErrorGroup();
143 
144         applyChangesToDetailsFromScrubberEdits(scrubberStatus.getUnscrubbedToScrubbedEntries());
145         
146         return collectorScrubberStatus;
147     }
148 
149     /**
150      * Removes Collector IB details not associated with entries in the Collector data
151      */
152     protected void removeInterDepartmentalBillingNotAssociatedWithInputEntries() {
153         Iterator<CollectorDetail> detailIter = batch.getCollectorDetails().iterator();
154         while (detailIter.hasNext()) {
155             CollectorDetail detail = detailIter.next();
156             for (OriginEntryFull inputEntry : batch.getOriginEntries()) {
157                 if (!isOriginEntryRelatedToDetailRecord(inputEntry, detail)) {
158                     // TODO: add reporting data
159                     detailIter.remove();
160                 }
161             }
162         }
163     }
164 
165     /**
166      * This method's purpose is similar to the scrubber's demerger. This method scans through all of the origin entries and removes
167      * those billing details that share the same doc number, doc type, and origination code
168      */
169     protected void removeInterDepartmentalBillingAssociatedWithErrorGroup() {
170         int numDetailDeleted = 0;
171         Iterator<CollectorDetail> detailIter = batch.getCollectorDetails().iterator();
172         while (detailIter.hasNext()) {
173             CollectorDetail detail = detailIter.next();
174             for (DocumentGroupData errorDocumentGroup : errorDocumentGroups) {
175                 if (errorDocumentGroup.matchesCollectorDetail(detail)) {
176                     numDetailDeleted++;
177                     detailIter.remove();
178                 }
179             }
180         }
181 
182         collectorReportData.setNumDetailDeleted(batch, new Integer(numDetailDeleted));
183     }
184 
185     /**
186      * Determines if an origin entry is related to the given Collector detail record
187      * 
188      * @param originEntry the origin entry to check
189      * @param detail the Collector detail to check against
190      * @return true if the origin entry is related, false otherwise
191      */
192     protected boolean isOriginEntryRelatedToDetailRecord(OriginEntryInformation originEntry, CollectorDetail detail) {
193         return StringUtils.equals(originEntry.getUniversityFiscalPeriodCode(), detail.getUniversityFiscalPeriodCode()) 
194                 && originEntry.getUniversityFiscalYear() != null && originEntry.getUniversityFiscalYear().equals(detail.getUniversityFiscalYear()) 
195                 && StringUtils.equals(originEntry.getChartOfAccountsCode(), detail.getChartOfAccountsCode()) 
196                 && StringUtils.equals(originEntry.getAccountNumber(), detail.getAccountNumber()) 
197                 && StringUtils.equals(originEntry.getSubAccountNumber(), detail.getSubAccountNumber()) 
198                 && StringUtils.equals(originEntry.getFinancialObjectCode(), detail.getFinancialObjectCode()) 
199                 && StringUtils.equals(originEntry.getFinancialSubObjectCode(), detail.getFinancialSubObjectCode()) 
200                 && StringUtils.equals(originEntry.getFinancialSystemOriginationCode(), detail.getFinancialSystemOriginationCode()) 
201                 && StringUtils.equals(originEntry.getFinancialDocumentTypeCode(), detail.getFinancialDocumentTypeCode())
202                 && StringUtils.equals(originEntry.getDocumentNumber(), detail.getDocumentNumber()) 
203                 && StringUtils.equals(originEntry.getFinancialObjectTypeCode(), detail.getFinancialObjectTypeCode());
204     }
205 
206     /**
207      * Determines if one of the messages in the given list of errors is a fatal message
208      * 
209      * @param errors a List of errors generated by the scrubber
210      * @return true if one of the errors was fatal, false otherwise
211      */
212     private boolean hasFatal(List<Message> errors) {
213         for (Iterator<Message> iter = errors.iterator(); iter.hasNext();) {
214             Message element = iter.next();
215             if (element.getType() == Message.TYPE_FATAL) {
216                 return true;
217             }
218         }
219         return false;
220     }
221 
222     /**
223      * Determines if any of the error messages in the given list are warnings
224      * 
225      * @param errors a list of errors generated by the Scrubber
226      * @return true if there are any warnings in the list, false otherwise
227      */
228     private boolean hasWarning(List<Message> errors) {
229         for (Iterator<Message> iter = errors.iterator(); iter.hasNext();) {
230             Message element = iter.next();
231             if (element.getType() == Message.TYPE_WARNING) {
232                 return true;
233             }
234         }
235         return false;
236     }
237 
238     /**
239      * Updates the Collector detail with the data from a scrubbed entry
240      * 
241      * @param originEntry a scrubbed origin entry
242      * @param detail a Collector detail to update
243      */
244     protected void applyScrubberEditsToDetail(OriginEntryInformation originEntry, CollectorDetail detail) {
245         detail.setUniversityFiscalPeriodCode(originEntry.getUniversityFiscalPeriodCode());
246         detail.setUniversityFiscalYear(originEntry.getUniversityFiscalYear());
247         detail.setChartOfAccountsCode(originEntry.getChartOfAccountsCode());
248         detail.setAccountNumber(originEntry.getAccountNumber());
249         detail.setSubAccountNumber(originEntry.getSubAccountNumber());
250         detail.setFinancialObjectCode(originEntry.getFinancialObjectCode());
251         detail.setFinancialSubObjectCode(originEntry.getFinancialSubObjectCode());
252         detail.setFinancialSystemOriginationCode(originEntry.getFinancialSystemOriginationCode());
253         detail.setFinancialDocumentTypeCode(originEntry.getFinancialDocumentTypeCode());
254         detail.setDocumentNumber(originEntry.getDocumentNumber());
255         detail.setFinancialBalanceTypeCode(originEntry.getFinancialBalanceTypeCode());
256         detail.setFinancialObjectTypeCode(originEntry.getFinancialObjectTypeCode());
257     }
258 
259     /**
260      * Updates all Collector details with the data from scrubbed origin entries
261      * 
262      * @param unscrubbedToScrubbedEntries a Map relating original origin entries to scrubbed origin entries
263      */
264     protected void applyChangesToDetailsFromScrubberEdits(Map<OriginEntryInformation, OriginEntryInformation> unscrubbedToScrubbedEntries) {
265         Set<Entry<OriginEntryInformation, OriginEntryInformation>> mappings = unscrubbedToScrubbedEntries.entrySet();
266 
267         int numDetailAccountValuesChanged = 0;
268         for (CollectorDetail detail : batch.getCollectorDetails()) {
269             for (Entry<OriginEntryInformation, OriginEntryInformation> mapping : mappings) {
270                 OriginEntryInformation originalEntry = mapping.getKey();
271                 OriginEntryInformation scrubbedEntry = mapping.getValue();
272                 // TODO: this algorithm could be made faster using a lookup table instead of a nested loop
273                 if (isOriginEntryRelatedToDetailRecord(originalEntry, detail)) {
274                     if (!StringUtils.equals(originalEntry.getChartOfAccountsCode(), scrubbedEntry.getChartOfAccountsCode()) || !StringUtils.equals(originalEntry.getAccountNumber(), scrubbedEntry.getAccountNumber())) {
275                         numDetailAccountValuesChanged++;
276                     }
277                     applyScrubberEditsToDetail(scrubbedEntry, detail);
278                     break;
279                 }
280             }
281         }
282         
283         collectorReportData.setNumDetailAccountValuesChanged(batch, numDetailAccountValuesChanged);
284     }
285 
286     /**
287      * Based on the origin entries in the origin entry scrubber-produced error group, creates a set of all {@link DocumentGroupData}s
288      * represented by those origin entries and initializes the {@link #errorDocumentGroups} variable
289      */
290     protected void retrieveErrorDocumentGroups() {
291         File errorFile = new File( collectorFileDirectoryName + File.separator + errorFileName);
292         OriginEntryFileIterator entryIterator = new OriginEntryFileIterator(errorFile);
293         errorDocumentGroups = DocumentGroupData.getDocumentGroupDatasForTransactions(entryIterator);
294     }
295 
296     /**
297      * Computes the totals of the input entries that were associated with the entries in the error group, which is created in the
298      * scrubber. These totals are reflected in the collector report data object.
299      */
300     protected void retrieveTotalsOnInputOriginEntriesAssociatedWithErrorGroup() {
301         Map<DocumentGroupData, OriginEntryTotals> inputDocumentTotals = new HashMap<DocumentGroupData, OriginEntryTotals>();
302 
303         File inputFile = new File(collectorFileDirectoryName + File.separator + inputFileName);
304         OriginEntryFileIterator inputIterator = new OriginEntryFileIterator(inputFile);
305 
306         while (inputIterator.hasNext()) {
307             OriginEntryFull originEntryFull = (OriginEntryFull) inputIterator.next();
308 
309             DocumentGroupData inputGroupData = new DocumentGroupData(originEntryFull.getDocumentNumber(), originEntryFull.getFinancialDocumentTypeCode(), originEntryFull.getFinancialSystemOriginationCode());
310             if (errorDocumentGroups.contains(inputGroupData)) {
311                 OriginEntryTotals originEntryTotals = new OriginEntryTotals();
312                 if (inputDocumentTotals.containsKey(inputGroupData)) {
313                     originEntryTotals = inputDocumentTotals.get(inputGroupData);
314                 }
315 
316                 originEntryTotals.addToTotals(originEntryFull);
317                 inputDocumentTotals.put(inputGroupData, originEntryTotals);
318             }
319         }
320 
321         collectorReportData.setTotalsOnInputOriginEntriesAssociatedWithErrorGroup(batch, inputDocumentTotals);
322     }
323 }