001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.gl.service.impl;
017    
018    import java.io.IOException;
019    import java.io.PrintStream;
020    import java.util.ArrayList;
021    import java.util.Collection;
022    import java.util.HashMap;
023    import java.util.Iterator;
024    import java.util.LinkedHashMap;
025    import java.util.LinkedHashSet;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Set;
029    import java.util.TreeSet;
030    import java.util.Map.Entry;
031    
032    import org.apache.commons.collections.IteratorUtils;
033    import org.apache.commons.lang.StringUtils;
034    import org.kuali.kfs.coa.businessobject.Account;
035    import org.kuali.kfs.coa.service.AccountService;
036    import org.kuali.kfs.gl.GeneralLedgerConstants;
037    import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
038    import org.kuali.kfs.gl.report.PreScrubberReportData;
039    import org.kuali.kfs.gl.service.PreScrubberService;
040    import org.kuali.kfs.sys.KFSPropertyConstants;
041    import org.kuali.kfs.sys.KFSConstants.SystemGroupParameterNames;
042    import org.kuali.kfs.sys.context.SpringContext;
043    import org.kuali.kfs.sys.service.ReportWriterService;
044    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
045    import org.kuali.rice.kns.service.BusinessObjectService;
046    import org.kuali.rice.kns.service.DataDictionaryService;
047    import org.kuali.rice.kns.service.ParameterService;
048    import org.kuali.rice.kns.util.TransactionalServiceUtils;
049    
050    /**
051     * This class assumes that an account number may only belong to one chart code (i.e. as if the account number were the only primary key column of the account table)
052     * Based on that assumption, this code will attempt to fill in the chart code for an origin entry if it is blank and the account number is valid
053     */
054    public class PreScrubberServiceImpl implements PreScrubberService {
055        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PreScrubberServiceImpl.class);
056        
057        private int maxCacheSize = 10000;
058        private ParameterService parameterService;
059        
060        public PreScrubberReportData preprocessOriginEntries(Iterator<String> inputOriginEntries, String outputFileName) throws IOException {
061            PrintStream outputStream = new PrintStream(outputFileName);
062            
063            Map<String, String> chartCodeCache = new LinkedHashMap<String, String>() {
064                @Override
065                protected boolean removeEldestEntry(Entry<String, String> eldest) {
066                    return size() > getMaxCacheSize();
067                }
068            };
069            
070            Set<String> nonExistentAccountCache = new TreeSet<String>();
071            Set<String> multipleAccountCache = new TreeSet<String>();
072            
073            AccountService accountService = SpringContext.getBean(AccountService.class);
074            ParameterService parameterService = SpringContext.getBean(ParameterService.class);
075            boolean fillInChartCodesIfSpaces = deriveChartOfAccountsCodeIfSpaces();
076            
077            int inputLines = 0;
078            int outputLines = 0;
079            
080            try {
081                while (inputOriginEntries.hasNext()) {
082                    inputLines++;
083                    
084                    String originEntry = inputOriginEntries.next();
085                    String outputLine = originEntry;
086                    if (fillInChartCodesIfSpaces && originEntry.length() >= getExclusiveAccountNumberEndPosition()) {
087                        String chartOfAccountsCode = originEntry.substring(getInclusiveChartOfAccountsCodeStartPosition(), getExclusiveChartOfAccountsCodeEndPosition());
088                        if (GeneralLedgerConstants.getSpaceChartOfAccountsCode().equals(chartOfAccountsCode)) {
089                            // blank chart code... try to find the chart code
090                            String accountNumber = originEntry.substring(getInclusiveAccountNumberStartPosition(), getExclusiveAccountNumberEndPosition());
091                            if (StringUtils.isNotEmpty(accountNumber)) {
092                                String replacementChartOfAccountsCode = null;
093                                boolean nonExistent = false;
094                                boolean multipleFound = false;
095                                
096                                if (chartCodeCache.containsKey(accountNumber))
097                                    replacementChartOfAccountsCode = chartCodeCache.get(accountNumber);
098                                else if (nonExistentAccountCache.contains(accountNumber))
099                                    nonExistent = true;
100                                else if (multipleAccountCache.contains(accountNumber))
101                                    multipleFound = true;
102                                else {
103                                    Collection<Account> results = accountService.getAccountsForAccountNumber(accountNumber);
104                                    
105                                    if (results.isEmpty()) {
106                                        nonExistent = true;
107                                        nonExistentAccountCache.add(accountNumber);
108                                        LOG.warn("Could not find account record for account number " + accountNumber);
109                                    }
110                                    else {
111                                        Iterator<Account> accounts = results.iterator();
112                                        Account account = accounts.next();
113                                        if (accounts.hasNext()) {
114                                            LOG.warn("Multiple chart codes found for account number " + accountNumber + ", not filling in chart code for this account");
115                                            TransactionalServiceUtils.exhaustIterator(accounts);
116                                            multipleAccountCache.add(accountNumber);
117                                            multipleFound = true;
118                                        }
119                                        else {
120                                            replacementChartOfAccountsCode = account.getChartOfAccountsCode();
121                                            chartCodeCache.put(accountNumber, replacementChartOfAccountsCode);
122                                        }
123                                    }
124                                }
125                                
126                                if (!nonExistent && !multipleFound) {
127                                    StringBuilder buf = new StringBuilder(originEntry.length());
128                                    buf.append(originEntry.substring(0, getInclusiveChartOfAccountsCodeStartPosition()));
129                                    buf.append(replacementChartOfAccountsCode);
130                                    buf.append(originEntry.subSequence(getExclusiveChartOfAccountsCodeEndPosition(), originEntry.length()));
131                                    outputLine = buf.toString();
132                                }
133                            }
134                        }
135                    }
136                    outputStream.printf("%s\n", outputLine);
137                    outputLines++;
138                }
139            }
140            finally {
141                outputStream.close();
142            }
143            return new PreScrubberReportData(inputLines, outputLines, nonExistentAccountCache, multipleAccountCache);
144        }
145        
146        /**
147         * Returns the position of the chart of accounts code on an origin entry line
148         * @return
149         */
150        protected int getInclusiveChartOfAccountsCodeStartPosition() {
151            return 4;
152        }
153        
154        /**
155         * Returns the position of the end of the chart of accounts code on an origin entry line,   
156         * @return
157         */
158        protected int getExclusiveChartOfAccountsCodeEndPosition() {
159            return 6;
160        }
161        
162        /**
163         * Returns the position of the chart of accounts code on an origin entry line
164         * @return
165         */
166        protected int getInclusiveAccountNumberStartPosition() {
167            return 6;
168        }
169        
170        /**
171         * Returns the position of the end of the chart of accounts code on an origin entry line,   
172         * @return
173         */
174        protected int getExclusiveAccountNumberEndPosition() {
175            return 13;
176        }
177        
178        public int getMaxCacheSize() {
179            return maxCacheSize;
180        }
181    
182        public void setMaxCacheSize(int maxCacheSize) {
183            this.maxCacheSize = maxCacheSize;
184        }
185        
186        /**
187         * @return
188         */
189        public boolean deriveChartOfAccountsCodeIfSpaces() {
190            return !parameterService.getIndicatorParameter(KfsParameterConstants.FINANCIAL_SYSTEM_ALL.class, SystemGroupParameterNames.ACCOUNTS_CAN_CROSS_CHARTS_IND);
191        }
192    
193        /**
194         * Sets the parameterService attribute value.
195         * @param parameterService The parameterService to set.
196         */
197        public void setParameterService(ParameterService parameterService) {
198            this.parameterService = parameterService;
199        }
200    }