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 }