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 }