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    }