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.ar.document.web.struts; 017 018 import java.util.ArrayList; 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.ServletRequest; 025 import javax.servlet.ServletResponse; 026 import javax.servlet.http.HttpServletRequest; 027 import javax.servlet.http.HttpServletResponse; 028 029 import org.apache.commons.lang.StringUtils; 030 import org.apache.struts.action.ActionForm; 031 import org.apache.struts.action.ActionForward; 032 import org.apache.struts.action.ActionMapping; 033 import org.kuali.kfs.module.ar.ArKeyConstants; 034 import org.kuali.kfs.module.ar.ArPropertyConstants; 035 import org.kuali.kfs.module.ar.businessobject.AccountsReceivableDocumentHeader; 036 import org.kuali.kfs.module.ar.businessobject.Customer; 037 import org.kuali.kfs.module.ar.businessobject.InvoicePaidApplied; 038 import org.kuali.kfs.module.ar.businessobject.NonAppliedHolding; 039 import org.kuali.kfs.module.ar.businessobject.NonInvoiced; 040 import org.kuali.kfs.module.ar.document.CustomerInvoiceDocument; 041 import org.kuali.kfs.module.ar.document.PaymentApplicationDocument; 042 import org.kuali.kfs.module.ar.document.service.AccountsReceivableDocumentHeaderService; 043 import org.kuali.kfs.module.ar.document.service.CustomerInvoiceDetailService; 044 import org.kuali.kfs.module.ar.document.service.CustomerInvoiceDocumentService; 045 import org.kuali.kfs.module.ar.document.service.NonAppliedHoldingService; 046 import org.kuali.kfs.module.ar.document.service.PaymentApplicationDocumentService; 047 import org.kuali.kfs.module.ar.document.validation.impl.PaymentApplicationDocumentRuleUtil; 048 import org.kuali.kfs.sys.KFSConstants; 049 import org.kuali.kfs.sys.context.SpringContext; 050 import org.kuali.kfs.sys.document.web.struts.FinancialSystemTransactionalDocumentActionBase; 051 import org.kuali.rice.kew.exception.WorkflowException; 052 import org.kuali.rice.kns.document.Document; 053 import org.kuali.rice.kns.service.BusinessObjectService; 054 import org.kuali.rice.kns.service.DocumentService; 055 import org.kuali.rice.kns.util.GlobalVariables; 056 import org.kuali.rice.kns.util.KNSConstants; 057 import org.kuali.rice.kns.util.KualiDecimal; 058 import org.kuali.rice.kns.util.ObjectUtils; 059 import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase; 060 import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument; 061 import org.kuali.rice.kns.workflow.service.WorkflowDocumentService; 062 063 public class PaymentApplicationDocumentAction extends FinancialSystemTransactionalDocumentActionBase { 064 065 @Override 066 public ActionForward save(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 067 doApplicationOfFunds((PaymentApplicationDocumentForm) form); 068 return super.save(mapping, form, request, response); 069 } 070 071 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PaymentApplicationDocumentAction.class); 072 073 protected BusinessObjectService businessObjectService; 074 protected DocumentService documentService; 075 protected WorkflowDocumentService workflowDocumentService; 076 protected PaymentApplicationDocumentService paymentApplicationDocumentService; 077 protected CustomerInvoiceDocumentService customerInvoiceDocumentService; 078 protected CustomerInvoiceDetailService customerInvoiceDetailService; 079 protected NonAppliedHoldingService nonAppliedHoldingService; 080 081 /** 082 * Constructs a PaymentApplicationDocumentAction.java. 083 */ 084 public PaymentApplicationDocumentAction() { 085 super(); 086 businessObjectService = SpringContext.getBean(BusinessObjectService.class); 087 documentService = SpringContext.getBean(DocumentService.class); 088 workflowDocumentService = SpringContext.getBean(WorkflowDocumentService.class); 089 paymentApplicationDocumentService = SpringContext.getBean(PaymentApplicationDocumentService.class); 090 customerInvoiceDocumentService = SpringContext.getBean(CustomerInvoiceDocumentService.class); 091 customerInvoiceDetailService = SpringContext.getBean(CustomerInvoiceDetailService.class); 092 nonAppliedHoldingService = SpringContext.getBean(NonAppliedHoldingService.class); 093 } 094 095 @Override 096 public ActionForward execute(ActionMapping mapping, ActionForm form, ServletRequest request, ServletResponse response) throws Exception { 097 PaymentApplicationDocumentForm payAppForm = (PaymentApplicationDocumentForm) form; 098 if (!payAppForm.getPaymentApplicationDocument().isFinal()) { 099 doApplicationOfFunds(payAppForm); 100 } 101 return super.execute(mapping, form, request, response); 102 } 103 104 /** 105 * This is overridden in order to recalculate the invoice totals before doing the submit. 106 * 107 * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#route(org.apache.struts.action.ActionMapping, 108 * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 109 */ 110 @Override 111 public ActionForward route(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 112 doApplicationOfFunds((PaymentApplicationDocumentForm) form); 113 return super.route(mapping, form, request, response); 114 } 115 116 public ActionForward deleteNonArLine(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 117 PaymentApplicationDocumentForm paymentApplicationDocumentForm = (PaymentApplicationDocumentForm) form; 118 119 // TODO Andrew - should this be run or not here? 120 // doApplicationOfFunds((PaymentApplicationDocumentForm)form); 121 122 PaymentApplicationDocument paymentApplicationDocument = paymentApplicationDocumentForm.getPaymentApplicationDocument(); 123 Map<String, Object> parameters = request.getParameterMap(); 124 String _indexToRemove = null; 125 Integer indexToRemove = null; 126 127 // Figure out which line to remove. 128 for (String k : parameters.keySet()) { 129 if (k.startsWith(ArPropertyConstants.PaymentApplicationDocumentFields.DELETE_NON_INVOICED_LINE_PREFIX) && k.endsWith(".x")) { 130 if (null != parameters.get(k)) { 131 int beginIndex = ArPropertyConstants.PaymentApplicationDocumentFields.DELETE_NON_INVOICED_LINE_PREFIX.length(); 132 int endIndex = k.lastIndexOf("."); 133 if (beginIndex >= 0 && endIndex > beginIndex) { 134 _indexToRemove = k.substring(beginIndex, endIndex); 135 } 136 break; 137 } 138 } 139 } 140 141 // If we know which line to remove, remove it. 142 if (null != _indexToRemove) { 143 indexToRemove = new Integer(_indexToRemove); 144 NonInvoiced toRemove = null; 145 for (NonInvoiced nonInvoiced : paymentApplicationDocument.getNonInvoiceds()) { 146 if (indexToRemove.equals(nonInvoiced.getFinancialDocumentLineNumber())) { 147 toRemove = nonInvoiced; 148 break; 149 } 150 } 151 if (null != toRemove) { 152 paymentApplicationDocument.getNonInvoiceds().remove(toRemove); 153 } 154 } 155 156 // re-number the non-invoiceds 157 Integer nonInvoicedItemNumber = 1; 158 for (NonInvoiced n : paymentApplicationDocument.getNonInvoiceds()) { 159 n.setFinancialDocumentLineNumber(nonInvoicedItemNumber++); 160 n.setFinancialDocumentLineNumber(nonInvoicedItemNumber++); 161 n.refreshReferenceObject("chartOfAccounts"); 162 n.refreshReferenceObject("account"); 163 n.refreshReferenceObject("subAccount"); 164 n.refreshReferenceObject("financialObject"); 165 n.refreshReferenceObject("financialSubObject"); 166 n.refreshReferenceObject("project"); 167 } 168 169 return mapping.findForward(KFSConstants.MAPPING_BASIC); 170 } 171 172 /** 173 * Create an InvoicePaidApplied for a CustomerInvoiceDetail and validate it. If the validation succeeds the paidApplied is 174 * returned. If the validation does succeed a null is returned. 175 * 176 * @param customerInvoiceDetail 177 * @param paymentApplicationDocument 178 * @param amount 179 * @param fieldName 180 * @return 181 * @throws WorkflowException 182 */ 183 protected InvoicePaidApplied generateAndValidateNewPaidApplied(PaymentApplicationInvoiceDetailApply detailApplication, String fieldName, KualiDecimal totalFromControl) { 184 185 // generate the paidApplied 186 InvoicePaidApplied paidApplied = detailApplication.generatePaidApplied(); 187 188 // validate the paidApplied, but ignore any failures (other than the error message) 189 PaymentApplicationDocumentRuleUtil.validateInvoicePaidApplied(paidApplied, fieldName, totalFromControl); 190 191 // return the generated paidApplied 192 return paidApplied; 193 } 194 195 public ActionForward applyAllAmounts(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 196 doApplicationOfFunds((PaymentApplicationDocumentForm) form); 197 return mapping.findForward(KFSConstants.MAPPING_BASIC); 198 } 199 200 protected void doApplicationOfFunds(PaymentApplicationDocumentForm paymentApplicationDocumentForm) throws WorkflowException { 201 PaymentApplicationDocument paymentApplicationDocument = paymentApplicationDocumentForm.getPaymentApplicationDocument(); 202 203 List<InvoicePaidApplied> invoicePaidApplieds = new ArrayList<InvoicePaidApplied>(); 204 205 // apply invoice detail entries 206 invoicePaidApplieds.addAll(applyToIndividualCustomerInvoiceDetails(paymentApplicationDocumentForm)); 207 208 // quick-apply invoices 209 invoicePaidApplieds.addAll(quickApplyToInvoices(paymentApplicationDocumentForm, invoicePaidApplieds)); 210 211 // re-number the paidApplieds internal sequence numbers 212 int paidAppliedItemNumber = 1; 213 for (InvoicePaidApplied i : invoicePaidApplieds) { 214 i.setPaidAppliedItemNumber(paidAppliedItemNumber++); 215 } 216 217 // apply non-Invoiced 218 NonInvoiced nonInvoiced = applyNonInvoiced(paymentApplicationDocumentForm); 219 220 // apply non-applied holdings 221 NonAppliedHolding nonAppliedHolding = applyUnapplied(paymentApplicationDocumentForm); 222 223 // sum up the paid applieds 224 KualiDecimal sumOfInvoicePaidApplieds = KualiDecimal.ZERO; 225 for (InvoicePaidApplied invoicePaidApplied : invoicePaidApplieds) { 226 KualiDecimal amount = invoicePaidApplied.getInvoiceItemAppliedAmount(); 227 if (null == amount) { 228 amount = KualiDecimal.ZERO; 229 } 230 sumOfInvoicePaidApplieds = sumOfInvoicePaidApplieds.add(amount); 231 } 232 233 // sum up all applieds 234 KualiDecimal appliedAmount = KualiDecimal.ZERO; 235 appliedAmount = appliedAmount.add(sumOfInvoicePaidApplieds); 236 if (null != nonInvoiced && null != nonInvoiced.getFinancialDocumentLineAmount()) { 237 appliedAmount = appliedAmount.add(nonInvoiced.getFinancialDocumentLineAmount()); 238 } 239 appliedAmount = appliedAmount.add(paymentApplicationDocument.getSumOfNonAppliedDistributions()); 240 appliedAmount = appliedAmount.add(paymentApplicationDocument.getSumOfNonInvoicedDistributions()); 241 appliedAmount = appliedAmount.add(paymentApplicationDocument.getSumOfNonInvoiceds()); 242 if (null != paymentApplicationDocument.getNonAppliedHoldingAmount()) { 243 appliedAmount = appliedAmount.add(paymentApplicationDocument.getNonAppliedHoldingAmount()); 244 } 245 246 // check that we havent applied more than our control total 247 KualiDecimal controlTotalAmount = paymentApplicationDocumentForm.getTotalFromControl(); 248 249 // if the person over-applies, we dont stop them, we just complain 250 if (appliedAmount.isGreaterThan(controlTotalAmount)) { 251 addGlobalError(ArKeyConstants.PaymentApplicationDocumentErrors.CANNOT_APPLY_MORE_THAN_CASH_CONTROL_TOTAL_AMOUNT); 252 } 253 254 // swap out the old paidApplieds with the newly generated 255 paymentApplicationDocument.getInvoicePaidApplieds().clear(); 256 paymentApplicationDocument.getInvoicePaidApplieds().addAll(invoicePaidApplieds); 257 258 // NonInvoiced list management 259 if (null != nonInvoiced) { 260 paymentApplicationDocument.getNonInvoiceds().add(nonInvoiced); 261 262 // re-number the non-invoiced 263 Integer nonInvoicedItemNumber = 1; 264 for (NonInvoiced n : paymentApplicationDocument.getNonInvoiceds()) { 265 n.setFinancialDocumentLineNumber(nonInvoicedItemNumber++); 266 n.refreshReferenceObject("chartOfAccounts"); 267 n.refreshReferenceObject("account"); 268 n.refreshReferenceObject("subAccount"); 269 n.refreshReferenceObject("financialObject"); 270 n.refreshReferenceObject("financialSubObject"); 271 n.refreshReferenceObject("project"); 272 } 273 274 // make an empty new one 275 paymentApplicationDocumentForm.setNonInvoicedAddLine(new NonInvoiced()); 276 } 277 278 // reset the allocations, so it gets re-calculated 279 paymentApplicationDocumentForm.setNonAppliedControlAllocations(null); 280 281 // Update the doc total if it is not a CashControl generated PayApp 282 if (!paymentApplicationDocument.hasCashControlDetail()) { 283 paymentApplicationDocument.getDocumentHeader().setFinancialDocumentTotalAmount(appliedAmount); 284 } 285 } 286 287 protected List<InvoicePaidApplied> applyToIndividualCustomerInvoiceDetails(PaymentApplicationDocumentForm paymentApplicationDocumentForm) { 288 PaymentApplicationDocument paymentApplicationDocument = paymentApplicationDocumentForm.getPaymentApplicationDocument(); 289 String applicationDocNbr = paymentApplicationDocument.getDocumentNumber(); 290 291 // Handle amounts applied at the invoice detail level 292 int paidAppliedsGenerated = 1; 293 int simpleInvoiceDetailApplicationCounter = 0; 294 295 // calculate paid applieds for all invoices 296 List<InvoicePaidApplied> invoicePaidApplieds = new ArrayList<InvoicePaidApplied>(); 297 for (PaymentApplicationInvoiceApply invoiceApplication : paymentApplicationDocumentForm.getInvoiceApplications()) { 298 for (PaymentApplicationInvoiceDetailApply detailApplication : invoiceApplication.getDetailApplications()) { 299 300 // selectedInvoiceDetailApplications[${ctr}].amountApplied 301 String fieldName = "selectedInvoiceDetailApplications[" + Integer.toString(simpleInvoiceDetailApplicationCounter) + "].amountApplied"; 302 simpleInvoiceDetailApplicationCounter++; // needs to be incremented even if we skip this line 303 304 305 // handle the user clicking full apply 306 if (detailApplication.isFullApply()) { 307 detailApplication.setAmountApplied(detailApplication.getAmountOpen()); 308 } 309 // handle the user manually entering an amount 310 else { 311 if (detailApplication.isFullApplyChanged()) { // means it went from true to false 312 detailApplication.setAmountApplied(KualiDecimal.ZERO); 313 } 314 } 315 316 // Don't add lines where the amount to apply is zero. Wouldn't make any sense to do that. 317 if (KualiDecimal.ZERO.equals(detailApplication.getAmountApplied())) { 318 continue; 319 } 320 321 // generate and validate the paidApplied, and always add it to the list, even if 322 // it fails validation. Validation failures will stop routing. 323 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.PaymentApplicationTabErrorCodes.APPLY_TO_INVOICE_DETAIL_TAB); 324 InvoicePaidApplied invoicePaidApplied = generateAndValidateNewPaidApplied(detailApplication, fieldName, paymentApplicationDocument.getTotalFromControl()); 325 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.PaymentApplicationTabErrorCodes.APPLY_TO_INVOICE_DETAIL_TAB); 326 invoicePaidApplieds.add(invoicePaidApplied); 327 paidAppliedsGenerated++; 328 } 329 } 330 331 return invoicePaidApplieds; 332 } 333 334 protected List<InvoicePaidApplied> quickApplyToInvoices(PaymentApplicationDocumentForm paymentApplicationDocumentForm, List<InvoicePaidApplied> appliedToIndividualDetails) { 335 PaymentApplicationDocument applicationDocument = (PaymentApplicationDocument) paymentApplicationDocumentForm.getDocument(); 336 List<InvoicePaidApplied> invoicePaidApplieds = new ArrayList<InvoicePaidApplied>(); 337 338 // go over the selected invoices and apply full amount to each of their details 339 int index = 0; 340 for (PaymentApplicationInvoiceApply invoiceApplication : paymentApplicationDocumentForm.getInvoiceApplications()) { 341 String invoiceDocNumber = invoiceApplication.getDocumentNumber(); 342 343 // skip the line if its not set to quick apply 344 if (!invoiceApplication.isQuickApply()) { 345 346 // if it was just flipped from True to False 347 if (invoiceApplication.isQuickApplyChanged()) { 348 for (PaymentApplicationInvoiceDetailApply detailApplication : invoiceApplication.getDetailApplications()) { 349 350 // zero out all the details 351 detailApplication.setAmountApplied(KualiDecimal.ZERO); 352 detailApplication.setFullApply(false); 353 354 // remove any existing paidApplieds for this invoice 355 for (int i = appliedToIndividualDetails.size() - 1; i >= 0; i--) { 356 InvoicePaidApplied applied = appliedToIndividualDetails.get(i); 357 if (applied.getFinancialDocumentReferenceInvoiceNumber().equals(invoiceApplication.getDocumentNumber())) { 358 appliedToIndividualDetails.remove(i); 359 } 360 } 361 } 362 } 363 continue; 364 } 365 366 // make sure none of the invoices selected have zero open amounts, complain if so 367 if (invoiceApplication.getOpenAmount().isZero()) { 368 addGlobalError(ArKeyConstants.PaymentApplicationDocumentErrors.CANNOT_QUICK_APPLY_ON_INVOICE_WITH_ZERO_OPEN_AMOUNT); 369 return invoicePaidApplieds; 370 } 371 372 // remove any existing paidApplieds for this invoice 373 for (int i = appliedToIndividualDetails.size() - 1; i >= 0; i--) { 374 InvoicePaidApplied applied = appliedToIndividualDetails.get(i); 375 if (applied.getFinancialDocumentReferenceInvoiceNumber().equals(invoiceApplication.getDocumentNumber())) { 376 appliedToIndividualDetails.remove(i); 377 } 378 } 379 380 // create and validate the paid applieds for each invoice detail 381 String fieldName = "invoiceApplications[" + invoiceDocNumber + "].quickApply"; 382 for (PaymentApplicationInvoiceDetailApply detailApplication : invoiceApplication.getDetailApplications()) { 383 detailApplication.setAmountApplied(detailApplication.getAmountOpen()); 384 detailApplication.setFullApply(true); 385 InvoicePaidApplied paidApplied = generateAndValidateNewPaidApplied(detailApplication, fieldName, applicationDocument.getTotalFromControl()); 386 if (paidApplied != null) { 387 invoicePaidApplieds.add(paidApplied); 388 } 389 } 390 391 // maintain the selected doc number 392 if (invoiceDocNumber.equals(paymentApplicationDocumentForm.getEnteredInvoiceDocumentNumber())) { 393 paymentApplicationDocumentForm.setSelectedInvoiceDocumentNumber(invoiceDocNumber); 394 } 395 } 396 397 return invoicePaidApplieds; 398 } 399 400 protected NonInvoiced applyNonInvoiced(PaymentApplicationDocumentForm payAppForm) throws WorkflowException { 401 PaymentApplicationDocument applicationDocument = (PaymentApplicationDocument) payAppForm.getDocument(); 402 403 NonInvoiced nonInvoiced = payAppForm.getNonInvoicedAddLine(); 404 405 // if the line or line amount is null or zero, don't add the line. Additional validation is performed for the amount within 406 // the rules 407 // class, so no validation is needed here. 408 // 409 // NOTE: This conditional is in place because the "apply" button on the payment application document functions as a 410 // universal button, 411 // and therefore checks each tab where the button resides on the interface and attempts to apply values for that tab. This 412 // functionality 413 // causes this method to be called, regardless of if any values were entered in the "Non-AR" tab of the document. We want to 414 // ignore this 415 // method being called if there are no values entered in the fields. 416 // 417 // For the sake of this algorithm, a "Non-AR" accounting line will be ignored if it is null, or if the dollar amount entered 418 // is blank or zero. 419 if (ObjectUtils.isNull(payAppForm.getNonInvoicedAddLine()) || nonInvoiced.getFinancialDocumentLineAmount() == null || nonInvoiced.getFinancialDocumentLineAmount().isZero()) { 420 return null; 421 } 422 423 // If we got past the above conditional, wire it up for adding 424 nonInvoiced.setFinancialDocumentPostingYear(applicationDocument.getPostingYear()); 425 nonInvoiced.setDocumentNumber(applicationDocument.getDocumentNumber()); 426 nonInvoiced.setFinancialDocumentLineNumber(payAppForm.getNextNonInvoicedLineNumber()); 427 if (StringUtils.isNotBlank(nonInvoiced.getChartOfAccountsCode())) { 428 nonInvoiced.setChartOfAccountsCode(nonInvoiced.getChartOfAccountsCode().toUpperCase()); 429 } 430 431 // run the validations 432 boolean isValid = PaymentApplicationDocumentRuleUtil.validateNonInvoiced(nonInvoiced, applicationDocument, payAppForm.getTotalFromControl()); 433 434 // check the validation results and return null if there were any errors 435 if (!isValid) { 436 return null; 437 } 438 439 return nonInvoiced; 440 } 441 442 protected NonAppliedHolding applyUnapplied(PaymentApplicationDocumentForm payAppForm) throws WorkflowException { 443 PaymentApplicationDocument payAppDoc = payAppForm.getPaymentApplicationDocument(); 444 String customerNumber = payAppForm.getNonAppliedHoldingCustomerNumber(); 445 KualiDecimal amount = payAppForm.getNonAppliedHoldingAmount(); 446 447 // validate the customer number in the unapplied 448 if (StringUtils.isNotBlank(customerNumber)) { 449 // force customer number to upper 450 payAppForm.setNonAppliedHoldingCustomerNumber(customerNumber.toUpperCase()); 451 452 Map<String, String> pkMap = new HashMap<String, String>(); 453 pkMap.put(ArPropertyConstants.CustomerFields.CUSTOMER_NUMBER, customerNumber); 454 int found = businessObjectService.countMatching(Customer.class, pkMap); 455 if (found == 0) { 456 addFieldError(KFSConstants.PaymentApplicationTabErrorCodes.UNAPPLIED_TAB, ArPropertyConstants.PaymentApplicationDocumentFields.UNAPPLIED_CUSTOMER_NUMBER, ArKeyConstants.PaymentApplicationDocumentErrors.ENTERED_INVOICE_CUSTOMER_NUMBER_INVALID); 457 return null; 458 } 459 } 460 461 // validate the amount in the unapplied 462 if (payAppForm.getNonAppliedHoldingAmount() != null && payAppForm.getNonAppliedHoldingAmount().isNegative()) { 463 addFieldError(KFSConstants.PaymentApplicationTabErrorCodes.UNAPPLIED_TAB, ArPropertyConstants.PaymentApplicationDocumentFields.UNAPPLIED_AMOUNT, ArKeyConstants.PaymentApplicationDocumentErrors.UNAPPLIED_AMOUNT_CANNOT_BE_NEGATIVE); 464 return null; 465 } 466 467 // if we dont have enough information to make an UnApplied, then do nothing 468 if (StringUtils.isBlank(customerNumber) || amount == null || amount.isZero()) { 469 payAppDoc.setNonAppliedHolding(null); 470 return null; 471 } 472 473 // build a new NonAppliedHolding 474 NonAppliedHolding nonAppliedHolding = new NonAppliedHolding(); 475 nonAppliedHolding.setCustomerNumber(customerNumber); 476 nonAppliedHolding.setReferenceFinancialDocumentNumber(payAppDoc.getDocumentNumber()); 477 nonAppliedHolding.setFinancialDocumentLineAmount(amount); 478 479 // set it to the document 480 payAppDoc.setNonAppliedHolding(nonAppliedHolding); 481 482 // validate it 483 boolean isValid = PaymentApplicationDocumentRuleUtil.validateNonAppliedHolding(payAppDoc, payAppForm.getTotalFromControl()); 484 485 // check the validation results and return null if there were any errors 486 if (!isValid) { 487 return null; 488 } 489 490 return nonAppliedHolding; 491 } 492 493 /** 494 * This method loads the invoices for currently selected customer 495 * 496 * @param applicationDocumentForm 497 */ 498 protected void loadInvoices(PaymentApplicationDocumentForm payAppForm, String selectedInvoiceNumber) { 499 PaymentApplicationDocument payAppDoc = payAppForm.getPaymentApplicationDocument(); 500 AccountsReceivableDocumentHeader arDocHeader = payAppDoc.getAccountsReceivableDocumentHeader(); 501 String currentInvoiceNumber = selectedInvoiceNumber; 502 503 // before we do anything, validate the validity of any customerNumber or invoiceNumber 504 // entered against the db, and complain to the user if either is not right. 505 if (StringUtils.isNotBlank(payAppForm.getSelectedCustomerNumber())) { 506 Map<String, String> pkMap = new HashMap<String, String>(); 507 pkMap.put(ArPropertyConstants.CustomerFields.CUSTOMER_NUMBER, payAppForm.getSelectedCustomerNumber()); 508 int found = businessObjectService.countMatching(Customer.class, pkMap); 509 if (found == 0) { 510 addFieldError(KFSConstants.PaymentApplicationTabErrorCodes.APPLY_TO_INVOICE_DETAIL_TAB, ArPropertyConstants.PaymentApplicationDocumentFields.ENTERED_INVOICE_CUSTOMER_NUMBER, ArKeyConstants.PaymentApplicationDocumentErrors.ENTERED_INVOICE_CUSTOMER_NUMBER_INVALID); 511 } 512 } 513 boolean validInvoice = true; 514 if (StringUtils.isNotBlank(payAppForm.getEnteredInvoiceDocumentNumber())) { 515 Map<String, String> pkMap = new HashMap<String, String>(); 516 if (!SpringContext.getBean(CustomerInvoiceDocumentService.class).checkIfInvoiceNumberIsFinal(payAppForm.getEnteredInvoiceDocumentNumber())) { 517 validInvoice &= false; 518 addFieldError(KFSConstants.PaymentApplicationTabErrorCodes.APPLY_TO_INVOICE_DETAIL_TAB, ArPropertyConstants.PaymentApplicationDocumentFields.ENTERED_INVOICE_NUMBER, ArKeyConstants.ERROR_CUSTOMER_INVOICE_DOCUMENT_NOT_FINAL); 519 } 520 } 521 522 // This handles the priority of the payapp selected customer number and the 523 // ar doc header customer number. The ar doc header customer number should always 524 // reflect what customer number is entered on the form for invoices. This code chunk 525 // ensures that whatever the user enters always wins, but also tries to not load the form 526 // with an empty customer number wherever possible. 527 if (StringUtils.isBlank(payAppForm.getSelectedCustomerNumber())) { 528 if (StringUtils.isBlank(arDocHeader.getCustomerNumber())) { 529 if (payAppDoc.hasCashControlDetail()) { 530 payAppForm.setSelectedCustomerNumber(payAppDoc.getCashControlDetail().getCustomerNumber()); 531 arDocHeader.setCustomerNumber(payAppDoc.getCashControlDetail().getCustomerNumber()); 532 } 533 } 534 else { 535 payAppForm.setSelectedCustomerNumber(arDocHeader.getCustomerNumber()); 536 } 537 } 538 else { 539 arDocHeader.setCustomerNumber(payAppForm.getSelectedCustomerNumber()); 540 } 541 String customerNumber = payAppForm.getSelectedCustomerNumber(); 542 543 // Invoice number entered, but no customer number entered 544 if (StringUtils.isBlank(customerNumber) && StringUtils.isNotBlank(currentInvoiceNumber) && validInvoice) { 545 Customer customer = customerInvoiceDocumentService.getCustomerByInvoiceDocumentNumber(currentInvoiceNumber); 546 customerNumber = customer.getCustomerNumber(); 547 payAppDoc.getAccountsReceivableDocumentHeader().setCustomerNumber(customerNumber); 548 } 549 550 // load up the control docs and non-applied holdings for non-cash-control payapps 551 if (StringUtils.isNotBlank(customerNumber)) { 552 if (!payAppDoc.hasCashControlDocument()) { 553 List<PaymentApplicationDocument> nonAppliedControlDocs = new ArrayList<PaymentApplicationDocument>(); 554 List<NonAppliedHolding> nonAppliedControlHoldings = new ArrayList<NonAppliedHolding>(); 555 556 // if the doc is already final/approved, then we only pull the relevant control 557 // documents and nonapplied holdings that this doc paid against. 558 if (payAppDoc.isFinal()) { 559 nonAppliedControlDocs.addAll(payAppDoc.getPaymentApplicationDocumentsUsedAsControlDocuments()); 560 nonAppliedControlHoldings.addAll(payAppDoc.getNonAppliedHoldingsUsedAsControls()); 561 } 562 563 // otherwise, we pull all available non-zero non-applied holdings for 564 // this customer, and make the associated docs and non-applied holdings available 565 else { 566 // retrieve the set of available non-applied holdings for this customer 567 NonAppliedHoldingService nonAppliedHoldingService = SpringContext.getBean(NonAppliedHoldingService.class); 568 nonAppliedControlHoldings.addAll(nonAppliedHoldingService.getNonAppliedHoldingsForCustomer(customerNumber)); 569 570 // get the parent list of payapp documents that they come from 571 List<String> controlDocNumbers = new ArrayList<String>(); 572 for (NonAppliedHolding nonAppliedHolding : nonAppliedControlHoldings) { 573 if (nonAppliedHolding.getAvailableUnappliedAmount().isPositive()) { 574 if (!controlDocNumbers.contains(nonAppliedHolding.getReferenceFinancialDocumentNumber())) { 575 controlDocNumbers.add(nonAppliedHolding.getReferenceFinancialDocumentNumber()); 576 } 577 } 578 } 579 // only try to retrieve docs if we have any to retrieve 580 if (!controlDocNumbers.isEmpty()) { 581 try { 582 nonAppliedControlDocs.addAll(documentService.getDocumentsByListOfDocumentHeaderIds(PaymentApplicationDocument.class, controlDocNumbers)); 583 } 584 catch (WorkflowException e) { 585 throw new RuntimeException("A runtimeException was thrown when trying to retrieve a list of documents.", e); 586 } 587 } 588 } 589 590 // set the form vars from what we've loaded up here 591 payAppForm.setNonAppliedControlDocs(nonAppliedControlDocs); 592 payAppForm.setNonAppliedControlHoldings(nonAppliedControlHoldings); 593 payAppDoc.setNonAppliedHoldingsForCustomer(new ArrayList<NonAppliedHolding>(nonAppliedControlHoldings)); 594 payAppForm.setNonAppliedControlAllocations(null); 595 } 596 } 597 598 // reload invoices for the selected customer number 599 if (StringUtils.isNotBlank(customerNumber)) { 600 Collection<CustomerInvoiceDocument> openInvoicesForCustomer; 601 602 // we have to special case the invoices once the document is finished, because 603 // at this point, we want to show the invoices it paid against, NOT the set of 604 // open invoices 605 if (payAppDoc.isFinal()) { 606 openInvoicesForCustomer = payAppDoc.getInvoicesPaidAgainst(); 607 } 608 else { 609 openInvoicesForCustomer = customerInvoiceDocumentService.getOpenInvoiceDocumentsByCustomerNumber(customerNumber); 610 } 611 payAppForm.setInvoices(new ArrayList<CustomerInvoiceDocument>(openInvoicesForCustomer)); 612 payAppForm.setupInvoiceWrappers(payAppDoc.getDocumentNumber()); 613 } 614 615 // if no invoice number entered than get the first invoice 616 if (StringUtils.isNotBlank(customerNumber) && StringUtils.isBlank(currentInvoiceNumber)) { 617 if (payAppForm.getInvoices() == null || payAppForm.getInvoices().isEmpty()) { 618 currentInvoiceNumber = null; 619 } 620 else { 621 currentInvoiceNumber = payAppForm.getInvoices().get(0).getDocumentNumber(); 622 } 623 } 624 625 // load information for the current selected invoice 626 if (StringUtils.isNotBlank(currentInvoiceNumber)) { 627 payAppForm.setSelectedInvoiceDocumentNumber(currentInvoiceNumber); 628 payAppForm.setEnteredInvoiceDocumentNumber(currentInvoiceNumber); 629 } 630 631 // make sure all paidApplieds are synched with the PaymentApplicationInvoiceApply and 632 // PaymentApplicationInvoiceDetailApply objects, so that the form reflects how it was left pre-save. 633 // This is only necessary when the doc is saved, and then re-opened, as the invoice-detail wrappers 634 // will no longer hold the state info. I know this is a monstrosity. Get over it. 635 for (InvoicePaidApplied paidApplied : payAppDoc.getInvoicePaidApplieds()) { 636 for (PaymentApplicationInvoiceApply invoiceApplication : payAppForm.getInvoiceApplications()) { 637 if (paidApplied.getFinancialDocumentReferenceInvoiceNumber().equalsIgnoreCase(invoiceApplication.getDocumentNumber())) { 638 for (PaymentApplicationInvoiceDetailApply detailApplication : invoiceApplication.getDetailApplications()) { 639 if (paidApplied.getInvoiceItemNumber().equals(detailApplication.getSequenceNumber())) { 640 641 // if the amount applieds dont match, then have the paidApplied fill in the applied amounts 642 // for the invoiceApplication details 643 if (!paidApplied.getInvoiceItemAppliedAmount().equals(detailApplication.getAmountApplied())) { 644 detailApplication.setAmountApplied(paidApplied.getInvoiceItemAppliedAmount()); 645 if (paidApplied.getInvoiceItemAppliedAmount().equals(detailApplication.getAmountOpen())) { 646 detailApplication.setFullApply(true); 647 } 648 } 649 } 650 } 651 } 652 } 653 } 654 655 // clear any NonInvoiced add line information from the form vars 656 payAppForm.setNonInvoicedAddLine(null); 657 658 // load any NonAppliedHolding information into the form vars 659 if (payAppDoc.getNonAppliedHolding() != null) { 660 payAppForm.setNonAppliedHoldingCustomerNumber(payAppDoc.getNonAppliedHolding().getCustomerNumber()); 661 payAppForm.setNonAppliedHoldingAmount(payAppDoc.getNonAppliedHolding().getFinancialDocumentLineAmount()); 662 } 663 else { 664 // clear any NonAppliedHolding information from the form vars if it's empty 665 payAppForm.setNonAppliedHoldingCustomerNumber(null); 666 payAppForm.setNonAppliedHoldingAmount(null); 667 } 668 } 669 670 /** 671 * This method updates the customer invoice details when a new invoice is selected 672 * 673 * @param mapping 674 * @param form 675 * @param request 676 * @param response 677 * @return 678 * @throws Exception 679 */ 680 public ActionForward goToInvoice(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 681 PaymentApplicationDocumentForm payAppForm = (PaymentApplicationDocumentForm) form; 682 loadInvoices(payAppForm, payAppForm.getSelectedInvoiceDocumentNumber()); 683 if (!payAppForm.getPaymentApplicationDocument().isFinal()) { 684 doApplicationOfFunds(payAppForm); 685 } 686 return mapping.findForward(KFSConstants.MAPPING_BASIC); 687 } 688 689 /** 690 * This method updates customer invoice details when next invoice is selected 691 * 692 * @param mapping 693 * @param form 694 * @param request 695 * @param response 696 * @return 697 * @throws Exception 698 */ 699 public ActionForward goToNextInvoice(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 700 PaymentApplicationDocumentForm payAppForm = (PaymentApplicationDocumentForm) form; 701 loadInvoices(payAppForm, payAppForm.getNextInvoiceDocumentNumber()); 702 if (!payAppForm.getPaymentApplicationDocument().isFinal()) { 703 doApplicationOfFunds(payAppForm); 704 } 705 return mapping.findForward(KFSConstants.MAPPING_BASIC); 706 } 707 708 /** 709 * This method updates customer invoice details when previous invoice is selected 710 * 711 * @param mapping 712 * @param form 713 * @param request 714 * @param response 715 * @return 716 * @throws Exception 717 */ 718 public ActionForward goToPreviousInvoice(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 719 PaymentApplicationDocumentForm payAppForm = (PaymentApplicationDocumentForm) form; 720 loadInvoices(payAppForm, payAppForm.getPreviousInvoiceDocumentNumber()); 721 if (!payAppForm.getPaymentApplicationDocument().isFinal()) { 722 doApplicationOfFunds(payAppForm); 723 } 724 return mapping.findForward(KFSConstants.MAPPING_BASIC); 725 } 726 727 /** 728 * Retrieve all invoices for the selected customer. 729 * 730 * @param mapping 731 * @param form 732 * @param request 733 * @param response 734 * @return 735 * @throws Exception 736 */ 737 public ActionForward loadInvoices(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 738 PaymentApplicationDocumentForm pform = (PaymentApplicationDocumentForm) form; 739 loadInvoices(pform, pform.getEnteredInvoiceDocumentNumber()); 740 return mapping.findForward(KFSConstants.MAPPING_BASIC); 741 } 742 743 /** 744 * Cancel the document. 745 * 746 * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#cancel(org.apache.struts.action.ActionMapping, 747 * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 748 */ 749 @Override 750 public ActionForward cancel(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { 751 PaymentApplicationDocumentForm _form = (PaymentApplicationDocumentForm) form; 752 if (null == _form.getCashControlDocument()) { 753 return super.cancel(mapping, form, request, response); 754 } 755 return mapping.findForward(KFSConstants.MAPPING_BASIC); 756 } 757 758 /** 759 * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#createDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase) 760 */ 761 @Override 762 protected void createDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException { 763 super.createDocument(kualiDocumentFormBase); 764 PaymentApplicationDocumentForm form = (PaymentApplicationDocumentForm) kualiDocumentFormBase; 765 PaymentApplicationDocument document = form.getPaymentApplicationDocument(); 766 767 // create new accounts receivable header and set it to the payment application document 768 AccountsReceivableDocumentHeaderService accountsReceivableDocumentHeaderService = SpringContext.getBean(AccountsReceivableDocumentHeaderService.class); 769 AccountsReceivableDocumentHeader accountsReceivableDocumentHeader = accountsReceivableDocumentHeaderService.getNewAccountsReceivableDocumentHeaderForCurrentUser(); 770 accountsReceivableDocumentHeader.setDocumentNumber(document.getDocumentNumber()); 771 document.setAccountsReceivableDocumentHeader(accountsReceivableDocumentHeader); 772 } 773 774 /** 775 * @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#loadDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase) 776 */ 777 @Override 778 protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException { 779 super.loadDocument(kualiDocumentFormBase); 780 PaymentApplicationDocumentForm pform = (PaymentApplicationDocumentForm) kualiDocumentFormBase; 781 loadInvoices(pform, pform.getEnteredInvoiceDocumentNumber()); 782 } 783 784 /** 785 * Get an error to display in the UI for a certain field. 786 * 787 * @param propertyName 788 * @param errorKey 789 */ 790 protected void addFieldError(String errorPathToAdd, String propertyName, String errorKey) { 791 GlobalVariables.getMessageMap().addToErrorPath(errorPathToAdd); 792 GlobalVariables.getMessageMap().putError(propertyName, errorKey); 793 GlobalVariables.getMessageMap().removeFromErrorPath(errorPathToAdd); 794 } 795 796 /** 797 * Get an error to display at the global level, for the whole document. 798 * 799 * @param errorKey 800 */ 801 protected void addGlobalError(String errorKey) { 802 GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KNSConstants.DOCUMENT_ERRORS, errorKey, "document.hiddenFieldForErrors"); 803 } 804 805 }