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.sys.KFSKeyConstants;
023    import org.kuali.kfs.sys.document.validation.impl.KfsMaintenanceDocumentRuleBase;
024    import org.kuali.rice.kns.util.GlobalVariables;
025    
026    /**
027     * This class contains common Business Rule functionality for Global Documents.
028     */
029    public class GlobalDocumentRuleBase extends KfsMaintenanceDocumentRuleBase {
030    
031        /**
032         * Constructs a GlobalDocumentRuleBase.java.
033         */
034        public GlobalDocumentRuleBase() {
035            super();
036        }
037    
038        /**
039         * This method checks whether the set of Account Change Detail records on this document all are under the same Chart of
040         * Accounts. It will set the appropriate field error if it did fail, and return the result.
041         * 
042         * @param accountGlobalDetails
043         * @return True if the test passed with no errors, False if any errors occurred.
044         */
045        protected boolean checkOnlyOneChartErrorWrapper(List<AccountGlobalDetail> accountGlobalDetails) {
046            CheckOnlyOneChartResult result = checkOnlyOneChart(accountGlobalDetails);
047            if (!result.success) {
048                putFieldError("accountGlobalDetails[" + result.firstLineNumber + "].chartOfAccountsCode", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_ONE_CHART_ONLY);
049                putFieldError("accountGlobalDetails[" + result.failedLineNumber + "].chartOfAccountsCode", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_ONE_CHART_ONLY);
050            }
051            return result.success;
052        }
053    
054        /**
055         * This method checks whether the set of Account Change Detail records on this document all are under the same Chart of
056         * Accounts. It will return a failed CheckOnlyOneChartResult if so. Note that this method doesnt actually set any errors, it
057         * just returns whether or not the test succeeded, and where it failed if it failed.
058         * 
059         * @param accountGlobalDetails The popualted accountGlobalDocument to test.
060         * @return A populated CheckOnlyOneChartResult object. This will contain whether the test succeeded or failed, and if failed,
061         *         what lines the failures occurred on.
062         */
063        protected CheckOnlyOneChartResult checkOnlyOneChart(List<AccountGlobalDetail> accountGlobalDetails) {
064            // if there is not enough information to do the test, then exit happily with no failure
065            if (accountGlobalDetails == null) {
066                return new CheckOnlyOneChartResult(true);
067            }
068            if (accountGlobalDetails.isEmpty()) {
069                return new CheckOnlyOneChartResult(true);
070            }
071    
072            // test to see if there is more than one chart listed, ignores blank chartcodes
073            int compareLineNumber = 0;
074            int firstChartLineNumber = 0;
075            String firstChart = "";
076            for (AccountGlobalDetail account : accountGlobalDetails) {
077                if (StringUtils.isBlank(firstChart)) {
078                    if (StringUtils.isNotBlank(account.getChartOfAccountsCode())) {
079                        firstChart = account.getChartOfAccountsCode();
080                        firstChartLineNumber = compareLineNumber;
081                    }
082                }
083                else {
084                    if (StringUtils.isNotBlank(account.getChartOfAccountsCode())) {
085                        if (!firstChart.equalsIgnoreCase(account.getChartOfAccountsCode())) {
086                            return new CheckOnlyOneChartResult(false, firstChartLineNumber, compareLineNumber);
087                        }
088                    }
089                }
090                compareLineNumber++;
091            }
092            return new CheckOnlyOneChartResult(true);
093        }
094    
095        /**
096         * This class is used internally to represent the result of the CheckOnlyOneChart method.
097         */
098        protected class CheckOnlyOneChartResult {
099    
100            public int firstLineNumber;
101            public int failedLineNumber;
102            public boolean success;
103    
104            /**
105             * Constructs a CheckOnlyOneChartResult
106             */
107            public CheckOnlyOneChartResult() {
108                firstLineNumber = -1;
109                failedLineNumber = -1;
110                success = true;
111            }
112    
113            /**
114             * Constructs a CheckOnlyOneChartResult
115             * 
116             * @param success
117             */
118            public CheckOnlyOneChartResult(boolean success) {
119                this();
120                this.success = success;
121            }
122    
123            /**
124             * Constructs a CheckOnlyOneChartResult
125             * 
126             * @param success
127             * @param firstLineNumber
128             * @param failedLineNumber
129             */
130            public CheckOnlyOneChartResult(boolean success, int firstLineNumber, int failedLineNumber) {
131                this.success = success;
132                this.firstLineNumber = firstLineNumber;
133                this.failedLineNumber = failedLineNumber;
134            }
135    
136        }
137    
138        /**
139         * This method tests whether the line being added has a different Chart of Accounts Code from any of the existing lines. It will
140         * set an Error and return false if this is the case.
141         * 
142         * @param newAccountLine
143         * @param accountGlobalDetails
144         * @return True if the line being added has the exact same chart as all the existing lines, False if not.
145         */
146        protected boolean checkOnlyOneChartAddLineErrorWrapper(AccountGlobalDetail newAccountLine, List<AccountGlobalDetail> accountGlobalDetails) {
147            boolean success = checkOnlyOneChartAddLine(newAccountLine, accountGlobalDetails);
148            if (!success) {
149                // putGlobalError(KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_ONE_CHART_ONLY_ADDNEW);
150                // TODO: KULCOA-1091 Need to add this error to the add line, but this doesn't work right, as the
151                // error message comes out at the bottom, and the field doesn't get highlighted.
152                // putFieldError("newAccountGlobalDetail.chartOfAccountsCode",
153                // KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_ONE_CHART_ONLY);
154    
155                // added to prevent error from showing at the top of the document, but rather in the Account Detail Edit section
156                GlobalVariables.getMessageMap().putError("chartOfAccountsCode", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_ONE_CHART_ONLY_ADDNEW);
157            }
158            return success;
159        }
160    
161        /**
162         * This method tests whether a new line can be added, based on the rule that says all the accounts being used must belong to the
163         * same chart. If the line being added differs from any existing line's Chart code, it will return false. Note that this
164         * document does not actually set any errors, it just reports success or failure.
165         * 
166         * @param newAccountLine
167         * @param accountGlobalDetails
168         * @return True if no errors are found, False if the line being added has a different Chart code than any of the existing lines.
169         */
170        protected boolean checkOnlyOneChartAddLine(AccountGlobalDetail newAccountLine, List<AccountGlobalDetail> accountGlobalDetails) {
171            if (newAccountLine == null || accountGlobalDetails == null) {
172                return true;
173            }
174            if (accountGlobalDetails.isEmpty()) {
175                return true;
176            }
177            String newChart = newAccountLine.getChartOfAccountsCode();
178            if (StringUtils.isBlank(newChart)) {
179                return true;
180            }
181            for (AccountGlobalDetail account : accountGlobalDetails) {
182                if (StringUtils.isNotBlank(account.getChartOfAccountsCode())) {
183                    if (!newChart.equalsIgnoreCase(account.getChartOfAccountsCode())) {
184                        return false;
185                    }
186                }
187            }
188            return true;
189        }
190    
191    }