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 package org.kuali.kfs.fp.batch.service.impl; 017 018 import java.sql.Timestamp; 019 import java.text.ParseException; 020 import java.util.ArrayList; 021 import java.util.Collection; 022 import java.util.Date; 023 import java.util.HashSet; 024 import java.util.Iterator; 025 import java.util.List; 026 import java.util.Set; 027 028 import org.apache.commons.lang.StringUtils; 029 import org.apache.commons.lang.WordUtils; 030 import org.kuali.kfs.fp.batch.DvToPdpExtractStep; 031 import org.kuali.kfs.fp.batch.service.DisbursementVoucherExtractService; 032 import org.kuali.kfs.fp.businessobject.DisbursementVoucherNonEmployeeExpense; 033 import org.kuali.kfs.fp.businessobject.DisbursementVoucherNonEmployeeTravel; 034 import org.kuali.kfs.fp.businessobject.DisbursementVoucherPayeeDetail; 035 import org.kuali.kfs.fp.businessobject.DisbursementVoucherPreConferenceDetail; 036 import org.kuali.kfs.fp.businessobject.DisbursementVoucherPreConferenceRegistrant; 037 import org.kuali.kfs.fp.dataaccess.DisbursementVoucherDao; 038 import org.kuali.kfs.fp.document.DisbursementVoucherConstants; 039 import org.kuali.kfs.fp.document.DisbursementVoucherDocument; 040 import org.kuali.kfs.pdp.PdpConstants; 041 import org.kuali.kfs.pdp.PdpParameterConstants; 042 import org.kuali.kfs.pdp.businessobject.Batch; 043 import org.kuali.kfs.pdp.businessobject.CustomerProfile; 044 import org.kuali.kfs.pdp.businessobject.PaymentAccountDetail; 045 import org.kuali.kfs.pdp.businessobject.PaymentDetail; 046 import org.kuali.kfs.pdp.businessobject.PaymentGroup; 047 import org.kuali.kfs.pdp.businessobject.PaymentNoteText; 048 import org.kuali.kfs.pdp.service.CustomerProfileService; 049 import org.kuali.kfs.pdp.service.PaymentFileService; 050 import org.kuali.kfs.pdp.service.PaymentGroupService; 051 import org.kuali.kfs.pdp.service.PdpEmailService; 052 import org.kuali.kfs.sys.KFSConstants; 053 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry; 054 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper; 055 import org.kuali.kfs.sys.businessobject.SourceAccountingLine; 056 import org.kuali.kfs.sys.context.SpringContext; 057 import org.kuali.kfs.sys.document.service.FinancialSystemDocumentService; 058 import org.kuali.kfs.sys.document.validation.event.AccountingDocumentSaveWithNoLedgerEntryGenerationEvent; 059 import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService; 060 import org.kuali.kfs.sys.service.impl.KfsParameterConstants; 061 import org.kuali.kfs.vnd.businessobject.VendorDetail; 062 import org.kuali.kfs.vnd.document.service.VendorService; 063 import org.kuali.rice.kew.exception.WorkflowException; 064 import org.kuali.rice.kim.bo.Person; 065 import org.kuali.rice.kim.service.PersonService; 066 import org.kuali.rice.kns.service.BusinessObjectService; 067 import org.kuali.rice.kns.service.DateTimeService; 068 import org.kuali.rice.kns.service.DocumentService; 069 import org.kuali.rice.kns.service.ParameterEvaluator; 070 import org.kuali.rice.kns.service.ParameterService; 071 import org.kuali.rice.kns.util.KualiDecimal; 072 import org.kuali.rice.kns.util.KualiInteger; 073 import org.kuali.rice.kns.util.ObjectUtils; 074 import org.springframework.transaction.annotation.Transactional; 075 076 /** 077 * This is the default implementation of the DisbursementVoucherExtractService interface. 078 */ 079 @Transactional 080 public class DisbursementVoucherExtractServiceImpl implements DisbursementVoucherExtractService { 081 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DisbursementVoucherExtractServiceImpl.class); 082 083 private PersonService<Person> personService; 084 private ParameterService parameterService; 085 private DisbursementVoucherDao disbursementVoucherDao; 086 private DateTimeService dateTimeService; 087 private CustomerProfileService customerProfileService; 088 private PaymentFileService paymentFileService; 089 private PaymentGroupService paymentGroupService; 090 private BusinessObjectService businessObjectService; 091 private PdpEmailService paymentFileEmailService; 092 private int maxNoteLines; 093 094 // This should only be set to true when testing this system. Setting this to true will run the code but 095 // won't set the doc status to extracted 096 boolean testMode = false; 097 098 /** 099 * This method extracts all payments from a disbursement voucher with a status code of "A" and uploads them as a batch for 100 * processing. 101 * 102 * @return Always returns true if the method completes. 103 * @see org.kuali.kfs.fp.batch.service.DisbursementVoucherExtractService#extractPayments() 104 */ 105 public boolean extractPayments() { 106 LOG.debug("extractPayments() started"); 107 108 Date processRunDate = dateTimeService.getCurrentDate(); 109 110 String noteLines = parameterService.getParameterValue(KfsParameterConstants.PRE_DISBURSEMENT_ALL.class, PdpParameterConstants.MAX_NOTE_LINES); 111 112 try { 113 maxNoteLines = Integer.parseInt(noteLines); 114 } 115 catch (NumberFormatException nfe) { 116 throw new IllegalArgumentException("Invalid Max Notes Lines parameter"); 117 } 118 119 Person uuser = getPersonService().getPersonByPrincipalName(KFSConstants.SYSTEM_USER); 120 if (uuser == null) { 121 LOG.debug("extractPayments() Unable to find user " + KFSConstants.SYSTEM_USER); 122 throw new IllegalArgumentException("Unable to find user " + KFSConstants.SYSTEM_USER); 123 } 124 125 // Get a list of campuses that have documents with an 'A' (approved) status. 126 Set<String> campusList = getCampusListByDocumentStatusCode(DisbursementVoucherConstants.DocumentStatusCodes.APPROVED); 127 128 // Process each campus one at a time 129 for (String campusCode : campusList) { 130 extractPaymentsForCampus(campusCode, uuser, processRunDate); 131 } 132 133 return true; 134 } 135 136 /** 137 * This method extracts all outstanding payments from all the disbursement vouchers in approved status for a given campus and 138 * adds these payments to a batch file that is uploaded for processing. 139 * 140 * @param campusCode The id code of the campus the payments will be retrieved for. 141 * @param user The user object used when creating the batch file to upload with outstanding payments. 142 * @param processRunDate This is the date that the batch file is created, often this value will be today's date. 143 */ 144 protected void extractPaymentsForCampus(String campusCode, Person user, Date processRunDate) { 145 LOG.debug("extractPaymentsForCampus() started for campus: " + campusCode); 146 147 Batch batch = createBatch(campusCode, user, processRunDate); 148 Integer count = 0; 149 KualiDecimal totalAmount = KualiDecimal.ZERO; 150 151 Collection<DisbursementVoucherDocument> dvd = getListByDocumentStatusCodeCampus(DisbursementVoucherConstants.DocumentStatusCodes.APPROVED, campusCode); 152 for (DisbursementVoucherDocument document : dvd) { 153 addPayment(document, batch, processRunDate); 154 count++; 155 totalAmount = totalAmount.add(document.getDisbVchrCheckTotalAmount()); 156 } 157 158 batch.setPaymentCount(new KualiInteger(count)); 159 batch.setPaymentTotalAmount(totalAmount); 160 161 businessObjectService.save(batch); 162 paymentFileEmailService.sendLoadEmail(batch); 163 } 164 165 /** 166 * This method creates a payment group from the disbursement voucher and batch provided and persists that group to the database. 167 * 168 * @param document The document used to build a payment group detail. 169 * @param batch The batch file used to build a payment group and detail. 170 * @param processRunDate The date the batch file is to post. 171 */ 172 protected void addPayment(DisbursementVoucherDocument document, Batch batch, Date processRunDate) { 173 LOG.debug("addPayment() started"); 174 175 PaymentGroup pg = buildPaymentGroup(document, batch); 176 PaymentDetail pd = buildPaymentDetail(document, batch, processRunDate); 177 178 pd.setPaymentGroup(pg); 179 pg.addPaymentDetails(pd); 180 this.businessObjectService.save(pg); 181 182 if (!testMode) { 183 try { 184 document.getDocumentHeader().setFinancialDocumentStatusCode(DisbursementVoucherConstants.DocumentStatusCodes.EXTRACTED); 185 document.setExtractDate(new java.sql.Date(processRunDate.getTime())); 186 SpringContext.getBean(DocumentService.class).saveDocument(document, AccountingDocumentSaveWithNoLedgerEntryGenerationEvent.class); 187 } 188 catch (WorkflowException we) { 189 LOG.error("Could not save disbursement voucher document #" + document.getDocumentNumber() + ": " + we); 190 throw new RuntimeException(we); 191 } 192 } 193 } 194 195 /** 196 * This method creates a PaymentGroup from the disbursement voucher and batch provided. The values provided by the disbursement 197 * voucher are used to assign appropriate attributes to the payment group, including address and vendor detail information. The 198 * information added to the payment group includes tax encoding to identify if taxes should be taken out of the payment. The tax 199 * rules vary depending on the type of individual or entity being paid 200 * 201 * @param document The document to be used for retrieving the information about the vendor being paid. 202 * @param batch The batch that the payment group will be associated with. 203 * @return A PaymentGroup object fully populated with all the values necessary to make a payment. 204 */ 205 protected PaymentGroup buildPaymentGroup(DisbursementVoucherDocument document, Batch batch) { 206 LOG.debug("buildPaymentGroup() started"); 207 208 PaymentGroup pg = new PaymentGroup(); 209 pg.setBatch(batch); 210 pg.setCombineGroups(Boolean.TRUE); 211 pg.setCampusAddress(Boolean.FALSE); 212 213 DisbursementVoucherPayeeDetail pd = document.getDvPayeeDetail(); 214 String rc = pd.getDisbVchrPaymentReasonCode(); 215 216 // If the payee is an employee, set these flags accordingly 217 if ((document.getDvPayeeDetail().isVendor() && SpringContext.getBean(VendorService.class).isVendorInstitutionEmployee(pd.getDisbVchrVendorHeaderIdNumberAsInteger())) || document.getDvPayeeDetail().isEmployee()) { 218 pg.setEmployeeIndicator(Boolean.TRUE); 219 pg.setPayeeIdTypeCd(PdpConstants.PayeeIdTypeCodes.EMPLOYEE); 220 221 // All payments are taxable except research participant, rental & royalties 222 pg.setTaxablePayment( 223 !parameterService.getParameterEvaluator(DisbursementVoucherDocument.class, DisbursementVoucherConstants.RESEARCH_PAYMENT_REASONS_PARM_NM, rc).evaluationSucceeds() 224 && !DisbursementVoucherConstants.PaymentReasonCodes.RENTAL_PAYMENT.equals(rc) 225 && !DisbursementVoucherConstants.PaymentReasonCodes.ROYALTIES.equals(rc)); 226 } 227 // Payee is not an employee 228 else { 229 230 // These are taxable 231 VendorDetail vendDetail = SpringContext.getBean(VendorService.class).getVendorDetail(pd.getDisbVchrVendorHeaderIdNumberAsInteger(), pd.getDisbVchrVendorDetailAssignedIdNumberAsInteger()); 232 String vendorOwnerCode = vendDetail.getVendorHeader().getVendorOwnershipCode(); 233 String vendorOwnerCategoryCode = vendDetail.getVendorHeader().getVendorOwnershipCategoryCode(); 234 String payReasonCode = pd.getDisbVchrPaymentReasonCode(); 235 236 pg.setPayeeIdTypeCd(PdpConstants.PayeeIdTypeCodes.VENDOR_ID); 237 238 // Assume it is not taxable until proven otherwise 239 pg.setTaxablePayment(Boolean.FALSE); 240 pg.setPayeeOwnerCd(vendorOwnerCode); 241 242 ParameterEvaluator parameterEvaluator1 = this.parameterService.getParameterEvaluator(DvToPdpExtractStep.class, PdpParameterConstants.TAXABLE_PAYMENT_REASON_CODES_BY_OWNERSHIP_CODES_PARAMETER_NAME, PdpParameterConstants.NON_TAXABLE_PAYMENT_REASON_CODES_BY_OWNERSHIP_CODES_PARAMETER_NAME, vendorOwnerCode, payReasonCode); 243 ParameterEvaluator parameterEvaluator2 = this.parameterService.getParameterEvaluator(DvToPdpExtractStep.class, PdpParameterConstants.TAXABLE_PAYMENT_REASON_CODES_BY_CORPORATION_OWNERSHIP_TYPE_CATEGORY_PARAMETER_NAME, PdpParameterConstants.NON_TAXABLE_PAYMENT_REASON_CODES_BY_CORPORATION_OWNERSHIP_TYPE_CATEGORY_PARAMETER_NAME, vendorOwnerCategoryCode, payReasonCode); 244 245 if ( parameterEvaluator1.evaluationSucceeds() ) { 246 pg.setTaxablePayment(Boolean.TRUE); 247 } 248 else if (this.parameterService.getParameterValue(DvToPdpExtractStep.class, PdpParameterConstants.CORPORATION_OWNERSHIP_TYPE_PARAMETER_NAME).equals("CP") && 249 StringUtils.isEmpty(vendorOwnerCategoryCode) && 250 this.parameterService.getParameterEvaluator(DvToPdpExtractStep.class, PdpParameterConstants.TAXABLE_PAYMENT_REASON_CODES_FOR_BLANK_CORPORATION_OWNERSHIP_TYPE_CATEGORIES_PARAMETER_NAME, payReasonCode).evaluationSucceeds()) { 251 pg.setTaxablePayment(Boolean.TRUE); 252 } 253 else if (this.parameterService.getParameterValue(DvToPdpExtractStep.class, PdpParameterConstants.CORPORATION_OWNERSHIP_TYPE_PARAMETER_NAME).equals("CP") 254 && !StringUtils.isEmpty(vendorOwnerCategoryCode) 255 && parameterEvaluator2.evaluationSucceeds() ) { 256 pg.setTaxablePayment(Boolean.TRUE); 257 } 258 } 259 260 pg.setCity(pd.getDisbVchrPayeeCityName()); 261 pg.setCountry(pd.getDisbVchrPayeeCountryCode()); 262 pg.setLine1Address(pd.getDisbVchrPayeeLine1Addr()); 263 pg.setLine2Address(pd.getDisbVchrPayeeLine2Addr()); 264 pg.setPayeeName(pd.getDisbVchrPayeePersonName()); 265 pg.setPayeeId(pd.getDisbVchrPayeeIdNumber()); 266 pg.setState(pd.getDisbVchrPayeeStateCode()); 267 pg.setZipCd(pd.getDisbVchrPayeeZipCode()); 268 pg.setPaymentDate(document.getDisbursementVoucherDueDate()); 269 270 // It doesn't look like the DV has a way to do immediate processes 271 pg.setProcessImmediate(Boolean.FALSE); 272 pg.setPymtAttachment(document.isDisbVchrAttachmentCode()); 273 pg.setPymtSpecialHandling(document.isDisbVchrSpecialHandlingCode()); 274 pg.setNraPayment(pd.isDisbVchrAlienPaymentCode()); 275 276 pg.setBankCode(document.getDisbVchrBankCode()); 277 pg.setPaymentStatusCode(KFSConstants.PdpConstants.PAYMENT_OPEN_STATUS_CODE); 278 279 return pg; 280 } 281 282 /** 283 * This method builds a payment detail object from the disbursement voucher document provided and links that detail file to the 284 * batch and process run date given. 285 * 286 * @param document The disbursement voucher document to retrieve payment information from to populate the PaymentDetail. 287 * @param batch The batch file associated with the payment. 288 * @param processRunDate The date of the payment detail invoice. 289 * @return A fully populated PaymentDetail instance. 290 */ 291 protected PaymentDetail buildPaymentDetail(DisbursementVoucherDocument document, Batch batch, Date processRunDate) { 292 LOG.debug("buildPaymentDetail() started"); 293 294 PaymentDetail pd = new PaymentDetail(); 295 if (StringUtils.isNotEmpty(document.getDocumentHeader().getOrganizationDocumentNumber())) { 296 pd.setOrganizationDocNbr(document.getDocumentHeader().getOrganizationDocumentNumber()); 297 } 298 pd.setCustPaymentDocNbr(document.getDocumentNumber()); 299 pd.setInvoiceDate(new java.sql.Date(processRunDate.getTime())); 300 pd.setOrigInvoiceAmount(document.getDisbVchrCheckTotalAmount()); 301 pd.setInvTotDiscountAmount(KualiDecimal.ZERO); 302 pd.setInvTotOtherCreditAmount(KualiDecimal.ZERO); 303 pd.setInvTotOtherDebitAmount(KualiDecimal.ZERO); 304 pd.setInvTotShipAmount(KualiDecimal.ZERO); 305 pd.setNetPaymentAmount(document.getDisbVchrCheckTotalAmount()); 306 pd.setPrimaryCancelledPayment(Boolean.FALSE); 307 pd.setFinancialDocumentTypeCode(DisbursementVoucherConstants.DOCUMENT_TYPE_CHECKACH); 308 pd.setFinancialSystemOriginCode(KFSConstants.ORIGIN_CODE_KUALI); 309 310 // Handle accounts 311 for (Iterator iter = document.getSourceAccountingLines().iterator(); iter.hasNext();) { 312 SourceAccountingLine sal = (SourceAccountingLine) iter.next(); 313 314 PaymentAccountDetail pad = new PaymentAccountDetail(); 315 pad.setFinChartCode(sal.getChartOfAccountsCode()); 316 pad.setAccountNbr(sal.getAccountNumber()); 317 if (StringUtils.isNotEmpty(sal.getSubAccountNumber())) { 318 pad.setSubAccountNbr(sal.getSubAccountNumber()); 319 } 320 else { 321 pad.setSubAccountNbr(KFSConstants.getDashSubAccountNumber()); 322 } 323 pad.setFinObjectCode(sal.getFinancialObjectCode()); 324 if (StringUtils.isNotEmpty(sal.getFinancialSubObjectCode())) { 325 pad.setFinSubObjectCode(sal.getFinancialSubObjectCode()); 326 } 327 else { 328 pad.setFinSubObjectCode(KFSConstants.getDashFinancialSubObjectCode()); 329 } 330 if (StringUtils.isNotEmpty(sal.getOrganizationReferenceId())) { 331 pad.setOrgReferenceId(sal.getOrganizationReferenceId()); 332 } 333 if (StringUtils.isNotEmpty(sal.getProjectCode())) { 334 pad.setProjectCode(sal.getProjectCode()); 335 } 336 else { 337 pad.setProjectCode(KFSConstants.getDashProjectCode()); 338 } 339 pad.setAccountNetAmount(sal.getAmount()); 340 pd.addAccountDetail(pad); 341 } 342 343 // Handle notes 344 DisbursementVoucherPayeeDetail dvpd = document.getDvPayeeDetail(); 345 346 int line = 0; 347 PaymentNoteText pnt = new PaymentNoteText(); 348 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 349 pnt.setCustomerNoteText("Info: " + document.getDisbVchrContactPersonName() + " " + document.getDisbVchrContactPhoneNumber()); 350 pd.addNote(pnt); 351 352 String dvSpecialHandlingPersonName = null; 353 String dvSpecialHandlingLine1Address = null; 354 String dvSpecialHandlingLine2Address = null; 355 String dvSpecialHandlingCity = null; 356 String dvSpecialHandlingState = null; 357 String dvSpecialHandlingZip = null; 358 359 dvSpecialHandlingPersonName = dvpd.getDisbVchrSpecialHandlingPersonName(); 360 dvSpecialHandlingLine1Address = dvpd.getDisbVchrSpecialHandlingLine1Addr(); 361 dvSpecialHandlingLine2Address = dvpd.getDisbVchrSpecialHandlingLine2Addr(); 362 dvSpecialHandlingCity = dvpd.getDisbVchrSpecialHandlingCityName(); 363 dvSpecialHandlingState = dvpd.getDisbVchrSpecialHandlingStateCode(); 364 dvSpecialHandlingZip = dvpd.getDisbVchrSpecialHandlingZipCode(); 365 366 if (StringUtils.isNotEmpty(dvSpecialHandlingPersonName)) { 367 pnt = new PaymentNoteText(); 368 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 369 pnt.setCustomerNoteText("Send Check To: " + dvSpecialHandlingPersonName); 370 if (LOG.isDebugEnabled()) { 371 LOG.debug("Creating special handling person name note: "+pnt.getCustomerNoteText()); 372 } 373 pd.addNote(pnt); 374 } 375 if (StringUtils.isNotEmpty(dvSpecialHandlingLine1Address)) { 376 pnt = new PaymentNoteText(); 377 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 378 pnt.setCustomerNoteText(dvSpecialHandlingLine1Address); 379 if (LOG.isDebugEnabled()) { 380 LOG.debug("Creating special handling address 1 note: "+pnt.getCustomerNoteText()); 381 } 382 pd.addNote(pnt); 383 } 384 if (StringUtils.isNotEmpty(dvSpecialHandlingLine2Address)) { 385 pnt = new PaymentNoteText(); 386 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 387 pnt.setCustomerNoteText(dvSpecialHandlingLine2Address); 388 if (LOG.isDebugEnabled()) { 389 LOG.debug("Creating special handling address 2 note: "+pnt.getCustomerNoteText()); 390 } 391 pd.addNote(pnt); 392 } 393 if (StringUtils.isNotEmpty(dvSpecialHandlingCity)) { 394 pnt = new PaymentNoteText(); 395 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 396 pnt.setCustomerNoteText(dvSpecialHandlingCity + ", " + dvSpecialHandlingState + " " + dvSpecialHandlingZip); 397 if (LOG.isDebugEnabled()) { 398 LOG.debug("Creating special handling city note: "+pnt.getCustomerNoteText()); 399 } 400 pd.addNote(pnt); 401 } 402 if (document.isDisbVchrAttachmentCode()) { 403 pnt = new PaymentNoteText(); 404 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 405 pnt.setCustomerNoteText("Attachment Included"); 406 if (LOG.isDebugEnabled()) { 407 LOG.debug("create attachment note: "+pnt.getCustomerNoteText()); 408 } 409 pd.addNote(pnt); 410 } 411 412 String paymentReasonCode = dvpd.getDisbVchrPaymentReasonCode(); 413 if (parameterService.getParameterEvaluator(DisbursementVoucherDocument.class, DisbursementVoucherConstants.NONEMPLOYEE_TRAVEL_PAY_REASONS_PARM_NM, paymentReasonCode).evaluationSucceeds()) { 414 DisbursementVoucherNonEmployeeTravel dvnet = document.getDvNonEmployeeTravel(); 415 416 pnt = new PaymentNoteText(); 417 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 418 pnt.setCustomerNoteText("Reimbursement associated with " + dvnet.getDisbVchrServicePerformedDesc()); 419 if (LOG.isDebugEnabled()) { 420 LOG.debug("Creating non employee travel notes: "+pnt.getCustomerNoteText()); 421 } 422 pd.addNote(pnt); 423 424 pnt = new PaymentNoteText(); 425 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 426 pnt.setCustomerNoteText("The total per diem amount for your daily expenses is " + dvnet.getDisbVchrPerdiemCalculatedAmt()); 427 if (LOG.isDebugEnabled()) { 428 LOG.debug("Creating non employee travel notes: "+pnt.getCustomerNoteText()); 429 } 430 pd.addNote(pnt); 431 432 if (dvnet.getDisbVchrPersonalCarAmount() != null && dvnet.getDisbVchrPersonalCarAmount().compareTo(KualiDecimal.ZERO) != 0) { 433 pnt = new PaymentNoteText(); 434 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 435 pnt.setCustomerNoteText("The total dollar amount for your vehicle mileage is " + dvnet.getDisbVchrPersonalCarAmount()); 436 if (LOG.isDebugEnabled()) { 437 LOG.debug("Creating non employee travel vehicle note: "+pnt.getCustomerNoteText()); 438 } 439 pd.addNote(pnt); 440 441 for (Iterator iter = dvnet.getDvNonEmployeeExpenses().iterator(); iter.hasNext();) { 442 DisbursementVoucherNonEmployeeExpense exp = (DisbursementVoucherNonEmployeeExpense) iter.next(); 443 444 if (line < (maxNoteLines - 8)) { 445 pnt = new PaymentNoteText(); 446 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 447 pnt.setCustomerNoteText(exp.getDisbVchrExpenseCompanyName() + " " + exp.getDisbVchrExpenseAmount()); 448 if (LOG.isDebugEnabled()) { 449 LOG.debug("Creating non employee travel expense note: "+pnt.getCustomerNoteText()); 450 } 451 pd.addNote(pnt); 452 } 453 } 454 } 455 } 456 else if (parameterService.getParameterEvaluator(DisbursementVoucherDocument.class, DisbursementVoucherConstants.PREPAID_TRAVEL_PAYMENT_REASONS_PARM_NM, paymentReasonCode).evaluationSucceeds()) { 457 pnt = new PaymentNoteText(); 458 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 459 pnt.setCustomerNoteText("Payment is for the following individuals/charges:"); 460 pd.addNote(pnt); 461 if (LOG.isDebugEnabled()) { 462 LOG.info("Creating prepaid travel note note: "+pnt.getCustomerNoteText()); 463 } 464 465 DisbursementVoucherPreConferenceDetail dvpcd = document.getDvPreConferenceDetail(); 466 467 for (Iterator iter = dvpcd.getDvPreConferenceRegistrants().iterator(); iter.hasNext();) { 468 DisbursementVoucherPreConferenceRegistrant dvpcr = (DisbursementVoucherPreConferenceRegistrant) iter.next(); 469 470 if (line < (maxNoteLines - 8)) { 471 pnt = new PaymentNoteText(); 472 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 473 pnt.setCustomerNoteText(dvpcr.getDvConferenceRegistrantName() + " " + dvpcr.getDisbVchrExpenseAmount()); 474 if (LOG.isDebugEnabled()) { 475 LOG.debug("Creating pre-paid conference registrants note: "+pnt.getCustomerNoteText()); 476 } 477 pd.addNote(pnt); 478 } 479 } 480 } 481 482 // Get the original, raw form, note text from the DV document. 483 String text = document.getDisbVchrCheckStubText(); 484 if (text != null && text.length() > 0) { 485 486 // The WordUtils should be sufficient for the majority of cases. This method will 487 // word wrap the whole string based on the MAX_NOTE_LINE_SIZE, separating each wrapped 488 // word by a newline character. The 'wrap' method adds line feeds to the end causing 489 // the character length to exceed the max length by 1, hence the need for the replace 490 // method before splitting. 491 String wrappedText = WordUtils.wrap(text, DisbursementVoucherConstants.MAX_NOTE_LINE_SIZE); 492 String[] noteLines = wrappedText.replaceAll("[\r]", "").split("\\n"); 493 494 // Loop through all the note lines. 495 for (String noteLine : noteLines) { 496 if (line < (maxNoteLines - 3) && !StringUtils.isEmpty(noteLine)) { 497 498 // This should only happen if we encounter a word that is greater than the max length. 499 // The only concern I have for this occurring is with URLs/email addresses. 500 if (noteLine.length() > DisbursementVoucherConstants.MAX_NOTE_LINE_SIZE) { 501 for (String choppedWord : chopWord(noteLine, DisbursementVoucherConstants.MAX_NOTE_LINE_SIZE)) { 502 503 // Make sure we're still under the maximum number of note lines. 504 if (line < (maxNoteLines - 3) && !StringUtils.isEmpty(choppedWord)) { 505 pnt = new PaymentNoteText(); 506 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 507 pnt.setCustomerNoteText(choppedWord.replaceAll("\\n", "").trim()); 508 } 509 // We can't add any additional note lines, or we'll exceed the maximum, therefore 510 // just break out of the loop early - there's nothing left to do. 511 else { 512 break; 513 } 514 } 515 } 516 // This should be the most common case. Simply create a new PaymentNoteText, 517 // add the line at the correct line location. 518 else { 519 pnt = new PaymentNoteText(); 520 pnt.setCustomerNoteLineNbr(new KualiInteger(line++)); 521 pnt.setCustomerNoteText(noteLine.replaceAll("\\n", "").trim()); 522 } 523 524 // Logging... 525 if (LOG.isDebugEnabled()) { 526 LOG.debug("Creating check stub text note: " + pnt.getCustomerNoteText()); 527 } 528 pd.addNote(pnt); 529 } 530 } 531 } 532 533 return pd; 534 } 535 536 /** 537 * This method will take a word and simply chop into smaller 538 * text segments that satisfy the limit requirements. All words 539 * brute force chopped, with no regard to preserving whole words. 540 * 541 * For example: 542 * 543 * "Java is a fun programming language!" 544 * 545 * Might be chopped into: 546 * 547 * "Java is a fun prog" 548 * "ramming language!" 549 * 550 * @param word The word that needs chopping 551 * @param limit Number of character that should represent a chopped word 552 * @return String [] of chopped words 553 */ 554 private String [] chopWord(String word, int limit) 555 { 556 StringBuilder builder = new StringBuilder(); 557 if (word != null && word.trim().length() > 0) { 558 559 char[] chars = word.toCharArray(); 560 int index = 0; 561 562 // First process all the words that fit into the limit. 563 for (int i = 0; i < chars.length/limit; i++) { 564 builder.append(String.copyValueOf(chars, index, limit)); 565 builder.append("\n"); 566 567 index += limit; 568 } 569 570 // Not all words will fit perfectly into the limit amount, so 571 // calculate the modulus value to determine any remaining characters. 572 int modValue = chars.length%limit; 573 if (modValue > 0) { 574 builder.append(String.copyValueOf(chars, index, modValue)); 575 } 576 577 } 578 579 // Split the chopped words into individual segments. 580 return builder.toString().split("\\n"); 581 } 582 583 /** 584 * This method creates a Batch instance and populates it with the information provided. 585 * 586 * @param campusCode The campus code used to retrieve a customer profile to be set on the batch. 587 * @param user The user who submitted the batch. 588 * @param processRunDate The date the batch was submitted and the date the customer profile was generated. 589 * @return A fully populated batch instance. 590 */ 591 protected Batch createBatch(String campusCode, Person user, Date processRunDate) { 592 String orgCode = parameterService.getParameterValue(DisbursementVoucherDocument.class, DisbursementVoucherConstants.DvPdpExtractGroup.DV_PDP_ORG_CODE); 593 String subUnitCode = parameterService.getParameterValue(DisbursementVoucherDocument.class, DisbursementVoucherConstants.DvPdpExtractGroup.DV_PDP_SBUNT_CODE); 594 CustomerProfile customer = customerProfileService.get(campusCode, orgCode, subUnitCode); 595 if (customer == null) { 596 throw new IllegalArgumentException("Unable to find customer profile for " + campusCode + "/" + orgCode + "/" + subUnitCode); 597 } 598 599 // Create the group for this campus 600 Batch batch = new Batch(); 601 batch.setCustomerProfile(customer); 602 batch.setCustomerFileCreateTimestamp(new Timestamp(processRunDate.getTime())); 603 batch.setFileProcessTimestamp(new Timestamp(processRunDate.getTime())); 604 batch.setPaymentFileName(KFSConstants.DISBURSEMENT_VOUCHER_PDP_EXTRACT_FILE_NAME); 605 batch.setSubmiterUserId(user.getPrincipalId()); 606 607 // Set these for now, we will update them later 608 batch.setPaymentCount(KualiInteger.ZERO); 609 batch.setPaymentTotalAmount(KualiDecimal.ZERO); 610 611 businessObjectService.save(batch); 612 613 return batch; 614 } 615 616 /** 617 * This method retrieves a collection of campus instances representing all the campuses which currently have disbursement 618 * vouchers with the status code provided. 619 * 620 * @param statusCode The status code to retrieve disbursement vouchers by. 621 * @return A collection of campus codes of all the campuses with disbursement vouchers in the status given. 622 */ 623 protected Set<String> getCampusListByDocumentStatusCode(String statusCode) { 624 LOG.debug("getCampusListByDocumentStatusCode() started"); 625 626 Set<String> campusSet = new HashSet<String>(); 627 628 Collection<DisbursementVoucherDocument> docs = disbursementVoucherDao.getDocumentsByHeaderStatus(statusCode); 629 for (DisbursementVoucherDocument element : docs) { 630 String dvdCampusCode = element.getCampusCode(); 631 campusSet.add(dvdCampusCode); 632 } 633 634 return campusSet; 635 } 636 637 /** 638 * This method retrieves a list of disbursement voucher documents that are in the status provided for the campus code given. 639 * 640 * @param statusCode The status of the disbursement vouchers to be retrieved. 641 * @param campusCode The campus code that the disbursement vouchers will be associated with. 642 * @return A collection of disbursement voucher objects that meet the search criteria given. 643 */ 644 protected Collection<DisbursementVoucherDocument> getListByDocumentStatusCodeCampus(String statusCode, String campusCode) { 645 LOG.debug("getListByDocumentStatusCodeCampus() started"); 646 647 Collection<DisbursementVoucherDocument> list = new ArrayList<DisbursementVoucherDocument>(); 648 649 try { 650 Collection<DisbursementVoucherDocument> docs = SpringContext.getBean(FinancialSystemDocumentService.class).findByDocumentHeaderStatusCode(DisbursementVoucherDocument.class, statusCode); 651 for (DisbursementVoucherDocument element : docs) { 652 String dvdCampusCode = element.getCampusCode(); 653 654 if (dvdCampusCode.equals(campusCode) && DisbursementVoucherConstants.PAYMENT_METHOD_CHECK.equals(element.getDisbVchrPaymentMethodCode())) { 655 list.add(element); 656 } 657 } 658 } 659 catch (WorkflowException we) { 660 LOG.error("Could not load Disbursement Voucher Documents with status code = " + statusCode + ": " + we); 661 throw new RuntimeException(we); 662 } 663 664 return list; 665 } 666 667 /** 668 * This cancels the disbursement voucher 669 * 670 * @param dv the disbursement voucher document to cancel 671 * @param processDate the date of the cancelation 672 * @see org.kuali.kfs.fp.batch.service.DisbursementVoucherExtractService#cancelExtractedDisbursementVoucher(org.kuali.kfs.fp.document.DisbursementVoucherDocument) 673 */ 674 public void cancelExtractedDisbursementVoucher(DisbursementVoucherDocument dv, java.sql.Date processDate) { 675 if (dv.getCancelDate() == null) { 676 try { 677 BusinessObjectService boService = SpringContext.getBean(BusinessObjectService.class); 678 // set the canceled date 679 dv.setCancelDate(processDate); 680 dv.refreshReferenceObject("generalLedgerPendingEntries"); 681 if (ObjectUtils.isNull(dv.getGeneralLedgerPendingEntries()) || dv.getGeneralLedgerPendingEntries().size() == 0) { 682 // generate all the pending entries for the document 683 SpringContext.getBean(GeneralLedgerPendingEntryService.class).generateGeneralLedgerPendingEntries(dv); 684 // for each pending entry, opposite-ify it and reattach it to the document 685 GeneralLedgerPendingEntrySequenceHelper glpeSeqHelper = new GeneralLedgerPendingEntrySequenceHelper(); 686 for (GeneralLedgerPendingEntry glpe : dv.getGeneralLedgerPendingEntries()) { 687 oppositifyEntry(glpe, boService, glpeSeqHelper); 688 } 689 } 690 else { 691 List<GeneralLedgerPendingEntry> newGLPEs = new ArrayList<GeneralLedgerPendingEntry>(); 692 GeneralLedgerPendingEntrySequenceHelper glpeSeqHelper = new GeneralLedgerPendingEntrySequenceHelper(dv.getGeneralLedgerPendingEntries().size() + 1); 693 for (GeneralLedgerPendingEntry glpe : dv.getGeneralLedgerPendingEntries()) { 694 glpe.refresh(); 695 if (glpe.getFinancialDocumentApprovedCode().equals(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.PROCESSED)) { 696 // damn! it got processed! well, make a copy, oppositify, and save 697 GeneralLedgerPendingEntry undoer = new GeneralLedgerPendingEntry(glpe); 698 oppositifyEntry(undoer, boService, glpeSeqHelper); 699 newGLPEs.add(undoer); 700 } 701 else { 702 // just delete the GLPE before anything happens to it 703 boService.delete(glpe); 704 } 705 } 706 dv.setGeneralLedgerPendingEntries(newGLPEs); 707 } 708 // set the financial document status to canceled 709 dv.getDocumentHeader().setFinancialDocumentStatusCode(KFSConstants.DocumentStatusCodes.CANCELLED); 710 // save the document 711 SpringContext.getBean(DocumentService.class).saveDocument(dv, AccountingDocumentSaveWithNoLedgerEntryGenerationEvent.class); 712 } 713 catch (WorkflowException we) { 714 LOG.error("encountered workflow exception while attempting to save Disbursement Voucher: " + dv.getDocumentNumber() + " " + we); 715 throw new RuntimeException(we); 716 } 717 } 718 } 719 720 /** 721 * Updates the given general ledger pending entry so that it will have the opposite effect of what it was created to do; this, 722 * in effect, undoes the entries that were already posted for this document 723 * 724 * @param glpe the general ledger pending entry to undo 725 */ 726 protected void oppositifyEntry(GeneralLedgerPendingEntry glpe, BusinessObjectService boService, GeneralLedgerPendingEntrySequenceHelper glpeSeqHelper) { 727 if (glpe.getTransactionDebitCreditCode().equals(KFSConstants.GL_CREDIT_CODE)) { 728 glpe.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE); 729 } 730 else if (glpe.getTransactionDebitCreditCode().equals(KFSConstants.GL_DEBIT_CODE)) { 731 glpe.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE); 732 } 733 glpe.setTransactionLedgerEntrySequenceNumber(glpeSeqHelper.getSequenceCounter()); 734 glpeSeqHelper.increment(); 735 glpe.setFinancialDocumentApprovedCode(KFSConstants.PENDING_ENTRY_APPROVED_STATUS_CODE.APPROVED); 736 boService.save(glpe); 737 } 738 739 /** 740 * This updates the disbursement voucher so that when it is re-extracted, information about it will be accurate 741 * 742 * @param dv the disbursement voucher document to reset 743 * @param processDate the date of the reseting 744 * @see org.kuali.kfs.fp.batch.service.DisbursementVoucherExtractService#resetExtractedDisbursementVoucher(org.kuali.kfs.fp.document.DisbursementVoucherDocument) 745 */ 746 public void resetExtractedDisbursementVoucher(DisbursementVoucherDocument dv, java.sql.Date processDate) { 747 try { 748 // 1. reset the extracted date 749 dv.setExtractDate(null); 750 dv.setPaidDate(null); 751 // 2. save the doc 752 SpringContext.getBean(DocumentService.class).saveDocument(dv, AccountingDocumentSaveWithNoLedgerEntryGenerationEvent.class); 753 } 754 catch (WorkflowException we) { 755 LOG.error("encountered workflow exception while attempting to save Disbursement Voucher: " + dv.getDocumentNumber() + " " + we); 756 throw new RuntimeException(we); 757 } 758 } 759 760 /** 761 * Looks up the document using document service, and deals with any nasty WorkflowException or ClassCastExceptions that pop up 762 * 763 * @param documentNumber the number of the document to look up 764 * @return the dv doc if found, or null otherwise 765 * @see org.kuali.kfs.fp.batch.service.DisbursementVoucherExtractService#getDocumentById(java.lang.String) 766 */ 767 public DisbursementVoucherDocument getDocumentById(String documentNumber) { 768 DisbursementVoucherDocument dv = null; 769 try { 770 dv = (DisbursementVoucherDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(documentNumber); 771 } 772 catch (WorkflowException we) { 773 LOG.error("encountered workflow exception while attempting to retrieve Disbursement Voucher: " + dv.getDocumentNumber() + " " + we); 774 throw new RuntimeException(we); 775 } 776 return dv; 777 } 778 779 /** 780 * Marks the disbursement voucher as paid by setting its paid date 781 * 782 * @param dv the dv document to mark as paid 783 * @param processDate the date when the dv was paid 784 * @see org.kuali.kfs.fp.batch.service.DisbursementVoucherExtractService#markDisbursementVoucherAsPaid(org.kuali.kfs.fp.document.DisbursementVoucherDocument) 785 */ 786 public void markDisbursementVoucherAsPaid(DisbursementVoucherDocument dv, java.sql.Date processDate) { 787 try { 788 dv.setPaidDate(processDate); 789 SpringContext.getBean(DocumentService.class).saveDocument(dv, AccountingDocumentSaveWithNoLedgerEntryGenerationEvent.class); 790 } 791 catch (WorkflowException we) { 792 LOG.error("encountered workflow exception while attempting to save Disbursement Voucher: " + dv.getDocumentNumber() + " " + we); 793 throw new RuntimeException(we); 794 } 795 } 796 797 /** 798 * This method sets the disbursementVoucherDao instance. 799 * 800 * @param disbursementVoucherDao The DisbursementVoucherDao to be set. 801 */ 802 public void setDisbursementVoucherDao(DisbursementVoucherDao disbursementVoucherDao) { 803 this.disbursementVoucherDao = disbursementVoucherDao; 804 } 805 806 /** 807 * This method sets the ParameterService instance. 808 * 809 * @param parameterService The ParameterService to be set. 810 */ 811 public void setParameterService(ParameterService parameterService) { 812 this.parameterService = parameterService; 813 } 814 815 /** 816 * This method sets the dateTimeService instance. 817 * 818 * @param dateTimeService The DateTimeService to be set. 819 */ 820 public void setDateTimeService(DateTimeService dateTimeService) { 821 this.dateTimeService = dateTimeService; 822 } 823 824 /** 825 * This method sets the customerProfileService instance. 826 * 827 * @param customerProfileService The CustomerProfileService to be set. 828 */ 829 public void setCustomerProfileService(CustomerProfileService customerProfileService) { 830 this.customerProfileService = customerProfileService; 831 } 832 833 /** 834 * This method sets the paymentFileService instance. 835 * 836 * @param paymentFileService The PaymentFileService to be set. 837 */ 838 public void setPaymentFileService(PaymentFileService paymentFileService) { 839 this.paymentFileService = paymentFileService; 840 } 841 842 /** 843 * This method sets the paymentGroupService instance. 844 * 845 * @param paymentGroupService The PaymentGroupService to be set. 846 */ 847 public void setPaymentGroupService(PaymentGroupService paymentGroupService) { 848 this.paymentGroupService = paymentGroupService; 849 } 850 851 /** 852 * Sets the businessObjectService attribute value. 853 * 854 * @param businessObjectService The businessObjectService to set. 855 */ 856 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 857 this.businessObjectService = businessObjectService; 858 } 859 860 /** 861 * Sets the paymentFileEmailService attribute value. 862 * 863 * @param paymentFileEmailService The paymentFileEmailService to set. 864 */ 865 public void setPaymentFileEmailService(PdpEmailService paymentFileEmailService) { 866 this.paymentFileEmailService = paymentFileEmailService; 867 } 868 869 /** 870 * @return Returns the personService. 871 */ 872 protected PersonService<Person> getPersonService() { 873 if(personService==null) 874 personService = SpringContext.getBean(PersonService.class); 875 return personService; 876 } 877 878 }