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    }