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.sql.Timestamp;
019 import java.util.Calendar;
020 import java.util.Collection;
021 import java.util.HashMap;
022 import java.util.Map;
023
024 import org.apache.commons.lang.StringUtils;
025 import org.apache.commons.lang.time.DateUtils;
026 import org.kuali.kfs.coa.businessobject.Account;
027 import org.kuali.kfs.coa.businessobject.AccountDelegate;
028 import org.kuali.kfs.sys.KFSConstants;
029 import org.kuali.kfs.sys.KFSKeyConstants;
030 import org.kuali.kfs.sys.KFSPropertyConstants;
031 import org.kuali.kfs.sys.context.SpringContext;
032 import org.kuali.kfs.sys.document.service.FinancialSystemDocumentTypeService;
033 import org.kuali.rice.kim.bo.Person;
034 import org.kuali.rice.kns.document.MaintenanceDocument;
035 import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
036 import org.kuali.rice.kns.util.KualiDecimal;
037 import org.kuali.rice.kns.util.ObjectUtils;
038
039 /**
040 * Validates content of a <code>{@link AccountDelegate}</code> maintenance document upon triggering of a approve, save, or route
041 * event.
042 */
043 public class DelegateRule extends MaintenanceDocumentRuleBase {
044
045 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DelegateRule.class);
046
047 protected AccountDelegate oldDelegate;
048 protected AccountDelegate newDelegate;
049
050 /**
051 * Constructs a DelegateRule.java.
052 */
053 public DelegateRule() {
054 super();
055 }
056
057 /**
058 * This runs specific rules that are called when a document is saved:
059 * <ul>
060 * <li>{@link DelegateRule#checkSimpleRules()}</li>
061 * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
062 * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
063 * </ul>
064 *
065 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
066 * @return doesn't fail on save, even if sub-rules fail
067 */
068 @Override
069 protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
070
071 LOG.info("Entering processCustomSaveDocumentBusinessRules()");
072 setupConvenienceObjects(document);
073
074 // check simple rules
075 checkSimpleRules();
076
077 // disallow more than one PrimaryRoute for a given Chart/Account/Doctype
078 checkOnlyOnePrimaryRoute(document);
079
080 // delegate user must be Active and Professional
081 checkDelegateUserRules(document);
082
083 return true;
084 }
085
086 /**
087 * This runs specific rules that are called when a document is routed:
088 * <ul>
089 * <li>{@link DelegateRule#checkSimpleRules()}</li>
090 * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
091 * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
092 * </ul>
093 *
094 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
095 * @return fails if sub-rules fail
096 */
097 protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
098 LOG.info("Entering processCustomRouteDocumentBusinessRules()");
099
100 setupConvenienceObjects(document);
101
102 // check simple rules
103 boolean success = checkSimpleRules();
104
105 // disallow more than one PrimaryRoute for a given Chart/Account/Doctype
106 success &= checkOnlyOnePrimaryRoute(document);
107
108 // delegate user must be Active and Professional
109 success &= checkDelegateUserRules(document);
110
111 return success;
112 }
113
114 /**
115 * This runs specific rules that are called when a document is approved:
116 * <ul>
117 * <li>{@link DelegateRule#checkSimpleRules()}</li>
118 * <li>{@link DelegateRule#checkOnlyOnePrimaryRoute(MaintenanceDocument)}</li>
119 * <li>{@link DelegateRule#checkDelegateUserRules(MaintenanceDocument)}</li>
120 * </ul>
121 *
122 * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
123 */
124 protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
125
126 boolean success = true;
127
128 LOG.info("Entering processCustomApproveDocumentBusinessRules()");
129 setupConvenienceObjects(document);
130
131 // check simple rules
132 success &= checkSimpleRules();
133
134 success &= checkOnlyOnePrimaryRoute(document);
135
136 // delegate user must be Active and Professional
137 success &= checkDelegateUserRules(document);
138
139 return success;
140 }
141
142 /**
143 * This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and
144 * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
145 * all sub-objects from the DB by their primary keys, if available.
146 *
147 * @param document - the maintenanceDocument being evaluated
148 */
149 protected void setupConvenienceObjects(MaintenanceDocument document) {
150
151 // setup oldAccount convenience objects, make sure all possible sub-objects are populated
152 oldDelegate = (AccountDelegate) super.getOldBo();
153
154 // setup newAccount convenience objects, make sure all possible sub-objects are populated
155 newDelegate = (AccountDelegate) super.getNewBo();
156 }
157
158
159 /**
160 * This checks to see if
161 * <ul>
162 * <li>the delegate start date is valid and they are active</li>
163 * <li>from amount is >= 0</li>
164 * <li>to amount cannot be empty when from amount is filled out</li>
165 * <li>to amount is >= from amount</li>
166 * <li>account cannot be closed</li>
167 * </ul>
168 *
169 * @return
170 */
171 protected boolean checkSimpleRules() {
172 boolean success = true;
173
174 Map<String, String> fieldValues = new HashMap<String, String>();
175 fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, newDelegate.getChartOfAccountsCode());
176 fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, newDelegate.getAccountNumber());
177
178 int accountExist = getBoService().countMatching(Account.class, fieldValues);
179 if (accountExist<=0) {
180 putFieldError(KFSPropertyConstants.ACCOUNT_NUMBER, KFSKeyConstants.ERROR_EXISTENCE, newDelegate.getAccountNumber());
181 success &= false;
182 }
183
184 // start date must be greater than or equal to today if active
185 boolean newActive = newDelegate.isActive();
186 if ((ObjectUtils.isNotNull(newDelegate.getAccountDelegateStartDate())) && newActive) {
187 Timestamp today = getDateTimeService().getCurrentTimestamp();
188 today.setTime(DateUtils.truncate(today, Calendar.DAY_OF_MONTH).getTime());
189 if (newDelegate.getAccountDelegateStartDate().before(today)) {
190 putFieldError(KFSPropertyConstants.ACCOUNT_DELEGATE_START_DATE, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_STARTDATE_IN_PAST);
191 success &= false;
192 }
193 }
194
195 // FROM amount must be >= 0 (may not be negative)
196 KualiDecimal fromAmount = newDelegate.getFinDocApprovalFromThisAmt();
197 if (ObjectUtils.isNotNull(fromAmount)) {
198 if (fromAmount.isLessThan(KualiDecimal.ZERO)) {
199 putFieldError(KFSPropertyConstants.FIN_DOC_APPROVAL_FROM_THIS_AMT, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_FROM_AMOUNT_NONNEGATIVE);
200 success &= false;
201 }
202 }
203
204 // TO amount must be >= FROM amount or Zero
205 KualiDecimal toAmount = newDelegate.getFinDocApprovalToThisAmount();
206 if (ObjectUtils.isNotNull(toAmount) && !toAmount.equals(KualiDecimal.ZERO)) {
207 // case if FROM amount is non-null and positive, disallow TO amount being less
208 if (fromAmount != null && toAmount.isLessThan(fromAmount)) {
209 putFieldError(KFSPropertyConstants.FIN_DOC_APPROVAL_TO_THIS_AMOUNT, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
210 success &= false;
211 } else if (toAmount.isLessThan(KualiDecimal.ZERO)) {
212 putFieldError(KFSPropertyConstants.FIN_DOC_APPROVAL_TO_THIS_AMOUNT, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
213 success &= false;
214 }
215 }
216
217 // do we have a good document type?
218 final FinancialSystemDocumentTypeService documentService = SpringContext.getBean(FinancialSystemDocumentTypeService.class);
219 if (!documentService.isFinancialSystemDocumentType(newDelegate.getFinancialDocumentTypeCode())) {
220 putFieldError(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_INVALID_DOC_TYPE, new String[] { newDelegate.getFinancialDocumentTypeCode(), KFSConstants.ROOT_DOCUMENT_TYPE });
221 success = false;
222 }
223
224 return success;
225 }
226
227 /**
228 * This checks to see if there is already a record for the primary route
229 *
230 * @param document
231 * @return false if there is a record
232 */
233 protected boolean checkOnlyOnePrimaryRoute(MaintenanceDocument document) {
234
235 boolean success = true;
236 boolean checkDb = false;
237 boolean newPrimary;
238 boolean newActive;
239 boolean blockingDocumentExists;
240
241 // exit out immediately if this doc is not requesting a primary route
242 newPrimary = newDelegate.isAccountsDelegatePrmrtIndicator();
243 if (!newPrimary) {
244 return success;
245 }
246
247 // exit if new document not active
248 newActive = newDelegate.isActive();
249 if (!newActive) {
250 return success;
251 }
252
253 // if its a new document, we are only interested if they have chosen this one
254 // to be a primary route
255 if (document.isNew()) {
256 if (newPrimary) {
257 checkDb = true;
258 }
259 }
260
261 // handle an edit, where all we care about is that someone might change it
262 // from NOT a primary TO a primary
263 if (document.isEdit()) {
264 boolean oldPrimary = oldDelegate.isAccountsDelegatePrmrtIndicator();
265 if (!oldPrimary && newPrimary) {
266 checkDb = true;
267 }
268 }
269
270 // if we dont want to check the db for another primary, then exit
271 if (!checkDb) {
272 return success;
273 }
274
275 // if a primary already exists for a document type (including ALL), throw an error. However, current business rules
276 // should allow a primary for other document types, even if a primary for ALL already exists.
277
278 Map whereMap = new HashMap();
279 whereMap.put("chartOfAccountsCode", newDelegate.getChartOfAccountsCode());
280 whereMap.put("accountNumber", newDelegate.getAccountNumber());
281 whereMap.put("accountsDelegatePrmrtIndicator", Boolean.valueOf(true));
282 whereMap.put("financialDocumentTypeCode", newDelegate.getFinancialDocumentTypeCode());
283 whereMap.put("active", Boolean.valueOf(true));
284
285 // find all the matching records
286 Collection primaryRoutes = getBoService().findMatching(AccountDelegate.class, whereMap);
287
288 // if there is at least one result, then this business rule is tripped
289 if (primaryRoutes.size() > 0) {
290 putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_PRIMARY_ROUTE_ALREADY_EXISTS_FOR_DOCTYPE);
291 success &= false;
292 }
293
294 return success;
295 }
296
297 /**
298 * This checks to see if the user is valid and active for this module
299 *
300 * @param document
301 * @return false if this user is not valid or active for being a delegate
302 */
303 protected boolean checkDelegateUserRules(MaintenanceDocument document) {
304 boolean success = true;
305 final Person accountDelegate = newDelegate.getAccountDelegate();
306
307 // if the user doesn't exist, then do nothing, it'll fail the existence test elsewhere
308 if (ObjectUtils.isNull(accountDelegate)) {
309 return success;
310 }
311
312 // if the document is inactivating an account delegate, don't bother validating the user
313 if (document.getOldMaintainableObject() != null && document.getOldMaintainableObject().getBusinessObject() != null && ((AccountDelegate)document.getOldMaintainableObject().getBusinessObject()).isActive() && !((AccountDelegate)document.getNewMaintainableObject().getBusinessObject()).isActive()) {
314 return success;
315 }
316
317 if (StringUtils.isBlank(accountDelegate.getEntityId()) || !getDocumentHelperService().getDocumentAuthorizer(document).isAuthorized(document, KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE, accountDelegate.getPrincipalId())) {
318 super.putFieldError("accountDelegate.principalName", KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[] {accountDelegate.getName(), KFSConstants.ParameterNamespaces.CHART, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE});
319 success = false;
320 }
321 return success;
322 }
323 }