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 }