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 }