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.coa.document.validation.impl; 017 018 import java.util.List; 019 020 import org.apache.commons.lang.StringUtils; 021 import org.kuali.kfs.coa.businessobject.AccountGlobalDetail; 022 import org.kuali.kfs.coa.businessobject.SubObjectCodeGlobal; 023 import org.kuali.kfs.coa.businessobject.SubObjectCodeGlobalDetail; 024 import org.kuali.kfs.sys.KFSConstants; 025 import org.kuali.kfs.sys.KFSKeyConstants; 026 import org.kuali.kfs.sys.KFSPropertyConstants; 027 import org.kuali.rice.kns.bo.PersistableBusinessObject; 028 import org.kuali.rice.kns.document.MaintenanceDocument; 029 import org.kuali.rice.kns.util.GlobalVariables; 030 import org.kuali.rice.kns.util.ObjectUtils; 031 032 /** 033 * 034 * This class implements the business rules specific to the {@link SubObjCdGlobal} Maintenance Document. 035 */ 036 public class SubObjCdGlobalRule extends GlobalDocumentRuleBase { 037 protected SubObjectCodeGlobal subObjCdGlobal; 038 039 /** 040 * This method sets the convenience objects like subObjCdGlobal and all the detail objects, so you have short and easy handles to the new and 041 * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load 042 * all sub-objects from the DB by their primary keys, if available. This also loops through each detail item (SubObjCdGlobalDetail and AccountGlobalDetail) 043 * are refreshed 044 * 045 * @param document - the maintenanceDocument being evaluated 046 */ 047 @Override 048 public void setupConvenienceObjects() { 049 050 // setup subObjCdGlobal convenience objects, 051 // make sure all possible sub-objects are populated 052 subObjCdGlobal = (SubObjectCodeGlobal) super.getNewBo(); 053 054 // forces refreshes on all the sub-objects in the lists 055 for (SubObjectCodeGlobalDetail objectCodeGlobalDetail : subObjCdGlobal.getSubObjCdGlobalDetails()) { 056 objectCodeGlobalDetail.refreshNonUpdateableReferences(); 057 } 058 for (AccountGlobalDetail accountGlobalDetail : subObjCdGlobal.getAccountGlobalDetails()) { 059 accountGlobalDetail.refreshNonUpdateableReferences(); 060 } 061 } 062 063 /** 064 * This performs rules checks on document approve 065 * <ul> 066 * <li>{@link SubObjCdGlobalRule#checkSimpleRulesAllLines()}</li> 067 * </ul> 068 * This rule fails on business rule failures 069 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 070 */ 071 @Override 072 protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) { 073 boolean success = true; 074 setupConvenienceObjects(); 075 // check simple rules 076 success &= checkSimpleRulesAllLines(); 077 078 success &= checkOnlyOneChartErrorWrapper(subObjCdGlobal.getAccountGlobalDetails()); 079 080 return success; 081 } 082 083 /** 084 * This performs rules checks on document route 085 * <ul> 086 * <li>{@link SubObjCdGlobalRule#checkSimpleRulesAllLines()}</li> 087 * </ul> 088 * This rule fails on business rule failures 089 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 090 */ 091 @Override 092 protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { 093 boolean success = true; 094 setupConvenienceObjects(); 095 // check simple rules 096 success &= checkSimpleRulesAllLines(); 097 098 success &= checkAccountDetails(subObjCdGlobal.getAccountGlobalDetails()); 099 100 return success; 101 } 102 103 /** 104 * This performs rules checks on document save 105 * <ul> 106 * <li>{@link SubObjCdGlobalRule#checkSimpleRulesAllLines()}</li> 107 * </ul> 108 * This rule does not fail on business rule failures 109 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument) 110 */ 111 @Override 112 protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) { 113 setupConvenienceObjects(); 114 // check simple rules 115 checkSimpleRulesAllLines(); 116 117 return true; 118 } 119 120 /** 121 * Before adding either a {@link AccountGlobalDetail} or {@link SubObjCdGlobalDetail} this checks to make sure 122 * that the account and chart are filled in, in the case of SubObjCdGlobalDetail it also checks 123 * that the object code and fiscal year are filled in 124 * If any of these fail, it fails to add the new line to the collection 125 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument, java.lang.String, org.kuali.rice.kns.bo.PersistableBusinessObject) 126 */ 127 public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) { 128 boolean success = true; 129 if (bo instanceof AccountGlobalDetail) { 130 AccountGlobalDetail detail = (AccountGlobalDetail) bo; 131 // make sure that both primary keys are available for this object 132 if (!checkEmptyValue(detail.getAccountNumber())) { 133 // put an error about accountnumber 134 GlobalVariables.getMessageMap().putError("accountNumber", KFSKeyConstants.ERROR_REQUIRED, "Account Number"); 135 success &= false; 136 } 137 if (!checkEmptyValue(detail.getChartOfAccountsCode())) { 138 // put an error about chart code 139 GlobalVariables.getMessageMap().putError("chartOfAccountsCode", KFSKeyConstants.ERROR_REQUIRED, "Chart of Accounts Code"); 140 success &= false; 141 } 142 success &= checkAccountDetails(detail); 143 } 144 else if (bo instanceof SubObjectCodeGlobalDetail) { 145 SubObjectCodeGlobalDetail detail = (SubObjectCodeGlobalDetail) bo; 146 if (!checkEmptyValue(detail.getChartOfAccountsCode())) { 147 // put an error about accountnumber 148 GlobalVariables.getMessageMap().putError("chartOfAccountsCode", KFSKeyConstants.ERROR_REQUIRED, "Chart of Accounts Code"); 149 success &= false; 150 } 151 if (!checkEmptyValue(detail.getFinancialObjectCode())) { 152 // put an error about financial object code 153 GlobalVariables.getMessageMap().putError("financialObjectCode", KFSKeyConstants.ERROR_REQUIRED, "Financial Object Code"); 154 success &= false; 155 } 156 if (!checkEmptyValue(detail.getUniversityFiscalYear())) { 157 // put an error about financial object code 158 GlobalVariables.getMessageMap().putError("universityFiscalYear", KFSKeyConstants.ERROR_REQUIRED, "University Fiscal Year"); 159 success &= false; 160 } 161 success &= checkSubObjectCodeDetails(detail); 162 } 163 return success; 164 } 165 166 /** 167 * 168 * This calls the {@link SubObjCdGlobalRule#checkAccountDetails(AccountGlobalDetail)} on each AccountGlobalDetail as well as calling 169 * {@link SubObjCdGlobalRule#checkOnlyOneChartErrorWrapper(List)} to ensure there is just one chart 170 * @param details 171 * @return false if any of the detail objects fail they're sub-rule 172 */ 173 public boolean checkAccountDetails(List<AccountGlobalDetail> details) { 174 boolean success = true; 175 176 // check if there are any accounts 177 if (details.size() == 0) { 178 putFieldError(KFSConstants.MAINTENANCE_ADD_PREFIX + "accountGlobalDetails.accountNumber", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_NO_ACCOUNTS); 179 success = false; 180 } 181 else { 182 // check each account 183 int index = 0; 184 for (AccountGlobalDetail dtl : details) { 185 String errorPath = MAINTAINABLE_ERROR_PREFIX + "accountGlobalDetails[" + index + "]"; 186 GlobalVariables.getMessageMap().addToErrorPath(errorPath); 187 success &= checkAccountDetails(dtl); 188 GlobalVariables.getMessageMap().removeFromErrorPath(errorPath); 189 index++; 190 } 191 success &= checkOnlyOneChartErrorWrapper(details); 192 } 193 194 return success; 195 } 196 197 /** 198 * 199 * This checks that if the account and chart are entered that the account associated with the AccountGlobalDetail is valid 200 * @param dtl - the AccountGlobalDetail we are dealing with 201 * @return false if any of the fields are found to be invalid 202 */ 203 public boolean checkAccountDetails(AccountGlobalDetail dtl) { 204 boolean success = true; 205 int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount(); 206 getDictionaryValidationService().validateBusinessObject(dtl); 207 if (StringUtils.isNotBlank(dtl.getAccountNumber()) && StringUtils.isNotBlank(dtl.getChartOfAccountsCode())) { 208 dtl.refreshReferenceObject("account"); 209 if (ObjectUtils.isNull(dtl.getAccount())) { 210 GlobalVariables.getMessageMap().putError("accountNumber", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ACCOUNT, new String[] { dtl.getChartOfAccountsCode(), dtl.getAccountNumber() }); 211 } 212 } 213 success &= GlobalVariables.getMessageMap().getErrorCount() == originalErrorCount; 214 215 return success; 216 } 217 218 /** 219 * 220 * This checks that if the object code, chart code, and fiscal year are entered it is a valid Object Code, chart, and Fiscal Year 221 * associated with this SubObjectCode 222 * @param dtl - the SubObjCdGlobalDetail we are checking 223 * @return false if any of the fields are found to be invalid 224 */ 225 public boolean checkSubObjectCodeDetails(SubObjectCodeGlobalDetail dtl) { 226 boolean success = true; 227 int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount(); 228 getDictionaryValidationService().validateBusinessObject(dtl); 229 if (StringUtils.isNotBlank(dtl.getFinancialObjectCode()) && StringUtils.isNotBlank(dtl.getChartOfAccountsCode()) && dtl.getUniversityFiscalYear() > 0) { 230 dtl.refreshReferenceObject("financialObject"); 231 dtl.refreshReferenceObject("universityFiscal"); 232 dtl.refreshReferenceObject("chartOfAccounts"); 233 if (ObjectUtils.isNull(dtl.getChartOfAccounts()) || ObjectUtils.isNull(dtl.getUniversityFiscal()) || ObjectUtils.isNull(dtl.getFinancialObject())) { 234 GlobalVariables.getMessageMap().putError("financialObjectCode", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_INVALID_OBJECT_CODE, new String[] { dtl.getFinancialObjectCode(), dtl.getChartOfAccountsCode(), dtl.getUniversityFiscalYear().toString() }); 235 } 236 } 237 success &= GlobalVariables.getMessageMap().getErrorCount() == originalErrorCount; 238 239 return success; 240 } 241 242 /** 243 * This method checks the simple rules for all lines at once and gets called on save, submit, etc. but not on add 244 * <ul> 245 * <li>{@link SubObjCdGlobalRule#checkForSubObjCdGlobalDetails(List)}</li> 246 * <li>{@link SubObjCdGlobalRule#checkForAccountGlobalDetails(List)}</li> 247 * <li>{@link SubObjCdGlobalRule#checkFiscalYearAllLines(SubObjCdGlobal)}</li> 248 * <li>{@link SubObjCdGlobalRule#checkChartAllLines(SubObjCdGlobal)}</li> 249 * </ul> 250 * @return 251 */ 252 protected boolean checkSimpleRulesAllLines() { 253 boolean success = true; 254 // check if there are any object codes and accounts, if either fails this should fail 255 if (!checkForSubObjCdGlobalDetails(subObjCdGlobal.getSubObjCdGlobalDetails()) && !checkForAccountGlobalDetails(subObjCdGlobal.getAccountGlobalDetails())) { 256 success = false; 257 } 258 else { 259 // check object codes 260 success &= checkFiscalYearAllLines(subObjCdGlobal); 261 262 // check chart code 263 success &= checkChartAllLines(subObjCdGlobal); 264 265 } 266 return success; 267 } 268 269 /** 270 * 271 * This checks that the SubObjCdGlobalDetail list isn't empty or null 272 * @param subObjCdGlobalDetails 273 * @return false if the list is null or empty 274 */ 275 protected boolean checkForSubObjCdGlobalDetails(List<SubObjectCodeGlobalDetail> subObjCdGlobalDetails) { 276 if (subObjCdGlobalDetails == null || subObjCdGlobalDetails.size() == 0) { 277 putFieldError(KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_NO_OBJECT_CODE); 278 return false; 279 } 280 return true; 281 } 282 283 /** 284 * 285 * This checks that the AccountGlobalDetail list isn't empty or null 286 * @param acctChangeDetails 287 * @return false if the list is null or empty 288 */ 289 protected boolean checkForAccountGlobalDetails(List<AccountGlobalDetail> acctChangeDetails) { 290 if (acctChangeDetails == null || acctChangeDetails.size() == 0) { 291 putFieldError(KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.ACCOUNT_CHANGE_DETAILS + "." + KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_NO_ACCOUNT); 292 return false; 293 } 294 return true; 295 } 296 297 /** 298 * 299 * This checks that the fiscal year is the same on the doc and all SubObjCdGlobalDetails 300 * @param socChangeDocument 301 * @return false if the fiscal year is not the same on the doc and any of the SubObjCdGlobalDetails 302 */ 303 protected boolean checkFiscalYearAllLines(SubObjectCodeGlobal socChangeDocument) { 304 boolean success = true; 305 int i = 0; 306 for (SubObjectCodeGlobalDetail subObjCdGlobal : socChangeDocument.getSubObjCdGlobalDetails()) { 307 308 // check fiscal year first 309 success &= checkFiscalYear(socChangeDocument, subObjCdGlobal, i, false); 310 311 // increment counter for sub object changes list 312 i++; 313 } 314 315 return success; 316 } 317 318 /** 319 * 320 * This checks that the chart is the same on the document, SubObjCdGlobalDetails and AccountGlobalDetails 321 * @param socChangeDocument 322 * @return false if the chart is missing or not the same on the doc, or the detail lists 323 */ 324 protected boolean checkChartAllLines(SubObjectCodeGlobal socChangeDocument) { 325 boolean success = true; 326 int i = 0; 327 for (SubObjectCodeGlobalDetail subObjCdGlobal : socChangeDocument.getSubObjCdGlobalDetails()) { 328 329 // check chart 330 success &= checkChartOnSubObjCodeDetails(socChangeDocument, subObjCdGlobal, i, false); 331 // increment counter for sub object changes list 332 i++; 333 } 334 335 // check each account change 336 i = 0; 337 for (AccountGlobalDetail acctChangeDetail : socChangeDocument.getAccountGlobalDetails()) { 338 339 // check chart 340 success &= checkChartOnAccountDetails(socChangeDocument, acctChangeDetail, i, false); 341 // increment counter for account changes list 342 i++; 343 } 344 345 return success; 346 } 347 348 /** 349 * This checks to make sure that the fiscal year on the {@link SubObjCdGlobalDetail} is not empty and 350 * the document's fiscal year matches the detail's fiscal year 351 * 352 * @param socChangeDocument 353 * @return false if the fiscal year is missing or is not the same between the doc and the detail 354 */ 355 protected boolean checkFiscalYear(SubObjectCodeGlobal socChangeDocument, SubObjectCodeGlobalDetail socChangeDetail, int lineNum, boolean add) { 356 boolean success = true; 357 String errorPath = KFSConstants.EMPTY_STRING; 358 // first must have an actual fiscal year 359 if (ObjectUtils.isNull(socChangeDetail.getUniversityFiscal())) { 360 if (add) { 361 errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + "universityFiscalYear"; 362 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_FISCAL_YEAR_MUST_EXIST); 363 } 364 else { 365 errorPath = KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "[" + lineNum + "]." + "universityFiscalYear"; 366 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_FISCAL_YEAR_MUST_EXIST); 367 } 368 success &= false; 369 return success; 370 } 371 372 // the two fiscal years from the document and detail must match 373 if (!socChangeDocument.getUniversityFiscal().equals(socChangeDetail.getUniversityFiscal())) { 374 if (add) { 375 errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + "universityFiscalYear"; 376 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_FISCAL_YEAR_MUST_BE_SAME); 377 } 378 else { 379 errorPath = KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "[" + lineNum + "]." + "universityFiscalYear"; 380 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_FISCAL_YEAR_MUST_BE_SAME); 381 } 382 success &= false; 383 return success; 384 } 385 386 return success; 387 } 388 389 /** 390 * 391 * This checks to make sure that the chart of accounts on the {@link SubObjCdGlobalDetail} is not empty and 392 * the document's chart matches the detail's chart 393 * @param socChangeDocument 394 * @param socChangeDetail 395 * @param lineNum 396 * @param add 397 * @return false if the chart is missing or is not the same between the doc and the detail 398 */ 399 protected boolean checkChartOnSubObjCodeDetails(SubObjectCodeGlobal socChangeDocument, SubObjectCodeGlobalDetail socChangeDetail, int lineNum, boolean add) { 400 boolean success = true; 401 String errorPath = KFSConstants.EMPTY_STRING; 402 403 if (StringUtils.isBlank(socChangeDetail.getChartOfAccountsCode())) { 404 return success; // just return, the existence check will balk at empty details 405 } 406 407 // first must have an actual fiscal year 408 if (socChangeDetail.getChartOfAccounts() == null) { 409 if (add) { 410 errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + "chartOfAccountsCode"; 411 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_EXIST); 412 } 413 else { 414 errorPath = KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode"; 415 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_EXIST); 416 } 417 success &= false; 418 return success; 419 } 420 421 // the two fiscal years from the document and detail must match 422 if (!socChangeDocument.getChartOfAccounts().equals(socChangeDetail.getChartOfAccounts())) { 423 if (add) { 424 errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + "chartOfAccountsCode"; 425 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_BE_SAME); 426 } 427 else { 428 errorPath = KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode"; 429 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_BE_SAME); 430 } 431 success &= false; 432 return success; 433 } 434 435 return success; 436 } 437 438 /** 439 * 440 * This checks that the chart of accounts on the {@link AccountGlobalDetail} is not empty and matches 441 * the document's chart matches the detail's chart 442 * @param socChangeDocument 443 * @param acctDetail 444 * @param lineNum 445 * @param add 446 * @return false if the chart is missing or is not the same between the doc and the detail 447 */ 448 protected boolean checkChartOnAccountDetails(SubObjectCodeGlobal socChangeDocument, AccountGlobalDetail acctDetail, int lineNum, boolean add) { 449 boolean success = true; 450 String errorPath = KFSConstants.EMPTY_STRING; 451 // first must have an actual fiscal year 452 if (acctDetail.getChartOfAccounts() == null) { 453 if (add) { 454 errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "." + "chartOfAccountsCode"; 455 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_EXIST); 456 } 457 else { 458 errorPath = KFSPropertyConstants.SUB_OBJ_CODE_CHANGE_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode"; 459 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_EXIST); 460 } 461 success &= false; 462 return success; 463 } 464 465 // the two fiscal years from the document and detail must match 466 if (!socChangeDocument.getChartOfAccounts().equals(acctDetail.getChartOfAccounts())) { 467 if (add) { 468 errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.ACCOUNT_CHANGE_DETAILS + "." + "chartOfAccountsCode"; 469 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_BE_SAME); 470 } 471 else { 472 errorPath = KFSPropertyConstants.ACCOUNT_CHANGE_DETAILS + "[" + lineNum + "]." + "chartOfAccountsCode"; 473 putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_SUBOBJECTMAINT_CHART_MUST_BE_SAME); 474 } 475 success &= false; 476 return success; 477 } 478 479 return success; 480 } 481 482 483 }