001/*
002 * Copyright 2008 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 */
016package org.kuali.ole.pdp.service.impl;
017
018import java.sql.Timestamp;
019import java.text.MessageFormat;
020import java.text.ParseException;
021import java.util.ArrayList;
022import java.util.Calendar;
023import java.util.List;
024
025import org.apache.commons.lang.StringUtils;
026import org.kuali.ole.coa.businessobject.Account;
027import org.kuali.ole.coa.businessobject.ObjectCode;
028import org.kuali.ole.coa.businessobject.ProjectCode;
029import org.kuali.ole.coa.businessobject.SubAccount;
030import org.kuali.ole.coa.businessobject.SubObjectCode;
031import org.kuali.ole.coa.service.AccountService;
032import org.kuali.ole.coa.service.ObjectCodeService;
033import org.kuali.ole.coa.service.SubAccountService;
034import org.kuali.ole.coa.service.SubObjectCodeService;
035import org.kuali.ole.pdp.PdpConstants;
036import org.kuali.ole.pdp.PdpKeyConstants;
037import org.kuali.ole.pdp.PdpParameterConstants;
038import org.kuali.ole.pdp.PdpPropertyConstants;
039import org.kuali.ole.pdp.businessobject.AccountingChangeCode;
040import org.kuali.ole.pdp.businessobject.CustomerProfile;
041import org.kuali.ole.pdp.businessobject.PayeeType;
042import org.kuali.ole.pdp.businessobject.PaymentAccountDetail;
043import org.kuali.ole.pdp.businessobject.PaymentAccountHistory;
044import org.kuali.ole.pdp.businessobject.PaymentDetail;
045import org.kuali.ole.pdp.businessobject.PaymentFileLoad;
046import org.kuali.ole.pdp.businessobject.PaymentGroup;
047import org.kuali.ole.pdp.businessobject.PaymentStatus;
048import org.kuali.ole.pdp.dataaccess.PaymentFileLoadDao;
049import org.kuali.ole.pdp.service.CustomerProfileService;
050import org.kuali.ole.pdp.service.PaymentFileValidationService;
051import org.kuali.ole.sys.OLEConstants;
052import org.kuali.ole.sys.businessobject.Bank;
053import org.kuali.ole.sys.businessobject.OriginationCode;
054import org.kuali.ole.sys.service.BankService;
055import org.kuali.ole.sys.service.OriginationCodeService;
056import org.kuali.ole.sys.service.impl.OleParameterConstants;
057import org.kuali.rice.core.api.config.property.ConfigurationService;
058import org.kuali.rice.core.api.datetime.DateTimeService;
059import org.kuali.rice.core.api.util.type.KualiDecimal;
060import org.kuali.rice.coreservice.framework.parameter.ParameterService;
061import org.kuali.rice.kew.api.doctype.DocumentTypeService;
062import org.kuali.rice.krad.bo.KualiCodeBase;
063import org.kuali.rice.krad.service.BusinessObjectService;
064import org.kuali.rice.krad.util.MessageMap;
065import org.springframework.transaction.annotation.Transactional;
066
067/**
068 * @see org.kuali.ole.pdp.batch.service.PaymentFileValidationService
069 */
070@Transactional
071public class PaymentFileValidationServiceImpl implements PaymentFileValidationService {
072    protected CustomerProfileService customerProfileService;
073    protected PaymentFileLoadDao paymentFileLoadDao;
074    protected ParameterService parameterService;
075    protected ConfigurationService kualiConfigurationService;
076    protected DateTimeService dateTimeService;
077    protected AccountService accountService;
078    protected SubAccountService subAccountService;
079    protected ObjectCodeService objectCodeService;
080    protected SubObjectCodeService subObjectCodeService;
081    protected BankService bankService;
082    protected OriginationCodeService originationCodeService;
083    protected DocumentTypeService documentTypeService;
084    protected BusinessObjectService businessObjectService;
085
086    /**
087     * @see org.kuali.ole.pdp.batch.service.PaymentFileValidationService#doHardEdits(org.kuali.ole.pdp.businessobject.PaymentFile,
088     *      org.kuali.rice.krad.util.MessageMap)
089     */
090    @Override
091    public void doHardEdits(PaymentFileLoad paymentFile, MessageMap errorMap) {
092        processHeaderValidation(paymentFile, errorMap);
093
094        if (errorMap.hasNoErrors()) {
095            processGroupValidation(paymentFile, errorMap);
096        }
097
098        if (errorMap.hasNoErrors()) {
099            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}