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.fp.document.service.impl;
018    
019    import java.io.ByteArrayOutputStream;
020    import java.io.File;
021    import java.io.IOException;
022    import java.io.OutputStream;
023    
024    import org.apache.commons.logging.Log;
025    import org.apache.commons.logging.LogFactory;
026    import org.kuali.kfs.fp.businessobject.Check;
027    import org.kuali.kfs.fp.document.CashReceiptDocument;
028    import org.kuali.kfs.fp.document.service.CashReceiptCoverSheetService;
029    import org.kuali.rice.kns.service.DataDictionaryService;
030    import org.kuali.rice.kns.service.DocumentHelperService;
031    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
032    
033    import com.lowagie.text.Document;
034    import com.lowagie.text.DocumentException;
035    import com.lowagie.text.Rectangle;
036    import com.lowagie.text.pdf.AcroFields;
037    import com.lowagie.text.pdf.BaseFont;
038    import com.lowagie.text.pdf.PdfContentByte;
039    import com.lowagie.text.pdf.PdfImportedPage;
040    import com.lowagie.text.pdf.PdfReader;
041    import com.lowagie.text.pdf.PdfStamper;
042    import com.lowagie.text.pdf.PdfWriter;
043    
044    /**
045     * Implementation of service for handling creation of the cover sheet of the <code>{@link CashReceiptDocument}</code>
046     */
047    public class CashReceiptCoverSheetServiceImpl implements CashReceiptCoverSheetService {
048        private static Log LOG = LogFactory.getLog(CashReceiptCoverSheetService.class);
049        
050        private DataDictionaryService dataDictionaryService;
051        private DocumentHelperService documentHelperService;
052    
053        public static final String CR_COVERSHEET_TEMPLATE_NM = "CashReceiptCoverSheetTemplate.pdf";
054    
055        private static final float LEFT_MARGIN = 45;
056        private static final float TOP_MARGIN = 45;
057        private static final float TOP_FIRST_PAGE = 440;
058    
059        private static final String DOCUMENT_NUMBER_FIELD = "DocumentNumber";
060        private static final String INITIATOR_FIELD = "Initiator";
061        private static final String CREATED_DATE_FIELD = "CreatedDate";
062        private static final String AMOUNT_FIELD = "Amount";
063        private static final String ORG_DOC_NUMBER_FIELD = "OrgDocNumber";
064        private static final String CAMPUS_FIELD = "Campus";
065        private static final String DEPOSIT_DATE_FIELD = "DepositDate";
066        private static final String DESCRIPTION_FIELD = "Description";
067        private static final String EXPLANATION_FIELD = "Explanation";
068        private static final String CHECKS_FIELD = "Checks";
069        private static final String CURRENCY_FIELD = "Currency";
070        private static final String COIN_FIELD = "Coin";
071        private static final String CREDIT_CARD_FIELD = "CreditCard";
072        private static final String ADV_DEPOSIT_FIELD = "AdvancedDeposit";
073        private static final String CHANGE_OUT_FIELD = "ChangeOut";
074        private static final String REVIV_FUND_OUT_FIELD = "RevivFundOut";
075    
076        private static final int FRONT_PAGE = 1;
077        private static final int CHECK_PAGE_NORMAL = 2;
078        private static final float CHECK_DETAIL_HEADING_HEIGHT = 45;
079        private static final float CHECK_LINE_SPACING = 12;
080        private static final float CHECK_FIELD_MARGIN = 12;
081        private static final float CHECK_NORMAL_FIELD_LENGTH = 100;
082        private static final float CHECK_FIELD_HEIGHT = 10;
083        private static final int MAX_CHECKS_FIRST_PAGE = 30;
084        private static final int MAX_CHECKS_NORMAL = 65;
085    
086        private static final float CHECK_HEADER_HEIGHT = 12;
087        private static final String CHECK_NUMBER_FIELD_PREFIX = "CheckNumber";
088        private static final float CHECK_NUMBER_FIELD_POSITION = LEFT_MARGIN;
089    
090        private static final String CHECK_DATE_FIELD_PREFIX = "CheckDate";
091        private static final float CHECK_DATE_FIELD_POSITION = CHECK_NUMBER_FIELD_POSITION + CHECK_NORMAL_FIELD_LENGTH + CHECK_FIELD_MARGIN;
092    
093        private static final String CHECK_DESCRIPTION_FIELD_PREFIX = "CheckDescription";
094        private static final float CHECK_DESCRIPTION_FIELD_POSITION = CHECK_DATE_FIELD_POSITION + CHECK_NORMAL_FIELD_LENGTH + CHECK_FIELD_MARGIN;
095        private static final float CHECK_DESCRIPTION_FIELD_LENGTH = 250;
096    
097        private static final String CHECK_AMOUNT_FIELD_PREFIX = "CheckAmount";
098        private static final float CHECK_AMOUNT_FIELD_POSITION = CHECK_DESCRIPTION_FIELD_POSITION + CHECK_DESCRIPTION_FIELD_LENGTH + CHECK_FIELD_MARGIN;
099    
100        private float _yPos;
101    
102    
103        /**
104         * This method determines if cover sheet printing is allowed by reviewing the CashReceiptDocumentRule to see if the 
105         * cover sheet is printable.
106         * 
107         * @param crDoc The document the cover sheet is being printed for.
108         * @return True if the cover sheet is printable, false otherwise.
109         * 
110         * @see org.kuali.kfs.fp.document.service.CashReceiptCoverSheetService#isCoverSheetPrintingAllowed(org.kuali.kfs.fp.document.CashReceiptDocument)
111         * @see org.kuali.kfs.fp.document.validation.impl.CashReceiptDocumentRule#isCoverSheetPrintable(org.kuali.kfs.fp.document.CashReceiptFamilyBase)
112         */
113        public boolean isCoverSheetPrintingAllowed(CashReceiptDocument crDoc) {
114            KualiWorkflowDocument workflowDocument = crDoc.getDocumentHeader().getWorkflowDocument();
115            return !(workflowDocument.stateIsCanceled() || workflowDocument.stateIsInitiated() || workflowDocument.stateIsDisapproved() || workflowDocument.stateIsException() || workflowDocument.stateIsDisapproved() || workflowDocument.stateIsSaved());
116        }
117        
118        /**
119         * Generate a cover sheet for the <code>{@link CashReceiptDocument}</code>. An <code>{@link OutputStream}</code> is written
120         * to for the cover sheet.
121         * 
122         * @param document The cash receipt document the cover sheet is for.
123         * @param searchPath The directory path to the template to be used to generate the cover sheet.
124         * @param returnStream The output stream the cover sheet will be written to.
125         * @exception DocumentException Thrown if the document provided is invalid, including null.
126         * @exception IOException Thrown if there is a problem writing to the output stream.
127         * @see org.kuali.rice.kns.module.financial.service.CashReceiptCoverSheetServiceImpl#generateCoverSheet(
128         *      org.kuali.module.financial.documentCashReceiptDocument )
129         */
130        public void generateCoverSheet(CashReceiptDocument document, String searchPath, OutputStream returnStream) throws Exception {
131    
132            if (isCoverSheetPrintingAllowed(document)) {
133                ByteArrayOutputStream stamperStream = new ByteArrayOutputStream();
134    
135                stampPdfFormValues(document, searchPath, stamperStream);
136                
137                PdfReader reader = new PdfReader(stamperStream.toByteArray());
138                Document pdfDoc = new Document(reader.getPageSize(FRONT_PAGE));
139                PdfWriter writer = PdfWriter.getInstance(pdfDoc, returnStream);
140    
141                pdfDoc.open();
142                populateCheckDetail(document, writer, reader);
143                pdfDoc.close();
144                writer.close();
145            }
146        }
147    
148        /**
149         * Use iText <code>{@link PdfStamper}</code> to stamp information from <code>{@link CashReceiptDocument}</code> into field
150         * values on a PDF Form Template.
151         * 
152         * @param document The cash receipt document the values will be pulled from.
153         * @param searchPath The directory path of the template to be used to generate the cover sheet.
154         * @param returnStream The output stream the cover sheet will be written to.
155         */
156        protected void stampPdfFormValues(CashReceiptDocument document, String searchPath, OutputStream returnStream) throws Exception {
157            String templateName = CR_COVERSHEET_TEMPLATE_NM;
158    
159            try {
160                // populate form with document values
161                PdfStamper stamper = new PdfStamper(new PdfReader(searchPath + File.separator + templateName), returnStream);
162                AcroFields populatedCoverSheet = stamper.getAcroFields();
163                
164                populatedCoverSheet.setField(DOCUMENT_NUMBER_FIELD, document.getDocumentNumber());
165                populatedCoverSheet.setField(INITIATOR_FIELD, document.getDocumentHeader().getWorkflowDocument().getInitiatorNetworkId());
166                populatedCoverSheet.setField(CREATED_DATE_FIELD, document.getDocumentHeader().getWorkflowDocument().getCreateDate().toString());
167                populatedCoverSheet.setField(AMOUNT_FIELD, document.getTotalDollarAmount().toString());
168                populatedCoverSheet.setField(ORG_DOC_NUMBER_FIELD, document.getDocumentHeader().getOrganizationDocumentNumber());
169                populatedCoverSheet.setField(CAMPUS_FIELD, document.getCampusLocationCode());
170                if (document.getDepositDate() != null) {
171                    // This value won't be set until the CR document is
172                    // deposited. A CR document is deposited only when it has
173                    // been associated with a Cash Management Document (CMD)
174                    // and with a Deposit within that CMD. And only when the
175                    // CMD is submitted and FINAL, will the CR documents
176                    // associated with it, be "deposited." So this value will
177                    // fill in at an arbitrarily later point in time. So your
178                    // code shouldn't expect it, but if it's there, then
179                    // display it.
180                    populatedCoverSheet.setField(DEPOSIT_DATE_FIELD, document.getDepositDate().toString());
181                }
182                populatedCoverSheet.setField(DESCRIPTION_FIELD, document.getDocumentHeader().getDocumentDescription());
183                populatedCoverSheet.setField(EXPLANATION_FIELD, document.getDocumentHeader().getExplanation());
184                populatedCoverSheet.setField(CHECKS_FIELD, document.getTotalCheckAmount().toString());
185                populatedCoverSheet.setField(CURRENCY_FIELD, document.getTotalCashAmount().toString());
186                populatedCoverSheet.setField(COIN_FIELD, document.getTotalCoinAmount().toString());
187                /*
188                 * Fields currently not used. Pulling them out. These are advanced features of the CR which will come during the
189                 * post-3/31 timeframe populatedCoverSheet.setField( CREDIT_CARD_FIELD, document.getDocumentNumber() );
190                 * populatedCoverSheet.setField( ADV_DEPOSIT_FIELD, document.getDocumentNumber() ); populatedCoverSheet.setField(
191                 * CHANGE_OUT_FIELD, document.getDocumentNumber() ); populatedCoverSheet.setField( REVIV_FUND_OUT_FIELD,
192                 * document.getDocumentNumber() );
193                 */
194    
195                stamper.setFormFlattening(true);
196                stamper.close();
197            }
198            catch (Exception e) {
199                LOG.error("Error creating coversheet for: " + document.getDocumentNumber() + ". ::" + e);
200                throw e;
201            }
202        }
203    
204        /**
205         * 
206         * This method writes the check number from the check provided to the PDF template.
207         * @param output The PDF output field the check number will be written to.
208         * @param check The check the check number will be retrieved from.
209         */
210        protected void writeCheckNumber(PdfContentByte output, Check check) {
211            writeCheckField(output, CHECK_NUMBER_FIELD_POSITION, check.getCheckNumber().toString());
212        }
213    
214        /**
215         * 
216         * This method writes the check date from the check provided to the PDF template.
217         * @param output The PDF output field the check date will be written to.
218         * @param check The check the check date will be retrieved from.
219         */
220        protected void writeCheckDate(PdfContentByte output, Check check) {
221            writeCheckField(output, CHECK_DATE_FIELD_POSITION, check.getCheckDate().toString());
222        }
223    
224        /**
225         * 
226         * This method writes the check description from the check provided to the PDF template.
227         * @param output The PDF output field the check description will be written to.
228         * @param check The check the check description will be retrieved from.
229         */
230        protected void writeCheckDescription(PdfContentByte output, Check check) {
231            writeCheckField(output, CHECK_DESCRIPTION_FIELD_POSITION, check.getDescription());
232        }
233    
234        /**
235         * 
236         * This method writes the check amount from the check provided to the PDF template.
237         * @param output The PDF output field the check amount will be written to.
238         * @param check The check the check amount will be retrieved from.
239         */
240        protected void writeCheckAmount(PdfContentByte output, Check check) {
241            writeCheckField(output, CHECK_AMOUNT_FIELD_POSITION, check.getAmount().toString());
242        }
243    
244        /**
245         * 
246         * This method writes out the value provided to the output provided and aligns the value outputted using the xPos float
247         * provided.
248         * @param output The content byte used to write out the field to the PDF template.
249         * @param xPos The x coordinate of the starting point on the document where the value will be written to.
250         * @param fieldValue The value to be written to the PDF cover sheet.
251         */
252        protected void writeCheckField(PdfContentByte output, float xPos, String fieldValue) {
253            output.beginText();
254            output.setTextMatrix(xPos, getCurrentRenderingYPosition());
255            output.newlineShowText(fieldValue);
256            output.endText();
257        }
258    
259        /**
260         * Read-only accessor for <code>{@link BaseFont}</code>. Used for creating the check detail information.  The font being 
261         * used is  Helvetica.
262         * 
263         * @return A BaseFont object used to identify what type of font is used on the cover sheet.
264         */
265        protected BaseFont getTextFont() throws DocumentException, IOException {
266            return BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
267        }
268    
269        /**
270         * Defines a state of Y position for the text.
271         * 
272         * @param y The y coordinate to be set.
273         */
274        protected void setCurrentRenderingYPosition(float y) {
275            _yPos = y;
276        }
277    
278        /**
279         * Defines a state of Y position for the text.
280         * 
281         * @return The current y coordinate.
282         */
283        protected float getCurrentRenderingYPosition() {
284            return _yPos;
285        }
286    
287        /**
288         * Method responsible for producing Check Detail section of the cover sheet. Not all Cash Receipt documents have checks.
289         * 
290         * @param crDoc The CashReceipt document the cover sheet is being created for.
291         * @param writer The output writer used to write the check data to the PDF file.
292         * @param reader The input reader used to read data from the PDF file.
293         */
294        protected void populateCheckDetail(CashReceiptDocument crDoc, PdfWriter writer, PdfReader reader) throws Exception {
295            PdfContentByte content;
296            ModifiableInteger pageNumber;
297            int checkCount = 0;
298            int maxCheckCount = MAX_CHECKS_FIRST_PAGE;
299    
300            pageNumber = new ModifiableInteger(0);
301            content = startNewPage(writer, reader, pageNumber);
302    
303            for (Check current : crDoc.getChecks()) {
304                writeCheckNumber(content, current);
305                writeCheckDate(content, current);
306                writeCheckDescription(content, current);
307                writeCheckAmount(content, current);
308                setCurrentRenderingYPosition(getCurrentRenderingYPosition() - CHECK_FIELD_HEIGHT);
309    
310                checkCount++;
311    
312                if (checkCount > maxCheckCount) {
313                    checkCount = 0;
314                    maxCheckCount = MAX_CHECKS_NORMAL;
315                    content = startNewPage(writer, reader, pageNumber);
316                }
317            }
318        }
319    
320        /**
321         * Responsible for creating a new PDF page and workspace through <code>{@link PdfContentByte}</code> for direct writing to the
322         * PDF.
323         * 
324         * @param writer The PDF writer used to write to the new page with.
325         * @param reader The PDF reader used to read information from the PDF file.
326         * @param pageNumber The current number of pages in the PDF file, which will be incremented by one inside this method.
327         * 
328         * @return The PDFContentByte used to access the new PDF page.
329         * @exception DocumentException
330         * @exception IOException
331         */
332        protected PdfContentByte startNewPage(PdfWriter writer, PdfReader reader, ModifiableInteger pageNumber) throws DocumentException, IOException {
333            PdfContentByte retval;
334            PdfContentByte under;
335            Rectangle pageSize;
336            Document pdfDoc;
337            PdfImportedPage newPage;
338    
339            pageNumber.increment();
340            pageSize = reader.getPageSize(FRONT_PAGE);
341            retval = writer.getDirectContent();
342            // under = writer.getDirectContentUnder();
343    
344            if (pageNumber.getInt() > FRONT_PAGE) {
345                newPage = writer.getImportedPage(reader, CHECK_PAGE_NORMAL);
346                setCurrentRenderingYPosition(pageSize.top(TOP_MARGIN + CHECK_DETAIL_HEADING_HEIGHT));
347            }
348            else {
349                newPage = writer.getImportedPage(reader, FRONT_PAGE);
350                setCurrentRenderingYPosition(pageSize.top(TOP_FIRST_PAGE));
351            }
352    
353            pdfDoc = retval.getPdfDocument();
354            pdfDoc.newPage();
355            retval.addTemplate(newPage, 0, 0);
356            retval.setFontAndSize(getTextFont(), 8);
357    
358            return retval;
359        }
360    
361        /**
362         * Gets the dataDictionaryService attribute. 
363         * @return Returns the dataDictionaryService.
364         */
365        public DataDictionaryService getDataDictionaryService() {
366            return dataDictionaryService;
367        }
368    
369        /**
370         * Sets the dataDictionaryService attribute value.
371         * @param dataDictionaryService The dataDictionaryService to set.
372         */
373        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
374            this.dataDictionaryService = dataDictionaryService;
375        }
376    
377        /**
378         * Gets the documentHelperService attribute. 
379         * @return Returns the documentHelperService.
380         */
381        public DocumentHelperService getDocumentHelperService() {
382            return documentHelperService;
383        }
384    
385        /**
386         * Sets the documentHelperService attribute value.
387         * @param documentHelperService The documentHelperService to set.
388         */
389        public void setDocumentHelperService(DocumentHelperService documentHelperService) {
390            this.documentHelperService = documentHelperService;
391        }
392    
393    }
394    
395    
396    
397    /**
398     * Utility class used to replace an <code>{@link Integer}</code> because an integer cannot be modified once it has been
399     * instantiated.
400     */
401    class ModifiableInteger {
402        int _value;
403    
404        /**
405         * 
406         * Constructs a ModifiableInteger object.
407         * @param val The initial value of the object.
408         */
409        public ModifiableInteger(Integer val) {
410            this(val.intValue());
411        }
412    
413        /**
414         * 
415         * Constructs a ModifiableInteger object.
416         * @param val The initial value of the object.
417         */
418        public ModifiableInteger(int val) {
419            setInt(val);
420        }
421    
422        /**
423         * 
424         * This method sets the local attribute to the value given.
425         * @param val The int value to be set.
426         */
427        public void setInt(int val) {
428            _value = val;
429        }
430    
431        /**
432         * 
433         * This method retrieves the value of the object.
434         * @return The int value of this object.
435         */
436        public int getInt() {
437            return _value;
438        }
439    
440        /**
441         * 
442         * This method increments the value of this class by one.
443         * @return An instance of this class with the value incremented by one.
444         */
445        public ModifiableInteger increment() {
446            _value++;
447            return this;
448        }
449    
450        /**
451         * 
452         * This method increments the value of this class by the amount specified.
453         * @param inc The amount the class value should be incremented by.
454         * @return An instance of this class with the value incremented by the amount specified.
455         */
456        public ModifiableInteger increment(int inc) {
457            _value += inc;
458            return this;
459        }
460    
461        /**
462         * 
463         * This method decrements the value of this class by one.
464         * @return An instance of this class with the value decremented by one.
465         */
466        public ModifiableInteger decrement() {
467            _value--;
468            return this;
469        }
470    
471        /**
472         * 
473         * This method decrements the value of this class by the amount specified.
474         * @param dec The amount the class value should be decremented by.
475         * @return An instance of this class with the value decremented by the amount specified.
476         */
477        public ModifiableInteger decrement(int dec) {
478            _value -= dec;
479            return this;
480        }
481    
482        /**
483         * 
484         * This method converts the value of this class and returns it as an Integer object.
485         * @return The value of this class formatted as an Integer.
486         */
487        public Integer getInteger() {
488            return new Integer(_value);
489        }
490    
491        /**
492         * This method generates and returns a String representation of this class.
493         * @return A string representation of this object.
494         * 
495         * @see java.lang.Object#toString()
496         */
497        @Override
498        public String toString() {
499            return getInteger().toString();
500        }
501    }