001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.opensource.org/licenses/ecl1.php
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.kuali.module.gl.service.impl.scrubber;
017:
018: import java.util.ArrayList;
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Map;
023: import java.util.Set;
024: import java.util.Map.Entry;
025:
026: import org.apache.commons.lang.StringUtils;
027: import org.kuali.core.service.KualiConfigurationService;
028: import org.kuali.core.service.PersistenceService;
029: import org.kuali.module.gl.batch.collector.CollectorBatch;
030: import org.kuali.module.gl.bo.CollectorDetail;
031: import org.kuali.module.gl.bo.OriginEntry;
032: import org.kuali.module.gl.bo.OriginEntryFull;
033: import org.kuali.module.gl.bo.OriginEntryGroup;
034: import org.kuali.module.gl.service.OriginEntryGroupService;
035: import org.kuali.module.gl.service.OriginEntryService;
036: import org.kuali.module.gl.service.ScrubberService;
037: import org.kuali.module.gl.util.CollectorReportData;
038: import org.kuali.module.gl.util.CollectorScrubberStatus;
039: import org.kuali.module.gl.util.DocumentGroupData;
040: import org.kuali.module.gl.util.Message;
041: import org.kuali.module.gl.util.OriginEntryTotals;
042: import org.kuali.module.gl.util.ScrubberStatus;
043:
044: /**
045: * This class scrubs the billing details in a collector batch. Note that all services used by this class are passed in as parameters
046: * to the constructor. NOTE: IT IS IMPERATIVE that a new instance of this class is constructed and used to parse each batch. Sharing
047: * instances to scrub multiple batches may lead to unpredictable results.
048: */
049: public class CollectorScrubberProcess {
050: protected CollectorBatch batch;
051: protected OriginEntryGroup inputGroup;
052: protected OriginEntryGroup validGroup;
053: protected OriginEntryGroup errorGroup;
054: protected OriginEntryGroup expiredGroup;
055: protected OriginEntryService originEntryService;
056: protected OriginEntryGroupService originEntryGroupService;
057: protected KualiConfigurationService kualiConfigurationService;
058: protected PersistenceService persistenceService;
059: protected CollectorReportData collectorReportData;
060: protected ScrubberService scrubberService;
061:
062: protected Set<DocumentGroupData> errorDocumentGroups;
063:
064: /**
065: * Constructs a CollectorScrubberProcess.java.
066: *
067: * @param batch the batch to scrub
068: * @param inputGroup the origin entry group that holds all of the origin entries coming from the parsed input groups in the
069: * given batch
070: * @param validGroup the origin entry group that holds all of the origin entries coming that are in the origin entry scrubber
071: * valid group
072: * @param errorGroup the origin entry group that holds all of the origin entries coming that are in the origin entry scrubber
073: * error group
074: * @param expiredGroup are in the origin entry scrubber valid group that are in the origin entry scrubber expired group
075: * @param originEntryService the origin entry service that holds the origin entries in the batch (not necessarily the default
076: * implementation)
077: * @param originEntryGroupService the origin entry group service that holds the 3 group parameters (not necessarily the default
078: * implementation)
079: * @param kualiConfigurationService the config service
080: * @param persistenceService the persistence service
081: */
082: public CollectorScrubberProcess(CollectorBatch batch,
083: OriginEntryService originEntryService,
084: OriginEntryGroupService originEntryGroupService,
085: KualiConfigurationService kualiConfigurationService,
086: PersistenceService persistenceService,
087: ScrubberService scrubberService,
088: CollectorReportData collectorReportData) {
089: this .batch = batch;
090: this .originEntryService = originEntryService;
091: this .originEntryGroupService = originEntryGroupService;
092: this .kualiConfigurationService = kualiConfigurationService;
093: this .persistenceService = persistenceService;
094: this .collectorReportData = collectorReportData;
095: this .scrubberService = scrubberService;
096: }
097:
098: /**
099: * Scrubs the entries read in by the Collector
100: *
101: * @return a CollectorScrubberStatus object encapsulating the results of the scrubbing process
102: */
103: public CollectorScrubberStatus scrub() {
104: // for the collector origin entry group scrub, we make sure that we're using a custom impl of the origin entry service and
105: // group service.
106: ScrubberStatus scrubberStatus = scrubberService
107: .scrubCollectorBatch(batch, collectorReportData,
108: originEntryService, originEntryGroupService);
109: CollectorScrubberStatus collectorScrubberStatus = new CollectorScrubberStatus();
110:
111: // extract the group BOs form the scrubber
112:
113: // the group that contains all of the origin entries from the collector file
114: inputGroup = scrubberStatus.getInputGroup();
115: collectorScrubberStatus.setInputGroup(inputGroup);
116:
117: // the group that contains all of the origin entries from the scrubber valid group
118: validGroup = scrubberStatus.getValidGroup();
119: collectorScrubberStatus.setValidGroup(validGroup);
120:
121: // the group that contains all of the origin entries from the scrubber error group
122: errorGroup = scrubberStatus.getErrorGroup();
123: collectorScrubberStatus.setErrorGroup(errorGroup);
124:
125: // the group that contains all of the origin entries from the scrubber expired group (expired accounts)
126: expiredGroup = scrubberStatus.getExpiredGroup();
127: collectorScrubberStatus.setExpiredGroup(expiredGroup);
128:
129: collectorScrubberStatus
130: .setOriginEntryGroupService(originEntryGroupService);
131: collectorScrubberStatus
132: .setOriginEntryService(originEntryService);
133:
134: retrieveErrorDocumentGroups();
135:
136: retrieveTotalsOnInputOriginEntriesAssociatedWithErrorGroup();
137:
138: removeInterDepartmentalBillingAssociatedWithErrorGroup();
139:
140: applyChangesToDetailsFromScrubberEdits(scrubberStatus
141: .getUnscrubbedToScrubbedEntries());
142:
143: retainBatchValidEntriesOnly();
144:
145: return collectorScrubberStatus;
146: }
147:
148: /**
149: * Removes Collector IB details not associated with entries in the Collector data
150: */
151: protected void removeInterDepartmentalBillingNotAssociatedWithInputEntries() {
152: Iterator<CollectorDetail> detailIter = batch
153: .getCollectorDetails().iterator();
154: while (detailIter.hasNext()) {
155: CollectorDetail detail = detailIter.next();
156: for (OriginEntryFull inputEntry : batch.getOriginEntries()) {
157: if (!isOriginEntryRelatedToDetailRecord(inputEntry,
158: detail)) {
159: // TODO: add reporting data
160: detailIter.remove();
161: }
162: }
163: }
164: }
165:
166: /**
167: * This method's purpose is similar to the scrubber's demerger. This method scans through all of the origin entries and removes
168: * those billing details that share the same doc number, doc type, and origination code
169: */
170: protected void removeInterDepartmentalBillingAssociatedWithErrorGroup() {
171: int numDetailDeleted = 0;
172: Iterator<CollectorDetail> detailIter = batch
173: .getCollectorDetails().iterator();
174: while (detailIter.hasNext()) {
175: CollectorDetail detail = detailIter.next();
176: for (DocumentGroupData errorDocumentGroup : errorDocumentGroups) {
177: if (errorDocumentGroup.matchesCollectorDetail(detail)) {
178: numDetailDeleted++;
179: detailIter.remove();
180: }
181: }
182: }
183:
184: collectorReportData.setNumDetailDeleted(batch, new Integer(
185: numDetailDeleted));
186: }
187:
188: /**
189: * Determines if an origin entry is related to the given Collector detail record
190: *
191: * @param originEntry the origin entry to check
192: * @param detail the Collector detail to check against
193: * @return true if the origin entry is related, false otherwise
194: */
195: protected boolean isOriginEntryRelatedToDetailRecord(
196: OriginEntry originEntry, CollectorDetail detail) {
197: return StringUtils.equals(originEntry
198: .getUniversityFiscalPeriodCode(), detail
199: .getUniversityFiscalPeriodCode())
200: && originEntry.getUniversityFiscalYear() != null
201: && originEntry.getUniversityFiscalYear().equals(
202: detail.getUniversityFiscalYear())
203: && StringUtils.equals(originEntry
204: .getChartOfAccountsCode(), detail
205: .getChartOfAccountsCode())
206: && StringUtils.equals(originEntry.getAccountNumber(),
207: detail.getAccountNumber())
208: && StringUtils.equals(
209: originEntry.getSubAccountNumber(), detail
210: .getSubAccountNumber())
211: && StringUtils.equals(originEntry
212: .getFinancialObjectCode(), detail
213: .getFinancialObjectCode())
214: && StringUtils.equals(originEntry
215: .getFinancialSubObjectCode(), detail
216: .getFinancialSubObjectCode())
217: && StringUtils.equals(originEntry
218: .getFinancialSystemOriginationCode(), detail
219: .getFinancialSystemOriginationCode())
220: && StringUtils.equals(originEntry
221: .getFinancialDocumentTypeCode(), detail
222: .getFinancialDocumentTypeCode())
223: && StringUtils.equals(originEntry.getDocumentNumber(),
224: detail.getDocumentNumber())
225: && StringUtils.equals(originEntry
226: .getFinancialBalanceTypeCode(), detail
227: .getFinancialBalanceTypeCode())
228: && StringUtils.equals(originEntry
229: .getFinancialObjectTypeCode(), detail
230: .getFinancialObjectTypeCode());
231: }
232:
233: /**
234: * Determines if one of the messages in the given list of errors is a fatal message
235: *
236: * @param errors a List of errors generated by the scrubber
237: * @return true if one of the errors was fatal, false otherwise
238: */
239: private boolean hasFatal(List<Message> errors) {
240: for (Iterator<Message> iter = errors.iterator(); iter.hasNext();) {
241: Message element = iter.next();
242: if (element.getType() == Message.TYPE_FATAL) {
243: return true;
244: }
245: }
246: return false;
247: }
248:
249: /**
250: * Determines if any of the error messages in the given list are warnings
251: *
252: * @param errors a list of errors generated by the Scrubber
253: * @return true if there are any warnings in the list, false otherwise
254: */
255: private boolean hasWarning(List<Message> errors) {
256: for (Iterator<Message> iter = errors.iterator(); iter.hasNext();) {
257: Message element = iter.next();
258: if (element.getType() == Message.TYPE_WARNING) {
259: return true;
260: }
261: }
262: return false;
263: }
264:
265: /**
266: * Updates the Collector detail with the data from a scrubbed entry
267: *
268: * @param originEntry a scrubbed origin entry
269: * @param detail a Collector detail to update
270: */
271: protected void applyScrubberEditsToDetail(OriginEntry originEntry,
272: CollectorDetail detail) {
273: detail.setUniversityFiscalPeriodCode(originEntry
274: .getUniversityFiscalPeriodCode());
275: detail.setUniversityFiscalYear(originEntry
276: .getUniversityFiscalYear());
277: detail.setChartOfAccountsCode(originEntry
278: .getChartOfAccountsCode());
279: detail.setAccountNumber(originEntry.getAccountNumber());
280: detail.setSubAccountNumber(originEntry.getSubAccountNumber());
281: detail.setFinancialObjectCode(originEntry
282: .getFinancialObjectCode());
283: detail.setFinancialSubObjectCode(originEntry
284: .getFinancialSubObjectCode());
285: detail.setFinancialSystemOriginationCode(originEntry
286: .getFinancialSystemOriginationCode());
287: detail.setFinancialDocumentTypeCode(originEntry
288: .getFinancialDocumentTypeCode());
289: detail.setDocumentNumber(originEntry.getDocumentNumber());
290: detail.setFinancialBalanceTypeCode(originEntry
291: .getFinancialBalanceTypeCode());
292: detail.setFinancialObjectTypeCode(originEntry
293: .getFinancialObjectTypeCode());
294: }
295:
296: /**
297: * Updates all Collector details with the data from scrubbed origin entries
298: *
299: * @param unscrubbedToScrubbedEntries a Map relating original origin entries to scrubbed origin entries
300: */
301: protected void applyChangesToDetailsFromScrubberEdits(
302: Map<OriginEntry, OriginEntry> unscrubbedToScrubbedEntries) {
303: Set<Entry<OriginEntry, OriginEntry>> mappings = unscrubbedToScrubbedEntries
304: .entrySet();
305:
306: for (CollectorDetail detail : batch.getCollectorDetails()) {
307: int numDetailAccountValuesChanged = 0;
308: for (Entry<OriginEntry, OriginEntry> mapping : mappings) {
309: OriginEntry originalEntry = mapping.getKey();
310: OriginEntry scrubbedEntry = mapping.getValue();
311: if (isOriginEntryRelatedToDetailRecord(originalEntry,
312: detail)) {
313: if (!StringUtils.equals(originalEntry
314: .getChartOfAccountsCode(), scrubbedEntry
315: .getChartOfAccountsCode())
316: && !StringUtils.equals(originalEntry
317: .getAccountNumber(), scrubbedEntry
318: .getAccountNumber())) {
319: // TODO: determine whether the account was closed/expired before incrementing?
320: numDetailAccountValuesChanged++;
321: }
322: applyScrubberEditsToDetail(scrubbedEntry, detail);
323: break;
324: }
325: }
326: collectorReportData.setNumDetailAccountValuesChanged(batch,
327: numDetailAccountValuesChanged);
328: }
329: }
330:
331: /**
332: * Updates the origin entries read in by the collector are only the entries the scrubber declared valid
333: */
334: protected void retainBatchValidEntriesOnly() {
335: Iterator<OriginEntryFull> validEntriesIter = originEntryService
336: .getEntriesByGroup(validGroup);
337: List<OriginEntryFull> validEntries = new ArrayList<OriginEntryFull>();
338: while (validEntriesIter.hasNext()) {
339: OriginEntryFull entry = validEntriesIter.next();
340:
341: // clear out the entry ID, (which is the PK) because the origin entry service we're using for the scrubber
342: // may not assign a correct entry ID that should be used to persist in the DB, causing optimistic locking
343: // exceptions.
344: entry.setEntryId(null);
345: validEntries.add(entry);
346: }
347: batch.setOriginEntries(validEntries);
348: }
349:
350: /**
351: * Based on the origin entries in the origin entry scrubber-produced error group, creates a set of all {@link DocumentGroupData}s
352: * represented by those origin entries and initializes the {@link #errorDocumentGroups} variable
353: */
354: protected void retrieveErrorDocumentGroups() {
355: List<OriginEntryFull> errorEntries = originEntryService
356: .getEntriesByGroupId(errorGroup.getId());
357: errorDocumentGroups = DocumentGroupData
358: .getDocumentGroupDatasForTransactions(errorEntries
359: .iterator());
360: }
361:
362: /**
363: * Computes the totals of the input entries that were associated with the entries in the error group, which is created in the
364: * scrubber. For the purposes of this method, an input origin entry associated with the error group is (1) an origin entry that
365: * was parsed from the collector file (i.e. in the input group), and (2) these exists an origin entry in the error group such
366: * that the input origin entry and the error origin entry have equal doc number, doc type, and origination code. These totals
367: * are reflected in the collector report data object.
368: */
369: protected void retrieveTotalsOnInputOriginEntriesAssociatedWithErrorGroup() {
370: Map<DocumentGroupData, OriginEntryTotals> inputDocumentTotals = new HashMap<DocumentGroupData, OriginEntryTotals>();
371:
372: for (DocumentGroupData errorDocumentGroupData : errorDocumentGroups) {
373: Iterator<OriginEntryFull> associatedInputEntries = originEntryService
374: .getEntriesByDocument(
375: inputGroup,
376: errorDocumentGroupData.getDocumentNumber(),
377: errorDocumentGroupData
378: .getFinancialDocumentTypeCode(),
379: errorDocumentGroupData
380: .getFinancialSystemOriginationCode());
381: OriginEntryTotals originEntryTotals = new OriginEntryTotals();
382: originEntryTotals.addToTotals(associatedInputEntries);
383: inputDocumentTotals.put(errorDocumentGroupData,
384: originEntryTotals);
385: }
386:
387: collectorReportData
388: .setTotalsOnInputOriginEntriesAssociatedWithErrorGroup(
389: batch, inputDocumentTotals);
390: }
391: }
|