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 }