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.ld.document.validation.impl;
017
018
019 import java.util.List;
020
021 import org.apache.commons.lang.ArrayUtils;
022 import org.apache.commons.lang.StringUtils;
023 import org.kuali.kfs.coa.businessobject.Account;
024 import org.kuali.kfs.coa.businessobject.BalanceType;
025 import org.kuali.kfs.coa.businessobject.Chart;
026 import org.kuali.kfs.coa.businessobject.ObjectCode;
027 import org.kuali.kfs.coa.businessobject.ObjectType;
028 import org.kuali.kfs.coa.businessobject.SubAccount;
029 import org.kuali.kfs.coa.businessobject.SubObjectCode;
030 import org.kuali.kfs.module.ld.LaborKeyConstants;
031 import org.kuali.kfs.module.ld.batch.service.LaborAccountingCycleCachingService;
032 import org.kuali.kfs.module.ld.businessobject.LaborOriginEntry;
033 import org.kuali.kfs.module.ld.businessobject.LaborTransaction;
034 import org.kuali.kfs.sys.KFSConstants;
035 import org.kuali.kfs.sys.KFSKeyConstants;
036 import org.kuali.kfs.sys.Message;
037 import org.kuali.kfs.sys.MessageBuilder;
038 import org.kuali.kfs.sys.businessobject.SystemOptions;
039 import org.kuali.kfs.sys.context.SpringContext;
040 import org.kuali.rice.kns.service.KualiConfigurationService;
041 import org.kuali.rice.kns.util.KualiDecimal;
042 import org.kuali.rice.kns.util.ObjectUtils;
043
044 /**
045 * This class provides a set of utilities that can be used to validate a transaction in the field level.
046 */
047 public class TransactionFieldValidator {
048 private static LaborAccountingCycleCachingService accountingCycleCachingService;
049 private static KualiConfigurationService kualiConfigurationService;
050
051 /**
052 * Checks if the given transaction contains valid university fiscal year
053 *
054 * @param transaction the given transaction
055 * @return null if the university fiscal year is valid; otherwise, return error message
056 */
057 public static Message checkUniversityFiscalYear(LaborTransaction transaction) {
058
059 Integer fiscalYear = transaction.getUniversityFiscalYear();
060 if (fiscalYear == null) {
061 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_UNIV_FISCAL_YR_NOT_FOUND, Message.TYPE_FATAL);
062 }
063
064 else {
065 SystemOptions option = getAccountingCycleCachingService().getSystemOptions(((LaborOriginEntry) transaction).getUniversityFiscalYear());
066 if (ObjectUtils.isNull(option)) {
067 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_UNIV_FISCAL_YR_NOT_FOUND, fiscalYear.toString(), Message.TYPE_FATAL);
068 }
069 }
070 return null;
071 }
072
073 /**
074 * Checks if the given transaction contains valid char of accounts code
075 *
076 * @param transaction the given transaction
077 * @return null if the char of accounts code is valid; otherwise, return error message
078 */
079 public static Message checkChartOfAccountsCode(LaborTransaction transaction) {
080 String chartOfAccountsCode = transaction.getChartOfAccountsCode();
081 Chart chart = getAccountingCycleCachingService().getChart(((LaborOriginEntry) transaction).getChartOfAccountsCode());
082 if (StringUtils.isBlank(chartOfAccountsCode) || ObjectUtils.isNull(chart)) {
083 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CHART_NOT_FOUND, chartOfAccountsCode, Message.TYPE_FATAL);
084 }
085
086 if (!chart.isActive()) {
087 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CHART_NOT_ACTIVE, chartOfAccountsCode, Message.TYPE_FATAL);
088 }
089 return null;
090 }
091
092 /**
093 * Checks if the given transaction contains valid account number
094 *
095 * @param transaction the given transaction
096 * @return null if the account number is valid; otherwise, return error message
097 */
098 public static Message checkAccountNumber(LaborTransaction transaction) {
099 String accountNumber = transaction.getAccountNumber();
100 Account account = getAccountingCycleCachingService().getAccount(((LaborOriginEntry) transaction).getChartOfAccountsCode(), ((LaborOriginEntry) transaction).getAccountNumber());
101 if (StringUtils.isBlank(accountNumber) || ObjectUtils.isNull(account)) {
102 String chartOfAccountsCode = transaction.getChartOfAccountsCode();
103 String accountKey = chartOfAccountsCode + "-" + accountNumber;
104 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ACCOUNT_NOT_FOUND, accountKey, Message.TYPE_FATAL);
105 }
106 return null;
107 }
108
109 /**
110 * Checks if the given transaction contains valid sub account number
111 *
112 * @param transaction the given transaction
113 * @return null if the sub account number is valid; otherwise, return error message
114 */
115 public static Message checkSubAccountNumber(LaborTransaction transaction) {
116 return checkSubAccountNumber(transaction, null);
117 }
118
119 /**
120 * Checks if the given transaction contains valid sub account number
121 *
122 * @param transaction the given transaction
123 * @param exclusiveDocumentTypeCode inactive sub account can be OK if the document type of the given transaction is
124 * exclusiveDocumentTypeCode
125 * @return null if the sub account number is valid; otherwise, return error message
126 */
127 public static Message checkSubAccountNumber(LaborTransaction transaction, String exclusiveDocumentTypeCode) {
128 String subAccountNumber = transaction.getSubAccountNumber();
129 String chartOfAccountsCode = transaction.getChartOfAccountsCode();
130 String accountNumber = transaction.getAccountNumber();
131 String documentTypeCode = transaction.getFinancialDocumentTypeCode();
132 String subAccountKey = chartOfAccountsCode + "-" + accountNumber + "-" + subAccountNumber;
133 SubAccount subAccount = getAccountingCycleCachingService().getSubAccount(((LaborOriginEntry) transaction).getChartOfAccountsCode(), ((LaborOriginEntry) transaction).getAccountNumber(), ((LaborOriginEntry) transaction).getSubAccountNumber());
134
135 if (StringUtils.isBlank(subAccountNumber)) {
136 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SUB_ACCOUNT_NOT_FOUND, subAccountKey, Message.TYPE_FATAL);
137 }
138
139 if (!KFSConstants.getDashSubAccountNumber().equals(subAccountNumber)) {
140 if (ObjectUtils.isNull(subAccount)) {
141 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SUB_ACCOUNT_NOT_FOUND, subAccountKey, Message.TYPE_FATAL);
142 }
143
144 if (!StringUtils.equals(documentTypeCode, exclusiveDocumentTypeCode)) {
145 if (!subAccount.isActive()) {
146 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SUB_ACCOUNT_NOT_ACTIVE, subAccountKey, Message.TYPE_FATAL);
147 }
148 }
149 }
150 return null;
151 }
152
153 /**
154 * Checks if the given transaction contains valid account number
155 *
156 * @param transaction the given transaction
157 * @return null if the account number is valid; otherwise, return error message
158 */
159 public static Message checkFinancialObjectCode(LaborTransaction transaction) {
160 String objectCode = transaction.getFinancialObjectCode();
161 if (StringUtils.isBlank(objectCode)) {
162 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_OBJECT_CODE_EMPTY, Message.TYPE_FATAL);
163 }
164
165 Integer fiscalYear = transaction.getUniversityFiscalYear();
166 String chartOfAccountsCode = transaction.getChartOfAccountsCode();
167 String objectCodeKey = fiscalYear + "-" + chartOfAccountsCode + "-" + objectCode;
168 ObjectCode financialObject = getAccountingCycleCachingService().getObjectCode(((LaborOriginEntry) transaction).getUniversityFiscalYear(), ((LaborOriginEntry) transaction).getChartOfAccountsCode(), ((LaborOriginEntry) transaction).getFinancialObjectCode());
169
170 //do we need it?
171 transaction.refreshNonUpdateableReferences();
172
173 if (ObjectUtils.isNull(financialObject)) {
174 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_OBJECT_CODE_NOT_FOUND, objectCodeKey, Message.TYPE_FATAL);
175 }
176 return null;
177 }
178
179 /**
180 * Checks if the given transaction contains valid sub object code
181 *
182 * @param transaction the given transaction
183 * @return null if the sub object code is valid; otherwise, return error message
184 */
185 public static Message checkFinancialSubObjectCode(LaborTransaction transaction) {
186 Integer fiscalYear = transaction.getUniversityFiscalYear();
187 String chartOfAccountsCode = transaction.getChartOfAccountsCode();
188 String objectCode = transaction.getFinancialObjectCode();
189 String subObjectCode = transaction.getFinancialSubObjectCode();
190
191 String subObjectCodeKey = fiscalYear + "-" + chartOfAccountsCode + "-" + objectCode + "-" + subObjectCode;
192 if (StringUtils.isBlank(subObjectCode)) {
193 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SUB_OBJECT_CODE_NOT_BE_NULL, subObjectCodeKey, Message.TYPE_FATAL);
194 }
195 SubObjectCode financialSubObject = getAccountingCycleCachingService().getSubObjectCode(((LaborOriginEntry) transaction).getUniversityFiscalYear(), ((LaborOriginEntry) transaction).getChartOfAccountsCode(), ((LaborOriginEntry) transaction).getAccountNumber(), ((LaborOriginEntry) transaction).getFinancialObjectCode(), ((LaborOriginEntry) transaction).getFinancialSubObjectCode());
196 if (!KFSConstants.getDashFinancialSubObjectCode().equals(subObjectCode)) {
197 if (ObjectUtils.isNull(financialSubObject)) {
198 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SUB_OBJECT_CODE_NOT_BE_NULL, subObjectCodeKey, Message.TYPE_FATAL);
199 }
200 }
201 return null;
202 }
203
204 /**
205 * Checks if the given transaction contains valid balance type code
206 *
207 * @param transaction the given transaction
208 * @return null if the balance type code is valid; otherwise, return error message
209 */
210 public static Message checkFinancialBalanceTypeCode(LaborTransaction transaction) {
211 String balanceTypeCode = transaction.getFinancialBalanceTypeCode();
212 BalanceType balanceType = getAccountingCycleCachingService().getBalanceType(((LaborOriginEntry) transaction).getFinancialBalanceTypeCode());
213 if (StringUtils.isBlank(balanceTypeCode) || ObjectUtils.isNull(balanceType)) {
214 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_BALANCE_TYPE_NOT_FOUND, balanceTypeCode, Message.TYPE_FATAL);
215 }
216 return null;
217 }
218
219 /**
220 * Checks if the given transaction contains valid object type code
221 *
222 * @param transaction the given transaction
223 * @return null if the object type code is valid; otherwise, return error message
224 */
225 public static Message checkFinancialObjectTypeCode(LaborTransaction transaction) {
226 String objectTypeCode = transaction.getFinancialObjectTypeCode();
227 ObjectType objectType = getAccountingCycleCachingService().getObjectType(((LaborOriginEntry) transaction).getFinancialObjectTypeCode());
228 if (StringUtils.isBlank(objectTypeCode) || ObjectUtils.isNull(objectType)) {
229 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_OBJECT_TYPE_NOT_FOUND, objectTypeCode, Message.TYPE_FATAL);
230 }
231 return null;
232 }
233
234 /**
235 * Checks if the given transaction contains university fiscal period code
236 *
237 * @param transaction the given transaction
238 * @return null if the university fiscal period code is valid; otherwise, return error message
239 */
240 public static Message checkUniversityFiscalPeriodCode(LaborTransaction transaction) {
241 String fiscalPeriodCode = transaction.getUniversityFiscalPeriodCode();
242 if (StringUtils.isBlank(fiscalPeriodCode)) {
243 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ACCOUNTING_PERIOD_NOT_FOUND, fiscalPeriodCode, Message.TYPE_FATAL);
244 }
245 return null;
246 }
247
248 /**
249 * Checks if the given transaction contains document type code
250 *
251 * @param transaction the given transaction
252 * @return null if the document type code is valid; otherwise, return error message
253 */
254 public static Message checkFinancialDocumentTypeCode(LaborTransaction transaction) {
255 if (StringUtils.isBlank(transaction.getFinancialDocumentTypeCode()) || !getAccountingCycleCachingService().isCurrentActiveAccountingDocumentType(transaction.getFinancialDocumentTypeCode())) {
256 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DOCUMENT_TYPE_NOT_FOUND, transaction.getFinancialDocumentTypeCode(), Message.TYPE_FATAL);
257 }
258 return null;
259 }
260
261 /**
262 * Checks if the given transaction contains document number
263 *
264 * @param transaction the given transaction
265 * @return null if the document number is valid; otherwise, return error message
266 */
267 public static Message checkFinancialDocumentNumber(LaborTransaction transaction) {
268 String documentNumber = transaction.getDocumentNumber();
269 if (StringUtils.isBlank(documentNumber)) {
270 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DOCUMENT_NUMBER_REQUIRED, Message.TYPE_FATAL);
271 }
272 return null;
273 }
274
275 /**
276 * Checks if the given transaction contains transaction sequence number
277 *
278 * @param transaction the given transaction
279 * @return null if the transaction sequence number is valid; otherwise, return error message
280 */
281 // Don't need to check SequenceNumber because it sets in each poster (LaborLedgerEntryPoster and LaborGLLedgerEntryPoster), so commented out
282 // public static Message checkTransactionLedgerEntrySequenceNumber(LaborTransaction transaction) {
283 // Integer sequenceNumber = transaction.getTransactionLedgerEntrySequenceNumber();
284 // if (sequenceNumber == null) {
285 // return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SEQUENCE_NUMBER_NOT_BE_NULL, Message.TYPE_FATAL);
286 // }
287 // return null;
288 // }
289
290 /**
291 * Checks if the given transaction contains debit credit code
292 *
293 * @param transaction the given transaction
294 * @return null if the debit credit code is valid; otherwise, return error message
295 */
296 public static Message checkTransactionDebitCreditCode(LaborTransaction transaction) {
297 String[] validDebitCreditCode = { KFSConstants.GL_BUDGET_CODE, KFSConstants.GL_CREDIT_CODE, KFSConstants.GL_DEBIT_CODE };
298 String debitCreditCode = transaction.getTransactionDebitCreditCode();
299 if (debitCreditCode == null || !ArrayUtils.contains(validDebitCreditCode, debitCreditCode)) {
300 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DEDIT_CREDIT_CODE_NOT_BE_NULL, Message.TYPE_FATAL);
301 } else if (transaction.getBalanceType().isFinancialOffsetGenerationIndicator() && !KFSConstants.GL_DEBIT_CODE.equals(transaction.getTransactionDebitCreditCode()) && !KFSConstants.GL_CREDIT_CODE.equals(transaction.getTransactionDebitCreditCode())) {
302 return new Message(getKualiConfigurationService().getPropertyString(KFSKeyConstants.MSG_DEDIT_CREDIT_CODE_MUST_BE) + " '" + KFSConstants.GL_DEBIT_CODE + " or " + KFSConstants.GL_CREDIT_CODE + getKualiConfigurationService().getPropertyString(KFSKeyConstants.MSG_FOR_BALANCE_TYPE), Message.TYPE_FATAL);
303 }
304 return null;
305 }
306
307 /**
308 * Checks if the given transaction contains system origination code
309 *
310 * @param transaction the given transaction
311 * @return null if the system origination code is valid; otherwise, return error message
312 */
313 public static Message checkFinancialSystemOriginationCode(LaborTransaction transaction) {
314 String originationCode = transaction.getFinancialSystemOriginationCode();
315 if (StringUtils.isBlank(originationCode)) {
316 return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ORIGIN_CODE_NOT_FOUND, Message.TYPE_FATAL);
317 }
318 return null;
319 }
320
321 /**
322 * Checks if the given transaction contains the posteable period code
323 *
324 * @param transaction the given transaction
325 * @param unpostableperidCodes the list of unpostable period code
326 * @return null if the perid code of the transaction is not in unpostableperidCodes; otherwise, return error message
327 */
328 public static Message checkPostablePeridCode(LaborTransaction transaction, List<String> unpostableperidCodes) {
329 String periodCode = transaction.getUniversityFiscalPeriodCode();
330 if (unpostableperidCodes.contains(periodCode)) {
331 return MessageBuilder.buildMessage(LaborKeyConstants.ERROR_UNPOSTABLE_PERIOD_CODE, periodCode, Message.TYPE_FATAL);
332 }
333 return null;
334 }
335
336 /**
337 * Checks if the given transaction contains the posteable balance type code
338 *
339 * @param transaction the given transaction
340 * @param unpostableBalanceTypeCodes the list of unpostable balance type codes
341 * @return null if the balance type code of the transaction is not in unpostableBalanceTypeCodes; otherwise, return error
342 * message
343 */
344 public static Message checkPostableBalanceTypeCode(LaborTransaction transaction, List<String> unpostableBalanceTypeCodes) {
345 String balanceTypeCode = transaction.getFinancialBalanceTypeCode();
346 if (unpostableBalanceTypeCodes.contains(balanceTypeCode)) {
347 return MessageBuilder.buildMessage(LaborKeyConstants.ERROR_UNPOSTABLE_BALANCE_TYPE, balanceTypeCode, Message.TYPE_FATAL);
348 }
349 return null;
350 }
351
352 /**
353 * Checks if the transaction amount of the given transaction is ZERO
354 *
355 * @param transaction the given transaction
356 * @return null if the transaction amount is not ZERO or null; otherwise, return error message
357 */
358 public static Message checkZeroTotalAmount(LaborTransaction transaction) {
359 KualiDecimal amount = transaction.getTransactionLedgerEntryAmount();
360 if (amount == null || amount.isZero()) {
361 return MessageBuilder.buildMessage(LaborKeyConstants.ERROR_ZERO_TOTAL_AMOUNT, Message.TYPE_FATAL);
362 }
363 return null;
364 }
365
366 /**
367 * Checks if the given transaction contains the valid employee id
368 *
369 * @param transaction the given transaction
370 * @param unpostableObjectCodes the list of unpostable object codes
371 * @return null if the object code of the transaction is not in unpostableObjectCodes; otherwise, return error message
372 */
373 public static Message checkEmplid(LaborTransaction transaction) {
374 String emplid = transaction.getEmplid();
375 if (StringUtils.isBlank(emplid)) {
376 return MessageBuilder.buildMessage(LaborKeyConstants.MISSING_EMPLOYEE_ID, Message.TYPE_FATAL);
377 }
378 return null;
379 }
380
381 /**
382 * When in Rome... This method checks if the encumbrance update code is valid
383 * @param transaction the transaction to check
384 * @return a Message if the encumbrance update code is not valid, or null if all is well
385 */
386 public static Message checkEncumbranceUpdateCode(LaborTransaction transaction) {
387 // The encumbrance update code can only be space, N, R or D. Nothing else
388 if ((StringUtils.isNotBlank(transaction.getTransactionEncumbranceUpdateCode())) && (!" ".equals(transaction.getTransactionEncumbranceUpdateCode())) && (!KFSConstants.ENCUMB_UPDT_NO_ENCUMBRANCE_CD.equals(transaction.getTransactionEncumbranceUpdateCode())) && (!KFSConstants.ENCUMB_UPDT_REFERENCE_DOCUMENT_CD.equals(transaction.getTransactionEncumbranceUpdateCode())) && (!KFSConstants.ENCUMB_UPDT_DOCUMENT_CD.equals(transaction.getTransactionEncumbranceUpdateCode()))) {
389 return new Message("Invalid Encumbrance Update Code (" + transaction.getTransactionEncumbranceUpdateCode() + ")", Message.TYPE_FATAL);
390 }
391 return null;
392 }
393
394 static LaborAccountingCycleCachingService getAccountingCycleCachingService() {
395 if (accountingCycleCachingService == null) {
396 accountingCycleCachingService = SpringContext.getBean(LaborAccountingCycleCachingService.class);
397 }
398 return accountingCycleCachingService;
399 }
400
401 static KualiConfigurationService getKualiConfigurationService() {
402 if (kualiConfigurationService == null) {
403 kualiConfigurationService = SpringContext.getBean(KualiConfigurationService.class);
404 }
405 return kualiConfigurationService;
406 }
407 }