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