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 }