001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.gl.batch.service.impl;
017    
018    import java.io.BufferedReader;
019    import java.io.File;
020    import java.io.FileNotFoundException;
021    import java.io.FileReader;
022    import java.io.IOException;
023    import java.io.PrintStream;
024    import java.sql.Date;
025    import java.text.NumberFormat;
026    import java.util.ArrayList;
027    import java.util.Calendar;
028    import java.util.HashMap;
029    import java.util.IdentityHashMap;
030    import java.util.Iterator;
031    import java.util.List;
032    import java.util.Map;
033    
034    import org.apache.commons.io.FileUtils;
035    import org.apache.commons.io.LineIterator;
036    import org.kuali.kfs.coa.businessobject.A21SubAccount;
037    import org.kuali.kfs.coa.businessobject.Account;
038    import org.kuali.kfs.coa.businessobject.BalanceType;
039    import org.kuali.kfs.coa.businessobject.Chart;
040    import org.kuali.kfs.coa.businessobject.ObjectCode;
041    import org.kuali.kfs.coa.businessobject.OffsetDefinition;
042    import org.kuali.kfs.gl.GeneralLedgerConstants;
043    import org.kuali.kfs.gl.ObjectHelper;
044    import org.kuali.kfs.gl.batch.BatchSortUtil;
045    import org.kuali.kfs.gl.batch.CollectorBatch;
046    import org.kuali.kfs.gl.batch.DemergerSortComparator;
047    import org.kuali.kfs.gl.batch.ScrubberSortComparator;
048    import org.kuali.kfs.gl.batch.ScrubberStep;
049    import org.kuali.kfs.gl.batch.service.AccountingCycleCachingService;
050    import org.kuali.kfs.gl.batch.service.RunDateService;
051    import org.kuali.kfs.gl.batch.service.ScrubberProcess;
052    import org.kuali.kfs.gl.batch.service.impl.FilteringOriginEntryFileIterator.OriginEntryFilter;
053    import org.kuali.kfs.gl.businessobject.DemergerReportData;
054    import org.kuali.kfs.gl.businessobject.OriginEntryFieldUtil;
055    import org.kuali.kfs.gl.businessobject.OriginEntryFull;
056    import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
057    import org.kuali.kfs.gl.businessobject.Transaction;
058    import org.kuali.kfs.gl.report.CollectorReportData;
059    import org.kuali.kfs.gl.report.LedgerSummaryReport;
060    import org.kuali.kfs.gl.report.PreScrubberReport;
061    import org.kuali.kfs.gl.report.PreScrubberReportData;
062    import org.kuali.kfs.gl.report.TransactionListingReport;
063    import org.kuali.kfs.gl.service.PreScrubberService;
064    import org.kuali.kfs.gl.service.ScrubberReportData;
065    import org.kuali.kfs.gl.service.ScrubberValidator;
066    import org.kuali.kfs.gl.service.impl.ScrubberStatus;
067    import org.kuali.kfs.gl.service.impl.StringHelper;
068    import org.kuali.kfs.sys.KFSConstants;
069    import org.kuali.kfs.sys.KFSKeyConstants;
070    import org.kuali.kfs.sys.KFSPropertyConstants;
071    import org.kuali.kfs.sys.Message;
072    import org.kuali.kfs.sys.KFSParameterKeyConstants.GlParameterConstants;
073    import org.kuali.kfs.sys.batch.service.WrappingBatchService;
074    import org.kuali.kfs.sys.businessobject.SystemOptions;
075    import org.kuali.kfs.sys.businessobject.UniversityDate;
076    import org.kuali.kfs.sys.exception.InvalidFlexibleOffsetException;
077    import org.kuali.kfs.sys.service.DocumentNumberAwareReportWriterService;
078    import org.kuali.kfs.sys.service.FlexibleOffsetAccountService;
079    import org.kuali.kfs.sys.service.ReportWriterService;
080    import org.kuali.rice.kns.service.BusinessObjectService;
081    import org.kuali.rice.kns.service.DateTimeService;
082    import org.kuali.rice.kns.service.KualiConfigurationService;
083    import org.kuali.rice.kns.service.ParameterEvaluator;
084    import org.kuali.rice.kns.service.ParameterService;
085    import org.kuali.rice.kns.service.PersistenceService;
086    import org.kuali.rice.kns.util.KualiDecimal;
087    import org.kuali.rice.kns.util.ObjectUtils;
088    import org.springframework.util.StringUtils;
089    
090    /**
091     * This class has the logic for the scrubber. It is required because the scrubber process needs instance variables. Instance
092     * variables in a spring service are shared between all code calling the service. This will make sure each run of the scrubber has
093     * it's own instance variables instead of being shared.
094     */
095    public class ScrubberProcessImpl implements ScrubberProcess {
096        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ScrubberProcessImpl.class);
097        
098        private static final String TRANSACTION_TYPE_COST_SHARE_ENCUMBRANCE = "CE";
099        private static final String TRANSACTION_TYPE_OFFSET = "O";
100        private static final String TRANSACTION_TYPE_CAPITALIZATION = "C";
101        private static final String TRANSACTION_TYPE_LIABILITY = "L";
102        private static final String TRANSACTION_TYPE_TRANSFER = "T";
103        private static final String TRANSACTION_TYPE_COST_SHARE = "CS";
104        private static final String TRANSACTION_TYPE_OTHER = "X";
105    
106        enum GROUP_TYPE {VALID, ERROR, EXPIRED}
107        
108        private static final String COST_SHARE_CODE = "CSHR";
109    
110        private static final String COST_SHARE_TRANSFER_ENTRY_IND = "***";
111    
112        // These lengths are different then database field lengths, hence they are not from the DD
113        private static final int COST_SHARE_ENCUMBRANCE_ENTRY_MAXLENGTH = 28;
114        private static final int DEMERGER_TRANSACTION_LEDGET_ENTRY_DESCRIPTION = 33;
115        private static final int OFFSET_MESSAGE_MAXLENGTH = 33;
116    
117        /* Services required */
118        private FlexibleOffsetAccountService flexibleOffsetAccountService;
119        private DateTimeService dateTimeService;
120        private KualiConfigurationService configurationService;
121        private PersistenceService persistenceService;
122        private ScrubberValidator scrubberValidator;
123        private RunDateService runDateService;
124        private AccountingCycleCachingService accountingCycleCachingService;
125        private DocumentNumberAwareReportWriterService scrubberReportWriterService;
126        private DocumentNumberAwareReportWriterService scrubberLedgerReportWriterService;
127        private DocumentNumberAwareReportWriterService scrubberListingReportWriterService;
128        protected DocumentNumberAwareReportWriterService preScrubberReportWriterService;
129        private ReportWriterService scrubberBadBalanceListingReportWriterService;
130        private ReportWriterService demergerRemovedTransactionsListingReportWriterService;
131        private ReportWriterService demergerReportWriterService;
132        private PreScrubberService preScrubberService;
133        
134        // these three members will only be populated when in collector mode, otherwise the memory requirements will be huge
135        private Map<OriginEntryInformation, OriginEntryInformation> unscrubbedToScrubbedEntries = new HashMap<OriginEntryInformation, OriginEntryInformation>();
136        private Map<Transaction, List<Message>> scrubberReportErrors = new IdentityHashMap<Transaction, List<Message>>();
137        private LedgerSummaryReport ledgerSummaryReport = new LedgerSummaryReport();
138        
139        private ScrubberReportData scrubberReport;
140        private DemergerReportData demergerReport;
141        
142        /* These are all different forms of the run date for this job */
143        private Date runDate;
144        private Calendar runCal;
145        private UniversityDate universityRunDate;
146        private String offsetString;
147    
148        /* Unit Of Work info */
149        private UnitOfWorkInfo unitOfWork;
150        private KualiDecimal scrubCostShareAmount;
151    
152        /* Statistics for the reports */
153        private List<Message> transactionErrors;
154    
155        /* Description names */
156        private String offsetDescription;
157        private String capitalizationDescription;
158        private String liabilityDescription;
159        private String transferDescription;
160        private String costShareDescription;
161    
162        private ParameterService parameterService;
163        private BusinessObjectService businessObjectService;
164        
165        /**
166         * Whether this instance is being used to support the scrubbing of a collector batch
167         */
168        private boolean collectorMode;
169        private String batchFileDirectoryName;
170        
171        PrintStream OUTPUT_GLE_FILE_ps;
172        PrintStream OUTPUT_ERR_FILE_ps;
173        PrintStream OUTPUT_EXP_FILE_ps;
174    
175        private String inputFile;
176        private String validFile;
177        private String errorFile;
178        private String expiredFile;
179        
180        /**
181         * Scrub this single group read only. This will only output the scrubber report. It won't output any other groups.
182         * 
183         * @param group the origin entry group that should be scrubbed
184         * @param the document number of any specific entries to scrub
185         */
186        public void scrubGroupReportOnly(String fileName, String documentNumber) {
187            LOG.debug("scrubGroupReportOnly() started");
188            String unsortedFile = fileName;
189            this.inputFile = fileName + ".sort";
190            this.validFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.SCRUBBER_VALID_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
191            this.errorFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.SCRUBBER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
192            this.expiredFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.SCRUBBER_EXPIRED_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
193            String prescrubOutput = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.PRE_SCRUBBER_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
194            this.ledgerSummaryReport = new LedgerSummaryReport();
195            runDate = calculateRunDate(dateTimeService.getCurrentDate());
196            
197            PreScrubberReportData preScrubberReportData = null;
198            
199            // run pre-scrubber on the raw input into the sort process
200            LineIterator inputEntries = null;
201            try {
202                inputEntries = FileUtils.lineIterator(new File(unsortedFile));
203                preScrubberReportData = preScrubberService.preprocessOriginEntries(inputEntries, prescrubOutput);
204            }
205            catch (IOException e1) {
206                LOG.error("Error encountered trying to prescrub GLCP/LLCP document", e1);
207                throw new RuntimeException("Error encountered trying to prescrub GLCP/LLCP document", e1);
208            }
209            finally {
210                LineIterator.closeQuietly(inputEntries);
211            }
212            if (preScrubberReportData != null) {
213                preScrubberReportWriterService.setDocumentNumber(documentNumber);
214                ((WrappingBatchService)preScrubberReportWriterService).initialize();
215                try {
216                    new PreScrubberReport().generateReport(preScrubberReportData, preScrubberReportWriterService);
217                }
218                finally {
219                    ((WrappingBatchService)preScrubberReportWriterService).destroy();
220                }
221            }
222            BatchSortUtil.sortTextFileWithFields(prescrubOutput, inputFile, new ScrubberSortComparator());
223            
224            scrubEntries(true, documentNumber);
225            
226            // delete files
227            File deleteSortFile = new File(inputFile);
228            File deleteValidFile = new File(validFile);
229            File deleteErrorFile = new File(errorFile);
230            File deleteExpiredFile = new File(expiredFile);
231            try {
232                deleteSortFile.delete();
233                deleteValidFile.delete();
234                deleteErrorFile.delete();
235                deleteExpiredFile.delete();
236            } catch (Exception e){
237                LOG.error("scrubGroupReportOnly delete output files process Stopped: " + e.getMessage());
238                throw new RuntimeException("scrubGroupReportOnly delete output files process Stopped: " + e.getMessage(), e);
239            }
240        }
241    
242        /**
243         * Scrubs all entries in all groups and documents.
244         */
245        public void scrubEntries() {
246            this.inputFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.SCRUBBER_INPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
247            this.validFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.SCRUBBER_VALID_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
248            this.errorFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.SCRUBBER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
249            this.expiredFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.SCRUBBER_EXPIRED_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
250            runDate = calculateRunDate(dateTimeService.getCurrentDate());
251    
252            scrubEntries(false, null);
253        }
254    
255        /**
256         * Scrubs the origin entry and ID billing details if the given batch. Store all scrubber output into the collectorReportData
257         * parameter. NOTE: DO NOT CALL ANY OF THE scrub* METHODS OF THIS CLASS AFTER CALLING THIS METHOD FOR EVERY UNIQUE INSTANCE OF
258         * THIS CLASS, OR THE COLLECTOR REPORTS MAY BE CORRUPTED
259         * 
260         * @param batch the data gathered from a Collector file
261         * @param collectorReportData the statistics generated by running the Collector
262         */
263        public void scrubCollectorBatch(ScrubberStatus scrubberStatus, CollectorBatch batch, CollectorReportData collectorReportData) {
264            collectorMode = true;
265    
266            this.inputFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_SCRUBBER_INPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
267            this.validFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_SCRUBBER_VALID_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
268            this.errorFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_SCRUBBER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
269            this.expiredFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_SCRUBBER_EXPIRED_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
270            runDate = calculateRunDate(dateTimeService.getCurrentDate());
271            
272            this.ledgerSummaryReport = collectorReportData.getLedgerSummaryReport();
273            
274            // sort input file
275            String scrubberSortInputFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_BACKUP_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
276            String scrubberSortOutputFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_SCRUBBER_INPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
277            BatchSortUtil.sortTextFileWithFields(scrubberSortInputFile, scrubberSortOutputFile, new ScrubberSortComparator());
278            
279            scrubEntries(false, null);
280            
281            //sort scrubber error file for demerger
282            String demergerSortInputFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_SCRUBBER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION; 
283            String demergerSortOutputFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_SCRUBBER_ERROR_SORTED_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION; 
284            BatchSortUtil.sortTextFileWithFields(demergerSortInputFile, demergerSortOutputFile, new DemergerSortComparator());
285            
286            performDemerger();
287            
288            // the scrubber process has just updated several member variables of this class. Store these values for the collector report
289            collectorReportData.setBatchOriginEntryScrubberErrors(batch, scrubberReportErrors);
290            collectorReportData.setScrubberReportData(batch, scrubberReport);
291            collectorReportData.setDemergerReportData(batch, demergerReport);
292    
293            // report purpose - commented out.  If we need, the put string values for fileNames.
294            scrubberStatus.setInputFileName(GeneralLedgerConstants.BatchFileSystem.COLLECTOR_SCRUBBER_INPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
295            scrubberStatus.setValidFileName(GeneralLedgerConstants.BatchFileSystem.COLLECTOR_DEMERGER_VAILD_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
296            scrubberStatus.setErrorFileName(GeneralLedgerConstants.BatchFileSystem.COLLECTOR_DEMERGER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
297            scrubberStatus.setExpiredFileName(GeneralLedgerConstants.BatchFileSystem.COLLECTOR_SCRUBBER_EXPIRED_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION);
298            scrubberStatus.setUnscrubbedToScrubbedEntries(unscrubbedToScrubbedEntries);
299        }
300    
301        /**
302         * Scrub all entries that need it in origin entry. Put valid scrubbed entries in a scrubber valid group, put errors in a
303         * scrubber error group, and transactions with an expired account in the scrubber expired account group.
304         * @param group the specific origin entry group to scrub
305         * @param documentNumber the number of the document with entries to scrub
306         */
307        public void scrubEntries(boolean reportOnlyMode, String documentNumber) {
308            LOG.debug("scrubEntries() started");
309    
310            if (reportOnlyMode) {
311                scrubberReportWriterService.setDocumentNumber(documentNumber);
312                scrubberLedgerReportWriterService.setDocumentNumber(documentNumber);
313            }
314            
315            // setup an object to hold the "default" date information
316            runDate = calculateRunDate(dateTimeService.getCurrentDate());
317            runCal = Calendar.getInstance();
318            runCal.setTime(runDate);
319    
320            universityRunDate = accountingCycleCachingService.getUniversityDate(runDate);
321            if (universityRunDate == null) {
322                throw new IllegalStateException(configurationService.getPropertyString(KFSKeyConstants.ERROR_UNIV_DATE_NOT_FOUND));
323            }
324            
325            setOffsetString();
326            setDescriptions();
327            scrubberReport = new ScrubberReportData();
328            
329            try {
330                if (!collectorMode) {
331                    ((WrappingBatchService) scrubberReportWriterService).initialize();
332                    ((WrappingBatchService) scrubberLedgerReportWriterService).initialize();
333                }
334    
335                processGroup(reportOnlyMode, scrubberReport);
336    
337                if (reportOnlyMode) {
338                    generateScrubberTransactionListingReport(documentNumber, inputFile);
339                }
340                else if (collectorMode) {
341                    // defer report generation for later
342                }
343                else {
344                    generateScrubberBlankBalanceTypeCodeReport(inputFile);
345                }
346            }
347            finally {
348                if (!collectorMode) {
349                    ((WrappingBatchService) scrubberReportWriterService).destroy();
350                    ((WrappingBatchService) scrubberLedgerReportWriterService).destroy();
351                }
352            }
353        }
354    
355        /**
356         * The demerger process reads all of the documents in the error group, then moves all of the original entries for that document
357         * from the valid group to the error group. It does not move generated entries to the error group. Those are deleted. It also
358         * modifies the doc number and origin code of cost share transfers.
359         * 
360         * @param errorGroup this scrubber run's error group
361         * @param validGroup this scrubber run's valid group
362         */
363        public void performDemerger() {
364            LOG.debug("performDemerger() started");
365    
366            OriginEntryFieldUtil oefu = new OriginEntryFieldUtil();
367            Map<String, Integer> pMap = oefu.getFieldBeginningPositionMap();
368            
369            // Without this step, the job fails with Optimistic Lock Exceptions
370            persistenceService.clearCache();
371    
372            demergerReport = new DemergerReportData();
373            
374            // set runDate here again, because demerger is calling outside from scrubber
375            runDate = calculateRunDate(dateTimeService.getCurrentDate());
376            runCal = Calendar.getInstance();
377            runCal.setTime(runDate);
378            
379            // demerger called by outside from scrubber, so reset those values
380            setOffsetString();
381            setDescriptions();
382            
383            // new demerger starts
384    
385            String validOutputFilename = null; 
386            String errorOutputFilename = null;
387    
388            String demergerValidOutputFilename = null; 
389            String demergerErrorOutputFilename = null;
390          
391            if(!collectorMode){
392                validOutputFilename = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.SCRUBBER_VALID_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION; 
393                errorOutputFilename = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.SCRUBBER_ERROR_SORTED_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
394    
395                demergerValidOutputFilename = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.DEMERGER_VAILD_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION; 
396                demergerErrorOutputFilename = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.DEMERGER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
397    
398            } else {
399    
400                validOutputFilename = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_SCRUBBER_VALID_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION; 
401                errorOutputFilename = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_SCRUBBER_ERROR_SORTED_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
402    
403                demergerValidOutputFilename = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_DEMERGER_VAILD_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION; 
404                demergerErrorOutputFilename = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.COLLECTOR_DEMERGER_ERROR_OUTPUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION;
405            }
406            
407            // Without this step, the job fails with Optimistic Lock Exceptions
408            persistenceService.clearCache();
409            
410            FileReader INPUT_GLE_FILE = null;
411            FileReader INPUT_ERR_FILE = null;
412            BufferedReader INPUT_GLE_FILE_br;
413            BufferedReader INPUT_ERR_FILE_br;
414            PrintStream OUTPUT_DEMERGER_GLE_FILE_ps;
415            PrintStream OUTPUT_DEMERGER_ERR_FILE_ps;     
416            
417            try {
418                INPUT_GLE_FILE = new FileReader(validOutputFilename);
419                INPUT_ERR_FILE = new FileReader(errorOutputFilename);
420            }
421            catch (FileNotFoundException e) {
422                throw new RuntimeException(e);
423            }
424            try {
425                OUTPUT_DEMERGER_GLE_FILE_ps = new PrintStream(demergerValidOutputFilename);
426                OUTPUT_DEMERGER_ERR_FILE_ps = new PrintStream(demergerErrorOutputFilename);
427            }
428            catch (IOException e) {
429                throw new RuntimeException(e);
430            }
431    
432            int validSaved = 0;
433            int errorSaved = 0;
434            
435            int validReadLine = 0;
436            int errorReadLine = 0;
437            
438            boolean errorsLoading = false;
439            INPUT_GLE_FILE_br = new BufferedReader(INPUT_GLE_FILE);
440            INPUT_ERR_FILE_br = new BufferedReader(INPUT_ERR_FILE);
441            
442            try {
443                String currentValidLine = INPUT_GLE_FILE_br.readLine();
444                String currentErrorLine = INPUT_ERR_FILE_br.readLine();
445    
446                boolean meetFlag = false;
447                
448                while (currentValidLine != null || currentErrorLine != null) {
449                   
450                    // Demerger only catch IOexception since demerger report doesn't display
451                    // detail error message.
452                    try{
453                       //validLine is null means that errorLine is not null 
454                       if (org.apache.commons.lang.StringUtils.isEmpty(currentValidLine)) {
455                           String errorDesc = currentErrorLine.substring(pMap.get(KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_DESC), pMap.get(KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_AMOUNT));
456                           String errorFinancialBalanceTypeCode = currentErrorLine.substring(pMap.get(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE), pMap.get(KFSPropertyConstants.FINANCIAL_OBJECT_TYPE_CODE));
457                           
458                           if (!checkingBypassEntry(errorFinancialBalanceTypeCode, errorDesc, demergerReport)){
459                               createOutputEntry(currentErrorLine, OUTPUT_DEMERGER_ERR_FILE_ps);
460                               errorSaved++;    
461                           }
462                           currentErrorLine = INPUT_ERR_FILE_br.readLine();
463                           errorReadLine++;
464                           continue;
465                       }
466                       
467                       String financialBalanceTypeCode = currentValidLine.substring(pMap.get(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE), pMap.get(KFSPropertyConstants.FINANCIAL_OBJECT_TYPE_CODE));
468                       String desc = currentValidLine.substring(pMap.get(KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_DESC), pMap.get(KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_AMOUNT));
469                       
470                       //errorLine is null means that validLine is not null
471                       if (org.apache.commons.lang.StringUtils.isEmpty(currentErrorLine)) {
472                           // Read all the transactions in the valid group and update the cost share transactions
473                           String updatedValidLine = checkAndSetTransactionTypeCostShare(financialBalanceTypeCode, desc, currentValidLine);
474                           createOutputEntry(updatedValidLine, OUTPUT_DEMERGER_GLE_FILE_ps);
475                           handleDemergerSaveValidEntry(updatedValidLine);
476                           validSaved++;
477                           currentValidLine = INPUT_GLE_FILE_br.readLine();
478                           validReadLine++;
479                           continue;
480                       }
481                       
482                       String compareStringFromValidEntry = currentValidLine.substring(pMap.get(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE), pMap.get(KFSPropertyConstants.TRANSACTION_ENTRY_SEQUENCE_NUMBER)); 
483                       String compareStringFromErrorEntry = currentErrorLine.substring(pMap.get(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE), pMap.get(KFSPropertyConstants.TRANSACTION_ENTRY_SEQUENCE_NUMBER));
484                       
485                       String errorDesc = currentErrorLine.substring(pMap.get(KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_DESC), pMap.get(KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_AMOUNT));
486                       String errorFinancialBalanceTypeCode = currentErrorLine.substring(pMap.get(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE), pMap.get(KFSPropertyConstants.FINANCIAL_OBJECT_TYPE_CODE));
487                       
488                       if (compareStringFromValidEntry.compareTo(compareStringFromErrorEntry) < 0){
489                           // Read all the transactions in the valid group and update the cost share transactions
490                           String updatedValidLine = checkAndSetTransactionTypeCostShare(financialBalanceTypeCode, desc, currentValidLine);
491                           createOutputEntry(updatedValidLine, OUTPUT_DEMERGER_GLE_FILE_ps);
492                           handleDemergerSaveValidEntry(updatedValidLine);
493                           validSaved++;
494                           currentValidLine = INPUT_GLE_FILE_br.readLine();
495                           validReadLine++;
496           
497                       } else if (compareStringFromValidEntry.compareTo(compareStringFromErrorEntry) > 0) {
498                           if (!checkingBypassEntry(errorFinancialBalanceTypeCode, errorDesc, demergerReport)){
499                               createOutputEntry(currentErrorLine, OUTPUT_DEMERGER_ERR_FILE_ps);
500                               errorSaved++;
501                           }
502                           currentErrorLine = INPUT_ERR_FILE_br.readLine();
503                           errorReadLine++;
504    
505                       } else {
506                           if (!checkingBypassEntry(financialBalanceTypeCode, desc, demergerReport)){
507                               createOutputEntry(currentValidLine, OUTPUT_DEMERGER_ERR_FILE_ps);
508                               errorSaved++;
509                           }
510                           currentValidLine = INPUT_GLE_FILE_br.readLine();
511                           validReadLine++;
512                       }
513                       
514                       continue;
515                       
516                   } catch (RuntimeException re) {
517                       LOG.error("performDemerger Stopped: " + re.getMessage());
518                       throw new RuntimeException("performDemerger Stopped: " + re.getMessage(), re);
519                   }
520                }
521                INPUT_GLE_FILE_br.close();    
522                INPUT_ERR_FILE_br.close();
523                OUTPUT_DEMERGER_GLE_FILE_ps.close();
524                OUTPUT_DEMERGER_ERR_FILE_ps.close();
525                
526            } catch (IOException e) {
527                LOG.error("performDemerger Stopped: " + e.getMessage());
528                throw new RuntimeException("performDemerger Stopped: " + e.getMessage(), e);
529            }
530            demergerReport.setErrorTransactionWritten(errorSaved);
531            demergerReport.setErrorTransactionsRead(errorReadLine);
532            demergerReport.setValidTransactionsRead(validReadLine);
533            demergerReport.setValidTransactionsSaved(validSaved);
534            
535            if (!collectorMode) {
536                demergerReportWriterService.writeStatisticLine("SCRUBBER ERROR TRANSACTIONS READ       %,9d", demergerReport.getErrorTransactionsRead());
537                demergerReportWriterService.writeStatisticLine("SCRUBBER VALID TRANSACTIONS READ       %,9d", demergerReport.getValidTransactionsRead());
538                demergerReportWriterService.writeNewLines(1);
539                demergerReportWriterService.writeStatisticLine("DEMERGER ERRORS SAVED                  %,9d", demergerReport.getErrorTransactionsSaved());
540                demergerReportWriterService.writeStatisticLine("DEMERGER VALID TRANSACTIONS SAVED      %,9d", demergerReport.getValidTransactionsSaved());
541                demergerReportWriterService.writeStatisticLine("OFFSET TRANSACTIONS BYPASSED           %,9d", demergerReport.getOffsetTransactionsBypassed());
542                demergerReportWriterService.writeStatisticLine("CAPITALIZATION TRANSACTIONS BYPASSED   %,9d", demergerReport.getCapitalizationTransactionsBypassed());
543                demergerReportWriterService.writeStatisticLine("LIABILITY TRANSACTIONS BYPASSED        %,9d", demergerReport.getLiabilityTransactionsBypassed());
544                demergerReportWriterService.writeStatisticLine("TRANSFER TRANSACTIONS BYPASSED         %,9d", demergerReport.getTransferTransactionsBypassed());
545                demergerReportWriterService.writeStatisticLine("COST SHARE TRANSACTIONS BYPASSED       %,9d", demergerReport.getCostShareTransactionsBypassed());
546                demergerReportWriterService.writeStatisticLine("COST SHARE ENC TRANSACTIONS BYPASSED   %,9d", demergerReport.getCostShareEncumbranceTransactionsBypassed());
547                
548                generateDemergerRemovedTransactionsReport(demergerErrorOutputFilename);
549            }
550        }
551    
552        /**
553         * Determine the type of the transaction by looking at attributes
554         * 
555         * @param transaction Transaction to identify
556         * @return CE (Cost share encumbrance, O (Offset), C (apitalization), L (Liability), T (Transfer), CS (Cost Share), X (Other)
557         */
558        protected String getTransactionType(OriginEntryInformation transaction) {
559            if (TRANSACTION_TYPE_COST_SHARE_ENCUMBRANCE.equals(transaction.getFinancialBalanceTypeCode())) {
560                return TRANSACTION_TYPE_COST_SHARE_ENCUMBRANCE;
561            }
562            String desc = transaction.getTransactionLedgerEntryDescription();
563    
564            if (desc == null) {
565                return TRANSACTION_TYPE_OTHER;
566            }
567            if (desc.startsWith(offsetDescription) && desc.indexOf(COST_SHARE_TRANSFER_ENTRY_IND) > -1) {
568                return TRANSACTION_TYPE_COST_SHARE;
569            }
570            if (desc.startsWith(costShareDescription) && desc.indexOf(COST_SHARE_TRANSFER_ENTRY_IND) > -1) {
571                return TRANSACTION_TYPE_COST_SHARE;
572            }
573            if (desc.startsWith(offsetDescription)) {
574                return TRANSACTION_TYPE_OFFSET;
575            }
576            if (desc.startsWith(capitalizationDescription)) {
577                return TRANSACTION_TYPE_CAPITALIZATION;
578            }
579            if (desc.startsWith(liabilityDescription)) {
580                return TRANSACTION_TYPE_LIABILITY;
581            }
582            if (desc.startsWith(transferDescription)) {
583                return TRANSACTION_TYPE_TRANSFER;
584            }
585            return TRANSACTION_TYPE_OTHER;
586        }
587        
588        
589        protected String getTransactionType(String financialBalanceTypeCode, String desc) {
590            if (TRANSACTION_TYPE_COST_SHARE_ENCUMBRANCE.equals(financialBalanceTypeCode)) {
591                return TRANSACTION_TYPE_COST_SHARE_ENCUMBRANCE;
592            }
593            if (desc == null) {
594                return TRANSACTION_TYPE_OTHER;
595            }
596    
597            if (desc.startsWith(offsetDescription) && desc.indexOf(COST_SHARE_TRANSFER_ENTRY_IND) > -1) {
598                return TRANSACTION_TYPE_COST_SHARE;
599            }
600            if (desc.startsWith(costShareDescription) && desc.indexOf(COST_SHARE_TRANSFER_ENTRY_IND) > -1) {
601                return TRANSACTION_TYPE_COST_SHARE;
602            }
603            if (desc.startsWith(offsetDescription)) {
604                return TRANSACTION_TYPE_OFFSET;
605            }
606            if (desc.startsWith(capitalizationDescription)) {
607                return TRANSACTION_TYPE_CAPITALIZATION;
608            }
609            if (desc.startsWith(liabilityDescription)) {
610                return TRANSACTION_TYPE_LIABILITY;
611            }
612            if (desc.startsWith(transferDescription)) {
613                return TRANSACTION_TYPE_TRANSFER;
614            }
615            return TRANSACTION_TYPE_OTHER;
616        }
617        
618    
619        /**
620         * This will process a group of origin entries. The COBOL code was refactored a lot to get this so there isn't a 1 to 1 section
621         * of Cobol relating to this.
622         * 
623         * @param originEntryGroup Group to process
624         */
625        protected void processGroup(boolean reportOnlyMode, ScrubberReportData scrubberReport) {
626            OriginEntryFull lastEntry = null;
627            scrubCostShareAmount = KualiDecimal.ZERO;
628            unitOfWork = new UnitOfWorkInfo();
629    
630            FileReader INPUT_GLE_FILE = null;
631            String GLEN_RECORD;
632            BufferedReader INPUT_GLE_FILE_br;
633            try {
634                INPUT_GLE_FILE = new FileReader(inputFile);
635            }
636            catch (FileNotFoundException e) {
637                throw new RuntimeException(e);
638            }
639            try {
640                OUTPUT_GLE_FILE_ps = new PrintStream(validFile);
641                OUTPUT_ERR_FILE_ps = new PrintStream(errorFile);
642                OUTPUT_EXP_FILE_ps = new PrintStream(expiredFile);
643            }
644            catch (IOException e) {
645                throw new RuntimeException(e);
646            }
647    
648            INPUT_GLE_FILE_br = new BufferedReader(INPUT_GLE_FILE);
649            int line = 0;
650            LOG.info("Starting Scrubber Process process group...");
651            try {
652                while ((GLEN_RECORD = INPUT_GLE_FILE_br.readLine()) != null) {
653                    if (!org.apache.commons.lang.StringUtils.isEmpty(GLEN_RECORD) && !org.apache.commons.lang.StringUtils.isBlank(GLEN_RECORD.trim())) {
654                        line++;
655                        OriginEntryFull unscrubbedEntry = new OriginEntryFull();
656                        List<Message> tmperrors = unscrubbedEntry.setFromTextFileForBatch(GLEN_RECORD, line);
657                        scrubberReport.incrementUnscrubbedRecordsRead();
658                        transactionErrors = new ArrayList<Message>();
659    
660                        // 
661                        // This is done so if the code modifies this row, then saves it, it will be an insert,
662                        // and it won't touch the original. The Scrubber never modifies input rows/groups.
663                        // not relevant for file version
664    
665                        boolean saveErrorTransaction = false;
666                        boolean saveValidTransaction = false;
667                        boolean fatalErrorOccurred = false;
668    
669                        // Build a scrubbed entry
670                        OriginEntryFull scrubbedEntry = new OriginEntryFull();
671                        scrubbedEntry.setDocumentNumber(unscrubbedEntry.getDocumentNumber());
672                        scrubbedEntry.setOrganizationDocumentNumber(unscrubbedEntry.getOrganizationDocumentNumber());
673                        scrubbedEntry.setOrganizationReferenceId(unscrubbedEntry.getOrganizationReferenceId());
674                        scrubbedEntry.setReferenceFinancialDocumentNumber(unscrubbedEntry.getReferenceFinancialDocumentNumber());
675    
676                        Integer transactionNumber = unscrubbedEntry.getTransactionLedgerEntrySequenceNumber();
677                        scrubbedEntry.setTransactionLedgerEntrySequenceNumber(null == transactionNumber ? new Integer(0) : transactionNumber);
678                        scrubbedEntry.setTransactionLedgerEntryDescription(unscrubbedEntry.getTransactionLedgerEntryDescription());
679                        scrubbedEntry.setTransactionLedgerEntryAmount(unscrubbedEntry.getTransactionLedgerEntryAmount());
680                        scrubbedEntry.setTransactionDebitCreditCode(unscrubbedEntry.getTransactionDebitCreditCode());
681        
682                        if (!collectorMode) {
683                            ledgerSummaryReport.summarizeEntry(unscrubbedEntry); 
684                        }
685    
686                        // For Labor Scrubber  
687                        boolean laborIndicator = false;
688                        tmperrors.addAll(scrubberValidator.validateTransaction(unscrubbedEntry, scrubbedEntry, universityRunDate, laborIndicator, accountingCycleCachingService));
689                        transactionErrors.addAll(tmperrors);
690    
691    
692                        Account unscrubbedEntryAccount = accountingCycleCachingService.getAccount(unscrubbedEntry.getChartOfAccountsCode(), unscrubbedEntry.getAccountNumber());
693                        // KFSMI-173: both the expired and closed accounts rows are put in the expired account
694                        if ((unscrubbedEntryAccount != null) && (scrubberValidator.isAccountExpired(unscrubbedEntryAccount, universityRunDate) || unscrubbedEntryAccount.isClosed())) {
695                            // Make a copy of it so OJB doesn't just update the row in the original
696                            // group. It needs to make a new one in the expired group
697                            OriginEntryFull expiredEntry = OriginEntryFull.copyFromOriginEntryable(scrubbedEntry);
698                            createOutputEntry(expiredEntry, OUTPUT_EXP_FILE_ps);
699                            scrubberReport.incrementExpiredAccountFound();
700                        }
701    
702                        // the collector scrubber uses this map to apply the same changes made on an origin entry during scrubbing to
703                        // the collector detail record
704                        if (collectorMode) {
705                            unscrubbedToScrubbedEntries.put(unscrubbedEntry, scrubbedEntry);
706                        }
707    
708                        if (!isFatal(transactionErrors)) {
709                            saveValidTransaction = true;
710                            
711                            if (!collectorMode) {
712    
713                                // See if unit of work has changed
714                                if (!unitOfWork.isSameUnitOfWork(scrubbedEntry)) {
715                                    // Generate offset for last unit of work
716                                    // pass the String line for generating error files
717                                    generateOffset(lastEntry, scrubberReport);
718    
719                                    unitOfWork = new UnitOfWorkInfo(scrubbedEntry);
720                                }
721    
722                                KualiDecimal transactionAmount = scrubbedEntry.getTransactionLedgerEntryAmount();
723    
724                                ParameterEvaluator offsetFiscalPeriods = parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.OFFSET_FISCAL_PERIOD_CODES, scrubbedEntry.getUniversityFiscalPeriodCode());
725    
726                                BalanceType scrubbedEntryBalanceType = accountingCycleCachingService.getBalanceType(scrubbedEntry.getFinancialBalanceTypeCode());
727                                if (scrubbedEntryBalanceType.isFinancialOffsetGenerationIndicator() && offsetFiscalPeriods.evaluationSucceeds()) {
728                                    if (scrubbedEntry.isDebit()) {
729                                        unitOfWork.offsetAmount = unitOfWork.offsetAmount.add(transactionAmount);
730                                    }
731                                    else {
732                                        unitOfWork.offsetAmount = unitOfWork.offsetAmount.subtract(transactionAmount);
733                                    }
734                                }
735    
736                                // The sub account type code will only exist if there is a valid sub account
737                                String subAccountTypeCode = GeneralLedgerConstants.getSpaceSubAccountTypeCode();
738                                // major assumption: the a21 subaccount is proxied, so we don't want to query the database if the
739                                // subacct
740                                // number is dashes
741                                if (!KFSConstants.getDashSubAccountNumber().equals(scrubbedEntry.getSubAccountNumber())) {
742                                    A21SubAccount scrubbedEntryA21SubAccount = accountingCycleCachingService.getA21SubAccount(scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getAccountNumber(), scrubbedEntry.getSubAccountNumber());
743                                    if (ObjectUtils.isNotNull(scrubbedEntryA21SubAccount)) {
744                                        subAccountTypeCode = scrubbedEntryA21SubAccount.getSubAccountTypeCode();
745                                    }
746                                }
747    
748                                ParameterEvaluator costShareObjectTypeCodes = parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.COST_SHARE_OBJ_TYPE_CODES, scrubbedEntry.getFinancialObjectTypeCode());
749                                ParameterEvaluator costShareEncBalanceTypeCodes = parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.COST_SHARE_ENC_BAL_TYP_CODES, scrubbedEntry.getFinancialBalanceTypeCode());
750                                ParameterEvaluator costShareEncFiscalPeriodCodes = parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.COST_SHARE_ENC_FISCAL_PERIOD_CODES, scrubbedEntry.getUniversityFiscalPeriodCode());
751                                ParameterEvaluator costShareEncDocTypeCodes = parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.COST_SHARE_ENC_DOC_TYPE_CODES, scrubbedEntry.getFinancialDocumentTypeCode().trim());
752                                ParameterEvaluator costShareFiscalPeriodCodes = parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.COST_SHARE_FISCAL_PERIOD_CODES, scrubbedEntry.getUniversityFiscalPeriodCode());
753                                Account scrubbedEntryAccount = accountingCycleCachingService.getAccount(scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getAccountNumber());
754    
755                                if (costShareObjectTypeCodes.evaluationSucceeds() && costShareEncBalanceTypeCodes.evaluationSucceeds() && scrubbedEntryAccount.isForContractsAndGrants() && KFSConstants.SubAccountType.COST_SHARE.equals(subAccountTypeCode) && costShareEncFiscalPeriodCodes.evaluationSucceeds() && costShareEncDocTypeCodes.evaluationSucceeds()) {
756                                    TransactionError te1 = generateCostShareEncumbranceEntries(scrubbedEntry, scrubberReport);
757                                    if (te1 != null) {
758                                        List errors = new ArrayList();
759                                        errors.add(te1.message);
760                                        handleTransactionErrors(te1.transaction, errors);
761                                        saveValidTransaction = false;
762                                        saveErrorTransaction = true;
763                                    }
764                                }
765    
766                                SystemOptions scrubbedEntryOption = accountingCycleCachingService.getSystemOptions(scrubbedEntry.getUniversityFiscalYear());
767                                if (costShareObjectTypeCodes.evaluationSucceeds() && scrubbedEntryOption.getActualFinancialBalanceTypeCd().equals(scrubbedEntry.getFinancialBalanceTypeCode()) && scrubbedEntryAccount.isForContractsAndGrants() && KFSConstants.SubAccountType.COST_SHARE.equals(subAccountTypeCode) && costShareFiscalPeriodCodes.evaluationSucceeds() && costShareEncDocTypeCodes.evaluationSucceeds()) {
768                                    if (scrubbedEntry.isDebit()) {
769                                        scrubCostShareAmount = scrubCostShareAmount.subtract(transactionAmount);
770                                    }
771                                    else {
772                                        scrubCostShareAmount = scrubCostShareAmount.add(transactionAmount);
773                                    }
774                                }
775    
776                                ParameterEvaluator otherDocTypeCodes = parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.OFFSET_DOC_TYPE_CODES, scrubbedEntry.getFinancialDocumentTypeCode());
777    
778                                if (otherDocTypeCodes.evaluationSucceeds()) {
779                                    String m = processCapitalization(scrubbedEntry, scrubberReport);
780                                    if (m != null) {
781                                        saveValidTransaction = false;
782                                        saveErrorTransaction = false;
783                                        addTransactionError(m, "", Message.TYPE_FATAL);
784                                    }
785    
786                                    m = processLiabilities(scrubbedEntry, scrubberReport);
787                                    if (m != null) {
788                                        saveValidTransaction = false;
789                                        saveErrorTransaction = false;
790                                        addTransactionError(m, "", Message.TYPE_FATAL);
791                                    }
792    
793                                    m = processPlantIndebtedness(scrubbedEntry, scrubberReport);
794                                    if (m != null) {
795                                        saveValidTransaction = false;
796                                        saveErrorTransaction = false;
797                                        addTransactionError(m, "", Message.TYPE_FATAL);
798                                    }
799                                }
800    
801                                if (!scrubCostShareAmount.isZero()) {
802                                    TransactionError te = generateCostShareEntries(scrubbedEntry, scrubberReport);
803    
804                                    if (te != null) {
805                                        saveValidTransaction = false;
806                                        saveErrorTransaction = false;
807    
808                                        // Make a copy of it so OJB doesn't just update the row in the original
809                                        // group. It needs to make a new one in the error group
810                                        OriginEntryFull errorEntry = new OriginEntryFull(te.transaction);
811                                        errorEntry.setTransactionScrubberOffsetGenerationIndicator(false);
812                                        createOutputEntry(GLEN_RECORD, OUTPUT_ERR_FILE_ps);
813                                        scrubberReport.incrementErrorRecordWritten();
814                                        unitOfWork.errorsFound = true;
815    
816                                        handleTransactionError(te.transaction, te.message);
817                                    }
818                                    scrubCostShareAmount = KualiDecimal.ZERO;
819                                }
820    
821                                lastEntry = scrubbedEntry;
822                            }
823                        }
824                        else {
825                            // Error transaction
826                            saveErrorTransaction = true;
827                            fatalErrorOccurred = true;
828                        }
829                        handleTransactionErrors(OriginEntryFull.copyFromOriginEntryable(unscrubbedEntry), transactionErrors);
830     
831                        if (saveValidTransaction) {
832                            scrubbedEntry.setTransactionScrubberOffsetGenerationIndicator(false);
833                            createOutputEntry(scrubbedEntry, OUTPUT_GLE_FILE_ps);
834                            scrubberReport.incrementScrubbedRecordWritten();
835                        }
836    
837                        if (saveErrorTransaction) {
838                            // Make a copy of it so OJB doesn't just update the row in the original
839                            // group. It needs to make a new one in the error group
840                            OriginEntryFull errorEntry = OriginEntryFull.copyFromOriginEntryable(unscrubbedEntry);
841                            errorEntry.setTransactionScrubberOffsetGenerationIndicator(false);
842                            createOutputEntry(GLEN_RECORD, OUTPUT_ERR_FILE_ps);
843                            scrubberReport.incrementErrorRecordWritten();
844                            if (!fatalErrorOccurred) {
845                                // if a fatal error occurred, the creation of a new unit of work was by-passed;
846                                // therefore, it shouldn't ruin the previous unit of work
847                                unitOfWork.errorsFound = true;
848                            }
849                        }
850                    }
851                }
852    
853                if (!collectorMode) {
854                    // Generate last offset (if necessary)
855                    generateOffset(lastEntry, scrubberReport);
856                }
857    
858                INPUT_GLE_FILE_br.close();
859                INPUT_GLE_FILE.close();
860                OUTPUT_GLE_FILE_ps.close();
861                OUTPUT_ERR_FILE_ps.close();
862                OUTPUT_EXP_FILE_ps.close();
863    
864                handleEndOfScrubberReport(scrubberReport);
865    
866                if (!collectorMode) {  // winston
867                    ledgerSummaryReport.writeReport(this.scrubberLedgerReportWriterService);
868                }
869            }
870            catch (IOException e) {
871                throw new RuntimeException(e);
872            }
873        }
874    
875        /**
876         * Determines if a given error is fatal and should stop this scrubber run
877         * 
878         * @param errors errors from a scrubber run
879         * @return true if the run should be abended, false otherwise
880         */
881        protected boolean isFatal(List<Message> errors) {
882            for (Iterator<Message> iter = errors.iterator(); iter.hasNext();) {
883                Message element = iter.next();
884                if (element.getType() == Message.TYPE_FATAL) {
885                    return true;
886                }
887            }
888            return false;
889        }
890    
891        /**
892         * Generates a cost share entry and offset for the given entry and saves both to the valid group
893         * 
894         * @param scrubbedEntry the originEntry that was scrubbed
895         * @return a TransactionError initialized with any error encounted during entry generation, or (hopefully) null
896         */
897        protected TransactionError generateCostShareEntries(OriginEntryInformation scrubbedEntry, ScrubberReportData scrubberReport) {
898            // 3000-COST-SHARE to 3100-READ-OFSD in the cobol Generate Cost Share Entries
899            LOG.debug("generateCostShareEntries() started");
900            try {
901                OriginEntryFull costShareEntry = OriginEntryFull.copyFromOriginEntryable(scrubbedEntry);
902    
903                SystemOptions scrubbedEntryOption = accountingCycleCachingService.getSystemOptions(scrubbedEntry.getUniversityFiscalYear());
904                A21SubAccount scrubbedEntryA21SubAccount = accountingCycleCachingService.getA21SubAccount(scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getAccountNumber(), scrubbedEntry.getSubAccountNumber());
905    
906                costShareEntry.setFinancialObjectCode(parameterService.getParameterValue(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupParameters.COST_SHARE_OBJECT_CODE_PARM_NM));
907                costShareEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
908                costShareEntry.setFinancialObjectTypeCode(scrubbedEntryOption.getFinancialObjectTypeTransferExpenseCd());
909                costShareEntry.setTransactionLedgerEntrySequenceNumber(new Integer(0));
910    
911                StringBuffer description = new StringBuffer();
912                description.append(costShareDescription);
913                description.append(" ").append(scrubbedEntry.getAccountNumber());
914                description.append(offsetString);
915                costShareEntry.setTransactionLedgerEntryDescription(description.toString());
916    
917                costShareEntry.setTransactionLedgerEntryAmount(scrubCostShareAmount);
918                if (scrubCostShareAmount.isPositive()) {
919                    costShareEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
920                }
921                else {
922                    costShareEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
923                    costShareEntry.setTransactionLedgerEntryAmount(scrubCostShareAmount.negated());
924                }
925    
926                costShareEntry.setTransactionDate(runDate);
927                costShareEntry.setOrganizationDocumentNumber(null);
928                costShareEntry.setProjectCode(KFSConstants.getDashProjectCode());
929                costShareEntry.setOrganizationReferenceId(null);
930                costShareEntry.setReferenceFinancialDocumentTypeCode(null);
931                costShareEntry.setReferenceFinancialSystemOriginationCode(null);
932                costShareEntry.setReferenceFinancialDocumentNumber(null);
933                costShareEntry.setFinancialDocumentReversalDate(null);
934                costShareEntry.setTransactionEncumbranceUpdateCode(null);
935    
936                createOutputEntry(costShareEntry, OUTPUT_GLE_FILE_ps);
937                scrubberReport.incrementCostShareEntryGenerated();
938    
939                OriginEntryFull costShareOffsetEntry = new OriginEntryFull(costShareEntry);
940                costShareOffsetEntry.setTransactionLedgerEntryDescription(getOffsetMessage());
941                OffsetDefinition offsetDefinition = accountingCycleCachingService.getOffsetDefinition(scrubbedEntry.getUniversityFiscalYear(), scrubbedEntry.getChartOfAccountsCode(), KFSConstants.TRANSFER_FUNDS, scrubbedEntry.getFinancialBalanceTypeCode());
942                if (offsetDefinition != null) {
943                    if (offsetDefinition.getFinancialObject() == null) {
944                        StringBuffer objectCodeKey = new StringBuffer();
945                        objectCodeKey.append(offsetDefinition.getUniversityFiscalYear());
946                        objectCodeKey.append("-").append(offsetDefinition.getChartOfAccountsCode());
947                        objectCodeKey.append("-").append(offsetDefinition.getFinancialObjectCode());
948    
949                        Message m = new Message(configurationService.getPropertyString(KFSKeyConstants.ERROR_OFFSET_DEFINITION_OBJECT_CODE_NOT_FOUND) + " (" + objectCodeKey.toString() + ")", Message.TYPE_FATAL);
950                        LOG.debug("generateCostShareEntries() Error 1 object not found");
951                        return new TransactionError(costShareEntry, m);
952                    }
953    
954                    costShareOffsetEntry.setFinancialObjectCode(offsetDefinition.getFinancialObjectCode());
955                    costShareOffsetEntry.setFinancialObject(offsetDefinition.getFinancialObject());
956                    costShareOffsetEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
957                }
958                else {
959                    Map<Transaction, List<Message>> errors = new HashMap<Transaction, List<Message>>();
960    
961                    StringBuffer offsetKey = new StringBuffer("cost share transfer ");
962                    offsetKey.append(scrubbedEntry.getUniversityFiscalYear());
963                    offsetKey.append("-");
964                    offsetKey.append(scrubbedEntry.getChartOfAccountsCode());
965                    offsetKey.append("-TF-");
966                    offsetKey.append(scrubbedEntry.getFinancialBalanceTypeCode());
967    
968                    Message m = new Message(configurationService.getPropertyString(KFSKeyConstants.ERROR_OFFSET_DEFINITION_NOT_FOUND) + " (" + offsetKey.toString() + ")", Message.TYPE_FATAL);
969    
970                    LOG.debug("generateCostShareEntries() Error 2 offset not found");
971                    return new TransactionError(costShareEntry, m);
972                }
973    
974                costShareOffsetEntry.setFinancialObjectTypeCode(offsetDefinition.getFinancialObject().getFinancialObjectTypeCode());
975    
976                if (costShareEntry.isCredit()) {
977                    costShareOffsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
978                }
979                else {
980                    costShareOffsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
981                }
982    
983                try {
984                    flexibleOffsetAccountService.updateOffset(costShareOffsetEntry);
985                }
986                catch (InvalidFlexibleOffsetException e) {
987                    Message m = new Message(e.getMessage(), Message.TYPE_FATAL);
988                    LOG.debug("generateCostShareEntries() Cost Share Transfer Flexible Offset Error: " + e.getMessage());
989                    return new TransactionError(costShareEntry, m);
990                }
991    
992                createOutputEntry(costShareOffsetEntry, OUTPUT_GLE_FILE_ps);
993                scrubberReport.incrementCostShareEntryGenerated();
994    
995                OriginEntryFull costShareSourceAccountEntry = new OriginEntryFull(costShareEntry);
996    
997                description = new StringBuffer();
998                description.append(costShareDescription);
999                description.append(" ").append(scrubbedEntry.getAccountNumber());
1000                description.append(offsetString);
1001                costShareSourceAccountEntry.setTransactionLedgerEntryDescription(description.toString());
1002    
1003                costShareSourceAccountEntry.setChartOfAccountsCode(scrubbedEntryA21SubAccount.getCostShareChartOfAccountCode());
1004                costShareSourceAccountEntry.setAccountNumber(scrubbedEntryA21SubAccount.getCostShareSourceAccountNumber());
1005    
1006                setCostShareObjectCode(costShareSourceAccountEntry, scrubbedEntry);
1007                costShareSourceAccountEntry.setSubAccountNumber(scrubbedEntryA21SubAccount.getCostShareSourceSubAccountNumber());
1008    
1009                if (StringHelper.isNullOrEmpty(costShareSourceAccountEntry.getSubAccountNumber())) {
1010                    costShareSourceAccountEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
1011                }
1012    
1013                costShareSourceAccountEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
1014                costShareSourceAccountEntry.setFinancialObjectTypeCode(scrubbedEntryOption.getFinancialObjectTypeTransferExpenseCd());
1015                costShareSourceAccountEntry.setTransactionLedgerEntrySequenceNumber(new Integer(0));
1016    
1017                costShareSourceAccountEntry.setTransactionLedgerEntryAmount(scrubCostShareAmount);
1018                if (scrubCostShareAmount.isPositive()) {
1019                    costShareSourceAccountEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
1020                }
1021                else {
1022                    costShareSourceAccountEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
1023                    costShareSourceAccountEntry.setTransactionLedgerEntryAmount(scrubCostShareAmount.negated());
1024                }
1025    
1026                costShareSourceAccountEntry.setTransactionDate(runDate);
1027                costShareSourceAccountEntry.setOrganizationDocumentNumber(null);
1028                costShareSourceAccountEntry.setProjectCode(KFSConstants.getDashProjectCode());
1029                costShareSourceAccountEntry.setOrganizationReferenceId(null);
1030                costShareSourceAccountEntry.setReferenceFinancialDocumentTypeCode(null);
1031                costShareSourceAccountEntry.setReferenceFinancialSystemOriginationCode(null);
1032                costShareSourceAccountEntry.setReferenceFinancialDocumentNumber(null);
1033                costShareSourceAccountEntry.setFinancialDocumentReversalDate(null);
1034                costShareSourceAccountEntry.setTransactionEncumbranceUpdateCode(null);
1035    
1036                createOutputEntry(costShareSourceAccountEntry, OUTPUT_GLE_FILE_ps);
1037                scrubberReport.incrementCostShareEntryGenerated();
1038    
1039                OriginEntryFull costShareSourceAccountOffsetEntry = new OriginEntryFull(costShareSourceAccountEntry);
1040                costShareSourceAccountOffsetEntry.setTransactionLedgerEntryDescription(getOffsetMessage());
1041    
1042                // Lookup the new offset definition.
1043                offsetDefinition = accountingCycleCachingService.getOffsetDefinition(scrubbedEntry.getUniversityFiscalYear(), scrubbedEntry.getChartOfAccountsCode(), KFSConstants.TRANSFER_FUNDS, scrubbedEntry.getFinancialBalanceTypeCode());
1044                if (offsetDefinition != null) {
1045                    if (offsetDefinition.getFinancialObject() == null) {
1046                        Map<Transaction, List<Message>> errors = new HashMap<Transaction, List<Message>>();
1047    
1048                        StringBuffer objectCodeKey = new StringBuffer();
1049                        objectCodeKey.append(costShareEntry.getUniversityFiscalYear());
1050                        objectCodeKey.append("-").append(scrubbedEntry.getChartOfAccountsCode());
1051                        objectCodeKey.append("-").append(scrubbedEntry.getFinancialObjectCode());
1052    
1053                        Message m = new Message(configurationService.getPropertyString(KFSKeyConstants.ERROR_OFFSET_DEFINITION_OBJECT_CODE_NOT_FOUND) + " (" + objectCodeKey.toString() + ")", Message.TYPE_FATAL);
1054    
1055                        LOG.debug("generateCostShareEntries() Error 3 object not found");
1056                        return new TransactionError(costShareSourceAccountEntry, m);
1057                    }
1058    
1059                    costShareSourceAccountOffsetEntry.setFinancialObjectCode(offsetDefinition.getFinancialObjectCode());
1060                    costShareSourceAccountOffsetEntry.setFinancialObject(offsetDefinition.getFinancialObject());
1061                    costShareSourceAccountOffsetEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
1062                }
1063                else {
1064                    Map<Transaction, List<Message>> errors = new HashMap<Transaction, List<Message>>();
1065    
1066                    StringBuffer offsetKey = new StringBuffer("cost share transfer source ");
1067                    offsetKey.append(scrubbedEntry.getUniversityFiscalYear());
1068                    offsetKey.append("-");
1069                    offsetKey.append(scrubbedEntry.getChartOfAccountsCode());
1070                    offsetKey.append("-TF-");
1071                    offsetKey.append(scrubbedEntry.getFinancialBalanceTypeCode());
1072    
1073                    Message m = new Message(configurationService.getPropertyString(KFSKeyConstants.ERROR_OFFSET_DEFINITION_NOT_FOUND) + " (" + offsetKey.toString() + ")", Message.TYPE_FATAL);
1074    
1075                    LOG.debug("generateCostShareEntries() Error 4 offset not found");
1076                    return new TransactionError(costShareSourceAccountEntry, m);
1077                }
1078    
1079                costShareSourceAccountOffsetEntry.setFinancialObjectTypeCode(offsetDefinition.getFinancialObject().getFinancialObjectTypeCode());
1080    
1081                if (scrubbedEntry.isCredit()) {
1082                    costShareSourceAccountOffsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
1083                }
1084                else {
1085                    costShareSourceAccountOffsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
1086                }
1087    
1088                try {
1089                    flexibleOffsetAccountService.updateOffset(costShareSourceAccountOffsetEntry);
1090                }
1091                catch (InvalidFlexibleOffsetException e) {
1092                    Message m = new Message(e.getMessage(), Message.TYPE_FATAL);
1093                    LOG.debug("generateCostShareEntries() Cost Share Transfer Account Flexible Offset Error: " + e.getMessage());
1094                    return new TransactionError(costShareEntry, m);
1095                }
1096    
1097                createOutputEntry(costShareSourceAccountOffsetEntry, OUTPUT_GLE_FILE_ps);
1098                scrubberReport.incrementCostShareEntryGenerated();
1099    
1100                scrubCostShareAmount = KualiDecimal.ZERO;
1101            } catch (IOException ioe) {
1102                LOG.error("generateCostShareEntries() Stopped: " + ioe.getMessage());
1103                throw new RuntimeException("generateCostShareEntries() Stopped: " + ioe.getMessage(), ioe);
1104            }
1105            LOG.debug("generateCostShareEntries() successful");
1106            return null;
1107        }
1108    
1109        /**
1110         * Get all the transaction descriptions from the param table
1111         */
1112        protected void setDescriptions() {
1113            //TODO: move to constants class?
1114            offsetDescription = "GENERATED OFFSET";
1115            capitalizationDescription = "GENERATED CAPITALIZATION";
1116            liabilityDescription = "GENERATED LIABILITY";
1117            costShareDescription = "GENERATED COST SHARE FROM";
1118            transferDescription = "GENERATED TRANSFER FROM";
1119        }
1120    
1121        /**
1122         * Generate the flag for the end of specific descriptions. This will be used in the demerger step
1123         */
1124        protected void setOffsetString() {
1125    
1126            NumberFormat nf = NumberFormat.getInstance();
1127            nf.setMaximumFractionDigits(0);
1128            nf.setMaximumIntegerDigits(2);
1129            nf.setMinimumFractionDigits(0);
1130            nf.setMinimumIntegerDigits(2);
1131    
1132            offsetString = COST_SHARE_TRANSFER_ENTRY_IND + nf.format(runCal.get(Calendar.MONTH) + 1) + nf.format(runCal.get(Calendar.DAY_OF_MONTH));
1133        }
1134    
1135        /**
1136         * Generate the offset message with the flag at the end
1137         * 
1138         * @return a generated offset message
1139         */
1140        protected String getOffsetMessage() {
1141            String msg = offsetDescription + GeneralLedgerConstants.getSpaceTransactionLedgetEntryDescription();
1142    
1143            return msg.substring(0, OFFSET_MESSAGE_MAXLENGTH) + offsetString;
1144        }
1145    
1146        /**
1147         * Generates capitalization entries if necessary
1148         * 
1149         * @param scrubbedEntry the entry to generate capitalization entries (possibly) for
1150         * @return null if no error, message if error
1151         */
1152        protected String processCapitalization(OriginEntryInformation scrubbedEntry, ScrubberReportData scrubberReport) {
1153            
1154            try {
1155             // Lines 4694 - 4798 of the Pro Cobol listing on Confluence
1156                if (!parameterService.getIndicatorParameter(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupParameters.CAPITALIZATION_IND)) {
1157                    return null;
1158                }
1159    
1160                OriginEntryFull capitalizationEntry = OriginEntryFull.copyFromOriginEntryable(scrubbedEntry);
1161                SystemOptions scrubbedEntryOption = accountingCycleCachingService.getSystemOptions(scrubbedEntry.getUniversityFiscalYear());
1162                ObjectCode scrubbedEntryObjectCode = accountingCycleCachingService.getObjectCode(scrubbedEntry.getUniversityFiscalYear(), scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getFinancialObjectCode());
1163                Chart scrubbedEntryChart = accountingCycleCachingService.getChart(scrubbedEntry.getChartOfAccountsCode());
1164                Account scrubbedEntryAccount = accountingCycleCachingService.getAccount(scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getAccountNumber());
1165    
1166                ParameterEvaluator documentTypeCodes = (!ObjectUtils.isNull(scrubbedEntry)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.CAPITALIZATION_DOC_TYPE_CODES, scrubbedEntry.getFinancialDocumentTypeCode()) : null;
1167                ParameterEvaluator fiscalPeriodCodes = (!ObjectUtils.isNull(scrubbedEntry)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.CAPITALIZATION_FISCAL_PERIOD_CODES, scrubbedEntry.getUniversityFiscalPeriodCode()) : null;
1168                ParameterEvaluator objectSubTypeCodes = (!ObjectUtils.isNull(scrubbedEntryObjectCode)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.CAPITALIZATION_OBJ_SUB_TYPE_CODES, scrubbedEntryObjectCode.getFinancialObjectSubTypeCode()) : null;
1169                ParameterEvaluator subFundGroupCodes = (!ObjectUtils.isNull(scrubbedEntryAccount)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.CAPITALIZATION_SUB_FUND_GROUP_CODES, scrubbedEntryAccount.getSubFundGroupCode()) : null;
1170                ParameterEvaluator chartCodes = (!ObjectUtils.isNull(scrubbedEntry)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.CAPITALIZATION_CHART_CODES, scrubbedEntry.getChartOfAccountsCode()) : null;
1171    
1172                if (scrubbedEntry.getFinancialBalanceTypeCode().equals(scrubbedEntryOption.getActualFinancialBalanceTypeCd()) && scrubbedEntry.getUniversityFiscalYear().intValue() > 1995 && (documentTypeCodes != null && documentTypeCodes.evaluationSucceeds()) && (fiscalPeriodCodes != null && fiscalPeriodCodes.evaluationSucceeds()) && (objectSubTypeCodes != null && objectSubTypeCodes.evaluationSucceeds()) && (subFundGroupCodes != null && subFundGroupCodes.evaluationSucceeds()) && (chartCodes != null && chartCodes.evaluationSucceeds())) {
1173    
1174                    String objectSubTypeCode = scrubbedEntryObjectCode.getFinancialObjectSubTypeCode();
1175    
1176                    String capitalizationObjectCode = parameterService.getParameterValue(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupParameters.CAPITALIZATION_SUBTYPE_OBJECT, objectSubTypeCode);
1177                    if (capitalizationObjectCode != null) {
1178                        capitalizationEntry.setFinancialObjectCode(capitalizationObjectCode);
1179                        capitalizationEntry.setFinancialObject(accountingCycleCachingService.getObjectCode(capitalizationEntry.getUniversityFiscalYear(), capitalizationEntry.getChartOfAccountsCode(), capitalizationEntry.getFinancialObjectCode()));
1180                    }
1181    
1182                    capitalizationEntry.setFinancialObjectTypeCode(scrubbedEntryOption.getFinancialObjectTypeAssetsCd());
1183                    capitalizationEntry.setTransactionLedgerEntryDescription(capitalizationDescription);
1184    
1185                    plantFundAccountLookup(scrubbedEntry, capitalizationEntry);
1186    
1187                    capitalizationEntry.setUniversityFiscalPeriodCode(scrubbedEntry.getUniversityFiscalPeriodCode());
1188    
1189                    createOutputEntry(capitalizationEntry, OUTPUT_GLE_FILE_ps);
1190                    scrubberReport.incrementCapitalizationEntryGenerated();
1191    
1192                    // Clear out the id & the ojb version number to make sure we do an insert on the next one
1193                    capitalizationEntry.setVersionNumber(null);
1194                    capitalizationEntry.setEntryId(null);
1195                    
1196                    // Check system parameters for overriding fund balance object code; otherwise, use
1197                    // the chart fund balance object code.
1198                    String fundBalanceCode    = parameterService.getParameterValue(
1199                            ScrubberStep.class, 
1200                            GlParameterConstants.CAPITALIZATION_OFFSET_CODE);
1201                    
1202                    ObjectCode fundObjectCode = getFundBalanceObjectCode(fundBalanceCode, capitalizationEntry);
1203                    
1204                    if (fundObjectCode != null) {
1205                        capitalizationEntry.setFinancialObjectTypeCode(fundObjectCode.getFinancialObjectTypeCode());
1206                        capitalizationEntry.setFinancialObjectCode(fundBalanceCode);
1207                    }
1208                    else {
1209                        capitalizationEntry.setFinancialObjectCode(scrubbedEntryChart.getFundBalanceObjectCode());
1210                        //TODO: check to see if COBOL does this - seems weird - is this saying if the object code doesn't exist use the value from options?  Shouldn't it always come from one or the other?
1211                        if (ObjectUtils.isNotNull(scrubbedEntryChart.getFundBalanceObject())) {
1212                            capitalizationEntry.setFinancialObjectTypeCode(scrubbedEntryChart.getFundBalanceObject().getFinancialObjectTypeCode());
1213                        }
1214                        else {
1215                            capitalizationEntry.setFinancialObjectTypeCode(scrubbedEntryOption.getFinObjectTypeFundBalanceCd());
1216                        }
1217                    }
1218                    
1219                    populateTransactionDebtCreditCode(scrubbedEntry, capitalizationEntry);
1220    
1221                    try {
1222                        flexibleOffsetAccountService.updateOffset(capitalizationEntry);
1223                    }
1224                    catch (InvalidFlexibleOffsetException e) {
1225                        LOG.debug("processCapitalization() Capitalization Flexible Offset Error: " + e.getMessage());
1226                        return e.getMessage();
1227                    }
1228    
1229                    createOutputEntry(capitalizationEntry, OUTPUT_GLE_FILE_ps);
1230                    scrubberReport.incrementCapitalizationEntryGenerated();
1231                }
1232            } catch (IOException ioe) {
1233                LOG.error("processCapitalization() Stopped: " + ioe.getMessage());
1234                throw new RuntimeException("processCapitalization() Stopped: " + ioe.getMessage(), ioe);
1235            }
1236            return null;
1237        }
1238        
1239        /**
1240         * Generates the plant indebtedness entries
1241         * 
1242         * @param scrubbedEntry the entry to generated plant indebtedness entries for if necessary
1243         * @return null if no error, message if error
1244         */
1245        protected String processPlantIndebtedness(OriginEntryInformation scrubbedEntry, ScrubberReportData scrubberReport) {
1246            try{
1247                // Lines 4855 - 4979 of the Pro Cobol listing on Confluence 
1248                if (!parameterService.getIndicatorParameter(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupParameters.PLANT_INDEBTEDNESS_IND)) {
1249                    return null;
1250                }
1251    
1252                OriginEntryFull plantIndebtednessEntry = OriginEntryFull.copyFromOriginEntryable(scrubbedEntry);
1253    
1254                SystemOptions scrubbedEntryOption = accountingCycleCachingService.getSystemOptions(scrubbedEntry.getUniversityFiscalYear());
1255                ObjectCode scrubbedEntryObjectCode = accountingCycleCachingService.getObjectCode(scrubbedEntry.getUniversityFiscalYear(), scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getFinancialObjectCode());
1256                Account scrubbedEntryAccount = accountingCycleCachingService.getAccount(scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getAccountNumber());
1257                Chart scrubbedEntryChart = accountingCycleCachingService.getChart(scrubbedEntry.getChartOfAccountsCode());
1258                if (!ObjectUtils.isNull(scrubbedEntryAccount)) {
1259                    scrubbedEntryAccount.setOrganization(accountingCycleCachingService.getOrganization(scrubbedEntryAccount.getChartOfAccountsCode(), scrubbedEntryAccount.getOrganizationCode()));
1260                }
1261                
1262                ParameterEvaluator objectSubTypeCodes = (!ObjectUtils.isNull(scrubbedEntryObjectCode)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.PLANT_INDEBTEDNESS_OBJ_SUB_TYPE_CODES, scrubbedEntryObjectCode.getFinancialObjectSubTypeCode()) : null;
1263                ParameterEvaluator subFundGroupCodes = (!ObjectUtils.isNull(scrubbedEntryAccount)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.PLANT_INDEBTEDNESS_SUB_FUND_GROUP_CODES, scrubbedEntryAccount.getSubFundGroupCode()) : null;
1264    
1265                if (scrubbedEntry.getFinancialBalanceTypeCode().equals(scrubbedEntryOption.getActualFinancialBalanceTypeCd()) && (subFundGroupCodes != null && subFundGroupCodes.evaluationSucceeds()) && (objectSubTypeCodes != null && objectSubTypeCodes.evaluationSucceeds())) {
1266    
1267                    plantIndebtednessEntry.setTransactionLedgerEntryDescription(KFSConstants.PLANT_INDEBTEDNESS_ENTRY_DESCRIPTION);
1268                    populateTransactionDebtCreditCode(scrubbedEntry, plantIndebtednessEntry);
1269    
1270                    plantIndebtednessEntry.setTransactionScrubberOffsetGenerationIndicator(true);
1271                    createOutputEntry(plantIndebtednessEntry, OUTPUT_GLE_FILE_ps);
1272                    scrubberReport.incrementPlantIndebtednessEntryGenerated();
1273    
1274                    // Clear out the id & the ojb version number to make sure we do an insert on the next one
1275                    plantIndebtednessEntry.setVersionNumber(null);
1276                    plantIndebtednessEntry.setEntryId(null);
1277    
1278                    // Check system parameters for overriding fund balance object code; otherwise, use
1279                    // the chart fund balance object code.
1280                    String fundBalanceCode    = parameterService.getParameterValue(
1281                            ScrubberStep.class, 
1282                            GlParameterConstants.PLANT_INDEBTEDNESS_OFFSET_CODE);
1283                    
1284                    ObjectCode fundObjectCode = getFundBalanceObjectCode(fundBalanceCode, plantIndebtednessEntry);
1285                    if (fundObjectCode != null) {
1286                        plantIndebtednessEntry.setFinancialObjectTypeCode(fundObjectCode.getFinancialObjectTypeCode());
1287                        plantIndebtednessEntry.setFinancialObjectCode(fundBalanceCode);
1288                    }
1289                    else {
1290                        plantIndebtednessEntry.setFinancialObjectTypeCode(scrubbedEntryOption.getFinObjectTypeFundBalanceCd());
1291                        plantIndebtednessEntry.setFinancialObjectCode(scrubbedEntryChart.getFundBalanceObjectCode());
1292                    }
1293                    
1294                    plantIndebtednessEntry.setTransactionDebitCreditCode(scrubbedEntry.getTransactionDebitCreditCode());
1295    
1296                    plantIndebtednessEntry.setTransactionScrubberOffsetGenerationIndicator(true);
1297                    plantIndebtednessEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
1298    
1299                    try {
1300                        flexibleOffsetAccountService.updateOffset(plantIndebtednessEntry);
1301                    }
1302                    catch (InvalidFlexibleOffsetException e) {
1303                        LOG.error("processPlantIndebtedness() Flexible Offset Exception (1)", e);
1304                        LOG.debug("processPlantIndebtedness() Plant Indebtedness Flexible Offset Error: " + e.getMessage());
1305                        return e.getMessage();
1306                    }
1307    
1308                    createOutputEntry(plantIndebtednessEntry, OUTPUT_GLE_FILE_ps);
1309                    scrubberReport.incrementPlantIndebtednessEntryGenerated();
1310    
1311                    // Clear out the id & the ojb version number to make sure we do an insert on the next one
1312                    plantIndebtednessEntry.setVersionNumber(null);
1313                    plantIndebtednessEntry.setEntryId(null);
1314    
1315                    plantIndebtednessEntry.setFinancialObjectCode(scrubbedEntry.getFinancialObjectCode());
1316                    plantIndebtednessEntry.setFinancialObjectTypeCode(scrubbedEntry.getFinancialObjectTypeCode());
1317                    plantIndebtednessEntry.setTransactionDebitCreditCode(scrubbedEntry.getTransactionDebitCreditCode());
1318    
1319                    plantIndebtednessEntry.setTransactionLedgerEntryDescription(scrubbedEntry.getTransactionLedgerEntryDescription());
1320    
1321                    plantIndebtednessEntry.setAccountNumber(scrubbedEntry.getAccountNumber());
1322                    plantIndebtednessEntry.setSubAccountNumber(scrubbedEntry.getSubAccountNumber());
1323    
1324                    plantIndebtednessEntry.setAccountNumber(scrubbedEntryAccount.getOrganization().getCampusPlantAccountNumber());
1325                    plantIndebtednessEntry.setChartOfAccountsCode(scrubbedEntryAccount.getOrganization().getCampusPlantChartCode());
1326    
1327                    plantIndebtednessEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
1328                    plantIndebtednessEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
1329    
1330                    StringBuffer litGenPlantXferFrom = new StringBuffer();
1331                    litGenPlantXferFrom.append(transferDescription + " ");
1332                    litGenPlantXferFrom.append(scrubbedEntry.getChartOfAccountsCode()).append(" ");
1333                    litGenPlantXferFrom.append(scrubbedEntry.getAccountNumber());
1334                    plantIndebtednessEntry.setTransactionLedgerEntryDescription(litGenPlantXferFrom.toString());
1335    
1336                    createOutputEntry(plantIndebtednessEntry, OUTPUT_GLE_FILE_ps);
1337                    scrubberReport.incrementPlantIndebtednessEntryGenerated();
1338    
1339                    // Clear out the id & the ojb version number to make sure we do an insert on the next one
1340                    plantIndebtednessEntry.setVersionNumber(null);
1341                    plantIndebtednessEntry.setEntryId(null);
1342    
1343                    plantIndebtednessEntry.setFinancialObjectCode(scrubbedEntryChart.getFundBalanceObjectCode());
1344                    plantIndebtednessEntry.setFinancialObjectTypeCode(scrubbedEntryOption.getFinObjectTypeFundBalanceCd());
1345                    plantIndebtednessEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
1346    
1347                    populateTransactionDebtCreditCode(scrubbedEntry, plantIndebtednessEntry);
1348    
1349                    try {
1350                        flexibleOffsetAccountService.updateOffset(plantIndebtednessEntry);
1351                    }
1352                    catch (InvalidFlexibleOffsetException e) {
1353                        LOG.error("processPlantIndebtedness() Flexible Offset Exception (2)", e);
1354                        LOG.debug("processPlantIndebtedness() Plant Indebtedness Flexible Offset Error: " + e.getMessage());
1355                        return e.getMessage();
1356                    }
1357    
1358                    createOutputEntry(plantIndebtednessEntry, OUTPUT_GLE_FILE_ps);
1359                    scrubberReport.incrementPlantIndebtednessEntryGenerated();
1360                }
1361            } catch (IOException ioe) {
1362                LOG.error("processPlantIndebtedness() Stopped: " + ioe.getMessage());
1363                throw new RuntimeException("processPlantIndebtedness() Stopped: " + ioe.getMessage(), ioe);
1364            }
1365            return null;
1366        }
1367    
1368        /**
1369         * Generate the liability entries for the entry if necessary
1370         * 
1371         * @param scrubbedEntry the entry to generate liability entries for if necessary
1372         * @return null if no error, message if error
1373         */
1374        protected String processLiabilities(OriginEntryInformation scrubbedEntry, ScrubberReportData scrubberReport) {
1375            try{
1376                // Lines 4799 to 4839 of the Pro Cobol list of the scrubber on Confluence
1377                if (!parameterService.getIndicatorParameter(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupParameters.LIABILITY_IND)) {
1378                    return null;
1379                }
1380    
1381                Chart scrubbedEntryChart = accountingCycleCachingService.getChart(scrubbedEntry.getChartOfAccountsCode());
1382                SystemOptions scrubbedEntryOption = accountingCycleCachingService.getSystemOptions(scrubbedEntry.getUniversityFiscalYear());
1383                ObjectCode scrubbedEntryFinancialObject = accountingCycleCachingService.getObjectCode(scrubbedEntry.getUniversityFiscalYear(), scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getFinancialObjectCode());
1384                Account scrubbedEntryAccount = accountingCycleCachingService.getAccount(scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getAccountNumber());
1385    
1386                ParameterEvaluator chartCodes = (!ObjectUtils.isNull(scrubbedEntry)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.LIABILITY_CHART_CODES, scrubbedEntry.getChartOfAccountsCode()) : null;
1387                ParameterEvaluator docTypeCodes = (!ObjectUtils.isNull(scrubbedEntry)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.LIABILITY_DOC_TYPE_CODES, scrubbedEntry.getFinancialDocumentTypeCode()) : null;
1388                ParameterEvaluator fiscalPeriods = (!ObjectUtils.isNull(scrubbedEntry)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.LIABILITY_FISCAL_PERIOD_CODES, scrubbedEntry.getUniversityFiscalPeriodCode()) : null;
1389                ParameterEvaluator objSubTypeCodes = (!ObjectUtils.isNull(scrubbedEntryFinancialObject)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.LIABILITY_OBJ_SUB_TYPE_CODES, scrubbedEntryFinancialObject.getFinancialObjectSubTypeCode()) : null;
1390                ParameterEvaluator subFundGroupCodes = (!ObjectUtils.isNull(scrubbedEntryAccount)) ? parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.LIABILITY_SUB_FUND_GROUP_CODES, scrubbedEntryAccount.getSubFundGroupCode()) : null;
1391    
1392                if (scrubbedEntry.getFinancialBalanceTypeCode().equals(scrubbedEntryOption.getActualFinancialBalanceTypeCd()) && scrubbedEntry.getUniversityFiscalYear().intValue() > 1995 && (docTypeCodes != null && docTypeCodes.evaluationSucceeds()) && (fiscalPeriods != null && fiscalPeriods.evaluationSucceeds()) && (objSubTypeCodes != null && objSubTypeCodes.evaluationSucceeds()) && (subFundGroupCodes != null && subFundGroupCodes.evaluationSucceeds()) && (chartCodes != null && chartCodes.evaluationSucceeds())) {
1393                    OriginEntryFull liabilityEntry = OriginEntryFull.copyFromOriginEntryable(scrubbedEntry);
1394    
1395                    liabilityEntry.setFinancialObjectCode(parameterService.getParameterValue(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupParameters.LIABILITY_OBJECT_CODE));
1396                    liabilityEntry.setFinancialObjectTypeCode(scrubbedEntryOption.getFinObjectTypeLiabilitiesCode());
1397    
1398                    liabilityEntry.setTransactionDebitCreditCode(scrubbedEntry.getTransactionDebitCreditCode());
1399                    liabilityEntry.setTransactionLedgerEntryDescription(liabilityDescription);
1400                    plantFundAccountLookup(scrubbedEntry, liabilityEntry);
1401    
1402                    createOutputEntry(liabilityEntry, OUTPUT_GLE_FILE_ps);
1403                    scrubberReport.incrementLiabilityEntryGenerated();
1404    
1405                    // Clear out the id & the ojb version number to make sure we do an insert on the next one
1406                    liabilityEntry.setVersionNumber(null);
1407                    liabilityEntry.setEntryId(null);
1408    
1409                    // Check system parameters for overriding fund balance object code; otherwise, use
1410                    // the chart fund balance object code.
1411                    String fundBalanceCode    = parameterService.getParameterValue(
1412                            ScrubberStep.class, 
1413                            GlParameterConstants.LIABILITY_OFFSET_CODE);
1414                    
1415                    ObjectCode fundObjectCode = getFundBalanceObjectCode(fundBalanceCode, liabilityEntry);
1416                    if (fundObjectCode != null) {
1417                        liabilityEntry.setFinancialObjectTypeCode(fundObjectCode.getFinancialObjectTypeCode());
1418                        liabilityEntry.setFinancialObjectCode(fundBalanceCode);
1419                    }
1420                    else {
1421                        // ... and now generate the offset half of the liability entry
1422                        liabilityEntry.setFinancialObjectCode(scrubbedEntryChart.getFundBalanceObjectCode());
1423                        if (ObjectUtils.isNotNull(scrubbedEntryChart.getFundBalanceObject())) {
1424                            liabilityEntry.setFinancialObjectTypeCode(scrubbedEntryChart.getFundBalanceObject().getFinancialObjectTypeCode());
1425                        }
1426                        else {
1427                            liabilityEntry.setFinancialObjectTypeCode(scrubbedEntryOption.getFinObjectTypeFundBalanceCd());
1428                        }
1429                    }
1430                    
1431                    if (liabilityEntry.isDebit()) {
1432                        liabilityEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
1433                    }
1434                    else {
1435                        liabilityEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
1436                    }
1437    
1438                    try {
1439                        flexibleOffsetAccountService.updateOffset(liabilityEntry);
1440                    }
1441                    catch (InvalidFlexibleOffsetException e) {
1442                        LOG.debug("processLiabilities() Liability Flexible Offset Error: " + e.getMessage());
1443                        return e.getMessage();
1444                    }
1445    
1446                    createOutputEntry(liabilityEntry, OUTPUT_GLE_FILE_ps);
1447                    scrubberReport.incrementLiabilityEntryGenerated();
1448                }
1449            } catch (IOException ioe) {
1450                LOG.error("processLiabilities() Stopped: " + ioe.getMessage());
1451                throw new RuntimeException("processLiabilities() Stopped: " + ioe.getMessage(), ioe);
1452            } 
1453            return null;
1454        }
1455        
1456        /**
1457         * 
1458         * This method...
1459         * @param fundBalanceCodeParameter
1460         * @param originEntryFull
1461         * @return
1462         */
1463        private ObjectCode getFundBalanceObjectCode(String fundBalanceCode, OriginEntryFull originEntryFull)
1464        {
1465            ObjectCode fundBalanceObjectCode = null;
1466            if (fundBalanceCode != null) {
1467                Map<String, Object> criteriaMap = new HashMap<String, Object>();
1468                criteriaMap.put("universityFiscalYear", originEntryFull.getUniversityFiscalYear());
1469                criteriaMap.put("chartOfAccountsCode", originEntryFull.getChartOfAccountsCode());
1470                criteriaMap.put("financialObjectCode",  fundBalanceCode);
1471                
1472                fundBalanceObjectCode = ((ObjectCode) businessObjectService.findByPrimaryKey(ObjectCode.class, criteriaMap));
1473            }
1474            
1475            return fundBalanceObjectCode;
1476        }
1477        
1478        /**
1479         * 
1480         * This method...
1481         * @param scrubbedEntry
1482         * @param fullEntry
1483         */
1484        private void populateTransactionDebtCreditCode(OriginEntryInformation scrubbedEntry, OriginEntryFull fullEntry) 
1485        {
1486            if (scrubbedEntry.isDebit()) {
1487                fullEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
1488            }
1489            else {
1490                fullEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
1491            }
1492        }
1493        
1494        /**
1495         * Updates the entries with the proper chart and account for the plant fund
1496         * 
1497         * @param scrubbedEntry basis for plant fund entry
1498         * @param liabilityEntry liability entry
1499         */
1500        protected void plantFundAccountLookup(OriginEntryInformation scrubbedEntry, OriginEntryFull liabilityEntry) {
1501            // 4000-PLANT-FUND-ACCT to 4000-PLANT-FUND-ACCT-EXIT in cobol
1502            
1503            liabilityEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
1504            ObjectCode scrubbedEntryObjectCode = accountingCycleCachingService.getObjectCode(scrubbedEntry.getUniversityFiscalYear(), scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getFinancialObjectCode());
1505            Account scrubbedEntryAccount = accountingCycleCachingService.getAccount(scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getAccountNumber());
1506            scrubbedEntryAccount.setOrganization(accountingCycleCachingService.getOrganization(scrubbedEntryAccount.getChartOfAccountsCode(), scrubbedEntryAccount.getOrganizationCode()));
1507    
1508            if (!ObjectUtils.isNull(scrubbedEntryAccount) && !ObjectUtils.isNull(scrubbedEntryObjectCode)) {
1509                String objectSubTypeCode = scrubbedEntryObjectCode.getFinancialObjectSubTypeCode();
1510                ParameterEvaluator campusObjSubTypeCodes = parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.PLANT_FUND_CAMPUS_OBJECT_SUB_TYPE_CODES, objectSubTypeCode);
1511                ParameterEvaluator orgObjSubTypeCodes = parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.PLANT_FUND_ORG_OBJECT_SUB_TYPE_CODES, objectSubTypeCode);
1512    
1513                if (campusObjSubTypeCodes.evaluationSucceeds()) {
1514                    liabilityEntry.setAccountNumber(scrubbedEntryAccount.getOrganization().getCampusPlantAccountNumber());
1515                    liabilityEntry.setChartOfAccountsCode(scrubbedEntryAccount.getOrganization().getCampusPlantChartCode());
1516                }
1517                else if (orgObjSubTypeCodes.evaluationSucceeds()) {
1518                    liabilityEntry.setAccountNumber(scrubbedEntryAccount.getOrganization().getOrganizationPlantAccountNumber());
1519                    liabilityEntry.setChartOfAccountsCode(scrubbedEntryAccount.getOrganization().getOrganizationPlantChartCode());
1520                }
1521            }
1522        }
1523    
1524        /**
1525         * The purpose of this method is to generate a "Cost Share Encumbrance"
1526         * transaction for the current transaction and its offset. The cost share chart and account for current transaction are obtained
1527         * from the CA_A21_SUB_ACCT_T table. This method calls the method SET-OBJECT-2004 to get the Cost Share Object Code. It then
1528         * writes out the cost share transaction. Next it read the GL_OFFSET_DEFN_T table for the offset object code that corresponds to
1529         * the cost share object code. In addition to the object code it needs to get subobject code. It then reads the CA_OBJECT_CODE_T
1530         * table to make sure the offset object code found in the GL_OFFSET_DEFN_T is valid and to get the object type code associated
1531         * with this object code. It writes out the offset transaction and returns.
1532         * 
1533         * @param scrubbedEntry the entry to perhaps create a cost share encumbrance for 
1534         * @return a message if there was an error encountered generating the entries, or (hopefully) null if no errors were encountered
1535         */
1536        protected TransactionError generateCostShareEncumbranceEntries(OriginEntryInformation scrubbedEntry, ScrubberReportData scrubberReport) {
1537            try{
1538                // 3200-COST-SHARE-ENC to 3200-CSE-EXIT in the COBOL
1539                LOG.debug("generateCostShareEncumbranceEntries() started");
1540    
1541                OriginEntryFull costShareEncumbranceEntry = OriginEntryFull.copyFromOriginEntryable(scrubbedEntry);
1542    
1543                // First 28 characters of the description, padding to 28 if shorter)
1544                StringBuffer buffer = new StringBuffer((scrubbedEntry.getTransactionLedgerEntryDescription() + GeneralLedgerConstants.getSpaceTransactionLedgetEntryDescription()).substring(0, COST_SHARE_ENCUMBRANCE_ENTRY_MAXLENGTH));
1545    
1546                buffer.append("FR-");
1547                buffer.append(costShareEncumbranceEntry.getChartOfAccountsCode());
1548                buffer.append(costShareEncumbranceEntry.getAccountNumber());
1549    
1550                costShareEncumbranceEntry.setTransactionLedgerEntryDescription(buffer.toString());
1551    
1552                A21SubAccount scrubbedEntryA21SubAccount = accountingCycleCachingService.getA21SubAccount(scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getAccountNumber(), scrubbedEntry.getSubAccountNumber());
1553                SystemOptions scrubbedEntryOption = accountingCycleCachingService.getSystemOptions(scrubbedEntry.getUniversityFiscalYear());
1554    
1555                costShareEncumbranceEntry.setChartOfAccountsCode(scrubbedEntryA21SubAccount.getCostShareChartOfAccountCode());
1556                costShareEncumbranceEntry.setAccountNumber(scrubbedEntryA21SubAccount.getCostShareSourceAccountNumber());
1557                costShareEncumbranceEntry.setSubAccountNumber(scrubbedEntryA21SubAccount.getCostShareSourceSubAccountNumber());
1558    
1559                if (!StringUtils.hasText(costShareEncumbranceEntry.getSubAccountNumber())) {
1560                    costShareEncumbranceEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
1561                }
1562    
1563                costShareEncumbranceEntry.setFinancialBalanceTypeCode(scrubbedEntryOption.getCostShareEncumbranceBalanceTypeCd());
1564                setCostShareObjectCode(costShareEncumbranceEntry, scrubbedEntry);
1565                costShareEncumbranceEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
1566                costShareEncumbranceEntry.setTransactionLedgerEntrySequenceNumber(new Integer(0));
1567    
1568                if (!StringUtils.hasText(scrubbedEntry.getTransactionDebitCreditCode())) {
1569                    if (scrubbedEntry.getTransactionLedgerEntryAmount().isPositive()) {
1570                        costShareEncumbranceEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
1571                    }
1572                    else {
1573                        costShareEncumbranceEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
1574                        costShareEncumbranceEntry.setTransactionLedgerEntryAmount(scrubbedEntry.getTransactionLedgerEntryAmount().negated());
1575                    }
1576                }
1577    
1578                costShareEncumbranceEntry.setTransactionDate(runDate);
1579    
1580                costShareEncumbranceEntry.setTransactionScrubberOffsetGenerationIndicator(true);
1581                createOutputEntry(costShareEncumbranceEntry, OUTPUT_GLE_FILE_ps);
1582                scrubberReport.incrementCostShareEncumbranceGenerated();
1583    
1584                OriginEntryFull costShareEncumbranceOffsetEntry = new OriginEntryFull(costShareEncumbranceEntry);
1585                costShareEncumbranceOffsetEntry.setTransactionLedgerEntryDescription(offsetDescription);
1586                OffsetDefinition offset = accountingCycleCachingService.getOffsetDefinition(costShareEncumbranceEntry.getUniversityFiscalYear(), costShareEncumbranceEntry.getChartOfAccountsCode(), costShareEncumbranceEntry.getFinancialDocumentTypeCode(), costShareEncumbranceEntry.getFinancialBalanceTypeCode());
1587    
1588                if (offset != null) {
1589                    if (offset.getFinancialObject() == null) {
1590                        StringBuffer offsetKey = new StringBuffer();
1591                        offsetKey.append(offset.getUniversityFiscalYear());
1592                        offsetKey.append("-");
1593                        offsetKey.append(offset.getChartOfAccountsCode());
1594                        offsetKey.append("-");
1595                        offsetKey.append(offset.getFinancialObjectCode());
1596    
1597                        LOG.debug("generateCostShareEncumbranceEntries() object code not found");
1598                        return new TransactionError(costShareEncumbranceEntry, new Message(configurationService.getPropertyString(KFSKeyConstants.ERROR_NO_OBJECT_FOR_OBJECT_ON_OFSD) + "(" + offsetKey.toString() + ")", Message.TYPE_FATAL));
1599                    }
1600                    costShareEncumbranceOffsetEntry.setFinancialObjectCode(offset.getFinancialObjectCode());
1601                    costShareEncumbranceOffsetEntry.setFinancialObject(offset.getFinancialObject());
1602                    costShareEncumbranceOffsetEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
1603                }
1604                else {
1605                    StringBuffer offsetKey = new StringBuffer("Cost share encumbrance ");
1606                    offsetKey.append(costShareEncumbranceEntry.getUniversityFiscalYear());
1607                    offsetKey.append("-");
1608                    offsetKey.append(costShareEncumbranceEntry.getChartOfAccountsCode());
1609                    offsetKey.append("-");
1610                    offsetKey.append(costShareEncumbranceEntry.getFinancialDocumentTypeCode());
1611                    offsetKey.append("-");
1612                    offsetKey.append(costShareEncumbranceEntry.getFinancialBalanceTypeCode());
1613    
1614                    LOG.debug("generateCostShareEncumbranceEntries() offset not found");
1615                    return new TransactionError(costShareEncumbranceEntry, new Message(configurationService.getPropertyString(KFSKeyConstants.ERROR_OFFSET_DEFINITION_NOT_FOUND) + "(" + offsetKey.toString() + ")", Message.TYPE_FATAL));
1616                }
1617    
1618                costShareEncumbranceOffsetEntry.setFinancialObjectTypeCode(offset.getFinancialObject().getFinancialObjectTypeCode());
1619    
1620                if (costShareEncumbranceEntry.isCredit()) {
1621                    costShareEncumbranceOffsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
1622                }
1623                else {
1624                    costShareEncumbranceOffsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
1625                }
1626    
1627                costShareEncumbranceOffsetEntry.setTransactionDate(runDate);
1628                costShareEncumbranceOffsetEntry.setOrganizationDocumentNumber(null);
1629                costShareEncumbranceOffsetEntry.setProjectCode(KFSConstants.getDashProjectCode());
1630                costShareEncumbranceOffsetEntry.setOrganizationReferenceId(null);
1631                costShareEncumbranceOffsetEntry.setReferenceFinancialDocumentTypeCode(null);
1632                costShareEncumbranceOffsetEntry.setReferenceFinancialSystemOriginationCode(null);
1633                costShareEncumbranceOffsetEntry.setReferenceFinancialDocumentNumber(null);
1634                costShareEncumbranceOffsetEntry.setReversalDate(null);
1635                costShareEncumbranceOffsetEntry.setTransactionEncumbranceUpdateCode(null);
1636    
1637                costShareEncumbranceOffsetEntry.setTransactionScrubberOffsetGenerationIndicator(true);
1638    
1639                try {
1640                    flexibleOffsetAccountService.updateOffset(costShareEncumbranceOffsetEntry);
1641                }
1642                catch (InvalidFlexibleOffsetException e) {
1643                    Message m = new Message(e.getMessage(), Message.TYPE_FATAL);
1644                    LOG.debug("generateCostShareEncumbranceEntries() Cost Share Encumbrance Flexible Offset Error: " + e.getMessage());
1645                    return new TransactionError(costShareEncumbranceOffsetEntry, m);
1646                }
1647    
1648                createOutputEntry(costShareEncumbranceOffsetEntry, OUTPUT_GLE_FILE_ps);
1649                scrubberReport.incrementCostShareEncumbranceGenerated();
1650            } catch (IOException ioe) {
1651                LOG.error("generateCostShareEncumbranceEntries() Stopped: " + ioe.getMessage());
1652                throw new RuntimeException("generateCostShareEncumbranceEntries() Stopped: " + ioe.getMessage(), ioe);
1653            } 
1654            LOG.debug("generateCostShareEncumbranceEntries() returned successfully");
1655            return null;
1656        }
1657    
1658        /**
1659         * Sets the proper cost share object code in an entry and its offset
1660         * 
1661         * @param costShareEntry GL Entry for cost share
1662         * @param originEntry Scrubbed GL Entry that this is based on
1663         */
1664        public void setCostShareObjectCode(OriginEntryFull costShareEntry, OriginEntryInformation originEntry) {
1665            ObjectCode originEntryFinancialObject = accountingCycleCachingService.getObjectCode(originEntry.getUniversityFiscalYear(), originEntry.getChartOfAccountsCode(), originEntry.getFinancialObjectCode());
1666    
1667            if (originEntryFinancialObject == null) {
1668                addTransactionError(configurationService.getPropertyString(KFSKeyConstants.ERROR_OBJECT_CODE_NOT_FOUND), originEntry.getFinancialObjectCode(), Message.TYPE_FATAL);
1669            }
1670    
1671            String originEntryObjectLevelCode = (originEntryFinancialObject == null) ? "" : originEntryFinancialObject.getFinancialObjectLevelCode();
1672    
1673            String financialOriginEntryObjectCode = originEntry.getFinancialObjectCode();
1674            //String originEntryObjectCode = scrubberProcessObjectCodeOverride.getOriginEntryObjectCode(originEntryObjectLevelCode, financialOriginEntryObjectCode);
1675    
1676            // General rules
1677            String param = parameterService.getParameterValue(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupParameters.COST_SHARE_OBJECT_CODE_BY_LEVEL_PARM_NM, originEntryObjectLevelCode);
1678            if (param == null) {
1679                param = parameterService.getParameterValue(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupParameters.COST_SHARE_OBJECT_CODE_BY_LEVEL_PARM_NM, "DEFAULT");
1680                if (param == null) {
1681                    throw new RuntimeException("Unable to determine cost sharing object code from object level.  Default entry missing.");
1682                }
1683            }
1684            financialOriginEntryObjectCode = param;
1685    
1686            // Lookup the new object code
1687            ObjectCode objectCode = accountingCycleCachingService.getObjectCode(costShareEntry.getUniversityFiscalYear(), costShareEntry.getChartOfAccountsCode(), financialOriginEntryObjectCode);
1688            if (objectCode != null) {
1689                costShareEntry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
1690                costShareEntry.setFinancialObjectCode(financialOriginEntryObjectCode);
1691            }
1692            else {
1693                addTransactionError(configurationService.getPropertyString(KFSKeyConstants.ERROR_COST_SHARE_OBJECT_NOT_FOUND), costShareEntry.getFinancialObjectCode(), Message.TYPE_FATAL);
1694            }
1695        }
1696    
1697        /**
1698         * The purpose of this method is to build the actual offset transaction. It does this by performing the following steps: 1.
1699         * Getting the offset object code and offset subobject code from the GL Offset Definition Table. 2. For the offset object code
1700         * it needs to get the associated object type, object subtype, and object active code. 
1701         * 
1702         * @param scrubbedEntry entry to determine if an offset is needed for
1703         * @return true if an offset would be needed for this entry, false otherwise
1704         */
1705        protected boolean generateOffset(OriginEntryInformation scrubbedEntry, ScrubberReportData scrubberReport) {
1706            OriginEntryFull offsetEntry = new OriginEntryFull();
1707            try{
1708             // This code is 3000-OFFSET to SET-OBJECT-2004 in the Cobol
1709                LOG.debug("generateOffset() started");
1710    
1711                // There was no previous unit of work so we need no offset
1712                if (scrubbedEntry == null) {
1713                    return true;
1714                }
1715    
1716                // If there was an error, don't generate an offset since the record was pulled
1717                // and the rest of the document's records will be demerged
1718                if (unitOfWork.errorsFound == true) {
1719                    return true;
1720                }
1721                
1722                // If the offset amount is zero, don't bother to lookup the offset definition ...
1723                if (unitOfWork.offsetAmount.isZero()) {
1724                    return true;
1725                }
1726    
1727                ParameterEvaluator docTypeRule = parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.OFFSET_DOC_TYPE_CODES, scrubbedEntry.getFinancialDocumentTypeCode());
1728                if (!docTypeRule.evaluationSucceeds()) {
1729                    return true;
1730                }
1731                
1732                // do nothing if flexible offset is enabled and scrubber offset indicator of the document
1733                // type code is turned off in the document type table
1734                if (flexibleOffsetAccountService.getEnabled() && !shouldScrubberGenerateOffsetsForDocType(scrubbedEntry.getFinancialDocumentTypeCode())) {
1735                    return true;
1736                }
1737                
1738                // Create an offset
1739                offsetEntry = OriginEntryFull.copyFromOriginEntryable(scrubbedEntry);
1740                offsetEntry.setTransactionLedgerEntryDescription(offsetDescription);
1741    
1742                //of course this method should go elsewhere, not in ScrubberValidator!
1743                OffsetDefinition offsetDefinition = accountingCycleCachingService.getOffsetDefinition(scrubbedEntry.getUniversityFiscalYear(), scrubbedEntry.getChartOfAccountsCode(), scrubbedEntry.getFinancialDocumentTypeCode(), scrubbedEntry.getFinancialBalanceTypeCode());
1744                if (offsetDefinition != null) {
1745                    if (offsetDefinition.getFinancialObject() == null) {
1746                        StringBuffer offsetKey = new StringBuffer(offsetDefinition.getUniversityFiscalYear());
1747                        offsetKey.append("-");
1748                        offsetKey.append(offsetDefinition.getChartOfAccountsCode());
1749                        offsetKey.append("-");
1750                        offsetKey.append(offsetDefinition.getFinancialObjectCode());
1751    
1752                        putTransactionError(offsetEntry, configurationService.getPropertyString(KFSKeyConstants.ERROR_OFFSET_DEFINITION_OBJECT_CODE_NOT_FOUND), offsetKey.toString(), Message.TYPE_FATAL);
1753    
1754                        createOutputEntry(offsetEntry, OUTPUT_ERR_FILE_ps);
1755                        scrubberReport.incrementErrorRecordWritten();
1756                        return false;
1757                    }
1758    
1759                    offsetEntry.setFinancialObject(offsetDefinition.getFinancialObject());
1760                    offsetEntry.setFinancialObjectCode(offsetDefinition.getFinancialObjectCode());
1761    
1762                    offsetEntry.setFinancialSubObject(null);
1763                    offsetEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
1764                }
1765                else {
1766                    StringBuffer sb = new StringBuffer("Unit of work offset ");
1767                    sb.append(scrubbedEntry.getUniversityFiscalYear());
1768                    sb.append("-");
1769                    sb.append(scrubbedEntry.getChartOfAccountsCode());
1770                    sb.append("-");
1771                    sb.append(scrubbedEntry.getFinancialDocumentTypeCode());
1772                    sb.append("-");
1773                    sb.append(scrubbedEntry.getFinancialBalanceTypeCode());
1774    
1775                    putTransactionError(offsetEntry, configurationService.getPropertyString(KFSKeyConstants.ERROR_OFFSET_DEFINITION_NOT_FOUND), sb.toString(), Message.TYPE_FATAL);
1776    
1777                    createOutputEntry(offsetEntry, OUTPUT_ERR_FILE_ps);
1778                    scrubberReport.incrementErrorRecordWritten();
1779                    return false;
1780                }
1781    
1782                offsetEntry.setFinancialObjectTypeCode(offsetEntry.getFinancialObject().getFinancialObjectTypeCode());
1783                offsetEntry.setTransactionLedgerEntryAmount(unitOfWork.offsetAmount);
1784    
1785                if (unitOfWork.offsetAmount.isPositive()) {
1786                    offsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
1787                }
1788                else {
1789                    offsetEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
1790                    offsetEntry.setTransactionLedgerEntryAmount(unitOfWork.offsetAmount.negated());
1791                }
1792    
1793                offsetEntry.setOrganizationDocumentNumber(null);
1794                offsetEntry.setOrganizationReferenceId(null);
1795                offsetEntry.setReferenceFinancialDocumentTypeCode(null);
1796                offsetEntry.setReferenceFinancialSystemOriginationCode(null);
1797                offsetEntry.setReferenceFinancialDocumentNumber(null);
1798                offsetEntry.setTransactionEncumbranceUpdateCode(null);
1799                offsetEntry.setProjectCode(KFSConstants.getDashProjectCode());
1800                offsetEntry.setTransactionDate(runDate);
1801    
1802                try {
1803                    flexibleOffsetAccountService.updateOffset(offsetEntry);
1804                }
1805                catch (InvalidFlexibleOffsetException e) {
1806                    LOG.debug("generateOffset() Offset Flexible Offset Error: " + e.getMessage());
1807                    putTransactionError(offsetEntry, e.getMessage(), "", Message.TYPE_FATAL);
1808                    return true;
1809                }
1810    
1811                createOutputEntry(offsetEntry, OUTPUT_GLE_FILE_ps);
1812                scrubberReport.incrementOffsetEntryGenerated();
1813                
1814            } catch (IOException ioe) {
1815                LOG.error("generateOffset() Stopped: " + ioe.getMessage());
1816                throw new RuntimeException("generateOffset() Stopped: " + ioe.getMessage(), ioe);
1817            }
1818            
1819            return true;
1820        }
1821    
1822        
1823        protected void createOutputEntry(OriginEntryInformation entry, PrintStream ps) throws IOException {
1824            try {
1825                ps.printf("%s\n", entry.getLine());
1826            } catch (Exception e) {
1827                throw new IOException(e.toString());
1828            }
1829        }
1830        
1831        protected void createOutputEntry(String line, PrintStream ps) throws IOException {
1832            try {
1833                ps.printf("%s\n", line);
1834            } catch (Exception e) {
1835                throw new IOException(e.toString());
1836            }
1837        }
1838        
1839        /**
1840         * Add an error message to the list of messages for this transaction
1841         * 
1842         * @param errorMessage Error message
1843         * @param errorValue Value that is in error
1844         * @param type Type of error (Fatal or Warning)
1845         */
1846        protected void addTransactionError(String errorMessage, String errorValue, int type) {
1847            transactionErrors.add(new Message(errorMessage + " (" + errorValue + ")", type));
1848        }
1849    
1850        /**
1851         * Puts a transaction error into this instance's collection of errors
1852         * 
1853         * @param s a transaction that caused a scrubber error
1854         * @param errorMessage the message of what caused the error
1855         * @param errorValue the value in error
1856         * @param type the type of error
1857         */
1858        protected void putTransactionError(Transaction s, String errorMessage, String errorValue, int type) {
1859            Message m = new Message(errorMessage + "(" + errorValue + ")", type);
1860            scrubberReportWriterService.writeError(s, m);
1861        }
1862        
1863        /**
1864         * Determines if the scrubber should generate offsets for the given document type
1865         * @param docTypeCode the document type code to check if it generates scrubber offsets
1866         * @return true if the scrubber should generate offsets for this doc type, false otherwise
1867         */
1868        protected boolean shouldScrubberGenerateOffsetsForDocType(String docTypeCode) {
1869            return parameterService.getParameterEvaluator(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.DOCUMENT_TYPES_REQUIRING_FLEXIBLE_OFFSET_BALANCING_ENTRIES, docTypeCode).evaluationSucceeds();
1870        }
1871    
1872        /**
1873         * A class to hold the current unit of work the scrubber is using
1874         */
1875        class UnitOfWorkInfo {
1876            // Unit of work key
1877            public Integer univFiscalYr = 0;
1878            public String finCoaCd = "";
1879            public String accountNbr = "";
1880            public String subAcctNbr = "";
1881            public String finBalanceTypCd = "";
1882            public String fdocTypCd = "";
1883            public String fsOriginCd = "";
1884            public String fdocNbr = "";
1885            public Date fdocReversalDt = new Date(dateTimeService.getCurrentDate().getTime());
1886            public String univFiscalPrdCd = "";
1887    
1888            // Data about unit of work
1889            public boolean errorsFound = false;
1890            public KualiDecimal offsetAmount = KualiDecimal.ZERO;
1891            public String scrbFinCoaCd;
1892            public String scrbAccountNbr;
1893    
1894            /**
1895             * Constructs a ScrubberProcess.UnitOfWorkInfo instance
1896             */
1897            public UnitOfWorkInfo() {
1898            }
1899    
1900            /**
1901             * Constructs a ScrubberProcess.UnitOfWorkInfo instance
1902             * @param e an origin entry belonging to this unit of work
1903             */
1904            public UnitOfWorkInfo(OriginEntryInformation e) {
1905                univFiscalYr = e.getUniversityFiscalYear();
1906                finCoaCd = e.getChartOfAccountsCode();
1907                accountNbr = e.getAccountNumber();
1908                subAcctNbr = e.getSubAccountNumber();
1909                finBalanceTypCd = e.getFinancialBalanceTypeCode();
1910                fdocTypCd = e.getFinancialDocumentTypeCode();
1911                fsOriginCd = e.getFinancialSystemOriginationCode();
1912                fdocNbr = e.getDocumentNumber();
1913                fdocReversalDt = e.getFinancialDocumentReversalDate();
1914                univFiscalPrdCd = e.getUniversityFiscalPeriodCode();
1915            }
1916    
1917            /**
1918             * Determines if an entry belongs to this unit of work
1919             * 
1920             * @param e the entry to check
1921             * @return true if it belongs to this unit of work, false otherwise
1922             */
1923            public boolean isSameUnitOfWork(OriginEntryInformation e) {
1924                // Compare the key fields
1925                return univFiscalYr.equals(e.getUniversityFiscalYear()) && finCoaCd.equals(e.getChartOfAccountsCode()) && accountNbr.equals(e.getAccountNumber()) && subAcctNbr.equals(e.getSubAccountNumber()) && finBalanceTypCd.equals(e.getFinancialBalanceTypeCode()) && fdocTypCd.equals(e.getFinancialDocumentTypeCode()) && fsOriginCd.equals(e.getFinancialSystemOriginationCode()) && fdocNbr.equals(e.getDocumentNumber()) && ObjectHelper.isEqual(fdocReversalDt, e.getFinancialDocumentReversalDate()) && univFiscalPrdCd.equals(e.getUniversityFiscalPeriodCode());
1926            }
1927    
1928            /**
1929             * Converts this unit of work info to a String
1930             * @return a String representation of this UnitOfWorkInfo
1931             * @see java.lang.Object#toString()
1932             */
1933            public String toString() {
1934                return univFiscalYr + finCoaCd + accountNbr + subAcctNbr + finBalanceTypCd + fdocTypCd + fsOriginCd + fdocNbr + fdocReversalDt + univFiscalPrdCd;
1935            }
1936    
1937            /**
1938             * Generates the beginning of an OriginEntryFull, based on the unit of work info
1939             * 
1940             * @return a partially initialized OriginEntryFull
1941             */
1942            public OriginEntryFull getOffsetTemplate() {
1943                OriginEntryFull e = new OriginEntryFull();
1944                e.setUniversityFiscalYear(univFiscalYr);
1945                e.setChartOfAccountsCode(finCoaCd);
1946                e.setAccountNumber(accountNbr);
1947                e.setSubAccountNumber(subAcctNbr);
1948                e.setFinancialBalanceTypeCode(finBalanceTypCd);
1949                e.setFinancialDocumentTypeCode(fdocTypCd);
1950                e.setFinancialSystemOriginationCode(fsOriginCd);
1951                e.setDocumentNumber(fdocNbr);
1952                e.setFinancialDocumentReversalDate(fdocReversalDt);
1953                e.setUniversityFiscalPeriodCode(univFiscalPrdCd);
1954                return e;
1955            }
1956        }
1957    
1958        /**
1959         * An internal class to hold errors encountered by the scrubber
1960         */
1961        class TransactionError {
1962            public Transaction transaction;
1963            public Message message;
1964    
1965            /**
1966             * Constructs a ScrubberProcess.TransactionError instance
1967             * @param t the transaction that had the error
1968             * @param m a message about the error
1969             */
1970            public TransactionError(Transaction t, Message m) {
1971                transaction = t;
1972                message = m;
1973            }
1974        }
1975    
1976        /**
1977         * This method modifies the run date if it is before the cutoff time specified by the RunTimeService See
1978         * KULRNE-70 This method is public to facilitate unit testing
1979         * 
1980         * @param currentDate the date the scrubber should report as having run on
1981         * @return the run date
1982         */
1983        public Date calculateRunDate(java.util.Date currentDate) {
1984            return new Date(runDateService.calculateRunDate(currentDate).getTime());
1985        }
1986    
1987        protected boolean checkingBypassEntry (String financialBalanceTypeCode, String desc, DemergerReportData demergerReport){
1988            String transactionType = getTransactionType(financialBalanceTypeCode, desc);
1989            
1990            if (TRANSACTION_TYPE_COST_SHARE_ENCUMBRANCE.equals(transactionType)) {
1991                demergerReport.incrementCostShareEncumbranceTransactionsBypassed();
1992                return true;
1993            }
1994            else if (TRANSACTION_TYPE_OFFSET.equals(transactionType)) {
1995                demergerReport.incrementOffsetTransactionsBypassed();
1996                return true;
1997            }
1998            else if (TRANSACTION_TYPE_CAPITALIZATION.equals(transactionType)) {
1999                demergerReport.incrementCapitalizationTransactionsBypassed();
2000                return true;
2001            }
2002            else if (TRANSACTION_TYPE_LIABILITY.equals(transactionType)) {
2003                demergerReport.incrementLiabilityTransactionsBypassed();
2004                return true;
2005            }
2006            else if (TRANSACTION_TYPE_TRANSFER.equals(transactionType)) {
2007                demergerReport.incrementTransferTransactionsBypassed();
2008                return true;
2009            }
2010            else if (TRANSACTION_TYPE_COST_SHARE.equals(transactionType)) {
2011                demergerReport.incrementCostShareTransactionsBypassed();
2012                return true;
2013            }
2014            
2015            return false;
2016        }
2017        
2018        
2019        protected String checkAndSetTransactionTypeCostShare (String financialBalanceTypeCode, String desc, String currentValidLine){
2020            
2021            // Read all the transactions in the valid group and update the cost share transactions
2022            String transactionType = getTransactionType(financialBalanceTypeCode, desc);
2023            if (TRANSACTION_TYPE_COST_SHARE.equals(transactionType)) {
2024                OriginEntryFull transaction = new OriginEntryFull();
2025                transaction.setFromTextFileForBatch(currentValidLine, 0);
2026                
2027                transaction.setFinancialDocumentTypeCode(KFSConstants.TRANSFER_FUNDS);
2028                transaction.setFinancialSystemOriginationCode(KFSConstants.SubAccountType.COST_SHARE);
2029                StringBuffer docNbr = new StringBuffer(COST_SHARE_CODE);
2030                
2031                docNbr.append(desc.substring(36, 38));
2032                docNbr.append("/");
2033                docNbr.append(desc.substring(38, 40));
2034                transaction.setDocumentNumber(docNbr.toString());
2035                transaction.setTransactionLedgerEntryDescription(desc.substring(0, DEMERGER_TRANSACTION_LEDGET_ENTRY_DESCRIPTION));
2036                
2037                currentValidLine = transaction.getLine();
2038           }
2039            
2040            return currentValidLine;
2041            
2042        }
2043        
2044        
2045        /**
2046         * Generates the scrubber listing report for the GLCP document
2047         * @param documentNumber the document number of the GLCP document
2048         */
2049        protected void generateScrubberTransactionListingReport(String documentNumber, String inputFileName) {
2050            try {
2051                scrubberListingReportWriterService.setDocumentNumber(documentNumber);
2052            ((WrappingBatchService) scrubberListingReportWriterService).initialize();
2053            new TransactionListingReport().generateReport(scrubberListingReportWriterService, new OriginEntryFileIterator(new File(inputFileName)));
2054            } finally {
2055            ((WrappingBatchService) scrubberListingReportWriterService).destroy();
2056        }
2057        }
2058        
2059        /**
2060         * Generates the scrubber report that lists out the input origin entries with blank balance type codes.
2061         */
2062        protected void generateScrubberBlankBalanceTypeCodeReport(String inputFileName) {
2063            OriginEntryFilter blankBalanceTypeFilter = new OriginEntryFilter() {
2064                /**
2065                 * @see org.kuali.kfs.gl.batch.service.impl.FilteringOriginEntryFileIterator.OriginEntryFilter#accept(org.kuali.kfs.gl.businessobject.OriginEntryFull)
2066                 */
2067                public boolean accept(OriginEntryFull originEntry) {
2068                    BalanceType originEntryBalanceType = accountingCycleCachingService.getBalanceType(originEntry.getFinancialBalanceTypeCode());
2069                    return ObjectUtils.isNull(originEntryBalanceType);
2070                }
2071            };
2072            Iterator<OriginEntryFull> blankBalanceOriginEntries = new FilteringOriginEntryFileIterator(new File(inputFileName), blankBalanceTypeFilter);
2073            new TransactionListingReport().generateReport(scrubberBadBalanceListingReportWriterService, blankBalanceOriginEntries);   
2074        }
2075        
2076        protected void generateDemergerRemovedTransactionsReport(String errorFileName) {
2077            OriginEntryFileIterator removedTransactions = new OriginEntryFileIterator(new File(errorFileName));
2078            new TransactionListingReport().generateReport(demergerRemovedTransactionsListingReportWriterService, removedTransactions);
2079        }
2080        
2081        protected void handleTransactionError(Transaction errorTransaction, Message message) {
2082            if (collectorMode) {
2083                List<Message> messages = scrubberReportErrors.get(errorTransaction);
2084                if (messages == null) {
2085                    messages = new ArrayList<Message>();
2086                    scrubberReportErrors.put(errorTransaction, messages);
2087                }
2088                messages.add(message);
2089            }
2090            else {
2091                scrubberReportWriterService.writeError(errorTransaction, message);
2092            }
2093        }
2094        
2095        protected void handleTransactionErrors(Transaction errorTransaction, List<Message> messages) {
2096            if (collectorMode) {
2097                for (Message message : messages) {
2098                    handleTransactionError(errorTransaction, message);
2099                }
2100            }
2101            else {
2102                if (LOG.isDebugEnabled()) {
2103                    LOG.debug("Errors on transaction: "+errorTransaction);
2104                    for (Message message: messages) {
2105                        LOG.debug(message);
2106                    }
2107                }
2108                scrubberReportWriterService.writeError(errorTransaction, messages);
2109            }
2110        }
2111        
2112        protected void handleEndOfScrubberReport(ScrubberReportData scrubberReport) {
2113            if (!collectorMode) {
2114                scrubberReportWriterService.writeStatisticLine("UNSCRUBBED RECORDS READ              %,9d", scrubberReport.getNumberOfUnscrubbedRecordsRead());
2115                scrubberReportWriterService.writeStatisticLine("SCRUBBED RECORDS WRITTEN             %,9d", scrubberReport.getNumberOfScrubbedRecordsWritten());
2116                scrubberReportWriterService.writeStatisticLine("ERROR RECORDS WRITTEN                %,9d", scrubberReport.getNumberOfErrorRecordsWritten());
2117                scrubberReportWriterService.writeStatisticLine("OFFSET ENTRIES GENERATED             %,9d", scrubberReport.getNumberOfOffsetEntriesGenerated());
2118                scrubberReportWriterService.writeStatisticLine("CAPITALIZATION ENTRIES GENERATED     %,9d", scrubberReport.getNumberOfCapitalizationEntriesGenerated());
2119                scrubberReportWriterService.writeStatisticLine("LIABILITY ENTRIES GENERATED          %,9d", scrubberReport.getNumberOfLiabilityEntriesGenerated());
2120                scrubberReportWriterService.writeStatisticLine("PLANT INDEBTEDNESS ENTRIES GENERATED %,9d", scrubberReport.getNumberOfPlantIndebtednessEntriesGenerated());
2121                scrubberReportWriterService.writeStatisticLine("COST SHARE ENTRIES GENERATED         %,9d", scrubberReport.getNumberOfCostShareEntriesGenerated());
2122                scrubberReportWriterService.writeStatisticLine("COST SHARE ENC ENTRIES GENERATED     %,9d", scrubberReport.getNumberOfCostShareEncumbrancesGenerated());
2123                scrubberReportWriterService.writeStatisticLine("TOTAL OUTPUT RECORDS WRITTEN         %,9d", scrubberReport.getTotalNumberOfRecordsWritten());
2124                scrubberReportWriterService.writeStatisticLine("EXPIRED ACCOUNTS FOUND               %,9d", scrubberReport.getNumberOfExpiredAccountsFound());
2125            }
2126        }
2127        
2128        protected void handleDemergerSaveValidEntry(String entryString) {
2129            if (collectorMode) {
2130                OriginEntryInformation tempEntry = new OriginEntryFull(entryString);
2131                ledgerSummaryReport.summarizeEntry(tempEntry);
2132            }
2133        }
2134    
2135        /**
2136         * Sets the batchFileDirectoryName attribute value.
2137         * @param batchFileDirectoryName The batchFileDirectoryName to set.
2138         */
2139        public void setBatchFileDirectoryName(String batchFileDirectoryName) {
2140            this.batchFileDirectoryName = batchFileDirectoryName;
2141        }
2142    
2143        /**
2144         * Gets the transferDescription attribute. 
2145         * @return Returns the transferDescription.
2146         */
2147        public String getTransferDescription() {
2148            return transferDescription;
2149        }
2150    
2151        /**
2152         * Sets the transferDescription attribute value.
2153         * @param transferDescription The transferDescription to set.
2154         */
2155        public void setTransferDescription(String transferDescription) {
2156            this.transferDescription = transferDescription;
2157        }
2158    
2159        /**
2160         * Sets the dateTimeService attribute value.
2161         * @param dateTimeService The dateTimeService to set.
2162         */
2163        public void setDateTimeService(DateTimeService dateTimeService) {
2164            this.dateTimeService = dateTimeService;
2165        }
2166    
2167        /**
2168         * Sets the lOG attribute value.
2169         * @param log The lOG to set.
2170         */
2171        public static void setLOG(org.apache.log4j.Logger log) {
2172            LOG = log;
2173        }
2174    
2175        /**
2176         * Sets the flexibleOffsetAccountService attribute value.
2177         * @param flexibleOffsetAccountService The flexibleOffsetAccountService to set.
2178         */
2179        public void setFlexibleOffsetAccountService(FlexibleOffsetAccountService flexibleOffsetAccountService) {
2180            this.flexibleOffsetAccountService = flexibleOffsetAccountService;
2181        }
2182    
2183        /**
2184         * Sets the configurationService attribute value.
2185         * @param configurationService The configurationService to set.
2186         */
2187        public void setConfigurationService(KualiConfigurationService configurationService) {
2188            this.configurationService = configurationService;
2189        }
2190    
2191        /**
2192         * Sets the persistenceService attribute value.
2193         * @param persistenceService The persistenceService to set.
2194         */
2195        public void setPersistenceService(PersistenceService persistenceService) {
2196            this.persistenceService = persistenceService;
2197        }
2198    
2199        /**
2200         * Sets the scrubberValidator attribute value.
2201         * @param scrubberValidator The scrubberValidator to set.
2202         */
2203        public void setScrubberValidator(ScrubberValidator scrubberValidator) {
2204            this.scrubberValidator = scrubberValidator;
2205        }
2206    
2207        /**
2208         * Sets the accountingCycleCachingService attribute value.
2209         * @param accountingCycleCachingService The accountingCycleCachingService to set.
2210         */
2211        public void setAccountingCycleCachingService(AccountingCycleCachingService accountingCycleCachingService) {
2212            this.accountingCycleCachingService = accountingCycleCachingService;
2213        }
2214    
2215        /**
2216         * Sets the scrubberReportWriterService attribute value.
2217         * @param scrubberReportWriterService The scrubberReportWriterService to set.
2218         */
2219        public void setScrubberReportWriterService(DocumentNumberAwareReportWriterService scrubberReportWriterService) {
2220            this.scrubberReportWriterService = scrubberReportWriterService;
2221        }
2222    
2223        /**
2224         * Sets the scrubberLedgerReportWriterService attribute value.
2225         * @param scrubberLedgerReportWriterService The scrubberLedgerReportWriterService to set.
2226         */
2227        public void setScrubberLedgerReportWriterService(DocumentNumberAwareReportWriterService scrubberLedgerReportWriterService) {
2228            this.scrubberLedgerReportWriterService = scrubberLedgerReportWriterService;
2229        }
2230    
2231        /**
2232         * Sets the scrubberListingReportWriterService attribute value.
2233         * @param scrubberListingReportWriterService The scrubberListingReportWriterService to set.
2234         */
2235        public void setScrubberListingReportWriterService(DocumentNumberAwareReportWriterService scrubberListingReportWriterService) {
2236            this.scrubberListingReportWriterService = scrubberListingReportWriterService;
2237        }
2238    
2239        /**
2240         * Sets the scrubberBadBalanceListingReportWriterService attribute value.
2241         * @param scrubberBadBalanceListingReportWriterService The scrubberBadBalanceListingReportWriterService to set.
2242         */
2243        public void setScrubberBadBalanceListingReportWriterService(ReportWriterService scrubberBadBalanceListingReportWriterService) {
2244            this.scrubberBadBalanceListingReportWriterService = scrubberBadBalanceListingReportWriterService;
2245        }
2246    
2247        /**
2248         * Sets the demergerRemovedTransactionsListingReportWriterService attribute value.
2249         * @param demergerRemovedTransactionsListingReportWriterService The demergerRemovedTransactionsListingReportWriterService to set.
2250         */
2251        public void setDemergerRemovedTransactionsListingReportWriterService(ReportWriterService demergerRemovedTransactionsListingReportWriterService) {
2252            this.demergerRemovedTransactionsListingReportWriterService = demergerRemovedTransactionsListingReportWriterService;
2253        }
2254    
2255        /**
2256         * Sets the demergerReportWriterService attribute value.
2257         * @param demergerReportWriterService The demergerReportWriterService to set.
2258         */
2259        public void setDemergerReportWriterService(ReportWriterService demergerReportWriterService) {
2260            this.demergerReportWriterService = demergerReportWriterService;
2261        }
2262    
2263        /**
2264         * Sets the preScrubberService attribute value.
2265         * @param preScrubberService The preScrubberService to set.
2266         */
2267        public void setPreScrubberService(PreScrubberService preScrubberService) {
2268            this.preScrubberService = preScrubberService;
2269        }
2270    
2271        /**
2272         * Sets the parameterService attribute value.
2273         * @param parameterService The parameterService to set.
2274         */
2275        public void setParameterService(ParameterService parameterService) {
2276            this.parameterService = parameterService;
2277        }
2278    
2279        /**
2280         * Sets the runDateService attribute value.
2281         * @param runDateService The runDateService to set.
2282         */
2283        public void setRunDateService(RunDateService runDateService) {
2284            this.runDateService = runDateService;
2285        }
2286    
2287        /**
2288         * Gets the flexibleOffsetAccountService attribute. 
2289         * @return Returns the flexibleOffsetAccountService.
2290         */
2291        public FlexibleOffsetAccountService getFlexibleOffsetAccountService() {
2292            return flexibleOffsetAccountService;
2293        }
2294    
2295        /**
2296         * Gets the dateTimeService attribute. 
2297         * @return Returns the dateTimeService.
2298         */
2299        public DateTimeService getDateTimeService() {
2300            return dateTimeService;
2301        }
2302    
2303        /**
2304         * Gets the configurationService attribute. 
2305         * @return Returns the configurationService.
2306         */
2307        public KualiConfigurationService getConfigurationService() {
2308            return configurationService;
2309        }
2310    
2311        /**
2312         * Gets the persistenceService attribute. 
2313         * @return Returns the persistenceService.
2314         */
2315        public PersistenceService getPersistenceService() {
2316            return persistenceService;
2317        }
2318    
2319        /**
2320         * Gets the scrubberValidator attribute. 
2321         * @return Returns the scrubberValidator.
2322         */
2323        public ScrubberValidator getScrubberValidator() {
2324            return scrubberValidator;
2325        }
2326    
2327        /**
2328         * Gets the runDateService attribute. 
2329         * @return Returns the runDateService.
2330         */
2331        public RunDateService getRunDateService() {
2332            return runDateService;
2333        }
2334    
2335        /**
2336         * Gets the accountingCycleCachingService attribute. 
2337         * @return Returns the accountingCycleCachingService.
2338         */
2339        public AccountingCycleCachingService getAccountingCycleCachingService() {
2340            return accountingCycleCachingService;
2341        }
2342    
2343        /**
2344         * Gets the scrubberReportWriterService attribute. 
2345         * @return Returns the scrubberReportWriterService.
2346         */
2347        public DocumentNumberAwareReportWriterService getScrubberReportWriterService() {
2348            return scrubberReportWriterService;
2349        }
2350    
2351        /**
2352         * Gets the scrubberLedgerReportWriterService attribute. 
2353         * @return Returns the scrubberLedgerReportWriterService.
2354         */
2355        public DocumentNumberAwareReportWriterService getScrubberLedgerReportWriterService() {
2356            return scrubberLedgerReportWriterService;
2357        }
2358    
2359        /**
2360         * Gets the scrubberListingReportWriterService attribute. 
2361         * @return Returns the scrubberListingReportWriterService.
2362         */
2363        public DocumentNumberAwareReportWriterService getScrubberListingReportWriterService() {
2364            return scrubberListingReportWriterService;
2365        }
2366    
2367        /**
2368         * Gets the scrubberBadBalanceListingReportWriterService attribute. 
2369         * @return Returns the scrubberBadBalanceListingReportWriterService.
2370         */
2371        public ReportWriterService getScrubberBadBalanceListingReportWriterService() {
2372            return scrubberBadBalanceListingReportWriterService;
2373        }
2374    
2375        /**
2376         * Gets the demergerRemovedTransactionsListingReportWriterService attribute. 
2377         * @return Returns the demergerRemovedTransactionsListingReportWriterService.
2378         */
2379        public ReportWriterService getDemergerRemovedTransactionsListingReportWriterService() {
2380            return demergerRemovedTransactionsListingReportWriterService;
2381        }
2382    
2383        /**
2384         * Gets the demergerReportWriterService attribute. 
2385         * @return Returns the demergerReportWriterService.
2386         */
2387        public ReportWriterService getDemergerReportWriterService() {
2388            return demergerReportWriterService;
2389        }
2390    
2391        /**
2392         * Gets the preScrubberService attribute. 
2393         * @return Returns the preScrubberService.
2394         */
2395        public PreScrubberService getPreScrubberService() {
2396            return preScrubberService;
2397        }
2398    
2399        /**
2400         * Gets the parameterService attribute. 
2401         * @return Returns the parameterService.
2402         */
2403        public ParameterService getParameterService() {
2404            return parameterService;
2405        }
2406    
2407        /**
2408         * Sets the preScrubberReportWriterService attribute value.
2409         * @param preScrubberReportWriterService The preScrubberReportWriterService to set.
2410         */
2411        public void setPreScrubberReportWriterService(DocumentNumberAwareReportWriterService preScrubberReportWriterService) {
2412            this.preScrubberReportWriterService = preScrubberReportWriterService;
2413        }
2414    
2415        /**
2416         * Sets the businessObjectService attribute value.
2417         * @param businessObjectService The businessObjectService to set.
2418         */
2419        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
2420            this.businessObjectService = businessObjectService;
2421        }
2422        
2423    }