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 017 package org.kuali.kfs.gl.document; 018 019 import java.io.File; 020 import java.io.FilenameFilter; 021 import java.text.DateFormat; 022 import java.text.SimpleDateFormat; 023 import java.util.ArrayList; 024 import java.util.Arrays; 025 import java.util.Collections; 026 import java.util.Date; 027 import java.util.Iterator; 028 import java.util.LinkedHashMap; 029 import java.util.List; 030 031 import org.apache.commons.lang.StringUtils; 032 import org.kuali.kfs.gl.GeneralLedgerConstants; 033 import org.kuali.kfs.gl.batch.CorrectionProcessScrubberStep; 034 import org.kuali.kfs.gl.businessobject.CorrectionChangeGroup; 035 import org.kuali.kfs.gl.document.service.CorrectionDocumentService; 036 import org.kuali.kfs.gl.service.OriginEntryGroupService; 037 import org.kuali.kfs.sys.KFSConstants; 038 import org.kuali.kfs.sys.KFSPropertyConstants; 039 import org.kuali.kfs.sys.batch.BatchSpringContext; 040 import org.kuali.kfs.sys.batch.Step; 041 import org.kuali.kfs.sys.context.ProxyUtils; 042 import org.kuali.kfs.sys.context.SpringContext; 043 import org.kuali.kfs.sys.document.AmountTotaling; 044 import org.kuali.kfs.sys.document.FinancialSystemTransactionalDocumentBase; 045 import org.kuali.rice.kew.dto.DocumentRouteLevelChangeDTO; 046 import org.kuali.rice.kew.dto.DocumentRouteStatusChangeDTO; 047 import org.kuali.rice.kns.service.DateTimeService; 048 import org.kuali.rice.kns.util.KualiDecimal; 049 import org.kuali.rice.kns.util.ObjectUtils; 050 import org.springframework.aop.framework.Advised; 051 import org.springframework.aop.support.AopUtils; 052 053 /** 054 * The General Ledger Correction Document, a document that allows editing and processing of origin entry groups and the origin 055 * entries within them. 056 */ 057 public class GeneralLedgerCorrectionProcessDocument extends FinancialSystemTransactionalDocumentBase implements AmountTotaling { 058 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(GeneralLedgerCorrectionProcessDocument.class); 059 060 protected String correctionTypeCode; // CorrectionDocumentService.CORRECTION_TYPE_MANUAL or 061 protected boolean correctionSelection; // false if all input rows should be in the output, true if only selected rows should be 062 // in the output 063 protected boolean correctionFileDelete; // false if the file should be processed by scrubber, true if the file should not be 064 // processed by scrubber 065 protected Integer correctionRowCount; // Row count in output group 066 protected KualiDecimal correctionDebitTotalAmount; // Debit amount total in output group 067 protected KualiDecimal correctionCreditTotalAmount; // Credit amount total in output group 068 protected KualiDecimal correctionBudgetTotalAmount; // Budget amount total in output group 069 protected String correctionInputFileName; // input file name 070 protected String correctionOutputFileName; // output file name 071 protected String correctionScriptText; // Not used 072 protected Integer correctionChangeGroupNextLineNumber; 073 074 protected List<CorrectionChangeGroup> correctionChangeGroup; 075 076 public GeneralLedgerCorrectionProcessDocument() { 077 super(); 078 correctionChangeGroupNextLineNumber = new Integer(0); 079 080 correctionChangeGroup = new ArrayList<CorrectionChangeGroup>(); 081 } 082 083 /** 084 * Returns a Map representation of the primary key of this document 085 * 086 * @return a Map that represents the database key of this document 087 * @see org.kuali.rice.kns.bo.BusinessObjectBase#toStringMapper() 088 */ 089 @Override 090 protected LinkedHashMap toStringMapper() { 091 LinkedHashMap m = new LinkedHashMap(); 092 m.put(KFSPropertyConstants.DOCUMENT_NUMBER, this.documentNumber); 093 return m; 094 } 095 096 /** 097 * Returns the editing method to use on the origin entries in the document, either "Manual Edit," "Using Criteria," "Remove 098 * Group from Processing," or "Not Available" 099 * 100 * @return the String representation of the method this document is using 101 */ 102 public String getMethod() { 103 if (CorrectionDocumentService.CORRECTION_TYPE_MANUAL.equals(correctionTypeCode)) { 104 return "Manual Edit"; 105 } 106 else if (CorrectionDocumentService.CORRECTION_TYPE_CRITERIA.equals(correctionTypeCode)) { 107 return "Using Criteria"; 108 } 109 else if (CorrectionDocumentService.CORRECTION_TYPE_REMOVE_GROUP_FROM_PROCESSING.equals(correctionTypeCode)) { 110 return "Remove Group from Processing"; 111 } 112 else { 113 return KFSConstants.NOT_AVAILABLE_STRING; 114 } 115 } 116 117 /** 118 * Returns the source of the origin entries this document uses: either an uploaded file of origin entries or the database 119 * 120 * @return a String with the name of the system in use 121 */ 122 public String getSystem() { 123 if (correctionInputFileName != null) { 124 return "File Upload"; 125 } 126 else { 127 return "Database"; 128 } 129 } 130 131 /** 132 * This method... 133 * 134 * @param ccg 135 */ 136 public void addCorrectionChangeGroup(CorrectionChangeGroup ccg) { 137 ccg.setDocumentNumber(documentNumber); 138 ccg.setCorrectionChangeGroupLineNumber(correctionChangeGroupNextLineNumber++); 139 correctionChangeGroup.add(ccg); 140 } 141 142 /** 143 * This method... 144 * 145 * @param changeNumber 146 */ 147 public void removeCorrectionChangeGroup(int changeNumber) { 148 for (Iterator iter = correctionChangeGroup.iterator(); iter.hasNext();) { 149 CorrectionChangeGroup element = (CorrectionChangeGroup) iter.next(); 150 if (changeNumber == element.getCorrectionChangeGroupLineNumber().intValue()) { 151 iter.remove(); 152 } 153 } 154 } 155 156 /** 157 * This method... 158 * 159 * @param groupNumber 160 * @return 161 */ 162 public CorrectionChangeGroup getCorrectionChangeGroupItem(int groupNumber) { 163 for (Iterator iter = correctionChangeGroup.iterator(); iter.hasNext();) { 164 CorrectionChangeGroup element = (CorrectionChangeGroup) iter.next(); 165 if (groupNumber == element.getCorrectionChangeGroupLineNumber().intValue()) { 166 return element; 167 } 168 } 169 170 CorrectionChangeGroup ccg = new CorrectionChangeGroup(documentNumber, groupNumber); 171 correctionChangeGroup.add(ccg); 172 173 return ccg; 174 } 175 176 177 public void doRouteStatusChange(DocumentRouteStatusChangeDTO statusChangeEvent) { 178 super.doRouteStatusChange(statusChangeEvent); 179 if (getDocumentHeader().getWorkflowDocument().stateIsProcessed()) { 180 if (LOG.isDebugEnabled()) { 181 LOG.debug("GLCP Route status Change: " + statusChangeEvent); 182 } 183 CorrectionDocumentService correctionDocumentService = SpringContext.getBean(CorrectionDocumentService.class); 184 OriginEntryGroupService originEntryGroupService = SpringContext.getBean(OriginEntryGroupService.class); 185 186 String docId = getDocumentHeader().getDocumentNumber(); 187 GeneralLedgerCorrectionProcessDocument doc = correctionDocumentService.findByCorrectionDocumentHeaderId(docId); 188 189 String correctionType = doc.getCorrectionTypeCode(); 190 if (CorrectionDocumentService.CORRECTION_TYPE_REMOVE_GROUP_FROM_PROCESSING.equals(correctionType)) { 191 192 String dataFileName = doc.getCorrectionInputFileName(); 193 String doneFileName = dataFileName.replace(GeneralLedgerConstants.BatchFileSystem.EXTENSION, GeneralLedgerConstants.BatchFileSystem.DONE_FILE_EXTENSION); 194 originEntryGroupService.deleteFile(doneFileName); 195 196 } 197 else if (CorrectionDocumentService.CORRECTION_TYPE_MANUAL.equals(correctionType) 198 || CorrectionDocumentService.CORRECTION_TYPE_CRITERIA.equals(correctionType)) { 199 // KFSMI-5760 - apparently, this node can be executed more than once, which results in multiple 200 // files being created. We need to check for the existence of a file with the proper 201 // name pattern and abort the rest of this if found 202 synchronized ( CorrectionDocumentService.class ) { 203 if ( !checkForExistingOutputDocument( doc.getDocumentNumber() ) ) { 204 // save the output file to originEntry directory when correctionFileDelete is false 205 DateTimeService dateTimeService = SpringContext.getBean(DateTimeService.class); 206 Date today = dateTimeService.getCurrentDate(); 207 208 // generate output file and set file name 209 String outputFileName = ""; 210 if (!correctionFileDelete) { 211 outputFileName = correctionDocumentService.createOutputFileForProcessing(doc.getDocumentNumber(), today); 212 } 213 doc.setCorrectionOutputFileName(outputFileName); 214 Step step = BatchSpringContext.getStep(CorrectionProcessScrubberStep.STEP_NAME); 215 CorrectionProcessScrubberStep correctionStep = (CorrectionProcessScrubberStep) ProxyUtils.getTargetIfProxied(step); 216 correctionStep.setDocumentId(docId); 217 218 try { 219 step.execute(getClass().getName(), dateTimeService.getCurrentDate()); 220 } 221 catch (Exception e) { 222 LOG.error("GLCP scrubber encountered error:", e); 223 throw new RuntimeException("GLCP scrubber encountered error:", e); 224 } 225 226 correctionStep = (CorrectionProcessScrubberStep) ProxyUtils.getTargetIfProxied(step); 227 correctionStep.setDocumentId(null); 228 229 correctionDocumentService.generateCorrectionReport(this); 230 correctionDocumentService.aggregateCorrectionDocumentReports(this); 231 } else { 232 LOG.warn( "Attempt to re-process final GLCP operations for document: " + doc.getDocumentNumber() + " File with that document number already exists." ); 233 } 234 } 235 } 236 else { 237 LOG.error("GLCP doc " + doc.getDocumentNumber() + " has an unknown correction type code: " + correctionType); 238 } 239 } 240 } 241 242 /** 243 * Returns true if an existing document like "glcp_output.docNum" is found. 244 */ 245 protected boolean checkForExistingOutputDocument( String documentNumber ) { 246 CorrectionDocumentService correctionDocumentService = SpringContext.getBean(CorrectionDocumentService.class); 247 String[] filenamesFound = correctionDocumentService.findExistingCorrectionOutputFilesForDocument(documentNumber); 248 if ( LOG.isInfoEnabled() ) { 249 LOG.info( "Scanned for output files for document: " + documentNumber ); 250 LOG.info( "Files Found: " + Arrays.toString(filenamesFound)); 251 } 252 return filenamesFound != null && filenamesFound.length > 0; 253 } 254 255 256 /** 257 * Waits for the event of the route level changing to "Approve" and at that point, saving all the entries as origin entries in a 258 * newly created origin entry group, then scrubbing those entries 259 * 260 * @param cahnge a representation of the route level changed that just occurred 261 * @see org.kuali.rice.kns.document.DocumentBase#handleRouteLevelChange(org.kuali.rice.kew.clientapp.vo.DocumentRouteLevelChangeDTO) 262 */ 263 @Override 264 public void doRouteLevelChange(DocumentRouteLevelChangeDTO change) { 265 super.doRouteLevelChange(change); 266 } 267 268 /** 269 * Returns the total dollar amount associated with this document 270 * 271 * @return if credit total is zero, debit total, otherwise credit total 272 */ 273 public KualiDecimal getTotalDollarAmount() { 274 return getCorrectionCreditTotalAmount().add(getCorrectionDebitTotalAmount()); 275 } 276 277 /** 278 * Sets this document's document number, but also sets the document number on all children objects 279 * 280 * @param documentNumber the document number for this document 281 * @see org.kuali.rice.kns.document.DocumentBase#setDocumentNumber(java.lang.String) 282 */ 283 @Override 284 public void setDocumentNumber(String documentNumber) { 285 super.setDocumentNumber(documentNumber); 286 287 for (Iterator iter = correctionChangeGroup.iterator(); iter.hasNext();) { 288 CorrectionChangeGroup element = (CorrectionChangeGroup) iter.next(); 289 element.setDocumentNumber(documentNumber); 290 } 291 } 292 293 public String getCorrectionTypeCode() { 294 return correctionTypeCode; 295 } 296 297 public void setCorrectionTypeCode(String correctionTypeCode) { 298 this.correctionTypeCode = correctionTypeCode; 299 } 300 301 public boolean getCorrectionSelection() { 302 return correctionSelection; 303 } 304 305 public void setCorrectionSelection(boolean correctionSelection) { 306 this.correctionSelection = correctionSelection; 307 } 308 309 public boolean getCorrectionFileDelete() { 310 return correctionFileDelete; 311 } 312 313 public void setCorrectionFileDelete(boolean correctionFileDelete) { 314 this.correctionFileDelete = correctionFileDelete; 315 } 316 317 public Integer getCorrectionRowCount() { 318 return correctionRowCount; 319 } 320 321 public void setCorrectionRowCount(Integer correctionRowCount) { 322 this.correctionRowCount = correctionRowCount; 323 } 324 325 public Integer getCorrectionChangeGroupNextLineNumber() { 326 return correctionChangeGroupNextLineNumber; 327 } 328 329 public void setCorrectionChangeGroupNextLineNumber(Integer correctionChangeGroupNextLineNumber) { 330 this.correctionChangeGroupNextLineNumber = correctionChangeGroupNextLineNumber; 331 } 332 333 public KualiDecimal getCorrectionDebitTotalAmount() { 334 if (ObjectUtils.isNull(correctionDebitTotalAmount)) { 335 return KualiDecimal.ZERO; 336 } 337 338 return correctionDebitTotalAmount; 339 } 340 341 public void setCorrectionDebitTotalAmount(KualiDecimal correctionDebitTotalAmount) { 342 this.correctionDebitTotalAmount = correctionDebitTotalAmount; 343 } 344 345 public KualiDecimal getCorrectionCreditTotalAmount() { 346 if (ObjectUtils.isNull(correctionCreditTotalAmount)) { 347 return KualiDecimal.ZERO; 348 } 349 350 return correctionCreditTotalAmount; 351 } 352 353 public void setCorrectionCreditTotalAmount(KualiDecimal correctionCreditTotalAmount) { 354 this.correctionCreditTotalAmount = correctionCreditTotalAmount; 355 } 356 357 public KualiDecimal getCorrectionBudgetTotalAmount() { 358 return correctionBudgetTotalAmount; 359 } 360 361 public void setCorrectionBudgetTotalAmount(KualiDecimal correctionBudgetTotalAmount) { 362 this.correctionBudgetTotalAmount = correctionBudgetTotalAmount; 363 } 364 365 public String getCorrectionInputFileName() { 366 return correctionInputFileName; 367 } 368 369 public void setCorrectionInputFileName(String correctionInputFileName) { 370 this.correctionInputFileName = correctionInputFileName; 371 } 372 373 public String getCorrectionOutputFileName() { 374 return correctionOutputFileName; 375 } 376 377 public void setCorrectionOutputFileName(String correctionOutputFileName) { 378 this.correctionOutputFileName = correctionOutputFileName; 379 } 380 381 public List<CorrectionChangeGroup> getCorrectionChangeGroup() { 382 Collections.sort(correctionChangeGroup); 383 return correctionChangeGroup; 384 } 385 386 public void setCorrectionChangeGroup(List<CorrectionChangeGroup> correctionChangeGroup) { 387 this.correctionChangeGroup = correctionChangeGroup; 388 } 389 390 protected String buildFileExtensionWithDate(Date date) { 391 String dateFormatStr = ".yyyy-MMM-dd.HH-mm-ss"; 392 DateFormat dateFormat = new SimpleDateFormat(dateFormatStr); 393 394 return dateFormat.format(date) + GeneralLedgerConstants.BatchFileSystem.EXTENSION; 395 396 397 } 398 }