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.module.cab.document.web.struts;
017    
018    
019    import java.util.Collection;
020    import java.util.HashMap;
021    import java.util.List;
022    import java.util.Map;
023    
024    import javax.servlet.http.HttpServletRequest;
025    import javax.servlet.http.HttpServletResponse;
026    
027    import org.apache.commons.lang.StringUtils;
028    import org.apache.log4j.Logger;
029    import org.apache.struts.action.ActionForm;
030    import org.apache.struts.action.ActionForward;
031    import org.apache.struts.action.ActionMapping;
032    import org.kuali.kfs.module.cab.CabConstants;
033    import org.kuali.kfs.module.cab.CabKeyConstants;
034    import org.kuali.kfs.module.cab.CabPropertyConstants;
035    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument;
036    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset;
037    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableLineAssetAccount;
038    import org.kuali.kfs.module.cab.document.service.PurApInfoService;
039    import org.kuali.kfs.module.cab.document.service.PurApLineDocumentService;
040    import org.kuali.kfs.module.cab.document.service.PurApLineService;
041    import org.kuali.kfs.module.cab.document.web.PurApLineSession;
042    import org.kuali.kfs.module.cam.CamsConstants;
043    import org.kuali.kfs.module.cam.CamsKeyConstants;
044    import org.kuali.kfs.module.cam.businessobject.AssetGlobal;
045    import org.kuali.kfs.sys.KFSConstants;
046    import org.kuali.kfs.sys.context.SpringContext;
047    import org.kuali.rice.kew.exception.WorkflowException;
048    import org.kuali.rice.kns.question.ConfirmationQuestion;
049    import org.kuali.rice.kns.service.BusinessObjectService;
050    import org.kuali.rice.kns.service.KualiConfigurationService;
051    import org.kuali.rice.kns.service.ParameterService;
052    import org.kuali.rice.kns.util.GlobalVariables;
053    import org.kuali.rice.kns.util.KNSConstants;
054    import org.kuali.rice.kns.util.KualiDecimal;
055    import org.kuali.rice.kns.util.ObjectUtils;
056    import org.kuali.rice.kns.util.RiceKeyConstants;
057    
058    public class PurApLineAction extends CabActionBase {
059        private static final Logger LOG = Logger.getLogger(PurApLineAction.class);
060        PurApLineService purApLineService = SpringContext.getBean(PurApLineService.class);
061        PurApInfoService purApInfoService = SpringContext.getBean(PurApInfoService.class);
062        PurApLineDocumentService purApLineDocumentService = SpringContext.getBean(PurApLineDocumentService.class);
063    
064        /**
065         * Handle start action.
066         * 
067         * @param mapping
068         * @param form
069         * @param request
070         * @param response
071         * @return
072         * @throws Exception
073         */
074        public ActionForward start(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
075            PurApLineForm purApLineForm = (PurApLineForm) form;
076            if (purApLineForm.getPurchaseOrderIdentifier() == null) {
077                GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, CabKeyConstants.ERROR_PO_ID_EMPTY);
078            }
079            else {
080                // set Contact Email Address and Phone Number from PurAp Purchase Order document
081                purApInfoService.setPurchaseOrderFromPurAp(purApLineForm);
082    
083                // save PurAp document list into form
084                buildPurApDocList(purApLineForm);
085    
086                if (!purApLineForm.getPurApDocs().isEmpty()) {
087                    // set item pre-populated fields
088                    purApLineService.buildPurApItemAssetList(purApLineForm.getPurApDocs());
089                    // create session object for current processing
090                    createPurApLineSession(purApLineForm.getPurchaseOrderIdentifier());
091                }
092            }
093            return mapping.findForward(KFSConstants.MAPPING_BASIC);
094        }
095    
096        protected void createPurApLineSession(Integer purchaseOrderIdentifier) {
097            GlobalVariables.getUserSession().addObject(CabConstants.CAB_PURAP_SESSION.concat(purchaseOrderIdentifier.toString()), new PurApLineSession());
098        }
099    
100        protected void clearPurApLineSession(Integer purchaseOrderIdentifier) {
101            if (purchaseOrderIdentifier != null) {
102                GlobalVariables.getUserSession().removeObject(CabConstants.CAB_PURAP_SESSION.concat(purchaseOrderIdentifier.toString()));
103            }
104        }
105    
106        public ActionForward reload(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
107            PurApLineForm purApLineForm = (PurApLineForm) form;
108            if (purApLineForm.getPurchaseOrderIdentifier() == null) {
109                GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, CabKeyConstants.ERROR_PO_ID_EMPTY);
110            }
111            purApLineForm.getPurApDocs().clear();
112            // clear the session and reload the page
113            clearPurApLineSession(purApLineForm.getPurchaseOrderIdentifier());
114            return start(mapping, form, request, response);
115        }
116    
117        /**
118         * Build PurchasingAccountsPayableDocument list in which all documents have the same PO_ID.
119         * 
120         * @param purApLineForm
121         */
122        protected void buildPurApDocList(PurApLineForm purApLineForm) {
123            Map<String, Object> cols = new HashMap<String, Object>();
124            cols.put(CabPropertyConstants.PurchasingAccountsPayableDocument.PURCHASE_ORDER_IDENTIFIER, purApLineForm.getPurchaseOrderIdentifier());
125            Collection<PurchasingAccountsPayableDocument> purApDocs = SpringContext.getBean(BusinessObjectService.class).findMatchingOrderBy(PurchasingAccountsPayableDocument.class, cols, CabPropertyConstants.PurchasingAccountsPayableDocument.DOCUMENT_NUMBER, true);
126    
127            if (purApDocs == null || purApDocs.isEmpty()) {
128                GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, CabKeyConstants.ERROR_PO_ID_INVALID, purApLineForm.getPurchaseOrderIdentifier().toString());
129            }
130            else {
131                boolean existActiveDoc = false;
132                for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
133                    if (ObjectUtils.isNotNull(purApDoc) && purApDoc.isActive()) {
134                        // If there exists active document, set the existActiveDoc indicator.
135                        existActiveDoc = true;
136                        break;
137                    }
138                }
139                purApLineForm.getPurApDocs().clear();
140                purApLineForm.getPurApDocs().addAll(purApDocs);
141                setupObjectRelationship(purApLineForm.getPurApDocs());
142                // If no active item exists or no exist document, display a message.
143                if (!existActiveDoc) {
144                    GlobalVariables.getMessageList().add(CabKeyConstants.MESSAGE_NO_ACTIVE_PURAP_DOC);
145                }
146            }
147        }
148    
149        /**
150         * Setup relationship from account to item and item to doc. In this way, we keep all working objects in the same view as form.
151         * 
152         * @param purApDocs
153         */
154        protected void setupObjectRelationship(List<PurchasingAccountsPayableDocument> purApDocs) {
155            for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
156                for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
157                    item.setPurchasingAccountsPayableDocument(purApDoc);
158                    for (PurchasingAccountsPayableLineAssetAccount account : item.getPurchasingAccountsPayableLineAssetAccounts()) {
159                        account.setPurchasingAccountsPayableItemAsset(item);
160                    }
161                }
162            }
163        }
164    
165        /**
166         * Cancels the action and returns to portal main page
167         * 
168         * @param mapping {@link ActionMapping}
169         * @param form {@link ActionForm}
170         * @param request {@link HttpServletRequest}
171         * @param response {@link HttpServletResponse}
172         * @return {@link ActionForward}
173         * @throws Exception
174         */
175        public ActionForward cancel(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
176            return mapping.findForward(KNSConstants.MAPPING_PORTAL);
177        }
178    
179        /**
180         * save the information in the current form into underlying data store
181         */
182        public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
183            PurApLineForm purApLineForm = (PurApLineForm) form;
184            // get the current processing object from session
185            PurApLineSession purApLineSession = retrievePurApLineSession(purApLineForm);
186            // persistent changes to CAB tables
187            purApLineService.processSaveBusinessObjects(purApLineForm.getPurApDocs(), purApLineSession);
188            GlobalVariables.getMessageList().add(CabKeyConstants.MESSAGE_CAB_CHANGES_SAVED_SUCCESS);
189            return mapping.findForward(KFSConstants.MAPPING_BASIC);
190        }
191    
192        /**
193         * Handling for screen close. Default action is return to caller.
194         */
195        public ActionForward close(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
196            PurApLineForm purApLineForm = (PurApLineForm) form;
197    
198            // Create question page for save before close.
199            Object question = request.getParameter(KNSConstants.QUESTION_INST_ATTRIBUTE_NAME);
200            KualiConfigurationService kualiConfiguration = SpringContext.getBean(KualiConfigurationService.class);
201    
202            // logic for close question
203            if (question == null) {
204                // ask question if not already asked
205                return this.performQuestionWithoutInput(mapping, form, request, response, KNSConstants.DOCUMENT_SAVE_BEFORE_CLOSE_QUESTION, kualiConfiguration.getPropertyString(RiceKeyConstants.QUESTION_SAVE_BEFORE_CLOSE), KNSConstants.CONFIRMATION_QUESTION, KNSConstants.MAPPING_CLOSE, "");
206            }
207            else {
208                Object buttonClicked = request.getParameter(KNSConstants.QUESTION_CLICKED_BUTTON);
209                PurApLineSession purApLineSession = retrievePurApLineSession(purApLineForm);
210                if ((KNSConstants.DOCUMENT_SAVE_BEFORE_CLOSE_QUESTION.equals(question)) && ConfirmationQuestion.YES.equals(buttonClicked)) {
211                    purApLineService.processSaveBusinessObjects(purApLineForm.getPurApDocs(), purApLineSession);
212                }
213                // remove current processing object from session
214                removePurApLineSession(purApLineForm.getPurchaseOrderIdentifier());
215            }
216    
217            return mapping.findForward(KNSConstants.MAPPING_PORTAL);
218        }
219    
220        /**
221         * Remove PurApLineSession object from user session.
222         * 
223         * @param purApLineForm
224         */
225        private void removePurApLineSession(Integer purchaseOrderIdentifier) {
226            GlobalVariables.getUserSession().removeObject(CabConstants.CAB_PURAP_SESSION.concat(purchaseOrderIdentifier.toString()));
227        }
228    
229    
230        /**
231         * This method handles split action. Create one item with split quantity
232         * 
233         * @param mapping
234         * @param form
235         * @param request
236         * @param response
237         * @return
238         * @throws Exception
239         */
240        public ActionForward split(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
241            PurApLineForm purApLineForm = (PurApLineForm) form;
242            // Get the line item for applying split action.
243            PurchasingAccountsPayableItemAsset selectedLineItem = getSelectedLineItem((PurApLineForm) form);
244    
245            if (selectedLineItem == null) {
246                return mapping.findForward(KFSConstants.MAPPING_BASIC);
247            }
248    
249            String errorPath = CabPropertyConstants.PurApLineForm.PURAP_DOCS + KFSConstants.SQUARE_BRACKET_LEFT + purApLineForm.getActionPurApDocIndex() + KFSConstants.SQUARE_BRACKET_RIGHT + "." + CabPropertyConstants.PurchasingAccountsPayableDocument.PURCHASEING_ACCOUNTS_PAYABLE_ITEM_ASSETS + KFSConstants.SQUARE_BRACKET_LEFT + purApLineForm.getActionItemAssetIndex() + KFSConstants.SQUARE_BRACKET_RIGHT;
250            GlobalVariables.getMessageMap().addToErrorPath(errorPath);
251            // check user input split quantity.
252            checkSplitQty(selectedLineItem, errorPath);
253            GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
254    
255            // apply split when error free
256            if (GlobalVariables.getMessageMap().hasNoErrors() && selectedLineItem != null) {
257                PurApLineSession purApLineSession = retrievePurApLineSession(purApLineForm);
258                // create a new item with split quantity from selected item
259                purApLineService.processSplit(selectedLineItem, purApLineSession.getActionsTakenHistory());
260            }
261    
262            return mapping.findForward(KFSConstants.MAPPING_BASIC);
263        }
264    
265        /**
266         * Get PurApLineSession object from user session.
267         * 
268         * @param purApLineForm
269         * @return
270         */
271        private PurApLineSession retrievePurApLineSession(PurApLineForm purApForm) {
272            PurApLineSession purApLineSession = (PurApLineSession) GlobalVariables.getUserSession().retrieveObject(CabConstants.CAB_PURAP_SESSION.concat(purApForm.getPurchaseOrderIdentifier().toString()));
273            if (purApLineSession == null) {
274                purApLineSession = new PurApLineSession();
275                GlobalVariables.getUserSession().addObject(CabConstants.CAB_PURAP_SESSION.concat(purApForm.getPurchaseOrderIdentifier().toString()), purApLineSession);
276            }
277            return purApLineSession;
278        }
279    
280        /**
281         * Check user input splitQty. It must be required and can't be zero or greater than current quantity.
282         * 
283         * @param itemAsset
284         * @param errorPath
285         */
286        protected void checkSplitQty(PurchasingAccountsPayableItemAsset itemAsset, String errorPath) {
287            if (itemAsset.getSplitQty() == null)
288                itemAsset.setSplitQty(KualiDecimal.ZERO);
289    
290            if (itemAsset.getAccountsPayableItemQuantity() == null)
291                itemAsset.setAccountsPayableItemQuantity(KualiDecimal.ZERO);
292    
293            KualiDecimal splitQty = itemAsset.getSplitQty();
294            KualiDecimal oldQty = itemAsset.getAccountsPayableItemQuantity();
295            KualiDecimal maxAllowQty = oldQty.subtract(new KualiDecimal(0.1));
296    
297            if (splitQty == null) {
298                GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurchasingAccountsPayableItemAsset.SPLIT_QTY, CabKeyConstants.ERROR_SPLIT_QTY_REQUIRED);
299            }
300            else if (splitQty.isLessEqual(KualiDecimal.ZERO) || splitQty.isGreaterEqual(oldQty)) {
301                GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurchasingAccountsPayableItemAsset.SPLIT_QTY, CabKeyConstants.ERROR_SPLIT_QTY_INVALID, maxAllowQty.toString());
302            }
303            return;
304        }
305    
306        /**
307         * Merge Action includes merge all functionality.
308         * 
309         * @param mapping
310         * @param form
311         * @param request
312         * @param response
313         * @return
314         * @throws Exception
315         */
316        public ActionForward merge(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
317            PurApLineForm purApForm = (PurApLineForm) form;
318            boolean isMergeAll = purApLineService.isMergeAllAction(purApForm.getPurApDocs());
319            List<PurchasingAccountsPayableItemAsset> mergeLines = purApLineService.getSelectedMergeLines(isMergeAll, purApForm.getPurApDocs());
320    
321            Object question = request.getParameter(KNSConstants.QUESTION_INST_ATTRIBUTE_NAME);
322            // logic for trade-in allowance question
323            if (question != null) {
324                Object buttonClicked = request.getParameter(KNSConstants.QUESTION_CLICKED_BUTTON);
325                if (CabConstants.TRADE_IN_INDICATOR_QUESTION.equals(question) && ConfirmationQuestion.YES.equals(buttonClicked)) {
326                    if (purApLineService.mergeLinesHasDifferentObjectSubTypes(mergeLines)) {
327                        // check if objectSubTypes are different and bring the obj sub types warning message
328                        String warningMessage = generateObjectSubTypeQuestion();
329                        return this.performQuestionWithoutInput(mapping, form, request, response, CabConstants.PAYMENT_DIFFERENT_OBJECT_SUB_TYPE_CONFIRMATION_QUESTION, warningMessage, KNSConstants.CONFIRMATION_QUESTION, CabConstants.Actions.MERGE, "");
330                    }
331                    else {
332                        performMerge(purApForm, mergeLines, isMergeAll);
333                    }
334                }
335                else if (CabConstants.PAYMENT_DIFFERENT_OBJECT_SUB_TYPE_CONFIRMATION_QUESTION.equals(question) && ConfirmationQuestion.YES.equals(buttonClicked)) {
336                    performMerge(purApForm, mergeLines, isMergeAll);
337                }
338    
339                return mapping.findForward(KFSConstants.MAPPING_BASIC);
340            }
341    
342            boolean tradeInAllowanceInAllLines = purApLineService.isTradeInAllowanceExist(purApForm.getPurApDocs());
343            boolean tradeInIndicatorInSelectedLines = purApLineService.isTradeInIndExistInSelectedLines(mergeLines);
344            // validating...
345            validateMergeAction(purApForm, mergeLines, isMergeAll, tradeInAllowanceInAllLines, tradeInIndicatorInSelectedLines);
346    
347            if (GlobalVariables.getMessageMap().hasNoErrors()) {
348                // Display a warning message without blocking the action if TI indicator exists but TI allowance not exist.
349                if (tradeInIndicatorInSelectedLines && !tradeInAllowanceInAllLines) {
350                    return this.performQuestionWithoutInput(mapping, form, request, response, CabConstants.TRADE_IN_INDICATOR_QUESTION, SpringContext.getBean(KualiConfigurationService.class).getPropertyString(CabKeyConstants.QUESTION_TRADE_IN_INDICATOR_EXISTING), KNSConstants.CONFIRMATION_QUESTION, CabConstants.Actions.MERGE, "");
351                }
352                else if (purApLineService.mergeLinesHasDifferentObjectSubTypes(mergeLines)) {
353                    // check if objectSubTypes are different and bring the obj sub types warning message
354                    String warningMessage = generateObjectSubTypeQuestion();
355                    return this.performQuestionWithoutInput(mapping, form, request, response, CabConstants.PAYMENT_DIFFERENT_OBJECT_SUB_TYPE_CONFIRMATION_QUESTION, warningMessage, KNSConstants.CONFIRMATION_QUESTION, CabConstants.Actions.MERGE, "");
356                }
357                else {
358                    performMerge(purApForm, mergeLines, isMergeAll);
359                }
360            }
361    
362            return mapping.findForward(KFSConstants.MAPPING_BASIC);
363        }
364    
365    
366        /**
367         * Generate the question string for different object sub type codes.
368         * 
369         * @return
370         */
371        protected String generateObjectSubTypeQuestion() {
372            String parameterDetail = "(module:" + getParameterService().getNamespace(AssetGlobal.class) + "/component:" + getParameterService().getDetailType(AssetGlobal.class) + ")";
373            KualiConfigurationService kualiConfiguration = this.getKualiConfigurationService();
374    
375            String continueQuestion = kualiConfiguration.getPropertyString(CamsKeyConstants.CONTINUE_QUESTION);
376            String warningMessage = kualiConfiguration.getPropertyString(CabKeyConstants.QUESTION_DIFFERENT_OBJECT_SUB_TYPES) + " " + CamsConstants.Parameters.OBJECT_SUB_TYPE_GROUPS + " " + parameterDetail + ". " + continueQuestion;
377            return warningMessage;
378        }
379    
380        /**
381         * Merge with service help.
382         * 
383         * @param purApForm
384         * @param mergeLines
385         */
386        protected void performMerge(PurApLineForm purApForm, List<PurchasingAccountsPayableItemAsset> mergeLines, boolean isMergeAll) {
387            PurApLineSession purApLineSession = retrievePurApLineSession(purApForm);
388            // handle merging lines including merge all situation.
389            retrieveUserInputForMerge(mergeLines.get(0), purApForm);
390            purApLineService.processMerge(mergeLines, purApLineSession.getActionsTakenHistory(), isMergeAll);
391            // add all other mergeLines except the first one into processedItem list.
392            mergeLines.remove(0);
393            purApLineSession.getProcessedItems().addAll(mergeLines);
394            clearForMerge(purApForm);
395        }
396    
397        /**
398         * Retrieve user input merge quantity and merge description.
399         * 
400         * @param firstItem
401         * @param purApForm
402         */
403        protected void retrieveUserInputForMerge(PurchasingAccountsPayableItemAsset firstItem, PurApLineForm purApForm) {
404            if (ObjectUtils.isNotNull(firstItem)) {
405                // Set new value for quantity and description.
406                firstItem.setAccountsPayableItemQuantity(purApForm.getMergeQty());
407                firstItem.setAccountsPayableLineItemDescription(purApForm.getMergeDesc());
408            }
409        }
410    
411        /**
412         * Clear user input after merge.
413         * 
414         * @param purApForm
415         */
416        protected void clearForMerge(PurApLineForm purApForm) {
417            // reset user input values.
418            purApLineService.resetSelectedValue(purApForm.getPurApDocs());
419            purApForm.setMergeQty(null);
420            purApForm.setMergeDesc(null);
421            purApForm.setSelectAll(false);
422        }
423    
424        /**
425         * Check if the merge action is valid or not.
426         * 
427         * @param purApForm
428         * @param mergeLines
429         */
430        protected void validateMergeAction(PurApLineForm purApForm, List<PurchasingAccountsPayableItemAsset> mergeLines, boolean isMergeAll, boolean tradeInAllowanceInAllLines, boolean tradeInIndicatorInSelectedLines) {
431            // check if the user entered merge quantity and merge description
432            checkMergeRequiredFields(purApForm);
433    
434            // Check if the selected merge lines violate the business constraints.
435            if (isMergeAll) {
436                checkMergeAllValid(tradeInAllowanceInAllLines, tradeInIndicatorInSelectedLines);
437            }
438            else {
439                checkMergeLinesValid(mergeLines, tradeInAllowanceInAllLines, tradeInIndicatorInSelectedLines);
440            }
441    
442            // Check the associated pre-tagging data entries.
443            checkPreTagValidForMerge(mergeLines, purApForm.getPurchaseOrderIdentifier());
444        }
445    
446        /**
447         * If to be merged items have: (1) No Pretag data: No problem; (2) 1 Pretag data entry: Associate this one with the new item
448         * created after merge;(3) 1+ Pretag data entries: Display error, user has to manually fix data
449         * 
450         * @param mergeLines
451         */
452        protected void checkPreTagValidForMerge(List<PurchasingAccountsPayableItemAsset> mergeLines, Integer purchaseOrderIdentifier) {
453            Map validNumberMap = getItemLineNumberMap(mergeLines);
454    
455            if (!validNumberMap.isEmpty() && validNumberMap.size() > 1 && purApLineService.isMultipleTagExisting(purchaseOrderIdentifier, validNumberMap.keySet())) {
456                GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.PURAP_DOCS, CabKeyConstants.ERROR_MERGE_WITH_PRETAGGING);
457            }
458        }
459    
460        /**
461         * Build a Hashmap for itemLineNumber since itemLines could exist duplicate itemLineNumber
462         * 
463         * @param itemLines
464         * @return
465         */
466        protected Map getItemLineNumberMap(List<PurchasingAccountsPayableItemAsset> itemLines) {
467            Map validNumberMap = new HashMap<Integer, Integer>();
468            for (PurchasingAccountsPayableItemAsset item : itemLines) {
469                if (item.getItemLineNumber() != null) {
470                    validNumberMap.put(item.getItemLineNumber(), item.getItemLineNumber());
471                }
472            }
473            return validNumberMap;
474        }
475    
476        /**
477         * For merge all, check if exists Trade-in allowance pending for allocate.
478         * 
479         * @param mergeLines
480         * @param purApForm
481         */
482        protected void checkMergeAllValid(boolean tradeInAllowanceInAllLines, boolean tradeInIndicatorInSelectedLines) {
483            if (tradeInAllowanceInAllLines && tradeInIndicatorInSelectedLines) {
484                GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.PURAP_DOCS, CabKeyConstants.ERROR_TRADE_IN_PENDING);
485            }
486        }
487    
488        /**
489         * Check if merge lines selected are allowed to continue this action. Constraints include: merge lines must more than 1; no
490         * additional charges pending for allocate.
491         * 
492         * @param mergeLines
493         * @param purApForm
494         */
495        protected void checkMergeLinesValid(List<PurchasingAccountsPayableItemAsset> mergeLines, boolean tradeInAllowanceInAllLines, boolean tradeInIndicatorInSelectedLines) {
496            if (mergeLines.size() <= 1) {
497                GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.PURAP_DOCS, CabKeyConstants.ERROR_MERGE_LINE_SELECTED);
498            }
499            else {
500                // if merge for different document lines and that document has additional charge allocation pending, additional charges
501                // should be allocated first.
502                if (purApLineService.isAdditionalChargePending(mergeLines)) {
503                    GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.PURAP_DOCS, CabKeyConstants.ERROR_ADDL_CHARGE_PENDING);
504                }
505    
506                // if merge lines has indicator exists and trade-in allowance is pending for allocation, we will block this action.
507                if (tradeInIndicatorInSelectedLines && tradeInAllowanceInAllLines) {
508                    GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.PURAP_DOCS, CabKeyConstants.ERROR_TRADE_IN_PENDING);
509                }
510            }
511        }
512    
513    
514        /**
515         * Check the required fields entered for merge.
516         */
517        protected void checkMergeRequiredFields(PurApLineForm purApForm) {
518            if (purApForm.getMergeQty() == null) {
519                GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.MERGE_QTY, CabKeyConstants.ERROR_MERGE_QTY_EMPTY);
520            }
521    
522            if (StringUtils.isBlank(purApForm.getMergeDesc())) {
523                GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.MERGE_DESC, CabKeyConstants.ERROR_MERGE_DESCRIPTION_EMPTY);
524            }
525        }
526    
527    
528        /**
529         * Update the item quantity value from a decimal(less than 1) to 1.
530         * 
531         * @param mapping
532         * @param form
533         * @param request
534         * @param response
535         * @return
536         * @throws Exception
537         */
538        public ActionForward percentPayment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
539            PurApLineForm purApform = (PurApLineForm) form;
540            PurchasingAccountsPayableItemAsset itemAsset = getSelectedLineItem(purApform);
541    
542            if (itemAsset != null) {
543                PurApLineSession purApLineSession = retrievePurApLineSession(purApform);
544                purApLineService.processPercentPayment(itemAsset, purApLineSession.getActionsTakenHistory());
545            }
546    
547            return mapping.findForward(KFSConstants.MAPPING_BASIC);
548        }
549    
550        /**
551         * Allocate line items including allocate additional charges functionality.
552         * 
553         * @param mapping
554         * @param form
555         * @param request
556         * @param response
557         * @return
558         * @throws Exception
559         */
560        public ActionForward allocate(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
561            PurApLineForm purApForm = (PurApLineForm) form;
562            PurchasingAccountsPayableItemAsset allocateSourceLine = getSelectedLineItem(purApForm);
563            if (allocateSourceLine == null) {
564                return mapping.findForward(KFSConstants.MAPPING_BASIC);
565            }
566            List<PurchasingAccountsPayableItemAsset> allocateTargetLines = purApLineService.getAllocateTargetLines(allocateSourceLine, purApForm.getPurApDocs());
567    
568            Object question = request.getParameter(KNSConstants.QUESTION_INST_ATTRIBUTE_NAME);
569            // logic for trade-in allowance question
570            if (question != null) {
571                Object buttonClicked = request.getParameter(KNSConstants.QUESTION_CLICKED_BUTTON);
572                if ((CabConstants.TRADE_IN_INDICATOR_QUESTION.equals(question)) && ConfirmationQuestion.YES.equals(buttonClicked)) {
573                    if (purApLineService.allocateLinesHasDifferentObjectSubTypes(allocateTargetLines, allocateSourceLine)) {
574                        // check if objectSubTypes are different and bring the obj sub types warning message
575                        String warningMessage = generateObjectSubTypeQuestion();
576                        return this.performQuestionWithoutInput(mapping, form, request, response, CabConstants.PAYMENT_DIFFERENT_OBJECT_SUB_TYPE_CONFIRMATION_QUESTION, warningMessage, KNSConstants.CONFIRMATION_QUESTION, CabConstants.Actions.ALLOCATE, "");
577                    }
578                    else {
579                        performAllocate(purApForm, allocateSourceLine, allocateTargetLines);
580                    }
581                }
582                else if (CabConstants.PAYMENT_DIFFERENT_OBJECT_SUB_TYPE_CONFIRMATION_QUESTION.equals(question) && ConfirmationQuestion.YES.equals(buttonClicked)) {
583                    performAllocate(purApForm, allocateSourceLine, allocateTargetLines);
584                }
585    
586                return mapping.findForward(KFSConstants.MAPPING_BASIC);
587            }
588    
589            boolean targetLineHasTradeIn = purApLineService.isTradeInIndExistInSelectedLines(allocateTargetLines);
590            boolean hasTradeInAllowance = purApLineService.isTradeInAllowanceExist(purApForm.getPurApDocs());
591            // Check if this allocate is valid.
592            validateAllocateAction(allocateSourceLine, allocateTargetLines, targetLineHasTradeIn, hasTradeInAllowance, purApForm.getPurApDocs());
593            if (GlobalVariables.getMessageMap().hasNoErrors()) {
594                // TI indicator exists in either source or target lines, but TI allowance not found, bring up a warning message
595                // to confirm this action.
596                if (!allocateSourceLine.isAdditionalChargeNonTradeInIndicator() && !allocateSourceLine.isTradeInAllowance() && (allocateSourceLine.isItemAssignedToTradeInIndicator() || targetLineHasTradeIn) && hasTradeInAllowance) {
597                    return this.performQuestionWithoutInput(mapping, form, request, response, CabConstants.TRADE_IN_INDICATOR_QUESTION, SpringContext.getBean(KualiConfigurationService.class).getPropertyString(CabKeyConstants.QUESTION_TRADE_IN_INDICATOR_EXISTING), KNSConstants.CONFIRMATION_QUESTION, CabConstants.Actions.ALLOCATE, "");
598                }
599                else if (purApLineService.allocateLinesHasDifferentObjectSubTypes(allocateTargetLines, allocateSourceLine)) {
600                    // check if objectSubTypes are different and bring the obj sub types warning message
601                    String warningMessage = generateObjectSubTypeQuestion();
602                    return this.performQuestionWithoutInput(mapping, form, request, response, CabConstants.PAYMENT_DIFFERENT_OBJECT_SUB_TYPE_CONFIRMATION_QUESTION, warningMessage, KNSConstants.CONFIRMATION_QUESTION, CabConstants.Actions.ALLOCATE, "");
603                }
604                else {
605                    performAllocate(purApForm, allocateSourceLine, allocateTargetLines);
606                }
607            }
608    
609            return mapping.findForward(KFSConstants.MAPPING_BASIC);
610        }
611    
612        /**
613         * Allocate with service help.
614         * 
615         * @param purApForm
616         * @param allocateSourceLine
617         * @param allocateTargetLines
618         */
619        protected void performAllocate(PurApLineForm purApForm, PurchasingAccountsPayableItemAsset allocateSourceLine, List<PurchasingAccountsPayableItemAsset> allocateTargetLines) {
620            PurApLineSession purApLineSession = retrievePurApLineSession(purApForm);
621            if (!purApLineService.processAllocate(allocateSourceLine, allocateTargetLines, purApLineSession.getActionsTakenHistory(), purApForm.getPurApDocs(), false)) {
622                GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.PURAP_DOCS, CabKeyConstants.ERROR_ALLOCATE_NO_TARGET_ACCOUNT);
623            }
624            else {
625                purApLineSession.getProcessedItems().add(allocateSourceLine);
626                // clear select check box
627                purApLineService.resetSelectedValue(purApForm.getPurApDocs());
628                purApForm.setSelectAll(false);
629            }
630        }
631    
632        /**
633         * Check if the line items are allowed to allocate.
634         * 
635         * @param selectedLine
636         * @param allocateTargetLines
637         * @param purApForm
638         */
639        protected void validateAllocateAction(PurchasingAccountsPayableItemAsset allocateSourceLine, List<PurchasingAccountsPayableItemAsset> allocateTargetLines, boolean targetLineHasTradeIn, boolean hasTradeInAllowance, List<PurchasingAccountsPayableDocument> purApDocs) {
640            // if no target selected...
641            if (allocateTargetLines.isEmpty()) {
642                GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.PURAP_DOCS, CabKeyConstants.ERROR_ALLOCATE_NO_LINE_SELECTED);
643            }
644    
645            // For allocate trade-in allowance, additional charges(non trade-in) must be allocated before allocate trade-in.
646            if (allocateSourceLine.isTradeInAllowance() && purApLineService.isAdditionalChargeExistInAllLines(purApDocs)) {
647                GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.PURAP_DOCS, CabKeyConstants.ERROR_ADDL_CHARGE_PENDING);
648            }
649            // For line item, we need to check...
650            if (!allocateSourceLine.isAdditionalChargeNonTradeInIndicator() && !allocateSourceLine.isTradeInAllowance()) {
651                allocateTargetLines.add(allocateSourceLine);
652                // Pending additional charges(non trade-in) can't associate with either source line or target lines.
653                if (purApLineService.isAdditionalChargePending(allocateTargetLines)) {
654                    GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.PURAP_DOCS, CabKeyConstants.ERROR_ADDL_CHARGE_PENDING);
655                }
656    
657                // For line item, check if trade-in allowance allocation pending.
658                if (targetLineHasTradeIn && hasTradeInAllowance) {
659                    GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.PURAP_DOCS, CabKeyConstants.ERROR_TRADE_IN_PENDING);
660                }
661                allocateTargetLines.remove(allocateSourceLine);
662            }
663    
664        }
665    
666        /**
667         * Get the user selected line item and set the link reference to document
668         * 
669         * @param purApLineForm
670         * @return
671         */
672        private PurchasingAccountsPayableItemAsset getSelectedLineItem(PurApLineForm purApLineForm) {
673            PurchasingAccountsPayableDocument purApDoc = purApLineForm.getPurApDocs().get(purApLineForm.getActionPurApDocIndex());
674            PurchasingAccountsPayableItemAsset selectedItem = purApDoc.getPurchasingAccountsPayableItemAssets().get(purApLineForm.getActionItemAssetIndex());
675            if (!selectedItem.isActive()) {
676                selectedItem = null;
677            }
678            return selectedItem;
679        }
680    
681    
682        /**
683         * Get the user selected document.
684         * 
685         * @param purApLineForm
686         * @return
687         */
688        private PurchasingAccountsPayableDocument getSelectedPurApDoc(PurApLineForm purApLineForm) {
689            return purApLineForm.getPurApDocs().get(purApLineForm.getActionPurApDocIndex());
690        }
691    
692        /**
693         * Handle apply payment action.
694         * 
695         * @param mapping
696         * @param form
697         * @param request
698         * @param response
699         * @return
700         * @throws Exception
701         */
702        public ActionForward applyPayment(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
703            PurApLineForm purApForm = (PurApLineForm) form;
704            PurchasingAccountsPayableItemAsset selectedLine = getSelectedLineItem(purApForm);
705    
706            if (selectedLine == null) {
707                return mapping.findForward(KFSConstants.MAPPING_BASIC);
708            }
709    
710            Object question = request.getParameter(KNSConstants.QUESTION_INST_ATTRIBUTE_NAME);
711            if (question != null) {
712                Object buttonClicked = request.getParameter(KNSConstants.QUESTION_CLICKED_BUTTON);
713                if ((CabConstants.TRADE_IN_INDICATOR_QUESTION.equals(question)) && ConfirmationQuestion.YES.equals(buttonClicked)) {
714                    // create CAMS asset payment global document.
715                    return createApplyPaymentDocument(mapping, purApForm, selectedLine);
716                }
717                else {
718                    return mapping.findForward(KFSConstants.MAPPING_BASIC);
719                }
720            }
721            if (selectedLine.isItemAssignedToTradeInIndicator()) {
722                // TI indicator exists, bring up a warning message to confirm this action.
723                return this.performQuestionWithoutInput(mapping, form, request, response, CabConstants.TRADE_IN_INDICATOR_QUESTION, SpringContext.getBean(KualiConfigurationService.class).getPropertyString(CabKeyConstants.QUESTION_TRADE_IN_INDICATOR_EXISTING), KNSConstants.CONFIRMATION_QUESTION, CabConstants.Actions.APPLY_PAYMENT, "");
724            }
725    
726            // create CAMS asset payment global document.
727            return createApplyPaymentDocument(mapping, purApForm, selectedLine);
728        }
729    
730        /**
731         * Create CAMS asset payment document.
732         * 
733         * @param mapping
734         * @param purApForm
735         * @param selectedLine
736         * @param purApLineSession
737         * @return
738         * @throws WorkflowException
739         */
740        private ActionForward createApplyPaymentDocument(ActionMapping mapping, PurApLineForm purApForm, PurchasingAccountsPayableItemAsset selectedLine) throws WorkflowException {
741            PurApLineSession purApLineSession = retrievePurApLineSession(purApForm);
742            String documentNumber;
743            // create CAMS asset payment global document.
744            if ((documentNumber = purApLineDocumentService.processApplyPayment(selectedLine, purApForm.getPurApDocs(), purApLineSession, purApForm.getRequisitionIdentifier())) != null) {
745                purApForm.setDocumentNumber(documentNumber);
746            }
747    
748            return mapping.findForward(KFSConstants.MAPPING_BASIC);
749    
750        }
751    
752        /**
753         * Handle create asset action.
754         * 
755         * @param mapping
756         * @param form
757         * @param request
758         * @param response
759         * @return
760         * @throws Exception
761         */
762        public ActionForward createAsset(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
763            PurApLineForm purApForm = (PurApLineForm) form;
764            PurchasingAccountsPayableItemAsset selectedLine = getSelectedLineItem(purApForm);
765    
766            if (selectedLine == null) {
767                return mapping.findForward(KFSConstants.MAPPING_BASIC);
768            }
769    
770            Object question = request.getParameter(KNSConstants.QUESTION_INST_ATTRIBUTE_NAME);
771            if (question != null) {
772                Object buttonClicked = request.getParameter(KNSConstants.QUESTION_CLICKED_BUTTON);
773                if ((CabConstants.TRADE_IN_INDICATOR_QUESTION.equals(question)) && ConfirmationQuestion.YES.equals(buttonClicked)) {
774                    // If PurAp user set capitalAssetNumbers for apply Asset Payment document, bring up a warning message for
775                    // confirmation.
776                    if (isSettingAssetsInPurAp(selectedLine)) {
777                        return this.performQuestionWithoutInput(mapping, form, request, response, CabConstants.SKIP_ASSET_NUMBERS_TO_ASSET_GLOBAL_QUESTION, SpringContext.getBean(KualiConfigurationService.class).getPropertyString(CabKeyConstants.QUESTION_SKIP_ASSET_NUMBERS_TO_ASSET_GLOBAL), KNSConstants.CONFIRMATION_QUESTION, CabConstants.Actions.CREATE_ASSET, "");
778                    }
779                    else {
780                        return createAssetGlobalDocument(mapping, purApForm, selectedLine);
781                    }
782                }
783                else if (CabConstants.SKIP_ASSET_NUMBERS_TO_ASSET_GLOBAL_QUESTION.equals(question) && ConfirmationQuestion.YES.equals(buttonClicked)) {
784                    return createAssetGlobalDocument(mapping, purApForm, selectedLine);
785                }
786                return mapping.findForward(KFSConstants.MAPPING_BASIC);
787            }
788    
789            // validate selected line item
790            validateCreateAssetAction(selectedLine);
791    
792            if (GlobalVariables.getMessageMap().hasNoErrors()) {
793                // TI indicator exists, bring up a warning message to confirm this action.
794                if (selectedLine.isItemAssignedToTradeInIndicator()) {
795                    return this.performQuestionWithoutInput(mapping, form, request, response, CabConstants.TRADE_IN_INDICATOR_QUESTION, SpringContext.getBean(KualiConfigurationService.class).getPropertyString(CabKeyConstants.QUESTION_TRADE_IN_INDICATOR_EXISTING), KNSConstants.CONFIRMATION_QUESTION, CabConstants.Actions.CREATE_ASSET, "");
796                }
797                // If PurAp user set capitalAssetNumbers for apply Asset Payment document, bring up a warning message to confirm using
798                // Asset Global document.
799                else if (isSettingAssetsInPurAp(selectedLine)) {
800                    return this.performQuestionWithoutInput(mapping, form, request, response, CabConstants.SKIP_ASSET_NUMBERS_TO_ASSET_GLOBAL_QUESTION, SpringContext.getBean(KualiConfigurationService.class).getPropertyString(CabKeyConstants.QUESTION_SKIP_ASSET_NUMBERS_TO_ASSET_GLOBAL), KNSConstants.CONFIRMATION_QUESTION, CabConstants.Actions.CREATE_ASSET, "");
801                }
802                else {
803                    return createAssetGlobalDocument(mapping, purApForm, selectedLine);
804                }
805            }
806    
807            return mapping.findForward(KFSConstants.MAPPING_BASIC);
808        }
809    
810        /**
811         * check if PurAp set CAMS Assets information
812         * 
813         * @param selectedLine
814         * @return
815         */
816        private boolean isSettingAssetsInPurAp(PurchasingAccountsPayableItemAsset selectedLine) {
817            return selectedLine.getPurApItemAssets() != null && !selectedLine.getPurApItemAssets().isEmpty();
818        }
819    
820        /**
821         * Create asset global document
822         * 
823         * @param mapping
824         * @param purApForm
825         * @param selectedLine
826         * @param purApLineSession
827         * @return
828         * @throws WorkflowException
829         */
830        private ActionForward createAssetGlobalDocument(ActionMapping mapping, PurApLineForm purApForm, PurchasingAccountsPayableItemAsset selectedLine) throws WorkflowException {
831            PurApLineSession purApLineSession = retrievePurApLineSession(purApForm);
832            String documentNumber = null;
833            // create CAMS asset global document.
834            if ((documentNumber = purApLineDocumentService.processCreateAsset(selectedLine, purApForm.getPurApDocs(), purApLineSession, purApForm.getRequisitionIdentifier())) != null) {
835                // forward link to asset global
836                purApForm.setDocumentNumber(documentNumber);
837            }
838    
839            return mapping.findForward(KFSConstants.MAPPING_BASIC);
840        }
841    
842        /**
843         * Validate selected line item for asset global creation.
844         * 
845         * @param selectedLine
846         */
847        protected void validateCreateAssetAction(PurchasingAccountsPayableItemAsset selectedLine) {
848            KualiDecimal integerOne = new KualiDecimal(1);
849            KualiDecimal quantity = selectedLine.getAccountsPayableItemQuantity();
850            // check if item quantity is a fractional value greater than 1.
851            if (quantity.isGreaterThan(integerOne) && quantity.mod(integerOne).isNonZero()) {
852                GlobalVariables.getMessageMap().putError(CabPropertyConstants.PurApLineForm.PURAP_DOCS, CabKeyConstants.ERROR_FRACTIONAL_QUANTITY);
853            }
854            // if quantity is between (0,1) , set it to 1.
855            else if (quantity.isGreaterThan(KualiDecimal.ZERO) && quantity.isLessThan(integerOne)) {
856                selectedLine.setAccountsPayableItemQuantity(integerOne);
857            }
858    
859        }
860    
861        protected ParameterService getParameterService() {
862            return (ParameterService) SpringContext.getBean(ParameterService.class);
863        }
864    
865        protected KualiConfigurationService getKualiConfigurationService() {
866            return SpringContext.getBean(KualiConfigurationService.class);
867        }
868    }