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.File;
019import java.io.IOException;
020import java.io.PrintStream;
021import java.sql.Date;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Iterator;
025import java.util.LinkedHashMap;
026import java.util.Map;
027
028import org.apache.commons.lang.StringUtils;
029import org.kuali.ole.gl.GeneralLedgerConstants;
030import org.kuali.ole.gl.businessobject.Entry;
031import org.kuali.ole.gl.businessobject.OriginEntryFull;
032import org.kuali.ole.gl.businessobject.OriginEntryInformation;
033import org.kuali.ole.gl.businessobject.PendingEntrySummary;
034import org.kuali.ole.gl.report.LedgerSummaryReport;
035import org.kuali.ole.gl.service.NightlyOutService;
036import org.kuali.ole.gl.service.OriginEntryGroupService;
037import org.kuali.ole.gl.service.OriginEntryService;
038import org.kuali.ole.sys.OLEConstants;
039import org.kuali.ole.sys.OLEPropertyConstants;
040import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntry;
041import org.kuali.ole.sys.service.GeneralLedgerPendingEntryService;
042import org.kuali.ole.sys.service.ReportWriterService;
043import org.kuali.rice.core.api.datetime.DateTimeService;
044import org.kuali.rice.core.api.util.type.KualiDecimal;
045import org.kuali.rice.core.web.format.CurrencyFormatter;
046import org.kuali.rice.kns.service.DataDictionaryService;
047import org.springframework.transaction.annotation.Transactional;
048
049/**
050 * This class implements the nightly out batch job.
051 */
052@Transactional
053public class NightlyOutServiceImpl implements NightlyOutService {
054    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(NightlyOutServiceImpl.class);
055
056    private GeneralLedgerPendingEntryService generalLedgerPendingEntryService;
057    private OriginEntryService originEntryService;
058    private DateTimeService dateTimeService;
059    private OriginEntryGroupService originEntryGroupService;
060    private String batchFileDirectoryName;
061    private ReportWriterService pendingEntryListReportWriterService;
062    private ReportWriterService pendingEntrySummaryReportWriterService;
063    private DataDictionaryService dataDictionaryService;
064    
065    /**
066     * Constructs a NightlyOutServiceImpl instance
067     */
068    public NightlyOutServiceImpl() {
069    }
070
071    /**
072     * Deletes all the pending general ledger entries that have now been copied to origin entries
073     * @see org.kuali.ole.gl.service.NightlyOutService#deleteCopiedPendingLedgerEntries()
074     */
075    public void deleteCopiedPendingLedgerEntries() {
076        LOG.debug("deleteCopiedPendingLedgerEntries() started");
077
078        generalLedgerPendingEntryService.deleteByFinancialDocumentApprovedCode(OLEConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.PROCESSED);
079    }
080
081    /**
082     * Copies the approved pending ledger entries to origin entry table and generates a report
083     * @see org.kuali.ole.gl.service.NightlyOutService#copyApprovedPendingLedgerEntries()
084     */
085    public void copyApprovedPendingLedgerEntries() {
086        if (LOG.isInfoEnabled()) {
087            LOG.info("copyApprovedPendingLedgerEntries() started");
088        }
089        Date today = new Date(dateTimeService.getCurrentTimestamp().getTime());
090        
091        Iterator pendingEntries = generalLedgerPendingEntryService.findApprovedPendingLedgerEntries();
092        String outputFile = batchFileDirectoryName + File.separator + GeneralLedgerConstants.BatchFileSystem.NIGHTLY_OUT_FILE + GeneralLedgerConstants.BatchFileSystem.EXTENSION ;
093        PrintStream outputFilePs = null;
094        
095        try {
096            outputFilePs  = new PrintStream(outputFile);
097        }
098        catch (IOException ioe) {
099            throw new RuntimeException("Cannot open output file "+outputFile+" for writing", ioe);
100        }
101        
102        EntryListReport entryListReport = new EntryListReport();
103        LedgerSummaryReport nightlyOutLedgerSummaryReport = new LedgerSummaryReport();
104        
105        Collection<OriginEntryFull> group = new ArrayList<OriginEntryFull>();
106        while (pendingEntries.hasNext()) {
107            // get one pending entry
108            GeneralLedgerPendingEntry pendingEntry = (GeneralLedgerPendingEntry) pendingEntries.next();
109            
110            OriginEntryFull entry = new OriginEntryFull(pendingEntry);
111            
112            // write entry to reports
113            entryListReport.writeEntry(entry, pendingEntryListReportWriterService);
114            nightlyOutLedgerSummaryReport.summarizeEntry(entry);
115
116            group.add(entry);
117            
118            // copy the pending entry to text file
119                outputFilePs.printf("%s\n", entry.getLine());
120
121            // update the pending entry to indicate it has been copied
122            pendingEntry.setFinancialDocumentApprovedCode(OLEConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.PROCESSED);
123            pendingEntry.setTransactionDate(today);
124            
125            generalLedgerPendingEntryService.save(pendingEntry);
126        }
127        
128        outputFilePs.close();
129        
130        //create done file    
131        String doneFileName = outputFile.replace(GeneralLedgerConstants.BatchFileSystem.EXTENSION, GeneralLedgerConstants.BatchFileSystem.DONE_FILE_EXTENSION);
132        File doneFile = new File (doneFileName);
133        if (!doneFile.exists()){
134            try {
135                doneFile.createNewFile();
136            } catch (IOException e) {
137                throw new RuntimeException();
138            }
139        }
140        
141        // finish writing reports
142        entryListReport.writeReportFooter(pendingEntryListReportWriterService);
143        nightlyOutLedgerSummaryReport.writeReport(pendingEntrySummaryReportWriterService);
144    }
145    
146    
147
148    public void setGeneralLedgerPendingEntryService(GeneralLedgerPendingEntryService generalLedgerPendingEntryService) {
149        this.generalLedgerPendingEntryService = generalLedgerPendingEntryService;
150    }
151
152    public void setOriginEntryService(OriginEntryService originEntryService) {
153        this.originEntryService = originEntryService;
154    }
155
156    public void setOriginEntryGroupService(OriginEntryGroupService originEntryGroupService) {
157        this.originEntryGroupService = originEntryGroupService;
158    }
159
160    public void setDateTimeService(DateTimeService dateTimeService) {
161        this.dateTimeService = dateTimeService;
162    }
163
164    public void setBatchFileDirectoryName(String batchFileDirectoryName) {
165        this.batchFileDirectoryName = batchFileDirectoryName;
166    }
167    
168    /**
169     * Gets the pendingEntryListReportWriterService attribute. 
170     * @return Returns the pendingEntryListReportWriterService.
171     */
172    public ReportWriterService getPendingEntryListReportWriterService() {
173        return pendingEntryListReportWriterService;
174    }
175
176    /**
177     * Sets the pendingEntryListReportWriterService attribute value.
178     * @param pendingEntryListReportWriterService The pendingEntryListReportWriterService to set.
179     */
180    public void setPendingEntryListReportWriterService(ReportWriterService pendingEntryListReportWriterService) {
181        this.pendingEntryListReportWriterService = pendingEntryListReportWriterService;
182    }
183
184    /**
185     * Gets the pendingEntrySummaryReportWriterService attribute. 
186     * @return Returns the pendingEntrySummaryReportWriterService.
187     */
188    public ReportWriterService getPendingEntrySummaryReportWriterService() {
189        return pendingEntrySummaryReportWriterService;
190    }
191
192    /**
193     * Sets the pendingEntrySummaryReportWriterService attribute value.
194     * @param pendingEntrySummaryReportWriterService The pendingEntrySummaryReportWriterService to set.
195     */
196    public void setPendingEntrySummaryReportWriterService(ReportWriterService pendingEntrySummaryReportWriterService) {
197        this.pendingEntrySummaryReportWriterService = pendingEntrySummaryReportWriterService;
198    }
199
200    /**
201     * Gets the dataDictionaryService attribute. 
202     * @return Returns the dataDictionaryService.
203     */
204    public DataDictionaryService getDataDictionaryService() {
205        return dataDictionaryService;
206    }
207
208    /**
209     * Sets the dataDictionaryService attribute value.
210     * @param dataDictionaryService The dataDictionaryService to set.
211     */
212    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
213        this.dataDictionaryService = dataDictionaryService;
214    }
215
216    /**
217     * A helper class which writes out the nightly out entry list report
218     */
219    protected class EntryListReport {
220        private PendingEntrySummary pendingEntrySummary;
221        private EntryReportTotalLine totalLine;
222        private Map<String, EntryReportDocumentTypeTotalLine> documentTypeTotals;
223        private EntryReportDocumentNumberTotalLine documentNumberTotal;
224        private int entryCount = 0;
225        private String suppressKey = "";
226        
227        /**
228         * Constructs a NightlyOutServiceImpl
229         */
230        public EntryListReport() {
231            pendingEntrySummary = new PendingEntrySummary();
232            totalLine = new EntryReportTotalLine();
233            documentTypeTotals = new LinkedHashMap<String, EntryReportDocumentTypeTotalLine>();
234        }
235        
236        /**
237         * Writes an entry to the list report
238         * @param entry the entry to write
239         * @param reportWriterService the reportWriterService to write the entry to
240         */
241        public void writeEntry(OriginEntryInformation entry, ReportWriterService reportWriterService) {
242            pendingEntrySummary.setOriginEntry(entry);
243            if (pendingEntrySummary.getSuppressableFieldsAsKey().equals(suppressKey)) {
244                pendingEntrySummary.suppressCommonFields(true);
245            }
246            else if (StringUtils.isNotBlank(suppressKey)) {
247                writeDocumentTotalLine(documentNumberTotal, reportWriterService);
248                documentNumberTotal = new EntryReportDocumentNumberTotalLine(pendingEntrySummary.getConstantDocumentNumber());
249            }
250            
251            if (StringUtils.isBlank(suppressKey)) {
252                documentNumberTotal = new EntryReportDocumentNumberTotalLine(pendingEntrySummary.getConstantDocumentNumber());
253                reportWriterService.writeTableHeader(pendingEntrySummary);
254            }
255            suppressKey = pendingEntrySummary.getSuppressableFieldsAsKey();
256            
257            reportWriterService.writeTableRow(pendingEntrySummary);
258            
259            addPendingEntryToDocumentType(pendingEntrySummary, documentTypeTotals);
260            addSummaryToTotal(pendingEntrySummary, documentNumberTotal);
261            addSummaryToTotal(pendingEntrySummary, totalLine);
262            entryCount += 1;
263        }
264        
265        /**
266         * Adds the given pending entry summary to the appropriate doc type's line total
267         * @param pendingEntrySummary the pending entry summary to add
268         * @param docTypeTotals the Map of doc type line total helpers to add the summary to
269         */
270        protected void addPendingEntryToDocumentType(PendingEntrySummary pendingEntrySummary, Map<String, EntryReportDocumentTypeTotalLine> docTypeTotals) {
271            EntryReportDocumentTypeTotalLine docTypeTotal = docTypeTotals.get(pendingEntrySummary.getConstantDocumentTypeCode());
272            if (docTypeTotal == null) {
273                docTypeTotal = new EntryReportDocumentTypeTotalLine(pendingEntrySummary.getConstantDocumentTypeCode());
274                docTypeTotals.put(pendingEntrySummary.getConstantDocumentTypeCode(), docTypeTotal);
275            }
276            addSummaryToTotal(pendingEntrySummary, docTypeTotal);
277        }
278        
279        
280        /**
281         * Adds the given summary to the correct credit, debit, or budget total in the total line
282         * @param pendingEntrySummary the summary to add
283         * @param totalLine the entry report total line which holds debit, credit, and budget sum totals
284         */
285        protected void addSummaryToTotal(PendingEntrySummary pendingEntrySummary, EntryReportTotalLine totalLine) {
286            if (pendingEntrySummary.getDebitAmount() != null) {
287                totalLine.addDebitAmount(pendingEntrySummary.getDebitAmount());
288            }
289            if (pendingEntrySummary.getCreditAmount() != null) {
290                totalLine.addCreditAmount(pendingEntrySummary.getCreditAmount());
291            }
292            if (pendingEntrySummary.getBudgetAmount() != null) {
293                totalLine.addBudgetAmount(pendingEntrySummary.getBudgetAmount());
294            }
295        }
296        
297        /**
298         * Writes totals for the document number we just finished writing out
299         * 
300         * @param documentNumberTotal EntryReportDocumentNumberTotalLine containing totals to write
301         * @param reportWriterService ReportWriterService for writing output to report
302         */
303        protected void writeDocumentTotalLine(EntryReportDocumentNumberTotalLine documentNumberTotal, ReportWriterService reportWriterService) {
304            final CurrencyFormatter formatter = new CurrencyFormatter();
305            final int amountLength = getDataDictionaryService().getAttributeMaxLength(Entry.class, OLEPropertyConstants.TRANSACTION_LEDGER_ENTRY_AMOUNT);
306            
307            reportWriterService.writeNewLines(1);
308            reportWriterService.writeFormattedMessageLine("                                          Total: %"+amountLength+"s %"+amountLength+"s %"+amountLength+"s", formatter.format(documentNumberTotal.getCreditAmount()), formatter.format(documentNumberTotal.getDebitAmount()), formatter.format(documentNumberTotal.getBudgetAmount()));
309            reportWriterService.writeNewLines(1);
310        }
311        
312        /**
313         * Completes the footer summary information for the report
314         * @param reportWriterService the reportWriterService to write the footer to
315         */
316        public void writeReportFooter(ReportWriterService reportWriterService) {
317            final CurrencyFormatter formatter = new CurrencyFormatter();
318            final int amountLength = getDataDictionaryService().getAttributeMaxLength(Entry.class, OLEPropertyConstants.TRANSACTION_LEDGER_ENTRY_AMOUNT);
319          
320            reportWriterService.writeNewLines(1);
321            for (String documentTypeCode : documentTypeTotals.keySet()) {
322                final EntryReportDocumentTypeTotalLine docTypeTotal = documentTypeTotals.get(documentTypeCode);
323                reportWriterService.writeFormattedMessageLine("       Totals for Document Type %4s Cnt %6d: %"+amountLength+"s %"+amountLength+"s %"+amountLength+"s",documentTypeCode, docTypeTotal.getEntryCount(), formatter.format(docTypeTotal.getCreditAmount()), formatter.format(docTypeTotal.getDebitAmount()), formatter.format(docTypeTotal.getBudgetAmount()));
324            }
325            
326            reportWriterService.writeNewLines(1);
327            reportWriterService.writeFormattedMessageLine("                        Grand Totals Cnt %6d: %"+amountLength+"s %"+amountLength+"s %"+amountLength+"s", new Integer(entryCount), formatter.format(totalLine.getCreditAmount()), formatter.format(totalLine.getDebitAmount()), formatter.format(totalLine.getBudgetAmount()));
328        }
329        
330        /**
331         * Summarizes entries for the pending entry view
332         */
333        protected class EntryReportTotalLine {
334            private KualiDecimal debitAmount = new KualiDecimal("0");
335            private KualiDecimal creditAmount = new KualiDecimal("0");
336            private KualiDecimal budgetAmount = new KualiDecimal("0");
337            
338            /**
339             * @return the debit total
340             */
341            public KualiDecimal getDebitAmount() {
342                return debitAmount;
343            }
344            
345            /**
346             * @return the credit total
347             */
348            public KualiDecimal getCreditAmount() {
349                return creditAmount;
350            }
351            
352            /**
353             * @return the budget total
354             */
355            public KualiDecimal getBudgetAmount() {
356                return budgetAmount;
357            }
358            
359            /**
360             * Adds the given amount to the debit total
361             * @param debitAmount the amount to add to the debit total
362             */
363            public void addDebitAmount(KualiDecimal debitAmount) {
364                this.debitAmount = this.debitAmount.add(debitAmount);
365            }
366            
367            /**
368             * Adds the given amount to the credit total
369             * @param creditAmount the amount to add to the credit total
370             */
371            public void addCreditAmount(KualiDecimal creditAmount) {
372                this.creditAmount = this.creditAmount.add(creditAmount);
373            }
374            
375            /**
376             * Adds the given amount to the budget total
377             * @param budgetAmount the amount to add to the budget total
378             */
379            public void addBudgetAmount(KualiDecimal budgetAmount) {
380                this.budgetAmount = this.budgetAmount.add(budgetAmount);
381            }
382        }
383        
384        /**
385         * Summarizes pending entry data per document type
386         */
387        protected class EntryReportDocumentTypeTotalLine extends EntryReportTotalLine {
388            private String documentTypeCode;
389            private int entryCount = 0;
390            
391            /**
392             * Constructs a NightlyOutServiceImpl
393             * @param documentTypeCode the document type code to 
394             */
395            public EntryReportDocumentTypeTotalLine(String documentTypeCode) {
396                this.documentTypeCode = documentTypeCode;
397            }
398            
399            /**
400             * @return the document type associated with this summarizer
401             */
402            public String getDocumentTypeCode() {
403                return this.documentTypeCode;
404            }
405            
406            /**
407             * @return the number of entries associated with the current document type
408             */
409            public int getEntryCount() {
410                return this.entryCount;
411            }
412
413            /**
414             * Overridden to automagically udpate the entry count
415             * @see org.kuali.ole.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addBudgetAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
416             */
417            @Override
418            public void addBudgetAmount(KualiDecimal budgetAmount) {
419                super.addBudgetAmount(budgetAmount);
420                entryCount += 1;
421            }
422
423            /**
424             * Overridden to automagically update the entry count
425             * @see org.kuali.ole.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addCreditAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
426             */
427            @Override
428            public void addCreditAmount(KualiDecimal creditAmount) {
429                super.addCreditAmount(creditAmount);
430                entryCount += 1;
431            }
432
433            /**
434             * Overridden to automagically update the entry count
435             * @see org.kuali.ole.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addDebitAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
436             */
437            @Override
438            public void addDebitAmount(KualiDecimal debitAmount) {
439                super.addDebitAmount(debitAmount);
440                entryCount += 1;
441            }
442        }
443        
444        /**
445         * Summarizes pending entry data per document number
446         */
447        protected class EntryReportDocumentNumberTotalLine extends EntryReportTotalLine {
448            private String documentNumber;
449            private int entryCount = 0;
450            
451            /**
452             * Constructs a NightlyOutServiceImpl
453             * @param documentNumber the document number to total
454             */
455            public EntryReportDocumentNumberTotalLine(String documentNumber) {
456                this.documentNumber = documentNumber;
457            }
458            
459            /**
460             * @return the document number associated with this summarizer
461             */
462            public String getDocumentNumber() {
463                return this.documentNumber;
464            }
465            
466            /**
467             * @return the number of entries associated with the current document number
468             */
469            public int getEntryCount() {
470                return this.entryCount;
471            }
472
473            /**
474             * Overridden to automagically udpate the entry count
475             * @see org.kuali.ole.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addBudgetAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
476             */
477            @Override
478            public void addBudgetAmount(KualiDecimal budgetAmount) {
479                super.addBudgetAmount(budgetAmount);
480                entryCount += 1;
481            }
482
483            /**
484             * Overridden to automagically update the entry count
485             * @see org.kuali.ole.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addCreditAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
486             */
487            @Override
488            public void addCreditAmount(KualiDecimal creditAmount) {
489                super.addCreditAmount(creditAmount);
490                entryCount += 1;
491            }
492
493            /**
494             * Overridden to automagically update the entry count
495             * @see org.kuali.ole.gl.batch.service.impl.NightlyOutServiceImpl.EntryReportTotalLine#addDebitAmount(org.kuali.rice.core.api.util.type.KualiDecimal)
496             */
497            @Override
498            public void addDebitAmount(KualiDecimal debitAmount) {
499                super.addDebitAmount(debitAmount);
500                entryCount += 1;
501            }
502        }
503    }
504}