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 }