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.validation.impl;
017
018 import java.util.HashMap;
019 import java.util.List;
020 import java.util.Map;
021
022 import org.apache.commons.lang.StringUtils;
023 import org.kuali.kfs.gl.service.EntryService;
024 import org.kuali.kfs.module.ar.ArAuthorizationConstants;
025 import org.kuali.kfs.module.ar.ArConstants;
026 import org.kuali.kfs.module.ar.ArKeyConstants;
027 import org.kuali.kfs.module.ar.ArPropertyConstants;
028 import org.kuali.kfs.module.ar.businessobject.CashControlDetail;
029 import org.kuali.kfs.module.ar.businessobject.Customer;
030 import org.kuali.kfs.module.ar.businessobject.PaymentMedium;
031 import org.kuali.kfs.module.ar.document.CashControlDocument;
032 import org.kuali.kfs.module.ar.document.PaymentApplicationDocument;
033 import org.kuali.kfs.module.ar.document.authorization.CashControlDocumentPresentationController;
034 import org.kuali.kfs.module.ar.document.validation.AddCashControlDetailRule;
035 import org.kuali.kfs.module.ar.document.validation.DeleteCashControlDetailRule;
036 import org.kuali.kfs.module.ar.document.validation.GenerateReferenceDocumentRule;
037 import org.kuali.kfs.sys.KFSConstants;
038 import org.kuali.kfs.sys.KFSPropertyConstants;
039 import org.kuali.kfs.sys.businessobject.Bank;
040 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
041 import org.kuali.kfs.sys.context.SpringContext;
042 import org.kuali.kfs.sys.service.BankService;
043 import org.kuali.rice.kns.document.Document;
044 import org.kuali.rice.kns.rule.event.ApproveDocumentEvent;
045 import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase;
046 import org.kuali.rice.kns.service.BusinessObjectService;
047 import org.kuali.rice.kns.service.DictionaryValidationService;
048 import org.kuali.rice.kns.service.DocumentHelperService;
049 import org.kuali.rice.kns.service.DocumentService;
050 import org.kuali.rice.kns.util.GlobalVariables;
051 import org.kuali.rice.kns.util.MessageMap;
052 import org.kuali.rice.kns.util.ObjectUtils;
053 import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
054
055 /**
056 * This class holds the business rules for the AR Cash Control Document
057 */
058 public class CashControlDocumentRule extends TransactionalDocumentRuleBase implements AddCashControlDetailRule<CashControlDocument>, DeleteCashControlDetailRule<CashControlDocument>, GenerateReferenceDocumentRule<CashControlDocument> {
059
060 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CashControlDocumentRule.class);
061
062 /**
063 * @see org.kuali.rice.kns.rules.TransactionalDocumentRuleBase#processCustomSaveDocumentBusinessRules(Document)
064 */
065 @Override
066 protected boolean processCustomSaveDocumentBusinessRules(Document document) {
067
068 boolean isValid = super.processCustomSaveDocumentBusinessRules(document);
069 CashControlDocument ccDocument = (CashControlDocument) document;
070
071 ccDocument.refreshReferenceObject("customerPaymentMedium");
072 ccDocument.refreshReferenceObject("generalLedgerPendingEntries");
073
074 MessageMap errorMap = GlobalVariables.getMessageMap();
075
076 if (errorMap.isEmpty()) {
077 isValid &= checkRefDocNumber(ccDocument);
078 isValid &= validateCashControlDetails(ccDocument);
079 }
080
081 return isValid;
082
083 }
084
085 /**
086 * @see org.kuali.rice.kns.rules.TransactionalDocumentRuleBase#processCustomRouteDocumentBusinessRules(Document)
087 */
088 @Override
089 protected boolean processCustomRouteDocumentBusinessRules(Document document) {
090
091 boolean isValid = super.processCustomRouteDocumentBusinessRules(document);
092 CashControlDocument cashControlDocument = (CashControlDocument) document;
093
094 if (isValid) {
095 isValid &= checkPaymentMedium(cashControlDocument);
096 isValid &= checkRefDocNumber(cashControlDocument);
097 isValid &= validateBankCode(cashControlDocument);
098 isValid &= validateCashControlDetails(cashControlDocument);
099 isValid &= checkCashControlDocumentHasDetails(cashControlDocument);
100 }
101
102 return isValid;
103
104 }
105
106 /**
107 * @see org.kuali.rice.kns.rules.TransactionalDocumentRuleBase#processCustomApproveDocumentBusinessRules(Document)
108 */
109 @Override
110 protected boolean processCustomApproveDocumentBusinessRules(ApproveDocumentEvent approveEvent) {
111
112 boolean isValid = super.processCustomApproveDocumentBusinessRules(approveEvent);
113 CashControlDocument cashControlDocument = (CashControlDocument) approveEvent.getDocument();
114
115 cashControlDocument.refreshReferenceObject("customerPaymentMedium");
116 cashControlDocument.refreshReferenceObject("generalLedgerPendingEntries");
117
118 isValid &= checkAllAppDocsApproved(cashControlDocument);
119 isValid &= checkGLPEsCreated(cashControlDocument);
120
121 return isValid;
122
123 }
124
125 /**
126 * This method checks the CashControlDetail line amount is not zero or negative.
127 *
128 * @param document the CashControldocument
129 * @param detail the CashControlDetail
130 * @return true is amount is valid, false otherwise
131 */
132 public boolean checkLineAmount(CashControlDocument document, CashControlDetail detail) {
133
134 boolean isValid = true;
135
136 // line amount cannot be zero
137 if (detail.getFinancialDocumentLineAmount().isZero()) {
138 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.FINANCIAL_DOCUMENT_LINE_AMOUNT, ArKeyConstants.ERROR_LINE_AMOUNT_CANNOT_BE_ZERO);
139 isValid = false;
140 }
141 // line amount cannot be negative
142 if (detail.getFinancialDocumentLineAmount().isNegative()) {
143 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.FINANCIAL_DOCUMENT_LINE_AMOUNT, ArKeyConstants.ERROR_LINE_AMOUNT_CANNOT_BE_NEGATIVE);
144 isValid = false;
145 }
146 return isValid;
147
148 }
149
150 /**
151 * This method checks if the CashControlDocument has any details to be processed.
152 *
153 * @param cashControlDocument the CashControlDocument
154 * @return true if it has details, false otherwise
155 */
156 public boolean checkCashControlDocumentHasDetails(CashControlDocument cashControlDocument) {
157
158 boolean isValid = true;
159 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
160
161 if (cashControlDocument.getCashControlDetails().isEmpty()) {
162 GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CASH_CONTROL_DETAILS, ArKeyConstants.ERROR_NO_LINES_TO_PROCESS);
163 }
164
165 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
166
167 return isValid;
168
169 }
170
171 /**
172 * This method checks that payment medium has a valid value
173 *
174 * @param document
175 * @return true if valid, false otherwise
176 */
177 public boolean checkPaymentMedium(CashControlDocument document) {
178
179 boolean isValid = true;
180 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
181 String paymentMediumCode = document.getCustomerPaymentMediumCode();
182
183 Map<String, String> criteria = new HashMap<String, String>();
184 criteria.put("customerPaymentMediumCode", paymentMediumCode);
185
186 PaymentMedium paymentMedium = (PaymentMedium) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(PaymentMedium.class, criteria);
187
188 if (paymentMedium == null) {
189 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.CUSTOMER_PAYMENT_MEDIUM_CODE, ArKeyConstants.ERROR_PAYMENT_MEDIUM_IS_NOT_VALID);
190 isValid = false;
191 }
192
193 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
194 return isValid;
195
196 }
197
198 /**
199 * This method checks that reference document number is not null when payment medium is Cash.
200 *
201 * @param document CashControlDocument
202 * @return true if valid, false otherwise
203 */
204 public boolean checkRefDocNumber(CashControlDocument document) {
205
206 boolean isValid = true;
207 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
208 String paymentMedium = document.getCustomerPaymentMediumCode();
209 if (ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(paymentMedium)) {
210 String refDocNumber = document.getReferenceFinancialDocumentNumber();
211 try {
212 Long.parseLong(refDocNumber);
213 if (StringUtils.isBlank(refDocNumber)) {
214 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_CANNOT_BE_NULL_FOR_PAYMENT_MEDIUM_CASH);
215 isValid = false;
216 }
217 else {
218 boolean docExists = SpringContext.getBean(DocumentService.class).documentExists(refDocNumber);
219 if (!docExists) {
220 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_MUST_BE_VALID_FOR_PAYMENT_MEDIUM_CASH);
221 isValid = false;
222 }
223 }
224 }
225 catch (NumberFormatException nfe) {
226 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.REFERENCE_FINANCIAL_DOC_NBR, ArKeyConstants.ERROR_REFERENCE_DOC_NUMBER_MUST_BE_VALID_FOR_PAYMENT_MEDIUM_CASH);
227 isValid = false;
228 }
229
230 }
231 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
232 return isValid;
233
234 }
235
236 /**
237 * This method checks that the GLPEs have been created
238 *
239 * @param document CashControlDocument
240 * @return true if not null, false otherwise
241 */
242 public boolean checkGLPEsCreated(CashControlDocument cashControlDocument) {
243
244 boolean isValid = true;
245 List<GeneralLedgerPendingEntry> glpes = cashControlDocument.getGeneralLedgerPendingEntries();
246
247 Integer totalGLRecordsCreated = 0;
248
249 if (glpes == null || glpes.isEmpty()) {
250 totalGLRecordsCreated = cashControlDocument.getGeneralLedgerEntriesPostedCount();
251 }
252
253 // if payment medium is not Cash the general ledger pending entries must not be empty; if payment medium is Cash then a Cash
254 // Receipt Document must be created prior to creating the Cash Control document and it's number should be set in Reference
255 // Document number
256 if (!ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(cashControlDocument.getCustomerPaymentMediumCode()) && ((glpes == null || glpes.isEmpty()) && totalGLRecordsCreated.intValue() == 0)) {
257 GlobalVariables.getMessageMap().putError(KFSConstants.GENERAL_LEDGER_PENDING_ENTRIES_TAB_ERRORS, ArKeyConstants.ERROR_GLPES_NOT_CREATED);
258 isValid = false;
259 }
260
261 return isValid;
262
263 }
264
265 /**
266 * @see org.kuali.kfs.module.ar.document.validation.AddCashControlDetailRule#processAddCashControlDetailBusinessRules(org.kuali.rice.kns.document.TransactionalDocument,
267 * org.kuali.kfs.module.ar.businessobject.CashControlDetail)
268 */
269 public boolean processAddCashControlDetailBusinessRules(CashControlDocument transactionalDocument, CashControlDetail cashControlDetail) {
270
271 boolean success = true;
272
273 success &= checkGLPEsNotGenerated(transactionalDocument);
274 if (success) {
275 GlobalVariables.getMessageMap().removeFromErrorPath(ArConstants.NEW_CASH_CONTROL_DETAIL_ERROR_PATH_PREFIX);
276 success &= validateBankCode(transactionalDocument);
277 GlobalVariables.getMessageMap().addToErrorPath(ArConstants.NEW_CASH_CONTROL_DETAIL_ERROR_PATH_PREFIX);
278
279 success &= validateCashControlDetail(transactionalDocument, cashControlDetail);
280 }
281 return success;
282
283 }
284
285 /**
286 * This method validates CashControlDetail
287 *
288 * @param document CashControlDocument
289 * @param cashControlDetail CashControlDetail
290 * @return true if CashControlDetail is valid, false otherwise
291 */
292 protected boolean validateCashControlDetail(CashControlDocument document, CashControlDetail cashControlDetail) {
293
294 MessageMap errorMap = GlobalVariables.getMessageMap();
295
296 boolean isValid = true;
297
298 int originalErrorCount = errorMap.getErrorCount();
299 // call the DD validation which checks basic data integrity
300 SpringContext.getBean(DictionaryValidationService.class).validateBusinessObject(cashControlDetail);
301 isValid = (errorMap.getErrorCount() == originalErrorCount);
302
303 // validate customer number and line amount
304 if (isValid) {
305 String customerNumber = cashControlDetail.getCustomerNumber();
306 // if customer number is not empty check that it is valid
307 if (customerNumber != null && !customerNumber.equals("")) {
308 isValid &= checkCustomerNumber(customerNumber);
309 }
310 // check if line amount is valid
311 isValid &= checkLineAmount(document, cashControlDetail);
312 }
313
314 return isValid;
315
316 }
317
318 /**
319 * This method validates cash control document's details
320 *
321 * @param cashControlDocument CashControldocument
322 * @return true if valid, false otherwise
323 */
324 public boolean validateCashControlDetails(CashControlDocument cashControlDocument) {
325
326 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
327 boolean isValid = true;
328
329 for (int i = 0; i < cashControlDocument.getCashControlDetails().size(); i++) {
330
331 CashControlDetail cashControlDetail = cashControlDocument.getCashControlDetail(i);
332 String propertyName = KFSPropertyConstants.CASH_CONTROL_DETAIL + "[" + i + "]";
333 GlobalVariables.getMessageMap().addToErrorPath(propertyName);
334
335 isValid &= validateCashControlDetail(cashControlDocument, cashControlDetail);
336
337 GlobalVariables.getMessageMap().removeFromErrorPath(propertyName);
338 }
339
340 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
341 return isValid;
342
343 }
344
345 /**
346 * This method checks that the customer number is valid and not an inactive customer when it is not empty
347 *
348 * @param cashControlDetail
349 * @return true if valid, false otherwise
350 */
351 protected boolean checkCustomerNumber(String customerNumber) {
352 boolean isValid = true;
353
354 if (customerNumber != null && !customerNumber.equals("")) {
355
356 Map<String, String> criteria = new HashMap<String, String>();
357 criteria.put("customerNumber", customerNumber);
358
359 Customer customer = (Customer) SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(Customer.class, criteria);
360
361 if (customer == null) {
362 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.CUSTOMER_NUMBER, ArKeyConstants.ERROR_CUSTOMER_NUMBER_IS_NOT_VALID);
363 isValid = false;
364 }
365 }
366
367 return isValid;
368 }
369
370 /**
371 * This method checks if GLPEs have been already generated
372 *
373 * @param cashControlDocument the cash control document
374 * @return true if it was not generated, false otherwise
375 */
376 public boolean checkGLPEsNotGenerated(CashControlDocument cashControlDocument) {
377
378 boolean success = true;
379 List<GeneralLedgerPendingEntry> glpes = cashControlDocument.getGeneralLedgerPendingEntries();
380
381 if (glpes != null && !glpes.isEmpty()) {
382 success = false;
383 GlobalVariables.getMessageMap().putError(KFSConstants.GENERAL_LEDGER_PENDING_ENTRIES_TAB_ERRORS, ArKeyConstants.ERROR_DELETE_ADD_APP_DOCS_NOT_ALLOWED_AFTER_GLPES_GEN);
384 }
385 return success;
386
387 }
388
389 /**
390 * This method checks if all application documents are in approved or in final state
391 *
392 * @param cashControlDocument
393 * @return true if all application documents approved/final, false otherwise
394 */
395 public boolean checkAllAppDocsApproved(CashControlDocument cashControlDocument) {
396
397 boolean allAppDocsApproved = true;
398
399 for (int i = 0; i < cashControlDocument.getCashControlDetails().size(); i++) {
400
401 CashControlDetail cashControlDetail = cashControlDocument.getCashControlDetail(i);
402 PaymentApplicationDocument applicationDocument = cashControlDetail.getReferenceFinancialDocument();
403 KualiWorkflowDocument workflowDocument = applicationDocument.getDocumentHeader().getWorkflowDocument();
404
405 if (!(workflowDocument.stateIsApproved() || workflowDocument.stateIsFinal())) {
406 allAppDocsApproved = false;
407
408 String propertyName = KFSPropertyConstants.CASH_CONTROL_DETAIL + "[" + i + "]";
409 GlobalVariables.getMessageMap().addToErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
410 GlobalVariables.getMessageMap().addToErrorPath(propertyName);
411 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.APPLICATION_DOC_STATUS, ArKeyConstants.ERROR_ALL_APPLICATION_DOCS_MUST_BE_APPROVED);
412 GlobalVariables.getMessageMap().removeFromErrorPath(propertyName);
413 GlobalVariables.getMessageMap().removeFromErrorPath(KFSConstants.DOCUMENT_PROPERTY_NAME);
414
415 break;
416 }
417
418 }
419
420 return allAppDocsApproved;
421
422 }
423
424 /**
425 * @see org.kuali.kfs.module.ar.document.validation.DeleteCashControlDetailRule#processDeleteCashControlDetailBusinessRules(org.kuali.rice.kns.document.TransactionalDocument,
426 * org.kuali.kfs.module.ar.businessobject.CashControlDetail)
427 */
428 public boolean processDeleteCashControlDetailBusinessRules(CashControlDocument transactionalDocument, CashControlDetail cashControlDetail) {
429
430 boolean success = true;
431 success &= checkGLPEsNotGenerated(transactionalDocument);
432 return success;
433
434 }
435
436 /**
437 * @see org.kuali.kfs.module.ar.document.validation.GenerateReferenceDocumentRule#processGenerateReferenceDocumentBusinessRules(org.kuali.rice.kns.document.TransactionalDocument)
438 */
439 public boolean processGenerateReferenceDocumentBusinessRules(CashControlDocument transactionalDocument) {
440
441 boolean success = true;
442 success &= checkPaymentMedium(transactionalDocument);
443 if (success) {
444 success &= checkGLPEsNotGenerated(transactionalDocument);
445 }
446 return success;
447
448 }
449
450 // validate bankCode
451 public boolean validateBankCode(CashControlDocument document) {
452 boolean isValid = true;
453
454 // if the EDIT_BANK_CODE isnt enabled, then dont bother checking it, return with success
455 CashControlDocumentPresentationController ccPC = (CashControlDocumentPresentationController) SpringContext.getBean(DocumentHelperService.class).getDocumentPresentationController(document);
456 if (!ccPC.getEditModes(document).contains(ArAuthorizationConstants.CashControlDocumentEditMode.EDIT_BANK_CODE)) {
457 return isValid;
458 }
459
460 // otherwise, make sure it exists and is valid
461 String bankCode = document.getBankCode();
462 if (StringUtils.isNotBlank(bankCode)) {
463 Bank bank = SpringContext.getBean(BankService.class).getByPrimaryId(bankCode);
464 if (ObjectUtils.isNull(bank)) {
465 isValid = false;
466 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_INVALID_BANK_CODE);
467 }
468 else {
469 // make sure the bank is eligible for deposit activity
470 if (!bank.isBankDepositIndicator()) {
471 isValid = false;
472 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_BANK_NOT_ELIGIBLE_FOR_DEPOSIT_ACTIVITY);
473 }
474 }
475 }
476 else {
477 if (SpringContext.getBean(BankService.class).isBankSpecificationEnabled()) {
478 isValid = false;
479 GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDocumentFields.BANK_CODE, ArKeyConstants.ERROR_BANK_CODE_REQUIRED);
480 }
481 }
482
483 return isValid;
484 }
485
486 }