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