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.pdp.service.impl;
017    
018    import java.sql.Timestamp;
019    import java.text.MessageFormat;
020    import java.text.ParseException;
021    import java.util.ArrayList;
022    import java.util.Calendar;
023    import java.util.List;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.kuali.kfs.coa.businessobject.Account;
027    import org.kuali.kfs.coa.businessobject.ObjectCode;
028    import org.kuali.kfs.coa.businessobject.ProjectCode;
029    import org.kuali.kfs.coa.businessobject.SubAccount;
030    import org.kuali.kfs.coa.businessobject.SubObjectCode;
031    import org.kuali.kfs.coa.service.AccountService;
032    import org.kuali.kfs.coa.service.ObjectCodeService;
033    import org.kuali.kfs.coa.service.ProjectCodeService;
034    import org.kuali.kfs.coa.service.SubAccountService;
035    import org.kuali.kfs.coa.service.SubObjectCodeService;
036    import org.kuali.kfs.pdp.PdpConstants;
037    import org.kuali.kfs.pdp.PdpKeyConstants;
038    import org.kuali.kfs.pdp.PdpParameterConstants;
039    import org.kuali.kfs.pdp.PdpPropertyConstants;
040    import org.kuali.kfs.pdp.businessobject.AccountingChangeCode;
041    import org.kuali.kfs.pdp.businessobject.CustomerProfile;
042    import org.kuali.kfs.pdp.businessobject.PayeeType;
043    import org.kuali.kfs.pdp.businessobject.PaymentAccountDetail;
044    import org.kuali.kfs.pdp.businessobject.PaymentAccountHistory;
045    import org.kuali.kfs.pdp.businessobject.PaymentDetail;
046    import org.kuali.kfs.pdp.businessobject.PaymentFileLoad;
047    import org.kuali.kfs.pdp.businessobject.PaymentGroup;
048    import org.kuali.kfs.pdp.businessobject.PaymentStatus;
049    import org.kuali.kfs.pdp.dataaccess.PaymentFileLoadDao;
050    import org.kuali.kfs.pdp.service.CustomerProfileService;
051    import org.kuali.kfs.pdp.service.PaymentFileValidationService;
052    import org.kuali.kfs.sys.KFSConstants;
053    import org.kuali.kfs.sys.businessobject.Bank;
054    import org.kuali.kfs.sys.businessobject.OriginationCode;
055    import org.kuali.kfs.sys.context.SpringContext;
056    import org.kuali.kfs.sys.service.BankService;
057    import org.kuali.kfs.sys.service.OriginationCodeService;
058    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
059    import org.kuali.rice.kew.exception.WorkflowException;
060    import org.kuali.rice.kns.bo.KualiCodeBase;
061    import org.kuali.rice.kns.service.BusinessObjectService;
062    import org.kuali.rice.kns.service.DateTimeService;
063    import org.kuali.rice.kns.service.KualiConfigurationService;
064    import org.kuali.rice.kns.service.ParameterService;
065    import org.kuali.rice.kns.util.KualiDecimal;
066    import org.kuali.rice.kns.util.MessageMap;
067    import org.kuali.rice.kns.workflow.service.KualiWorkflowInfo;
068    import org.springframework.transaction.annotation.Transactional;
069    
070    /**
071     * @see org.kuali.kfs.pdp.batch.service.PaymentFileValidationService
072     */
073    @Transactional
074    public class PaymentFileValidationServiceImpl implements PaymentFileValidationService {
075        private CustomerProfileService customerProfileService;
076        private PaymentFileLoadDao paymentFileLoadDao;
077        private ParameterService parameterService;
078        private KualiConfigurationService kualiConfigurationService;
079        private DateTimeService dateTimeService;
080        private AccountService accountService;
081        private SubAccountService subAccountService;
082        private ObjectCodeService objectCodeService;
083        private SubObjectCodeService subObjectCodeService;
084        private ProjectCodeService projectCodeService;
085        private BankService bankService;
086        private OriginationCodeService originationCodeService;
087        private KualiWorkflowInfo workflowInfoService;
088        private BusinessObjectService businessObjectService;
089    
090        /**
091         * @see org.kuali.kfs.pdp.batch.service.PaymentFileValidationService#doHardEdits(org.kuali.kfs.pdp.businessobject.PaymentFile,
092         *      org.kuali.rice.kns.util.ErrorMap)
093         */
094        public void doHardEdits(PaymentFileLoad paymentFile, MessageMap errorMap) {
095            processHeaderValidation(paymentFile, errorMap);
096    
097            if (errorMap.hasNoErrors()) {
098                processGroupValidation(paymentFile, errorMap);
099            }
100    
101            if (errorMap.hasNoErrors()) {
102                processTrailerValidation(paymentFile, errorMap);
103            }
104        }
105    
106        /**
107         * Validates payment file header fields <li>Checks customer exists in customer profile table and is active</li>
108         * 
109         * @param paymentFile payment file object
110         * @param errorMap map in which errors will be added to
111         */
112        protected void processHeaderValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
113            CustomerProfile customer = customerProfileService.get(paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
114            if (customer == null) {
115                errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_CUSTOMER, paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
116            }
117            else {
118                if (!customer.isActive()) {
119                    errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INACTIVE_CUSTOMER, paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
120                }
121                else {
122                    paymentFile.setCustomer(customer);
123                }
124            }
125        }
126    
127        /**
128         * Validates payment file trailer fields <li>Reconciles actual to expected payment count and totals</li> <li>Verifies the batch
129         * is not a duplicate</li>
130         * 
131         * @param paymentFile payment file object
132         * @param errorMap map in which errors will be added to
133         */
134        protected void processTrailerValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
135            // compare trailer payment count to actual count loaded
136            if (paymentFile.getActualPaymentCount() != paymentFile.getPaymentCount()) {
137                errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYMENT_COUNT_MISMATCH, Integer.toString(paymentFile.getPaymentCount()), Integer.toString(paymentFile.getActualPaymentCount()));
138            }
139    
140            // compare trailer total amount with actual total amount
141            if (paymentFile.getCalculatedPaymentTotalAmount().compareTo(paymentFile.getPaymentTotalAmount()) != 0) {
142                errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYMENT_TOTAL_MISMATCH, paymentFile.getPaymentTotalAmount().toString(), paymentFile.getCalculatedPaymentTotalAmount().toString());
143            }
144    
145            // Check to see if this is a duplicate batch
146            Timestamp now = new Timestamp(paymentFile.getCreationDate().getTime());
147    
148            if (paymentFileLoadDao.isDuplicateBatch(paymentFile.getCustomer(), paymentFile.getPaymentCount(), paymentFile.getPaymentTotalAmount().bigDecimalValue(), now)) {
149                errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_DUPLICATE_BATCH);
150            }
151        }
152    
153        /**
154         * Validates payment file groups <li>Checks number of note lines needed is not above the configured maximum allowed</li> <li>
155         * Verifies group total is not negative</li> <li>Verifies detail accounting total equals net payment amount</li>
156         * 
157         * @param paymentFile payment file object
158         * @param errorMap map in which errors will be added to
159         */
160        protected void processGroupValidation(PaymentFileLoad paymentFile, MessageMap errorMap) {
161            int groupCount = 0;
162            for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
163                groupCount++;
164    
165                int noteLineCount = 0;
166                int detailCount = 0;
167    
168                // verify payee id and owner code if customer requires them to be filled in
169                if (paymentFile.getCustomer().getPayeeIdRequired() && StringUtils.isBlank(paymentGroup.getPayeeId())) {
170                    errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYEE_ID_REQUIRED, Integer.toString(groupCount));
171                }
172    
173                if (paymentFile.getCustomer().getOwnershipCodeRequired() && StringUtils.isBlank(paymentGroup.getPayeeOwnerCd())) {
174                    errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_PAYEE_OWNER_CODE, Integer.toString(groupCount));
175                }
176    
177                // validate payee id type
178                if (StringUtils.isNotBlank(paymentGroup.getPayeeIdTypeCd())) {
179                    PayeeType payeeType = (PayeeType) businessObjectService.findBySinglePrimaryKey(PayeeType.class, paymentGroup.getPayeeIdTypeCd());
180                    if (payeeType == null) {
181                        errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_PAYEE_ID_TYPE, Integer.toString(groupCount), paymentGroup.getPayeeIdTypeCd());
182                    }
183                }
184    
185                // validate bank
186                String bankCode = paymentGroup.getBankCode();
187                if (StringUtils.isNotBlank(bankCode)) {
188                    Bank bank = bankService.getByPrimaryId(bankCode);
189                    if (bank == null) {
190                        errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_BANK_CODE, Integer.toString(groupCount), bankCode);
191                    }
192                    else if (!bank.isActive()) {
193                        errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INACTIVE_BANK_CODE, Integer.toString(groupCount), bankCode);
194                    }
195                }
196    
197                KualiDecimal groupTotal = KualiDecimal.ZERO;
198                for (PaymentDetail paymentDetail : paymentGroup.getPaymentDetails()) {
199                    detailCount++;
200    
201                    noteLineCount++; // Add a line to print the invoice number
202                    noteLineCount = noteLineCount + paymentDetail.getNotes().size();
203    
204                    if ((paymentDetail.getNetPaymentAmount() == null) && (!paymentDetail.isDetailAmountProvided())) {
205                        paymentDetail.setNetPaymentAmount(paymentDetail.getAccountTotal());
206                    }
207                    else if ((paymentDetail.getNetPaymentAmount() == null) && (paymentDetail.isDetailAmountProvided())) {
208                        paymentDetail.setNetPaymentAmount(paymentDetail.getCalculatedPaymentAmount());
209                    }
210    
211                    // compare net to accounting segments
212                    if (paymentDetail.getAccountTotal().compareTo(paymentDetail.getNetPaymentAmount()) != 0) {
213                        errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_DETAIL_TOTAL_MISMATCH, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getAccountTotal().toString(), paymentDetail.getNetPaymentAmount().toString());
214                    }
215    
216                    // validate origin code if given
217                    if (StringUtils.isNotBlank(paymentDetail.getFinancialSystemOriginCode())) {
218                        OriginationCode originationCode = originationCodeService.getByPrimaryKey(paymentDetail.getFinancialSystemOriginCode());
219                        if (originationCode == null) {
220                            errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_ORIGIN_CODE, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getFinancialSystemOriginCode());
221                        }
222                    }
223    
224                    // validate doc type if given
225                    if (StringUtils.isNotBlank(paymentDetail.getFinancialDocumentTypeCode())) {
226                        try {
227                            if (!workflowInfoService.isCurrentActiveDocumentType(paymentDetail.getFinancialDocumentTypeCode())) {
228                                errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_INVALID_DOC_TYPE, Integer.toString(groupCount), Integer.toString(detailCount), paymentDetail.getFinancialDocumentTypeCode());
229                            }
230                        }
231                        catch (WorkflowException e) {
232                            throw new RuntimeException("error trying to validate document type: " + e.getMessage(), e);
233                        }
234                    }
235    
236                    groupTotal = groupTotal.add(paymentDetail.getNetPaymentAmount());
237                }
238    
239                // verify total for group is not negative
240                if (groupTotal.doubleValue() < 0) {
241                    errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_NEGATIVE_GROUP_TOTAL, Integer.toString(groupCount));
242                }
243    
244                // check that the number of detail items and note lines will fit on a check stub
245                if (noteLineCount > getMaxNoteLines()) {
246                    errorMap.putError(KFSConstants.GLOBAL_ERRORS, PdpKeyConstants.ERROR_PAYMENT_LOAD_MAX_NOTE_LINES, Integer.toString(groupCount), Integer.toString(noteLineCount), Integer.toString(getMaxNoteLines()));
247                }
248            }
249        }
250    
251        /**
252         * @see org.kuali.kfs.pdp.service.PaymentFileValidationService#doSoftEdits(org.kuali.kfs.pdp.businessobject.PaymentFile)
253         */
254        public List<String> doSoftEdits(PaymentFileLoad paymentFile) {
255            List<String> warnings = new ArrayList<String>();
256    
257            CustomerProfile customer = customerProfileService.get(paymentFile.getChart(), paymentFile.getUnit(), paymentFile.getSubUnit());
258    
259            // check payment amount does not exceed the configured threshold amount of this customer
260            if (paymentFile.getPaymentTotalAmount().compareTo(customer.getFileThresholdAmount()) > 0) {
261                addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_FILE_THRESHOLD, paymentFile.getPaymentTotalAmount().toString(), customer.getFileThresholdAmount().toString());
262                paymentFile.setFileThreshold(true);
263            }
264    
265            processGroupSoftEdits(paymentFile, customer, warnings);
266    
267            return warnings;
268        }
269    
270        /**
271         * Set defaults for group fields and do tax checks.
272         * 
273         * @param paymentFile payment file object
274         * @param customer payment customer
275         * @param warnings <code>List</code> list of accumulated warning messages
276         */
277        public void processGroupSoftEdits(PaymentFileLoad paymentFile, CustomerProfile customer, List<String> warnings) {
278            PaymentStatus openStatus = (PaymentStatus) businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.OPEN);
279    
280            for (PaymentGroup paymentGroup : paymentFile.getPaymentGroups()) {
281                paymentGroup.setBatchId(paymentFile.getBatchId());
282                paymentGroup.setPaymentStatusCode(openStatus.getCode());
283                paymentGroup.setPaymentStatus(openStatus);
284                paymentGroup.setPayeeName(paymentGroup.getPayeeName().toUpperCase());
285    
286                // Set defaults for missing information
287                defaultGroupIndicators(paymentGroup);
288    
289                // Tax Group Requirements for automatic Holding
290                checkForTaxEmailRequired(paymentFile, paymentGroup, customer);
291    
292                // do edits on detail lines
293                for (PaymentDetail paymentDetail : paymentGroup.getPaymentDetails()) {
294                    paymentDetail.setPaymentGroupId(paymentGroup.getId());
295    
296                    processDetailSoftEdits(paymentFile, customer, paymentDetail, warnings);
297                }
298    
299            }
300        }
301    
302        /**
303         * Set default fields on detail line and check amount against customer threshold.
304         * 
305         * @param paymentFile payment file object
306         * @param customer payment customer
307         * @param paymentDetail <code>PaymentDetail</code> object to process
308         * @param warnings <code>List</code> list of accumulated warning messages
309         */
310        protected void processDetailSoftEdits(PaymentFileLoad paymentFile, CustomerProfile customer, PaymentDetail paymentDetail, List<String> warnings) {
311            updateDetailAmounts(paymentDetail);
312    
313            // Check net payment amount
314            KualiDecimal testAmount = paymentDetail.getNetPaymentAmount();
315            if (testAmount.compareTo(customer.getPaymentThresholdAmount()) > 0) {
316                addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_DETAIL_THRESHOLD, testAmount.toString(), customer.getPaymentThresholdAmount().toString());
317                paymentFile.setDetailThreshold(true);
318                paymentFile.getThresholdPaymentDetails().add(paymentDetail);
319            }
320    
321            // set invoice date if it doesn't exist
322            if (paymentDetail.getInvoiceDate() == null) {
323                paymentDetail.setInvoiceDate(dateTimeService.getCurrentSqlDate());
324            }
325    
326            if (paymentDetail.getPrimaryCancelledPayment() == null) {
327                paymentDetail.setPrimaryCancelledPayment(Boolean.FALSE);
328            }
329    
330            // do accounting edits
331            for (PaymentAccountDetail paymentAccountDetail : paymentDetail.getAccountDetail()) {
332                paymentAccountDetail.setPaymentDetailId(paymentDetail.getId());
333    
334                processAccountSoftEdits(paymentFile, customer, paymentAccountDetail, warnings);
335            }
336        }
337    
338        /**
339         * Set default fields on account line and perform account field existence checks
340         * 
341         * @param paymentFile payment file object
342         * @param customer payment customer
343         * @param paymentAccountDetail <code>PaymentAccountDetail</code> object to process
344         * @param warnings <code>List</code> list of accumulated warning messages
345         */
346        protected void processAccountSoftEdits(PaymentFileLoad paymentFile, CustomerProfile customer, PaymentAccountDetail paymentAccountDetail, List<String> warnings) {
347            List<PaymentAccountHistory> changeRecords = paymentAccountDetail.getAccountHistory();
348    
349            // uppercase chart
350            paymentAccountDetail.setFinChartCode(paymentAccountDetail.getFinChartCode().toUpperCase());
351    
352            // only do accounting edits if required by customer
353            if (customer.getAccountingEditRequired()) {
354                // check account number
355                Account account = accountService.getByPrimaryId(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr());
356                if (account == null) {
357                    addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_ACCOUNT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr());
358    
359                    KualiCodeBase objChangeCd = (KualiCodeBase) businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_ACCOUNT);
360                    replaceAccountingString(objChangeCd, changeRecords, customer, paymentAccountDetail);
361                }
362                else {
363                    // check sub account code
364                    if (StringUtils.isNotBlank(paymentAccountDetail.getSubAccountNbr())) {
365                        SubAccount subAccount = subAccountService.getByPrimaryId(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getSubAccountNbr());
366                        if (subAccount == null) {
367                            addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_SUB_ACCOUNT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getSubAccountNbr());
368    
369                            KualiCodeBase objChangeCd = (KualiCodeBase) businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_SUB_ACCOUNT);
370                            changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_ACCOUNT_DB_COLUMN_NAME, KFSConstants.getDashSubAccountNumber(), paymentAccountDetail.getSubAccountNbr(), objChangeCd));
371    
372                            paymentAccountDetail.setSubAccountNbr(KFSConstants.getDashSubAccountNumber());
373                        }
374                    }
375    
376                    // check object code
377                    ObjectCode objectCode = objectCodeService.getByPrimaryIdForCurrentYear(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getFinObjectCode());
378                    if (objectCode == null) {
379                        addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_OBJECT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getFinObjectCode());
380    
381                        KualiCodeBase objChangeCd = (KualiCodeBase) businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_OBJECT);
382                        replaceAccountingString(objChangeCd, changeRecords, customer, paymentAccountDetail);
383                    }
384    
385                    // check sub object code
386                    else if (StringUtils.isNotBlank(paymentAccountDetail.getFinSubObjectCode())) {
387                        SubObjectCode subObjectCode = subObjectCodeService.getByPrimaryIdForCurrentYear(paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getFinObjectCode(), paymentAccountDetail.getFinSubObjectCode());
388                        if (subObjectCode == null) {
389                            addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_SUB_OBJECT, paymentAccountDetail.getFinChartCode(), paymentAccountDetail.getAccountNbr(), paymentAccountDetail.getFinObjectCode(), paymentAccountDetail.getFinSubObjectCode());
390    
391                            KualiCodeBase objChangeCd = (KualiCodeBase) businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_SUB_OBJECT);
392                            changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_OBJECT_DB_COLUMN_NAME, KFSConstants.getDashFinancialSubObjectCode(), paymentAccountDetail.getFinSubObjectCode(), objChangeCd));
393    
394                            paymentAccountDetail.setFinSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
395                        }
396                    }
397                }
398    
399                // check project code
400                if (StringUtils.isNotBlank(paymentAccountDetail.getProjectCode())) {
401                    ProjectCode projectCode = projectCodeService.getByPrimaryId(paymentAccountDetail.getProjectCode());
402                    if (projectCode == null) {
403                        addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_INVALID_PROJECT, paymentAccountDetail.getProjectCode());
404    
405                        KualiCodeBase objChangeCd = (KualiCodeBase) businessObjectService.findBySinglePrimaryKey(AccountingChangeCode.class, PdpConstants.AccountChangeCodes.INVALID_PROJECT);
406                        changeRecords.add(newAccountHistory(PdpPropertyConstants.PROJECT_DB_COLUMN_NAME, KFSConstants.getDashProjectCode(), paymentAccountDetail.getProjectCode(), objChangeCd));
407                        paymentAccountDetail.setProjectCode(KFSConstants.getDashProjectCode());
408                    }
409                }
410            }
411    
412            // change nulls into ---'s for the fields that need it
413            if (StringUtils.isBlank(paymentAccountDetail.getFinSubObjectCode())) {
414                paymentAccountDetail.setFinSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
415            }
416    
417            if (StringUtils.isBlank(paymentAccountDetail.getSubAccountNbr())) {
418                paymentAccountDetail.setSubAccountNbr(KFSConstants.getDashSubAccountNumber());
419            }
420    
421            if (StringUtils.isBlank(paymentAccountDetail.getProjectCode())) {
422                paymentAccountDetail.setProjectCode(KFSConstants.getDashProjectCode());
423            }
424    
425        }
426    
427        /**
428         * Replaces the entire accounting string with defaults from the customer profile.
429         * 
430         * @param objChangeCd code indicating reason for change
431         * @param changeRecords <code>List</code> of <code>PaymentAccountHistory</code> records
432         * @param customer profile of payment customer
433         * @param paymentAccountDetail account detail record
434         */
435        protected void replaceAccountingString(KualiCodeBase objChangeCd, List<PaymentAccountHistory> changeRecords, CustomerProfile customer, PaymentAccountDetail paymentAccountDetail) {
436            changeRecords.add(newAccountHistory(PdpPropertyConstants.CHART_DB_COLUMN_NAME, customer.getDefaultChartCode(), paymentAccountDetail.getFinChartCode(), objChangeCd));
437            changeRecords.add(newAccountHistory(PdpPropertyConstants.ACCOUNT_DB_COLUMN_NAME, customer.getDefaultAccountNumber(), paymentAccountDetail.getAccountNbr(), objChangeCd));
438            changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_ACCOUNT_DB_COLUMN_NAME, customer.getDefaultSubAccountNumber(), paymentAccountDetail.getSubAccountNbr(), objChangeCd));
439            changeRecords.add(newAccountHistory(PdpPropertyConstants.OBJECT_DB_COLUMN_NAME, customer.getDefaultObjectCode(), paymentAccountDetail.getFinObjectCode(), objChangeCd));
440            changeRecords.add(newAccountHistory(PdpPropertyConstants.SUB_OBJECT_DB_COLUMN_NAME, customer.getDefaultSubObjectCode(), paymentAccountDetail.getFinSubObjectCode(), objChangeCd));
441    
442            paymentAccountDetail.setFinChartCode(customer.getDefaultChartCode());
443            paymentAccountDetail.setAccountNbr(customer.getDefaultAccountNumber());
444            if (StringUtils.isNotBlank(customer.getDefaultSubAccountNumber())) {
445                paymentAccountDetail.setSubAccountNbr(customer.getDefaultSubAccountNumber());
446            }
447            else {
448                paymentAccountDetail.setSubAccountNbr(KFSConstants.getDashSubAccountNumber());
449            }
450            paymentAccountDetail.setFinObjectCode(customer.getDefaultObjectCode());
451            if (StringUtils.isNotBlank(customer.getDefaultSubAccountNumber())) {
452                paymentAccountDetail.setFinSubObjectCode(customer.getDefaultSubObjectCode());
453            }
454            else {
455                paymentAccountDetail.setFinSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
456            }
457        }
458    
459        /**
460         * Helper method to construct a new payment account history record
461         * 
462         * @param attName name of field that has changed
463         * @param newValue new value for the field
464         * @param oldValue field value that was changed
465         * @param changeCode code indicating reason for change
466         * @return <code>PaymentAccountHistory</code>
467         */
468        protected PaymentAccountHistory newAccountHistory(String attName, String newValue, String oldValue, KualiCodeBase changeCode) {
469            PaymentAccountHistory paymentAccountHistory = new PaymentAccountHistory();
470    
471            paymentAccountHistory.setAcctAttributeName(attName);
472            paymentAccountHistory.setAcctAttributeNewValue(newValue);
473            paymentAccountHistory.setAcctAttributeOrigValue(oldValue);
474            paymentAccountHistory.setAcctChangeDate(dateTimeService.getCurrentTimestamp());
475            paymentAccountHistory.setAccountingChange((AccountingChangeCode) changeCode);
476    
477            return paymentAccountHistory;
478        }
479    
480        /**
481         * Sets null amount fields to 0
482         * 
483         * @param paymentDetail <code>PaymentDetail</code> to update
484         */
485        protected void updateDetailAmounts(PaymentDetail paymentDetail) {
486            KualiDecimal zero = KualiDecimal.ZERO;
487    
488            if (paymentDetail.getInvTotDiscountAmount() == null) {
489                paymentDetail.setInvTotDiscountAmount(zero);
490            }
491    
492            if (paymentDetail.getInvTotShipAmount() == null) {
493                paymentDetail.setInvTotShipAmount(zero);
494            }
495    
496            if (paymentDetail.getInvTotOtherDebitAmount() == null) {
497                paymentDetail.setInvTotOtherDebitAmount(zero);
498            }
499    
500            if (paymentDetail.getInvTotOtherCreditAmount() == null) {
501                paymentDetail.setInvTotOtherCreditAmount(zero);
502            }
503    
504            // update the total payment amount with the amount from the accounts if null
505            if (paymentDetail.getNetPaymentAmount() == null) {
506                paymentDetail.setNetPaymentAmount(paymentDetail.getAccountTotal());
507            }
508    
509            if (paymentDetail.getOrigInvoiceAmount() == null) {
510                KualiDecimal amt = paymentDetail.getNetPaymentAmount();
511                amt = amt.add(paymentDetail.getInvTotDiscountAmount());
512                amt = amt.subtract(paymentDetail.getInvTotShipAmount());
513                amt = amt.subtract(paymentDetail.getInvTotOtherDebitAmount());
514                amt = amt.add(paymentDetail.getInvTotOtherCreditAmount());
515                paymentDetail.setOrigInvoiceAmount(amt);
516            }
517        }
518    
519        /**
520         * Sets null indicators to false
521         * 
522         * @param paymentGroup <code>PaymentGroup</code> to update
523         */
524        protected void defaultGroupIndicators(PaymentGroup paymentGroup) {
525            // combineGroups column does not accept null values, so it will never be null
526            /*
527             * if (paymentGroup.getCombineGroups() == null) { paymentGroup.setCombineGroups(Boolean.TRUE); }
528             */
529    
530            if (paymentGroup.getCampusAddress() == null) {
531                paymentGroup.setCampusAddress(Boolean.FALSE);
532            }
533    
534            if (paymentGroup.getPymtAttachment() == null) {
535                paymentGroup.setPymtAttachment(Boolean.FALSE);
536            }
537    
538            if (paymentGroup.getPymtSpecialHandling() == null) {
539                paymentGroup.setPymtSpecialHandling(Boolean.FALSE);
540            }
541    
542            if (paymentGroup.getProcessImmediate() == null) {
543                paymentGroup.setProcessImmediate(Boolean.FALSE);
544            }
545    
546            if (paymentGroup.getEmployeeIndicator() == null) {
547                paymentGroup.setEmployeeIndicator(Boolean.FALSE);
548            }
549    
550            if (paymentGroup.getNraPayment() == null) {
551                paymentGroup.setNraPayment(Boolean.FALSE);
552            }
553    
554            if (paymentGroup.getTaxablePayment() == null) {
555                paymentGroup.setTaxablePayment(Boolean.FALSE);
556            }
557        }
558    
559        /**
560         * Checks whether payment status should be set to held and a tax email sent indicating so
561         * 
562         * @param paymentFile payment file object
563         * @param paymentGroup <code>PaymentGroup</code> being checked
564         * @param customer payment customer
565         */
566        protected void checkForTaxEmailRequired(PaymentFileLoad paymentFile, PaymentGroup paymentGroup, CustomerProfile customer) {
567            PaymentStatus heldForNRAEmployee = (PaymentStatus) businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.HELD_TAX_NRA_EMPL_CD);
568            PaymentStatus heldForEmployee = (PaymentStatus) businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.HELD_TAX_EMPLOYEE_CD);
569            PaymentStatus heldForNRA = (PaymentStatus) businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.HELD_TAX_NRA_CD);
570    
571            if (customer.getNraReview() && customer.getEmployeeCheck() && paymentGroup.getEmployeeIndicator().booleanValue() && paymentGroup.getNraPayment().booleanValue()) {
572                paymentGroup.setPaymentStatus(heldForNRAEmployee);
573                paymentFile.setTaxEmailRequired(true);
574            }
575    
576            else if (customer.getEmployeeCheck() && paymentGroup.getEmployeeIndicator().booleanValue()) {
577                paymentGroup.setPaymentStatus(heldForEmployee);
578                paymentFile.setTaxEmailRequired(true);
579            }
580    
581            else if (customer.getNraReview() && paymentGroup.getNraPayment().booleanValue()) {
582                paymentGroup.setPaymentStatus(heldForNRA);
583                paymentFile.setTaxEmailRequired(true);
584            }
585        }
586    
587        /**
588         * Checks the payment date is not more than 30 days past or 30 days coming
589         * 
590         * @param paymentGroup <code>PaymentGroup</code> being checked
591         * @param warnings <code>List</code> list of accumulated warning messages
592         */
593        protected void checkGroupPaymentDate(PaymentGroup paymentGroup, List<String> warnings) {
594            Timestamp now = dateTimeService.getCurrentTimestamp();
595    
596            Calendar nowPlus30 = Calendar.getInstance();
597            nowPlus30.setTime(now);
598            nowPlus30.add(Calendar.DATE, 30);
599    
600            Calendar nowMinus30 = Calendar.getInstance();
601            nowMinus30.setTime(now);
602            nowMinus30.add(Calendar.DATE, -30);
603    
604            if (paymentGroup.getPaymentDate() != null) {
605                Calendar payDate = Calendar.getInstance();
606                payDate.setTime(paymentGroup.getPaymentDate());
607    
608                if (payDate.before(nowMinus30)) {
609                    addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_PAYDATE_OVER_30_DAYS_PAST, dateTimeService.toDateString(paymentGroup.getPaymentDate()));
610                }
611    
612                if (payDate.after(nowPlus30)) {
613                    addWarningMessage(warnings, PdpKeyConstants.MESSAGE_PAYMENT_LOAD_PAYDATE_OVER_30_DAYS_OUT, dateTimeService.toDateString(paymentGroup.getPaymentDate()));
614                }
615            }
616            else {
617                try {
618                    paymentGroup.setPaymentDate(dateTimeService.convertToSqlDate(now));
619                }
620                catch (ParseException e) {
621                    throw new RuntimeException("Unable to parse current timestamp into sql date " + e.getMessage());
622                }
623            }
624        }
625    
626        /**
627         * @return system parameter value giving the maximum number of notes allowed.
628         */
629        protected int getMaxNoteLines() {
630            String maxLines = parameterService.getParameterValue(KfsParameterConstants.PRE_DISBURSEMENT_ALL.class, PdpParameterConstants.MAX_NOTE_LINES);
631            if (StringUtils.isBlank(maxLines)) {
632                throw new RuntimeException("System parameter for max note lines is blank");
633            }
634    
635            return Integer.parseInt(maxLines);
636        }
637    
638        /**
639         * Helper method for subsituting message parameters and adding the message to the warning list.
640         * 
641         * @param warnings <code>List</code> of messages to add to
642         * @param messageKey resource key for message
643         * @param arguments message substitute parameters
644         */
645        protected void addWarningMessage(List<String> warnings, String messageKey, String... arguments) {
646            String message = kualiConfigurationService.getPropertyString(messageKey);
647            warnings.add(MessageFormat.format(message, (Object[]) arguments));
648        }
649    
650        /**
651         * Sets the customerProfileService attribute value.
652         * 
653         * @param customerProfileService The customerProfileService to set.
654         */
655        public void setCustomerProfileService(CustomerProfileService customerProfileService) {
656            this.customerProfileService = customerProfileService;
657        }
658    
659        /**
660         * Sets the paymentFileLoadDao attribute value.
661         * 
662         * @param paymentFileLoadDao The paymentFileLoadDao to set.
663         */
664        public void setPaymentFileLoadDao(PaymentFileLoadDao paymentFileLoadDao) {
665            this.paymentFileLoadDao = paymentFileLoadDao;
666        }
667    
668        /**
669         * Sets the parameterService attribute value.
670         * 
671         * @param parameterService The parameterService to set.
672         */
673        public void setParameterService(ParameterService parameterService) {
674            this.parameterService = parameterService;
675        }
676    
677        /**
678         * Sets the dateTimeService attribute value.
679         * 
680         * @param dateTimeService The dateTimeService to set.
681         */
682        public void setDateTimeService(DateTimeService dateTimeService) {
683            this.dateTimeService = dateTimeService;
684        }
685    
686        /**
687         * Sets the accountService attribute value.
688         * 
689         * @param accountService The accountService to set.
690         */
691        public void setAccountService(AccountService accountService) {
692            this.accountService = accountService;
693        }
694    
695        /**
696         * Sets the subAccountService attribute value.
697         * 
698         * @param subAccountService The subAccountService to set.
699         */
700        public void setSubAccountService(SubAccountService subAccountService) {
701            this.subAccountService = subAccountService;
702        }
703    
704        /**
705         * Sets the objectCodeService attribute value.
706         * 
707         * @param objectCodeService The objectCodeService to set.
708         */
709        public void setObjectCodeService(ObjectCodeService objectCodeService) {
710            this.objectCodeService = objectCodeService;
711        }
712    
713        /**
714         * Sets the subObjectCodeService attribute value.
715         * 
716         * @param subObjectCodeService The subObjectCodeService to set.
717         */
718        public void setSubObjectCodeService(SubObjectCodeService subObjectCodeService) {
719            this.subObjectCodeService = subObjectCodeService;
720        }
721    
722        /**
723         * Sets the projectCodeService attribute value.
724         * 
725         * @param projectCodeService The projectCodeService to set.
726         */
727        public void setProjectCodeService(ProjectCodeService projectCodeService) {
728            this.projectCodeService = projectCodeService;
729        }
730    
731        /**
732         * Sets the kualiConfigurationService attribute value.
733         * 
734         * @param kualiConfigurationService The kualiConfigurationService to set.
735         */
736        public void setKualiConfigurationService(KualiConfigurationService kualiConfigurationService) {
737            this.kualiConfigurationService = kualiConfigurationService;
738        }
739    
740        /**
741         * Sets the bankService attribute value.
742         * 
743         * @param bankService The bankService to set.
744         */
745        public void setBankService(BankService bankService) {
746            this.bankService = bankService;
747        }
748    
749        /**
750         * Sets the originationCodeService attribute value.
751         * 
752         * @param originationCodeService The originationCodeService to set.
753         */
754        public void setOriginationCodeService(OriginationCodeService originationCodeService) {
755            this.originationCodeService = originationCodeService;
756        }
757    
758        /**
759         * Gets the workflowInfoService attribute.
760         * 
761         * @return Returns the workflowInfoService.
762         */
763        protected KualiWorkflowInfo getWorkflowInfoService() {
764            return workflowInfoService;
765        }
766    
767        /**
768         * Sets the workflowInfoService attribute value.
769         * 
770         * @param workflowInfoService The workflowInfoService to set.
771         */
772        public void setWorkflowInfoService(KualiWorkflowInfo workflowInfoService) {
773            this.workflowInfoService = workflowInfoService;
774        }
775    
776        /**
777         * Gets the businessObjectService attribute.
778         * 
779         * @return Returns the businessObjectService.
780         */
781        protected BusinessObjectService getBusinessObjectService() {
782            return businessObjectService;
783        }
784    
785        /**
786         * Sets the businessObjectService attribute value.
787         * 
788         * @param businessObjectService The businessObjectService to set.
789         */
790        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
791            this.businessObjectService = businessObjectService;
792        }
793    
794    }