View Javadoc
1   /*
2    * Copyright 2008 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.pdp.service.impl;
17  
18  import java.sql.Timestamp;
19  import java.text.MessageFormat;
20  import java.text.ParseException;
21  import java.util.ArrayList;
22  import java.util.Calendar;
23  import java.util.List;
24  
25  import org.apache.commons.lang.StringUtils;
26  import org.kuali.ole.coa.businessobject.Account;
27  import org.kuali.ole.coa.businessobject.ObjectCode;
28  import org.kuali.ole.coa.businessobject.ProjectCode;
29  import org.kuali.ole.coa.businessobject.SubAccount;
30  import org.kuali.ole.coa.businessobject.SubObjectCode;
31  import org.kuali.ole.coa.service.AccountService;
32  import org.kuali.ole.coa.service.ObjectCodeService;
33  import org.kuali.ole.coa.service.SubAccountService;
34  import org.kuali.ole.coa.service.SubObjectCodeService;
35  import org.kuali.ole.pdp.PdpConstants;
36  import org.kuali.ole.pdp.PdpKeyConstants;
37  import org.kuali.ole.pdp.PdpParameterConstants;
38  import org.kuali.ole.pdp.PdpPropertyConstants;
39  import org.kuali.ole.pdp.businessobject.AccountingChangeCode;
40  import org.kuali.ole.pdp.businessobject.CustomerProfile;
41  import org.kuali.ole.pdp.businessobject.PayeeType;
42  import org.kuali.ole.pdp.businessobject.PaymentAccountDetail;
43  import org.kuali.ole.pdp.businessobject.PaymentAccountHistory;
44  import org.kuali.ole.pdp.businessobject.PaymentDetail;
45  import org.kuali.ole.pdp.businessobject.PaymentFileLoad;
46  import org.kuali.ole.pdp.businessobject.PaymentGroup;
47  import org.kuali.ole.pdp.businessobject.PaymentStatus;
48  import org.kuali.ole.pdp.dataaccess.PaymentFileLoadDao;
49  import org.kuali.ole.pdp.service.CustomerProfileService;
50  import org.kuali.ole.pdp.service.PaymentFileValidationService;
51  import org.kuali.ole.sys.OLEConstants;
52  import org.kuali.ole.sys.businessobject.Bank;
53  import org.kuali.ole.sys.businessobject.OriginationCode;
54  import org.kuali.ole.sys.service.BankService;
55  import org.kuali.ole.sys.service.OriginationCodeService;
56  import org.kuali.ole.sys.service.impl.OleParameterConstants;
57  import org.kuali.rice.core.api.config.property.ConfigurationService;
58  import org.kuali.rice.core.api.datetime.DateTimeService;
59  import org.kuali.rice.core.api.util.type.KualiDecimal;
60  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
61  import org.kuali.rice.kew.api.doctype.DocumentTypeService;
62  import org.kuali.rice.krad.bo.KualiCodeBase;
63  import org.kuali.rice.krad.service.BusinessObjectService;
64  import org.kuali.rice.krad.util.MessageMap;
65  import org.springframework.transaction.annotation.Transactional;
66  
67  /**
68   * @see org.kuali.ole.pdp.batch.service.PaymentFileValidationService
69   */
70  @Transactional
71  public class PaymentFileValidationServiceImpl implements PaymentFileValidationService {
72      protected CustomerProfileService customerProfileService;
73      protected PaymentFileLoadDao paymentFileLoadDao;
74      protected ParameterService parameterService;
75      protected ConfigurationService kualiConfigurationService;
76      protected DateTimeService dateTimeService;
77      protected AccountService accountService;
78      protected SubAccountService subAccountService;
79      protected ObjectCodeService objectCodeService;
80      protected SubObjectCodeService subObjectCodeService;
81      protected BankService bankService;
82      protected OriginationCodeService originationCodeService;
83      protected DocumentTypeService documentTypeService;
84      protected BusinessObjectService businessObjectService;
85  
86      /**
87       * @see org.kuali.ole.pdp.batch.service.PaymentFileValidationService#doHardEdits(org.kuali.ole.pdp.businessobject.PaymentFile,
88       *      org.kuali.rice.krad.util.MessageMap)
89       */
90      @Override
91      public void doHardEdits(PaymentFileLoad paymentFile, MessageMap errorMap) {
92          processHeaderValidation(paymentFile, errorMap);
93  
94          if (errorMap.hasNoErrors()) {
95              processGroupValidation(paymentFile, errorMap);
96          }
97  
98          if (errorMap.hasNoErrors()) {
99              processTrailerValidation(paymentFile, errorMap);
100         }
101     }
102 
103     /**
104      * Validates payment file header fields <li>Checks customer exists in customer profile table and is active</li>
105      * 
106      * @param paymentFile payment file object
107      * @param errorMap map in which errors will be added to
108      */
109     protected void processHeaderValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
110         CustomerProfile customer = customerProfileService.get(paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
111         if (customer == null) {
112             errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_CUSTOMER, paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
113         }
114         else {
115             if (!customer.isActive()) {
116                 errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INACTIVE_CUSTOMER, paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
117             }
118             else {
119                 paymentFile.setCustomer(customer);
120             }
121         }
122     }
123 
124     /**
125      * Validates payment file trailer fields <li>Reconciles actual to expected payment count and totals</li> <li>Verifies the batch
126      * is not a duplicate</li>
127      * 
128      * @param paymentFile payment file object
129      * @param errorMap map in which errors will be added to
130      */
131     protected void processTrailerValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
132         // compare trailer payment count to actual count loaded
133         if (paymentFile.getActualPaymentCount() != paymentFile.getPaymentCount()) {
134             errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYMENT_COUNT_MISMATCH, Integer.toString(paymentFile.getPaymentCount()), Integer.toString(paymentFile.getActualPaymentCount()));
135         }
136 
137         // compare trailer total amount with actual total amount
138         if (paymentFile.getCalculatedPaymentTotalAmount().compareTo(paymentFile.getPaymentTotalAmount()) != 0) {
139             errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYMENT_TOTAL_MISMATCH, paymentFile.getPaymentTotalAmount().toString(), paymentFile.getCalculatedPaymentTotalAmount().toString());
140         }
141 
142         // Check to see if this is a duplicate batch
143         Timestamp now = new Timestamp(paymentFile.getCreationDate().getTime());
144 
145         if (paymentFileLoadDao.isDuplicateBatch(paymentFile.getCustomer(), paymentFile.getPaymentCount(), paymentFile.getPaymentTotalAmount().bigDecimalValue(), now)) {
146             errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_DUPLICATE_BATCH);
147         }
148     }
149 
150     /**
151      * Validates payment file groups <li>Checks number of note lines needed is not above the configured maximum allowed</li> <li>
152      * Verifies group total is not negative</li> <li>Verifies detail accounting total equals net payment amount</li>
153      * 
154      * @param paymentFile payment file object
155      * @param errorMap map in which errors will be added to
156      */
157     protected void processGroupValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
158         int groupCount = 0;
159         for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
160             groupCount++;
161 
162             int noteLineCount = 0;
163             int detailCount = 0;
164 
165             // verify payee id and owner code if customer requires them to be filled in
166             if (paymentFile.getCustomer().getPayeeIdRequired() && StringUtils.isBlank(paymentGroup.getPayeeId())) {
167                 errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYEE_ID_REQUIRED, Integer.toString(groupCount));
168             }
169 
170             if (paymentFile.getCustomer().getOwnershipCodeRequired() && StringUtils.isBlank(paymentGroup.getPayeeOwnerCd())) {
171                 errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYEE_OWNER_CODE, Integer.toString(groupCount));
172             }
173 
174             // validate payee id type
175             if (StringUtils.isNotBlank(paymentGroup.getPayeeIdTypeCd())) {
176                 PayeeType payeeType = businessObjectService.findBySinglePrimaryKey(PayeeType.class, paymentGroup.getPayeeIdTypeCd());
177                 if (payeeType == null) {
178                     errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_PAYEE_ID_TYPE, Integer.toString(groupCount), paymentGroup.getPayeeIdTypeCd());
179                 }
180             }
181 
182             // validate bank
183             String bankCode = paymentGroup.getBankCode();
184             if (StringUtils.isNotBlank(bankCode)) {
185                 Bank bank = bankService.getByPrimaryId(bankCode);
186                 if (bank == null) {
187                     errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_BANK_CODE, Integer.toString(groupCount), bankCode);
188                 }
189                 else if (!bank.isActive()) {
190                     errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INACTIVE_BANK_CODE, Integer.toString(groupCount), bankCode);
191                 }
192             }
193 
194             KualiDecimal groupTotal = KualiDecimal.ZERO;
195             for (PaymentDetail paymentDetail : paymentGroup.getPaymentDetails()) {
196                 detailCount++;
197 
198                 noteLineCount++; // Add a line to print the invoice number
199                 noteLineCount = noteLineCount + paymentDetail.getNotes().size();
200 
201                 if ((paymentDetail.getNetPaymentAmount() == null) && (!paymentDetail.isDetailAmountProvided())) {
202                     paymentDetail.setNetPaymentAmount(paymentDetail.getAccountTotal());
203                 }
204                 else if ((paymentDetail.getNetPaymentAmount() == null) && (paymentDetail.isDetailAmountProvided())) {
205                     paymentDetail.setNetPaymentAmount(paymentDetail.getCalculatedPaymentAmount());
206                 }
207 
208                 // compare net to accounting segments
209                 if (paymentDetail.getAccountTotal().compareTo(paymentDetail.getNetPaymentAmount()) != 0) {
210                     errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_DETAIL_TOTAL_MISMATCH, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getAccountTotal().toString(), paymentDetail.getNetPaymentAmount().toString());
211                 }
212 
213                 // validate origin code if given
214                 if (StringUtils.isNotBlank(paymentDetail.getFinancialSystemOriginCode())) {
215                     OriginationCode originationCode = originationCodeService.getByPrimaryKey(paymentDetail.getFinancialSystemOriginCode());
216                     if (originationCode == null) {
217                         errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_ORIGIN_CODE, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getFinancialSystemOriginCode());
218                     }
219                 }
220 
221                 // validate doc type if given
222                 if (StringUtils.isNotBlank(paymentDetail.getFinancialDocumentTypeCode())) {
223                     if ( !documentTypeService.isActiveByName(paymentDetail.getFinancialDocumentTypeCode()) ) {
224                             errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_DOC_TYPE, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getFinancialDocumentTypeCode());
225                         }
226                     }
227 
228                 groupTotal = groupTotal.add(paymentDetail.getNetPaymentAmount());
229             }
230 
231             // verify total for group is not negative
232             if (groupTotal.doubleValue() < 0) {
233                 errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_NEGATIVE_GROUP_TOTAL, Integer.toString(groupCount));
234             }
235 
236             // check that the number of detail items and note lines will fit on a check stub
237             if (noteLineCount > getMaxNoteLines()) {
238                 errorMap.putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_MAX_NOTE_LINES, Integer.toString(groupCount), Integer.toString(noteLineCount), Integer.toString(getMaxNoteLines()));
239             }
240         }
241     }
242 
243     /**
244      * @see org.kuali.ole.pdp.service.PaymentFileValidationService#doSoftEdits(org.kuali.ole.pdp.businessobject.PaymentFile)
245      */
246     @Override
247     public List<String> doSoftEdits(PaymentFileLoad paymentFile) {
248         List<String> warnings = new ArrayList<String>();
249 
250         CustomerProfile customer = customerProfileService.get(paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
251 
252         // check payment amount does not exceed the configured threshold amount of this customer
253         if (paymentFile.getPaymentTotalAmount().compareTo(customer.getFileThresholdAmount()) > 0) {
254             addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_FILE_THRESHOLD, paymentFile.getPaymentTotalAmount().toString(), customer.getFileThresholdAmount().toString());
255             paymentFile.setFileThreshold(true);
256         }
257 
258         processGroupSoftEdits(paymentFile, customer, warnings);
259 
260         return warnings;
261     }
262 
263     /**
264      * Set defaults for group fields and do tax checks.
265      * 
266      * @param paymentFile payment file object
267      * @param customer payment customer
268      * @param warnings <code>List</code> list of accumulated warning messages
269      */
270     public void processGroupSoftEdits(PaymentFileLoad paymentFile, CustomerProfile customer, List<String> warnings) {
271         PaymentStatus openStatus = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.OPEN);
272 
273         for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
274             paymentGroup.setBatchId(paymentFile.getBatchId());
275             paymentGroup.setPaymentStatusCode(openStatus.getCode());
276             paymentGroup.setPaymentStatus(openStatus);
277             paymentGroup.setPayeeName(paymentGroup.getPayeeName().toUpperCase());
278 
279             // Set defaults for missing information
280             defaultGroupIndicators(paymentGroup);
281 
282             // Tax Group Requirements for automatic Holding
283             checkForTaxEmailRequired(paymentFile, paymentGroup, customer);
284 
285             // do edits on detail lines
286             for (PaymentDetail paymentDetail : paymentGroup.getPaymentDetails()) {
287                 paymentDetail.setPaymentGroupId(paymentGroup.getId());
288 
289                 processDetailSoftEdits(paymentFile, customer, paymentDetail, warnings);
290             }
291 
292         }
293     }
294 
295     /**
296      * Set default fields on detail line and check amount against customer threshold.
297      * 
298      * @param paymentFile payment file object
299      * @param customer payment customer
300      * @param paymentDetail <code>PaymentDetail</code> object to process
301      * @param warnings <code>List</code> list of accumulated warning messages
302      */
303     protected void processDetailSoftEdits(PaymentFileLoad paymentFile, CustomerProfile customer, PaymentDetail paymentDetail, List<String> warnings) {
304         updateDetailAmounts(paymentDetail);
305 
306         // Check net payment amount
307         KualiDecimal testAmount = paymentDetail.getNetPaymentAmount();
308         if (testAmount.compareTo(customer.getPaymentThresholdAmount()) > 0) {
309             addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_DETAIL_THRESHOLD, testAmount.toString(), customer.getPaymentThresholdAmount().toString());
310             paymentFile.setDetailThreshold(true);
311             paymentFile.getThresholdPaymentDetails().add(paymentDetail);
312         }
313 
314         // set invoice date if it doesn't exist
315         if (paymentDetail.getInvoiceDate() == null) {
316             paymentDetail.setInvoiceDate(dateTimeService.getCurrentSqlDate());
317         }
318 
319         if (paymentDetail.getPrimaryCancelledPayment() == null) {
320             paymentDetail.setPrimaryCancelledPayment(Boolean.FALSE);
321         }
322 
323         // do accounting edits
324         for (PaymentAccountDetail paymentAccountDetail : paymentDetail.getAccountDetail()) {
325             paymentAccountDetail.setPaymentDetailId(paymentDetail.getId());
326 
327             processAccountSoftEdits(paymentFile, customer, paymentAccountDetail, warnings);
328         }
329     }
330 
331     /**
332      * Set default fields on account line and perform account field existence checks
333      * 
334      * @param paymentFile payment file object
335      * @param customer payment customer
336      * @param paymentAccountDetail <code>PaymentAccountDetail</code> object to process
337      * @param warnings <code>List</code> list of accumulated warning messages
338      */
339     protected void processAccountSoftEdits(PaymentFileLoad paymentFile, CustomerProfile customer, PaymentAccountDetail paymentAccountDetail, List<String> warnings) {
340         List<PaymentAccountHistory> changeRecords = paymentAccountDetail.getAccountHistory();
341 
342         // uppercase chart
343         paymentAccountDetail.setFinChartCode(paymentAccountDetail.getFinChartCode().toUpperCase());
344 
345         // only do accounting edits if required by customer
346         if (customer.getAccountingEditRequired()) {
347             // check account number
348             Account account = accountService.getByPrimaryId(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr());
349             if (account == null) {
350                 addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_ACCOUNT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr());
351 
352                 KualiCodeBase objChangeCd = businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_ACCOUNT);
353                 replaceAccountingString(objChangeCd, changeRecords, customer, paymentAccountDetail);
354             }
355             else {
356                 // check sub account code
357                 if (StringUtils.isNotBlank(paymentAccountDetail.getSubAccountNbr())) {
358                     SubAccount subAccount = subAccountService.getByPrimaryId(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getSubAccountNbr());
359                     if (subAccount == null) {
360                         addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_SUB_ACCOUNT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getSubAccountNbr());
361 
362                         KualiCodeBase objChangeCd = businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_SUB_ACCOUNT);
363                         changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_ACCOUNT_DB_COLUMN_NAME, OLEConstants.getDashSubAccountNumber(), paymentAccountDetail.getSubAccountNbr(), objChangeCd));
364 
365                         paymentAccountDetail.setSubAccountNbr(OLEConstants.getDashSubAccountNumber());
366                     }
367                 }
368 
369                 // check object code
370                 ObjectCode objectCode = objectCodeService.getByPrimaryIdForCurrentYear(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getFinObjectCode());
371                 if (objectCode == null) {
372                     addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_OBJECT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getFinObjectCode());
373 
374                     KualiCodeBase objChangeCd = businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_OBJECT);
375                     replaceAccountingString(objChangeCd, changeRecords, customer, paymentAccountDetail);
376                 }
377 
378                 // check sub object code
379                 else if (StringUtils.isNotBlank(paymentAccountDetail.getFinSubObjectCode())) {
380                     SubObjectCode subObjectCode = subObjectCodeService.getByPrimaryIdForCurrentYear(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getFinObjectCode(), paymentAccountDetail.getFinSubObjectCode());
381                     if (subObjectCode == null) {
382                         addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_SUB_OBJECT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getFinObjectCode(), paymentAccountDetail.getFinSubObjectCode());
383 
384                         KualiCodeBase objChangeCd = businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_SUB_OBJECT);
385                         changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_OBJECT_DB_COLUMN_NAME, OLEConstants.getDashFinancialSubObjectCode(), paymentAccountDetail.getFinSubObjectCode(), objChangeCd));
386 
387                         paymentAccountDetail.setFinSubObjectCode(OLEConstants.getDashFinancialSubObjectCode());
388                     }
389                 }
390             }
391 
392             // check project code
393             if (StringUtils.isNotBlank(paymentAccountDetail.getProjectCode())) {
394                 ProjectCode projectCode = businessObjectService.findBySinglePrimaryKey(ProjectCode.class, paymentAccountDetail.getProjectCode());
395                 if (projectCode == null) {
396                     addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_PROJECT, paymentAccountDetail.getProjectCode());
397 
398                     KualiCodeBase objChangeCd = businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_PROJECT);
399                     changeRecords.add(newAccountHistory(PdpPropertyConstants.PROJECT_DB_COLUMN_NAME, OLEConstants.getDashProjectCode(), paymentAccountDetail.getProjectCode(), objChangeCd));
400                     paymentAccountDetail.setProjectCode(OLEConstants.getDashProjectCode());
401                 }
402             }
403         }
404 
405         // change nulls into ---'s for the fields that need it
406         if (StringUtils.isBlank(paymentAccountDetail.getFinSubObjectCode())) {
407             paymentAccountDetail.setFinSubObjectCode(OLEConstants.getDashFinancialSubObjectCode());
408         }
409 
410         if (StringUtils.isBlank(paymentAccountDetail.getSubAccountNbr())) {
411             paymentAccountDetail.setSubAccountNbr(OLEConstants.getDashSubAccountNumber());
412         }
413 
414         if (StringUtils.isBlank(paymentAccountDetail.getProjectCode())) {
415             paymentAccountDetail.setProjectCode(OLEConstants.getDashProjectCode());
416         }
417 
418     }
419 
420     /**
421      * Replaces the entire accounting string with defaults from the customer profile.
422      * 
423      * @param objChangeCd code indicating reason for change
424      * @param changeRecords <code>List</code> of <code>PaymentAccountHistory</code> records
425      * @param customer profile of payment customer
426      * @param paymentAccountDetail account detail record
427      */
428     protected void replaceAccountingString(KualiCodeBase objChangeCd, List<PaymentAccountHistory> changeRecords, CustomerProfile customer, PaymentAccountDetail paymentAccountDetail) {
429         changeRecords.add(newAccountHistory(PdpPropertyConstants.CHART_DB_COLUMN_NAME, customer.getDefaultChartCode(), paymentAccountDetail.getFinChartCode(), objChangeCd));
430         changeRecords.add(newAccountHistory(PdpPropertyConstants.ACCOUNT_DB_COLUMN_NAME, customer.getDefaultAccountNumber(), paymentAccountDetail.getAccountNbr(), objChangeCd));
431         changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_ACCOUNT_DB_COLUMN_NAME, customer.getDefaultSubAccountNumber(), paymentAccountDetail.getSubAccountNbr(), objChangeCd));
432         changeRecords.add(newAccountHistory(PdpPropertyConstants.OBJECT_DB_COLUMN_NAME, customer.getDefaultObjectCode(), paymentAccountDetail.getFinObjectCode(), objChangeCd));
433         changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_OBJECT_DB_COLUMN_NAME, customer.getDefaultSubObjectCode(), paymentAccountDetail.getFinSubObjectCode(), objChangeCd));
434 
435         paymentAccountDetail.setFinChartCode(customer.getDefaultChartCode());
436         paymentAccountDetail.setAccountNbr(customer.getDefaultAccountNumber());
437         if (StringUtils.isNotBlank(customer.getDefaultSubAccountNumber())) {
438             paymentAccountDetail.setSubAccountNbr(customer.getDefaultSubAccountNumber());
439         }
440         else {
441             paymentAccountDetail.setSubAccountNbr(OLEConstants.getDashSubAccountNumber());
442         }
443         paymentAccountDetail.setFinObjectCode(customer.getDefaultObjectCode());
444         if (StringUtils.isNotBlank(customer.getDefaultSubAccountNumber())) {
445             paymentAccountDetail.setFinSubObjectCode(customer.getDefaultSubObjectCode());
446         }
447         else {
448             paymentAccountDetail.setFinSubObjectCode(OLEConstants.getDashFinancialSubObjectCode());
449         }
450     }
451 
452     /**
453      * Helper method to construct a new payment account history record
454      * 
455      * @param attName name of field that has changed
456      * @param newValue new value for the field
457      * @param oldValue field value that was changed
458      * @param changeCode code indicating reason for change
459      * @return <code>PaymentAccountHistory</code>
460      */
461     protected PaymentAccountHistory newAccountHistory(String attName, String newValue, String oldValue, KualiCodeBase changeCode) {
462         PaymentAccountHistory paymentAccountHistory = new PaymentAccountHistory();
463 
464         paymentAccountHistory.setAcctAttributeName(attName);
465         paymentAccountHistory.setAcctAttributeNewValue(newValue);
466         paymentAccountHistory.setAcctAttributeOrigValue(oldValue);
467         paymentAccountHistory.setAcctChangeDate(dateTimeService.getCurrentTimestamp());
468         paymentAccountHistory.setAccountingChange((AccountingChangeCode) changeCode);
469 
470         return paymentAccountHistory;
471     }
472 
473     /**
474      * Sets null amount fields to 0
475      * 
476      * @param paymentDetail <code>PaymentDetail</code> to update
477      */
478     protected void updateDetailAmounts(PaymentDetail paymentDetail) {
479         KualiDecimal zero = KualiDecimal.ZERO;
480 
481         if (paymentDetail.getInvTotDiscountAmount() == null) {
482             paymentDetail.setInvTotDiscountAmount(zero);
483         }
484 
485         if (paymentDetail.getInvTotShipAmount() == null) {
486             paymentDetail.setInvTotShipAmount(zero);
487         }
488 
489         if (paymentDetail.getInvTotOtherDebitAmount() == null) {
490             paymentDetail.setInvTotOtherDebitAmount(zero);
491         }
492 
493         if (paymentDetail.getInvTotOtherCreditAmount() == null) {
494             paymentDetail.setInvTotOtherCreditAmount(zero);
495         }
496 
497         // update the total payment amount with the amount from the accounts if null
498         if (paymentDetail.getNetPaymentAmount() == null) {
499             paymentDetail.setNetPaymentAmount(paymentDetail.getAccountTotal());
500         }
501 
502         if (paymentDetail.getOrigInvoiceAmount() == null) {
503             KualiDecimal amt = paymentDetail.getNetPaymentAmount();
504             amt = amt.add(paymentDetail.getInvTotDiscountAmount());
505             amt = amt.subtract(paymentDetail.getInvTotShipAmount());
506             amt = amt.subtract(paymentDetail.getInvTotOtherDebitAmount());
507             amt = amt.add(paymentDetail.getInvTotOtherCreditAmount());
508             paymentDetail.setOrigInvoiceAmount(amt);
509         }
510     }
511 
512     /**
513      * Sets null indicators to false
514      * 
515      * @param paymentGroup <code>PaymentGroup</code> to update
516      */
517     protected void defaultGroupIndicators(PaymentGroup paymentGroup) {
518         // combineGroups column does not accept null values, so it will never be null
519         /*
520          * if (paymentGroup.getCombineGroups() == null) { paymentGroup.setCombineGroups(Boolean.TRUE); }
521          */
522 
523         if (paymentGroup.getCampusAddress() == null) {
524             paymentGroup.setCampusAddress(Boolean.FALSE);
525         }
526 
527         if (paymentGroup.getPymtAttachment() == null) {
528             paymentGroup.setPymtAttachment(Boolean.FALSE);
529         }
530 
531         if (paymentGroup.getPymtSpecialHandling() == null) {
532             paymentGroup.setPymtSpecialHandling(Boolean.FALSE);
533         }
534 
535         if (paymentGroup.getProcessImmediate() == null) {
536             paymentGroup.setProcessImmediate(Boolean.FALSE);
537         }
538 
539         if (paymentGroup.getEmployeeIndicator() == null) {
540             paymentGroup.setEmployeeIndicator(Boolean.FALSE);
541         }
542 
543         if (paymentGroup.getNraPayment() == null) {
544             paymentGroup.setNraPayment(Boolean.FALSE);
545         }
546 
547         if (paymentGroup.getTaxablePayment() == null) {
548             paymentGroup.setTaxablePayment(Boolean.FALSE);
549         }
550     }
551 
552     /**
553      * Checks whether payment status should be set to held and a tax email sent indicating so
554      * 
555      * @param paymentFile payment file object
556      * @param paymentGroup <code>PaymentGroup</code> being checked
557      * @param customer payment customer
558      */
559     protected void checkForTaxEmailRequired(PaymentFileLoad paymentFile, PaymentGroup paymentGroup, CustomerProfile customer) {
560         PaymentStatus heldForNRAEmployee = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.HELD_TAX_NRA_EMPL_CD);
561         PaymentStatus heldForEmployee = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.HELD_TAX_EMPLOYEE_CD);
562         PaymentStatus heldForNRA = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.HELD_TAX_NRA_CD);
563 
564         if (customer.getNraReview() && customer.getEmployeeCheck() && paymentGroup.getEmployeeIndicator().booleanValue() && paymentGroup.getNraPayment().booleanValue()) {
565             paymentGroup.setPaymentStatus(heldForNRAEmployee);
566             paymentFile.setTaxEmailRequired(true);
567         }
568 
569         else if (customer.getEmployeeCheck() && paymentGroup.getEmployeeIndicator().booleanValue()) {
570             paymentGroup.setPaymentStatus(heldForEmployee);
571             paymentFile.setTaxEmailRequired(true);
572         }
573 
574         else if (customer.getNraReview() && paymentGroup.getNraPayment().booleanValue()) {
575             paymentGroup.setPaymentStatus(heldForNRA);
576             paymentFile.setTaxEmailRequired(true);
577         }
578     }
579 
580     /**
581      * Checks the payment date is not more than 30 days past or 30 days coming
582      * 
583      * @param paymentGroup <code>PaymentGroup</code> being checked
584      * @param warnings <code>List</code> list of accumulated warning messages
585      */
586     protected void checkGroupPaymentDate(PaymentGroup paymentGroup, List<String> warnings) {
587         Timestamp now = dateTimeService.getCurrentTimestamp();
588 
589         Calendar nowPlus30 = Calendar.getInstance();
590         nowPlus30.setTime(now);
591         nowPlus30.add(Calendar.DATE, 30);
592 
593         Calendar nowMinus30 = Calendar.getInstance();
594         nowMinus30.setTime(now);
595         nowMinus30.add(Calendar.DATE, -30);
596 
597         if (paymentGroup.getPaymentDate() != null) {
598             Calendar payDate = Calendar.getInstance();
599             payDate.setTime(paymentGroup.getPaymentDate());
600 
601             if (payDate.before(nowMinus30)) {
602                 addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_PAYDATE_OVER_30_DAYS_PAST, dateTimeService.toDateString(paymentGroup.getPaymentDate()));
603             }
604 
605             if (payDate.after(nowPlus30)) {
606                 addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_PAYDATE_OVER_30_DAYS_OUT, dateTimeService.toDateString(paymentGroup.getPaymentDate()));
607             }
608         }
609         else {
610             try {
611                 paymentGroup.setPaymentDate(dateTimeService.convertToSqlDate(now));
612             }
613             catch (ParseException e) {
614                 throw new RuntimeException("Unable to parse current timestamp into sql date " + e.getMessage());
615             }
616         }
617     }
618 
619     /**
620      * @return system parameter value giving the maximum number of notes allowed.
621      */
622     protected int getMaxNoteLines() {
623         String maxLines = parameterService.getParameterValueAsString(OleParameterConstants.PRE_DISBURSEMENT_ALL.class, PdpParameterConstants.MAX_NOTE_LINES);
624         if (StringUtils.isBlank(maxLines)) {
625             throw new RuntimeException("System parameter for max note lines is blank");
626         }
627 
628         return Integer.parseInt(maxLines);
629     }
630 
631     /**
632      * Helper method for subsituting message parameters and adding the message to the warning list.
633      * 
634      * @param warnings <code>List</code> of messages to add to
635      * @param messageKey resource key for message
636      * @param arguments message substitute parameters
637      */
638     protected void addWarningMessage(List<String> warnings, String messageKey, String... arguments) {
639         String message = kualiConfigurationService.getPropertyValueAsString(messageKey);
640         warnings.add(MessageFormat.format(message, (Object[]) arguments));
641     }
642 
643     /**
644      * Sets the customerProfileService attribute value.
645      * 
646      * @param customerProfileService The customerProfileService to set.
647      */
648     public void setCustomerProfileService(CustomerProfileService customerProfileService) {
649         this.customerProfileService = customerProfileService;
650     }
651 
652     /**
653      * Sets the paymentFileLoadDao attribute value.
654      * 
655      * @param paymentFileLoadDao The paymentFileLoadDao to set.
656      */
657     public void setPaymentFileLoadDao(PaymentFileLoadDao paymentFileLoadDao) {
658         this.paymentFileLoadDao = paymentFileLoadDao;
659     }
660 
661     /**
662      * Sets the parameterService attribute value.
663      * 
664      * @param parameterService The parameterService to set.
665      */
666     public void setParameterService(ParameterService parameterService) {
667         this.parameterService = parameterService;
668     }
669 
670     /**
671      * Sets the dateTimeService attribute value.
672      * 
673      * @param dateTimeService The dateTimeService to set.
674      */
675     public void setDateTimeService(DateTimeService dateTimeService) {
676         this.dateTimeService = dateTimeService;
677     }
678 
679     /**
680      * Sets the accountService attribute value.
681      * 
682      * @param accountService The accountService to set.
683      */
684     public void setAccountService(AccountService accountService) {
685         this.accountService = accountService;
686     }
687 
688     /**
689      * Sets the subAccountService attribute value.
690      * 
691      * @param subAccountService The subAccountService to set.
692      */
693     public void setSubAccountService(SubAccountService subAccountService) {
694         this.subAccountService = subAccountService;
695     }
696 
697     /**
698      * Sets the objectCodeService attribute value.
699      * 
700      * @param objectCodeService The objectCodeService to set.
701      */
702     public void setObjectCodeService(ObjectCodeService objectCodeService) {
703         this.objectCodeService = objectCodeService;
704     }
705 
706     /**
707      * Sets the subObjectCodeService attribute value.
708      * 
709      * @param subObjectCodeService The subObjectCodeService to set.
710      */
711     public void setSubObjectCodeService(SubObjectCodeService subObjectCodeService) {
712         this.subObjectCodeService = subObjectCodeService;
713     }
714 
715     /**
716      * Sets the kualiConfigurationService attribute value.
717      * 
718      * @param kualiConfigurationService The kualiConfigurationService to set.
719      */
720     public void setConfigurationService(ConfigurationService kualiConfigurationService) {
721         this.kualiConfigurationService = kualiConfigurationService;
722     }
723 
724     /**
725      * Sets the bankService attribute value.
726      * 
727      * @param bankService The bankService to set.
728      */
729     public void setBankService(BankService bankService) {
730         this.bankService = bankService;
731     }
732 
733     /**
734      * Sets the originationCodeService attribute value.
735      * 
736      * @param originationCodeService The originationCodeService to set.
737      */
738     public void setOriginationCodeService(OriginationCodeService originationCodeService) {
739         this.originationCodeService = originationCodeService;
740     }
741 
742     /**
743      * Gets the businessObjectService attribute.
744      * 
745      * @return Returns the businessObjectService.
746      */
747     protected BusinessObjectService getBusinessObjectService() {
748         return businessObjectService;
749     }
750 
751     /**
752      * Sets the businessObjectService attribute value.
753      * 
754      * @param businessObjectService The businessObjectService to set.
755      */
756     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
757         this.businessObjectService = businessObjectService;
758     }
759 
760     public void setDocumentTypeService(DocumentTypeService documentTypeService) {
761         this.documentTypeService = documentTypeService;
762     }
763 
764 }