View Javadoc
1   /*
2    * The Kuali Financial System, a comprehensive financial management system for higher education.
3    * 
4    * Copyright 2005-2014 The Kuali Foundation
5    * 
6    * This program is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Affero General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   * 
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Affero General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Affero General Public License
17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package org.kuali.kfs.module.ar.document.validation.impl;
20  
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.commons.lang.StringUtils;
26  import org.kuali.kfs.module.ar.ArAuthorizationConstants;
27  import org.kuali.kfs.module.ar.ArConstants;
28  import org.kuali.kfs.module.ar.ArKeyConstants;
29  import org.kuali.kfs.module.ar.ArPropertyConstants;
30  import org.kuali.kfs.module.ar.businessobject.CashControlDetail;
31  import org.kuali.kfs.module.ar.businessobject.Customer;
32  import org.kuali.kfs.module.ar.businessobject.PaymentMedium;
33  import org.kuali.kfs.module.ar.document.CashControlDocument;
34  import org.kuali.kfs.module.ar.document.PaymentApplicationDocument;
35  import org.kuali.kfs.module.ar.document.authorization.CashControlDocumentPresentationController;
36  import org.kuali.kfs.module.ar.document.validation.AddCashControlDetailRule;
37  import org.kuali.kfs.module.ar.document.validation.DeleteCashControlDetailRule;
38  import org.kuali.kfs.module.ar.document.validation.GenerateReferenceDocumentRule;
39  import org.kuali.kfs.sys.KFSConstants;
40  import org.kuali.kfs.sys.KFSPropertyConstants;
41  import org.kuali.kfs.sys.businessobject.Bank;
42  import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
43  import org.kuali.kfs.sys.context.SpringContext;
44  import org.kuali.kfs.sys.service.BankService;
45  import org.kuali.rice.kew.api.WorkflowDocument;
46  import org.kuali.rice.kns.service.DictionaryValidationService;
47  import org.kuali.rice.kns.service.DocumentHelperService;
48  import org.kuali.rice.krad.document.Document;
49  import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase;
50  import org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent;
51  import org.kuali.rice.krad.service.BusinessObjectService;
52  import org.kuali.rice.krad.service.DocumentService;
53  import org.kuali.rice.krad.util.GlobalVariables;
54  import org.kuali.rice.krad.util.MessageMap;
55  import org.kuali.rice.krad.util.ObjectUtils;
56  
57  /**
58   * This class holds the business rules for the AR Cash Control Document
59   */
60  public class CashControlDocumentRule extends TransactionalDocumentRuleBase implements AddCashControlDetailRule<CashControlDocument>, DeleteCashControlDetailRule<CashControlDocument>, GenerateReferenceDocumentRule<CashControlDocument> {
61  
62      protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CashControlDocumentRule.class);
63  
64      /**
65       * @see org.kuali.rice.krad.rules.TransactionalDocumentRuleBase#processCustomSaveDocumentBusinessRules(Document)
66       */
67      @Override
68      protected boolean processCustomSaveDocumentBusinessRules(Document document) {
69  
70          boolean isValid = super.processCustomSaveDocumentBusinessRules(document);
71          CashControlDocument ccDocument = (CashControlDocument) document;
72  
73          ccDocument.refreshReferenceObject(ArPropertyConstants.CashControlDocumentFields.CUSTOMER_PAYMENT_MEDIUM);
74          ccDocument.refreshReferenceObject(KFSPropertyConstants.GENERAL_LEDGER_PENDING_ENTRIES);
75  
76          MessageMap errorMap = GlobalVariables.getMessageMap();
77  
78          if (errorMap.hasErrors()) {
79              isValid &= checkRefDocNumber(ccDocument);
80              isValid &= validateCashControlDetails(ccDocument);
81          }
82  
83          return isValid;
84  
85      }
86  
87      /**
88       * @see org.kuali.rice.krad.rules.TransactionalDocumentRuleBase#processCustomRouteDocumentBusinessRules(Document)
89       */
90      @Override
91      protected boolean processCustomRouteDocumentBusinessRules(Document document) {
92  
93          boolean isValid = super.processCustomRouteDocumentBusinessRules(document);
94          CashControlDocument cashControlDocument = (CashControlDocument) document;
95  
96          if (isValid) {
97              isValid &= checkPaymentMedium(cashControlDocument);
98              isValid &= checkRefDocNumber(cashControlDocument);
99              isValid &= validateBankCode(cashControlDocument);
100             isValid &= validateCashControlDetails(cashControlDocument);
101             isValid &= checkCashControlDocumentHasDetails(cashControlDocument);
102         }
103 
104         return isValid;
105 
106     }
107 
108     /**
109      * @see org.kuali.rice.krad.rules.TransactionalDocumentRuleBase#processCustomApproveDocumentBusinessRules(Document)
110      */
111     @Override
112     protected boolean processCustomApproveDocumentBusinessRules(ApproveDocumentEvent approveEvent) {
113 
114         boolean isValid = super.processCustomApproveDocumentBusinessRules(approveEvent);
115         CashControlDocument cashControlDocument = (CashControlDocument) approveEvent.getDocument();
116 
117         cashControlDocument.refreshReferenceObject(ArPropertyConstants.CashControlDocumentFields.CUSTOMER_PAYMENT_MEDIUM);
118         cashControlDocument.refreshReferenceObject(KFSPropertyConstants.GENERAL_LEDGER_PENDING_ENTRIES);
119 
120         isValid &= checkAllAppDocsApproved(cashControlDocument);
121         isValid &= checkGLPEsCreated(cashControlDocument);
122 
123         return isValid;
124 
125     }
126 
127     /**
128      * This method checks the CashControlDetail line amount is not zero or negative.
129      *
130      * @param document the CashControldocument
131      * @param detail the CashControlDetail
132      * @return true is amount is valid, false otherwise
133      */
134     public boolean checkLineAmount(CashControlDocument document, CashControlDetail detail) {
135 
136         boolean isValid = true;
137 
138         // line amount cannot be zero
139         if (detail.getFinancialDocumentLineAmount().isZero()) {
140             GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.FINANCIAL_DOCUMENT_LINE_AMOUNT, ArKeyConstants.ERROR_LINE_AMOUNT_CANNOT_BE_ZERO);
141             isValid = false;
142         }
143         // line amount cannot be negative
144         if (detail.getFinancialDocumentLineAmount().isNegative()) {
145             GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.FINANCIAL_DOCUMENT_LINE_AMOUNT, ArKeyConstants.ERROR_LINE_AMOUNT_CANNOT_BE_NEGATIVE);
146             isValid = false;
147         }
148         return isValid;
149 
150     }
151 
152     /**
153      * This method checks if the CashControlDocument has any details to be processed.
154      *
155      * @param cashControlDocument the CashControlDocument
156      * @return true if it has details, false otherwise
157      */
158     public boolean checkCashControlDocumentHasDetails(CashControlDocument cashControlDocument) {
159 
160         boolean isValid = true;
161         GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
162 
163         if (cashControlDocument.getCashControlDetails().isEmpty()) {
164             GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CASH_CONTROL_DETAILS, ArKeyConstants.ERROR_NO_LINES_TO_PROCESS);
165         }
166 
167         GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
168 
169         return isValid;
170 
171     }
172 
173     /**
174      * This method checks that payment medium has a valid value
175      *
176      * @param document
177      * @return true if valid, false otherwise
178      */
179     public boolean checkPaymentMedium(CashControlDocument document) {
180 
181         boolean isValid = true;
182         GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
183         String paymentMediumCode = document.getCustomerPaymentMediumCode();
184 
185         Map<String, String> criteria = new HashMap<String, String>();
186         criteria.put("customerPaymentMediumCode", paymentMediumCode);
187 
188         PaymentMedium paymentMedium = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(PaymentMedium.class, criteria);
189 
190         if (paymentMedium == null) {
191             GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.CUSTOMER_PAYMENT_MEDIUM_CODE, ArKeyConstants.ERROR_PAYMENT_MEDIUM_IS_NOT_VALID);
192             isValid = false;
193         }
194 
195         GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
196         return isValid;
197 
198     }
199 
200     /**
201      * This method checks that reference document number is not null when payment medium is Cash.
202      *
203      * @param document CashControlDocument
204      * @return true if valid, false otherwise
205      */
206     public boolean checkRefDocNumber(CashControlDocument document) {
207 
208         boolean isValid = true;
209         GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
210         String paymentMedium = document.getCustomerPaymentMediumCode();
211         if (ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(paymentMedium)) {
212             String refDocNumber = document.getReferenceFinancialDocumentNumber();
213             try {
214                 Long.parseLong(refDocNumber);
215                 if (StringUtils.isBlank(refDocNumber)) {
216                     GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_CANNOT_BE_NULL_FOR_PAYMENT_MEDIUM_CASH);
217                     isValid = false;
218                 }
219                 else {
220                     boolean docExists = SpringContext.getBean(DocumentService.class).documentExists(refDocNumber);
221                     if (!docExists) {
222                         GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_MUST_BE_VALID_FOR_PAYMENT_MEDIUM_CASH);
223                         isValid = false;
224                     }
225                 }
226             }
227             catch (NumberFormatException nfe) {
228                 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_MUST_BE_VALID_FOR_PAYMENT_MEDIUM_CASH);
229                 isValid = false;
230             }
231 
232         }
233         GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
234         return isValid;
235 
236     }
237 
238     /**
239      * This method checks that the GLPEs have been created
240      *
241      * @param document CashControlDocument
242      * @return true if not null, false otherwise
243      */
244     public boolean checkGLPEsCreated(CashControlDocument cashControlDocument) {
245 
246         boolean isValid = true;
247         List<GeneralLedgerPendingEntry> glpes = cashControlDocument.getGeneralLedgerPendingEntries();
248 
249         Integer totalGLRecordsCreated = 0;
250 
251         if (glpes == null || glpes.isEmpty()) {
252             totalGLRecordsCreated = cashControlDocument.getGeneralLedgerEntriesPostedCount();
253         }
254 
255         // if payment medium is not Cash the general ledger pending entries must not be empty; if payment medium is Cash then a Cash
256         // Receipt Document must be created prior to creating the Cash Control document and it's number should be set in Reference
257         // Document number
258         if (!ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(cashControlDocument.getCustomerPaymentMediumCode()) && ((glpes == null || glpes.isEmpty()) && totalGLRecordsCreated.intValue() == 0)) {
259             GlobalVariables.getMessageMap().putError(KFSConstants.GENERAL_LEDGER_PENDING_ENTRIES_TAB_ERRORS, ArKeyConstants.ERROR_GLPES_NOT_CREATED);
260             isValid = false;
261         }
262 
263         return isValid;
264 
265     }
266 
267     /**
268      * @see org.kuali.kfs.module.ar.document.validation.AddCashControlDetailRule#processAddCashControlDetailBusinessRules(org.kuali.rice.krad.document.TransactionalDocument,
269      *      org.kuali.kfs.module.ar.businessobject.CashControlDetail)
270      */
271     @Override
272     public boolean processAddCashControlDetailBusinessRules(CashControlDocument transactionalDocument, CashControlDetail cashControlDetail) {
273 
274         boolean success = true;
275 
276         success &= checkGLPEsNotGenerated(transactionalDocument);
277         if (success) {
278             GlobalVariables.getMessageMap().removeFromErrorPath(ArConstants.NEW_CASH_CONTROL_DETAIL_ERROR_PATH_PREFIX);
279             success &= validateBankCode(transactionalDocument);
280             GlobalVariables.getMessageMap().addToErrorPath(ArConstants.NEW_CASH_CONTROL_DETAIL_ERROR_PATH_PREFIX);
281 
282             success &= validateCashControlDetail(transactionalDocument, cashControlDetail);
283         }
284         return success;
285 
286     }
287 
288     /**
289      * This method validates CashControlDetail
290      *
291      * @param document CashControlDocument
292      * @param cashControlDetail CashControlDetail
293      * @return true if CashControlDetail is valid, false otherwise
294      */
295     protected boolean validateCashControlDetail(CashControlDocument document, CashControlDetail cashControlDetail) {
296 
297         MessageMap errorMap = GlobalVariables.getMessageMap();
298 
299         boolean isValid = true;
300 
301         int originalErrorCount = errorMap.getErrorCount();
302         // call the DD validation which checks basic data integrity
303         SpringContext.getBean(DictionaryValidationService.class).validateBusinessObject(cashControlDetail);
304         isValid = (errorMap.getErrorCount() == originalErrorCount);
305 
306         // validate customer number and line amount
307         if (isValid) {
308             String customerNumber = cashControlDetail.getCustomerNumber();
309             // if customer number is not empty check that it is valid
310             if (customerNumber != null && !customerNumber.equals("")) {
311                 isValid &= checkCustomerNumber(customerNumber);
312             }
313             // check if line amount is valid
314             isValid &= checkLineAmount(document, cashControlDetail);
315         }
316 
317         return isValid;
318 
319     }
320 
321     /**
322      * This method validates cash control document's details
323      *
324      * @param cashControlDocument CashControldocument
325      * @return true if valid, false otherwise
326      */
327     public boolean validateCashControlDetails(CashControlDocument cashControlDocument) {
328 
329         GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
330         boolean isValid = true;
331 
332         for (int i = 0; i < cashControlDocument.getCashControlDetails().size(); i++) {
333 
334             CashControlDetail cashControlDetail = cashControlDocument.getCashControlDetail(i);
335             String propertyName = KFSPropertyConstants.CASH_CONTROL_DETAIL + "[" + i + "]";
336             GlobalVariables.getMessageMap().addToErrorPath(propertyName);
337 
338             isValid &= validateCashControlDetail(cashControlDocument, cashControlDetail);
339 
340             GlobalVariables.getMessageMap().removeFromErrorPath(propertyName);
341         }
342 
343         GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
344         return isValid;
345 
346     }
347 
348     /**
349      * This method checks that the customer number is valid and not an inactive customer when it is not empty
350      *
351      * @param cashControlDetail
352      * @return true if valid, false otherwise
353      */
354     protected boolean checkCustomerNumber(String customerNumber) {
355         boolean isValid = true;
356 
357         if (customerNumber != null && !customerNumber.equals("")) {
358 
359             Map<String, String> criteria = new HashMap<String, String>();
360             criteria.put(ArPropertyConstants.CustomerFields.CUSTOMER_NUMBER, customerNumber);
361 
362             Customer customer = SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Customer.class, criteria);
363 
364             if (customer == null) {
365                 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CustomerFields.CUSTOMER_NUMBER, ArKeyConstants.ERROR_CUSTOMER_NUMBER_IS_NOT_VALID);
366                 isValid = false;
367             }
368         }
369 
370         return isValid;
371     }
372 
373     /**
374      * This method checks if GLPEs have been already generated
375      *
376      * @param cashControlDocument the cash control document
377      * @return true if it was not generated, false otherwise
378      */
379     public boolean checkGLPEsNotGenerated(CashControlDocument cashControlDocument) {
380 
381         boolean success = true;
382         List<GeneralLedgerPendingEntry> glpes = cashControlDocument.getGeneralLedgerPendingEntries();
383 
384         if (glpes != null && !glpes.isEmpty()) {
385             success = false;
386             GlobalVariables.getMessageMap().putError(KFSConstants.GENERAL_LEDGER_PENDING_ENTRIES_TAB_ERRORS, ArKeyConstants.ERROR_DELETE_ADD_APP_DOCS_NOT_ALLOWED_AFTER_GLPES_GEN);
387         }
388         return success;
389 
390     }
391 
392     /**
393      * This method checks if all application documents are in approved or in final state
394      *
395      * @param cashControlDocument
396      * @return true if all application documents approved/final, false otherwise
397      */
398     public boolean checkAllAppDocsApproved(CashControlDocument cashControlDocument) {
399 
400         boolean allAppDocsApproved = true;
401 
402         for (int i = 0; i < cashControlDocument.getCashControlDetails().size(); i++) {
403 
404             CashControlDetail cashControlDetail = cashControlDocument.getCashControlDetail(i);
405             PaymentApplicationDocument applicationDocument = cashControlDetail.getReferenceFinancialDocument();
406             WorkflowDocument workflowDocument = applicationDocument.getDocumentHeader().getWorkflowDocument();
407 
408             if (!(workflowDocument.isApproved() || workflowDocument.isFinal())) {
409                 allAppDocsApproved = false;
410 
411                 String propertyName = KFSPropertyConstants.CASH_CONTROL_DETAIL + "[" + i + "]";
412                 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
413                 GlobalVariables.getMessageMap().addToErrorPath(propertyName);
414                 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.APPLICATION_DOC_STATUS, ArKeyConstants.ERROR_ALL_APPLICATION_DOCS_MUST_BE_APPROVED);
415                 GlobalVariables.getMessageMap().removeFromErrorPath(propertyName);
416                 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
417 
418                 break;
419             }
420 
421         }
422 
423         return allAppDocsApproved;
424 
425     }
426 
427     /**
428      * @see org.kuali.kfs.module.ar.document.validation.DeleteCashControlDetailRule#processDeleteCashControlDetailBusinessRules(org.kuali.rice.krad.document.TransactionalDocument,
429      *      org.kuali.kfs.module.ar.businessobject.CashControlDetail)
430      */
431     @Override
432     public boolean processDeleteCashControlDetailBusinessRules(CashControlDocument transactionalDocument, CashControlDetail cashControlDetail) {
433 
434         boolean success = true;
435         success &= checkGLPEsNotGenerated(transactionalDocument);
436         return success;
437 
438     }
439 
440     /**
441      * @see org.kuali.kfs.module.ar.document.validation.GenerateReferenceDocumentRule#processGenerateReferenceDocumentBusinessRules(org.kuali.rice.krad.document.TransactionalDocument)
442      */
443     @Override
444     public boolean processGenerateReferenceDocumentBusinessRules(CashControlDocument transactionalDocument) {
445 
446         boolean success = true;
447         success &= checkPaymentMedium(transactionalDocument);
448         if (success) {
449             success &= checkGLPEsNotGenerated(transactionalDocument);
450         }
451         return success;
452 
453     }
454 
455     /**
456      * validate bankCode
457      *
458      * @param document
459      * @return
460      */
461     public boolean validateBankCode(CashControlDocument document) {
462         boolean isValid = true;
463 
464         // if the EDIT_BANK_CODE isnt enabled, then dont bother checking it, return with success
465         CashControlDocumentPresentationController ccPC = (CashControlDocumentPresentationController) SpringContext.getBean(DocumentHelperService.class).getDocumentPresentationController(document);
466         if (!ccPC.getEditModes(document).contains(ArAuthorizationConstants.CashControlDocumentEditMode.EDIT_BANK_CODE)) {
467             return isValid;
468         }
469 
470         // otherwise, make sure it exists and is valid
471         String bankCode = document.getBankCode();
472         if (StringUtils.isNotBlank(bankCode)) {
473             Bank bank = SpringContext.getBean(BankService.class).getByPrimaryId(bankCode);
474             if (ObjectUtils.isNull(bank)) {
475                 isValid = false;
476                 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_INVALID_BANK_CODE);
477             }
478             else {
479                 // make sure the bank is eligible for deposit activity
480                 if (!bank.isBankDepositIndicator()) {
481                     isValid = false;
482                     GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_BANK_NOT_ELIGIBLE_FOR_DEPOSIT_ACTIVITY);
483                 }
484             }
485         }
486         else {
487             if (SpringContext.getBean(BankService.class).isBankSpecificationEnabled()) {
488                 isValid = false;
489                 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_BANK_CODE_REQUIRED);
490             }
491         }
492 
493         return isValid;
494     }
495 
496 }