001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.fp.document.validation.impl;
017    
018    import java.text.MessageFormat;
019    import java.util.ArrayList;
020    import java.util.Arrays;
021    import java.util.List;
022    
023    import org.apache.commons.lang.StringUtils;
024    import org.kuali.kfs.fp.businessobject.DisbursementVoucherNonEmployeeTravel;
025    import org.kuali.kfs.fp.businessobject.DisbursementVoucherWireTransfer;
026    import org.kuali.kfs.fp.businessobject.options.PaymentReasonValuesFinder;
027    import org.kuali.kfs.fp.document.DisbursementVoucherConstants;
028    import org.kuali.kfs.fp.document.DisbursementVoucherDocument;
029    import org.kuali.kfs.sys.KFSConstants;
030    import org.kuali.kfs.sys.KFSKeyConstants;
031    import org.kuali.kfs.sys.KFSPropertyConstants;
032    import org.kuali.kfs.sys.businessobject.Bank;
033    import org.kuali.kfs.sys.context.SpringContext;
034    import org.kuali.kfs.sys.service.BankService;
035    import org.kuali.rice.core.util.KeyLabelPair;
036    import org.kuali.rice.kew.exception.WorkflowException;
037    import org.kuali.rice.kns.document.Document;
038    import org.kuali.rice.kns.rules.PromptBeforeValidationBase;
039    import org.kuali.rice.kns.service.KualiConfigurationService;
040    import org.kuali.rice.kns.service.ParameterService;
041    import org.kuali.rice.kns.util.ObjectUtils;
042    
043    /**
044     * Checks warnings and prompt conditions for dv document.
045     */
046    public class DisbursementVoucherDocumentPreRules extends PromptBeforeValidationBase implements DisbursementVoucherConstants {
047    
048        /**
049         * Executes pre-rules for Disbursement Voucher Document
050         * 
051         * @param document submitted document
052         * @return true if pre-rules execute successfully
053         * @see org.kuali.rice.kns.rules.PromptBeforeValidationBase#doRules(org.kuali.rice.kns.document.MaintenanceDocument)
054         */
055        @Override
056        public boolean doPrompts(Document document) {
057            boolean preRulesOK = true;
058    
059            DisbursementVoucherDocument dvDocument = (DisbursementVoucherDocument) document;
060            checkSpecialHandlingIndicator(dvDocument);
061    
062            preRulesOK &= checkNonEmployeeTravelTabState(dvDocument);
063    
064            preRulesOK &= checkWireTransferTabState(dvDocument);
065    
066            preRulesOK &= checkForeignDraftTabState(dvDocument);
067    
068            preRulesOK &= checkBankCodeActive(dvDocument);
069    
070            return preRulesOK;
071        }
072    
073        /**
074         * If the special handling name and address 1 fields have value, this will mark the special handling indicator for the user.
075         * 
076         * @param dvDocument submitted disbursement voucher document
077         */
078        protected void checkSpecialHandlingIndicator(DisbursementVoucherDocument dvDocument) {
079            if (StringUtils.isNotBlank(dvDocument.getDvPayeeDetail().getDisbVchrSpecialHandlingPersonName()) && StringUtils.isNotBlank(dvDocument.getDvPayeeDetail().getDisbVchrSpecialHandlingLine1Addr()) && allowTurningOnOfSpecialHandling(dvDocument)) {
080                dvDocument.setDisbVchrSpecialHandlingCode(true);
081            }
082        }
083        
084        /**
085         * Allows the automatic turning on of special handling indicator - which will not be allowed at the Campus route level
086         * @param dvDocument the document to allow turning on of special handling for
087         * @return true if special handling can be automatically turned on, false otherwise
088         */
089        protected boolean allowTurningOnOfSpecialHandling(DisbursementVoucherDocument dvDocument) {
090            try {
091                List<String> currentNodes = Arrays.asList(dvDocument.getDocumentHeader().getWorkflowDocument().getNodeNames());
092                return !(currentNodes.contains(DisbursementVoucherConstants.RouteLevelNames.CAMPUS));
093            }
094            catch (WorkflowException we) {
095                throw new RuntimeException("Workflow Exception while attempting to check route levels", we);
096            }
097        }
098    
099        /**
100         * This method checks non-employee travel tab state is valid
101         * 
102         * @param dvDocument submitted disbursement voucher document
103         * @return true if the state of all the tabs is valid, false otherwise.
104         */
105        protected boolean checkNonEmployeeTravelTabState(DisbursementVoucherDocument dvDocument) {
106            boolean tabStatesOK = true;
107    
108            DisbursementVoucherNonEmployeeTravel dvNonEmplTrav = dvDocument.getDvNonEmployeeTravel();
109            if (!hasNonEmployeeTravelValues(dvNonEmplTrav)) {
110                return true;
111            }
112    
113            String paymentReasonCode = dvDocument.getDvPayeeDetail().getDisbVchrPaymentReasonCode();
114            List<String> nonEmpltravelPaymentReasonCodes = SpringContext.getBean(ParameterService.class).getParameterValues(DisbursementVoucherDocument.class, NONEMPLOYEE_TRAVEL_PAY_REASONS_PARM_NM);
115    
116            if (nonEmpltravelPaymentReasonCodes == null || !nonEmpltravelPaymentReasonCodes.contains(paymentReasonCode) || dvDocument.getDvPayeeDetail().isEmployee()) {
117                String nonEmplTravReasonStr = getValidPaymentReasonsAsString(nonEmpltravelPaymentReasonCodes);
118    
119                String paymentReasonName = dvDocument.getDvPayeeDetail().getDisbVchrPaymentReasonName();
120                Object[] args = { "payment reason", "'" + paymentReasonName + "'", "Non-Employee Travel", nonEmplTravReasonStr };
121    
122                String questionText = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(KFSKeyConstants.QUESTION_CLEAR_UNNEEDED_TAB);
123                questionText = MessageFormat.format(questionText, args);
124    
125                boolean clearTab = super.askOrAnalyzeYesNoQuestion(KFSConstants.DisbursementVoucherDocumentConstants.CLEAR_NON_EMPLOYEE_TAB_QUESTION_ID, questionText);
126                if (clearTab) {
127                    DisbursementVoucherNonEmployeeTravel blankDvNonEmplTrav = new DisbursementVoucherNonEmployeeTravel();
128                    blankDvNonEmplTrav.setDocumentNumber(dvNonEmplTrav.getDocumentNumber());
129                    blankDvNonEmplTrav.setVersionNumber(dvNonEmplTrav.getVersionNumber());
130                    dvDocument.setDvNonEmployeeTravel(blankDvNonEmplTrav);
131                }
132                else {
133                    // return to document if the user doesn't want to clear the Non Employee Travel tab
134                    super.event.setActionForwardName(KFSConstants.MAPPING_BASIC);
135                    tabStatesOK = false;
136                }
137            }
138    
139            return tabStatesOK;
140        }
141    
142        /**
143         * Returns true if non-employee travel tab contains any data in any of its fields
144         * 
145         * @param dvNonEmplTrav disbursement voucher non employee travel object
146         * @return True if non employee travel tab contains any data in any fields.
147         */
148        protected boolean hasNonEmployeeTravelValues(DisbursementVoucherNonEmployeeTravel dvNonEmplTrav) {
149            boolean hasValues = false;
150    
151            // Checks each explicit field in the tab for user entered values
152            hasValues = hasNonEmployeeTravelGeneralValues(dvNonEmplTrav);
153    
154            // Checks per diem section for values
155            if (!hasValues) {
156                hasValues = hasNonEmployeeTravelPerDiemValues(dvNonEmplTrav);
157            }
158    
159            if (!hasValues) {
160                hasValues = hasNonEmployeeTravelPersonalVehicleValues(dvNonEmplTrav);
161            }
162    
163            if (!hasValues) {
164                hasValues = dvNonEmplTrav.getDvNonEmployeeExpenses().size() > 0;
165            }
166    
167            if (!hasValues) {
168                hasValues = dvNonEmplTrav.getDvPrePaidEmployeeExpenses().size() > 0;
169            }
170    
171            return hasValues;
172        }
173    
174        /**
175         * Returns true if any values are not blank on non employee travel tab
176         * 
177         * @param dvNonEmplTrav disbursement voucher non employee travel object
178         * @return True if any values are found in the non employee travel tab
179         */
180        protected boolean hasNonEmployeeTravelGeneralValues(DisbursementVoucherNonEmployeeTravel dvNonEmplTrav) {
181            return StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrNonEmpTravelerName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrServicePerformedDesc()) || StringUtils.isNotBlank(dvNonEmplTrav.getDvServicePerformedLocName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDvServiceRegularEmprName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrTravelFromCityName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrTravelFromStateCode()) || StringUtils.isNotBlank(dvNonEmplTrav.getDvTravelFromCountryCode()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDvPerdiemStartDttmStamp()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrTravelToCityName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrTravelToStateCode()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrTravelToCountryCode()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDvPerdiemEndDttmStamp());
182        }
183    
184        /**
185         * Returns true if non employee travel tab contains data in any of the fields in the per diem section
186         * 
187         * @param dvNonEmplTrav disbursement voucher non employee travel object
188         * @return True if non employee travel tab contains data in any of the fields in the per diem section
189         */
190        protected boolean hasNonEmployeeTravelPerDiemValues(DisbursementVoucherNonEmployeeTravel dvNonEmplTrav) {
191            return StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrPerdiemCategoryName()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDisbVchrPerdiemRate()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDisbVchrPerdiemCalculatedAmt()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDisbVchrPerdiemActualAmount()) || StringUtils.isNotBlank(dvNonEmplTrav.getDvPerdiemChangeReasonText());
192        }
193    
194        /**
195         * Returns true if non employee travel tab contains data in any of the fields in the personal vehicle section
196         * 
197         * @param dvNonEmplTrav disbursement voucher non employee travel object
198         * @return True if non employee travel tab contains data in any of the fields in the personal vehicle section
199         */
200        protected boolean hasNonEmployeeTravelPersonalVehicleValues(DisbursementVoucherNonEmployeeTravel dvNonEmplTrav) {
201            return StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrAutoFromCityName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrAutoFromStateCode()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrAutoToCityName()) || StringUtils.isNotBlank(dvNonEmplTrav.getDisbVchrAutoToStateCode()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDisbVchrMileageCalculatedAmt()) || ObjectUtils.isNotNull(dvNonEmplTrav.getDisbVchrPersonalCarAmount());
202        }
203    
204        /**
205         * Returns true if the state of all the tabs is valid, false otherwise.
206         * 
207         * @param dvDocument submitted disbursemtn voucher document
208         * @return true if the state of all the tabs is valid, false otherwise.
209         */
210        protected boolean checkForeignDraftTabState(DisbursementVoucherDocument dvDocument) {
211            boolean tabStatesOK = true;
212    
213            DisbursementVoucherWireTransfer dvForeignDraft = dvDocument.getDvWireTransfer();
214    
215            // if payment method is CHECK and wire tab contains data, ask user to clear tab
216            if ((StringUtils.equals(DisbursementVoucherConstants.PAYMENT_METHOD_CHECK, dvDocument.getDisbVchrPaymentMethodCode()) || StringUtils.equals(DisbursementVoucherConstants.PAYMENT_METHOD_WIRE, dvDocument.getDisbVchrPaymentMethodCode())) && hasForeignDraftValues(dvForeignDraft)) {
217                String questionText = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(KFSKeyConstants.QUESTION_CLEAR_UNNEEDED_TAB);
218    
219                Object[] args = { "payment method", dvDocument.getDisbVchrPaymentMethodCode(), "Foreign Draft", DisbursementVoucherConstants.PAYMENT_METHOD_DRAFT };
220                questionText = MessageFormat.format(questionText, args);
221    
222                boolean clearTab = super.askOrAnalyzeYesNoQuestion(KFSConstants.DisbursementVoucherDocumentConstants.CLEAR_FOREIGN_DRAFT_TAB_QUESTION_ID, questionText);
223                if (clearTab) {
224                    // NOTE: Can't replace with new instance because Wire Transfer uses same object
225                    clearForeignDraftValues(dvForeignDraft);
226                }
227                else {
228                    // return to document if the user doesn't want to clear the Wire Transfer tab
229                    super.event.setActionForwardName(KFSConstants.MAPPING_BASIC);
230                    tabStatesOK = false;
231                }
232            }
233    
234            return tabStatesOK;
235        }
236    
237        /**
238         * Returns true if foreign draft tab contains any data in any fields. NOTE: Currently does not validate based on only required
239         * fields. Checks all fields within tab for data.
240         * 
241         * @param dvForeignDraft disbursement foreign draft object
242         * @return True if foreign draft tab contains any data in any fields.
243         */
244        protected boolean hasForeignDraftValues(DisbursementVoucherWireTransfer dvForeignDraft) {
245            boolean hasValues = false;
246    
247            // Checks each explicit field in the tab for user entered values
248            hasValues |= StringUtils.isNotBlank(dvForeignDraft.getDisbursementVoucherForeignCurrencyTypeCode());
249            hasValues |= StringUtils.isNotBlank(dvForeignDraft.getDisbursementVoucherForeignCurrencyTypeName());
250    
251            return hasValues;
252        }
253    
254        /**
255         * This method sets foreign currency type code and name to null for passed in disbursement foreign draft object
256         * 
257         * @param dvForeignDraft disbursement foreign draft object
258         */
259        protected void clearForeignDraftValues(DisbursementVoucherWireTransfer dvForeignDraft) {
260            dvForeignDraft.setDisbursementVoucherForeignCurrencyTypeCode(null);
261            dvForeignDraft.setDisbursementVoucherForeignCurrencyTypeName(null);
262        }
263    
264        /**
265         * This method returns true if the state of all the tabs is valid, false otherwise.
266         * 
267         * @param dvDocument submitted disbursement voucher document
268         * @return Returns true if the state of all the tabs is valid, false otherwise.
269         */
270        protected boolean checkWireTransferTabState(DisbursementVoucherDocument dvDocument) {
271            boolean tabStatesOK = true;
272    
273            DisbursementVoucherWireTransfer dvWireTransfer = dvDocument.getDvWireTransfer();
274    
275            // if payment method is CHECK and wire tab contains data, ask user to clear tab
276            if ((StringUtils.equals(DisbursementVoucherConstants.PAYMENT_METHOD_CHECK, dvDocument.getDisbVchrPaymentMethodCode()) || StringUtils.equals(DisbursementVoucherConstants.PAYMENT_METHOD_DRAFT, dvDocument.getDisbVchrPaymentMethodCode())) && hasWireTransferValues(dvWireTransfer)) {
277                String questionText = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(KFSKeyConstants.QUESTION_CLEAR_UNNEEDED_TAB);
278    
279                Object[] args = { "payment method", dvDocument.getDisbVchrPaymentMethodCode(), "Wire Transfer", DisbursementVoucherConstants.PAYMENT_METHOD_WIRE };
280                questionText = MessageFormat.format(questionText, args);
281    
282                boolean clearTab = super.askOrAnalyzeYesNoQuestion(KFSConstants.DisbursementVoucherDocumentConstants.CLEAR_WIRE_TRANSFER_TAB_QUESTION_ID, questionText);
283                if (clearTab) {
284                    // NOTE: Can't replace with new instance because Foreign Draft uses same object
285                    clearWireTransferValues(dvWireTransfer);
286                }
287                else {
288                    // return to document if the user doesn't want to clear the Wire Transfer tab
289                    super.event.setActionForwardName(KFSConstants.MAPPING_BASIC);
290                    tabStatesOK = false;
291                }
292            }
293    
294            return tabStatesOK;
295        }
296    
297        /**
298         * If bank specification is enabled, prompts user to use the continuation bank code when the given bank code is inactive
299         * 
300         * @param dvDocument document containing bank code
301         * @return true
302         */
303        protected boolean checkBankCodeActive(DisbursementVoucherDocument dvDocument) {
304            boolean continueRules = true;
305    
306            // if bank specification is not enabled, no need to validate bank
307            if (!SpringContext.getBean(BankService.class).isBankSpecificationEnabled()) {
308                return continueRules;
309            }
310    
311            // refresh bank reference so continuation bank can be checked for active status
312            dvDocument.refreshReferenceObject(KFSPropertyConstants.BANK);
313            Bank bank = dvDocument.getBank();
314    
315            // if bank is inactive and continuation is active, prompt user to use continuation bank
316            if (bank != null && !bank.isActive() && bank.getContinuationBank().isActive()) {
317                String questionText = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(KFSKeyConstants.QUESTION_BANK_INACTIVE);
318                questionText = MessageFormat.format(questionText, dvDocument.getDisbVchrBankCode(), bank.getContinuationBankCode());
319    
320                boolean useContinuation = super.askOrAnalyzeYesNoQuestion(KFSConstants.USE_CONTINUATION_BANK_QUESTION, questionText);
321                if (useContinuation) {
322                    dvDocument.setDisbVchrBankCode(bank.getContinuationBankCode());
323                }
324            }
325    
326            return continueRules;
327        }
328    
329        /**
330         * Returns true if wire transfer tab contains any data in any fields.
331         * 
332         * @param dvWireTransfer disbursement voucher wire transfer
333         * @return true if wire transfer tab contains any data in any fields.
334         */
335        protected boolean hasWireTransferValues(DisbursementVoucherWireTransfer dvWireTransfer) {
336            boolean hasValues = false;
337    
338            // Checks each explicit field in the tab for user entered values
339            hasValues |= StringUtils.isNotBlank(dvWireTransfer.getDisbursementVoucherAutomatedClearingHouseProfileNumber());
340            hasValues |= StringUtils.isNotBlank(dvWireTransfer.getDisbursementVoucherBankName());
341            hasValues |= StringUtils.isNotBlank(dvWireTransfer.getDisbVchrBankRoutingNumber());
342            hasValues |= StringUtils.isNotBlank(dvWireTransfer.getDisbVchrBankCityName());
343            hasValues |= StringUtils.isNotBlank(dvWireTransfer.getDisbVchrBankStateCode());
344            hasValues |= StringUtils.isNotBlank(dvWireTransfer.getDisbVchrBankCountryCode());
345            hasValues |= StringUtils.isNotBlank(dvWireTransfer.getDisbVchrPayeeAccountNumber());
346            hasValues |= StringUtils.isNotBlank(dvWireTransfer.getDisbVchrAttentionLineText());
347            hasValues |= StringUtils.isNotBlank(dvWireTransfer.getDisbVchrCurrencyTypeName());
348            hasValues |= StringUtils.isNotBlank(dvWireTransfer.getDisbVchrAdditionalWireText());
349            hasValues |= StringUtils.isNotBlank(dvWireTransfer.getDisbursementVoucherPayeeAccountName());
350    
351            return hasValues;
352        }
353    
354        /**
355         * This method sets all values in the passed in disbursement wire transfer object to null
356         * 
357         * @param dvWireTransfer
358         */
359        protected void clearWireTransferValues(DisbursementVoucherWireTransfer dvWireTransfer) {
360            dvWireTransfer.setDisbursementVoucherAutomatedClearingHouseProfileNumber(null);
361            dvWireTransfer.setDisbursementVoucherBankName(null);
362            dvWireTransfer.setDisbVchrBankRoutingNumber(null);
363            dvWireTransfer.setDisbVchrBankCityName(null);
364            dvWireTransfer.setDisbVchrBankStateCode(null);
365            dvWireTransfer.setDisbVchrBankCountryCode(null);
366            dvWireTransfer.setDisbVchrPayeeAccountNumber(null);
367            dvWireTransfer.setDisbVchrAttentionLineText(null);
368            dvWireTransfer.setDisbVchrCurrencyTypeName(null);
369            dvWireTransfer.setDisbVchrAdditionalWireText(null);
370            dvWireTransfer.setDisbursementVoucherPayeeAccountName(null);
371        }
372    
373        /**
374         * put the valid payment reason codes along with their description together
375         * 
376         * @param validPaymentReasonCodes the given valid payment reason codes
377         * @return the valid payment reason codes along with their description as a string
378         */
379        protected String getValidPaymentReasonsAsString(List<String> validPaymentReasonCodes) {
380            List<String> payementReasonString = new ArrayList<String>();
381    
382            if (validPaymentReasonCodes == null || validPaymentReasonCodes.isEmpty()) {
383                return StringUtils.EMPTY;
384            }
385    
386            List<KeyLabelPair> reasons = new PaymentReasonValuesFinder().getKeyValues();
387            for (KeyLabelPair reason : reasons) {
388                if (validPaymentReasonCodes.contains(reason.getKey())) {
389                    payementReasonString.add(reason.getLabel());
390                }
391            }
392    
393            return payementReasonString.toString();
394        }
395    }